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 | |
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
708 files changed, 30667 insertions, 14959 deletions
diff --git a/android/accessibilityservice/AccessibilityService.java b/android/accessibilityservice/AccessibilityService.java index a558d685..8824643d 100644 --- a/android/accessibilityservice/AccessibilityService.java +++ b/android/accessibilityservice/AccessibilityService.java @@ -358,6 +358,11 @@ public abstract class AccessibilityService extends Service { */ public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7; + /** + * Action to lock the screen + */ + public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; + private static final String LOG_TAG = "AccessibilityService"; /** diff --git a/android/accessibilityservice/AccessibilityServiceInfo.java b/android/accessibilityservice/AccessibilityServiceInfo.java index 06a9b067..e0d60cd0 100644 --- a/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/android/accessibilityservice/AccessibilityServiceInfo.java @@ -16,6 +16,8 @@ package android.accessibilityservice; +import static android.content.pm.PackageManager.FEATURE_FINGERPRINT; + import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; @@ -47,8 +49,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import static android.content.pm.PackageManager.FEATURE_FINGERPRINT; - /** * This class describes an {@link AccessibilityService}. The system notifies an * {@link AccessibilityService} for {@link android.view.accessibility.AccessibilityEvent}s @@ -554,7 +554,7 @@ public class AccessibilityServiceInfo implements Parcelable { } /** - * Updates the properties that an AccessibilitySerivice can change dynamically. + * Updates the properties that an AccessibilityService can change dynamically. * * @param other The info from which to update the properties. * 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. diff --git a/android/arch/paging/BoundedDataSource.java b/android/arch/paging/BoundedDataSource.java deleted file mode 100644 index 06564907..00000000 --- a/android/arch/paging/BoundedDataSource.java +++ /dev/null @@ -1,80 +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.arch.paging; - -import android.support.annotation.Nullable; -import android.support.annotation.RestrictTo; -import android.support.annotation.WorkerThread; - -import java.util.ArrayList; -import java.util.List; - -/** - * Simplest data source form that provides all of its data through a single loadRange() method. - * <p> - * Requires that your data resides in positions <code>0</code> through <code>N</code>, where - * <code>N</code> is the value returned from {@link #countItems()}. You must return the exact number - * requested, so that the data as returned can be safely prepended/appended to what has already - * been loaded. - * <p> - * For more flexibility in how many items to load, or to avoid counting your data source, override - * {@link PositionalDataSource} directly. - * - * @param <Value> Value type returned by the data source. - * - * @hide - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -public abstract class BoundedDataSource<Value> extends PositionalDataSource<Value> { - /** - * Called to load items at from the specified position range. - * - * @param startPosition Index of first item to load. - * @param loadCount Exact number of items to load. Returning a different number will cause - * an exception to be thrown. - * @return List of loaded items. Null if the BoundedDataSource is no longer valid, and should - * not be queried again. - */ - @WorkerThread - @Nullable - public abstract List<Value> loadRange(int startPosition, int loadCount); - - @WorkerThread - @Nullable - @Override - public List<Value> loadAfter(int startIndex, int pageSize) { - return loadRange(startIndex, pageSize); - } - - @WorkerThread - @Nullable - @Override - public List<Value> loadBefore(int startIndex, int pageSize) { - if (startIndex < 0) { - return new ArrayList<>(); - } - int loadSize = Math.min(pageSize, startIndex + 1); - startIndex = startIndex - loadSize + 1; - List<Value> result = loadRange(startIndex, loadSize); - if (result != null) { - if (result.size() != loadSize) { - throw new IllegalStateException("invalid number of items returned."); - } - } - return result; - } -} diff --git a/android/arch/paging/ContiguousDataSource.java b/android/arch/paging/ContiguousDataSource.java index 414c4ffe..38b7cc04 100644 --- a/android/arch/paging/ContiguousDataSource.java +++ b/android/arch/paging/ContiguousDataSource.java @@ -19,7 +19,7 @@ package android.arch.paging; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import java.util.List; +import java.util.concurrent.Executor; abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> { @Override @@ -27,36 +27,15 @@ abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> { return true; } - abstract void loadInitial(Key key, int initialLoadSize, boolean enablePlaceholders, - @NonNull PageResult.Receiver<Key, Value> receiver); + abstract void loadInitial(@Nullable Key key, int initialLoadSize, + int pageSize, boolean enablePlaceholders, + @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver); - void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize, - @NonNull PageResult.Receiver<Key, Value> receiver) { - if (!isInvalid()) { - List<Value> list = loadAfterImpl(currentEndIndex, currentEndItem, pageSize); + abstract void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize, + @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver); - if (list != null && !isInvalid()) { - receiver.postOnPageResult(new PageResult<>( - PageResult.APPEND, new Page<Key, Value>(list), 0, 0, 0)); - return; - } - } - receiver.postOnPageResult(new PageResult<Key, Value>(PageResult.APPEND)); - } - - void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize, - @NonNull PageResult.Receiver<Key, Value> receiver) { - if (!isInvalid()) { - List<Value> list = loadBeforeImpl(currentBeginIndex, currentBeginItem, pageSize); - - if (list != null && !isInvalid()) { - receiver.postOnPageResult(new PageResult<>( - PageResult.PREPEND, new Page<Key, Value>(list), 0, 0, 0)); - return; - } - } - receiver.postOnPageResult(new PageResult<Key, Value>(PageResult.PREPEND)); - } + abstract void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize, + @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver); /** * Get the key from either the position, or item, or null if position/item invalid. @@ -65,12 +44,4 @@ abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> { * that isn't yet loaded, a fallback item (last loaded item accessed) will be passed. */ abstract Key getKey(int position, Value item); - - @Nullable - abstract List<Value> loadAfterImpl(int currentEndIndex, - @NonNull Value currentEndItem, int pageSize); - - @Nullable - abstract List<Value> loadBeforeImpl(int currentBeginIndex, - @NonNull Value currentBeginItem, int pageSize); } diff --git a/android/arch/paging/ContiguousPagedList.java b/android/arch/paging/ContiguousPagedList.java index cdff391d..a134e440 100644 --- a/android/arch/paging/ContiguousPagedList.java +++ b/android/arch/paging/ContiguousPagedList.java @@ -21,6 +21,7 @@ import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import java.util.List; import java.util.concurrent.Executor; class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback { @@ -31,28 +32,14 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal private int mPrependItemsRequested = 0; private int mAppendItemsRequested = 0; - @SuppressWarnings("unchecked") - private final PagedStorage<K, V> mKeyedStorage = (PagedStorage<K, V>) mStorage; - - private final PageResult.Receiver<K, V> mReceiver = new PageResult.Receiver<K, V>() { - @AnyThread - @Override - public void postOnPageResult(@NonNull final PageResult<K, V> pageResult) { - // NOTE: if we're already on main thread, this can delay page receive by a frame - mMainThreadExecutor.execute(new Runnable() { - @Override - public void run() { - onPageResult(pageResult); - } - }); - } - + private PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() { // Creation thread for initial synchronous load, otherwise main thread // Safe to access main thread only state - no other thread has reference during construction @AnyThread @Override - public void onPageResult(@NonNull PageResult<K, V> pageResult) { - if (pageResult.page == null) { + public void onPageResult(@PageResult.ResultType int resultType, + @NonNull PageResult<V> pageResult) { + if (pageResult.isInvalid()) { detach(); return; } @@ -62,25 +49,25 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal return; } - Page<K, V> page = pageResult.page; - if (pageResult.type == PageResult.INIT) { - mKeyedStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls, + List<V> page = pageResult.page; + if (resultType == PageResult.INIT) { + mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls, pageResult.positionOffset, ContiguousPagedList.this); - notifyInserted(0, mKeyedStorage.size()); - } else if (pageResult.type == PageResult.APPEND) { - mKeyedStorage.appendPage(page, ContiguousPagedList.this); - } else if (pageResult.type == PageResult.PREPEND) { - mKeyedStorage.prependPage(page, ContiguousPagedList.this); + mLastLoad = pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2; + } else if (resultType == PageResult.APPEND) { + mStorage.appendPage(page, ContiguousPagedList.this); + } else if (resultType == PageResult.PREPEND) { + mStorage.prependPage(page, ContiguousPagedList.this); } if (mBoundaryCallback != null) { boolean deferEmpty = mStorage.size() == 0; boolean deferBegin = !deferEmpty - && pageResult.type == PageResult.PREPEND - && pageResult.page.items.size() == 0; + && resultType == PageResult.PREPEND + && pageResult.page.size() == 0; boolean deferEnd = !deferEmpty - && pageResult.type == PageResult.APPEND - && pageResult.page.items.size() == 0; + && resultType == PageResult.APPEND + && pageResult.page.size() == 0; deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd); } } @@ -93,24 +80,27 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal @Nullable BoundaryCallback<V> boundaryCallback, @NonNull Config config, final @Nullable K key) { - super(new PagedStorage<K, V>(), mainThreadExecutor, backgroundThreadExecutor, + super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor, boundaryCallback, config); mDataSource = dataSource; - // blocking init just triggers the initial load on the construction thread - - // Could still be posted with callback, if desired. - mDataSource.loadInitial(key, - mConfig.initialLoadSizeHint, - mConfig.enablePlaceholders, - mReceiver); + if (mDataSource.isInvalid()) { + detach(); + } else { + mDataSource.loadInitial(key, + mConfig.initialLoadSizeHint, + mConfig.pageSize, + mConfig.enablePlaceholders, + mMainThreadExecutor, + mReceiver); + } } @MainThread @Override void dispatchUpdatesSinceSnapshot( @NonNull PagedList<V> pagedListSnapshot, @NonNull Callback callback) { - - final PagedStorage<?, V> snapshot = pagedListSnapshot.mStorage; + final PagedStorage<V> snapshot = pagedListSnapshot.mStorage; final int newlyAppended = mStorage.getNumberAppended() - snapshot.getNumberAppended(); final int newlyPrepended = mStorage.getNumberPrepended() - snapshot.getNumberPrepended(); @@ -120,7 +110,8 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal // Validate that the snapshot looks like a previous version of this list - if it's not, // we can't be sure we'll dispatch callbacks safely - if (newlyAppended < 0 + if (snapshot.isEmpty() + || newlyAppended < 0 || newlyPrepended < 0 || mStorage.getTrailingNullCount() != Math.max(previousTrailing - newlyAppended, 0) || mStorage.getLeadingNullCount() != Math.max(previousLeading - newlyPrepended, 0) @@ -190,7 +181,13 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal if (isDetached()) { return; } - mDataSource.loadBefore(position, item, mConfig.pageSize, mReceiver); + if (mDataSource.isInvalid()) { + detach(); + } else { + mDataSource.loadBefore(position, item, mConfig.pageSize, + mMainThreadExecutor, mReceiver); + } + } }); } @@ -213,7 +210,12 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal if (isDetached()) { return; } - mDataSource.loadAfter(position, item, mConfig.pageSize, mReceiver); + if (mDataSource.isInvalid()) { + detach(); + } else { + mDataSource.loadAfter(position, item, mConfig.pageSize, + mMainThreadExecutor, mReceiver); + } } }); } diff --git a/android/arch/paging/DataSource.java b/android/arch/paging/DataSource.java index ff44521e..b82d4e6d 100644 --- a/android/arch/paging/DataSource.java +++ b/android/arch/paging/DataSource.java @@ -18,28 +18,71 @@ package android.arch.paging; import android.support.annotation.AnyThread; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; +import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; /** - * Base class for incremental data loading, used in list paging. To implement, extend either the - * {@link KeyedDataSource}, or {@link TiledDataSource} subclass. + * Base class for loading pages of snapshot data into a {@link PagedList}. * <p> - * Choose based on whether each load operation is based on the position of the data in the list. + * DataSource is queried to load pages of content into a {@link PagedList}. A PagedList can grow as + * it loads more data, but the data loaded cannot be updated. * <p> - * Use {@link KeyedDataSource} if you need to use data from item <code>N-1</code> to load item - * <code>N</code>. For example, if requesting the backend for the next comments in the list + * A PagedList / DataSource pair serve as a snapshot of the data set being loaded. If the + * underlying data set is modified, a new PagedList / DataSource pair must be created to represent + * the new data. + * <h4>Loading Pages</h4> + * PagedList queries data from its DataSource in response to loading hints. {@link PagedListAdapter} + * calls {@link PagedList#loadAround(int)} to load content as the user scrolls in a RecyclerView. + * <p> + * To control how and when a PagedList queries data from its DataSource, see + * {@link PagedList.Config}. The Config object defines things like load sizes and prefetch distance. + * <h4>Updating Paged Data</h4> + * A PagedList / DataSource pair are a snapshot of the data set. A new pair of + * PagedList / DataSource must be created if an update occurs, such as a reorder, insert, delete, or + * content update occurs. A DataSource must detect that it cannot continue loading its + * snapshot (for instance, when Database query notices a table being invalidated), and call + * {@link #invalidate()}. Then a new PagedList / DataSource pair would be created to load data from + * the new state of the Database query. + * <p> + * To page in data that doesn't update, you can create a single DataSource, and pass it to a single + * PagedList. For example, loading from network when the network's paging API doesn't provide + * updates. + * <p> + * To page in data from a source that does provide updates, you can create a + * {@link DataSource.Factory}, where each DataSource created is invalidated when an update to the + * data set occurs that makes the current snapshot invalid. For example, when paging a query from + * the Database, and the table being queried inserts or removes items. You can also use a + * DataSource.Factory to provide multiple versions of network-paged lists. If reloading all content + * (e.g. in response to an action like swipe-to-refresh) is required to get a new version of data, + * you can connect an explicit refresh signal to call {@link #invalidate()} on the current + * DataSource. + * <p> + * If you have more granular update signals, such as a network API signaling an update to a single + * item in the list, it's recommended to load data from network into memory. Then present that + * data to the PagedList via a DataSource that wraps an in-memory snapshot. Each time the in-memory + * copy changes, invalidate the previous DataSource, and a new one wrapping the new state of the + * snapshot can be created. + * <h4>Implementing a DataSource</h4> + * To implement, extend either the {@link KeyedDataSource}, or {@link PositionalDataSource} + * subclass. Choose based on whether each load operation is based on the position of the data in the + * list. + * <p> + * Use {@link KeyedDataSource} if you need to use data from item {@code N-1} to load item + * {@code N}. For example, if requesting the backend for the next comments in the list * requires the ID or timestamp of the most recent loaded comment, or if querying the next users * from a name-sorted database query requires the name and unique ID of the previous. * <p> - * Use {@link TiledDataSource} if you can load arbitrary pages based solely on position information, - * and can provide a fixed item count. TiledDataSource supports querying pages at arbitrary - * positions, so can provide data to PagedLists in arbitrary order. + * Use {@link PositionalDataSource} if you can load arbitrary pages based solely on position + * information, and can provide a fixed item count. PositionalDataSource supports querying pages at + * arbitrary positions, so can provide data to PagedLists in arbitrary order. * <p> - * Because a <code>null</code> item indicates a placeholder in {@link PagedList}, DataSource may not - * return <code>null</code> items in lists that it loads. This is so that users of the PagedList + * Because a {@code null} item indicates a placeholder in {@link PagedList}, DataSource may not + * return {@code null} items in lists that it loads. This is so that users of the PagedList * can differentiate unloaded placeholder items from content that has been paged in. * * @param <Key> Input used to trigger initial load from the DataSource. Often an Integer position. @@ -47,8 +90,36 @@ import java.util.concurrent.atomic.AtomicBoolean; */ @SuppressWarnings("unused") // suppress warning to remove Key/Value, needed for subclass type safety public abstract class DataSource<Key, Value> { - + /** + * Factory for DataSources. + * <p> + * Data-loading systems of an application or library can implement this interface to allow + * {@code LiveData<PagedList>}s to be created. For example, Room can provide a + * DataSource.Factory for a given SQL query: + * + * <pre> + * {@literal @}Dao + * interface UserDao { + * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC") + * public abstract DataSource.Factory<Integer, User> usersByLastName(); + * } + * </pre> + * In the above sample, {@code Integer} is used because it is the {@code Key} type of + * PositionalDataSource. Currently, Room uses the {@code LIMIT}/{@code OFFSET} SQL keywords to + * page a large query with a PositionalDataSource. + * + * @param <Key> Key identifying items in DataSource. + * @param <Value> Type of items in the list loaded by the DataSources. + */ public interface Factory<Key, Value> { + /** + * Create a DataSource. + * <p> + * The DataSource should invalidate itself if the snapshot is no longer valid, and a new + * DataSource should be queried from the Factory. + * + * @return the new DataSource. + */ DataSource<Key, Value> create(); } @@ -58,18 +129,77 @@ public abstract class DataSource<Key, Value> { } /** - * If returned by countItems(), indicates an undefined number of items are provided by the data - * source. Continued querying in either direction may continue to produce more data. - */ - @SuppressWarnings("WeakerAccess") - public static int COUNT_UNDEFINED = -1; - - /** * Returns true if the data source guaranteed to produce a contiguous set of items, * never producing gaps. */ abstract boolean isContiguous(); + static class BaseLoadCallback<T> { + static void validateInitialLoadParams(@NonNull List<?> data, int position, int totalCount) { + if (position < 0) { + throw new IllegalArgumentException("Position must be non-negative"); + } + if (data.size() + position > totalCount) { + throw new IllegalArgumentException( + "List size + position too large, last item in list beyond totalCount."); + } + if (data.size() == 0 && totalCount > 0) { + throw new IllegalArgumentException( + "Initial result cannot be empty if items are present in data set."); + } + } + + @PageResult.ResultType + final int mResultType; + private final DataSource mDataSource; + private final PageResult.Receiver<T> mReceiver; + + // mSignalLock protects mPostExecutor, and mHasSignalled + private final Object mSignalLock = new Object(); + private Executor mPostExecutor = null; + private boolean mHasSignalled = false; + + BaseLoadCallback(@PageResult.ResultType int resultType, @NonNull DataSource dataSource, + @Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) { + mResultType = resultType; + mPostExecutor = mainThreadExecutor; + mDataSource = dataSource; + mReceiver = receiver; + } + + void setPostExecutor(Executor postExecutor) { + synchronized (mSignalLock) { + mPostExecutor = postExecutor; + } + } + + void dispatchResultToReceiver(final @NonNull PageResult<T> result) { + Executor executor; + synchronized (mSignalLock) { + if (mHasSignalled) { + throw new IllegalStateException( + "LoadCallback already dispatched, cannot dispatch again."); + } + mHasSignalled = true; + executor = mPostExecutor; + } + + final PageResult<T> resolvedResult = + mDataSource.isInvalid() ? PageResult.<T>getInvalidResult() : result; + + if (executor != null) { + executor.execute(new Runnable() { + @Override + public void run() { + mReceiver.onPageResult(mResultType, result); + } + }); + } else { + mReceiver.onPageResult(mResultType, result); + } + } + } + /** * Invalidation callback for DataSource. * <p> diff --git a/android/arch/paging/KeyedDataSource.java b/android/arch/paging/KeyedDataSource.java index 3214a4ef..4f62692b 100644 --- a/android/arch/paging/KeyedDataSource.java +++ b/android/arch/paging/KeyedDataSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. @@ -16,328 +16,245 @@ package android.arch.paging; -import android.support.annotation.AnyThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; /** * Incremental data loader for paging keyed content, where loaded content uses previously loaded * items as input to future loads. * <p> - * Implement a DataSource using KeyedDataSource if you need to use data from item <code>N-1</code> - * to load item <code>N</code>. This is common, for example, in sorted database queries where + * Implement a DataSource using KeyedDataSource if you need to use data from item {@code N - 1} + * to load item {@code N}. This is common, for example, in sorted database queries where * attributes of the item such just before the next query define how to execute it. - * <p> - * A compute usage pattern with Room SQL queries would look like this (though note, Room plans to - * provide generation of much of this code in the future): - * <pre> - * {@literal @}Dao - * interface UserDao { - * {@literal @}Query("SELECT * from user ORDER BY name DESC LIMIT :limit") - * public abstract List<User> userNameInitial(int limit); - * - * {@literal @}Query("SELECT * from user WHERE name < :key ORDER BY name DESC LIMIT :limit") - * public abstract List<User> userNameLoadAfter(String key, int limit); - * - * {@literal @}Query("SELECT * from user WHERE name > :key ORDER BY name ASC LIMIT :limit") - * public abstract List<User> userNameLoadBefore(String key, int limit); - * } - * - * public class KeyedUserQueryDataSource extends KeyedDataSource<String, User> { - * private MyDatabase mDb; - * private final UserDao mUserDao; - * {@literal @}SuppressWarnings("FieldCanBeLocal") - * private final InvalidationTracker.Observer mObserver; - * - * public KeyedUserQueryDataSource(MyDatabase db) { - * mDb = db; - * mUserDao = db.getUserDao(); - * mObserver = new InvalidationTracker.Observer("user") { - * {@literal @}Override - * public void onInvalidated({@literal @}NonNull Set<String> tables) { - * // the user table has been invalidated, invalidate the DataSource - * invalidate(); - * } - * }; - * db.getInvalidationTracker().addWeakObserver(mObserver); - * } - * - * {@literal @}Override - * public boolean isInvalid() { - * mDb.getInvalidationTracker().refreshVersionsSync(); - * return super.isInvalid(); - * } - * - * {@literal @}Override - * public String getKey({@literal @}NonNull User item) { - * return item.getName(); - * } - * - * {@literal @}Override - * public List<User> loadInitial(int pageSize) { - * return mUserDao.userNameInitial(pageSize); - * } - * - * {@literal @}Override - * public List<User> loadBefore({@literal @}NonNull String userName, int pageSize) { - * // Return items adjacent to 'userName' in reverse order - * // it's valid to return a different-sized list of items than pageSize, if it's easier - * return mUserDao.userNameLoadBefore(userName, pageSize); - * } - * - * {@literal @}Override - * public List<User> loadAfter({@literal @}Nullable String userName, int pageSize) { - * // Return items adjacent to 'userName' - * // it's valid to return a different-sized list of items than pageSize, if it's easier - * return mUserDao.userNameLoadAfter(userName, pageSize); - * } - * }</pre> * * @param <Key> Type of data used to query Value types out of the DataSource. * @param <Value> Type of items being loaded by the DataSource. */ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> { - @Nullable - @Override - List<Value> loadAfterImpl(int currentEndIndex, @NonNull Value currentEndItem, int pageSize) { - return loadAfter(getKey(currentEndItem), pageSize); - } - - @Nullable - @Override - List<Value> loadBeforeImpl( - int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize) { - List<Value> list = loadBefore(getKey(currentBeginItem), pageSize); - - if (list != null && list.size() > 1) { - // TODO: move out of keyed entirely, into the DB DataSource. - list = new ArrayList<>(list); - Collections.reverse(list); - } - return list; - } - - - @Override - void loadInitial(Key key, int initialLoadSize, boolean enablePlaceholders, - @NonNull PageResult.Receiver<Key, Value> receiver) { - - PageResult<Key, Value> pageResult = - loadInitialInternal(key, initialLoadSize, enablePlaceholders); - if (pageResult == null) { - // loading failed, return empty page - receiver.onPageResult(new PageResult<Key, Value>(PageResult.INIT)); - } else { - receiver.onPageResult(pageResult); - } - } - /** - * Try initial load, and either return the successful initial load to the receiver, - * or null if unsuccessful. + * Callback for KeyedDataSource initial loading methods to return data and (optionally) + * position/count information. + * <p> + * A callback can be called only once, and will throw if called again. + * <p> + * It is always valid for a DataSource loading method that takes a callback to stash the + * callback and call it later. This enables DataSources to be fully asynchronous, and to handle + * temporary, recoverable error states (such as a network error that can be retried). + * + * @param <T> Type of items being loaded. */ - @Nullable - private PageResult<Key, Value> loadInitialInternal( - @Nullable Key key, int initialLoadSize, boolean enablePlaceholders) { - // check if invalid at beginning, and before returning a valid list - if (isInvalid()) { - return null; + public static class InitialLoadCallback<T> extends LoadCallback<T> { + private final boolean mCountingEnabled; + InitialLoadCallback(@NonNull KeyedDataSource dataSource, boolean countingEnabled, + @NonNull PageResult.Receiver<T> receiver) { + super(dataSource, PageResult.INIT, null, receiver); + mCountingEnabled = countingEnabled; } - List<Value> list; - if (key == null) { - // no key, so load initial. - list = loadInitial(initialLoadSize); - if (list == null) { - return null; - } - } else { - List<Value> after = loadAfter(key, initialLoadSize / 2); - if (after == null) { - return null; - } + /** + * Called to pass initial load state from a DataSource. + * <p> + * Call this method from your DataSource's {@code loadInitial} function to return data, + * and inform how many placeholders should be shown before and after. If counting is cheap + * to compute (for example, if a network load returns the information regardless), it's + * recommended to pass data back through this method. + * <p> + * It is always valid to pass a different amount of data than what is requested. Pass an + * empty list if there is no more data to load. + * + * @param data List of items loaded from the DataSource. If this is empty, the DataSource + * is treated as empty, and no further loads will occur. + * @param position Position of the item at the front of the list. If there are {@code N} + * items before the items in data that can be loaded from this DataSource, + * pass {@code N}. + * @param totalCount Total number of items that may be returned from this DataSource. + * Includes the number in the initial {@code data} parameter + * as well as any items that can be loaded in front or behind of + * {@code data}. + */ + public void onResult(@NonNull List<T> data, int position, int totalCount) { + validateInitialLoadParams(data, position, totalCount); - Key loadBeforeKey = after.isEmpty() ? key : getKey(after.get(0)); - List<Value> before = loadBefore(loadBeforeKey, initialLoadSize / 2); - if (before == null) { - return null; - } - if (!after.isEmpty() || !before.isEmpty()) { - // one of the lists has data - if (after.isEmpty()) { - // retry loading after, since it may be that the key passed points to the end of - // the list, so we need to load after the last item in the before list - after = loadAfter(getKey(before.get(0)), initialLoadSize / 2); - if (after == null) { - return null; - } - } - // assemble full list - list = new ArrayList<>(); - list.addAll(before); - // Note - we reverse the list instead of before, in case before is immutable - Collections.reverse(list); - list.addAll(after); + int trailingUnloadedCount = totalCount - position - data.size(); + if (mCountingEnabled) { + dispatchResultToReceiver(new PageResult<>( + data, position, trailingUnloadedCount, 0)); } else { - // load before(key) and load after(key) failed - try load initial to be *sure* we - // catch the case where there's only one item, which is loaded by the key case - list = loadInitial(initialLoadSize); - if (list == null) { - return null; - } + dispatchResultToReceiver(new PageResult<>(data, position)); } } + } - final Page<Key, Value> page = new Page<>(list); - - if (list.isEmpty()) { - if (isInvalid()) { - return null; - } - // wasn't able to load any items, but not invalid - return an empty page. - return new PageResult<>(PageResult.INIT, page, 0, 0, 0); + /** + * Callback for KeyedDataSource {@link #loadBefore(Object, int, LoadCallback)} + * and {@link #loadAfter(Object, int, LoadCallback)} methods to return data. + * <p> + * A callback can be called only once, and will throw if called again. + * <p> + * It is always valid for a DataSource loading method that takes a callback to stash the + * callback and call it later. This enables DataSources to be fully asynchronous, and to handle + * temporary, recoverable error states (such as a network error that can be retried). + * + * @param <T> Type of items being loaded. + */ + public static class LoadCallback<T> extends BaseLoadCallback<T> { + LoadCallback(@NonNull KeyedDataSource dataSource, @PageResult.ResultType int type, + @Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) { + super(type, dataSource, mainThreadExecutor, receiver); } - int itemsBefore = COUNT_UNDEFINED; - int itemsAfter = COUNT_UNDEFINED; - if (enablePlaceholders) { - itemsBefore = countItemsBefore(getKey(list.get(0))); - itemsAfter = countItemsAfter(getKey(list.get(list.size() - 1))); + /** + * Called to pass loaded data from a DataSource. + * <p> + * Call this method from your KeyedDataSource's + * {@link #loadBefore(Object, int, LoadCallback)} and + * {@link #loadAfter(Object, int, LoadCallback)} methods to return data. + * <p> + * Call this from {@link #loadInitial(Object, int, boolean, InitialLoadCallback)} to + * initialize without counting available data, or supporting placeholders. + * <p> + * It is always valid to pass a different amount of data than what is requested. Pass an + * empty list if there is no more data to load. + * + * @param data List of items loaded from the KeyedDataSource. + */ + public void onResult(@NonNull List<T> data) { + dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0)); } + } - if (isInvalid()) { + @Nullable + @Override + final Key getKey(int position, Value item) { + if (item == null) { return null; } - if (itemsBefore == COUNT_UNDEFINED || itemsAfter == COUNT_UNDEFINED) { - itemsBefore = 0; - itemsAfter = 0; - } - return new PageResult<>( - PageResult.INIT, - page, - itemsBefore, - itemsAfter, - 0); + + return getKey(item); } - /** - * Return a key associated with the given item. - * <p> - * If your KeyedDataSource is loading from a source that is sorted and loaded by a unique - * integer ID, you would return {@code item.getID()} here. This key can then be passed to - * {@link #loadBefore(Key, int)} or {@link #loadAfter(Key, int)} to load additional items - * adjacent to the item passed to this function. - * <p> - * If your key is more complex, such as when you're sorting by name, then resolving collisions - * with integer ID, you'll need to return both. In such a case you would use a wrapper class, - * such as {@code Pair<String, Integer>} or, in Kotlin, - * {@code data class Key(val name: String, val id: Int)} - * - * @param item Item to get the key from. - * @return Key associated with given item. - */ - @NonNull - @AnyThread - public abstract Key getKey(@NonNull Value item); + @Override + public void loadInitial(@Nullable Key key, int initialLoadSize, int pageSize, + boolean enablePlaceholders, @NonNull Executor mainThreadExecutor, + @NonNull PageResult.Receiver<Value> receiver) { + InitialLoadCallback<Value> callback = + new InitialLoadCallback<>(this, enablePlaceholders, receiver); + loadInitial(key, initialLoadSize, enablePlaceholders, callback); - /** - * Return the number of items that occur before the item uniquely identified by {@code key} in - * the data set. - * <p> - * For example, if you're loading items sorted by ID, then this would return the total number of - * items with ID less than {@code key}. - * <p> - * If you return {@link #COUNT_UNDEFINED} here, or from {@link #countItemsAfter(Key)}, your - * data source will not present placeholder null items in place of unloaded data. - * - * @param key A unique identifier of an item in the data set. - * @return Number of items in the data set before the item identified by {@code key}, or - * {@link #COUNT_UNDEFINED}. - * - * @see #countItemsAfter(Key) - */ - @WorkerThread - public int countItemsBefore(@NonNull Key key) { - return COUNT_UNDEFINED; + // If initialLoad's callback is not called within the body, we force any following calls + // to post to the UI thread. This constructor may be run on a background thread, but + // after constructor, mutation must happen on UI thread. + callback.setPostExecutor(mainThreadExecutor); + } + + @Override + void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize, + @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver) { + loadAfter(getKey(currentEndItem), pageSize, + new LoadCallback<>(this, PageResult.APPEND, mainThreadExecutor, receiver)); + } + + @Override + void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize, + @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver) { + loadBefore(getKey(currentBeginItem), pageSize, + new LoadCallback<>(this, PageResult.PREPEND, mainThreadExecutor, receiver)); } /** - * Return the number of items that occur after the item uniquely identified by {@code key} in - * the data set. + * Load initial data. * <p> - * For example, if you're loading items sorted by ID, then this would return the total number of - * items with ID greater than {@code key}. + * This method is called first to initialize a PagedList with data. If it's possible to count + * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to + * the callback via the three-parameter + * {@link InitialLoadCallback#onResult(List, int, int)}. This enables PagedLists + * presenting data from this source to display placeholders to represent unloaded items. * <p> - * If you return {@link #COUNT_UNDEFINED} here, or from {@link #countItemsBefore(Key)}, your - * data source will not present placeholder null items in place of unloaded data. - * - * @param key A unique identifier of an item in the data set. - * @return Number of items in the data set after the item identified by {@code key}, or - * {@link #COUNT_UNDEFINED}. + * {@code initialLoadKey} and {@code requestedLoadSize} are hints, not requirements, so if it is + * difficult or impossible to respect them, they may be altered. Note that ignoring the + * {@code initialLoadKey} can prevent subsequent PagedList/DataSource pairs from initializing at + * the same location. If your data source never invalidates (for example, loading from the + * network without the network ever signalling that old data must be reloaded), it's fine to + * ignore the {@code initialLoadKey} and always start from the beginning of the data set. * - * @see #countItemsBefore(Key) + * @param initialLoadKey Load items around this key, or at the beginning of the data set if null + * is passed. + * @param requestedLoadSize Suggested number of items to load. + * @param enablePlaceholders Signals whether counting is requested. If false, you can + * potentially save work by calling the single-parameter variant of + * {@link LoadCallback#onResult(List)} and not counting the + * number of items in the data set. + * @param callback DataSource.LoadCallback that receives initial load data. */ - @WorkerThread - public int countItemsAfter(@NonNull Key key) { - return COUNT_UNDEFINED; - } - - @WorkerThread - @Nullable - public abstract List<Value> loadInitial(int pageSize); + public abstract void loadInitial(@Nullable Key initialLoadKey, int requestedLoadSize, + boolean enablePlaceholders, @NonNull InitialLoadCallback<Value> callback); /** * Load list data after the specified item. * <p> * It's valid to return a different list size than the page size, if it's easier for this data * source. It is generally safer to increase the number loaded than reduce. + * <p> + * Data may be passed synchronously during the loadAfter method, or deferred and called at a + * later time. Further loads going down will be blocked until the callback is called. + * <p> + * If data cannot be loaded (for example, if the request is invalid, or the data would be stale + * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source, + * and prevent further loading. * * @param currentEndKey Load items after this key. May be null on initial load, to indicate load * from beginning. - * @param pageSize Suggested number of items to load. - * @return List of items, starting after the specified item. Null if the data source is - * no longer valid, and should not be queried again. + * @param pageSize Suggested number of items to load. + * @param callback DataSource.LoadCallback that receives loaded data. */ - @SuppressWarnings("WeakerAccess") - @WorkerThread - @Nullable - public abstract List<Value> loadAfter(@NonNull Key currentEndKey, int pageSize); + public abstract void loadAfter(@NonNull Key currentEndKey, int pageSize, + @NonNull LoadCallback<Value> callback); /** - * Load data before the currently loaded content, starting at the provided index, - * in reverse-display order. + * Load data before the currently loaded content. * <p> * It's valid to return a different list size than the page size, if it's easier for this data - * source. It is generally safer to increase the number loaded than reduce. - * <p class="note"><strong>Note:</strong> Items returned from loadBefore <em>must</em> be in - * reverse order from how they will be presented in the list. The first item in the return list - * will be prepended immediately before the current beginning of the list. This is so that the - * KeyedDataSource may return a different number of items from the requested {@code pageSize} by - * shortening or lengthening the return list as it desires. + * source. It is generally safer to increase the number loaded than reduce. Note that the last + * item returned must be directly adjacent to the key passed, so varying size from the pageSize + * requested should effectively grow or shrink the list by modifying the beginning, not the end. * <p> + * Data may be passed synchronously during the loadBefore method, or deferred and called at a + * later time. Further loads going up will be blocked until the callback is called. + * <p> + * If data cannot be loaded (for example, if the request is invalid, or the data would be stale + * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source, + * and prevent further loading. + * <p class="note"><strong>Note:</strong> Data must be returned in the order it will be + * presented in the list. * * @param currentBeginKey Load items before this key. - * @param pageSize Suggested number of items to load. - * @return List of items, in descending order, starting after the specified item. Null if the - * data source is no longer valid, and should not be queried again. + * @param pageSize Suggested number of items to load. + * @param callback DataSource.LoadCallback that receives loaded data. */ - @SuppressWarnings("WeakerAccess") - @WorkerThread - @Nullable - public abstract List<Value> loadBefore(@NonNull Key currentBeginKey, int pageSize); + public abstract void loadBefore(@NonNull Key currentBeginKey, int pageSize, + @NonNull LoadCallback<Value> callback); - @Nullable - @Override - Key getKey(int position, Value item) { - if (item == null) { - return null; - } - return getKey(item); - } + /** + * Return a key associated with the given item. + * <p> + * If your KeyedDataSource is loading from a source that is sorted and loaded by a unique + * integer ID, you would return {@code item.getID()} here. This key can then be passed to + * {@link #loadBefore(Object, int, LoadCallback)} or + * {@link #loadAfter(Object, int, LoadCallback)} to load additional items adjacent to the item + * passed to this function. + * <p> + * If your key is more complex, such as when you're sorting by name, then resolving collisions + * with integer ID, you'll need to return both. In such a case you would use a wrapper class, + * such as {@code Pair<String, Integer>} or, in Kotlin, + * {@code data class Key(val name: String, val id: Int)} + * + * @param item Item to get the key from. + * @return Key associated with given item. + */ + @NonNull + public abstract Key getKey(@NonNull Value item); } diff --git a/android/arch/paging/ListDataSource.java b/android/arch/paging/ListDataSource.java index d3a171e5..b6f366a3 100644 --- a/android/arch/paging/ListDataSource.java +++ b/android/arch/paging/ListDataSource.java @@ -16,10 +16,12 @@ package android.arch.paging; +import android.support.annotation.NonNull; + import java.util.ArrayList; import java.util.List; -public class ListDataSource<T> extends TiledDataSource<T> { +class ListDataSource<T> extends PositionalDataSource<T> { private final List<T> mList; public ListDataSource(List<T> list) { @@ -27,13 +29,22 @@ public class ListDataSource<T> extends TiledDataSource<T> { } @Override - public int countItems() { - return mList.size(); + public void loadInitial(int requestedStartPosition, int requestedLoadSize, int pageSize, + @NonNull InitialLoadCallback<T> callback) { + final int totalCount = mList.size(); + + final int firstLoadPosition = computeFirstLoadPosition( + requestedStartPosition, requestedLoadSize, pageSize, totalCount); + final int firstLoadSize = Math.min(totalCount - firstLoadPosition, requestedLoadSize); + + // for simplicity, we could return everything immediately, + // but we tile here since it's expected behavior + List<T> sublist = mList.subList(firstLoadPosition, firstLoadPosition + firstLoadSize); + callback.onResult(sublist, firstLoadPosition, totalCount); } @Override - public List<T> loadRange(int startPosition, int count) { - int endExclusive = Math.min(mList.size(), startPosition + count); - return mList.subList(startPosition, endExclusive); + public void loadRange(int startPosition, int count, @NonNull LoadCallback<T> callback) { + callback.onResult(mList.subList(startPosition, startPosition + count)); } } diff --git a/android/arch/paging/LivePagedListBuilder.java b/android/arch/paging/LivePagedListBuilder.java index ee1810b5..b0fddba2 100644 --- a/android/arch/paging/LivePagedListBuilder.java +++ b/android/arch/paging/LivePagedListBuilder.java @@ -25,42 +25,76 @@ import android.support.annotation.Nullable; import java.util.concurrent.Executor; +/** + * Builder for {@code LiveData<PagedList>}, given a {@link DataSource.Factory} and a + * {@link PagedList.Config}. + * <p> + * The required parameters are in the constructor, so you can simply construct and build, or + * optionally enable extra features (such as initial load key, or BoundaryCallback. + * + * @param <Key> Type of input valued used to load data from the DataSource. Must be integer if + * you're using PositionalDataSource. + * @param <Value> Item type being presented. + */ public class LivePagedListBuilder<Key, Value> { private Key mInitialLoadKey; private PagedList.Config mConfig; private DataSource.Factory<Key, Value> mDataSourceFactory; private PagedList.BoundaryCallback mBoundaryCallback; - private Executor mMainThreadExecutor; private Executor mBackgroundThreadExecutor; - @SuppressWarnings("WeakerAccess") - @NonNull - public LivePagedListBuilder<Key, Value> setInitialLoadKey(@Nullable Key key) { - mInitialLoadKey = key; - return this; - } - - @SuppressWarnings("WeakerAccess") - @NonNull - public LivePagedListBuilder<Key, Value> setPagingConfig(@NonNull PagedList.Config config) { + /** + * Creates a LivePagedListBuilder with required parameters. + * + * @param dataSourceFactory DataSource factory providing DataSource generations. + * @param config Paging configuration. + */ + public LivePagedListBuilder(@NonNull DataSource.Factory<Key, Value> dataSourceFactory, + @NonNull PagedList.Config config) { + mDataSourceFactory = dataSourceFactory; mConfig = config; - return this; } - @SuppressWarnings("WeakerAccess") - @NonNull - public LivePagedListBuilder<Key, Value> setPagingConfig(int pageSize) { - mConfig = new PagedList.Config.Builder().setPageSize(pageSize).build(); - return this; + /** + * Creates a LivePagedListBuilder with required parameters. + * <p> + * This method is a convenience for: + * <pre> + * LivePagedListBuilder(dataSourceFactory, + * new PagedList.Config.Builder().setPageSize(pageSize).build()) + * </pre> + * + * @param dataSourceFactory DataSource.Factory providing DataSource generations. + * @param pageSize Size of pages to load. + */ + public LivePagedListBuilder(@NonNull DataSource.Factory<Key, Value> dataSourceFactory, + int pageSize) { + this(dataSourceFactory, new PagedList.Config.Builder().setPageSize(pageSize).build()); } + /** + * First loading key passed to the first PagedList/DataSource. + * <p> + * When a new PagedList/DataSource pair is created after the first, it acquires a load key from + * the previous generation so that data is loaded around the position already being observed. + * + * @param key Initial load key passed to the first PagedList/DataSource. + * @return this + */ @NonNull - public LivePagedListBuilder<Key, Value> setDataSourceFactory( - @NonNull DataSource.Factory<Key, Value> dataSourceFactory) { - mDataSourceFactory = dataSourceFactory; + public LivePagedListBuilder<Key, Value> setInitialLoadKey(@Nullable Key key) { + mInitialLoadKey = key; return this; } + /** + * Sets a {@link PagedList.BoundaryCallback} on each PagedList created. + * <p> + * This can be used to + * + * @param boundaryCallback The boundary callback for listening to PagedList load state. + * @return this + */ @SuppressWarnings("unused") @NonNull public LivePagedListBuilder<Key, Value> setBoundaryCallback( @@ -69,14 +103,15 @@ public class LivePagedListBuilder<Key, Value> { return this; } - @SuppressWarnings("unused") - @NonNull - public LivePagedListBuilder<Key, Value> setMainThreadExecutor( - @NonNull Executor mainThreadExecutor) { - mMainThreadExecutor = mainThreadExecutor; - return this; - } - + /** + * Sets executor which will be used for background loading of pages. + * <p> + * Does not affect initial load, which will be always be done on done on the Arch components + * I/O thread. + * + * @param backgroundThreadExecutor Executor for background DataSource loading. + * @return this + */ @SuppressWarnings("unused") @NonNull public LivePagedListBuilder<Key, Value> setBackgroundThreadExecutor( @@ -85,6 +120,14 @@ public class LivePagedListBuilder<Key, Value> { return this; } + /** + * Constructs the {@code LiveData<PagedList>}. + * <p> + * No work (such as loading) is done immediately, the creation of the first PagedList is is + * deferred until the LiveData is observed. + * + * @return The LiveData of PagedLists + */ @NonNull public LiveData<PagedList<Value>> build() { if (mConfig == null) { @@ -93,20 +136,17 @@ public class LivePagedListBuilder<Key, Value> { if (mDataSourceFactory == null) { throw new IllegalArgumentException("DataSource.Factory must be provided"); } - if (mMainThreadExecutor == null) { - mMainThreadExecutor = ArchTaskExecutor.getMainThreadExecutor(); - } if (mBackgroundThreadExecutor == null) { mBackgroundThreadExecutor = ArchTaskExecutor.getIOThreadExecutor(); } return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory, - mMainThreadExecutor, mBackgroundThreadExecutor); + ArchTaskExecutor.getMainThreadExecutor(), mBackgroundThreadExecutor); } @AnyThread @NonNull - public static <Key, Value> LiveData<PagedList<Value>> create( + private static <Key, Value> LiveData<PagedList<Value>> create( @Nullable final Key initialLoadKey, @NonNull final PagedList.Config config, @Nullable final PagedList.BoundaryCallback boundaryCallback, @@ -143,12 +183,10 @@ public class LivePagedListBuilder<Key, Value> { mDataSource = dataSourceFactory.create(); mDataSource.addInvalidatedCallback(mCallback); - mList = new PagedList.Builder<Key, Value>() - .setDataSource(mDataSource) + mList = new PagedList.Builder<>(mDataSource, config) .setMainThreadExecutor(mainThreadExecutor) .setBackgroundThreadExecutor(backgroundThreadExecutor) .setBoundaryCallback(boundaryCallback) - .setConfig(config) .setInitialKey(initializeKey) .build(); } while (mList.isDetached()); diff --git a/android/arch/paging/Page.java b/android/arch/paging/Page.java deleted file mode 100644 index e9890ed4..00000000 --- a/android/arch/paging/Page.java +++ /dev/null @@ -1,51 +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.arch.paging; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.List; - -/** - * Immutable class representing a page of data loaded from a DataSource. - * <p> - * Optionally stores before/after keys for cases where they cannot be computed, but the DataSource - * can provide them as part of loading a page. - * <p> - * A page's list must never be modified. - */ -class Page<K, V> { - @SuppressWarnings("WeakerAccess") - @Nullable - public final K beforeKey; - @NonNull - public final List<V> items; - @SuppressWarnings("WeakerAccess") - @Nullable - public K afterKey; - - Page(@NonNull List<V> items) { - this(null, items, null); - } - - Page(@Nullable K beforeKey, @NonNull List<V> items, @Nullable K afterKey) { - this.beforeKey = beforeKey; - this.items = items; - this.afterKey = afterKey; - } -} diff --git a/android/arch/paging/PageResult.java b/android/arch/paging/PageResult.java index 55d5fb76..cf2216fa 100644 --- a/android/arch/paging/PageResult.java +++ b/android/arch/paging/PageResult.java @@ -16,11 +16,31 @@ package android.arch.paging; -import android.support.annotation.AnyThread; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.support.annotation.IntDef; import android.support.annotation.MainThread; import android.support.annotation.NonNull; -class PageResult<K, V> { +import java.lang.annotation.Retention; +import java.util.Collections; +import java.util.List; + +class PageResult<T> { + @SuppressWarnings("unchecked") + private static final PageResult INVALID_RESULT = + new PageResult(Collections.EMPTY_LIST, 0); + + @SuppressWarnings("unchecked") + static <T> PageResult<T> getInvalidResult() { + return INVALID_RESULT; + } + + + @Retention(SOURCE) + @IntDef({INIT, APPEND, PREPEND, TILE}) + @interface ResultType {} + static final int INIT = 0; // contiguous results @@ -30,8 +50,8 @@ class PageResult<K, V> { // non-contiguous, tile result static final int TILE = 3; - public final int type; - public final Page<K, V> page; + @NonNull + public final List<T> page; @SuppressWarnings("WeakerAccess") public final int leadingNulls; @SuppressWarnings("WeakerAccess") @@ -39,26 +59,34 @@ class PageResult<K, V> { @SuppressWarnings("WeakerAccess") public final int positionOffset; - PageResult(int type, Page<K, V> page, int leadingNulls, int trailingNulls, int positionOffset) { - this.type = type; - this.page = page; + PageResult(@NonNull List<T> list, int leadingNulls, int trailingNulls, int positionOffset) { + this.page = list; this.leadingNulls = leadingNulls; this.trailingNulls = trailingNulls; this.positionOffset = positionOffset; } - PageResult(int type) { - this.type = type; - this.page = null; + PageResult(@NonNull List<T> list, int positionOffset) { + this.page = list; this.leadingNulls = 0; this.trailingNulls = 0; - this.positionOffset = 0; + this.positionOffset = positionOffset; + } + + @Override + public String toString() { + return "Result " + leadingNulls + + ", " + page + + ", " + trailingNulls + + ", offset " + positionOffset; + } + + public boolean isInvalid() { + return this == INVALID_RESULT; } - interface Receiver<K, V> { - @AnyThread - void postOnPageResult(@NonNull PageResult<K, V> pageResult); + abstract static class Receiver<T> { @MainThread - void onPageResult(@NonNull PageResult<K, V> pageResult); + public abstract void onPageResult(@ResultType int type, @NonNull PageResult<T> pageResult); } } diff --git a/android/arch/paging/PagedList.java b/android/arch/paging/PagedList.java index f18e108c..4e17a151 100644 --- a/android/arch/paging/PagedList.java +++ b/android/arch/paging/PagedList.java @@ -17,6 +17,7 @@ package android.arch.paging; import android.support.annotation.AnyThread; +import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RestrictTo; @@ -51,7 +52,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * PagedList can present data for an unbounded, infinite scrolling list, or a very large but * countable list. Use {@link Config} to control how many items a PagedList loads, and when. * <p> - * If you use {@link LivePagedListProvider} to get a + * If you use {@link LivePagedListBuilder} to get a * {@link android.arch.lifecycle.LiveData}<PagedList>, it will initialize PagedLists on a * background thread for you. * <h4>Placeholders</h4> @@ -88,9 +89,8 @@ import java.util.concurrent.atomic.AtomicBoolean; * </ul> * <p> * Placeholders are enabled by default, but can be disabled in two ways. They are disabled if the - * DataSource returns {@link DataSource#COUNT_UNDEFINED} from any item counting method, or if - * {@code false} is passed to {@link Config.Builder#setEnablePlaceholders(boolean)} when building a - * {@link Config}. + * DataSource does not count its data set in its initial load, or if {@code false} is passed to + * {@link Config.Builder#setEnablePlaceholders(boolean)} when building a {@link Config}. * * @param <T> The type of the entries in the list. */ @@ -104,7 +104,7 @@ public abstract class PagedList<T> extends AbstractList<T> { @NonNull final Config mConfig; @NonNull - final PagedStorage<?, T> mStorage; + final PagedStorage<T> mStorage; int mLastLoad = 0; T mLastItem = null; @@ -123,7 +123,7 @@ public abstract class PagedList<T> extends AbstractList<T> { protected final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); - PagedList(@NonNull PagedStorage<?, T> storage, + PagedList(@NonNull PagedStorage<T> storage, @NonNull Executor mainThreadExecutor, @NonNull Executor backgroundThreadExecutor, @Nullable BoundaryCallback<T> boundaryCallback, @@ -162,7 +162,8 @@ public abstract class PagedList<T> extends AbstractList<T> { if (dataSource.isContiguous() || !config.enablePlaceholders) { if (!dataSource.isContiguous()) { //noinspection unchecked - dataSource = (DataSource<K, T>) ((TiledDataSource<T>) dataSource).getAsContiguous(); + dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource) + .wrapAsContiguousWithoutPlaceholders(); } ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource; return new ContiguousPagedList<>(contigDataSource, @@ -172,7 +173,7 @@ public abstract class PagedList<T> extends AbstractList<T> { config, key); } else { - return new TiledPagedList<>((TiledDataSource<T>) dataSource, + return new TiledPagedList<>((PositionalDataSource<T>) dataSource, mainThreadExecutor, backgroundThreadExecutor, boundaryCallback, @@ -184,18 +185,14 @@ public abstract class PagedList<T> extends AbstractList<T> { /** * Builder class for PagedList. * <p> - * DataSource, main thread and background executor, and Config must all be provided. + * DataSource, Config, main thread and background executor must all be provided. * <p> - * A valid PagedList may not be constructed without data, so building a PagedList queries - * initial data from the data source. This is done because it's generally undesired to present a - * PagedList with no data in it to the UI. It's better to present initial data, so that the UI - * doesn't show an empty list, or placeholders for a few frames, just before showing initial - * content. + * A PagedList queries initial data from its DataSource during construction, to avoid empty + * PagedLists being presented to the UI when possible. It's preferred to present initial data, + * so that the UI doesn't show an empty list, or placeholders for a few frames, just before + * showing initial content. * <p> - * Because PagedLists are initialized with data, PagedLists must be built on a background - * thread. - * <p> - * {@link LivePagedListProvider} does this creation on a background thread automatically, if you + * {@link LivePagedListBuilder} does this creation on a background thread automatically, if you * want to receive a {@code LiveData<PagedList<...>>}. * * @param <Key> Type of key used to load data from the DataSource. @@ -203,26 +200,48 @@ public abstract class PagedList<T> extends AbstractList<T> { */ @SuppressWarnings("WeakerAccess") public static class Builder<Key, Value> { - private DataSource<Key, Value> mDataSource; + private final DataSource<Key, Value> mDataSource; + private final Config mConfig; private Executor mMainThreadExecutor; private Executor mBackgroundThreadExecutor; private BoundaryCallback mBoundaryCallback; - private Config mConfig; private Key mInitialKey; /** - * The source of data that the PagedList should load from. - * @param dataSource Source of data for the PagedList. + * Create a PagedList.Builder with the provided {@link DataSource} and {@link Config}. * - * @return this + * @param dataSource DataSource the PagedList will load from. + * @param config Config that defines how the PagedList loads data from its DataSource. */ - @NonNull - public Builder<Key, Value> setDataSource(@NonNull DataSource<Key, Value> dataSource) { + public Builder(@NonNull DataSource<Key, Value> dataSource, @NonNull Config config) { + //noinspection ConstantConditions + if (dataSource == null) { + throw new IllegalArgumentException("DataSource may not be null"); + } + //noinspection ConstantConditions + if (config == null) { + throw new IllegalArgumentException("Config may not be null"); + } mDataSource = dataSource; - return this; + mConfig = config; } /** + * Create a PagedList.Builder with the provided {@link DataSource} and page size. + * <p> + * This method is a convenience for: + * <pre> + * PagedList.Builder(dataSource, + * new PagedList.Config.Builder().setPageSize(pageSize).build()); + * </pre> + * + * @param dataSource DataSource the PagedList will load from. + * @param pageSize Config that defines how the PagedList loads data from its DataSource. + */ + public Builder(@NonNull DataSource<Key, Value> dataSource, int pageSize) { + this(dataSource, new PagedList.Config.Builder().setPageSize(pageSize).build()); + } + /** * The executor defining where main/UI thread for page loading updates. * * @param mainThreadExecutor Executor for main/UI thread to receive {@link Callback} calls. @@ -250,24 +269,19 @@ public abstract class PagedList<T> extends AbstractList<T> { return this; } - @NonNull - public Builder<Key, Value> setBoundaryCallback( - @Nullable BoundaryCallback boundaryCallback) { - mBoundaryCallback = boundaryCallback; - return this; - } - - /** - * The Config defining how the PagedList should load from the DataSource. - * - * @param config The config that will define how the PagedList loads from the DataSource. + * The BoundaryCallback for out of data events. + * <p> + * Pass a BoundaryCallback to listen to when the PagedList runs out of data to load. * + * @param boundaryCallback BoundaryCallback for listening to out-of-data events. * @return this */ + @SuppressWarnings("unused") @NonNull - public Builder<Key, Value> setConfig(@NonNull Config config) { - mConfig = config; + public Builder<Key, Value> setBoundaryCallback( + @Nullable BoundaryCallback boundaryCallback) { + mBoundaryCallback = boundaryCallback; return this; } @@ -286,8 +300,21 @@ public abstract class PagedList<T> extends AbstractList<T> { /** * Creates a {@link PagedList} with the given parameters. * <p> - * This call will initial data and perform any counting needed to initialize the PagedList, - * therefore it should only be called on a worker thread. + * This call will dispatch the {@link DataSource}'s loadInitial method immediately. If a + * DataSource posts all of its work (e.g. to a network thread), the PagedList will + * be immediately created as empty, and grow to its initial size when the initial load + * completes. + * <p> + * If the DataSource implements its load synchronously, doing the load work immediately in + * the loadInitial method, the PagedList will block on that load before completing + * construction. In this case, use a background thread to create a PagedList. + * <p> + * It's fine to create a PagedList with an async DataSource on the main thread, such as in + * the constructor of a ViewModel. An async network load won't block the initialLoad + * function. For a synchronous DataSource such as one created from a Room database, a + * {@code LiveData<PagedList>} can be safely constructed with {@link LivePagedListBuilder} + * on the main thread, since actual construction work is deferred, and done on a background + * thread. * <p> * While build() will always return a PagedList, it's important to note that the PagedList * initial load may fail to acquire data from the DataSource. This can happen for example if @@ -300,18 +327,13 @@ public abstract class PagedList<T> extends AbstractList<T> { @WorkerThread @NonNull public PagedList<Value> build() { - if (mDataSource == null) { - throw new IllegalArgumentException("DataSource required"); - } + // TODO: define defaults, once they can be used in module without android dependency if (mMainThreadExecutor == null) { throw new IllegalArgumentException("MainThreadExecutor required"); } if (mBackgroundThreadExecutor == null) { throw new IllegalArgumentException("BackgroundThreadExecutor required"); } - if (mConfig == null) { - throw new IllegalArgumentException("Config required"); - } //noinspection unchecked return PagedList.create( @@ -451,13 +473,11 @@ public abstract class PagedList<T> extends AbstractList<T> { // safe to deref mBoundaryCallback here, since we only defer if mBoundaryCallback present if (begin) { //noinspection ConstantConditions - mBoundaryCallback.onItemAtFrontLoaded( - snapshot(), mStorage.getFirstLoadedItem(), mStorage.size()); + mBoundaryCallback.onItemAtFrontLoaded(mStorage.getFirstLoadedItem()); } if (end) { //noinspection ConstantConditions - mBoundaryCallback.onItemAtEndLoaded( - snapshot(), mStorage.getLastLoadedItem(), mStorage.size()); + mBoundaryCallback.onItemAtEndLoaded(mStorage.getLastLoadedItem()); } } @@ -501,7 +521,6 @@ public abstract class PagedList<T> extends AbstractList<T> { if (isImmutable()) { return this; } - return new SnapshotPagedList<>(this); } @@ -522,7 +541,7 @@ public abstract class PagedList<T> extends AbstractList<T> { * <p> * When a PagedList is invalidated, you can pass the key returned by this function to initialize * the next PagedList. This ensures (depending on load times) that the next PagedList that - * arrives will have data that overlaps. If you use {@link LivePagedListProvider}, it will do + * arrives will have data that overlaps. If you use {@link LivePagedListBuilder}, it will do * this for you. * * @return Key of position most recently passed to {@link #loadAround(int)}. @@ -556,8 +575,8 @@ public abstract class PagedList<T> extends AbstractList<T> { /** * Position offset of the data in the list. * <p> - * If data is supplied by a {@link TiledDataSource}, the item returned from <code>get(i)</code> - * has a position of <code>i + getPositionOffset()</code>. + * If data is supplied by a {@link PositionalDataSource}, the item returned from + * <code>get(i)</code> has a position of <code>i + getPositionOffset()</code>. * <p> * If the DataSource is a {@link KeyedDataSource}, and thus doesn't use positions, returns 0. */ @@ -583,15 +602,25 @@ public abstract class PagedList<T> extends AbstractList<T> { * GC'd. * * @param previousSnapshot Snapshot previously captured from this List, or null. - * @param callback Callback to dispatch to. + * @param callback LoadCallback to dispatch to. * @see #removeWeakCallback(Callback) */ @SuppressWarnings("WeakerAccess") public void addWeakCallback(@Nullable List<T> previousSnapshot, @NonNull Callback callback) { if (previousSnapshot != null && previousSnapshot != this) { - PagedList<T> storageSnapshot = (PagedList<T>) previousSnapshot; - //noinspection unchecked - dispatchUpdatesSinceSnapshot(storageSnapshot, callback); + + if (previousSnapshot.isEmpty()) { + if (!mStorage.isEmpty()) { + // If snapshot is empty, diff is trivial - just notify number new items. + // Note: occurs in async init, when snapshot taken before init page arrives + callback.onInserted(0, mStorage.size()); + } + } else { + PagedList<T> storageSnapshot = (PagedList<T>) previousSnapshot; + + //noinspection unchecked + dispatchUpdatesSinceSnapshot(storageSnapshot, callback); + } } // first, clean up any empty weak refs @@ -608,7 +637,7 @@ public abstract class PagedList<T> extends AbstractList<T> { /** * Removes a previously added callback. * - * @param callback Callback, previously added. + * @param callback LoadCallback, previously added. * @see #addWeakCallback(List, Callback) */ @SuppressWarnings("WeakerAccess") @@ -645,6 +674,14 @@ public abstract class PagedList<T> extends AbstractList<T> { } } + + + /** + * Dispatch updates since the non-empty snapshot was taken. + * + * @param snapshot Non-empty snapshot. + * @param callback LoadCallback for updates that have occurred since snapshot. + */ abstract void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> snapshot, @NonNull Callback callback); @@ -826,13 +863,6 @@ public abstract class PagedList<T> extends AbstractList<T> { * This value is typically larger than page size, so on first load data there's a large * enough range of content loaded to cover small scrolls. * <p> - * If used with a {@link TiledDataSource}, this value is rounded to the nearest number - * of pages, with a minimum of two pages, and loaded with a single call to - * {@link TiledDataSource#loadRange(int, int)}. - * <p> - * If used with a {@link KeyedDataSource}, this value will be passed to - * {@link KeyedDataSource#loadInitial(int)}. - * <p> * If not set, defaults to three times page size. * * @param initialLoadSizeHint Number of items to load while initializing the PagedList. @@ -873,13 +903,43 @@ public abstract class PagedList<T> extends AbstractList<T> { } /** - * WIP API for load-more-into-local-storage callbacks + * Signals when a PagedList has reached the end of available data. + * <p> + * This can be used to implement paging from the network into a local database - when the + * database has no more data to present, a BoundaryCallback can be used to fetch more data. + * <p> + * If an instance is shared across multiple PagedLists (e.g. when passed to + * {@link LivePagedListBuilder#setBoundaryCallback}), the callbacks may be issued multiple + * times. If for example {@link #onItemAtEndLoaded(Object)} triggers a network load, it should + * avoid triggering it again while the load is ongoing. + * + * @param <T> Type loaded by the PagedList. */ + @MainThread public abstract static class BoundaryCallback<T> { - public abstract void onZeroItemsLoaded(); - public abstract void onItemAtFrontLoaded(@NonNull List<T> pagedListSnapshot, - @NonNull T itemAtFront, int pagedListSize); - public abstract void onItemAtEndLoaded(@NonNull List<T> pagedListSnapshot, - @NonNull T itemAtEnd, int pagedListSize); + /** + * Called when zero items are returned from an initial load of the PagedList's data source. + */ + public void onZeroItemsLoaded() {} + + /** + * Called when the item at the front of the PagedList has been loaded, and access has + * occurred within {@link Config#prefetchDistance} of it. + * <p> + * No more data will be prepended to the PagedList before this item. + * + * @param itemAtFront The first item of PagedList + */ + public void onItemAtFrontLoaded(@NonNull T itemAtFront) {} + + /** + * Called when the item at the end of the PagedList has been loaded, and access has + * occurred within {@link Config#prefetchDistance} of it. + * <p> + * No more data will be appended to the PagedList after this item. + * + * @param itemAtEnd The first item of PagedList + */ + public void onItemAtEndLoaded(@NonNull T itemAtEnd) {} } } diff --git a/android/arch/paging/PagedListAdapter.java b/android/arch/paging/PagedListAdapter.java index 89b9c2ee..a8158c23 100644 --- a/android/arch/paging/PagedListAdapter.java +++ b/android/arch/paging/PagedListAdapter.java @@ -44,18 +44,14 @@ import android.support.v7.widget.RecyclerView; * {@literal @}Dao * interface UserDao { * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC") - * public abstract LivePagedListProvider<Integer, User> usersByLastName(); + * public abstract DataSource.Factory<Integer, User> usersByLastName(); * } * * class MyViewModel extends ViewModel { * public final LiveData<PagedList<User>> usersList; * public MyViewModel(UserDao userDao) { - * usersList = userDao.usersByLastName().create( - * /* initial load position {@literal *}/ 0, - * new PagedList.Config.Builder() - * .setPageSize(50) - * .setPrefetchDistance(50) - * .build()); + * usersList = LivePagedListBuilder<>( + * userDao.usersByLastName(), /* page size {@literal *}/ 20).build(); * } * } * diff --git a/android/arch/paging/PagedListAdapterHelper.java b/android/arch/paging/PagedListAdapterHelper.java index 51a6e37f..7a0b81a6 100644 --- a/android/arch/paging/PagedListAdapterHelper.java +++ b/android/arch/paging/PagedListAdapterHelper.java @@ -48,18 +48,14 @@ import android.support.v7.widget.RecyclerView; * {@literal @}Dao * interface UserDao { * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC") - * public abstract LivePagedListProvider<Integer, User> usersByLastName(); + * public abstract DataSource.Factory<Integer, User> usersByLastName(); * } * * class MyViewModel extends ViewModel { * public final LiveData<PagedList<User>> usersList; * public MyViewModel(UserDao userDao) { - * usersList = userDao.usersByLastName().create( - * /* initial load position {@literal *}/ 0, - * new PagedList.Config.Builder() - * .setPageSize(50) - * .setPrefetchDistance(50) - * .build()); + * usersList = LivePagedListBuilder<>( + * userDao.usersByLastName(), /* page size {@literal *}/ 20).build(); * } * } * diff --git a/android/arch/paging/PagedStorage.java b/android/arch/paging/PagedStorage.java index b857462b..d4531d33 100644 --- a/android/arch/paging/PagedStorage.java +++ b/android/arch/paging/PagedStorage.java @@ -17,13 +17,21 @@ package android.arch.paging; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import java.util.AbstractList; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -final class PagedStorage<K, V> extends AbstractList<V> { +final class PagedStorage<T> extends AbstractList<T> { + /** + * Lists instances are compared (with instance equality) to PLACEHOLDER_LIST to check if an item + * in that position is already loading. We use a singleton placeholder list that is distinct + * from Collections.EMPTY_LIST for safety. + */ + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") + private static final List PLACEHOLDER_LIST = new ArrayList(); + // Always set private int mLeadingNullCount; /** @@ -37,7 +45,7 @@ final class PagedStorage<K, V> extends AbstractList<V> { * Non-contiguous - mPages may have nulls or a placeholder page, isTiled() always returns true. * mPages may have nulls, or placeholder (empty) pages while content is loading. */ - private final ArrayList<Page<K, V>> mPages; + private final ArrayList<List<T>> mPages; private int mTrailingNullCount; private int mPositionOffset; @@ -53,9 +61,6 @@ final class PagedStorage<K, V> extends AbstractList<V> { private int mNumberPrepended; private int mNumberAppended; - // only used in tiling case - private Page<K, V> mPlaceholderPage; - PagedStorage() { mLeadingNullCount = 0; mPages = new ArrayList<>(); @@ -67,12 +72,12 @@ final class PagedStorage<K, V> extends AbstractList<V> { mNumberAppended = 0; } - PagedStorage(int leadingNulls, Page<K, V> page, int trailingNulls) { + PagedStorage(int leadingNulls, List<T> page, int trailingNulls) { this(); init(leadingNulls, page, trailingNulls, 0); } - private PagedStorage(PagedStorage<K, V> other) { + private PagedStorage(PagedStorage<T> other) { mLeadingNullCount = other.mLeadingNullCount; mPages = new ArrayList<>(other.mPages); mTrailingNullCount = other.mTrailingNullCount; @@ -81,40 +86,37 @@ final class PagedStorage<K, V> extends AbstractList<V> { mPageSize = other.mPageSize; mNumberPrepended = other.mNumberPrepended; mNumberAppended = other.mNumberAppended; - - // preserve placeholder page so we can locate placeholder pages if needed later - mPlaceholderPage = other.mPlaceholderPage; } - PagedStorage<K, V> snapshot() { + PagedStorage<T> snapshot() { return new PagedStorage<>(this); } - private void init(int leadingNulls, Page<K, V> page, int trailingNulls, int positionOffset) { + private void init(int leadingNulls, List<T> page, int trailingNulls, int positionOffset) { mLeadingNullCount = leadingNulls; mPages.clear(); mPages.add(page); mTrailingNullCount = trailingNulls; mPositionOffset = positionOffset; - mStorageCount = page.items.size(); + mStorageCount = page.size(); // initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled // even if it will break if nulls convert. - mPageSize = page.items.size(); + mPageSize = page.size(); mNumberPrepended = 0; mNumberAppended = 0; } - void init(int leadingNulls, Page<K, V> page, int trailingNulls, int positionOffset, + void init(int leadingNulls, @NonNull List<T> page, int trailingNulls, int positionOffset, @NonNull Callback callback) { init(leadingNulls, page, trailingNulls, positionOffset); callback.onInitialized(size()); } @Override - public V get(int i) { + public T get(int i) { if (i < 0 || i >= size()) { throw new IndexOutOfBoundsException("Index: " + i + ", Size: " + size()); } @@ -138,7 +140,7 @@ final class PagedStorage<K, V> extends AbstractList<V> { pageInternalIndex = localIndex; final int localPageCount = mPages.size(); for (localPageIndex = 0; localPageIndex < localPageCount; localPageIndex++) { - int pageSize = mPages.get(localPageIndex).items.size(); + int pageSize = mPages.get(localPageIndex).size(); if (pageSize > pageInternalIndex) { // stop, found the page break; @@ -147,12 +149,12 @@ final class PagedStorage<K, V> extends AbstractList<V> { } } - Page<?, V> page = mPages.get(localPageIndex); - if (page == null || page.items.size() == 0) { + List<T> page = mPages.get(localPageIndex); + if (page == null || page.size() == 0) { // can only occur in tiled case, with untouched inner/placeholder pages return null; } - return page.items.get(pageInternalIndex); + return page.get(pageInternalIndex); } /** @@ -207,8 +209,8 @@ final class PagedStorage<K, V> extends AbstractList<V> { int total = mLeadingNullCount; final int pageCount = mPages.size(); for (int i = 0; i < pageCount; i++) { - Page page = mPages.get(i); - if (page != null && page != mPlaceholderPage) { + List page = mPages.get(i); + if (page != null && page != PLACEHOLDER_LIST) { break; } total += mPageSize; @@ -219,8 +221,8 @@ final class PagedStorage<K, V> extends AbstractList<V> { int computeTrailingNulls() { int total = mTrailingNullCount; for (int i = mPages.size() - 1; i >= 0; i--) { - Page page = mPages.get(i); - if (page != null && page != mPlaceholderPage) { + List page = mPages.get(i); + if (page != null && page != PLACEHOLDER_LIST) { break; } total += mPageSize; @@ -230,21 +232,21 @@ final class PagedStorage<K, V> extends AbstractList<V> { // ---------------- Contiguous API ------------------- - V getFirstLoadedItem() { + T getFirstLoadedItem() { // safe to access first page's first item here: // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty - return mPages.get(0).items.get(0); + return mPages.get(0).get(0); } - V getLastLoadedItem() { + T getLastLoadedItem() { // safe to access last page's last item here: // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty - Page<K, V> page = mPages.get(mPages.size() - 1); - return page.items.get(page.items.size() - 1); + List<T> page = mPages.get(mPages.size() - 1); + return page.get(page.size() - 1); } - public void prependPage(@NonNull Page<K, V> page, @NonNull Callback callback) { - final int count = page.items.size(); + void prependPage(@NonNull List<T> page, @NonNull Callback callback) { + final int count = page.size(); if (count == 0) { // Nothing returned from source, stop loading in this direction return; @@ -274,8 +276,8 @@ final class PagedStorage<K, V> extends AbstractList<V> { callback.onPagePrepended(mLeadingNullCount, changedCount, addedCount); } - public void appendPage(@NonNull Page<K, V> page, @NonNull Callback callback) { - final int count = page.items.size(); + void appendPage(@NonNull List<T> page, @NonNull Callback callback) { + final int count = page.size(); if (count == 0) { // Nothing returned from source, stop loading in this direction return; @@ -284,7 +286,7 @@ final class PagedStorage<K, V> extends AbstractList<V> { if (mPageSize > 0) { // if the previous page was smaller than mPageSize, // or if this page is larger than the previous, disable tiling - if (mPages.get(mPages.size() - 1).items.size() != mPageSize + if (mPages.get(mPages.size() - 1).size() != mPageSize || count > mPageSize) { mPageSize = -1; } @@ -306,8 +308,30 @@ final class PagedStorage<K, V> extends AbstractList<V> { // ------------------ Non-Contiguous API (tiling required) ---------------------- - public void insertPage(int position, @NonNull Page<K, V> page, Callback callback) { - final int newPageSize = page.items.size(); + void initAndSplit(int leadingNulls, @NonNull List<T> multiPageList, + int trailingNulls, int positionOffset, int pageSize, @NonNull Callback callback) { + + int pageCount = (multiPageList.size() + (pageSize - 1)) / pageSize; + for (int i = 0; i < pageCount; i++) { + int beginInclusive = i * pageSize; + int endExclusive = Math.min(multiPageList.size(), (i + 1) * pageSize); + + List<T> sublist = multiPageList.subList(beginInclusive, endExclusive); + + if (i == 0) { + // Trailing nulls for first page includes other pages in multiPageList + int initialTrailingNulls = trailingNulls + multiPageList.size() - sublist.size(); + init(leadingNulls, sublist, initialTrailingNulls, positionOffset); + } else { + int insertPosition = leadingNulls + beginInclusive; + insertPage(insertPosition, sublist, null); + } + } + callback.onInitialized(size()); + } + + public void insertPage(int position, @NonNull List<T> page, @Nullable Callback callback) { + final int newPageSize = page.size(); if (newPageSize != mPageSize) { // differing page size is OK in 2 cases, when the page is being added: // 1) to the end (in which case, ignore new smaller size) @@ -334,22 +358,15 @@ final class PagedStorage<K, V> extends AbstractList<V> { int localPageIndex = pageIndex - mLeadingNullCount / mPageSize; - Page<K, V> oldPage = mPages.get(localPageIndex); - if (oldPage != null && oldPage != mPlaceholderPage) { + List<T> oldPage = mPages.get(localPageIndex); + if (oldPage != null && oldPage != PLACEHOLDER_LIST) { throw new IllegalArgumentException( "Invalid position " + position + ": data already loaded"); } mPages.set(localPageIndex, page); - callback.onPageInserted(position, page.items.size()); - } - - private Page<K, V> getPlaceholderPage() { - if (mPlaceholderPage == null) { - @SuppressWarnings("unchecked") - List<V> list = Collections.emptyList(); - mPlaceholderPage = new Page<>(null, list, null); + if (callback != null) { + callback.onPageInserted(position, page.size()); } - return mPlaceholderPage; } private void allocatePageRange(final int minimumPage, final int maximumPage) { @@ -399,7 +416,8 @@ final class PagedStorage<K, V> extends AbstractList<V> { for (int pageIndex = minimumPage; pageIndex <= maximumPage; pageIndex++) { int localPageIndex = pageIndex - leadingNullPages; if (mPages.get(localPageIndex) == null) { - mPages.set(localPageIndex, getPlaceholderPage()); + //noinspection unchecked + mPages.set(localPageIndex, PLACEHOLDER_LIST); callback.onPagePlaceholderInserted(pageIndex); } } @@ -414,9 +432,9 @@ final class PagedStorage<K, V> extends AbstractList<V> { return false; } - Page<K, V> page = mPages.get(index - leadingNullPages); + List<T> page = mPages.get(index - leadingNullPages); - return page != null && page != mPlaceholderPage; + return page != null && page != PLACEHOLDER_LIST; } @Override diff --git a/android/arch/paging/PagedStorageDiffHelper.java b/android/arch/paging/PagedStorageDiffHelper.java index 6fc70390..d991b723 100644 --- a/android/arch/paging/PagedStorageDiffHelper.java +++ b/android/arch/paging/PagedStorageDiffHelper.java @@ -26,8 +26,8 @@ class PagedStorageDiffHelper { } static <T> DiffUtil.DiffResult computeDiff( - final PagedStorage<?, T> oldList, - final PagedStorage<?, T> newList, + final PagedStorage<T> oldList, + final PagedStorage<T> newList, final DiffCallback<T> diffCallback) { final int oldOffset = oldList.computeLeadingNulls(); final int newOffset = newList.computeLeadingNulls(); @@ -131,8 +131,8 @@ class PagedStorageDiffHelper { * immediately after dispatching this diff. */ static <T> void dispatchDiff(ListUpdateCallback callback, - final PagedStorage<?, T> oldList, - final PagedStorage<?, T> newList, + final PagedStorage<T> oldList, + final PagedStorage<T> newList, final DiffUtil.DiffResult diffResult) { final int trailingOld = oldList.computeTrailingNulls(); diff --git a/android/arch/paging/PositionalDataSource.java b/android/arch/paging/PositionalDataSource.java index fa2932ad..d3946370 100644 --- a/android/arch/paging/PositionalDataSource.java +++ b/android/arch/paging/PositionalDataSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. @@ -20,115 +20,284 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; +import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; /** - * Incremental data loader for paging positional content, where content can be loaded based on its - * integer position. + * Position-based data loader for a fixed-size, countable data set, supporting loads at arbitrary + * positions. * <p> - * Use PositionalDataSource if you only need position as input for item loading - if for example, - * you're asking the backend for items at positions 10 through 20, or using a limit/offset database - * query to load items at query position 10 through 20. + * Extend PositionalDataSource if you can support counting your data set, and loading based on + * position information. * <p> - * Implement a DataSource using PositionalDataSource if position is the only information you need to - * load items. + * Note that unless {@link PagedList.Config#enablePlaceholders placeholders are disabled} + * PositionalDataSource requires counting the size of the dataset. This allows pages to be tiled in + * at arbitrary, non-contiguous locations based upon what the user observes in a {@link PagedList}. * <p> - * Note that {@link BoundedDataSource} provides a simpler API for positional loading, if your - * backend or data store doesn't require - * <p> - * @param <Value> Value type of items being loaded by the DataSource. + * Room can generate a Factory of PositionalDataSources for you: + * <pre> + * {@literal @}Dao + * interface UserDao { + * {@literal @}Query("SELECT * FROM user ORDER BY mAge DESC") + * public abstract DataSource.Factory<Integer, User> loadUsersByAgeDesc(); + * }</pre> + * + * @param <T> Type of items being loaded by the PositionalDataSource. */ -abstract class PositionalDataSource<Value> extends ContiguousDataSource<Integer, Value> { - +public abstract class PositionalDataSource<T> extends DataSource<Integer, T> { /** - * Number of items that this DataSource can provide in total, or COUNT_UNDEFINED. + * Callback for PositionalDataSource initial loading methods to return data, position, and + * (optionally) count information. + * <p> + * A callback can be called only once, and will throw if called again. + * <p> + * It is always valid for a DataSource loading method that takes a callback to stash the + * callback and call it later. This enables DataSources to be fully asynchronous, and to handle + * temporary, recoverable error states (such as a network error that can be retried). * - * @return number of items that this DataSource can provide in total, or COUNT_UNDEFINED - * if difficult or undesired to compute. + * @param <T> Type of items being loaded. */ - public int countItems() { - return COUNT_UNDEFINED; - } + public static class InitialLoadCallback<T> extends BaseLoadCallback<T> { + private final boolean mCountingEnabled; + private final int mPageSize; - @Nullable - @Override - List<Value> loadAfterImpl(int currentEndIndex, @NonNull Value currentEndItem, int pageSize) { - return loadAfter(currentEndIndex + 1, pageSize); - } + InitialLoadCallback(@NonNull PositionalDataSource dataSource, boolean countingEnabled, + int pageSize, PageResult.Receiver<T> receiver) { + super(PageResult.INIT, dataSource, null, receiver); + mCountingEnabled = countingEnabled; + mPageSize = pageSize; + if (mPageSize < 1) { + throw new IllegalArgumentException("Page size must be non-negative"); + } + } - @Nullable - @Override - List<Value> loadBeforeImpl(int currentBeginIndex, @NonNull Value currentBeginItem, - int pageSize) { - return loadBefore(currentBeginIndex - 1, pageSize); - } + /** + * Called to pass initial load state from a DataSource. + * <p> + * Call this method from your DataSource's {@code loadInitial} function to return data, + * and inform how many placeholders should be shown before and after. If counting is cheap + * to compute (for example, if a network load returns the information regardless), it's + * recommended to pass data back through this method. + * + * @param data List of items loaded from the DataSource. If this is empty, the DataSource + * is treated as empty, and no further loads will occur. + * @param position Position of the item at the front of the list. If there are {@code N} + * items before the items in data that can be loaded from this DataSource, + * pass {@code N}. + * @param totalCount Total number of items that may be returned from this DataSource. + * Includes the number in the initial {@code data} parameter + * as well as any items that can be loaded in front or behind of + * {@code data}. + */ + public void onResult(@NonNull List<T> data, int position, int totalCount) { + validateInitialLoadParams(data, position, totalCount); + if (position + data.size() != totalCount + && data.size() % mPageSize != 0) { + throw new IllegalArgumentException("PositionalDataSource requires initial load size" + + " to be a multiple of page size to support internal tiling."); + } - @Override - void loadInitial(Integer position, int initialLoadSize, boolean enablePlaceholders, - @NonNull PageResult.Receiver<Integer, Value> receiver) { + if (mCountingEnabled) { + int trailingUnloadedCount = totalCount - position - data.size(); + dispatchResultToReceiver( + new PageResult<>(data, position, trailingUnloadedCount, 0)); + } else { + // Only occurs when wrapped as contiguous + dispatchResultToReceiver(new PageResult<>(data, position)); + } + } - final int convertPosition = position == null ? 0 : position; - final int loadPosition = Math.max(0, (convertPosition - initialLoadSize / 2)); + /** + * Called to pass initial load state from a DataSource without supporting placeholders. + * <p> + * Call this method from your DataSource's {@code loadInitial} function to return data, + * if position is known but total size is not. If counting is not expensive, consider + * calling the three parameter variant: {@link #onResult(List, int, int)}. + * + * @param data List of items loaded from the DataSource. If this is empty, the DataSource + * is treated as empty, and no further loads will occur. + * @param position Position of the item at the front of the list. If there are {@code N} + * items before the items in data that can be provided by this DataSource, + * pass {@code N}. + */ + void onResult(@NonNull List<T> data, int position) { + // not counting, don't need to check mAcceptCount + dispatchResultToReceiver(new PageResult<>( + data, 0, 0, position)); + } + } - int count = COUNT_UNDEFINED; - if (enablePlaceholders) { - count = countItems(); + /** + * Callback for PositionalDataSource {@link #loadRange(int, int, LoadCallback)} methods + * to return data. + * <p> + * A callback can be called only once, and will throw if called again. + * <p> + * It is always valid for a DataSource loading method that takes a callback to stash the + * callback and call it later. This enables DataSources to be fully asynchronous, and to handle + * temporary, recoverable error states (such as a network error that can be retried). + * + * @param <T> Type of items being loaded. + */ + public static class LoadCallback<T> extends BaseLoadCallback<T> { + private final int mPositionOffset; + LoadCallback(@NonNull PositionalDataSource dataSource, int positionOffset, + Executor mainThreadExecutor, PageResult.Receiver<T> receiver) { + super(PageResult.TILE, dataSource, mainThreadExecutor, receiver); + mPositionOffset = positionOffset; } - List<Value> data = loadAfter(loadPosition, initialLoadSize); - if (data == null) { - receiver.onPageResult(new PageResult<Integer, Value>(PageResult.INIT)); - return; + /** + * Called to pass loaded data from a DataSource. + * <p> + * Call this method from your DataSource's {@code load} methods to return data. + * + * @param data List of items loaded from the DataSource. + */ + public void onResult(@NonNull List<T> data) { + dispatchResultToReceiver(new PageResult<>( + data, 0, 0, mPositionOffset)); } + } + + void loadInitial(boolean acceptCount, + int requestedStartPosition, int requestedLoadSize, int pageSize, + @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) { + InitialLoadCallback<T> callback = + new InitialLoadCallback<>(this, acceptCount, pageSize, receiver); + loadInitial(requestedStartPosition, requestedLoadSize, pageSize, callback); - final boolean uncounted = count == COUNT_UNDEFINED; - int leadingNullCount = uncounted ? 0 : loadPosition; - int trailingNullCount = uncounted ? 0 : count - leadingNullCount - data.size(); - int positionOffset = uncounted ? loadPosition : 0; - - receiver.onPageResult(new PageResult<>( - PageResult.INIT, - new Page<Integer, Value>(data), - leadingNullCount, - trailingNullCount, - positionOffset)); + // If initialLoad's callback is not called within the body, we force any following calls + // to post to the UI thread. This constructor may be run on a background thread, but + // after constructor, mutation must happen on UI thread. + callback.setPostExecutor(mainThreadExecutor); + } + + void loadRange(int startPosition, int count, + @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) { + LoadCallback<T> callback = + new LoadCallback<>(this, startPosition, mainThreadExecutor, receiver); + if (count == 0) { + callback.onResult(Collections.<T>emptyList()); + } else { + loadRange(startPosition, count, callback); + } } /** - * Load data after currently loaded content, starting at the provided index. + * Load initial list data. * <p> - * It's valid to return a different list size than the page size, if it's easier for this data - * source. It is generally safer to increase the number loaded than reduce. + * This method is called to load the initial page(s) from the DataSource. + * <p> + * Result list must be a multiple of pageSize to enable efficient tiling. * - * @param startIndex Load items starting at this index. - * @param pageSize Suggested number of items to load. - * @return List of items, starting at position currentEndIndex + 1. Null if the data source is - * no longer valid, and should not be queried again. + * @param requestedStartPosition Initial load position requested. Note that this may not be + * within the bounds of your data set, it should be corrected + * before you make your query. + * @param requestedLoadSize Requested number of items to load. Note that this may be larger than + * available data. + * @param pageSize Defines page size acceptable for return values. List of items passed to the + * callback must be an integer multiple of page size. + * @param callback DataSource.InitialLoadCallback that receives initial load data, including + * position and total data set size. */ @WorkerThread - @Nullable - public abstract List<Value> loadAfter(int startIndex, int pageSize); + public abstract void loadInitial(int requestedStartPosition, int requestedLoadSize, + int pageSize, @NonNull InitialLoadCallback<T> callback); /** - * Load data before the currently loaded content, starting at the provided index. + * Called to load a range of data from the DataSource. + * <p> + * This method is called to load additional pages from the DataSource after the + * InitialLoadCallback passed to loadInitial has initialized a PagedList. * <p> - * It's valid to return a different list size than the page size, if it's easier for this data - * source. It is generally safer to increase the number loaded than reduce. + * Unlike {@link #loadInitial(int, int, int, InitialLoadCallback)}, this method must return the + * number of items requested, at the position requested. * - * @param startIndex Load items, starting at this index. - * @param pageSize Suggested number of items to load. - * @return List of items, in descending order, starting at position currentBeginIndex - 1. Null - * if the data source is no longer valid, and should not be queried again. + * @param startPosition Initial load position. + * @param count Number of items to load. + * @param callback DataSource.LoadCallback that receives loaded data. */ @WorkerThread - @Nullable - public abstract List<Value> loadBefore(int startIndex, int pageSize); + public abstract void loadRange(int startPosition, int count, @NonNull LoadCallback<T> callback); @Override - Integer getKey(int position, Value item) { - if (position < 0) { - return null; + boolean isContiguous() { + return false; + } + + + @NonNull + ContiguousDataSource<Integer, T> wrapAsContiguousWithoutPlaceholders() { + return new ContiguousWithoutPlaceholdersWrapper<>(this); + } + + static int computeFirstLoadPosition(int position, int firstLoadSize, int pageSize, int size) { + int roundedPageStart = Math.round(position / pageSize) * pageSize; + + // maximum start pos is that which will encompass end of list + int maximumLoadPage = ((size - firstLoadSize + pageSize - 1) / pageSize) * pageSize; + roundedPageStart = Math.min(maximumLoadPage, roundedPageStart); + + // minimum start position is 0 + roundedPageStart = Math.max(0, roundedPageStart); + + return roundedPageStart; + } + + @SuppressWarnings("deprecation") + static class ContiguousWithoutPlaceholdersWrapper<Value> + extends ContiguousDataSource<Integer, Value> { + + @NonNull + final PositionalDataSource<Value> mPositionalDataSource; + + ContiguousWithoutPlaceholdersWrapper( + @NonNull PositionalDataSource<Value> positionalDataSource) { + mPositionalDataSource = positionalDataSource; + } + + @Override + void loadInitial(@Nullable Integer position, int initialLoadSize, int pageSize, + boolean enablePlaceholders, @NonNull Executor mainThreadExecutor, + @NonNull PageResult.Receiver<Value> receiver) { + final int convertPosition = position == null ? 0 : position; + + // Note enablePlaceholders will be false here, but we don't have a way to communicate + // this to PositionalDataSource. This is fine, because only the list and its position + // offset will be consumed by the InitialLoadCallback. + mPositionalDataSource.loadInitial(false, convertPosition, initialLoadSize, + pageSize, mainThreadExecutor, receiver); + } + + @Override + void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize, + @NonNull Executor mainThreadExecutor, + @NonNull PageResult.Receiver<Value> receiver) { + int startIndex = currentEndIndex + 1; + mPositionalDataSource.loadRange(startIndex, pageSize, mainThreadExecutor, receiver); + } + + @Override + void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize, + @NonNull Executor mainThreadExecutor, + @NonNull PageResult.Receiver<Value> receiver) { + + int startIndex = currentBeginIndex - 1; + if (startIndex < 0) { + // trigger empty list load + mPositionalDataSource.loadRange(startIndex, 0, mainThreadExecutor, receiver); + } else { + int loadSize = Math.min(pageSize, startIndex + 1); + startIndex = startIndex - loadSize + 1; + mPositionalDataSource.loadRange(startIndex, loadSize, mainThreadExecutor, receiver); + } + } + + @Override + Integer getKey(int position, Value item) { + return position; } - return position; } } diff --git a/android/arch/paging/TiledDataSource.java b/android/arch/paging/TiledDataSource.java index 0ea94286..34d0091e 100644 --- a/android/arch/paging/TiledDataSource.java +++ b/android/arch/paging/TiledDataSource.java @@ -16,84 +16,24 @@ package android.arch.paging; -import android.support.annotation.Nullable; +import android.support.annotation.NonNull; +import android.support.annotation.RestrictTo; import android.support.annotation.WorkerThread; import java.util.Collections; import java.util.List; /** - * Position-based data loader for fixed size, arbitrary positioned loading. - * <p> - * Extend TiledDataSource if you want to load arbitrary pages based solely on position information, - * and can generate pages of a provided fixed size. - * <p> - * Room can generate a TiledDataSource for you: - * <pre> - * {@literal @}Dao - * interface UserDao { - * {@literal @}Query("SELECT * FROM user ORDER BY mAge DESC") - * public abstract TiledDataSource<User> loadUsersByAgeDesc(); - * }</pre> + * @param <T> Type loaded by the TiledDataSource. * - * Under the hood, Room will generate code equivalent to the below, using a limit/offset SQL query: - * <pre> - * {@literal @}Dao - * interface UserDao { - * {@literal @}Query("SELECT COUNT(*) from user") - * public abstract Integer getUserCount(); - * - * {@literal @}Query("SELECT * from user ORDER BY mName DESC LIMIT :limit OFFSET :offset") - * public abstract List<User> userNameLimitOffset(int limit, int offset); - * } - * - * public class OffsetUserQueryDataSource extends TiledDataSource<User> { - * private MyDatabase mDb; - * private final UserDao mUserDao; - * {@literal @}SuppressWarnings("FieldCanBeLocal") - * private final InvalidationTracker.Observer mObserver; - * - * public OffsetUserQueryDataSource(MyDatabase db) { - * mDb = db; - * mUserDao = db.getUserDao(); - * mObserver = new InvalidationTracker.Observer("user") { - * {@literal @}Override - * public void onInvalidated({@literal @}NonNull Set<String> tables) { - * // the user table has been invalidated, invalidate the DataSource - * invalidate(); - * } - * }; - * db.getInvalidationTracker().addWeakObserver(mObserver); - * } - * - * {@literal @}Override - * public boolean isInvalid() { - * mDb.getInvalidationTracker().refreshVersionsSync(); - * return super.isInvalid(); - * } - * - * {@literal @}Override - * public int countItems() { - * return mUserDao.getUserCount(); - * } - * - * {@literal @}Override - * public List<User> loadRange(int startPosition, int loadCount) { - * return mUserDao.userNameLimitOffset(loadCount, startPosition); - * } - * }</pre> - * - * @param <Type> Type of items being loaded by the TiledDataSource. + * @deprecated Use {@link PositionalDataSource} + * @hide */ -public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> { +@SuppressWarnings("DeprecatedIsStillUsed") +@Deprecated +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +public abstract class TiledDataSource<T> extends PositionalDataSource<T> { - private int mItemCount; - - /** - * Number of items that this DataSource can provide in total. - * - * @return Number of items this DataSource can provide. Must be <code>0</code> or greater. - */ @WorkerThread public abstract int countItems(); @@ -102,111 +42,39 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> { return false; } - /** - * Called to load items at from the specified position range. - * <p> - * This method must return a list of requested size, unless at the end of list. Fixed size pages - * enable TiledDataSource to navigate tiles efficiently, and quickly accesss any position in the - * data set. - * <p> - * If a list of a different size is returned, but it is not the last list in the data set based - * on the return value from {@link #countItems()}, an exception will be thrown. - * - * @param startPosition Index of first item to load. - * @param count Number of items to load. - * @return List of loaded items, of the requested length unless at end of list. Null if the - * DataSource is no longer valid, and should not be queried again. - */ @WorkerThread - public abstract List<Type> loadRange(int startPosition, int count); - - /** - * blocking, and splits pages - */ - void loadRangeInitial(int startPosition, int count, int pageSize, int itemCount, - PageResult.Receiver<Integer, Type> receiver) { - mItemCount = itemCount; + public abstract List<T> loadRange(int startPosition, int count); - if (itemCount == 0) { - // no data to load, just immediately return empty - receiver.onPageResult(new PageResult<>( - PageResult.INIT, new Page<Integer, Type>(Collections.<Type>emptyList()), - 0, 0, startPosition)); - return; - } - - List<Type> list = loadRangeWrapper(startPosition, count); - - count = Math.min(count, itemCount - startPosition); - - if (list == null) { - // invalid data, pass to receiver - receiver.onPageResult(new PageResult<Integer, Type>( - PageResult.INIT, null, 0, 0, startPosition)); + @Override + public void loadInitial(int requestedStartPosition, int requestedLoadSize, int pageSize, + @NonNull InitialLoadCallback callback) { + int totalCount = countItems(); + if (totalCount == 0) { + callback.onResult(Collections.<T>emptyList(), 0, 0); return; } - if (list.size() != count) { - throw new IllegalStateException("Invalid list, requested size: " + count - + ", returned size: " + list.size()); - } - - // emit the results as multiple pages - int pageCount = (count + (pageSize - 1)) / pageSize; - for (int i = 0; i < pageCount; i++) { - int beginInclusive = i * pageSize; - int endExclusive = Math.min(count, (i + 1) * pageSize); - - Page<Integer, Type> page = new Page<>(list.subList(beginInclusive, endExclusive)); - - int leadingNulls = startPosition + beginInclusive; - int trailingNulls = itemCount - leadingNulls - page.items.size(); - receiver.onPageResult(new PageResult<>( - PageResult.INIT, page, leadingNulls, trailingNulls, 0)); - } - } - - void loadRange(int startPosition, int count, PageResult.Receiver<Integer, Type> receiver) { - List<Type> list = loadRangeWrapper(startPosition, count); - - Page<Integer, Type> page = null; - int trailingNulls = mItemCount - startPosition; + // bound the size requested, based on known count + final int firstLoadPosition = computeFirstLoadPosition( + requestedStartPosition, requestedLoadSize, pageSize, totalCount); + final int firstLoadSize = Math.min(totalCount - firstLoadPosition, requestedLoadSize); + // convert from legacy behavior + List<T> list = loadRange(firstLoadPosition, firstLoadSize); if (list != null) { - page = new Page<Integer, Type>(list); - trailingNulls -= list.size(); + callback.onResult(list, firstLoadPosition, totalCount); + } else { + invalidate(); } - receiver.postOnPageResult(new PageResult<>( - PageResult.TILE, page, startPosition, trailingNulls, 0)); } - private List<Type> loadRangeWrapper(int startPosition, int count) { - if (isInvalid()) { - return null; - } - List<Type> list = loadRange(startPosition, count); - if (isInvalid()) { - return null; - } - return list; - } - - ContiguousDataSource<Integer, Type> getAsContiguous() { - return new TiledAsBoundedDataSource<>(this); - } - - static class TiledAsBoundedDataSource<Value> extends BoundedDataSource<Value> { - final TiledDataSource<Value> mTiledDataSource; - - TiledAsBoundedDataSource(TiledDataSource<Value> tiledDataSource) { - mTiledDataSource = tiledDataSource; - } - - @WorkerThread - @Nullable - @Override - public List<Value> loadRange(int startPosition, int loadCount) { - return mTiledDataSource.loadRange(startPosition, loadCount); + @Override + public void loadRange(int startPosition, int count, @NonNull LoadCallback callback) { + List<T> list = loadRange(startPosition, count); + if (list != null) { + callback.onResult(list); + } else { + invalidate(); } } } diff --git a/android/arch/paging/TiledPagedList.java b/android/arch/paging/TiledPagedList.java index 76bb682d..6c189cdb 100644 --- a/android/arch/paging/TiledPagedList.java +++ b/android/arch/paging/TiledPagedList.java @@ -25,32 +25,16 @@ import java.util.concurrent.Executor; class TiledPagedList<T> extends PagedList<T> implements PagedStorage.Callback { + private final PositionalDataSource<T> mDataSource; - private final TiledDataSource<T> mDataSource; - - @SuppressWarnings("unchecked") - private final PagedStorage<Integer, T> mKeyedStorage = (PagedStorage<Integer, T>) mStorage; - - private final PageResult.Receiver<Integer, T> mReceiver = - new PageResult.Receiver<Integer, T>() { - @AnyThread - @Override - public void postOnPageResult(@NonNull final PageResult<Integer, T> pageResult) { - // NOTE: if we're already on main thread, this can delay page receive by a frame - mMainThreadExecutor.execute(new Runnable() { - @Override - public void run() { - onPageResult(pageResult); - } - }); - } - + private PageResult.Receiver<T> mReceiver = new PageResult.Receiver<T>() { // Creation thread for initial synchronous load, otherwise main thread // Safe to access main thread only state - no other thread has reference during construction @AnyThread @Override - public void onPageResult(@NonNull PageResult<Integer, T> pageResult) { - if (pageResult.page == null) { + public void onPageResult(@PageResult.ResultType int type, + @NonNull PageResult<T> pageResult) { + if (pageResult.isInvalid()) { detach(); return; } @@ -61,60 +45,56 @@ class TiledPagedList<T> extends PagedList<T> } if (mStorage.getPageCount() == 0) { - mKeyedStorage.init( + mStorage.initAndSplit( pageResult.leadingNulls, pageResult.page, pageResult.trailingNulls, - pageResult.positionOffset, TiledPagedList.this); + pageResult.positionOffset, mConfig.pageSize, TiledPagedList.this); } else { - mKeyedStorage.insertPage(pageResult.leadingNulls, pageResult.page, + mStorage.insertPage(pageResult.positionOffset, pageResult.page, TiledPagedList.this); } if (mBoundaryCallback != null) { boolean deferEmpty = mStorage.size() == 0; - boolean deferBegin = !deferEmpty && pageResult.leadingNulls == 0; - boolean deferEnd = !deferEmpty && pageResult.trailingNulls == 0; + boolean deferBegin = !deferEmpty + && pageResult.leadingNulls == 0 + && pageResult.positionOffset == 0; + int size = size(); + boolean deferEnd = !deferEmpty + && ((type == PageResult.INIT && pageResult.trailingNulls == 0) + || (type == PageResult.TILE + && pageResult.positionOffset + == (size - size % mConfig.pageSize))); deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd); } } }; @WorkerThread - TiledPagedList(@NonNull TiledDataSource<T> dataSource, + TiledPagedList(@NonNull PositionalDataSource<T> dataSource, @NonNull Executor mainThreadExecutor, @NonNull Executor backgroundThreadExecutor, @Nullable BoundaryCallback<T> boundaryCallback, @NonNull Config config, int position) { - super(new PagedStorage<Integer, T>(), mainThreadExecutor, backgroundThreadExecutor, + super(new PagedStorage<T>(), mainThreadExecutor, backgroundThreadExecutor, boundaryCallback, config); mDataSource = dataSource; final int pageSize = mConfig.pageSize; + mLastLoad = position; - final int itemCount = mDataSource.countItems(); - - final int firstLoadSize = Math.min(itemCount, - (Math.max(mConfig.initialLoadSizeHint / pageSize, 2)) * pageSize); - final int firstLoadPosition = computeFirstLoadPosition( - position, firstLoadSize, pageSize, itemCount); - - mDataSource.loadRangeInitial(firstLoadPosition, firstLoadSize, pageSize, - itemCount, mReceiver); - } - - static int computeFirstLoadPosition(int position, int firstLoadSize, int pageSize, int size) { - int idealStart = position - firstLoadSize / 2; - - int roundedPageStart = Math.round(idealStart / pageSize) * pageSize; + if (mDataSource.isInvalid()) { + detach(); + } else { + final int firstLoadSize = + (Math.max(Math.round(mConfig.initialLoadSizeHint / pageSize), 2)) * pageSize; - // minimum start position is 0 - roundedPageStart = Math.max(0, roundedPageStart); + final int idealStart = position - firstLoadSize / 2; + final int roundedPageStart = Math.max(0, Math.round(idealStart / pageSize) * pageSize); - // maximum start pos is that which will encompass end of list - int maximumLoadPage = ((size - firstLoadSize + pageSize - 1) / pageSize) * pageSize; - roundedPageStart = Math.min(maximumLoadPage, roundedPageStart); - - return roundedPageStart; + mDataSource.loadInitial(true, roundedPageStart, firstLoadSize, + pageSize, mMainThreadExecutor, mReceiver); + } } @Override @@ -132,7 +112,13 @@ class TiledPagedList<T> extends PagedList<T> protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> pagedListSnapshot, @NonNull Callback callback) { //noinspection UnnecessaryLocalVariable - final PagedStorage<?, T> snapshot = pagedListSnapshot.mStorage; + final PagedStorage<T> snapshot = pagedListSnapshot.mStorage; + + if (snapshot.isEmpty() + || mStorage.size() != snapshot.size()) { + throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear" + + " to be a snapshot of this PagedList"); + } // loop through each page and signal the callback for any pages that are present now, // but not in the snapshot. @@ -186,7 +172,14 @@ class TiledPagedList<T> extends PagedList<T> return; } final int pageSize = mConfig.pageSize; - mDataSource.loadRange(pageIndex * pageSize, pageSize, mReceiver); + + if (mDataSource.isInvalid()) { + detach(); + } else { + int startPosition = pageIndex * pageSize; + int count = Math.min(pageSize, mStorage.size() - startPosition); + mDataSource.loadRange(startPosition, count, mMainThreadExecutor, mReceiver); + } } }); } diff --git a/android/arch/paging/integration/testapp/ItemDataSource.java b/android/arch/paging/integration/testapp/ItemDataSource.java index 46905334..bbbfabb8 100644 --- a/android/arch/paging/integration/testapp/ItemDataSource.java +++ b/android/arch/paging/integration/testapp/ItemDataSource.java @@ -16,9 +16,10 @@ package android.arch.paging.integration.testapp; -import android.arch.paging.BoundedDataSource; +import android.arch.paging.PositionalDataSource; import android.graphics.Color; import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; import java.util.ArrayList; import java.util.List; @@ -26,7 +27,7 @@ import java.util.List; /** * Sample data source with artificial data. */ -class ItemDataSource extends BoundedDataSource<Item> { +class ItemDataSource extends PositionalDataSource<Item> { private static final int COUNT = 500; @ColorInt @@ -39,18 +40,7 @@ class ItemDataSource extends BoundedDataSource<Item> { private static int sGenerationId; private final int mGenerationId = sGenerationId++; - @Override - public int countItems() { - return COUNT; - } - - @Override - public List<Item> loadRange(int startPosition, int loadCount) { - if (isInvalid()) { - // abort! - return null; - } - + private List<Item> loadRangeInternal(int startPosition, int loadCount) { List<Item> items = new ArrayList<>(); int end = Math.min(COUNT, startPosition + loadCount); int bgColor = COLORS[mGenerationId % COLORS.length]; @@ -63,11 +53,38 @@ class ItemDataSource extends BoundedDataSource<Item> { for (int i = startPosition; i != end; i++) { items.add(new Item(i, "item " + i, bgColor)); } - - if (isInvalid()) { - // abort! - return null; - } return items; } + + // TODO: open up this API in PositionalDataSource? + private static int computeFirstLoadPosition(int position, int firstLoadSize, + int pageSize, int size) { + int roundedPageStart = Math.round(position / pageSize) * pageSize; + + // minimum start position is 0 + roundedPageStart = Math.max(0, roundedPageStart); + + // maximum start pos is that which will encompass end of list + int maximumLoadPage = ((size - firstLoadSize + pageSize - 1) / pageSize) * pageSize; + roundedPageStart = Math.min(maximumLoadPage, roundedPageStart); + + return roundedPageStart; + } + + @Override + public void loadInitial(int requestedStartPosition, int requestedLoadSize, + int pageSize, @NonNull InitialLoadCallback<Item> callback) { + requestedStartPosition = computeFirstLoadPosition( + requestedStartPosition, requestedLoadSize, pageSize, COUNT); + + requestedLoadSize = Math.min(COUNT - requestedStartPosition, requestedLoadSize); + List<Item> data = loadRangeInternal(requestedStartPosition, requestedLoadSize); + callback.onResult(data, requestedStartPosition, COUNT); + } + + @Override + public void loadRange(int startPosition, int count, @NonNull LoadCallback<Item> callback) { + List<Item> data = loadRangeInternal(startPosition, count); + callback.onResult(data); + } } diff --git a/android/arch/paging/integration/testapp/PagedListItemViewModel.java b/android/arch/paging/integration/testapp/PagedListItemViewModel.java index 974eab95..be14bc1b 100644 --- a/android/arch/paging/integration/testapp/PagedListItemViewModel.java +++ b/android/arch/paging/integration/testapp/PagedListItemViewModel.java @@ -27,10 +27,24 @@ import android.arch.paging.PagedList; */ @SuppressWarnings("WeakerAccess") public class PagedListItemViewModel extends ViewModel { - private LiveData<PagedList<Item>> mLivePagedList; private ItemDataSource mDataSource; private final Object mDataSourceLock = new Object(); + private final DataSource.Factory<Integer, Item> mFactory = + new DataSource.Factory<Integer, Item>() { + @Override + public DataSource<Integer, Item> create() { + ItemDataSource newDataSource = new ItemDataSource(); + synchronized (mDataSourceLock) { + mDataSource = newDataSource; + return mDataSource; + } + } + }; + + private LiveData<PagedList<Item>> mLivePagedList = + new LivePagedListBuilder<>(mFactory, 20).build(); + void invalidateList() { synchronized (mDataSourceLock) { if (mDataSource != null) { @@ -40,22 +54,6 @@ public class PagedListItemViewModel extends ViewModel { } LiveData<PagedList<Item>> getLivePagedList() { - if (mLivePagedList == null) { - mLivePagedList = new LivePagedListBuilder<Integer, Item>() - .setPagingConfig(20) - .setDataSourceFactory(new DataSource.Factory<Integer, Item>() { - @Override - public DataSource<Integer, Item> create() { - ItemDataSource newDataSource = new ItemDataSource(); - synchronized (mDataSourceLock) { - mDataSource = newDataSource; - return mDataSource; - } - } - }) - .build(); - } - return mLivePagedList; } } diff --git a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java index 89d16b7e..d8cd8d49 100644 --- a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java +++ b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java @@ -83,13 +83,12 @@ public class CustomerViewModel extends AndroidViewModel { private static <K> LiveData<PagedList<Customer>> getLivePagedList( K initialLoadKey, DataSource.Factory<K, Customer> dataSourceFactory) { - return new LivePagedListBuilder<K, Customer>() + PagedList.Config config = new PagedList.Config.Builder() + .setPageSize(10) + .setEnablePlaceholders(false) + .build(); + return new LivePagedListBuilder<>(dataSourceFactory, config) .setInitialLoadKey(initialLoadKey) - .setPagingConfig(new PagedList.Config.Builder() - .setPageSize(10) - .setEnablePlaceholders(false) - .build()) - .setDataSourceFactory(dataSourceFactory) .build(); } diff --git a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java index cdd464e4..0c79aee4 100644 --- a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java +++ b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java @@ -16,7 +16,6 @@ package android.arch.persistence.room.integration.testapp; -import android.arch.lifecycle.LifecycleRegistry; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.Observer; import android.arch.lifecycle.ViewModelProviders; @@ -75,7 +74,7 @@ public class RoomPagedListActivity extends AppCompatActivity { mAdapter.setList(items); } }); - final Button button = findViewById(R.id.button); + final Button button = findViewById(R.id.addButton); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -114,11 +113,4 @@ public class RoomPagedListActivity extends AppCompatActivity { protected boolean useKeyedQuery() { return false; } - - private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this); - - @Override - public LifecycleRegistry getLifecycle() { - return mLifecycleRegistry; - } } diff --git a/android/arch/persistence/room/integration/testapp/dao/UserDao.java b/android/arch/persistence/room/integration/testapp/dao/UserDao.java index cb2bb034..0b184a9c 100644 --- a/android/arch/persistence/room/integration/testapp/dao/UserDao.java +++ b/android/arch/persistence/room/integration/testapp/dao/UserDao.java @@ -190,8 +190,12 @@ public abstract class UserDao { @Query("SELECT * FROM user where mAge > :age") public abstract LivePagedListProvider<Integer, User> loadPagedByAge_legacy(int age); + // TODO: switch to PositionalDataSource once Room supports it @Query("SELECT * FROM user ORDER BY mAge DESC") - public abstract TiledDataSource<User> loadUsersByAgeDesc(); + public abstract DataSource.Factory<Integer, User> loadUsersByAgeDesc(); + + @Query("SELECT * FROM user ORDER BY mAge DESC") + public abstract TiledDataSource<User> loadUsersByAgeDesc_legacy(); @Query("DELETE FROM User WHERE mId IN (:ids) AND mAge == :age") public abstract int deleteByAgeAndIds(int age, List<Integer> ids); diff --git a/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java b/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java index a38d6aed..2db543b3 100644 --- a/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java +++ b/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java @@ -21,6 +21,7 @@ import android.arch.persistence.room.InvalidationTracker; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import java.util.Collections; import java.util.List; import java.util.Set; @@ -60,7 +61,6 @@ public class LastNameAscCustomerDataSource extends KeyedDataSource<String, Custo @Override public boolean isInvalid() { mDb.getInvalidationTracker().refreshVersionsSync(); - return super.isInvalid(); } @@ -76,30 +76,48 @@ public class LastNameAscCustomerDataSource extends KeyedDataSource<String, Custo } @Override - public int countItemsBefore(@NonNull String customerName) { - return mCustomerDao.customerNameCountBefore(customerName); - } + public void loadInitial(@Nullable String customerName, int initialLoadSize, + boolean enablePlaceholders, @NonNull InitialLoadCallback<Customer> callback) { + List<Customer> list; + if (customerName != null) { + // initial keyed load - load before 'customerName', + // and load after last item in before list + int pageSize = initialLoadSize / 2; + String key = customerName; + list = mCustomerDao.customerNameLoadBefore(key, pageSize); + Collections.reverse(list); + if (!list.isEmpty()) { + key = getKey(list.get(list.size() - 1)); + } + list.addAll(mCustomerDao.customerNameLoadAfter(key, pageSize)); + } else { + list = mCustomerDao.customerNameInitial(initialLoadSize); + } - @Override - public int countItemsAfter(@NonNull String customerName) { - return mCustomerDao.customerNameCountAfter(customerName); - } + if (enablePlaceholders && !list.isEmpty()) { + String firstKey = getKey(list.get(0)); + String lastKey = getKey(list.get(list.size() - 1)); - @Nullable - @Override - public List<Customer> loadInitial(int pageSize) { - return mCustomerDao.customerNameInitial(pageSize); + // only bother counting if placeholders are desired + final int position = mCustomerDao.customerNameCountBefore(firstKey); + final int count = position + list.size() + mCustomerDao.customerNameCountAfter(lastKey); + callback.onResult(list, position, count); + } else { + callback.onResult(list); + } } - @Nullable @Override - public List<Customer> loadBefore(@NonNull String customerName, int pageSize) { - return mCustomerDao.customerNameLoadBefore(customerName, pageSize); + public void loadAfter(@NonNull String currentEndKey, int pageSize, + @NonNull LoadCallback<Customer> callback) { + callback.onResult(mCustomerDao.customerNameLoadAfter(currentEndKey, pageSize)); } - @Nullable @Override - public List<Customer> loadAfter(@Nullable String customerName, int pageSize) { - return mCustomerDao.customerNameLoadAfter(customerName, pageSize); + public void loadBefore(@NonNull String currentBeginKey, int pageSize, + @NonNull LoadCallback<Customer> callback) { + List<Customer> list = mCustomerDao.customerNameLoadBefore(currentBeginKey, pageSize); + Collections.reverse(list); + callback.onResult(list); } } diff --git a/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java b/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java index c5465314..b54abe8e 100644 --- a/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java +++ b/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java @@ -63,12 +63,12 @@ public class DataSourceFactoryTest extends TestDatabaseTest { validateUsersAsPagedList(new LivePagedListFactory() { @Override public LiveData<PagedList<User>> create() { - return new LivePagedListBuilder<Integer, User>() - .setPagingConfig(new PagedList.Config.Builder() + return new LivePagedListBuilder<>( + mUserDao.loadPagedByAge(3), + new PagedList.Config.Builder() .setPageSize(10) .setPrefetchDistance(1) .setInitialLoadSizeHint(10).build()) - .setDataSourceFactory(mUserDao.loadPagedByAge(3)) .build(); } }); diff --git a/android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java b/android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java index 8226759a..f0285a04 100644 --- a/android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java +++ b/android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java @@ -45,8 +45,17 @@ public class LimitOffsetDataSourceTest extends TestDatabaseTest { mUserDao.deleteEverything(); } + // TODO: delete this and factory abstraction when LivePagedListProvider is removed + @Test + public void limitOffsetDataSource_legacyTiledDataSource() { + // Simple verification that loading a TiledDataSource still works. + LimitOffsetDataSource<User> dataSource = + (LimitOffsetDataSource<User>) mUserDao.loadUsersByAgeDesc_legacy(); + assertThat(dataSource.countItems(), is(0)); + } + private LimitOffsetDataSource<User> loadUsersByAgeDesc() { - return (LimitOffsetDataSource<User>) mUserDao.loadUsersByAgeDesc(); + return (LimitOffsetDataSource<User>) mUserDao.loadUsersByAgeDesc().create(); } @Test diff --git a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java index 291cfd67..f076cf13 100644 --- a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java +++ b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java @@ -203,10 +203,8 @@ public class QueryTransactionTest { @Test public void pagedList() { - LiveData<PagedList<Entity1>> pagedList = new LivePagedListBuilder<Integer, Entity1>() - .setDataSourceFactory(mDao.pagedList()) - .setPagingConfig(10) - .build(); + LiveData<PagedList<Entity1>> pagedList = + new LivePagedListBuilder<>(mDao.pagedList(), 10).build(); observeForever(pagedList); drain(); assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 0 : 0)); @@ -228,6 +226,7 @@ public class QueryTransactionTest { mDao.insert(new Entity1(2, "bar")); drain(); resetTransactionCount(); + @SuppressWarnings("deprecation") TiledDataSource<Entity1> dataSource = mDao.dataSource(); dataSource.loadRange(0, 10); assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 1 : 0)); diff --git a/android/bluetooth/BluetoothHidDevice.java b/android/bluetooth/BluetoothHidDevice.java index e3d763ab..6692e137 100644 --- a/android/bluetooth/BluetoothHidDevice.java +++ b/android/bluetooth/BluetoothHidDevice.java @@ -350,13 +350,22 @@ public final class BluetoothHidDevice implements BluetoothProfile { * application can be registered at time. When no longer used, application * should be unregistered using * {@link #unregisterApp(BluetoothHidDeviceAppConfiguration)}. + * The registration status should be tracked by the application by handling callback from + * BluetoothHidDeviceCallback#onAppStatusChanged. The app registration status is not related + * to the return value of this method. * * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of HID Device SDP record. + * The HID Device SDP record is required. * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of Incoming QoS Settings. + * The Incoming QoS Settings is not required. Use null or default + * BluetoothHidDeviceAppQosSettings.Builder for default values. * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of Outgoing QoS Settings. + * The Outgoing QoS Settings is not required. Use null or default + * BluetoothHidDeviceAppQosSettings.Builder for default values. * @param callback {@link BluetoothHidDeviceCallback} object to which callback messages will be * sent. - * @return + * The BluetoothHidDeviceCallback object is required. + * @return true if the command is successfully sent; otherwise false. */ public boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp, BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos, @@ -394,12 +403,15 @@ public final class BluetoothHidDevice implements BluetoothProfile { * {@link #registerApp * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)} + * The registration status should be tracked by the application by handling callback from + * BluetoothHidDeviceCallback#onAppStatusChanged. The app registration status is not related + * to the return value of this method. * * @param config {@link BluetoothHidDeviceAppConfiguration} object as obtained from {@link * BluetoothHidDeviceCallback#onAppStatusChanged(BluetoothDevice, * BluetoothHidDeviceAppConfiguration, * boolean)} - * @return + * @return true if the command is successfully sent; otherwise false. */ public boolean unregisterApp(BluetoothHidDeviceAppConfiguration config) { Log.v(TAG, "unregisterApp()"); @@ -426,7 +438,7 @@ public final class BluetoothHidDevice implements BluetoothProfile { * @param id Report Id, as defined in descriptor. Can be 0 in case Report Id are not defined in * descriptor. * @param data Report data, not including Report Id. - * @return + * @return true if the command is successfully sent; otherwise false. */ public boolean sendReport(BluetoothDevice device, int id, byte[] data) { boolean result = false; @@ -452,7 +464,7 @@ public final class BluetoothHidDevice implements BluetoothProfile { * @param type Report Type, as in request. * @param id Report Id, as in request. * @param data Report data, not including Report Id. - * @return + * @return true if the command is successfully sent; otherwise false. */ public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) { Log.v(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id); @@ -478,7 +490,7 @@ public final class BluetoothHidDevice implements BluetoothProfile { * from {@link BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])}. * * @param error Error to be sent for SET_REPORT via HANDSHAKE. - * @return + * @return true if the command is successfully sent; otherwise false. */ public boolean reportError(BluetoothDevice device, byte error) { Log.v(TAG, "reportError(): device=" + device + " error=" + error); @@ -524,10 +536,13 @@ public final class BluetoothHidDevice implements BluetoothProfile { } /** - * Initiates connection to host which currently has Virtual Cable - * established with device. + * Initiates connection to host which is currently paired with this device. + * If the application is not registered, #connect(BluetoothDevice) will fail. + * The connection state should be tracked by the application by handling callback from + * BluetoothHidDeviceCallback#onConnectionStateChanged. The connection state is not related + * to the return value of this method. * - * @return + * @return true if the command is successfully sent; otherwise false. */ public boolean connect(BluetoothDevice device) { Log.v(TAG, "connect(): device=" + device); @@ -550,8 +565,11 @@ public final class BluetoothHidDevice implements BluetoothProfile { /** * Disconnects from currently connected host. + * The connection state should be tracked by the application by handling callback from + * BluetoothHidDeviceCallback#onConnectionStateChanged. The connection state is not related + * to the return value of this method. * - * @return + * @return true if the command is successfully sent; otherwise false. */ public boolean disconnect(BluetoothDevice device) { Log.v(TAG, "disconnect(): device=" + device); diff --git a/android/bluetooth/BluetoothHidDeviceAppQosSettings.java b/android/bluetooth/BluetoothHidDeviceAppQosSettings.java index ccc3ef40..881ae98d 100644 --- a/android/bluetooth/BluetoothHidDeviceAppQosSettings.java +++ b/android/bluetooth/BluetoothHidDeviceAppQosSettings.java @@ -45,6 +45,21 @@ public final class BluetoothHidDeviceAppQosSettings implements Parcelable { public static final int MAX = (int) 0xffffffff; + /** + * Create a BluetoothHidDeviceAppQosSettings object for the Bluetooth L2CAP channel. + * The QoS Settings is optional. + * Recommended to use BluetoothHidDeviceAppQosSettings.Builder. + * {@see <a href="https://www.bluetooth.com/specifications/profiles-overview"> + * https://www.bluetooth.com/specifications/profiles-overview + * </a> + * Bluetooth HID Specfication v1.1.1 Section 5.2 and Appendix D } + * @param serviceType L2CAP service type + * @param tokenRate L2CAP token rate + * @param tokenBucketSize L2CAP token bucket size + * @param peakBandwidth L2CAP peak bandwidth + * @param latency L2CAP latency + * @param delayVariation L2CAP delay variation + */ public BluetoothHidDeviceAppQosSettings(int serviceType, int tokenRate, int tokenBucketSize, int peakBandwidth, int latency, int delayVariation) { this.serviceType = serviceType; @@ -59,7 +74,12 @@ public final class BluetoothHidDeviceAppQosSettings implements Parcelable { public boolean equals(Object o) { if (o instanceof BluetoothHidDeviceAppQosSettings) { BluetoothHidDeviceAppQosSettings qos = (BluetoothHidDeviceAppQosSettings) o; - return false; + return this.serviceType == qos.serviceType + && this.tokenRate == qos.tokenRate + && this.tokenBucketSize == qos.tokenBucketSize + && this.peakBandwidth == qos.peakBandwidth + && this.latency == qos.latency + && this.delayVariation == qos.delayVariation; } return false; } @@ -106,4 +126,85 @@ public final class BluetoothHidDeviceAppQosSettings implements Parcelable { serviceType, tokenRate, tokenBucketSize, peakBandwidth, latency, delayVariation }; } + + /** + * A helper to build the BluetoothHidDeviceAppQosSettings object. + */ + public static class Builder { + // Optional parameters - initialized to default values + private int mServiceType = SERVICE_BEST_EFFORT; + private int mTokenRate = 0; + private int mTokenBucketSize = 0; + private int mPeakBandwidth = 0; + private int mLatency = MAX; + private int mDelayVariation = MAX; + + /** + * Set the service type. + * @param val service type. Should be one of {SERVICE_NO_TRAFFIC, SERVICE_BEST_EFFORT, + * SERVICE_GUARANTEED}, with SERVICE_BEST_EFFORT being the default one. + * @return BluetoothHidDeviceAppQosSettings Builder with specified service type. + */ + public Builder serviceType(int val) { + mServiceType = val; + return this; + } + /** + * Set the token rate. + * @param val token rate + * @return BluetoothHidDeviceAppQosSettings Builder with specified token rate. + */ + public Builder tokenRate(int val) { + mTokenRate = val; + return this; + } + + /** + * Set the bucket size. + * @param val bucket size + * @return BluetoothHidDeviceAppQosSettings Builder with specified bucket size. + */ + public Builder tokenBucketSize(int val) { + mTokenBucketSize = val; + return this; + } + + /** + * Set the peak bandwidth. + * @param val peak bandwidth + * @return BluetoothHidDeviceAppQosSettings Builder with specified peak bandwidth. + */ + public Builder peakBandwidth(int val) { + mPeakBandwidth = val; + return this; + } + /** + * Set the latency. + * @param val latency + * @return BluetoothHidDeviceAppQosSettings Builder with specified latency. + */ + public Builder latency(int val) { + mLatency = val; + return this; + } + + /** + * Set the delay variation. + * @param val delay variation + * @return BluetoothHidDeviceAppQosSettings Builder with specified delay variation. + */ + public Builder delayVariation(int val) { + mDelayVariation = val; + return this; + } + + /** + * Build the BluetoothHidDeviceAppQosSettings object. + * @return BluetoothHidDeviceAppQosSettings object with current settings. + */ + public BluetoothHidDeviceAppQosSettings build() { + return new BluetoothHidDeviceAppQosSettings(mServiceType, mTokenRate, mTokenBucketSize, + mPeakBandwidth, mLatency, mDelayVariation); + } + } } diff --git a/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java index f01c4932..46696370 100644 --- a/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java +++ b/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java @@ -19,6 +19,8 @@ package android.bluetooth; import android.os.Parcel; import android.os.Parcelable; +import java.util.Arrays; + /** * Represents the Service Discovery Protocol (SDP) settings for a Bluetooth * HID Device application. @@ -39,6 +41,18 @@ public final class BluetoothHidDeviceAppSdpSettings implements Parcelable { public final byte subclass; public final byte[] descriptors; + /** + * Create a BluetoothHidDeviceAppSdpSettings object for the Bluetooth SDP record. + * @param name Name of this Bluetooth HID device. Maximum length is 50 bytes. + * @param description Description for this Bluetooth HID device. Maximum length is 50 bytes. + * @param provider Provider of this Bluetooth HID device. Maximum length is 50 bytes. + * @param subclass Subclass of this Bluetooth HID device. + * See <a href="www.usb.org/developers/hidpage/HID1_11.pdf"> + * www.usb.org/developers/hidpage/HID1_11.pdf Section 4.2</a> + * @param descriptors Descriptors of this Bluetooth HID device. + * See <a href="www.usb.org/developers/hidpage/HID1_11.pdf"> + * www.usb.org/developers/hidpage/HID1_11.pdf Chapter 6</a> Maximum length is 2048 bytes. + */ public BluetoothHidDeviceAppSdpSettings(String name, String description, String provider, byte subclass, byte[] descriptors) { this.name = name; @@ -52,7 +66,11 @@ public final class BluetoothHidDeviceAppSdpSettings implements Parcelable { public boolean equals(Object o) { if (o instanceof BluetoothHidDeviceAppSdpSettings) { BluetoothHidDeviceAppSdpSettings sdp = (BluetoothHidDeviceAppSdpSettings) o; - return false; + return this.name.equals(sdp.name) + && this.description.equals(sdp.description) + && this.provider.equals(sdp.provider) + && this.subclass == sdp.subclass + && Arrays.equals(this.descriptors, sdp.descriptors); } return false; } diff --git a/android/content/CursorLoader.java b/android/content/CursorLoader.java index 33386e5c..7f24c51d 100644 --- a/android/content/CursorLoader.java +++ b/android/content/CursorLoader.java @@ -145,7 +145,7 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> { } /** - * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks + * Starts an asynchronous load of the data. When the result is ready the callbacks * will be called on the UI thread. If a previous load has been completed and is still valid * the result may be passed to the callbacks immediately. * diff --git a/android/content/pm/ActivityInfo.java b/android/content/pm/ActivityInfo.java index 837c00a7..f8cdce64 100644 --- a/android/content/pm/ActivityInfo.java +++ b/android/content/pm/ActivityInfo.java @@ -1156,6 +1156,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { dest.writeString(permission); dest.writeString(taskAffinity); dest.writeString(targetActivity); + dest.writeString(launchToken); dest.writeInt(flags); dest.writeInt(screenOrientation); dest.writeInt(configChanges); @@ -1282,6 +1283,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { permission = source.readString(); taskAffinity = source.readString(); targetActivity = source.readString(); + launchToken = source.readString(); flags = source.readInt(); screenOrientation = source.readInt(); configChanges = source.readInt(); diff --git a/android/content/pm/ApplicationInfo.java b/android/content/pm/ApplicationInfo.java index 20342807..edb27cd4 100644 --- a/android/content/pm/ApplicationInfo.java +++ b/android/content/pm/ApplicationInfo.java @@ -19,6 +19,7 @@ package android.content.pm; import static android.os.Build.VERSION_CODES.DONUT; import android.annotation.IntDef; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.content.Context; @@ -890,6 +891,29 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public int versionCode; /** + * The user-visible SDK version (ex. 26) of the framework against which the application claims + * to have been compiled, or {@code 0} if not specified. + * <p> + * This property is the compile-time equivalent of + * {@link android.os.Build.VERSION#CODENAME Build.VERSION.SDK_INT}. + * + * @hide For platform use only; we don't expect developers to need to read this value. + */ + public int compileSdkVersion; + + /** + * The development codename (ex. "O", "REL") of the framework against which the application + * claims to have been compiled, or {@code null} if not specified. + * <p> + * This property is the compile-time equivalent of + * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}. + * + * @hide For platform use only; we don't expect developers to need to read this value. + */ + @Nullable + public String compileSdkVersionCodename; + + /** * When false, indicates that all components within this application are * considered disabled, regardless of their individually set enabled status. */ @@ -1305,6 +1329,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(targetSandboxVersion); dest.writeString(classLoaderName); dest.writeStringArray(splitClassLoaderNames); + dest.writeInt(compileSdkVersion); + dest.writeString(compileSdkVersionCodename); } public static final Parcelable.Creator<ApplicationInfo> CREATOR @@ -1372,6 +1398,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { targetSandboxVersion = source.readInt(); classLoaderName = source.readString(); splitClassLoaderNames = source.readStringArray(); + compileSdkVersion = source.readInt(); + compileSdkVersionCodename = source.readString(); } /** diff --git a/android/content/pm/InstantAppInfo.java b/android/content/pm/InstantAppInfo.java index 67afc928..cb04fc3c 100644 --- a/android/content/pm/InstantAppInfo.java +++ b/android/content/pm/InstantAppInfo.java @@ -18,6 +18,7 @@ package android.content.pm; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; @@ -31,6 +32,7 @@ import android.os.Parcelable; * * @hide */ +@SystemApi public final class InstantAppInfo implements Parcelable { private final ApplicationInfo mApplicationInfo; diff --git a/android/content/pm/PackageInfo.java b/android/content/pm/PackageInfo.java index ba488f6a..f8889b68 100644 --- a/android/content/pm/PackageInfo.java +++ b/android/content/pm/PackageInfo.java @@ -16,6 +16,7 @@ package android.content.pm; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -289,6 +290,29 @@ public class PackageInfo implements Parcelable { /** @hide */ public boolean isStaticOverlay; + /** + * The user-visible SDK version (ex. 26) of the framework against which the application claims + * to have been compiled, or {@code 0} if not specified. + * <p> + * This property is the compile-time equivalent of + * {@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT}. + * + * @hide For platform use only; we don't expect developers to need to read this value. + */ + public int compileSdkVersion; + + /** + * The development codename (ex. "O", "REL") of the framework against which the application + * claims to have been compiled, or {@code null} if not specified. + * <p> + * This property is the compile-time equivalent of + * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}. + * + * @hide For platform use only; we don't expect developers to need to read this value. + */ + @Nullable + public String compileSdkVersionCodename; + public PackageInfo() { } @@ -344,6 +368,8 @@ public class PackageInfo implements Parcelable { dest.writeString(overlayTarget); dest.writeInt(isStaticOverlay ? 1 : 0); dest.writeInt(overlayPriority); + dest.writeInt(compileSdkVersion); + dest.writeString(compileSdkVersionCodename); } public static final Parcelable.Creator<PackageInfo> CREATOR @@ -396,6 +422,8 @@ public class PackageInfo implements Parcelable { overlayTarget = source.readString(); isStaticOverlay = source.readInt() != 0; overlayPriority = source.readInt(); + compileSdkVersion = source.readInt(); + compileSdkVersionCodename = source.readString(); // The component lists were flattened with the redundant ApplicationInfo // instances omitted. Distribute the canonical one here as appropriate. diff --git a/android/content/pm/PackageInstaller.java b/android/content/pm/PackageInstaller.java index 86288396..77c5743f 100644 --- a/android/content/pm/PackageInstaller.java +++ b/android/content/pm/PackageInstaller.java @@ -81,6 +81,9 @@ import java.util.List; * <li>All APKs must have unique split names. * <li>All installations must contain a single base APK. * </ul> + * <p> + * The ApiDemos project contains examples of using this API: + * <code>ApiDemos/src/com/example/android/apis/content/InstallApk*.java</code>. */ public class PackageInstaller { private static final String TAG = "PackageInstaller"; diff --git a/android/content/pm/PackageManager.java b/android/content/pm/PackageManager.java index 31ca1985..f796aabe 100644 --- a/android/content/pm/PackageManager.java +++ b/android/content/pm/PackageManager.java @@ -871,8 +871,8 @@ public abstract class PackageManager { public static final int INSTALL_REASON_USER = 4; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} on success. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * on success. * * @hide */ @@ -880,8 +880,8 @@ public abstract class PackageManager { public static final int INSTALL_SUCCEEDED = 1; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the package is already installed. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the package is already installed. * * @hide */ @@ -889,8 +889,8 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_ALREADY_EXISTS = -1; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the package archive file is invalid. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the package archive file is invalid. * * @hide */ @@ -898,8 +898,8 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_INVALID_APK = -2; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the URI passed in is invalid. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the URI passed in is invalid. * * @hide */ @@ -907,9 +907,9 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_INVALID_URI = -3; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the package manager service found that - * the device didn't have enough storage space to install the app. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the package manager service found that the device didn't have enough storage space to + * install the app. * * @hide */ @@ -917,9 +917,8 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if a package is already installed with - * the same name. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if a package is already installed with the same name. * * @hide */ @@ -927,9 +926,8 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the requested shared user does not - * exist. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the requested shared user does not exist. * * @hide */ @@ -937,10 +935,9 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_NO_SHARED_USER = -6; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if a previously installed package of the - * same name has a different signature than the new package (and the old - * package's data was not removed). + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if a previously installed package of the same name has a different signature than the new + * package (and the old package's data was not removed). * * @hide */ @@ -948,10 +945,9 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package is requested a shared - * user which is already installed on the device and does not have matching - * signature. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package is requested a shared user which is already installed on the device and + * does not have matching signature. * * @hide */ @@ -959,9 +955,8 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package uses a shared library - * that is not available. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package uses a shared library that is not available. * * @hide */ @@ -969,9 +964,8 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package uses a shared library - * that is not available. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package uses a shared library that is not available. * * @hide */ @@ -979,10 +973,9 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package failed while - * optimizing and validating its dex files, either because there was not - * enough storage or the validation failed. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package failed while optimizing and validating its dex files, either because there + * was not enough storage or the validation failed. * * @hide */ @@ -990,9 +983,9 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_DEXOPT = -11; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package failed because the - * current SDK version is older than that required by the package. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package failed because the current SDK version is older than that required by the + * package. * * @hide */ @@ -1000,10 +993,9 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_OLDER_SDK = -12; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package failed because it - * contains a content provider with the same authority as a provider already - * installed in the system. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package failed because it contains a content provider with the same authority as a + * provider already installed in the system. * * @hide */ @@ -1011,9 +1003,9 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package failed because the - * current SDK version is newer than that required by the package. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package failed because the current SDK version is newer than that required by the + * package. * * @hide */ @@ -1021,10 +1013,9 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_NEWER_SDK = -14; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package failed because it has - * specified that it is a test-only package and the caller has not supplied - * the {@link #INSTALL_ALLOW_TEST} flag. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package failed because it has specified that it is a test-only package and the + * caller has not supplied the {@link #INSTALL_ALLOW_TEST} flag. * * @hide */ @@ -1032,9 +1023,9 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_TEST_ONLY = -15; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the package being installed contains - * native code, but none that is compatible with the device's CPU_ABI. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the package being installed contains native code, but none that is compatible with the + * device's CPU_ABI. * * @hide */ @@ -1042,9 +1033,8 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_CPU_ABI_INCOMPATIBLE = -16; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package uses a feature that is - * not available. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package uses a feature that is not available. * * @hide */ @@ -1053,9 +1043,9 @@ public abstract class PackageManager { // ------ Errors related to sdcard /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if a secure container mount point - * couldn't be accessed on external media. + * Installation return code: this is passed in the + * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if a secure container mount point couldn't be + * accessed on external media. * * @hide */ @@ -1063,9 +1053,8 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_CONTAINER_ERROR = -18; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package couldn't be installed - * in the specified install location. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package couldn't be installed in the specified install location. * * @hide */ @@ -1073,9 +1062,9 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package couldn't be installed - * in the specified install location because the media is not available. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package couldn't be installed in the specified install location because the media + * is not available. * * @hide */ @@ -1083,9 +1072,8 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package couldn't be installed - * because the verification timed out. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package couldn't be installed because the verification timed out. * * @hide */ @@ -1093,9 +1081,8 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_VERIFICATION_TIMEOUT = -21; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package couldn't be installed - * because the verification did not succeed. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package couldn't be installed because the verification did not succeed. * * @hide */ @@ -1103,9 +1090,8 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_VERIFICATION_FAILURE = -22; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the package changed from what the - * calling program expected. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the package changed from what the calling program expected. * * @hide */ @@ -1113,28 +1099,25 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_PACKAGE_CHANGED = -23; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package is assigned a - * different UID than it previously held. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package is assigned a different UID than it previously held. * * @hide */ public static final int INSTALL_FAILED_UID_CHANGED = -24; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package has an older version - * code than the currently installed package. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package has an older version code than the currently installed package. * * @hide */ public static final int INSTALL_FAILED_VERSION_DOWNGRADE = -25; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the old package has target SDK high - * enough to support runtime permission and the new package has target SDK - * low enough to not support runtime permissions. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the old package has target SDK high enough to support runtime permission and the new + * package has target SDK low enough to not support runtime permissions. * * @hide */ @@ -1142,9 +1125,8 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE = -26; /** - * Installation return code: this is passed to the - * {@link IPackageInstallObserver} if the new package attempts to downgrade the - * target sandbox version of the app. + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package attempts to downgrade the target sandbox version of the app. * * @hide */ @@ -1152,9 +1134,9 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE = -27; /** - * Installation parse return code: this is passed to the - * {@link IPackageInstallObserver} if the parser was given a path that is - * not a file, or does not end with the expected '.apk' extension. + * Installation parse return code: this is passed in the + * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser was given a path that is not a + * file, or does not end with the expected '.apk' extension. * * @hide */ @@ -1162,8 +1144,8 @@ public abstract class PackageManager { public static final int INSTALL_PARSE_FAILED_NOT_APK = -100; /** - * Installation parse return code: this is passed to the - * {@link IPackageInstallObserver} if the parser was unable to retrieve the + * Installation parse return code: this is passed in the + * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser was unable to retrieve the * AndroidManifest.xml file. * * @hide @@ -1172,8 +1154,8 @@ public abstract class PackageManager { public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101; /** - * Installation parse return code: this is passed to the - * {@link IPackageInstallObserver} if the parser encountered an unexpected + * Installation parse return code: this is passed in the + * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered an unexpected * exception. * * @hide @@ -1182,9 +1164,9 @@ public abstract class PackageManager { public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102; /** - * Installation parse return code: this is passed to the - * {@link IPackageInstallObserver} if the parser did not find any - * certificates in the .apk. + * Installation parse return code: this is passed in the + * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser did not find any certificates in + * the .apk. * * @hide */ @@ -1192,9 +1174,9 @@ public abstract class PackageManager { public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103; /** - * Installation parse return code: this is passed to the - * {@link IPackageInstallObserver} if the parser found inconsistent - * certificates on the files in the .apk. + * Installation parse return code: this is passed in the + * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser found inconsistent certificates on + * the files in the .apk. * * @hide */ @@ -1202,8 +1184,8 @@ public abstract class PackageManager { public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104; /** - * Installation parse return code: this is passed to the - * {@link IPackageInstallObserver} if the parser encountered a + * Installation parse return code: this is passed in the + * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered a * CertificateEncodingException in one of the files in the .apk. * * @hide @@ -1212,9 +1194,9 @@ public abstract class PackageManager { public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105; /** - * Installation parse return code: this is passed to the - * {@link IPackageInstallObserver} if the parser encountered a bad or - * missing package name in the manifest. + * Installation parse return code: this is passed in the + * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered a bad or missing + * package name in the manifest. * * @hide */ @@ -1222,9 +1204,9 @@ public abstract class PackageManager { public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106; /** - * Installation parse return code: this is passed to the - * {@link IPackageInstallObserver} if the parser encountered a bad shared - * user id name in the manifest. + * Installation parse return code: tthis is passed in the + * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered a bad shared user id + * name in the manifest. * * @hide */ @@ -1232,8 +1214,8 @@ public abstract class PackageManager { public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107; /** - * Installation parse return code: this is passed to the - * {@link IPackageInstallObserver} if the parser encountered some structural + * Installation parse return code: this is passed in the + * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered some structural * problem in the manifest. * * @hide @@ -1242,9 +1224,9 @@ public abstract class PackageManager { public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108; /** - * Installation parse return code: this is passed to the - * {@link IPackageInstallObserver} if the parser did not find any actionable - * tags (instrumentation or application) in the manifest. + * Installation parse return code: this is passed in the + * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser did not find any actionable tags + * (instrumentation or application) in the manifest. * * @hide */ @@ -1252,9 +1234,9 @@ public abstract class PackageManager { public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109; /** - * Installation failed return code: this is passed to the - * {@link IPackageInstallObserver} if the system failed to install the - * package because of system issues. + * Installation failed return code: this is passed in the + * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package + * because of system issues. * * @hide */ @@ -1262,24 +1244,23 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_INTERNAL_ERROR = -110; /** - * Installation failed return code: this is passed to the - * {@link IPackageInstallObserver} if the system failed to install the - * package because the user is restricted from installing apps. + * Installation failed return code: this is passed in the + * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package + * because the user is restricted from installing apps. * * @hide */ public static final int INSTALL_FAILED_USER_RESTRICTED = -111; /** - * Installation failed return code: this is passed to the - * {@link IPackageInstallObserver} if the system failed to install the - * package because it is attempting to define a permission that is already - * defined by some existing package. + * Installation failed return code: this is passed in the + * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package + * because it is attempting to define a permission that is already defined by some existing + * package. * <p> - * The package name of the app which has already defined the permission is - * passed to a {@link PackageInstallObserver}, if any, as the - * {@link #EXTRA_FAILURE_EXISTING_PACKAGE} string extra; and the name of the - * permission being redefined is passed in the + * The package name of the app which has already defined the permission is passed to a + * {@link PackageInstallObserver}, if any, as the {@link #EXTRA_FAILURE_EXISTING_PACKAGE} string + * extra; and the name of the permission being redefined is passed in the * {@link #EXTRA_FAILURE_EXISTING_PERMISSION} string extra. * * @hide @@ -1287,10 +1268,9 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_DUPLICATE_PERMISSION = -112; /** - * Installation failed return code: this is passed to the - * {@link IPackageInstallObserver} if the system failed to install the - * package because its packaged native code did not match any of the ABIs - * supported by the system. + * Installation failed return code: this is passed in the + * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package + * because its packaged native code did not match any of the ABIs supported by the system. * * @hide */ @@ -1322,6 +1302,7 @@ public abstract class PackageManager { DELETE_ALL_USERS, DELETE_SYSTEM_APP, DELETE_DONT_KILL_APP, + DELETE_CHATTY, }) @Retention(RetentionPolicy.SOURCE) public @interface DeleteFlags {} @@ -1363,6 +1344,14 @@ public abstract class PackageManager { public static final int DELETE_DONT_KILL_APP = 0x00000008; /** + * Flag parameter for {@link #deletePackage} to indicate that package deletion + * should be chatty. + * + * @hide + */ + public static final int DELETE_CHATTY = 0x80000000; + + /** * Return code for when package deletion succeeds. This is passed to the * {@link IPackageDeleteObserver} if the system succeeded in deleting the * package. @@ -4718,16 +4707,6 @@ public abstract class PackageManager { @Deprecated public abstract void installPackage( Uri packageURI, - IPackageInstallObserver observer, - @InstallFlags int flags, - String installerPackageName); - /** - * @deprecated replaced by {@link PackageInstaller} - * @hide - */ - @Deprecated - public abstract void installPackage( - Uri packageURI, PackageInstallObserver observer, @InstallFlags int flags, String installerPackageName); @@ -5743,25 +5722,6 @@ public abstract class PackageManager { } /** {@hide} */ - public static class LegacyPackageInstallObserver extends PackageInstallObserver { - private final IPackageInstallObserver mLegacy; - - public LegacyPackageInstallObserver(IPackageInstallObserver legacy) { - mLegacy = legacy; - } - - @Override - public void onPackageInstalled(String basePackageName, int returnCode, String msg, - Bundle extras) { - if (mLegacy == null) return; - try { - mLegacy.packageInstalled(basePackageName, returnCode); - } catch (RemoteException ignored) { - } - } - } - - /** {@hide} */ public static class LegacyPackageDeleteObserver extends PackageDeleteObserver { private final IPackageDeleteObserver mLegacy; diff --git a/android/content/pm/PackageManagerInternal.java b/android/content/pm/PackageManagerInternal.java index 14cf8557..713cd109 100644 --- a/android/content/pm/PackageManagerInternal.java +++ b/android/content/pm/PackageManagerInternal.java @@ -164,6 +164,14 @@ public abstract class PackageManagerInternal { @PackageInfoFlags int flags, int filterCallingUid, int userId); /** + * Do a straight uid lookup for the given package/application in the given user. + * @see PackageManager#getPackageUidAsUser(String, int, int) + * @return The app's uid, or < 0 if the package was not found in that user + */ + public abstract int getPackageUid(String packageName, + @PackageInfoFlags int flags, int userId); + + /** * Retrieve all of the information we know about a particular package/application. * @param filterCallingUid The results will be filtered in the context of this UID instead * of the calling UID. diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java index 1c5cf15d..ebeaad78 100644 --- a/android/content/pm/PackageParser.java +++ b/android/content/pm/PackageParser.java @@ -42,6 +42,7 @@ import static android.os.Build.VERSION_CODES.O; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -107,6 +108,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.security.GeneralSecurityException; import java.security.KeyFactory; @@ -678,6 +681,8 @@ public class PackageParser { pi.overlayTarget = p.mOverlayTarget; pi.overlayPriority = p.mOverlayPriority; pi.isStaticOverlay = p.mIsStaticOverlay; + pi.compileSdkVersion = p.mCompileSdkVersion; + pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename; pi.firstInstallTime = firstInstallTime; pi.lastUpdateTime = lastUpdateTime; if ((flags&PackageManager.GET_GIDS) != 0) { @@ -816,22 +821,33 @@ public class PackageParser { } } - public static final int PARSE_IS_SYSTEM = 1 << 0; - public static final int PARSE_CHATTY = 1 << 1; - public static final int PARSE_MUST_BE_APK = 1 << 2; - public static final int PARSE_IGNORE_PROCESSES = 1 << 3; - public static final int PARSE_FORWARD_LOCK = 1 << 4; - public static final int PARSE_EXTERNAL_STORAGE = 1 << 5; - public static final int PARSE_IS_SYSTEM_DIR = 1 << 6; - public static final int PARSE_IS_PRIVILEGED = 1 << 7; - public static final int PARSE_COLLECT_CERTIFICATES = 1 << 8; - public static final int PARSE_TRUSTED_OVERLAY = 1 << 9; - public static final int PARSE_ENFORCE_CODE = 1 << 10; - /** @deprecated remove when fixing b/34761192 */ + public static final int PARSE_MUST_BE_APK = 1 << 0; + public static final int PARSE_IGNORE_PROCESSES = 1 << 1; + public static final int PARSE_FORWARD_LOCK = 1 << 2; + public static final int PARSE_EXTERNAL_STORAGE = 1 << 3; + public static final int PARSE_IS_SYSTEM_DIR = 1 << 4; + public static final int PARSE_COLLECT_CERTIFICATES = 1 << 5; + public static final int PARSE_ENFORCE_CODE = 1 << 6; + public static final int PARSE_FORCE_SDK = 1 << 7; + /** @deprecated remove when fixing b/68860689 */ @Deprecated - public static final int PARSE_IS_EPHEMERAL = 1 << 11; - public static final int PARSE_FORCE_SDK = 1 << 12; - public static final int PARSE_IS_OEM = 1 << 13; + public static final int PARSE_IS_EPHEMERAL = 1 << 8; + public static final int PARSE_CHATTY = 1 << 31; + + @IntDef(flag = true, prefix = { "PARSE_" }, value = { + PARSE_CHATTY, + PARSE_COLLECT_CERTIFICATES, + PARSE_ENFORCE_CODE, + PARSE_EXTERNAL_STORAGE, + PARSE_FORCE_SDK, + PARSE_FORWARD_LOCK, + PARSE_IGNORE_PROCESSES, + PARSE_IS_EPHEMERAL, + PARSE_IS_SYSTEM_DIR, + PARSE_MUST_BE_APK, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ParseFlags {} private static final Comparator<String> sSplitNameComparator = new SplitNameComparator(); @@ -1242,9 +1258,12 @@ public class PackageParser { } } - pkg.setCodePath(packageDir.getAbsolutePath()); + pkg.setCodePath(packageDir.getCanonicalPath()); pkg.setUse32bitAbi(lite.use32bitAbi); return pkg; + } catch (IOException e) { + throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, + "Failed to get path: " + lite.baseCodePath, e); } finally { IoUtils.closeQuietly(assetLoader); } @@ -1273,9 +1292,12 @@ public class PackageParser { try { final Package pkg = parseBaseApk(apkFile, assets, flags); - pkg.setCodePath(apkFile.getAbsolutePath()); + pkg.setCodePath(apkFile.getCanonicalPath()); pkg.setUse32bitAbi(lite.use32bitAbi); return pkg; + } catch (IOException e) { + throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, + "Failed to get path: " + apkFile, e); } finally { IoUtils.closeQuietly(assets); } @@ -1505,7 +1527,7 @@ public class PackageParser { * populating {@link Package#mSignatures}. Also asserts that all APK * contents are signed correctly and consistently. */ - public static void collectCertificates(Package pkg, int parseFlags) + public static void collectCertificates(Package pkg, @ParseFlags int parseFlags) throws PackageParserException { collectCertificatesInternal(pkg, parseFlags); final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; @@ -1517,7 +1539,7 @@ public class PackageParser { } } - private static void collectCertificatesInternal(Package pkg, int parseFlags) + private static void collectCertificatesInternal(Package pkg, @ParseFlags int parseFlags) throws PackageParserException { pkg.mCertificates = null; pkg.mSignatures = null; @@ -1537,7 +1559,7 @@ public class PackageParser { } } - private static void collectCertificates(Package pkg, File apkFile, int parseFlags) + private static void collectCertificates(Package pkg, File apkFile, @ParseFlags int parseFlags) throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); @@ -2076,6 +2098,16 @@ public class PackageParser { pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false); + pkg.mCompileSdkVersion = sa.getInteger( + com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0); + pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion; + pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString( + com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0); + if (pkg.mCompileSdkVersionCodename != null) { + pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern(); + } + pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename; + sa.recycle(); return parseBaseApkCommon(pkg, null, res, parser, flags, outError); @@ -2442,7 +2474,7 @@ public class PackageParser { sa.recycle(); - if (name != null && (flags&PARSE_IS_SYSTEM) != 0) { + if (name != null) { if (pkg.protectedBroadcasts == null) { pkg.protectedBroadcasts = new ArrayList<String>(); } @@ -3243,9 +3275,6 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags, 0); perm.info.priority = sa.getInt( com.android.internal.R.styleable.AndroidManifestPermissionGroup_priority, 0); - if (perm.info.priority > 0 && (flags&PARSE_IS_SYSTEM) == 0) { - perm.info.priority = 0; - } sa.recycle(); @@ -3551,17 +3580,14 @@ public class PackageParser { ai.descriptionRes = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestApplication_description, 0); - if ((flags&PARSE_IS_SYSTEM) != 0) { - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestApplication_persistent, - false)) { - // Check if persistence is based on a feature being present - final String requiredFeature = sa.getNonResourceString( - com.android.internal.R.styleable. - AndroidManifestApplication_persistentWhenFeatureAvailable); - if (requiredFeature == null || mCallback.hasFeature(requiredFeature)) { - ai.flags |= ApplicationInfo.FLAG_PERSISTENT; - } + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestApplication_persistent, + false)) { + // Check if persistence is based on a feature being present + final String requiredFeature = sa.getNonResourceString(com.android.internal.R.styleable + .AndroidManifestApplication_persistentWhenFeatureAvailable); + if (requiredFeature == null || mCallback.hasFeature(requiredFeature)) { + ai.flags |= ApplicationInfo.FLAG_PERSISTENT; } } @@ -4431,13 +4457,6 @@ public class PackageParser { if (sa.getBoolean(R.styleable.AndroidManifestActivity_singleUser, false)) { a.info.flags |= ActivityInfo.FLAG_SINGLE_USER; - if (a.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) { - Slog.w(TAG, "Activity exported request ignored due to singleUser: " - + a.className + " at " + mArchiveSourcePath + " " - + parser.getPositionDescription()); - a.info.exported = false; - setExported = true; - } } a.info.encryptionAware = a.info.directBootAware = sa.getBoolean( @@ -5026,12 +5045,6 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestProvider_singleUser, false)) { p.info.flags |= ProviderInfo.FLAG_SINGLE_USER; - if (p.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) { - Slog.w(TAG, "Provider exported request ignored due to singleUser: " - + p.className + " at " + mArchiveSourcePath + " " - + parser.getPositionDescription()); - p.info.exported = false; - } } p.info.encryptionAware = p.info.directBootAware = sa.getBoolean( @@ -5353,13 +5366,6 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestService_singleUser, false)) { s.info.flags |= ServiceInfo.FLAG_SINGLE_USER; - if (s.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) { - Slog.w(TAG, "Service exported request ignored due to singleUser: " - + s.className + " at " + mArchiveSourcePath + " " - + parser.getPositionDescription()); - s.info.exported = false; - setExported = true; - } } s.info.encryptionAware = s.info.directBootAware = sa.getBoolean( @@ -5967,6 +5973,9 @@ public class PackageParser { public boolean mIsStaticOverlay; public boolean mTrustedOverlay; + public int mCompileSdkVersion; + public String mCompileSdkVersionCodename; + /** * Data used to feed the KeySetManagerService */ @@ -6458,6 +6467,8 @@ public class PackageParser { mOverlayPriority = dest.readInt(); mIsStaticOverlay = (dest.readInt() == 1); mTrustedOverlay = (dest.readInt() == 1); + mCompileSdkVersion = dest.readInt(); + mCompileSdkVersionCodename = dest.readString(); mSigningKeys = (ArraySet<PublicKey>) dest.readArraySet(boot); mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot); @@ -6581,6 +6592,8 @@ public class PackageParser { dest.writeInt(mOverlayPriority); dest.writeInt(mIsStaticOverlay ? 1 : 0); dest.writeInt(mTrustedOverlay ? 1 : 0); + dest.writeInt(mCompileSdkVersion); + dest.writeString(mCompileSdkVersionCodename); dest.writeArraySet(mSigningKeys); dest.writeArraySet(mUpgradeKeySets); writeKeySetMapping(dest, mKeySetMapping); diff --git a/android/content/pm/PermissionInfo.java b/android/content/pm/PermissionInfo.java index 5dd7aeda..75887624 100644 --- a/android/content/pm/PermissionInfo.java +++ b/android/content/pm/PermissionInfo.java @@ -155,12 +155,18 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { /** * The level of access this permission is protecting, as per - * {@link android.R.attr#protectionLevel}. Values may be - * {@link #PROTECTION_NORMAL}, {@link #PROTECTION_DANGEROUS}, or - * {@link #PROTECTION_SIGNATURE}. May also include the additional - * flags {@link #PROTECTION_FLAG_SYSTEM} or {@link #PROTECTION_FLAG_DEVELOPMENT} - * (which only make sense in combination with the base - * {@link #PROTECTION_SIGNATURE}. + * {@link android.R.attr#protectionLevel}. Consists of + * a base permission type and zero or more flags: + * + * <pre> + * int basePermissionType = protectionLevel & {@link #PROTECTION_MASK_BASE}; + * int permissionFlags = protectionLevel & {@link #PROTECTION_MASK_FLAGS}; + * </pre> + * + * <p></p>Base permission types are {@link #PROTECTION_NORMAL}, + * {@link #PROTECTION_DANGEROUS}, {@link #PROTECTION_SIGNATURE} + * and the deprecated {@link #PROTECTION_SIGNATURE_OR_SYSTEM}. + * Flags are listed under {@link android.R.attr#protectionLevel}. */ public int protectionLevel; diff --git a/android/database/sqlite/SQLiteConnectionPool.java b/android/database/sqlite/SQLiteConnectionPool.java index 8b0fef4f..5adb1196 100644 --- a/android/database/sqlite/SQLiteConnectionPool.java +++ b/android/database/sqlite/SQLiteConnectionPool.java @@ -570,6 +570,16 @@ public final class SQLiteConnectionPool implements Closeable { mAvailableNonPrimaryConnections.clear(); } + /** + * Close non-primary connections that are not currently in use. This method is safe to use + * in finalize block as it doesn't throw RuntimeExceptions. + */ + void closeAvailableNonPrimaryConnectionsAndLogExceptions() { + synchronized (mLock) { + closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked(); + } + } + // Can't throw. private void closeExcessConnectionsAndLogExceptionsLocked() { int availableCount = mAvailableNonPrimaryConnections.size(); diff --git a/android/database/sqlite/SQLiteDatabase.java b/android/database/sqlite/SQLiteDatabase.java index 863fb198..09bb9c69 100644 --- a/android/database/sqlite/SQLiteDatabase.java +++ b/android/database/sqlite/SQLiteDatabase.java @@ -1740,7 +1740,8 @@ public final class SQLiteDatabase extends SQLiteClosable { private int executeSql(String sql, Object[] bindArgs) throws SQLException { acquireReference(); try { - if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) { + final int statementType = DatabaseUtils.getSqlStatementType(sql); + if (statementType == DatabaseUtils.STATEMENT_ATTACH) { boolean disableWal = false; synchronized (mLock) { if (!mHasAttachedDbsLocked) { @@ -1754,11 +1755,14 @@ public final class SQLiteDatabase extends SQLiteClosable { } } - SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs); - try { + try (SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs)) { return statement.executeUpdateDelete(); } finally { - statement.close(); + // If schema was updated, close non-primary connections, otherwise they might + // have outdated schema information + if (statementType == DatabaseUtils.STATEMENT_DDL) { + mConnectionPoolLocked.closeAvailableNonPrimaryConnectionsAndLogExceptions(); + } } } finally { releaseReference(); diff --git a/android/graphics/Paint_Delegate.java b/android/graphics/Paint_Delegate.java index 62427020..4f051761 100644 --- a/android/graphics/Paint_Delegate.java +++ b/android/graphics/Paint_Delegate.java @@ -125,8 +125,13 @@ public class Paint_Delegate { */ @NonNull public List<FontInfo> getFonts() { - if (mTypeface == null) { - return Collections.emptyList(); + Typeface_Delegate typeface = mTypeface; + if (typeface == null) { + if (Typeface.sDefaultTypeface == null) { + return Collections.emptyList(); + } + + typeface = Typeface_Delegate.getDelegate(Typeface.sDefaultTypeface.native_instance); } if (mFonts != null) { @@ -138,7 +143,7 @@ public class Paint_Delegate { new AffineTransform(mTextScaleX, mTextSkewX, 0, 1, 0, 0) : null; - List<FontInfo> infoList = StreamSupport.stream(mTypeface.getFonts(mFontVariant).spliterator + List<FontInfo> infoList = StreamSupport.stream(typeface.getFonts(mFontVariant).spliterator (), false) .map(font -> getFontInfo(font, mTextSize, affineTransform)) .collect(Collectors.toList()); diff --git a/android/hardware/camera2/CameraCharacteristics.java b/android/hardware/camera2/CameraCharacteristics.java index 46ad3f0e..3a3048ef 100644 --- a/android/hardware/camera2/CameraCharacteristics.java +++ b/android/hardware/camera2/CameraCharacteristics.java @@ -1280,11 +1280,11 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <ul> * <li>Processed (but stalling): any non-RAW format with a stallDurations > 0. * Typically {@link android.graphics.ImageFormat#JPEG JPEG format}.</li> - * <li>Raw formats: {@link android.graphics.ImageFormat#RAW_SENSOR RAW_SENSOR}, {@link android.graphics.ImageFormat#RAW10 RAW10}, or {@link android.graphics.ImageFormat#RAW12 RAW12}.</li> - * <li>Processed (but not-stalling): any non-RAW format without a stall duration. - * Typically {@link android.graphics.ImageFormat#YUV_420_888 YUV_420_888}, - * {@link android.graphics.ImageFormat#NV21 NV21}, or - * {@link android.graphics.ImageFormat#YV12 YV12}.</li> + * <li>Raw formats: {@link android.graphics.ImageFormat#RAW_SENSOR RAW_SENSOR}, {@link android.graphics.ImageFormat#RAW10 RAW10}, or + * {@link android.graphics.ImageFormat#RAW12 RAW12}.</li> + * <li>Processed (but not-stalling): any non-RAW format without a stall duration. Typically + * {@link android.graphics.ImageFormat#YUV_420_888 YUV_420_888}, + * {@link android.graphics.ImageFormat#NV21 NV21}, or {@link android.graphics.ImageFormat#YV12 YV12}.</li> * </ul> * <p><b>Range of valid values:</b><br></p> * <p>For processed (and stalling) format streams, >= 1.</p> @@ -1376,8 +1376,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * CPU resources that will consume more power. The image format for this kind of an output stream can * be any non-<code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p> * <p>A processed and stalling format is defined as any non-RAW format with a stallDurations - * > 0. Typically only the {@link android.graphics.ImageFormat#JPEG JPEG format} is a - * stalling format.</p> + * > 0. Typically only the {@link android.graphics.ImageFormat#JPEG JPEG format} is a stalling format.</p> * <p>For full guarantees, query {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration } with a * processed format -- it will return a non-0 value for a stalling stream.</p> * <p>LEGACY devices will support up to 1 processing/stalling stream.</p> @@ -1535,8 +1534,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<int[]>("android.request.availableRequestKeys", int[].class); /** - * <p>A list of all keys that the camera device has available - * to use with {@link android.hardware.camera2.CaptureResult }.</p> + * <p>A list of all keys that the camera device has available to use with {@link android.hardware.camera2.CaptureResult }.</p> * <p>Attempting to get a key from a CaptureResult that is not * listed here will always return a <code>null</code> value. Getting a key from * a CaptureResult that is listed here will generally never return a <code>null</code> @@ -1561,8 +1559,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<int[]>("android.request.availableResultKeys", int[].class); /** - * <p>A list of all keys that the camera device has available - * to use with {@link android.hardware.camera2.CameraCharacteristics }.</p> + * <p>A list of all keys that the camera device has available to use with {@link android.hardware.camera2.CameraCharacteristics }.</p> * <p>This entry follows the same rules as * android.request.availableResultKeys (except that it applies for * CameraCharacteristics instead of CaptureResult). See above for more @@ -1843,8 +1840,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and * android.scaler.availableStallDurations for more details about * calculating the max frame rate.</p> - * <p>(Keep in sync with - * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration })</p> * <p><b>Units</b>: (format, width, height, ns) x n</p> * <p>This key is available on all devices.</p> * @@ -1905,14 +1900,13 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <ul> * <li>{@link android.graphics.ImageFormat#YUV_420_888 }</li> * <li>{@link android.graphics.ImageFormat#RAW10 }</li> + * <li>{@link android.graphics.ImageFormat#RAW12 }</li> * </ul> * <p>All other formats may or may not have an allowed stall duration on * a per-capability basis; refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} * for more details.</p> * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} for more information about * calculating the max frame rate (absent stalls).</p> - * <p>(Keep up to date with - * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration } )</p> * <p><b>Units</b>: (format, width, height, ns) x n</p> * <p>This key is available on all devices.</p> * @@ -2195,9 +2189,9 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * the raw buffers produced by this sensor.</p> * <p>If a camera device supports raw sensor formats, either this or * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} is the maximum dimensions for the raw - * output formats listed in {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} (this depends on - * whether or not the image sensor returns buffers containing pixels that are not - * part of the active array region for blacklevel calibration or other purposes).</p> + * output formats listed in {@link android.hardware.camera2.params.StreamConfigurationMap } + * (this depends on whether or not the image sensor returns buffers containing pixels that + * are not part of the active array region for blacklevel calibration or other purposes).</p> * <p>Some parts of the full pixel array may not receive light from the scene, * or be otherwise inactive. The {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} key * defines the rectangle of active pixels that will be included in processed image @@ -2205,7 +2199,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p><b>Units</b>: Pixels</p> * <p>This key is available on all devices.</p> * - * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP * @see CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE */ @@ -2838,7 +2831,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>See the individual level enums for full descriptions of the supported capabilities. The * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} entry describes the device's capabilities at a * finer-grain level, if needed. In addition, many controls have their available settings or - * ranges defined in individual {@link android.hardware.camera2.CameraCharacteristics } entries.</p> + * ranges defined in individual entries from {@link android.hardware.camera2.CameraCharacteristics }.</p> * <p>Some features are not part of any particular hardware level or capability and must be * queried separately. These include:</p> * <ul> @@ -2973,7 +2966,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and * android.scaler.availableStallDurations for more details about * calculating the max frame rate.</p> - * <p>(Keep in sync with {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration })</p> * <p><b>Units</b>: (format, width, height, ns) x n</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * <p><b>Limited capability</b> - diff --git a/android/hardware/camera2/CameraMetadata.java b/android/hardware/camera2/CameraMetadata.java index 8c8c49fa..4b57018b 100644 --- a/android/hardware/camera2/CameraMetadata.java +++ b/android/hardware/camera2/CameraMetadata.java @@ -587,8 +587,8 @@ public abstract class CameraMetadata<TKey> { * then the list of resolutions for YUV_420_888 from {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes } contains at * least one resolution >= 8 megapixels, with a minimum frame duration of <= 1/20 * s.</p> - * <p>If the device supports the {@link android.graphics.ImageFormat#RAW10 }, {@link android.graphics.ImageFormat#RAW12 }, then those can also be captured at the same rate - * as the maximum-size YUV_420_888 resolution is.</p> + * <p>If the device supports the {@link android.graphics.ImageFormat#RAW10 }, {@link android.graphics.ImageFormat#RAW12 }, then those can also be + * captured at the same rate as the maximum-size YUV_420_888 resolution is.</p> * <p>If the device supports the PRIVATE_REPROCESSING capability, then the same guarantees * as for the YUV_420_888 format also apply to the {@link android.graphics.ImageFormat#PRIVATE } format.</p> * <p>In addition, the {@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} field is guaranted to have a value between 0 @@ -610,25 +610,22 @@ public abstract class CameraMetadata<TKey> { * following:</p> * <ul> * <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li> - * <li>{@link android.graphics.ImageFormat#YUV_420_888 } is supported as an output/input format, that is, - * YUV_420_888 is included in the lists of formats returned by - * {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and - * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.</li> + * <li>{@link android.graphics.ImageFormat#YUV_420_888 } is supported as an output/input + * format, that is, YUV_420_888 is included in the lists of formats returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.</li> * <li>{@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput } * returns non-empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.</li> * <li>Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(YUV_420_888)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(YUV_420_888)}</li> - * <li>Using {@link android.graphics.ImageFormat#YUV_420_888 } does not cause a frame rate drop - * relative to the sensor's maximum capture rate (at that resolution).</li> + * <li>Using {@link android.graphics.ImageFormat#YUV_420_888 } does not cause a frame rate + * drop relative to the sensor's maximum capture rate (at that resolution).</li> * <li>{@link android.graphics.ImageFormat#YUV_420_888 } will be reprocessable into both * {@link android.graphics.ImageFormat#YUV_420_888 } and {@link android.graphics.ImageFormat#JPEG } formats.</li> * <li>The maximum available resolution for {@link android.graphics.ImageFormat#YUV_420_888 } streams (both input/output) will match the * maximum available resolution of {@link android.graphics.ImageFormat#JPEG } streams.</li> * <li>Static metadata {@link CameraCharacteristics#REPROCESS_MAX_CAPTURE_STALL android.reprocess.maxCaptureStall}.</li> * <li>Only the below controls are effective for reprocessing requests and will be present - * in capture results. The reprocess requests are from the original capture results that - * are associated with the intermediate {@link android.graphics.ImageFormat#YUV_420_888 } - * output buffers. All other controls in the reprocess requests will be ignored by the - * camera device.<ul> + * in capture results. The reprocess requests are from the original capture results + * that are associated with the intermediate {@link android.graphics.ImageFormat#YUV_420_888 } output buffers. All other controls in the + * reprocess requests will be ignored by the camera device.<ul> * <li>android.jpeg.*</li> * <li>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}</li> * <li>{@link CaptureRequest#EDGE_MODE android.edge.mode}</li> @@ -654,13 +651,13 @@ public abstract class CameraMetadata<TKey> { * <p>The camera device can produce depth measurements from its field of view.</p> * <p>This capability requires the camera device to support the following:</p> * <ul> - * <li>{@link android.graphics.ImageFormat#DEPTH16 } is supported as an output format.</li> - * <li>{@link android.graphics.ImageFormat#DEPTH_POINT_CLOUD } is optionally supported as an - * output format.</li> - * <li>This camera device, and all camera devices with the same {@link CameraCharacteristics#LENS_FACING android.lens.facing}, - * will list the following calibration entries in both - * {@link android.hardware.camera2.CameraCharacteristics } and - * {@link android.hardware.camera2.CaptureResult }:<ul> + * <li>{@link android.graphics.ImageFormat#DEPTH16 } is supported as + * an output format.</li> + * <li>{@link android.graphics.ImageFormat#DEPTH_POINT_CLOUD } is + * optionally supported as an output format.</li> + * <li>This camera device, and all camera devices with the same {@link CameraCharacteristics#LENS_FACING android.lens.facing}, will + * list the following calibration metadata entries in both {@link android.hardware.camera2.CameraCharacteristics } + * and {@link android.hardware.camera2.CaptureResult }:<ul> * <li>{@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}</li> * <li>{@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}</li> * <li>{@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}</li> @@ -674,8 +671,7 @@ public abstract class CameraMetadata<TKey> { * </ul> * <p>Generally, depth output operates at a slower frame rate than standard color capture, * so the DEPTH16 and DEPTH_POINT_CLOUD formats will commonly have a stall duration that - * should be accounted for (see - * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }). + * should be accounted for (see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }). * On a device that supports both depth and color-based output, to enable smooth preview, * using a repeating burst is recommended, where a depth-output target is only included * once every N frames, where N is the ratio between preview output rate and depth output @@ -692,23 +688,19 @@ public abstract class CameraMetadata<TKey> { public static final int REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT = 8; /** - * <p>The device supports constrained high speed video recording (frame rate >=120fps) - * use case. The camera device will support high speed capture session created by - * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession }, which - * only accepts high speed request lists created by - * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList }.</p> - * <p>A camera device can still support high speed video streaming by advertising the high speed - * FPS ranges in {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges}. For this case, all normal - * capture request per frame control and synchronization requirements will apply to - * the high speed fps ranges, the same as all other fps ranges. This capability describes - * the capability of a specialized operating mode with many limitations (see below), which - * is only targeted at high speed video recording.</p> - * <p>The supported high speed video sizes and fps ranges are specified in - * {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRanges }. - * To get desired output frame rates, the application is only allowed to select video size - * and FPS range combinations provided by - * {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoSizes }. - * The fps range can be controlled via {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}.</p> + * <p>The device supports constrained high speed video recording (frame rate >=120fps) use + * case. The camera device will support high speed capture session created by {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession }, which + * only accepts high speed request lists created by {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList }.</p> + * <p>A camera device can still support high speed video streaming by advertising the high + * speed FPS ranges in {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges}. For this case, all + * normal capture request per frame control and synchronization requirements will apply + * to the high speed fps ranges, the same as all other fps ranges. This capability + * describes the capability of a specialized operating mode with many limitations (see + * below), which is only targeted at high speed video recording.</p> + * <p>The supported high speed video sizes and fps ranges are specified in {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRanges }. + * To get desired output frame rates, the application is only allowed to select video + * size and FPS range combinations provided by {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoSizes }. The + * fps range can be controlled via {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}.</p> * <p>In this capability, the camera device will override aeMode, awbMode, and afMode to * ON, AUTO, and CONTINUOUS_VIDEO, respectively. All post-processing block mode * controls will be overridden to be FAST. Therefore, no manual control of capture @@ -743,19 +735,16 @@ public abstract class CameraMetadata<TKey> { * frame rate. If the destination surface is from preview window, the actual preview frame * rate will be bounded by the screen refresh rate.</p> * <p>The camera device will only support up to 2 high speed simultaneous output surfaces - * (preview and recording surfaces) - * in this mode. Above controls will be effective only if all of below conditions are true:</p> + * (preview and recording surfaces) in this mode. Above controls will be effective only + * if all of below conditions are true:</p> * <ul> * <li>The application creates a camera capture session with no more than 2 surfaces via * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession }. The - * targeted surfaces must be preview surface (either from - * {@link android.view.SurfaceView } or {@link android.graphics.SurfaceTexture }) or - * recording surface(either from {@link android.media.MediaRecorder#getSurface } or - * {@link android.media.MediaCodec#createInputSurface }).</li> + * targeted surfaces must be preview surface (either from {@link android.view.SurfaceView } or {@link android.graphics.SurfaceTexture }) or recording + * surface(either from {@link android.media.MediaRecorder#getSurface } or {@link android.media.MediaCodec#createInputSurface }).</li> * <li>The stream sizes are selected from the sizes reported by * {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoSizes }.</li> - * <li>The FPS ranges are selected from - * {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRanges }.</li> + * <li>The FPS ranges are selected from {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRanges }.</li> * </ul> * <p>When above conditions are NOT satistied, * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession } @@ -1038,8 +1027,7 @@ public abstract class CameraMetadata<TKey> { /** * <p>This camera device is running in backward compatibility mode.</p> - * <p>Only the stream configurations listed in the <code>LEGACY</code> table in the {@link android.hardware.camera2.CameraDevice#createCaptureSession createCaptureSession} - * documentation are supported.</p> + * <p>Only the stream configurations listed in the <code>LEGACY</code> table in the {@link android.hardware.camera2.CameraDevice#createCaptureSession createCaptureSession} documentation are supported.</p> * <p>A <code>LEGACY</code> device does not support per-frame control, manual sensor control, manual * post-processing, arbitrary cropping regions, and has relaxed performance constraints. * No additional capabilities beyond <code>BACKWARD_COMPATIBLE</code> will ever be listed by a @@ -1061,8 +1049,7 @@ public abstract class CameraMetadata<TKey> { * <p>This camera device is capable of YUV reprocessing and RAW data capture, in addition to * FULL-level capabilities.</p> * <p>The stream configurations listed in the <code>LEVEL_3</code>, <code>RAW</code>, <code>FULL</code>, <code>LEGACY</code> and - * <code>LIMITED</code> tables in the {@link android.hardware.camera2.CameraDevice#createCaptureSession createCaptureSession} - * documentation are guaranteed to be supported.</p> + * <code>LIMITED</code> tables in the {@link android.hardware.camera2.CameraDevice#createCaptureSession createCaptureSession} documentation are guaranteed to be supported.</p> * <p>The following additional capabilities are guaranteed to be supported:</p> * <ul> * <li><code>YUV_REPROCESSING</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains @@ -2155,12 +2142,13 @@ public abstract class CameraMetadata<TKey> { public static final int EDGE_MODE_HIGH_QUALITY = 2; /** - * <p>Edge enhancement is applied at different levels for different output streams, - * based on resolution. Streams at maximum recording resolution (see {@link android.hardware.camera2.CameraDevice#createCaptureSession }) or below have - * edge enhancement applied, while higher-resolution streams have no edge enhancement - * applied. The level of edge enhancement for low-resolution streams is tuned so that - * frame rate is not impacted, and the quality is equal to or better than FAST (since it - * is only applied to lower-resolution outputs, quality may improve from FAST).</p> + * <p>Edge enhancement is applied at different + * levels for different output streams, based on resolution. Streams at maximum recording + * resolution (see {@link android.hardware.camera2.CameraDevice#createCaptureSession }) + * or below have edge enhancement applied, while higher-resolution streams have no edge + * enhancement applied. The level of edge enhancement for low-resolution streams is tuned + * so that frame rate is not impacted, and the quality is equal to or better than FAST + * (since it is only applied to lower-resolution outputs, quality may improve from FAST).</p> * <p>This mode is intended to be used by applications operating in a zero-shutter-lag mode * with YUV or PRIVATE reprocessing, where the application continuously captures * high-resolution intermediate buffers into a circular buffer, from which a final image is @@ -2287,12 +2275,12 @@ public abstract class CameraMetadata<TKey> { /** * <p>Noise reduction is applied at different levels for different output streams, - * based on resolution. Streams at maximum recording resolution (see {@link android.hardware.camera2.CameraDevice#createCaptureSession }) or below have noise - * reduction applied, while higher-resolution streams have MINIMAL (if supported) or no - * noise reduction applied (if MINIMAL is not supported.) The degree of noise reduction - * for low-resolution streams is tuned so that frame rate is not impacted, and the quality - * is equal to or better than FAST (since it is only applied to lower-resolution outputs, - * quality may improve from FAST).</p> + * based on resolution. Streams at maximum recording resolution (see {@link android.hardware.camera2.CameraDevice#createCaptureSession }) + * or below have noise reduction applied, while higher-resolution streams have MINIMAL (if + * supported) or no noise reduction applied (if MINIMAL is not supported.) The degree of + * noise reduction for low-resolution streams is tuned so that frame rate is not impacted, + * and the quality is equal to or better than FAST (since it is only applied to + * lower-resolution outputs, quality may improve from FAST).</p> * <p>This mode is intended to be used by applications operating in a zero-shutter-lag mode * with YUV or PRIVATE reprocessing, where the application continuously captures * high-resolution intermediate buffers into a circular buffer, from which a final image is diff --git a/android/hardware/camera2/CaptureRequest.java b/android/hardware/camera2/CaptureRequest.java index c41fc020..0262ecb5 100644 --- a/android/hardware/camera2/CaptureRequest.java +++ b/android/hardware/camera2/CaptureRequest.java @@ -680,7 +680,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * FAST or HIGH_QUALITY will yield a picture with the same white point * as what was produced by the camera device in the earlier frame.</p> * <p>The expected processing pipeline is as follows:</p> - * <p><img alt="White balance processing pipeline" src="../../../../images/camera2/metadata/android.colorCorrection.mode/processing_pipeline.png" /></p> + * <p><img alt="White balance processing pipeline" src="/reference/images/camera2/metadata/android.colorCorrection.mode/processing_pipeline.png" /></p> * <p>The white balance is encoded by two values, a 4-channel white-balance * gain vector (applied in the Bayer domain), and a 3x3 color transform * matrix (applied after demosaic).</p> @@ -1470,10 +1470,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>When set to AUTO, the individual algorithm controls in * android.control.* are in effect, such as {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}.</p> * <p>When set to USE_SCENE_MODE, the individual controls in - * android.control.* are mostly disabled, and the camera device implements - * one of the scene mode settings (such as ACTION, SUNSET, or PARTY) - * as it wishes. The camera device scene mode 3A settings are provided by - * {@link android.hardware.camera2.CaptureResult capture results}.</p> + * android.control.* are mostly disabled, and the camera device + * implements one of the scene mode settings (such as ACTION, + * SUNSET, or PARTY) as it wishes. The camera device scene mode + * 3A settings are provided by {@link android.hardware.camera2.CaptureResult capture results}.</p> * <p>When set to OFF_KEEP_STATE, it is similar to OFF mode, the only difference * is that this frame will not be used by camera device background 3A statistics * update, as if this frame is never captured. This mode can be used in the scenario @@ -2268,45 +2268,35 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * can run concurrently to the rest of the camera pipeline, but * cannot process more than 1 capture at a time.</li> * </ul> - * <p>The necessary information for the application, given the model above, - * is provided via the {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} field using + * <p>The necessary information for the application, given the model above, is provided via * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration }. - * These are used to determine the maximum frame rate / minimum frame - * duration that is possible for a given stream configuration.</p> + * These are used to determine the maximum frame rate / minimum frame duration that is + * possible for a given stream configuration.</p> * <p>Specifically, the application can use the following rules to * determine the minimum frame duration it can request from the camera * device:</p> * <ol> - * <li>Let the set of currently configured input/output streams - * be called <code>S</code>.</li> - * <li>Find the minimum frame durations for each stream in <code>S</code>, by looking - * it up in {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} using {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration } - * (with its respective size/format). Let this set of frame durations be - * called <code>F</code>.</li> - * <li>For any given request <code>R</code>, the minimum frame duration allowed - * for <code>R</code> is the maximum out of all values in <code>F</code>. Let the streams - * used in <code>R</code> be called <code>S_r</code>.</li> + * <li>Let the set of currently configured input/output streams be called <code>S</code>.</li> + * <li>Find the minimum frame durations for each stream in <code>S</code>, by looking it up in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration } + * (with its respective size/format). Let this set of frame durations be called <code>F</code>.</li> + * <li>For any given request <code>R</code>, the minimum frame duration allowed for <code>R</code> is the maximum + * out of all values in <code>F</code>. Let the streams used in <code>R</code> be called <code>S_r</code>.</li> * </ol> * <p>If none of the streams in <code>S_r</code> have a stall time (listed in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration } - * using its respective size/format), then the frame duration in <code>F</code> - * determines the steady state frame rate that the application will get - * if it uses <code>R</code> as a repeating request. Let this special kind of - * request be called <code>Rsimple</code>.</p> - * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved - * by a single capture of a new request <code>Rstall</code> (which has at least - * one in-use stream with a non-0 stall time) and if <code>Rstall</code> has the - * same minimum frame duration this will not cause a frame rate loss - * if all buffers from the previous <code>Rstall</code> have already been - * delivered.</p> - * <p>For more details about stalling, see - * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p> + * using its respective size/format), then the frame duration in <code>F</code> determines the steady + * state frame rate that the application will get if it uses <code>R</code> as a repeating request. Let + * this special kind of request be called <code>Rsimple</code>.</p> + * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved by a single capture of a + * new request <code>Rstall</code> (which has at least one in-use stream with a non-0 stall time) and if + * <code>Rstall</code> has the same minimum frame duration this will not cause a frame rate loss if all + * buffers from the previous <code>Rstall</code> have already been delivered.</p> + * <p>For more details about stalling, see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p> * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to * OFF; otherwise the auto-exposure algorithm will override this value.</p> * <p><b>Units</b>: Nanoseconds</p> * <p><b>Range of valid values:</b><br> - * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, - * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}. The duration - * is capped to <code>max(duration, exposureTime + overhead)</code>.</p> + * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, {@link android.hardware.camera2.params.StreamConfigurationMap }. + * The duration is capped to <code>max(duration, exposureTime + overhead)</code>.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * <p><b>Full capability</b> - * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the @@ -2315,7 +2305,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @see CaptureRequest#CONTROL_AE_MODE * @see CaptureRequest#CONTROL_MODE * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL - * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP * @see CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION */ @PublicKey @@ -2584,11 +2573,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>Linear mapping:</p> * <pre><code>android.tonemap.curveRed = [ 0, 0, 1.0, 1.0 ] * </code></pre> - * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> + * <p><img alt="Linear mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> * <p>Invert mapping:</p> * <pre><code>android.tonemap.curveRed = [ 0, 1.0, 1.0, 0 ] * </code></pre> - * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> + * <p><img alt="Inverting mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> * <p>Gamma 1/2.2 mapping, with 16 control points:</p> * <pre><code>android.tonemap.curveRed = [ * 0.0000, 0.0000, 0.0667, 0.2920, 0.1333, 0.4002, 0.2000, 0.4812, @@ -2596,7 +2585,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * 0.5333, 0.7515, 0.6000, 0.7928, 0.6667, 0.8317, 0.7333, 0.8685, * 0.8000, 0.9035, 0.8667, 0.9370, 0.9333, 0.9691, 1.0000, 1.0000 ] * </code></pre> - * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> + * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p> * <pre><code>android.tonemap.curveRed = [ * 0.0000, 0.0000, 0.0667, 0.2864, 0.1333, 0.4007, 0.2000, 0.4845, @@ -2604,7 +2593,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * 0.5333, 0.7569, 0.6000, 0.7977, 0.6667, 0.8360, 0.7333, 0.8721, * 0.8000, 0.9063, 0.8667, 0.9389, 0.9333, 0.9701, 1.0000, 1.0000 ] * </code></pre> - * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> + * <p><img alt="sRGB tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> * <p><b>Range of valid values:</b><br> * 0-1 on both input and output coordinates, normalized * as a floating-point value such that 0 == black and 1 == white.</p> @@ -2646,11 +2635,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>Linear mapping:</p> * <pre><code>curveRed = [ (0, 0), (1.0, 1.0) ] * </code></pre> - * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> + * <p><img alt="Linear mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> * <p>Invert mapping:</p> * <pre><code>curveRed = [ (0, 1.0), (1.0, 0) ] * </code></pre> - * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> + * <p><img alt="Inverting mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> * <p>Gamma 1/2.2 mapping, with 16 control points:</p> * <pre><code>curveRed = [ * (0.0000, 0.0000), (0.0667, 0.2920), (0.1333, 0.4002), (0.2000, 0.4812), @@ -2658,7 +2647,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * (0.5333, 0.7515), (0.6000, 0.7928), (0.6667, 0.8317), (0.7333, 0.8685), * (0.8000, 0.9035), (0.8667, 0.9370), (0.9333, 0.9691), (1.0000, 1.0000) ] * </code></pre> - * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> + * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p> * <pre><code>curveRed = [ * (0.0000, 0.0000), (0.0667, 0.2864), (0.1333, 0.4007), (0.2000, 0.4845), @@ -2666,7 +2655,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * (0.5333, 0.7569), (0.6000, 0.7977), (0.6667, 0.8360), (0.7333, 0.8721), * (0.8000, 0.9063), (0.8667, 0.9389), (0.9333, 0.9701), (1.0000, 1.0000) ] * </code></pre> - * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> + * <p><img alt="sRGB tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * <p><b>Full capability</b> - * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the @@ -2756,9 +2745,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * PRESET_CURVE</p> * <p>The tonemap curve will be defined by specified standard.</p> * <p>sRGB (approximated by 16 control points):</p> - * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> + * <p><img alt="sRGB tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> * <p>Rec. 709 (approximated by 16 control points):</p> - * <p><img alt="Rec. 709 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/rec709_tonemap.png" /></p> + * <p><img alt="Rec. 709 tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/rec709_tonemap.png" /></p> * <p>Note that above figures show a 16 control points approximation of preset * curves. Camera devices may apply a different approximation to the curve.</p> * <p><b>Possible values:</b> diff --git a/android/hardware/camera2/CaptureResult.java b/android/hardware/camera2/CaptureResult.java index 6d80c20a..cfad098c 100644 --- a/android/hardware/camera2/CaptureResult.java +++ b/android/hardware/camera2/CaptureResult.java @@ -390,7 +390,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * FAST or HIGH_QUALITY will yield a picture with the same white point * as what was produced by the camera device in the earlier frame.</p> * <p>The expected processing pipeline is as follows:</p> - * <p><img alt="White balance processing pipeline" src="../../../../images/camera2/metadata/android.colorCorrection.mode/processing_pipeline.png" /></p> + * <p><img alt="White balance processing pipeline" src="/reference/images/camera2/metadata/android.colorCorrection.mode/processing_pipeline.png" /></p> * <p>The white balance is encoded by two values, a 4-channel white-balance * gain vector (applied in the Bayer domain), and a 3x3 color transform * matrix (applied after demosaic).</p> @@ -1975,10 +1975,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>When set to AUTO, the individual algorithm controls in * android.control.* are in effect, such as {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}.</p> * <p>When set to USE_SCENE_MODE, the individual controls in - * android.control.* are mostly disabled, and the camera device implements - * one of the scene mode settings (such as ACTION, SUNSET, or PARTY) - * as it wishes. The camera device scene mode 3A settings are provided by - * {@link android.hardware.camera2.CaptureResult capture results}.</p> + * android.control.* are mostly disabled, and the camera device + * implements one of the scene mode settings (such as ACTION, + * SUNSET, or PARTY) as it wishes. The camera device scene mode + * 3A settings are provided by {@link android.hardware.camera2.CaptureResult capture results}.</p> * <p>When set to OFF_KEEP_STATE, it is similar to OFF mode, the only difference * is that this frame will not be used by camera device background 3A statistics * update, as if this frame is never captured. This mode can be used in the scenario @@ -3108,45 +3108,35 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * can run concurrently to the rest of the camera pipeline, but * cannot process more than 1 capture at a time.</li> * </ul> - * <p>The necessary information for the application, given the model above, - * is provided via the {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} field using + * <p>The necessary information for the application, given the model above, is provided via * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration }. - * These are used to determine the maximum frame rate / minimum frame - * duration that is possible for a given stream configuration.</p> + * These are used to determine the maximum frame rate / minimum frame duration that is + * possible for a given stream configuration.</p> * <p>Specifically, the application can use the following rules to * determine the minimum frame duration it can request from the camera * device:</p> * <ol> - * <li>Let the set of currently configured input/output streams - * be called <code>S</code>.</li> - * <li>Find the minimum frame durations for each stream in <code>S</code>, by looking - * it up in {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} using {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration } - * (with its respective size/format). Let this set of frame durations be - * called <code>F</code>.</li> - * <li>For any given request <code>R</code>, the minimum frame duration allowed - * for <code>R</code> is the maximum out of all values in <code>F</code>. Let the streams - * used in <code>R</code> be called <code>S_r</code>.</li> + * <li>Let the set of currently configured input/output streams be called <code>S</code>.</li> + * <li>Find the minimum frame durations for each stream in <code>S</code>, by looking it up in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration } + * (with its respective size/format). Let this set of frame durations be called <code>F</code>.</li> + * <li>For any given request <code>R</code>, the minimum frame duration allowed for <code>R</code> is the maximum + * out of all values in <code>F</code>. Let the streams used in <code>R</code> be called <code>S_r</code>.</li> * </ol> * <p>If none of the streams in <code>S_r</code> have a stall time (listed in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration } - * using its respective size/format), then the frame duration in <code>F</code> - * determines the steady state frame rate that the application will get - * if it uses <code>R</code> as a repeating request. Let this special kind of - * request be called <code>Rsimple</code>.</p> - * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved - * by a single capture of a new request <code>Rstall</code> (which has at least - * one in-use stream with a non-0 stall time) and if <code>Rstall</code> has the - * same minimum frame duration this will not cause a frame rate loss - * if all buffers from the previous <code>Rstall</code> have already been - * delivered.</p> - * <p>For more details about stalling, see - * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p> + * using its respective size/format), then the frame duration in <code>F</code> determines the steady + * state frame rate that the application will get if it uses <code>R</code> as a repeating request. Let + * this special kind of request be called <code>Rsimple</code>.</p> + * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved by a single capture of a + * new request <code>Rstall</code> (which has at least one in-use stream with a non-0 stall time) and if + * <code>Rstall</code> has the same minimum frame duration this will not cause a frame rate loss if all + * buffers from the previous <code>Rstall</code> have already been delivered.</p> + * <p>For more details about stalling, see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p> * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to * OFF; otherwise the auto-exposure algorithm will override this value.</p> * <p><b>Units</b>: Nanoseconds</p> * <p><b>Range of valid values:</b><br> - * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, - * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}. The duration - * is capped to <code>max(duration, exposureTime + overhead)</code>.</p> + * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, {@link android.hardware.camera2.params.StreamConfigurationMap }. + * The duration is capped to <code>max(duration, exposureTime + overhead)</code>.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * <p><b>Full capability</b> - * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the @@ -3155,7 +3145,6 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @see CaptureRequest#CONTROL_AE_MODE * @see CaptureRequest#CONTROL_MODE * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL - * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP * @see CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION */ @PublicKey @@ -3408,9 +3397,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * layout key (see {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT android.sensor.info.colorFilterArrangement}), i.e. the * nth value given corresponds to the black level offset for the nth * color channel listed in the CFA.</p> - * <p>This key will be available if {@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions} is - * available or the camera device advertises this key via - * {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureResultKeys }.</p> + * <p>This key will be available if {@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions} is available or the + * camera device advertises this key via {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureResultKeys }.</p> * <p><b>Range of valid values:</b><br> * >= 0 for each.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> @@ -3640,13 +3628,13 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * </code></pre> * <p>The low-resolution scaling map images for each channel are * (displayed using nearest-neighbor interpolation):</p> - * <p><img alt="Red lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/red_shading.png" /> - * <img alt="Green (even rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_e_shading.png" /> - * <img alt="Green (odd rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_o_shading.png" /> - * <img alt="Blue lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/blue_shading.png" /></p> + * <p><img alt="Red lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/red_shading.png" /> + * <img alt="Green (even rows) lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/green_e_shading.png" /> + * <img alt="Green (odd rows) lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/green_o_shading.png" /> + * <img alt="Blue lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/blue_shading.png" /></p> * <p>As a visualization only, inverting the full-color map to recover an * image of a gray wall (using bicubic interpolation for visual quality) as captured by the sensor gives:</p> - * <p><img alt="Image of a uniform white wall (inverse shading map)" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p> + * <p><img alt="Image of a uniform white wall (inverse shading map)" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p> * <p><b>Range of valid values:</b><br> * Each gain factor is >= 1</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> @@ -3707,14 +3695,14 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * </code></pre> * <p>The low-resolution scaling map images for each channel are * (displayed using nearest-neighbor interpolation):</p> - * <p><img alt="Red lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/red_shading.png" /> - * <img alt="Green (even rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_e_shading.png" /> - * <img alt="Green (odd rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_o_shading.png" /> - * <img alt="Blue lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/blue_shading.png" /></p> + * <p><img alt="Red lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/red_shading.png" /> + * <img alt="Green (even rows) lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/green_e_shading.png" /> + * <img alt="Green (odd rows) lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/green_o_shading.png" /> + * <img alt="Blue lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/blue_shading.png" /></p> * <p>As a visualization only, inverting the full-color map to recover an * image of a gray wall (using bicubic interpolation for visual quality) * as captured by the sensor gives:</p> - * <p><img alt="Image of a uniform white wall (inverse shading map)" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p> + * <p><img alt="Image of a uniform white wall (inverse shading map)" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p> * <p>Note that the RAW image data might be subject to lens shading * correction not reported on this map. Query * {@link CameraCharacteristics#SENSOR_INFO_LENS_SHADING_APPLIED android.sensor.info.lensShadingApplied} to see if RAW image data has subject @@ -3947,11 +3935,11 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>Linear mapping:</p> * <pre><code>android.tonemap.curveRed = [ 0, 0, 1.0, 1.0 ] * </code></pre> - * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> + * <p><img alt="Linear mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> * <p>Invert mapping:</p> * <pre><code>android.tonemap.curveRed = [ 0, 1.0, 1.0, 0 ] * </code></pre> - * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> + * <p><img alt="Inverting mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> * <p>Gamma 1/2.2 mapping, with 16 control points:</p> * <pre><code>android.tonemap.curveRed = [ * 0.0000, 0.0000, 0.0667, 0.2920, 0.1333, 0.4002, 0.2000, 0.4812, @@ -3959,7 +3947,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * 0.5333, 0.7515, 0.6000, 0.7928, 0.6667, 0.8317, 0.7333, 0.8685, * 0.8000, 0.9035, 0.8667, 0.9370, 0.9333, 0.9691, 1.0000, 1.0000 ] * </code></pre> - * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> + * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p> * <pre><code>android.tonemap.curveRed = [ * 0.0000, 0.0000, 0.0667, 0.2864, 0.1333, 0.4007, 0.2000, 0.4845, @@ -3967,7 +3955,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * 0.5333, 0.7569, 0.6000, 0.7977, 0.6667, 0.8360, 0.7333, 0.8721, * 0.8000, 0.9063, 0.8667, 0.9389, 0.9333, 0.9701, 1.0000, 1.0000 ] * </code></pre> - * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> + * <p><img alt="sRGB tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> * <p><b>Range of valid values:</b><br> * 0-1 on both input and output coordinates, normalized * as a floating-point value such that 0 == black and 1 == white.</p> @@ -4009,11 +3997,11 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>Linear mapping:</p> * <pre><code>curveRed = [ (0, 0), (1.0, 1.0) ] * </code></pre> - * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> + * <p><img alt="Linear mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> * <p>Invert mapping:</p> * <pre><code>curveRed = [ (0, 1.0), (1.0, 0) ] * </code></pre> - * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> + * <p><img alt="Inverting mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> * <p>Gamma 1/2.2 mapping, with 16 control points:</p> * <pre><code>curveRed = [ * (0.0000, 0.0000), (0.0667, 0.2920), (0.1333, 0.4002), (0.2000, 0.4812), @@ -4021,7 +4009,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * (0.5333, 0.7515), (0.6000, 0.7928), (0.6667, 0.8317), (0.7333, 0.8685), * (0.8000, 0.9035), (0.8667, 0.9370), (0.9333, 0.9691), (1.0000, 1.0000) ] * </code></pre> - * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> + * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p> * <pre><code>curveRed = [ * (0.0000, 0.0000), (0.0667, 0.2864), (0.1333, 0.4007), (0.2000, 0.4845), @@ -4029,7 +4017,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * (0.5333, 0.7569), (0.6000, 0.7977), (0.6667, 0.8360), (0.7333, 0.8721), * (0.8000, 0.9063), (0.8667, 0.9389), (0.9333, 0.9701), (1.0000, 1.0000) ] * </code></pre> - * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> + * <p><img alt="sRGB tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * <p><b>Full capability</b> - * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the @@ -4119,9 +4107,9 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * PRESET_CURVE</p> * <p>The tonemap curve will be defined by specified standard.</p> * <p>sRGB (approximated by 16 control points):</p> - * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> + * <p><img alt="sRGB tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> * <p>Rec. 709 (approximated by 16 control points):</p> - * <p><img alt="Rec. 709 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/rec709_tonemap.png" /></p> + * <p><img alt="Rec. 709 tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/rec709_tonemap.png" /></p> * <p>Note that above figures show a 16 control points approximation of preset * curves. Camera devices may apply a different approximation to the curve.</p> * <p><b>Possible values:</b> diff --git a/android/hardware/display/DisplayManager.java b/android/hardware/display/DisplayManager.java index ef77d6e6..89357456 100644 --- a/android/hardware/display/DisplayManager.java +++ b/android/hardware/display/DisplayManager.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.app.KeyguardManager; import android.content.Context; import android.graphics.Point; import android.media.projection.MediaProjection; @@ -27,7 +28,6 @@ import android.os.Handler; import android.util.SparseArray; import android.view.Display; import android.view.Surface; -import android.view.WindowManagerPolicy; import java.util.ArrayList; import java.util.List; @@ -254,8 +254,8 @@ public final class DisplayManager { * </p> * * @see #createVirtualDisplay - * @see WindowManagerPolicy#isKeyguardSecure(int) - * @see WindowManagerPolicy#isKeyguardTrustedLw() + * @see KeyguardManager#isDeviceSecure() + * @see KeyguardManager#isDeviceLocked() * @hide */ // TODO: Update name and documentation and un-hide the flag. Don't change the value before that. diff --git a/android/hardware/display/DisplayManagerInternal.java b/android/hardware/display/DisplayManagerInternal.java index e845359a..cd551bd4 100644 --- a/android/hardware/display/DisplayManagerInternal.java +++ b/android/hardware/display/DisplayManagerInternal.java @@ -174,6 +174,11 @@ public abstract class DisplayManagerInternal { public abstract boolean isUidPresentOnDisplay(int uid, int displayId); /** + * Persist brightness slider events. + */ + public abstract void persistBrightnessSliderEvents(); + + /** * Describes the requested power state of the display. * * This object is intended to describe the general characteristics of the diff --git a/android/hardware/location/ContextHubClient.java b/android/hardware/location/ContextHubClient.java index b7e353a4..52527ed6 100644 --- a/android/hardware/location/ContextHubClient.java +++ b/android/hardware/location/ContextHubClient.java @@ -16,43 +16,48 @@ package android.hardware.location; import android.annotation.RequiresPermission; -import android.os.Handler; +import android.os.RemoteException; + +import dalvik.system.CloseGuard; import java.io.Closeable; +import java.util.concurrent.atomic.AtomicBoolean; /** * A class describing a client of the Context Hub Service. * - * Clients can send messages to nanoapps at a Context Hub through this object. + * Clients can send messages to nanoapps at a Context Hub through this object. The APIs supported + * by this object are thread-safe and can be used without external synchronization. * * @hide */ public class ContextHubClient implements Closeable { /* - * The ContextHubClient interface associated with this client. + * The proxy to the client interface at the service. */ - // TODO: Implement this interface and associate with ContextHubClient object - // private final IContextHubClient mClientInterface; + private final IContextHubClient mClientProxy; /* - * The listening callback associated with this client. + * The callback interface associated with this client. */ - private ContextHubClientCallback mCallback; + private final IContextHubClientCallback mCallbackInterface; /* * The Context Hub that this client is attached to. */ - private ContextHubInfo mAttachedHub; + private final ContextHubInfo mAttachedHub; - /* - * The handler to invoke mCallback. - */ - private Handler mCallbackHandler; + private final CloseGuard mCloseGuard = CloseGuard.get(); - ContextHubClient(ContextHubClientCallback callback, Handler handler, ContextHubInfo hubInfo) { - mCallback = callback; - mCallbackHandler = handler; + private final AtomicBoolean mIsClosed = new AtomicBoolean(false); + + /* package */ ContextHubClient( + IContextHubClient clientProxy, IContextHubClientCallback callback, + ContextHubInfo hubInfo) { + mClientProxy = clientProxy; + mCallbackInterface = callback; mAttachedHub = hubInfo; + mCloseGuard.open("close"); } /** @@ -71,7 +76,14 @@ public class ContextHubClient implements Closeable { * All futures messages targeted for this client are dropped at the service. */ public void close() { - throw new UnsupportedOperationException("TODO: Implement this"); + if (!mIsClosed.getAndSet(true)) { + mCloseGuard.close(); + try { + mClientProxy.close(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } /** @@ -90,6 +102,22 @@ public class ContextHubClient implements Closeable { @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) @ContextHubTransaction.Result public int sendMessageToNanoApp(NanoAppMessage message) { - throw new UnsupportedOperationException("TODO: Implement this"); + try { + return mClientProxy.sendMessageToNanoApp(message); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + protected void finalize() throws Throwable { + try { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + close(); + } finally { + super.finalize(); + } } } diff --git a/android/hardware/location/ContextHubInfo.java b/android/hardware/location/ContextHubInfo.java index aaf6c57b..e1137aa5 100644 --- a/android/hardware/location/ContextHubInfo.java +++ b/android/hardware/location/ContextHubInfo.java @@ -16,6 +16,7 @@ package android.hardware.location; import android.annotation.SystemApi; +import android.hardware.contexthub.V1_0.ContextHub; import android.os.Parcel; import android.os.Parcelable; @@ -31,22 +32,52 @@ public class ContextHubInfo { private String mVendor; private String mToolchain; private int mPlatformVersion; - private int mStaticSwVersion; private int mToolchainVersion; private float mPeakMips; private float mStoppedPowerDrawMw; private float mSleepPowerDrawMw; private float mPeakPowerDrawMw; private int mMaxPacketLengthBytes; + private byte mChreApiMajorVersion; + private byte mChreApiMinorVersion; + private short mChrePatchVersion; + private long mChrePlatformId; private int[] mSupportedSensors; private MemoryRegion[] mMemoryRegions; + /* + * TODO(b/67734082): Deprecate this constructor and mark private fields as final. + */ public ContextHubInfo() { } /** + * @hide + */ + public ContextHubInfo(ContextHub contextHub) { + mId = contextHub.hubId; + mName = contextHub.name; + mVendor = contextHub.vendor; + mToolchain = contextHub.toolchain; + mPlatformVersion = contextHub.platformVersion; + mToolchainVersion = contextHub.toolchainVersion; + mPeakMips = contextHub.peakMips; + mStoppedPowerDrawMw = contextHub.stoppedPowerDrawMw; + mSleepPowerDrawMw = contextHub.sleepPowerDrawMw; + mPeakPowerDrawMw = contextHub.peakPowerDrawMw; + mMaxPacketLengthBytes = contextHub.maxSupportedMsgLen; + mChrePlatformId = contextHub.chrePlatformId; + mChreApiMajorVersion = contextHub.chreApiMajorVersion; + mChreApiMinorVersion = contextHub.chreApiMinorVersion; + mChrePatchVersion = contextHub.chrePatchVersion; + + mSupportedSensors = new int[0]; + mMemoryRegions = new MemoryRegion[0]; + } + + /** * returns the maximum number of bytes that can be sent per message to the hub * * @return int - maximum bytes that can be transmitted in a @@ -57,17 +88,6 @@ public class ContextHubInfo { } /** - * set the context hub unique identifer - * - * @param bytes - Maximum number of bytes per message - * - * @hide - */ - public void setMaxPacketLenBytes(int bytes) { - mMaxPacketLengthBytes = bytes; - } - - /** * get the context hub unique identifer * * @return int - unique system wide identifier @@ -77,17 +97,6 @@ public class ContextHubInfo { } /** - * set the context hub unique identifer - * - * @param id - unique system wide identifier for the hub - * - * @hide - */ - public void setId(int id) { - mId = id; - } - - /** * get a string as a hub name * * @return String - a name for the hub @@ -97,17 +106,6 @@ public class ContextHubInfo { } /** - * set a string as the hub name - * - * @param name - the name for the hub - * - * @hide - */ - public void setName(String name) { - mName = name; - } - - /** * get a string as the vendor name * * @return String - a name for the vendor @@ -117,17 +115,6 @@ public class ContextHubInfo { } /** - * set a string as the vendor name - * - * @param vendor - a name for the vendor - * - * @hide - */ - public void setVendor(String vendor) { - mVendor = vendor; - } - - /** * get tool chain string * * @return String - description of the tool chain @@ -137,17 +124,6 @@ public class ContextHubInfo { } /** - * set tool chain string - * - * @param toolchain - description of the tool chain - * - * @hide - */ - public void setToolchain(String toolchain) { - mToolchain = toolchain; - } - - /** * get platform version * * @return int - platform version number @@ -157,34 +133,12 @@ public class ContextHubInfo { } /** - * set platform version - * - * @param platformVersion - platform version number - * - * @hide - */ - public void setPlatformVersion(int platformVersion) { - mPlatformVersion = platformVersion; - } - - /** * get static platform version number * * @return int - platform version number */ public int getStaticSwVersion() { - return mStaticSwVersion; - } - - /** - * set platform software version - * - * @param staticSwVersion - platform static s/w version number - * - * @hide - */ - public void setStaticSwVersion(int staticSwVersion) { - mStaticSwVersion = staticSwVersion; + return (mChreApiMajorVersion << 24) | (mChreApiMinorVersion << 16) | (mChrePatchVersion); } /** @@ -197,17 +151,6 @@ public class ContextHubInfo { } /** - * set the tool chain version number - * - * @param toolchainVersion - tool chain version number - * - * @hide - */ - public void setToolchainVersion(int toolchainVersion) { - mToolchainVersion = toolchainVersion; - } - - /** * get the peak processing mips the hub can support * * @return float - peak MIPS that this hub can deliver @@ -217,17 +160,6 @@ public class ContextHubInfo { } /** - * set the peak mips that this hub can support - * - * @param peakMips - peak mips this hub can deliver - * - * @hide - */ - public void setPeakMips(float peakMips) { - mPeakMips = peakMips; - } - - /** * get the stopped power draw in milliwatts * This assumes that the hub enter a stopped state - which is * different from the sleep state. Latencies on exiting the @@ -241,17 +173,6 @@ public class ContextHubInfo { } /** - * Set the power consumed by the hub in stopped state - * - * @param stoppedPowerDrawMw - stopped power in milli watts - * - * @hide - */ - public void setStoppedPowerDrawMw(float stoppedPowerDrawMw) { - mStoppedPowerDrawMw = stoppedPowerDrawMw; - } - - /** * get the power draw of the hub in sleep mode. This assumes * that the hub supports a sleep mode in which the power draw is * lower than the power consumed when the hub is actively @@ -267,17 +188,6 @@ public class ContextHubInfo { } /** - * Set the sleep power draw in milliwatts - * - * @param sleepPowerDrawMw - sleep power draw in milliwatts. - * - * @hide - */ - public void setSleepPowerDrawMw(float sleepPowerDrawMw) { - mSleepPowerDrawMw = sleepPowerDrawMw; - } - - /** * get the peak powe draw of the hub. This is the power consumed * by the hub at maximum load. * @@ -288,18 +198,6 @@ public class ContextHubInfo { } /** - * set the peak power draw of the hub - * - * @param peakPowerDrawMw - peak power draw of the hub in - * milliwatts. - * - * @hide - */ - public void setPeakPowerDrawMw(float peakPowerDrawMw) { - mPeakPowerDrawMw = peakPowerDrawMw; - } - - /** * get the sensors supported by this hub * * @return int[] - all the supported sensors on this hub @@ -322,46 +220,65 @@ public class ContextHubInfo { } /** - * set the supported sensors on this hub - * - * @param supportedSensors - supported sensors on this hub + * @return the CHRE platform ID as defined in chre/version.h * + * TODO(b/67734082): Expose as public API * @hide */ - public void setSupportedSensors(int[] supportedSensors) { - mSupportedSensors = Arrays.copyOf(supportedSensors, supportedSensors.length); + public long getChrePlatformId() { + return mChrePlatformId; } /** - * set memory regions for this hub + * @return the CHRE API's major version as defined in chre/version.h * - * @param memoryRegions - memory regions information + * TODO(b/67734082): Expose as public API + * @hide + */ + public byte getChreApiMajorVersion() { + return mChreApiMajorVersion; + } + + /** + * @return the CHRE API's minor version as defined in chre/version.h * - * @see MemoryRegion + * TODO(b/67734082): Expose as public API + * @hide + */ + public byte getChreApiMinorVersion() { + return mChreApiMinorVersion; + } + + /** + * @return the CHRE patch version as defined in chre/version.h * + * TODO(b/67734082): Expose as public API * @hide */ - public void setMemoryRegions(MemoryRegion[] memoryRegions) { - mMemoryRegions = Arrays.copyOf(memoryRegions, memoryRegions.length); + public short getChrePatchVersion() { + return mChrePatchVersion; } @Override public String toString() { - String retVal = ""; - retVal += "Id : " + mId; - retVal += ", Name : " + mName; - retVal += "\n\tVendor : " + mVendor; - retVal += ", ToolChain : " + mToolchain; - retVal += "\n\tPlatformVersion : " + mPlatformVersion; - retVal += ", StaticSwVersion : " + mStaticSwVersion; - retVal += "\n\tPeakMips : " + mPeakMips; - retVal += ", StoppedPowerDraw : " + mStoppedPowerDrawMw + " mW"; - retVal += ", PeakPowerDraw : " + mPeakPowerDrawMw + " mW"; - retVal += ", MaxPacketLength : " + mMaxPacketLengthBytes + " Bytes"; - retVal += "\n\tSupported sensors : " + Arrays.toString(mSupportedSensors); - retVal += "\n\tMemory Regions : " + Arrays.toString(mMemoryRegions); - - return retVal; + String retVal = ""; + retVal += "Id : " + mId; + retVal += ", Name : " + mName; + retVal += "\n\tVendor : " + mVendor; + retVal += ", Toolchain : " + mToolchain; + retVal += ", Toolchain version: 0x" + Integer.toHexString(mToolchainVersion); + retVal += "\n\tPlatformVersion : 0x" + Integer.toHexString(mPlatformVersion); + retVal += ", SwVersion : " + + mChreApiMajorVersion + "." + mChreApiMinorVersion + "." + mChrePatchVersion; + retVal += ", CHRE platform ID: 0x" + Long.toHexString(mChrePlatformId); + retVal += "\n\tPeakMips : " + mPeakMips; + retVal += ", StoppedPowerDraw : " + mStoppedPowerDrawMw + " mW"; + retVal += ", PeakPowerDraw : " + mPeakPowerDrawMw + " mW"; + retVal += ", MaxPacketLength : " + mMaxPacketLengthBytes + " Bytes"; + retVal += "\n\tSupported sensors : " + Arrays.toString(mSupportedSensors); + retVal += "\n\tMemory Regions : " + Arrays.toString(mMemoryRegions); + + return retVal; } private ContextHubInfo(Parcel in) { @@ -371,12 +288,15 @@ public class ContextHubInfo { mToolchain = in.readString(); mPlatformVersion = in.readInt(); mToolchainVersion = in.readInt(); - mStaticSwVersion = in.readInt(); mPeakMips = in.readFloat(); mStoppedPowerDrawMw = in.readFloat(); mSleepPowerDrawMw = in.readFloat(); mPeakPowerDrawMw = in.readFloat(); mMaxPacketLengthBytes = in.readInt(); + mChrePlatformId = in.readLong(); + mChreApiMajorVersion = in.readByte(); + mChreApiMinorVersion = in.readByte(); + mChrePatchVersion = (short) in.readInt(); int numSupportedSensors = in.readInt(); mSupportedSensors = new int[numSupportedSensors]; @@ -395,12 +315,15 @@ public class ContextHubInfo { out.writeString(mToolchain); out.writeInt(mPlatformVersion); out.writeInt(mToolchainVersion); - out.writeInt(mStaticSwVersion); out.writeFloat(mPeakMips); out.writeFloat(mStoppedPowerDrawMw); out.writeFloat(mSleepPowerDrawMw); out.writeFloat(mPeakPowerDrawMw); out.writeInt(mMaxPacketLengthBytes); + out.writeLong(mChrePlatformId); + out.writeByte(mChreApiMajorVersion); + out.writeByte(mChreApiMinorVersion); + out.writeInt(mChrePatchVersion); out.writeInt(mSupportedSensors.length); out.writeIntArray(mSupportedSensors); diff --git a/android/hardware/location/ContextHubManager.java b/android/hardware/location/ContextHubManager.java index 24117278..b31c7bcd 100644 --- a/android/hardware/location/ContextHubManager.java +++ b/android/hardware/location/ContextHubManager.java @@ -456,6 +456,54 @@ public final class ContextHubManager { } /** + * Creates an interface to the ContextHubClient to send down to the service. + * + * @param callback the callback to invoke at the client process + * @param handler the handler to post callbacks for this client + * + * @return the callback interface + */ + private IContextHubClientCallback createClientCallback( + ContextHubClientCallback callback, Handler handler) { + return new IContextHubClientCallback.Stub() { + @Override + public void onMessageFromNanoApp(NanoAppMessage message) { + handler.post(() -> callback.onMessageFromNanoApp(message)); + } + + @Override + public void onHubReset() { + handler.post(() -> callback.onHubReset()); + } + + @Override + public void onNanoAppAborted(long nanoAppId, int abortCode) { + handler.post(() -> callback.onNanoAppAborted(nanoAppId, abortCode)); + } + + @Override + public void onNanoAppLoaded(long nanoAppId) { + handler.post(() -> callback.onNanoAppLoaded(nanoAppId)); + } + + @Override + public void onNanoAppUnloaded(long nanoAppId) { + handler.post(() -> callback.onNanoAppUnloaded(nanoAppId)); + } + + @Override + public void onNanoAppEnabled(long nanoAppId) { + handler.post(() -> callback.onNanoAppEnabled(nanoAppId)); + } + + @Override + public void onNanoAppDisabled(long nanoAppId) { + handler.post(() -> callback.onNanoAppDisabled(nanoAppId)); + } + }; + } + + /** * Creates and registers a client and its callback with the Context Hub Service. * * A client is registered with the Context Hub Service for a specified Context Hub. When the @@ -463,19 +511,37 @@ public final class ContextHubManager { * {@link ContextHubClient} object, and receive notifications through the provided callback. * * @param callback the notification callback to register - * @param hubInfo the hub to attach this client to - * @param handler the handler to invoke the callback, if null uses the main thread's Looper - * + * @param hubInfo the hub to attach this client to + * @param handler the handler to invoke the callback, if null uses the main thread's Looper * @return the registered client object * - * @see ContextHubClientCallback + * @throws IllegalArgumentException if hubInfo does not represent a valid hub + * @throws IllegalStateException if there were too many registered clients at the service + * @throws NullPointerException if callback or hubInfo is null * * @hide + * @see ContextHubClientCallback */ public ContextHubClient createClient( ContextHubClientCallback callback, ContextHubInfo hubInfo, @Nullable Handler handler) { - throw new UnsupportedOperationException( - "TODO: Implement this, and throw an exception on error"); + if (callback == null) { + throw new NullPointerException("Callback cannot be null"); + } + if (hubInfo == null) { + throw new NullPointerException("Hub info cannot be null"); + } + + Handler realHandler = (handler == null) ? new Handler(mMainLooper) : handler; + IContextHubClientCallback clientInterface = createClientCallback(callback, realHandler); + + IContextHubClient client; + try { + client = mService.createClient(clientInterface, hubInfo.getId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + return new ContextHubClient(client, clientInterface, hubInfo); } /** diff --git a/android/hardware/radio/RadioTuner.java b/android/hardware/radio/RadioTuner.java index 6e8991aa..e93fd5f1 100644 --- a/android/hardware/radio/RadioTuner.java +++ b/android/hardware/radio/RadioTuner.java @@ -309,6 +309,58 @@ public abstract class RadioTuner { public abstract void setAnalogForced(boolean isForced); /** + * Generic method for setting vendor-specific parameter values. + * The framework does not interpret the parameters, they are passed + * in an opaque manner between a vendor application and HAL. + * + * Framework does not make any assumptions on the keys or values, other than + * ones stated in VendorKeyValue documentation (a requirement of key + * prefixes). + * + * For each pair in the result map, the key will be one of the keys + * contained in the input (possibly with wildcards expanded), and the value + * will be a vendor-specific result status (such as "OK" or an error code). + * The implementation may choose to return an empty map, or only return + * a status for a subset of the provided inputs, at its discretion. + * + * Application and HAL must not use keys with unknown prefix. In particular, + * it must not place a key-value pair in results vector for unknown key from + * parameters vector - instead, an unknown key should simply be ignored. + * In other words, results vector may contain a subset of parameter keys + * (however, the framework doesn't enforce a strict subset - the only + * formal requirement is vendor domain prefix for keys). + * + * @param parameters Vendor-specific key-value pairs. + * @return Operation completion status for parameters being set. + * @hide FutureFeature + */ + public abstract @NonNull Map<String, String> + setParameters(@NonNull Map<String, String> parameters); + + /** + * Generic method for retrieving vendor-specific parameter values. + * The framework does not interpret the parameters, they are passed + * in an opaque manner between a vendor application and HAL. + * + * Framework does not cache set/get requests, so it's possible for + * getParameter to return a different value than previous setParameter call. + * + * The syntax and semantics of keys are up to the vendor (as long as prefix + * rules are obeyed). For instance, vendors may include some form of + * wildcard support. In such case, result vector may be of different size + * than requested keys vector. However, wildcards are not recognized by + * framework and they are passed as-is to the HAL implementation. + * + * Unknown keys must be ignored and not placed into results vector. + * + * @param keys Parameter keys to fetch. + * @return Vendor-specific key-value pairs. + * @hide FutureFeature + */ + public abstract @NonNull Map<String, String> + getParameters(@NonNull List<String> keys); + + /** * Get current antenna connection state for current configuration. * Only valid if a configuration has been applied. * @return {@code true} if the antenna is connected, {@code false} otherwise. @@ -429,6 +481,22 @@ public abstract class RadioTuner { * Use {@link RadioTuner#getProgramList(String)} to get an actual list. */ public void onProgramListChanged() {} + + /** + * Generic callback for passing updates to vendor-specific parameter values. + * The framework does not interpret the parameters, they are passed + * in an opaque manner between a vendor application and HAL. + * + * It's up to the HAL implementation if and how to implement this callback, + * as long as it obeys the prefix rule. In particular, only selected keys + * may be notified this way. However, setParameters must not trigger + * this callback, while an internal event can change parameters + * asynchronously. + * + * @param parameters Vendor-specific key-value pairs. + * @hide FutureFeature + */ + public void onParametersUpdated(@NonNull Map<String, String> parameters) {} } } diff --git a/android/hardware/radio/TunerAdapter.java b/android/hardware/radio/TunerAdapter.java index b6219690..864d17c2 100644 --- a/android/hardware/radio/TunerAdapter.java +++ b/android/hardware/radio/TunerAdapter.java @@ -24,6 +24,7 @@ import android.util.Log; import java.util.List; import java.util.Map; +import java.util.Objects; /** * Implements the RadioTuner interface by forwarding calls to radio service. @@ -251,6 +252,24 @@ class TunerAdapter extends RadioTuner { } @Override + public @NonNull Map<String, String> setParameters(@NonNull Map<String, String> parameters) { + try { + return mTuner.setParameters(Objects.requireNonNull(parameters)); + } catch (RemoteException e) { + throw new RuntimeException("service died", e); + } + } + + @Override + public @NonNull Map<String, String> getParameters(@NonNull List<String> keys) { + try { + return mTuner.getParameters(Objects.requireNonNull(keys)); + } catch (RemoteException e) { + throw new RuntimeException("service died", e); + } + } + + @Override public boolean isAntennaConnected() { try { return mTuner.isAntennaConnected(); diff --git a/android/hardware/radio/TunerCallbackAdapter.java b/android/hardware/radio/TunerCallbackAdapter.java index ffd5b30f..a01f658e 100644 --- a/android/hardware/radio/TunerCallbackAdapter.java +++ b/android/hardware/radio/TunerCallbackAdapter.java @@ -22,6 +22,8 @@ import android.os.Handler; import android.os.Looper; import android.util.Log; +import java.util.Map; + /** * Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback. */ @@ -94,4 +96,9 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { public void onProgramListChanged() { mHandler.post(() -> mCallback.onProgramListChanged()); } + + @Override + public void onParametersUpdated(Map parameters) { + mHandler.post(() -> mCallback.onParametersUpdated(parameters)); + } } diff --git a/android/hardware/usb/UsbManager.java b/android/hardware/usb/UsbManager.java index 6ce96698..bdb90bcc 100644 --- a/android/hardware/usb/UsbManager.java +++ b/android/hardware/usb/UsbManager.java @@ -344,7 +344,7 @@ public class UsbManager { public UsbDeviceConnection openDevice(UsbDevice device) { try { String deviceName = device.getDeviceName(); - ParcelFileDescriptor pfd = mService.openDevice(deviceName); + ParcelFileDescriptor pfd = mService.openDevice(deviceName, mContext.getPackageName()); if (pfd != null) { UsbDeviceConnection connection = new UsbDeviceConnection(device); boolean result = connection.open(deviceName, pfd, mContext); @@ -400,6 +400,9 @@ public class UsbManager { * Permission might have been granted temporarily via * {@link #requestPermission(UsbDevice, PendingIntent)} or * by the user choosing the caller as the default application for the device. + * Permission for USB devices of class {@link UsbConstants#USB_CLASS_VIDEO} for clients that + * target SDK {@link android.os.Build.VERSION_CODES#P} and above can be granted only if they + * have additionally the {@link android.Manifest.permission#CAMERA} permission. * * @param device to check permissions for * @return true if caller has permission @@ -409,7 +412,7 @@ public class UsbManager { return false; } try { - return mService.hasDevicePermission(device); + return mService.hasDevicePermission(device, mContext.getPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -450,6 +453,10 @@ public class UsbManager { * permission was granted by the user * </ul> * + * Permission for USB devices of class {@link UsbConstants#USB_CLASS_VIDEO} for clients that + * target SDK {@link android.os.Build.VERSION_CODES#P} and above can be granted only if they + * have additionally the {@link android.Manifest.permission#CAMERA} permission. + * * @param device to request permissions for * @param pi PendingIntent for returning result */ diff --git a/android/inputmethodservice/KeyboardView.java b/android/inputmethodservice/KeyboardView.java index 13b9206b..7836cd09 100644 --- a/android/inputmethodservice/KeyboardView.java +++ b/android/inputmethodservice/KeyboardView.java @@ -21,18 +21,16 @@ import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.Paint.Align; import android.graphics.PorterDuff; import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.Paint.Align; import android.graphics.Region.Op; +import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.inputmethodservice.Keyboard.Key; import android.media.AudioManager; import android.os.Handler; import android.os.Message; -import android.os.UserHandle; -import android.provider.Settings; import android.util.AttributeSet; import android.util.TypedValue; import android.view.GestureDetector; @@ -986,6 +984,9 @@ public class KeyboardView extends View implements View.OnClickListener { private void sendAccessibilityEventForUnicodeCharacter(int eventType, int code) { if (mAccessibilityManager.isEnabled()) { + if (!mAccessibilityManager.isObservedEventType(eventType)) { + return; + } AccessibilityEvent event = AccessibilityEvent.obtain(eventType); onInitializeAccessibilityEvent(event); final String text; diff --git a/android/media/ImageReader.java b/android/media/ImageReader.java index c78c99f7..10195805 100644 --- a/android/media/ImageReader.java +++ b/android/media/ImageReader.java @@ -640,7 +640,6 @@ public class ImageReader implements AutoCloseable { * The ImageReader continues to be usable after this call, but may need to reallocate buffers * when more buffers are needed for rendering. * </p> - * @hide */ public void discardFreeBuffers() { synchronized (mCloseLock) { diff --git a/android/media/MediaDrm.java b/android/media/MediaDrm.java index 1feea890..12e5744d 100644 --- a/android/media/MediaDrm.java +++ b/android/media/MediaDrm.java @@ -91,10 +91,10 @@ import android.util.Log; * are only decrypted when the samples are delivered to the decoder. * <p> * MediaDrm methods throw {@link android.media.MediaDrm.MediaDrmStateException} - * when a method is called on a MediaDrm object that has had an unrecoverable failure - * in the DRM plugin or security hardware. - * {@link android.media.MediaDrm.MediaDrmStateException} extends - * {@link java.lang.IllegalStateException} with the addition of a developer-readable + * when a method is called on a MediaDrm object that has had an unrecoverable failure + * in the DRM plugin or security hardware. + * {@link android.media.MediaDrm.MediaDrmStateException} extends + * {@link java.lang.IllegalStateException} with the addition of a developer-readable * diagnostic information string associated with the exception. * <p> * In the event of a mediaserver process crash or restart while a MediaDrm object @@ -102,9 +102,9 @@ import android.util.Log; * To recover, the app must release the MediaDrm object, then create and initialize * a new one. * <p> - * As {@link android.media.MediaDrmResetException} and - * {@link android.media.MediaDrm.MediaDrmStateException} both extend - * {@link java.lang.IllegalStateException}, they should be in an earlier catch() + * As {@link android.media.MediaDrmResetException} and + * {@link android.media.MediaDrm.MediaDrmStateException} both extend + * {@link java.lang.IllegalStateException}, they should be in an earlier catch() * block than {@link java.lang.IllegalStateException} if handled separately. * <p> * <a name="Callbacks"></a> @@ -165,7 +165,7 @@ public final class MediaDrm { /** * Query if the given scheme identified by its UUID is supported on - * this device, and whether the drm plugin is able to handle the + * this device, and whether the DRM plugin is able to handle the * media container format specified by mimeType. * @param uuid The UUID of the crypto scheme. * @param mimeType The MIME type of the media container, e.g. "video/mp4" @@ -745,7 +745,7 @@ public final class MediaDrm { * returned in KeyRequest.defaultUrl. * <p> * After the app has received the key request response from the server, - * it should deliver to the response to the DRM engine plugin using the method + * it should deliver to the response to the MediaDrm instance using the method * {@link #provideKeyResponse}. * * @param scope may be a sessionId or a keySetId, depending on the specified keyType. @@ -781,7 +781,7 @@ public final class MediaDrm { /** * A key response is received from the license server by the app, then it is - * provided to the DRM engine plugin using provideKeyResponse. When the + * provided to the MediaDrm instance using provideKeyResponse. When the * response is for an offline key request, a keySetId is returned that can be * used to later restore the keys to a new session with the method * {@link #restoreKeys}. @@ -829,7 +829,7 @@ public final class MediaDrm { * in the form of {name, value} pairs. Since DRM license policies vary by vendor, * the specific status field names are determined by each DRM vendor. Refer to your * DRM provider documentation for definitions of the field names for a particular - * DRM engine plugin. + * DRM plugin. * * @param sessionId the session ID for the DRM session */ @@ -897,11 +897,11 @@ public final class MediaDrm { @NonNull String certAuthority); /** - * After a provision response is received by the app, it is provided to the DRM - * engine plugin using this method. + * After a provision response is received by the app, it is provided to the + * MediaDrm instance using this method. * * @param response the opaque provisioning response byte array to provide to the - * DRM engine plugin. + * MediaDrm instance. * * @throws DeniedByServerException if the response indicates that the * server rejected the request @@ -912,7 +912,6 @@ public final class MediaDrm { } @NonNull - /* could there be a valid response with 0-sized certificate or key? */ private native Certificate provideProvisionResponseNative(@NonNull byte[] response) throws DeniedByServerException; @@ -953,26 +952,26 @@ public final class MediaDrm { /** * Remove all secure stops without requiring interaction with the server. */ - public native void releaseAllSecureStops(); + public native void releaseAllSecureStops(); /** - * String property name: identifies the maker of the DRM engine plugin + * String property name: identifies the maker of the DRM plugin */ public static final String PROPERTY_VENDOR = "vendor"; /** - * String property name: identifies the version of the DRM engine plugin + * String property name: identifies the version of the DRM plugin */ public static final String PROPERTY_VERSION = "version"; /** - * String property name: describes the DRM engine plugin + * String property name: describes the DRM plugin */ public static final String PROPERTY_DESCRIPTION = "description"; /** * String property name: a comma-separated list of cipher and mac algorithms - * supported by CryptoSession. The list may be empty if the DRM engine + * supported by CryptoSession. The list may be empty if the DRM * plugin does not support CryptoSession operations. */ public static final String PROPERTY_ALGORITHMS = "algorithms"; @@ -988,7 +987,7 @@ public final class MediaDrm { public @interface StringProperty {} /** - * Read a DRM engine plugin String property value, given the property name string. + * Read a MediaDrm String property value, given the property name string. * <p> * Standard fields names are: * {@link #PROPERTY_VENDOR}, {@link #PROPERTY_VERSION}, @@ -998,6 +997,13 @@ public final class MediaDrm { public native String getPropertyString(@NonNull @StringProperty String propertyName); /** + * Set a MediaDrm String property value, given the property name string + * and new value for the property. + */ + public native void setPropertyString(@NonNull @StringProperty String propertyName, + @NonNull String value); + + /** * Byte array property name: the device unique identifier is established during * device provisioning and provides a means of uniquely identifying each device. */ @@ -1011,7 +1017,7 @@ public final class MediaDrm { public @interface ArrayProperty {} /** - * Read a DRM engine plugin byte array property value, given the property name string. + * Read a MediaDrm byte array property value, given the property name string. * <p> * Standard fields names are {@link #PROPERTY_DEVICE_UNIQUE_ID} */ @@ -1019,17 +1025,13 @@ public final class MediaDrm { public native byte[] getPropertyByteArray(@ArrayProperty String propertyName); /** - * Set a DRM engine plugin String property value. - */ - public native void setPropertyString( - String propertyName, @NonNull String value); - - /** - * Set a DRM engine plugin byte array property value. - */ - public native void setPropertyByteArray( + * Set a MediaDrm byte array property value, given the property name string + * and new value for the property. + */ + public native void setPropertyByteArray(@NonNull @ArrayProperty String propertyName, @NonNull byte[] value); + private static final native void setCipherAlgorithmNative( @NonNull MediaDrm drm, @NonNull byte[] sessionId, @NonNull String algorithm); @@ -1158,7 +1160,7 @@ public final class MediaDrm { * The algorithm string conforms to JCA Standard Names for Mac * Algorithms and is case insensitive. For example "HmacSHA256". * <p> - * The list of supported algorithms for a DRM engine plugin can be obtained + * The list of supported algorithms for a DRM plugin can be obtained * using the method {@link #getPropertyString} with the property name * "algorithms". */ @@ -1272,7 +1274,7 @@ public final class MediaDrm { * storage, and used when invoking the signRSA method. * * @param response the opaque certificate response byte array to provide to the - * DRM engine plugin. + * MediaDrm instance. * * @throws DeniedByServerException if the response indicates that the * server rejected the request diff --git a/android/media/MediaFormat.java b/android/media/MediaFormat.java index c475e122..306ed83c 100644 --- a/android/media/MediaFormat.java +++ b/android/media/MediaFormat.java @@ -721,14 +721,16 @@ public final class MediaFormat { /** * A key for boolean DEFAULT behavior for the track. The track with DEFAULT=true is * selected in the absence of a specific user choice. - * This is currently only used for subtitle tracks, when the user selected - * 'Default' for the captioning locale. + * This is currently used in two scenarios: + * 1) for subtitle tracks, when the user selected 'Default' for the captioning locale. + * 2) for a {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track, indicating the image is the + * primary item in the file. + * The associated value is an integer, where non-0 means TRUE. This is an optional * field; if not specified, DEFAULT is considered to be FALSE. */ public static final String KEY_IS_DEFAULT = "is-default"; - /** * A key for the FORCED field for subtitle tracks. True if it is a * forced subtitle track. Forced subtitle tracks are essential for the diff --git a/android/media/MediaMetadata.java b/android/media/MediaMetadata.java index bdc0fda6..31eb948d 100644 --- a/android/media/MediaMetadata.java +++ b/android/media/MediaMetadata.java @@ -34,6 +34,7 @@ import android.util.SparseArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Set; +import java.util.Objects; /** * Contains metadata about an item, such as the title, artist, etc. @@ -616,6 +617,71 @@ public final class MediaMetadata implements Parcelable { }; /** + * Compares the contents of this object to another MediaMetadata object. It + * does not compare Bitmaps and Ratings as the media player can choose to + * forgo these fields depending on how you retrieve the MediaMetadata. + * + * @param o The Metadata object to compare this object against + * @return Whether or not the two objects have matching fields (excluding + * Bitmaps and Ratings) + */ + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof MediaMetadata)) { + return false; + } + + final MediaMetadata m = (MediaMetadata) o; + + for (int i = 0; i < METADATA_KEYS_TYPE.size(); i++) { + String key = METADATA_KEYS_TYPE.keyAt(i); + switch (METADATA_KEYS_TYPE.valueAt(i)) { + case METADATA_TYPE_TEXT: + if (!Objects.equals(getString(key), m.getString(key))) { + return false; + } + break; + case METADATA_TYPE_LONG: + if (getLong(key) != m.getLong(key)) { + return false; + } + break; + default: + // Ignore ratings and bitmaps when comparing + break; + } + } + + return true; + } + + @Override + public int hashCode() { + int hashCode = 17; + + for (int i = 0; i < METADATA_KEYS_TYPE.size(); i++) { + String key = METADATA_KEYS_TYPE.keyAt(i); + switch (METADATA_KEYS_TYPE.valueAt(i)) { + case METADATA_TYPE_TEXT: + hashCode = 31 * hashCode + Objects.hash(getString(key)); + break; + case METADATA_TYPE_LONG: + hashCode = 31 * hashCode + Long.hashCode(getLong(key)); + break; + default: + // Ignore ratings and bitmaps when comparing + break; + } + } + + return hashCode; + } + + /** * Use to build MediaMetadata objects. The system defined metadata keys must * use the appropriate data type. */ diff --git a/android/media/MediaMuxer.java b/android/media/MediaMuxer.java index 91e57ee0..02c71b28 100644 --- a/android/media/MediaMuxer.java +++ b/android/media/MediaMuxer.java @@ -258,12 +258,18 @@ final public class MediaMuxer { * in include/media/stagefright/MediaMuxer.h! */ private OutputFormat() {} + /** @hide */ + public static final int MUXER_OUTPUT_FIRST = 0; /** MPEG4 media file format*/ - public static final int MUXER_OUTPUT_MPEG_4 = 0; + public static final int MUXER_OUTPUT_MPEG_4 = MUXER_OUTPUT_FIRST; /** WEBM media file format*/ - public static final int MUXER_OUTPUT_WEBM = 1; + public static final int MUXER_OUTPUT_WEBM = MUXER_OUTPUT_FIRST + 1; /** 3GPP media file format*/ - public static final int MUXER_OUTPUT_3GPP = 2; + public static final int MUXER_OUTPUT_3GPP = MUXER_OUTPUT_FIRST + 2; + /** HEIF media file format*/ + public static final int MUXER_OUTPUT_HEIF = MUXER_OUTPUT_FIRST + 3; + /** @hide */ + public static final int MUXER_OUTPUT_LAST = MUXER_OUTPUT_HEIF; }; /** @hide */ @@ -271,6 +277,7 @@ final public class MediaMuxer { OutputFormat.MUXER_OUTPUT_MPEG_4, OutputFormat.MUXER_OUTPUT_WEBM, OutputFormat.MUXER_OUTPUT_3GPP, + OutputFormat.MUXER_OUTPUT_HEIF, }) @Retention(RetentionPolicy.SOURCE) public @interface Format {} @@ -347,8 +354,7 @@ final public class MediaMuxer { } private void setUpMediaMuxer(@NonNull FileDescriptor fd, @Format int format) throws IOException { - if (format != OutputFormat.MUXER_OUTPUT_MPEG_4 && format != OutputFormat.MUXER_OUTPUT_WEBM - && format != OutputFormat.MUXER_OUTPUT_3GPP) { + if (format < OutputFormat.MUXER_OUTPUT_FIRST || format > OutputFormat.MUXER_OUTPUT_LAST) { throw new IllegalArgumentException("format: " + format + " is invalid"); } mNativeObject = nativeSetup(fd, format); diff --git a/android/media/MediaRecorder.java b/android/media/MediaRecorder.java index 76784904..3c49b80b 100644 --- a/android/media/MediaRecorder.java +++ b/android/media/MediaRecorder.java @@ -25,6 +25,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; +import android.util.ArrayMap; import android.util.Log; import android.view.Surface; @@ -34,6 +35,8 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.lang.ref.WeakReference; +import com.android.internal.annotations.GuardedBy; + /** * Used to record audio and video. The recording control is based on a * simple state machine (see below). @@ -76,7 +79,7 @@ import java.lang.ref.WeakReference; * <a href="{@docRoot}guide/topics/media/audio-capture.html">Audio Capture</a> developer guide.</p> * </div> */ -public class MediaRecorder +public class MediaRecorder implements AudioRouting { static { System.loadLibrary("media_jni"); @@ -1243,6 +1246,7 @@ public class MediaRecorder private static final int MEDIA_RECORDER_TRACK_EVENT_INFO = 101; private static final int MEDIA_RECORDER_TRACK_EVENT_LIST_END = 1000; + private static final int MEDIA_RECORDER_AUDIO_ROUTING_CHANGED = 10000; @Override public void handleMessage(Message msg) { @@ -1265,6 +1269,16 @@ public class MediaRecorder return; + case MEDIA_RECORDER_AUDIO_ROUTING_CHANGED: + AudioManager.resetAudioPortGeneration(); + synchronized (mRoutingChangeListeners) { + for (NativeRoutingEventHandlerDelegate delegate + : mRoutingChangeListeners.values()) { + delegate.notifyClient(); + } + } + return; + default: Log.e(TAG, "Unknown message type " + msg.what); return; @@ -1272,6 +1286,155 @@ public class MediaRecorder } } + //-------------------------------------------------------------------------- + // Explicit Routing + //-------------------- + private AudioDeviceInfo mPreferredDevice = null; + + /** + * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route + * the input from this MediaRecorder. + * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio source. + * If deviceInfo is null, default routing is restored. + * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and + * does not correspond to a valid audio input device. + */ + @Override + public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) { + if (deviceInfo != null && !deviceInfo.isSource()) { + return false; + } + int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0; + boolean status = native_setInputDevice(preferredDeviceId); + if (status == true) { + synchronized (this) { + mPreferredDevice = deviceInfo; + } + } + return status; + } + + /** + * Returns the selected input device specified by {@link #setPreferredDevice}. Note that this + * is not guaranteed to correspond to the actual device being used for recording. + */ + @Override + public AudioDeviceInfo getPreferredDevice() { + synchronized (this) { + return mPreferredDevice; + } + } + + /** + * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaRecorder + * Note: The query is only valid if the MediaRecorder is currently recording. + * If the recorder is not recording, the returned device can be null or correspond to previously + * selected device when the recorder was last active. + */ + @Override + public AudioDeviceInfo getRoutedDevice() { + int deviceId = native_getRoutedDeviceId(); + if (deviceId == 0) { + return null; + } + AudioDeviceInfo[] devices = + AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_INPUTS); + for (int i = 0; i < devices.length; i++) { + if (devices[i].getId() == deviceId) { + return devices[i]; + } + } + return null; + } + + /* + * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler. + */ + private void enableNativeRoutingCallbacksLocked(boolean enabled) { + if (mRoutingChangeListeners.size() == 0) { + native_enableDeviceCallback(enabled); + } + } + + /** + * The list of AudioRouting.OnRoutingChangedListener interfaces added (with + * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)} + * by an app to receive (re)routing notifications. + */ + @GuardedBy("mRoutingChangeListeners") + private ArrayMap<AudioRouting.OnRoutingChangedListener, + NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>(); + + /** + * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing + * changes on this MediaRecorder. + * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive + * notifications of rerouting events. + * @param handler Specifies the {@link Handler} object for the thread on which to execute + * the callback. If <code>null</code>, the handler on the main looper will be used. + */ + @Override + public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener, + Handler handler) { + synchronized (mRoutingChangeListeners) { + if (listener != null && !mRoutingChangeListeners.containsKey(listener)) { + enableNativeRoutingCallbacksLocked(true); + mRoutingChangeListeners.put( + listener, new NativeRoutingEventHandlerDelegate(this, listener, handler)); + } + } + } + + /** + * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added + * to receive rerouting notifications. + * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface + * to remove. + */ + @Override + public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) { + synchronized (mRoutingChangeListeners) { + if (mRoutingChangeListeners.containsKey(listener)) { + mRoutingChangeListeners.remove(listener); + enableNativeRoutingCallbacksLocked(false); + } + } + } + + /** + * Helper class to handle the forwarding of native events to the appropriate listener + * (potentially) handled in a different thread + */ + private class NativeRoutingEventHandlerDelegate { + private MediaRecorder mMediaRecorder; + private AudioRouting.OnRoutingChangedListener mOnRoutingChangedListener; + private Handler mHandler; + + NativeRoutingEventHandlerDelegate(final MediaRecorder mediaRecorder, + final AudioRouting.OnRoutingChangedListener listener, Handler handler) { + mMediaRecorder = mediaRecorder; + mOnRoutingChangedListener = listener; + mHandler = handler != null ? handler : mEventHandler; + } + + void notifyClient() { + if (mHandler != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + if (mOnRoutingChangedListener != null) { + mOnRoutingChangedListener.onRoutingChanged(mMediaRecorder); + } + } + }); + } + } + } + + private native final boolean native_setInputDevice(int deviceId); + private native final int native_getRoutedDeviceId(); + private native final void native_enableDeviceCallback(boolean enabled); + /** * Called from native code when an interesting event happens. This method * just uses the EventHandler system to post the event back to the main app thread. diff --git a/android/media/tv/TvInputManager.java b/android/media/tv/TvInputManager.java index fd1f2cf6..143182f8 100644 --- a/android/media/tv/TvInputManager.java +++ b/android/media/tv/TvInputManager.java @@ -1330,6 +1330,7 @@ public final class TvInputManager { * * @return the list of content ratings blocked by the user. */ + @SystemApi public List<TvContentRating> getBlockedRatings() { try { List<TvContentRating> ratings = new ArrayList<>(); @@ -1585,8 +1586,10 @@ public final class TvInputManager { * @param info The TV input which will use the acquired Hardware. * @return Hardware on success, {@code null} otherwise. * + * @hide * @removed */ + @SystemApi @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public Hardware acquireTvInputHardware(int deviceId, final HardwareCallback callback, TvInputInfo info) { @@ -2591,6 +2594,7 @@ public final class TvInputManager { } /** @removed */ + @SystemApi public boolean dispatchKeyEventToHdmi(KeyEvent event) { return false; } diff --git a/android/net/IpSecAlgorithm.java b/android/net/IpSecAlgorithm.java index 64f8f39e..d6e62cf1 100644 --- a/android/net/IpSecAlgorithm.java +++ b/android/net/IpSecAlgorithm.java @@ -15,6 +15,7 @@ */ package android.net; +import android.annotation.NonNull; import android.annotation.StringDef; import android.os.Build; import android.os.Parcel; @@ -27,8 +28,10 @@ import java.lang.annotation.RetentionPolicy; import java.util.Arrays; /** - * IpSecAlgorithm specifies a single algorithm that can be applied to an IpSec Transform. Refer to - * RFC 4301. + * This class represents a single algorithm that can be used by an {@link IpSecTransform}. + * + * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the + * Internet Protocol</a> */ public final class IpSecAlgorithm implements Parcelable { /** @@ -39,16 +42,16 @@ public final class IpSecAlgorithm implements Parcelable { public static final String CRYPT_AES_CBC = "cbc(aes)"; /** - * MD5 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in new - * applications and is provided for legacy compatibility with 3gpp infrastructure. + * MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in + * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b> * * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 128. */ public static final String AUTH_HMAC_MD5 = "hmac(md5)"; /** - * SHA1 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in - * new applications and is provided for legacy compatibility with 3gpp infrastructure. + * SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in + * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b> * * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 160. */ @@ -69,7 +72,7 @@ public final class IpSecAlgorithm implements Parcelable { public static final String AUTH_HMAC_SHA384 = "hmac(sha384)"; /** - * SHA512 HMAC Authentication/Integrity Algorithm + * SHA512 HMAC Authentication/Integrity Algorithm. * * <p>Valid truncation lengths are multiples of 8 bits from 256 to (default) 512. */ @@ -80,9 +83,9 @@ public final class IpSecAlgorithm implements Parcelable { * * <p>Valid lengths for keying material are {160, 224, 288}. * - * <p>As per RFC4106 (Section 8.1), keying material consists of a 128, 192, or 256 bit AES key - * followed by a 32-bit salt. RFC compliance requires that the salt must be unique per - * invocation with the same key. + * <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section + * 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit + * salt. RFC compliance requires that the salt must be unique per invocation with the same key. * * <p>Valid ICV (truncation) lengths are {64, 96, 128}. */ @@ -105,48 +108,47 @@ public final class IpSecAlgorithm implements Parcelable { private final int mTruncLenBits; /** - * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the - * algorithm + * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are + * defined as constants in this class. * - * @param algorithm type for IpSec. - * @param key non-null Key padded to a multiple of 8 bits. + * @param algorithm name of the algorithm. + * @param key key padded to a multiple of 8 bits. */ - public IpSecAlgorithm(String algorithm, byte[] key) { + public IpSecAlgorithm(@AlgorithmName String algorithm, @NonNull byte[] key) { this(algorithm, key, key.length * 8); } /** - * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the - * algorithm + * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are + * defined as constants in this class. + * + * <p>This constructor only supports algorithms that use a truncation length. i.e. + * Authentication and Authenticated Encryption algorithms. * - * @param algoName precise name of the algorithm to be used. - * @param key non-null Key padded to a multiple of 8 bits. - * @param truncLenBits the number of bits of output hash to use; only meaningful for - * Authentication or Authenticated Encryption (equivalent to ICV length). + * @param algorithm name of the algorithm. + * @param key key padded to a multiple of 8 bits. + * @param truncLenBits number of bits of output hash to use. */ - public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) { - if (!isTruncationLengthValid(algoName, truncLenBits)) { + public IpSecAlgorithm(@AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) { + if (!isTruncationLengthValid(algorithm, truncLenBits)) { throw new IllegalArgumentException("Unknown algorithm or invalid length"); } - mName = algoName; + mName = algorithm; mKey = key.clone(); mTruncLenBits = Math.min(truncLenBits, key.length * 8); } - /** Retrieve the algorithm name */ + /** Get the algorithm name */ public String getName() { return mName; } - /** Retrieve the key for this algorithm */ + /** Get the key for this algorithm */ public byte[] getKey() { return mKey.clone(); } - /** - * Retrieve the truncation length, in bits, for the key in this algo. By default this will be - * the length in bits of the key. - */ + /** Get the truncation length of this algorithm, in bits */ public int getTruncationLengthBits() { return mTruncLenBits; } diff --git a/android/net/IpSecConfig.java b/android/net/IpSecConfig.java index 61b13a92..e6cd3fc1 100644 --- a/android/net/IpSecConfig.java +++ b/android/net/IpSecConfig.java @@ -20,7 +20,12 @@ import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; -/** @hide */ +/** + * This class encapsulates all the configuration parameters needed to create IPsec transforms and + * policies. + * + * @hide + */ public final class IpSecConfig implements Parcelable { private static final String TAG = "IpSecConfig"; @@ -38,6 +43,9 @@ public final class IpSecConfig implements Parcelable { // for outbound packets. It may also be used to select packets. private Network mNetwork; + /** + * This class captures the parameters that specifically apply to inbound or outbound traffic. + */ public static class Flow { // Minimum requirements for identifying a transform // SPI identifying the IPsec flow in packet processing diff --git a/android/net/IpSecManager.java b/android/net/IpSecManager.java index eccd5f47..a9e60ec8 100644 --- a/android/net/IpSecManager.java +++ b/android/net/IpSecManager.java @@ -19,6 +19,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.NonNull; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; import android.os.Binder; import android.os.ParcelFileDescriptor; @@ -37,22 +38,28 @@ import java.net.InetAddress; import java.net.Socket; /** - * This class contains methods for managing IPsec sessions, which will perform kernel-space - * encryption and decryption of socket or Network traffic. + * This class contains methods for managing IPsec sessions. Once configured, the kernel will apply + * confidentiality (encryption) and integrity (authentication) to IP traffic. * - * <p>An IpSecManager may be obtained by calling {@link - * android.content.Context#getSystemService(String) Context#getSystemService(String)} with {@link - * android.content.Context#IPSEC_SERVICE Context#IPSEC_SERVICE} + * <p>Note that not all aspects of IPsec are permitted by this API. Applications may create + * transport mode security associations and apply them to individual sockets. Applications looking + * to create a VPN should use {@link VpnService}. + * + * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the + * Internet Protocol</a> */ @SystemService(Context.IPSEC_SERVICE) public final class IpSecManager { private static final String TAG = "IpSecManager"; /** - * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index. + * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index. * * <p>No IPsec packet may contain an SPI of 0. + * + * @hide */ + @TestApi public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; /** @hide */ @@ -66,10 +73,12 @@ public final class IpSecManager { public static final int INVALID_RESOURCE_ID = 0; /** - * Indicates that the combination of remote InetAddress and SPI was non-unique for a given - * request. If encountered, selection of a new SPI is required before a transform may be - * created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random - * or reserved using reserveSecurityParameterIndex. + * Thrown to indicate that a requested SPI is in use. + * + * <p>The combination of remote {@code InetAddress} and SPI must be unique across all apps on + * one device. If this error is encountered, a new SPI is required before a transform may be + * created. This error can be avoided by calling {@link + * IpSecManager#reserveSecurityParameterIndex}. */ public static final class SpiUnavailableException extends AndroidException { private final int mSpi; @@ -78,24 +87,26 @@ public final class IpSecManager { * Construct an exception indicating that a transform with the given SPI is already in use * or otherwise unavailable. * - * @param msg Description indicating the colliding SPI + * @param msg description indicating the colliding SPI * @param spi the SPI that could not be used due to a collision */ SpiUnavailableException(String msg, int spi) { - super(msg + "(spi: " + spi + ")"); + super(msg + " (spi: " + spi + ")"); mSpi = spi; } - /** Retrieve the SPI that caused a collision */ + /** Get the SPI that caused a collision. */ public int getSpi() { return mSpi; } } /** - * Indicates that the requested system resource for IPsec, such as a socket or other system - * resource is unavailable. If this exception is thrown, try releasing allocated objects of the - * type requested. + * Thrown to indicate that an IPsec resource is unavailable. + * + * <p>This could apply to resources such as sockets, {@link SecurityParameterIndex}, {@link + * IpSecTransform}, or other system resources. If this exception is thrown, users should release + * allocated objects of the type requested. */ public static final class ResourceUnavailableException extends AndroidException { @@ -106,6 +117,13 @@ public final class IpSecManager { private final IIpSecService mService; + /** + * This class represents a reserved SPI. + * + * <p>Objects of this type are used to track reserved security parameter indices. They can be + * obtained by calling {@link IpSecManager#reserveSecurityParameterIndex} and must be released + * by calling {@link #close()} when they are no longer needed. + */ public static final class SecurityParameterIndex implements AutoCloseable { private final IIpSecService mService; private final InetAddress mRemoteAddress; @@ -113,7 +131,7 @@ public final class IpSecManager { private int mSpi = INVALID_SECURITY_PARAMETER_INDEX; private int mResourceId; - /** Return the underlying SPI held by this object */ + /** Get the underlying SPI held by this object. */ public int getSpi() { return mSpi; } @@ -135,6 +153,7 @@ public final class IpSecManager { mCloseGuard.close(); } + /** Check that the SPI was closed properly. */ @Override protected void finalize() throws Throwable { if (mCloseGuard != null) { @@ -197,13 +216,13 @@ public final class IpSecManager { } /** - * Reserve an SPI for traffic bound towards the specified remote address. + * Reserve a random SPI for traffic bound to or from the specified remote address. * * <p>If successful, this SPI is guaranteed available until released by a call to {@link * SecurityParameterIndex#close()}. * * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} - * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress. + * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress * @return the reserved SecurityParameterIndex * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated * for this user @@ -223,17 +242,18 @@ public final class IpSecManager { } /** - * Reserve an SPI for traffic bound towards the specified remote address. + * Reserve the requested SPI for traffic bound to or from the specified remote address. * * <p>If successful, this SPI is guaranteed available until released by a call to {@link * SecurityParameterIndex#close()}. * * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} - * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress. - * @param requestedSpi the requested SPI, or '0' to allocate a random SPI. + * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress + * @param requestedSpi the requested SPI, or '0' to allocate a random SPI * @return the reserved SecurityParameterIndex * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated * for this user + * @throws SpiUnavailableException indicating that the requested SPI could not be reserved */ public SecurityParameterIndex reserveSecurityParameterIndex( int direction, InetAddress remoteAddress, int requestedSpi) @@ -245,16 +265,28 @@ public final class IpSecManager { } /** - * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec - * encapsulation of the traffic flowing between the socket and the remote InetAddress of that - * transform. For security reasons, attempts to send traffic to any IP address other than the - * address associated with that transform will throw an IOException. In addition, if the - * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to - * send() or receive() until the transform is removed from the socket by calling {@link - * #removeTransportModeTransform(Socket, IpSecTransform)}; + * Apply an IPsec transform to a stream socket. + * + * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the + * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When + * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * unprotected traffic can resume on that socket. + * + * <p>For security reasons, the destination address of any traffic on the socket must match the + * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any + * other IP address will result in an IOException. In addition, reads and writes on the socket + * will throw IOException if the user deactivates the transform (by calling {@link + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * + * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform + * will be removed. However, inbound traffic on the old transform will continue to be decrypted + * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap + * allows rekey procedures where both transforms are valid until both endpoints are using the + * new transform and all in-flight packets have been received. * * @param socket a stream socket - * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform. + * @param transform a transport mode {@code IpSecTransform} + * @throws IOException indicating that the transform could not be applied * @hide */ public void applyTransportModeTransform(Socket socket, IpSecTransform transform) @@ -265,16 +297,28 @@ public final class IpSecManager { } /** - * Apply an active Transport Mode IPsec Transform to a datagram socket to perform IPsec - * encapsulation of the traffic flowing between the socket and the remote InetAddress of that - * transform. For security reasons, attempts to send traffic to any IP address other than the - * address associated with that transform will throw an IOException. In addition, if the - * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to - * send() or receive() until the transform is removed from the socket by calling {@link - * #removeTransportModeTransform(DatagramSocket, IpSecTransform)}; + * Apply an IPsec transform to a datagram socket. + * + * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the + * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When + * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * unprotected traffic can resume on that socket. + * + * <p>For security reasons, the destination address of any traffic on the socket must match the + * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any + * other IP address will result in an IOException. In addition, reads and writes on the socket + * will throw IOException if the user deactivates the transform (by calling {@link + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * + * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform + * will be removed. However, inbound traffic on the old transform will continue to be decrypted + * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap + * allows rekey procedures where both transforms are valid until both endpoints are using the + * new transform and all in-flight packets have been received. * * @param socket a datagram socket - * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform. + * @param transform a transport mode {@code IpSecTransform} + * @throws IOException indicating that the transform could not be applied * @hide */ public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform) @@ -285,16 +329,28 @@ public final class IpSecManager { } /** - * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec - * encapsulation of the traffic flowing between the socket and the remote InetAddress of that - * transform. For security reasons, attempts to send traffic to any IP address other than the - * address associated with that transform will throw an IOException. In addition, if the - * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to - * send() or receive() until the transform is removed from the socket by calling {@link - * #removeTransportModeTransform(FileDescriptor, IpSecTransform)}; + * Apply an IPsec transform to a socket. + * + * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the + * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When + * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * unprotected traffic can resume on that socket. + * + * <p>For security reasons, the destination address of any traffic on the socket must match the + * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any + * other IP address will result in an IOException. In addition, reads and writes on the socket + * will throw IOException if the user deactivates the transform (by calling {@link + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * + * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform + * will be removed. However, inbound traffic on the old transform will continue to be decrypted + * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap + * allows rekey procedures where both transforms are valid until both endpoints are using the + * new transform and all in-flight packets have been received. * * @param socket a socket file descriptor - * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform. + * @param transform a transport mode {@code IpSecTransform} + * @throws IOException indicating that the transform could not be applied */ public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform) throws IOException { @@ -323,6 +379,7 @@ public final class IpSecManager { * Applications should probably not use this API directly. Instead, they should use {@link * VpnService} to provide VPN capability in a more generic fashion. * + * TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked. * @param net a {@link Network} that will be tunneled via IP Sec. * @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform. * @hide @@ -330,14 +387,19 @@ public final class IpSecManager { public void applyTunnelModeTransform(Network net, IpSecTransform transform) {} /** - * Remove a transform from a given stream socket. Once removed, traffic on the socket will not - * be encypted. This allows sockets that have been used for IPsec to be reclaimed for - * communication in the clear in the event socket reuse is desired. This operation will succeed - * regardless of the underlying state of a transform. If a transform is removed, communication - * on all sockets to which that transform was applied will fail until this method is called. + * Remove an IPsec transform from a stream socket. + * + * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed + * regardless of the state of the transform. Removing a transform from a socket allows the + * socket to be reused for communication in the clear. + * + * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling + * {@link IpSecTransform#close()}, then communication on the socket will fail until this method + * is called. * - * @param socket a socket that previously had a transform applied to it. + * @param socket a socket that previously had a transform applied to it * @param transform the IPsec Transform that was previously applied to the given socket + * @throws IOException indicating that the transform could not be removed from the socket * @hide */ public void removeTransportModeTransform(Socket socket, IpSecTransform transform) @@ -348,14 +410,19 @@ public final class IpSecManager { } /** - * Remove a transform from a given datagram socket. Once removed, traffic on the socket will not - * be encypted. This allows sockets that have been used for IPsec to be reclaimed for - * communication in the clear in the event socket reuse is desired. This operation will succeed - * regardless of the underlying state of a transform. If a transform is removed, communication - * on all sockets to which that transform was applied will fail until this method is called. + * Remove an IPsec transform from a datagram socket. * - * @param socket a socket that previously had a transform applied to it. + * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed + * regardless of the state of the transform. Removing a transform from a socket allows the + * socket to be reused for communication in the clear. + * + * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling + * {@link IpSecTransform#close()}, then communication on the socket will fail until this method + * is called. + * + * @param socket a socket that previously had a transform applied to it * @param transform the IPsec Transform that was previously applied to the given socket + * @throws IOException indicating that the transform could not be removed from the socket * @hide */ public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform) @@ -366,14 +433,19 @@ public final class IpSecManager { } /** - * Remove a transform from a given stream socket. Once removed, traffic on the socket will not - * be encypted. This allows sockets that have been used for IPsec to be reclaimed for - * communication in the clear in the event socket reuse is desired. This operation will succeed - * regardless of the underlying state of a transform. If a transform is removed, communication - * on all sockets to which that transform was applied will fail until this method is called. + * Remove an IPsec transform from a socket. + * + * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed + * regardless of the state of the transform. Removing a transform from a socket allows the + * socket to be reused for communication in the clear. * - * @param socket a socket file descriptor that previously had a transform applied to it. + * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling + * {@link IpSecTransform#close()}, then communication on the socket will fail until this method + * is called. + * + * @param socket a socket that previously had a transform applied to it * @param transform the IPsec Transform that was previously applied to the given socket + * @throws IOException indicating that the transform could not be removed from the socket */ public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform) throws IOException { @@ -382,7 +454,7 @@ public final class IpSecManager { } } - /* Call down to activate a transform */ + /* Call down to remove a transform */ private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { try { mService.removeTransportModeTransform(pfd, transform.getResourceId()); @@ -397,6 +469,7 @@ public final class IpSecManager { * all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is * lost, all traffic will drop. * + * TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked. * @param net a network that currently has transform applied to it. * @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given * network @@ -405,11 +478,18 @@ public final class IpSecManager { public void removeTunnelModeTransform(Network net, IpSecTransform transform) {} /** - * Class providing access to a system-provided UDP Encapsulation Socket, which may be used for - * IKE signalling as well as for inbound and outbound UDP encapsulated IPsec traffic. + * This class provides access to a UDP encapsulation Socket. * - * <p>The socket provided by this class cannot be re-bound or closed via the inner - * FileDescriptor. Instead, disposing of this socket requires a call to close(). + * <p>{@code UdpEncapsulationSocket} wraps a system-provided datagram socket intended for IKEv2 + * signalling and UDP encapsulated IPsec traffic. Instances can be obtained by calling {@link + * IpSecManager#openUdpEncapsulationSocket}. The provided socket cannot be re-bound by the + * caller. The caller should not close the {@code FileDescriptor} returned by {@link + * #getSocket}, but should use {@link #close} instead. + * + * <p>Allowing the user to close or unbind a UDP encapsulation socket could impact the traffic + * of the next user who binds to that port. To prevent this scenario, these sockets are held + * open by the system so that they may only be closed by calling {@link #close} or when the user + * process exits. */ public static final class UdpEncapsulationSocket implements AutoCloseable { private final ParcelFileDescriptor mPfd; @@ -443,7 +523,7 @@ public final class IpSecManager { mCloseGuard.open("constructor"); } - /** Access the inner UDP Encapsulation Socket */ + /** Get the wrapped socket. */ public FileDescriptor getSocket() { if (mPfd == null) { return null; @@ -451,22 +531,19 @@ public final class IpSecManager { return mPfd.getFileDescriptor(); } - /** Retrieve the port number of the inner encapsulation socket */ + /** Get the bound port of the wrapped socket. */ public int getPort() { return mPort; } - @Override /** - * Release the resources that have been reserved for this Socket. + * Close this socket. * - * <p>This method closes the underlying socket, reducing a user's allocated sockets in the - * system. This must be done as part of cleanup following use of a socket. Failure to do so - * will cause the socket to count against a total allocation limit for IpSec and eventually - * fail due to resource limits. - * - * @param fd a file descriptor previously returned as a UDP Encapsulation socket. + * <p>This closes the wrapped socket. Open encapsulation sockets count against a user's + * resource limits, and forgetting to close them eventually will result in {@link + * ResourceUnavailableException} being thrown. */ + @Override public void close() throws IOException { try { mService.closeUdpEncapsulationSocket(mResourceId); @@ -483,6 +560,7 @@ public final class IpSecManager { mCloseGuard.close(); } + /** Check that the socket was closed properly. */ @Override protected void finalize() throws Throwable { if (mCloseGuard != null) { @@ -499,21 +577,14 @@ public final class IpSecManager { }; /** - * Open a socket that is bound to a free UDP port on the system. - * - * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by - * the caller. This provides safe access to a socket on a port that can later be used as a UDP - * Encapsulation port. + * Open a socket for UDP encapsulation and bind to the given port. * - * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the - * socket port. Explicitly opening this port is only necessary if communication is desired on - * that port. + * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket. * - * @param port a local UDP port to be reserved for UDP Encapsulation. is provided, then this - * method will bind to the specified port or fail. To retrieve the port number, call {@link - * android.system.Os#getsockname(FileDescriptor)}. - * @return a {@link UdpEncapsulationSocket} that is bound to the requested port for the lifetime - * of the object. + * @param port a local UDP port + * @return a socket that is bound to the given port + * @throws IOException indicating that the socket could not be opened or bound + * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open */ // Returning a socket in this fashion that has been created and bound by the system // is the only safe way to ensure that a socket is both accessible to the user and @@ -533,17 +604,16 @@ public final class IpSecManager { } /** - * Open a socket that is bound to a port selected by the system. + * Open a socket for UDP encapsulation. * - * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by - * the caller. This provides safe access to a socket on a port that can later be used as a UDP - * Encapsulation port. + * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket. * - * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the - * socket port. Explicitly opening this port is only necessary if communication is desired on - * that port. + * <p>The local port of the returned socket can be obtained by calling {@link + * UdpEncapsulationSocket#getPort()}. * - * @return a {@link UdpEncapsulationSocket} that is bound to an arbitrarily selected port + * @return a socket that is bound to a local port + * @throws IOException indicating that the socket could not be opened or bound + * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open */ // Returning a socket in this fashion that has been created and bound by the system // is the only safe way to ensure that a socket is both accessible to the user and @@ -556,7 +626,7 @@ public final class IpSecManager { } /** - * Retrieve an instance of an IpSecManager within you application context + * Construct an instance of IpSecManager within an application context. * * @param context the application context for this manager * @hide diff --git a/android/net/IpSecTransform.java b/android/net/IpSecTransform.java index 48b5bd5c..cda4ec76 100644 --- a/android/net/IpSecTransform.java +++ b/android/net/IpSecTransform.java @@ -38,27 +38,29 @@ import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; /** - * This class represents an IpSecTransform, which encapsulates both properties and state of IPsec. + * This class represents an IPsec transform, which comprises security associations in one or both + * directions. * - * <p>IpSecTransforms must be built from an IpSecTransform.Builder, and they must persist throughout - * the lifetime of the underlying transform. If a transform object leaves scope, the underlying - * transform may be disabled automatically, with likely undesirable results. + * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform} + * object encapsulates the properties and state of an inbound and outbound IPsec security + * association. That includes, but is not limited to, algorithm choice, key material, and allocated + * system resources. * - * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array - * of traffic or may represent a transport mode transform operating on a Socket or Sockets. + * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the + * Internet Protocol</a> */ public final class IpSecTransform implements AutoCloseable { private static final String TAG = "IpSecTransform"; /** - * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies - * to traffic towards the host. + * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute + * applies to traffic towards the host. */ public static final int DIRECTION_IN = 0; /** - * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies - * to traffic from the host. + * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute + * applies to traffic from the host. */ public static final int DIRECTION_OUT = 1; @@ -77,16 +79,16 @@ public final class IpSecTransform implements AutoCloseable { public static final int ENCAP_NONE = 0; /** - * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad - * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP. + * IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP + * header and payload. This prevents traffic from being interpreted as ESP or IKEv2. * * @hide */ public static final int ENCAP_ESPINUDP_NON_IKE = 1; /** - * IpSec traffic will be encapsulated within UDP as per <a - * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>. + * IPsec traffic will be encapsulated within UDP as per + * <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>. * * @hide */ @@ -165,13 +167,14 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Deactivate an IpSecTransform and free all resources for that transform that are managed by - * the system for this Transform. + * Deactivate this {@code IpSecTransform} and free allocated resources. * - * <p>Deactivating a transform while it is still applied to any Socket will result in sockets - * refusing to send or receive data. This method will silently succeed if the specified - * transform has already been removed; thus, it is always safe to attempt cleanup when a - * transform is no longer needed. + * <p>Deactivating a transform while it is still applied to a socket will result in errors on + * that socket. Make sure to remove transforms by calling {@link + * IpSecManager#removeTransportModeTransform}. Note, removing an {@code IpSecTransform} from a + * socket will not deactivate it (because one transform may be applied to multiple sockets). + * + * <p>It is safe to call this method on a transform that has already been deactivated. */ public void close() { Log.d(TAG, "Removing Transform with Id " + mResourceId); @@ -197,6 +200,7 @@ public final class IpSecTransform implements AutoCloseable { } } + /** Check that the transform was closed properly. */ @Override protected void finalize() throws Throwable { if (mCloseGuard != null) { @@ -264,65 +268,63 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Builder object to facilitate the creation of IpSecTransform objects. - * - * <p>Apply additional properties to the transform and then call a build() method to return an - * IpSecTransform object. - * - * @see Builder#buildTransportModeTransform(InetAddress) + * This class is used to build {@link IpSecTransform} objects. */ public static class Builder { private Context mContext; private IpSecConfig mConfig; /** - * Add an encryption algorithm to the transform for the given direction. + * Set the encryption algorithm for the given direction. * - * <p>If encryption is set for a given direction without also providing an SPI for that - * direction, creation of an IpSecTransform will fail upon calling a build() method. + * <p>If encryption is set for a direction without also providing an SPI for that direction, + * creation of an {@code IpSecTransform} will fail when attempting to build the transform. * - * <p>Authenticated encryption is mutually exclusive with encryption and authentication. + * <p>Encryption is mutually exclusive with authenticated encryption. * - * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied. */ public IpSecTransform.Builder setEncryption( @TransformDirection int direction, IpSecAlgorithm algo) { + // TODO: throw IllegalArgumentException if algo is not an encryption algorithm. mConfig.setEncryption(direction, algo); return this; } /** - * Add an authentication/integrity algorithm to the transform. + * Set the authentication (integrity) algorithm for the given direction. * - * <p>If authentication is set for a given direction without also providing an SPI for that - * direction, creation of an IpSecTransform will fail upon calling a build() method. + * <p>If authentication is set for a direction without also providing an SPI for that + * direction, creation of an {@code IpSecTransform} will fail when attempting to build the + * transform. * - * <p>Authenticated encryption is mutually exclusive with encryption and authentication. + * <p>Authentication is mutually exclusive with authenticated encryption. * - * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied. */ public IpSecTransform.Builder setAuthentication( @TransformDirection int direction, IpSecAlgorithm algo) { + // TODO: throw IllegalArgumentException if algo is not an authentication algorithm. mConfig.setAuthentication(direction, algo); return this; } /** - * Add an authenticated encryption algorithm to the transform for the given direction. + * Set the authenticated encryption algorithm for the given direction. * * <p>If an authenticated encryption algorithm is set for a given direction without also - * providing an SPI for that direction, creation of an IpSecTransform will fail upon calling - * a build() method. + * providing an SPI for that direction, creation of an {@code IpSecTransform} will fail when + * attempting to build the transform. * * <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated * Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as - * referred to in RFC 4301) + * referred to in <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>). * * <p>Authenticated encryption is mutually exclusive with encryption and authentication. * - * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to * be applied. */ @@ -333,19 +335,16 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Set the SPI, which uniquely identifies a particular IPsec session from others. Because - * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a - * given destination address. + * Set the SPI for the given direction. * - * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as - * possible. To reserve a value call {@link IpSecManager#reserveSecurityParameterIndex(int, - * InetAddress, int)}. Otherwise, SPI collisions would prevent a transform from being - * activated. IpSecManager#reserveSecurityParameterIndex(int, InetAddres$s, int)}. + * <p>Because IPsec operates at the IP layer, this 32-bit identifier uniquely identifies + * packets to a given destination address. To prevent SPI collisions, values should be + * reserved by calling {@link IpSecManager#reserveSecurityParameterIndex}. * - * <p>Unless an SPI is set for a given direction, traffic in that direction will be - * sent/received without any IPsec applied. + * <p>If the SPI and algorithms are omitted for one direction, traffic in that direction + * will not be encrypted or authenticated. * - * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed * traffic */ @@ -356,11 +355,10 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Specify the network on which this transform will emit its traffic; (otherwise it will - * emit on the default network). + * Set the {@link Network} which will carry tunneled traffic. * - * <p>Restricts the transformed traffic to a particular {@link Network}. This is required in - * tunnel mode. + * <p>Restricts the transformed traffic to a particular {@link Network}. This is required + * for tunnel mode, otherwise tunneled traffic would be sent on the default network. * * @hide */ @@ -371,15 +369,18 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Add UDP encapsulation to an IPv4 transform + * Add UDP encapsulation to an IPv4 transform. * - * <p>This option allows IPsec traffic to pass through NAT. Refer to RFC 3947 and 3948 for - * details on how UDP should be applied to IPsec. + * <p>This allows IPsec traffic to pass through a NAT. * - * @param localSocket a {@link IpSecManager.UdpEncapsulationSocket} for sending and - * receiving encapsulating traffic. - * @param remotePort the UDP port number of the remote that will send and receive - * encapsulated traffic. In the case of IKE, this is likely port 4500. + * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec + * ESP Packets</a> + * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23, + * NAT Traversal of IKEv2</a> + * + * @param localSocket a socket for sending and receiving encapsulated traffic + * @param remotePort the UDP port number of the remote host that will send and receive + * encapsulated traffic. In the case of IKEv2, this should be port 4500. */ public IpSecTransform.Builder setIpv4Encapsulation( IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { @@ -393,12 +394,15 @@ public final class IpSecTransform implements AutoCloseable { // TODO: Probably a better exception to throw for NATTKeepalive failure // TODO: Specify the needed NATT keepalive permission. /** - * Send a NATT Keepalive packet with a given maximum interval. This will create an offloaded - * request to do power-efficient NATT Keepalive. If NATT keepalive is requested but cannot - * be activated, then the transform will fail to activate and throw an IOException. + * Set NAT-T keepalives to be sent with a given interval. + * + * <p>This will set power-efficient keepalive packets to be sent by the system. If NAT-T + * keepalive is requested but cannot be activated, then creation of an {@link + * IpSecTransform} will fail when calling the build method. + * + * @param intervalSeconds the maximum number of seconds between keepalive packets. Must be + * between 20s and 3600s. * - * @param intervalSeconds the maximum number of seconds between keepalive packets, no less - * than 20s and no more than 3600s. * @hide */ @SystemApi @@ -408,36 +412,29 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Build and return an active {@link IpSecTransform} object as a Transport Mode Transform. - * Some parameters have interdependencies that are checked at build time. If a well-formed - * transform cannot be created from the supplied parameters, this method will throw an - * Exception. + * Build a transport mode {@link IpSecTransform}. * - * <p>Upon a successful return from this call, the provided IpSecTransform will be active - * and may be applied to sockets. If too many IpSecTransform objects are active for a given - * user this operation will fail and throw ResourceUnavailableException. To avoid these - * exceptions, unused Transform objects must be cleaned up by calling {@link - * IpSecTransform#close()} when they are no longer needed. + * <p>This builds and activates a transport mode transform. Note that an active transform + * will not affect any network traffic until it has been applied to one or more sockets. * - * @param remoteAddress the {@link InetAddress} that, when matched on traffic to/from this - * socket will cause the transform to be applied. - * <p>Note that an active transform will not impact any network traffic until it has - * been applied to one or more Sockets. Calling this method is a necessary precondition - * for applying it to a socket, but is not sufficient to actually apply IPsec. + * @see IpSecManager#applyTransportModeTransform + * + * @param remoteAddress the remote {@code InetAddress} of traffic on sockets that will use + * this transform * @throws IllegalArgumentException indicating that a particular combination of transform - * properties is invalid. - * @throws IpSecManager.ResourceUnavailableException in the event that no more Transforms - * may be allocated - * @throws SpiUnavailableException if the SPI collides with an existing transform - * (unlikely). - * @throws ResourceUnavailableException if the current user currently has exceeded the - * number of allowed active transforms. + * properties is invalid + * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms are + * active + * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI + * collides with an existing transform + * @throws IOException indicating other errors */ public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress) throws IpSecManager.ResourceUnavailableException, IpSecManager.SpiUnavailableException, IOException { mConfig.setMode(MODE_TRANSPORT); mConfig.setRemoteAddress(remoteAddress.getHostAddress()); + // FIXME: modifying a builder after calling build can change the built transform. return new IpSecTransform(mContext, mConfig).activate(); } @@ -465,9 +462,9 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Create a new IpSecTransform.Builder to construct an IpSecTransform + * Create a new IpSecTransform.Builder. * - * @param context current Context + * @param context current context */ public Builder(@NonNull Context context) { Preconditions.checkNotNull(context); diff --git a/android/net/NetworkCapabilities.java b/android/net/NetworkCapabilities.java index ee75fd44..f468e5d2 100644 --- a/android/net/NetworkCapabilities.java +++ b/android/net/NetworkCapabilities.java @@ -31,16 +31,10 @@ import java.util.Objects; import java.util.StringJoiner; /** - * Representation of the capabilities of a network. This object serves two - * purposes: - * <ul> - * <li>An expression of the current capabilities of an active network, typically - * expressed through + * Representation of the capabilities of an active network. Instances are + * typically obtained through * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)} * or {@link ConnectivityManager#getNetworkCapabilities(Network)}. - * <li>An expression of the future capabilities of a desired network, typically - * expressed through {@link NetworkRequest}. - * </ul> * <p> * This replaces the old {@link ConnectivityManager#TYPE_MOBILE} method of * network selection. Rather than indicate a need for Wi-Fi because an @@ -79,7 +73,7 @@ public final class NetworkCapabilities implements Parcelable { */ public void clearAll() { mNetworkCapabilities = mTransportTypes = 0; - mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = 0; + mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; mNetworkSpecifier = null; mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED; } @@ -359,6 +353,7 @@ public final class NetworkCapabilities implements Parcelable { /** * Sets all the capabilities set on this {@code NetworkCapability} instance. + * This overwrites any existing capabilities. * * @hide */ @@ -582,6 +577,7 @@ public final class NetworkCapabilities implements Parcelable { /** * Sets all the transports set on this {@code NetworkCapability} instance. + * This overwrites any existing transports. * * @hide */ @@ -780,7 +776,7 @@ public final class NetworkCapabilities implements Parcelable { * Signal strength. This is a signed integer, and higher values indicate better signal. * The exact units are bearer-dependent. For example, Wi-Fi uses RSSI. */ - private int mSignalStrength; + private int mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED; /** * Sets the signal strength. This is a signed integer, with higher values indicating a stronger diff --git a/android/net/NetworkRequest.java b/android/net/NetworkRequest.java index 25b17052..97ded2d7 100644 --- a/android/net/NetworkRequest.java +++ b/android/net/NetworkRequest.java @@ -16,6 +16,7 @@ package android.net; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -32,7 +33,7 @@ public class NetworkRequest implements Parcelable { * The {@link NetworkCapabilities} that define this request. * @hide */ - public final NetworkCapabilities networkCapabilities; + public final @NonNull NetworkCapabilities networkCapabilities; /** * Identifies the request. NetworkRequests should only be constructed by @@ -307,7 +308,7 @@ public class NetworkRequest implements Parcelable { return 0; } public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelable(networkCapabilities, flags); + networkCapabilities.writeToParcel(dest, flags); dest.writeInt(legacyType); dest.writeInt(requestId); dest.writeString(type.name()); @@ -315,7 +316,7 @@ public class NetworkRequest implements Parcelable { public static final Creator<NetworkRequest> CREATOR = new Creator<NetworkRequest>() { public NetworkRequest createFromParcel(Parcel in) { - NetworkCapabilities nc = (NetworkCapabilities)in.readParcelable(null); + NetworkCapabilities nc = NetworkCapabilities.CREATOR.createFromParcel(in); int legacyType = in.readInt(); int requestId = in.readInt(); Type type = Type.valueOf(in.readString()); // IllegalArgumentException if invalid. diff --git a/android/net/NetworkState.java b/android/net/NetworkState.java index 95e3802e..b00cb482 100644 --- a/android/net/NetworkState.java +++ b/android/net/NetworkState.java @@ -18,6 +18,7 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; +import android.util.Slog; /** * Snapshot of network state. @@ -43,6 +44,16 @@ public class NetworkState implements Parcelable { this.network = network; this.subscriberId = subscriberId; this.networkId = networkId; + + // This object is an atomic view of a network, so the various components + // should always agree on roaming state. + if (networkInfo != null && networkCapabilities != null) { + if (networkInfo.isRoaming() == networkCapabilities + .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)) { + Slog.wtf("NetworkState", "Roaming state disagreement between " + networkInfo + + " and " + networkCapabilities); + } + } } public NetworkState(Parcel in) { diff --git a/android/net/metrics/WakeupEvent.java b/android/net/metrics/WakeupEvent.java index 8f1a5c42..af9a73ca 100644 --- a/android/net/metrics/WakeupEvent.java +++ b/android/net/metrics/WakeupEvent.java @@ -29,7 +29,7 @@ public class WakeupEvent { public String iface; public int uid; public int ethertype; - public byte[] dstHwAddr; + public MacAddress dstHwAddr; public String srcIp; public String dstIp; public int ipNextHeader; @@ -44,7 +44,7 @@ public class WakeupEvent { j.add(iface); j.add("uid: " + Integer.toString(uid)); j.add("eth=0x" + Integer.toHexString(ethertype)); - j.add("dstHw=" + MacAddress.stringAddrFromByteAddr(dstHwAddr)); + j.add("dstHw=" + dstHwAddr); if (ipNextHeader > 0) { j.add("ipNxtHdr=" + ipNextHeader); j.add("srcIp=" + srcIp); diff --git a/android/net/metrics/WakeupStats.java b/android/net/metrics/WakeupStats.java index 1ba97771..23c1f20f 100644 --- a/android/net/metrics/WakeupStats.java +++ b/android/net/metrics/WakeupStats.java @@ -16,7 +16,6 @@ package android.net.metrics; -import android.net.MacAddress; import android.os.Process; import android.os.SystemClock; import android.util.SparseIntArray; @@ -80,7 +79,7 @@ public class WakeupStats { break; } - switch (MacAddress.macAddressType(ev.dstHwAddr)) { + switch (ev.dstHwAddr.addressType()) { case UNICAST: l2UnicastCount++; break; diff --git a/android/net/wifi/BatchedScanResult.java b/android/net/wifi/BatchedScanResult.java index 6d9f00f3..c06543ec 100644 --- a/android/net/wifi/BatchedScanResult.java +++ b/android/net/wifi/BatchedScanResult.java @@ -17,6 +17,7 @@ package android.net.wifi; import android.os.Parcelable; +import android.annotation.SystemApi; import android.os.Parcel; import java.util.ArrayList; @@ -29,6 +30,7 @@ import java.util.List; * @removed */ @Deprecated +@SystemApi public class BatchedScanResult implements Parcelable { private static final String TAG = "BatchedScanResult"; diff --git a/android/net/wifi/WifiManager.java b/android/net/wifi/WifiManager.java index 66fabf33..558004ce 100644 --- a/android/net/wifi/WifiManager.java +++ b/android/net/wifi/WifiManager.java @@ -33,6 +33,8 @@ import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.wifi.hotspot2.OsuProvider; import android.net.wifi.hotspot2.PasspointConfiguration; +import android.net.wifi.hotspot2.IProvisioningCallback; +import android.net.wifi.hotspot2.ProvisioningCallback; import android.os.Binder; import android.os.Build; import android.os.Handler; @@ -1128,7 +1130,7 @@ public class WifiManager { */ private int addOrUpdateNetwork(WifiConfiguration config) { try { - return mService.addOrUpdateNetwork(config, mContext.getOpPackageName()); + return mService.addOrUpdateNetwork(config); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1149,7 +1151,7 @@ public class WifiManager { */ public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) { try { - if (!mService.addOrUpdatePasspointConfiguration(config, mContext.getOpPackageName())) { + if (!mService.addOrUpdatePasspointConfiguration(config)) { throw new IllegalArgumentException(); } } catch (RemoteException e) { @@ -1166,7 +1168,7 @@ public class WifiManager { */ public void removePasspointConfiguration(String fqdn) { try { - if (!mService.removePasspointConfiguration(fqdn, mContext.getOpPackageName())) { + if (!mService.removePasspointConfiguration(fqdn)) { throw new IllegalArgumentException(); } } catch (RemoteException e) { @@ -1252,7 +1254,7 @@ public class WifiManager { */ public boolean removeNetwork(int netId) { try { - return mService.removeNetwork(netId, mContext.getOpPackageName()); + return mService.removeNetwork(netId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1298,7 +1300,7 @@ public class WifiManager { boolean success; try { - success = mService.enableNetwork(netId, attemptConnect, mContext.getOpPackageName()); + success = mService.enableNetwork(netId, attemptConnect); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1324,7 +1326,7 @@ public class WifiManager { */ public boolean disableNetwork(int netId) { try { - return mService.disableNetwork(netId, mContext.getOpPackageName()); + return mService.disableNetwork(netId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1337,7 +1339,7 @@ public class WifiManager { */ public boolean disconnect() { try { - mService.disconnect(mContext.getOpPackageName()); + mService.disconnect(); return true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1352,7 +1354,7 @@ public class WifiManager { */ public boolean reconnect() { try { - mService.reconnect(mContext.getOpPackageName()); + mService.reconnect(); return true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1367,7 +1369,7 @@ public class WifiManager { */ public boolean reassociate() { try { - mService.reassociate(mContext.getOpPackageName()); + mService.reassociate(); return true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1740,7 +1742,7 @@ public class WifiManager { @Deprecated public boolean saveConfiguration() { try { - return mService.saveConfiguration(mContext.getOpPackageName()); + return mService.saveConfiguration(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2056,7 +2058,7 @@ public class WifiManager { } mLOHSCallbackProxy = null; try { - mService.stopLocalOnlyHotspot(mContext.getOpPackageName()); + mService.stopLocalOnlyHotspot(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2175,7 +2177,7 @@ public class WifiManager { @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) { try { - mService.setWifiApConfiguration(wifiConfig, mContext.getOpPackageName()); + mService.setWifiApConfiguration(wifiConfig); return true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -2947,7 +2949,7 @@ public class WifiManager { public void disableEphemeralNetwork(String SSID) { if (SSID == null) throw new IllegalArgumentException("SSID cannot be null"); try { - mService.disableEphemeralNetwork(SSID, mContext.getOpPackageName()); + mService.disableEphemeralNetwork(SSID); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2986,7 +2988,7 @@ public class WifiManager { */ public Messenger getWifiServiceMessenger() { try { - return mService.getWifiServiceMessenger(mContext.getOpPackageName()); + return mService.getWifiServiceMessenger(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3516,7 +3518,7 @@ public class WifiManager { */ public void factoryReset() { try { - mService.factoryReset(mContext.getOpPackageName()); + mService.factoryReset(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3543,7 +3545,7 @@ public class WifiManager { */ public boolean setEnableAutoJoinWhenAssociated(boolean enabled) { try { - return mService.setEnableAutoJoinWhenAssociated(enabled, mContext.getOpPackageName()); + return mService.setEnableAutoJoinWhenAssociated(enabled); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3610,4 +3612,45 @@ public class WifiManager { throw e.rethrowFromSystemServer(); } } + + /** + * Start subscription provisioning flow + * @param provider {@link OsuProvider} to provision with + * @param callback {@link ProvisioningCallback} for updates regarding provisioning flow + * @hide + */ + public void startSubscriptionProvisioning(OsuProvider provider, ProvisioningCallback callback, + @Nullable Handler handler) { + Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper(); + try { + mService.startSubscriptionProvisioning(provider, + new ProvisioningCallbackProxy(looper, callback)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static class ProvisioningCallbackProxy extends IProvisioningCallback.Stub { + private final Handler mHandler; + private final ProvisioningCallback mCallback; + + ProvisioningCallbackProxy(Looper looper, ProvisioningCallback callback) { + mHandler = new Handler(looper); + mCallback = callback; + } + + @Override + public void onProvisioningStatus(int status) { + mHandler.post(() -> { + mCallback.onProvisioningStatus(status); + }); + } + + @Override + public void onProvisioningFailure(int status) { + mHandler.post(() -> { + mCallback.onProvisioningFailure(status); + }); + } + } } diff --git a/android/net/wifi/aware/DiscoverySessionCallback.java b/android/net/wifi/aware/DiscoverySessionCallback.java index 115b86d1..aa2c268c 100644 --- a/android/net/wifi/aware/DiscoverySessionCallback.java +++ b/android/net/wifi/aware/DiscoverySessionCallback.java @@ -113,6 +113,32 @@ public class DiscoverySessionCallback { } /** + * Called when a discovery (publish or subscribe) operation results in a + * service discovery. Called when a Subscribe service was configured with a range requirement + * {@link SubscribeConfig.Builder#setMinDistanceMm(int)} and/or + * {@link SubscribeConfig.Builder#setMaxDistanceMm(int)}. A discovery will only be declared + * (i.e. this callback called) if the range of the publisher is within the specified distance + * constraints. + * + * @param peerHandle An opaque handle to the peer matching our discovery operation. + * @param serviceSpecificInfo The service specific information (arbitrary + * byte array) provided by the peer as part of its discovery + * configuration. + * @param matchFilter The filter which resulted in this service discovery. For + * {@link PublishConfig#PUBLISH_TYPE_UNSOLICITED}, + * {@link SubscribeConfig#SUBSCRIBE_TYPE_PASSIVE} discovery sessions this is the publisher's + * match filter. For {@link PublishConfig#PUBLISH_TYPE_SOLICITED}, + * {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE} discovery sessions this + * is the subscriber's match filter. + * @param distanceMm The measured distance to the Publisher in mm. + * @hide + */ + public void onServiceDiscoveredWithinRange(PeerHandle peerHandle, + byte[] serviceSpecificInfo, List<byte[]> matchFilter, int distanceMm) { + /* empty */ + } + + /** * Called in response to * {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])} * when a message is transmitted successfully - i.e. when it was received successfully by the diff --git a/android/net/wifi/aware/PublishConfig.java b/android/net/wifi/aware/PublishConfig.java index d0186207..e60f52f8 100644 --- a/android/net/wifi/aware/PublishConfig.java +++ b/android/net/wifi/aware/PublishConfig.java @@ -29,6 +29,7 @@ import java.lang.annotation.RetentionPolicy; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; +import java.util.Objects; /** * Defines the configuration of a Aware publish session. Built using @@ -81,14 +82,19 @@ public final class PublishConfig implements Parcelable { public final boolean mEnableTerminateNotification; /** @hide */ + public final boolean mEnableRanging; + + /** @hide */ public PublishConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter, - int publishType, int ttlSec, boolean enableTerminateNotification) { + int publishType, int ttlSec, boolean enableTerminateNotification, + boolean enableRanging) { mServiceName = serviceName; mServiceSpecificInfo = serviceSpecificInfo; mMatchFilter = matchFilter; mPublishType = publishType; mTtlSec = ttlSec; mEnableTerminateNotification = enableTerminateNotification; + mEnableRanging = enableRanging; } @Override @@ -103,7 +109,8 @@ public final class PublishConfig implements Parcelable { + (new TlvBufferUtils.TlvIterable(0, 1, mMatchFilter)).toString() + ", mMatchFilter.length=" + (mMatchFilter == null ? 0 : mMatchFilter.length) + ", mPublishType=" + mPublishType + ", mTtlSec=" + mTtlSec - + ", mEnableTerminateNotification=" + mEnableTerminateNotification + "]"; + + ", mEnableTerminateNotification=" + mEnableTerminateNotification + + ", mEnableRanging=" + mEnableRanging + "]"; } @Override @@ -119,6 +126,7 @@ public final class PublishConfig implements Parcelable { dest.writeInt(mPublishType); dest.writeInt(mTtlSec); dest.writeInt(mEnableTerminateNotification ? 1 : 0); + dest.writeInt(mEnableRanging ? 1 : 0); } public static final Creator<PublishConfig> CREATOR = new Creator<PublishConfig>() { @@ -135,9 +143,10 @@ public final class PublishConfig implements Parcelable { int publishType = in.readInt(); int ttlSec = in.readInt(); boolean enableTerminateNotification = in.readInt() != 0; + boolean enableRanging = in.readInt() != 0; return new PublishConfig(serviceName, ssi, matchFilter, publishType, - ttlSec, enableTerminateNotification); + ttlSec, enableTerminateNotification, enableRanging); } }; @@ -157,21 +166,14 @@ public final class PublishConfig implements Parcelable { lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter, lhs.mMatchFilter) && mPublishType == lhs.mPublishType && mTtlSec == lhs.mTtlSec - && mEnableTerminateNotification == lhs.mEnableTerminateNotification; + && mEnableTerminateNotification == lhs.mEnableTerminateNotification + && mEnableRanging == lhs.mEnableRanging; } @Override public int hashCode() { - int result = 17; - - result = 31 * result + Arrays.hashCode(mServiceName); - result = 31 * result + Arrays.hashCode(mServiceSpecificInfo); - result = 31 * result + Arrays.hashCode(mMatchFilter); - result = 31 * result + mPublishType; - result = 31 * result + mTtlSec; - result = 31 * result + (mEnableTerminateNotification ? 1 : 0); - - return result; + return Objects.hash(mServiceName, mServiceSpecificInfo, mMatchFilter, mPublishType, mTtlSec, + mEnableTerminateNotification, mEnableRanging); } /** @@ -226,6 +228,7 @@ public final class PublishConfig implements Parcelable { private int mPublishType = PUBLISH_TYPE_UNSOLICITED; private int mTtlSec = 0; private boolean mEnableTerminateNotification = true; + private boolean mEnableRanging = false; /** * Specify the service name of the publish session. The actual on-air @@ -352,12 +355,35 @@ public final class PublishConfig implements Parcelable { } /** + * Configure whether the publish discovery session supports ranging and allows peers to + * measure distance to it. This API is used in conjunction with + * {@link SubscribeConfig.Builder#setMinDistanceMm(int)} and + * {@link SubscribeConfig.Builder#setMaxDistanceMm(int)} to specify a minimum and/or + * maximum distance at which discovery will be triggered. + * <p> + * Optional. Disabled by default - i.e. any peer which attempts to measure distance to this + * device will be refused. If the peer has ranging enabled (using the + * {@link SubscribeConfig} APIs listed above, it will never discover this device. + * + * @param enable If true, ranging is supported on request of the peer. + * + * @return The builder to facilitate chaining + * {@code builder.setXXX(..).setXXX(..)}. + * + * @hide + */ + public Builder setRangingEnabled(boolean enable) { + mEnableRanging = enable; + return this; + } + + /** * Build {@link PublishConfig} given the current requests made on the * builder. */ public PublishConfig build() { return new PublishConfig(mServiceName, mServiceSpecificInfo, mMatchFilter, mPublishType, - mTtlSec, mEnableTerminateNotification); + mTtlSec, mEnableTerminateNotification, mEnableRanging); } } } diff --git a/android/net/wifi/aware/SubscribeConfig.java b/android/net/wifi/aware/SubscribeConfig.java index 4bf2fb6a..f6552a76 100644 --- a/android/net/wifi/aware/SubscribeConfig.java +++ b/android/net/wifi/aware/SubscribeConfig.java @@ -29,6 +29,7 @@ import java.lang.annotation.RetentionPolicy; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; +import java.util.Objects; /** * Defines the configuration of a Aware subscribe session. Built using @@ -79,15 +80,32 @@ public final class SubscribeConfig implements Parcelable { public final boolean mEnableTerminateNotification; /** @hide */ + public final boolean mMinDistanceMmSet; + + /** @hide */ + public final int mMinDistanceMm; + + /** @hide */ + public final boolean mMaxDistanceMmSet; + + /** @hide */ + public final int mMaxDistanceMm; + + /** @hide */ public SubscribeConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter, - int subscribeType, int ttlSec, - boolean enableTerminateNotification) { + int subscribeType, int ttlSec, boolean enableTerminateNotification, + boolean minDistanceMmSet, int minDistanceMm, boolean maxDistanceMmSet, + int maxDistanceMm) { mServiceName = serviceName; mServiceSpecificInfo = serviceSpecificInfo; mMatchFilter = matchFilter; mSubscribeType = subscribeType; mTtlSec = ttlSec; mEnableTerminateNotification = enableTerminateNotification; + mMinDistanceMm = minDistanceMm; + mMinDistanceMmSet = minDistanceMmSet; + mMaxDistanceMm = maxDistanceMm; + mMaxDistanceMmSet = maxDistanceMmSet; } @Override @@ -102,7 +120,11 @@ public final class SubscribeConfig implements Parcelable { + (new TlvBufferUtils.TlvIterable(0, 1, mMatchFilter)).toString() + ", mMatchFilter.length=" + (mMatchFilter == null ? 0 : mMatchFilter.length) + ", mSubscribeType=" + mSubscribeType + ", mTtlSec=" + mTtlSec - + ", mEnableTerminateNotification=" + mEnableTerminateNotification + "]"; + + ", mEnableTerminateNotification=" + mEnableTerminateNotification + + ", mMinDistanceMm=" + mMinDistanceMm + + ", mMinDistanceMmSet=" + mMinDistanceMmSet + + ", mMaxDistanceMm=" + mMaxDistanceMm + + ", mMaxDistanceMmSet=" + mMaxDistanceMmSet + "]"; } @Override @@ -118,6 +140,10 @@ public final class SubscribeConfig implements Parcelable { dest.writeInt(mSubscribeType); dest.writeInt(mTtlSec); dest.writeInt(mEnableTerminateNotification ? 1 : 0); + dest.writeInt(mMinDistanceMm); + dest.writeInt(mMinDistanceMmSet ? 1 : 0); + dest.writeInt(mMaxDistanceMm); + dest.writeInt(mMaxDistanceMmSet ? 1 : 0); } public static final Creator<SubscribeConfig> CREATOR = new Creator<SubscribeConfig>() { @@ -134,9 +160,14 @@ public final class SubscribeConfig implements Parcelable { int subscribeType = in.readInt(); int ttlSec = in.readInt(); boolean enableTerminateNotification = in.readInt() != 0; - - return new SubscribeConfig(serviceName, ssi, matchFilter, subscribeType, - ttlSec, enableTerminateNotification); + int minDistanceMm = in.readInt(); + boolean minDistanceMmSet = in.readInt() != 0; + int maxDistanceMm = in.readInt(); + boolean maxDistanceMmSet = in.readInt() != 0; + + return new SubscribeConfig(serviceName, ssi, matchFilter, subscribeType, ttlSec, + enableTerminateNotification, minDistanceMmSet, minDistanceMm, maxDistanceMmSet, + maxDistanceMm); } }; @@ -152,23 +183,37 @@ public final class SubscribeConfig implements Parcelable { SubscribeConfig lhs = (SubscribeConfig) o; - return Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals(mServiceSpecificInfo, - lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter, lhs.mMatchFilter) - && mSubscribeType == lhs.mSubscribeType - && mTtlSec == lhs.mTtlSec - && mEnableTerminateNotification == lhs.mEnableTerminateNotification; + if (!(Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals( + mServiceSpecificInfo, lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter, + lhs.mMatchFilter) && mSubscribeType == lhs.mSubscribeType && mTtlSec == lhs.mTtlSec + && mEnableTerminateNotification == lhs.mEnableTerminateNotification + && mMinDistanceMmSet == lhs.mMinDistanceMmSet + && mMaxDistanceMmSet == lhs.mMaxDistanceMmSet)) { + return false; + } + + if (mMinDistanceMmSet && mMinDistanceMm != lhs.mMinDistanceMm) { + return false; + } + + if (mMaxDistanceMmSet && mMaxDistanceMm != lhs.mMaxDistanceMm) { + return false; + } + + return true; } @Override public int hashCode() { - int result = 17; + int result = Objects.hash(mServiceName, mServiceSpecificInfo, mMatchFilter, mSubscribeType, + mTtlSec, mEnableTerminateNotification, mMinDistanceMmSet, mMaxDistanceMmSet); - result = 31 * result + Arrays.hashCode(mServiceName); - result = 31 * result + Arrays.hashCode(mServiceSpecificInfo); - result = 31 * result + Arrays.hashCode(mMatchFilter); - result = 31 * result + mSubscribeType; - result = 31 * result + mTtlSec; - result = 31 * result + (mEnableTerminateNotification ? 1 : 0); + if (mMinDistanceMmSet) { + result = Objects.hash(result, mMinDistanceMm); + } + if (mMaxDistanceMmSet) { + result = Objects.hash(result, mMaxDistanceMm); + } return result; } @@ -213,6 +258,17 @@ public final class SubscribeConfig implements Parcelable { "Match filter longer than supported by device characteristics"); } } + + if (mMinDistanceMmSet && mMinDistanceMm < 0) { + throw new IllegalArgumentException("Minimum distance must be non-negative"); + } + if (mMaxDistanceMmSet && mMaxDistanceMm < 0) { + throw new IllegalArgumentException("Maximum distance must be non-negative"); + } + if (mMinDistanceMmSet && mMaxDistanceMmSet && mMaxDistanceMm <= mMinDistanceMm) { + throw new IllegalArgumentException( + "Maximum distance must be greater than minimum distance"); + } } /** @@ -225,6 +281,10 @@ public final class SubscribeConfig implements Parcelable { private int mSubscribeType = SUBSCRIBE_TYPE_PASSIVE; private int mTtlSec = 0; private boolean mEnableTerminateNotification = true; + private boolean mMinDistanceMmSet = false; + private int mMinDistanceMm; + private boolean mMaxDistanceMmSet = false; + private int mMaxDistanceMm; /** * Specify the service name of the subscribe session. The actual on-air @@ -350,13 +410,69 @@ public final class SubscribeConfig implements Parcelable { } /** + * Configure the minimum distance to a discovered publisher at which to trigger a discovery + * notification. I.e. discovery will only be triggered if we've found a matching publisher + * (based on the other criteria in this configuration) <b>and</b> the distance to the + * publisher is > the value specified in this API. + * <p> + * Can be used in conjunction with {@link #setMaxDistanceMm(int)} to specify a geo-fence, + * i.e. discovery with min < distance < max. + * <p> + * If this API is called, the subscriber requires ranging. In such a case, the publisher + * peer must enable ranging using + * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. Otherwise discovery will + * never be triggered. + * + * @param minDistanceMm Minimum distance, in mm, to the publisher above which to trigger + * discovery. + * + * @return The builder to facilitate chaining + * {@code builder.setXXX(..).setXXX(..)}. + * + * @hide + */ + public Builder setMinDistanceMm(int minDistanceMm) { + mMinDistanceMm = minDistanceMm; + mMinDistanceMmSet = true; + return this; + } + + /** + * Configure the maximum distance to a discovered publisher at which to trigger a discovery + * notification. I.e. discovery will only be triggered if we've found a matching publisher + * (based on the other criteria in this configuration) <b>and</b> the distance to the + * publisher is < the value specified in this API. + * <p> + * Can be used in conjunction with {@link #setMinDistanceMm(int)} to specify a geo-fence, + * i.e. discovery with min < distance < max. + * <p> + * If this API is called, the subscriber requires ranging. In such a case, the publisher + * peer must enable ranging using + * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. Otherwise discovery will + * never be triggered. + * + * @param maxDistanceMm Maximum distance, in mm, to the publisher below which to trigger + * discovery. + * + * @return The builder to facilitate chaining + * {@code builder.setXXX(..).setXXX(..)}. + * + * @hide + */ + public Builder setMaxDistanceMm(int maxDistanceMm) { + mMaxDistanceMm = maxDistanceMm; + mMaxDistanceMmSet = true; + return this; + } + + /** * Build {@link SubscribeConfig} given the current requests made on the * builder. */ public SubscribeConfig build() { return new SubscribeConfig(mServiceName, mServiceSpecificInfo, mMatchFilter, - mSubscribeType, mTtlSec, - mEnableTerminateNotification); + mSubscribeType, mTtlSec, mEnableTerminateNotification, + mMinDistanceMmSet, mMinDistanceMm, mMaxDistanceMmSet, mMaxDistanceMm); } } } diff --git a/android/net/wifi/aware/WifiAwareManager.java b/android/net/wifi/aware/WifiAwareManager.java index ed6804d5..166da48e 100644 --- a/android/net/wifi/aware/WifiAwareManager.java +++ b/android/net/wifi/aware/WifiAwareManager.java @@ -20,8 +20,8 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; -import android.annotation.SystemService; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SystemService; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkRequest; @@ -564,6 +564,7 @@ public class WifiAwareManager { private static final int CALLBACK_MESSAGE_SEND_SUCCESS = 5; private static final int CALLBACK_MESSAGE_SEND_FAIL = 6; private static final int CALLBACK_MESSAGE_RECEIVED = 7; + private static final int CALLBACK_MATCH_WITH_DISTANCE = 8; private static final String MESSAGE_BUNDLE_KEY_MESSAGE = "message"; private static final String MESSAGE_BUNDLE_KEY_MESSAGE2 = "message2"; @@ -618,7 +619,9 @@ public class WifiAwareManager { case CALLBACK_SESSION_TERMINATED: onProxySessionTerminated(msg.arg1); break; - case CALLBACK_MATCH: { + case CALLBACK_MATCH: + case CALLBACK_MATCH_WITH_DISTANCE: + { List<byte[]> matchFilter = null; byte[] arg = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2); try { @@ -629,9 +632,16 @@ public class WifiAwareManager { + new String(HexEncoding.encode(arg)) + "' - cannot be parsed: e=" + e); } - mOriginalCallback.onServiceDiscovered(new PeerHandle(msg.arg1), - msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE), - matchFilter); + if (msg.what == CALLBACK_MATCH) { + mOriginalCallback.onServiceDiscovered(new PeerHandle(msg.arg1), + msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE), + matchFilter); + } else { + mOriginalCallback.onServiceDiscoveredWithinRange( + new PeerHandle(msg.arg1), + msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE), + matchFilter, msg.arg2); + } break; } case CALLBACK_MESSAGE_SEND_SUCCESS: @@ -684,21 +694,38 @@ public class WifiAwareManager { mHandler.sendMessage(msg); } - @Override - public void onMatch(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter) { - if (VDBG) Log.v(TAG, "onMatch: peerId=" + peerId); - + private void onMatchCommon(int messageType, int peerId, byte[] serviceSpecificInfo, + byte[] matchFilter, int distanceMm) { Bundle data = new Bundle(); data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, serviceSpecificInfo); data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2, matchFilter); - Message msg = mHandler.obtainMessage(CALLBACK_MATCH); + Message msg = mHandler.obtainMessage(messageType); msg.arg1 = peerId; + msg.arg2 = distanceMm; msg.setData(data); mHandler.sendMessage(msg); } @Override + public void onMatch(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter) { + if (VDBG) Log.v(TAG, "onMatch: peerId=" + peerId); + + onMatchCommon(CALLBACK_MATCH, peerId, serviceSpecificInfo, matchFilter, 0); + } + + @Override + public void onMatchWithDistance(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter, + int distanceMm) { + if (VDBG) { + Log.v(TAG, "onMatchWithDistance: peerId=" + peerId + ", distanceMm=" + distanceMm); + } + + onMatchCommon(CALLBACK_MATCH_WITH_DISTANCE, peerId, serviceSpecificInfo, matchFilter, + distanceMm); + } + + @Override public void onMessageSendSuccess(int messageId) { if (VDBG) Log.v(TAG, "onMessageSendSuccess"); diff --git a/android/net/wifi/hotspot2/ProvisioningCallback.java b/android/net/wifi/hotspot2/ProvisioningCallback.java new file mode 100644 index 00000000..8b86cdde --- /dev/null +++ b/android/net/wifi/hotspot2/ProvisioningCallback.java @@ -0,0 +1,59 @@ +/* + * 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.net.wifi.hotspot2; + +import android.os.Handler; + +/** + * Base class for provisioning callbacks. Should be extended by applications and set when calling + * {@link WifiManager#startSubscriptionProvisiong(OsuProvider, ProvisioningCallback, Handler)}. + * + * @hide + */ +public abstract class ProvisioningCallback { + + /** + * The reason code for Provisioning Failure due to connection failure to OSU AP. + * @hide + */ + public static final int OSU_FAILURE_AP_CONNECTION = 1; + + /** + * The status code for Provisioning flow to indicate connecting to OSU AP + * @hide + */ + public static final int OSU_STATUS_AP_CONNECTING = 1; + + /** + * The status code for Provisioning flow to indicate connected to OSU AP + * @hide + */ + public static final int OSU_STATUS_AP_CONNECTED = 2; + + /** + * Provisioning status for OSU failure + * @param status indicates error condition + */ + public abstract void onProvisioningFailure(int status); + + /** + * Provisioning status when OSU is in progress + * @param status indicates status of OSU flow + */ + public abstract void onProvisioningStatus(int status); +} + diff --git a/android/net/wifi/rtt/WifiRttManager.java b/android/net/wifi/rtt/WifiRttManager.java index 128d6c91..735e872e 100644 --- a/android/net/wifi/rtt/WifiRttManager.java +++ b/android/net/wifi/rtt/WifiRttManager.java @@ -44,7 +44,7 @@ import java.util.List; @SystemService(Context.WIFI_RTT2_SERVICE) public class WifiRttManager { private static final String TAG = "WifiRttManager"; - private static final boolean VDBG = true; + private static final boolean VDBG = false; private final Context mContext; private final IWifiRttManager mService; diff --git a/android/os/BatteryManager.java b/android/os/BatteryManager.java index 6e0f70c1..843bdb50 100644 --- a/android/os/BatteryManager.java +++ b/android/os/BatteryManager.java @@ -18,6 +18,7 @@ package android.os; import android.annotation.SystemService; import android.content.Context; +import android.content.Intent; import android.hardware.health.V1_0.Constants; import com.android.internal.app.IBatteryStats; @@ -56,6 +57,14 @@ public class BatteryManager { /** * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}: + * Boolean field indicating whether the battery is currently considered to be + * low, that is whether a {@link Intent#ACTION_BATTERY_LOW} broadcast + * has been sent. + */ + public static final String EXTRA_BATTERY_LOW = "battery_low"; + + /** + * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}: * integer containing the maximum battery level. */ public static final String EXTRA_SCALE = "scale"; diff --git a/android/os/BatteryStats.java b/android/os/BatteryStats.java index a8bd9403..811091e3 100644 --- a/android/os/BatteryStats.java +++ b/android/os/BatteryStats.java @@ -222,8 +222,11 @@ public abstract class BatteryStats implements Parcelable { * - Resource power manager (rpm) states [but screenOffRpm is disabled from working properly] * New in version 27: * - Always On Display (screen doze mode) time and power + * New in version 28: + * - Light/Deep Doze power + * - WiFi Multicast Wakelock statistics (count & duration) */ - static final int CHECKIN_VERSION = 27; + static final int CHECKIN_VERSION = 28; /** * Old version, we hit 9 and ran out of room, need to remove. @@ -311,6 +314,8 @@ public abstract class BatteryStats implements Parcelable { private static final String CAMERA_DATA = "cam"; private static final String VIDEO_DATA = "vid"; private static final String AUDIO_DATA = "aud"; + private static final String WIFI_MULTICAST_TOTAL_DATA = "wmct"; + private static final String WIFI_MULTICAST_DATA = "wmc"; public static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity"; @@ -514,6 +519,13 @@ public abstract class BatteryStats implements Parcelable { public abstract ArrayMap<String, ? extends Wakelock> getWakelockStats(); /** + * Returns the WiFi Multicast Wakelock statistics. + * + * @return a Timer Object for the per uid Multicast statistics. + */ + public abstract Timer getMulticastWakelockStats(); + + /** * Returns a mapping containing sync statistics. * * @return a Map from Strings to Timer objects. @@ -2696,6 +2708,18 @@ public abstract class BatteryStats implements Parcelable { public abstract long getUahDischarge(int which); /** + * @return the amount of battery discharge while the device is in light idle mode, measured in + * micro-Ampere-hours. + */ + public abstract long getUahDischargeLightDoze(int which); + + /** + * @return the amount of battery discharge while the device is in deep idle mode, measured in + * micro-Ampere-hours. + */ + public abstract long getUahDischargeDeepDoze(int which); + + /** * Returns the estimated real battery capacity, which may be less than the capacity * declared by the PowerProfile. * @return The estimated battery capacity in mAh. @@ -3327,6 +3351,8 @@ public abstract class BatteryStats implements Parcelable { final long dischargeCount = getUahDischarge(which); final long dischargeScreenOffCount = getUahDischargeScreenOff(which); final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which); + final long dischargeLightDozeCount = getUahDischargeLightDoze(which); + final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which); final StringBuilder sb = new StringBuilder(128); @@ -3347,13 +3373,16 @@ public abstract class BatteryStats implements Parcelable { screenDozeTime / 1000); - // Calculate wakelock times across all uids. + // Calculate both wakelock and wifi multicast wakelock times across all uids. long fullWakeLockTimeTotal = 0; long partialWakeLockTimeTotal = 0; + long multicastWakeLockTimeTotalMicros = 0; + int multicastWakeLockCountTotal = 0; for (int iu = 0; iu < NU; iu++) { final Uid u = uidStats.valueAt(iu); + // First calculating the wakelock stats final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); for (int iw=wakelocks.size()-1; iw>=0; iw--) { @@ -3371,6 +3400,13 @@ public abstract class BatteryStats implements Parcelable { rawRealtime, which); } } + + // Now calculating the wifi multicast wakelock stats + final Timer mcTimer = u.getMulticastWakelockStats(); + if (mcTimer != null) { + multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which); + multicastWakeLockCountTotal += mcTimer.getCountLocked(which); + } } // Dump network stats @@ -3486,6 +3522,11 @@ public abstract class BatteryStats implements Parcelable { } dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_COUNT_DATA, args); + // Dump Multicast total stats + dumpLine(pw, 0 /* uid */, category, WIFI_MULTICAST_TOTAL_DATA, + multicastWakeLockTimeTotalMicros / 1000, + multicastWakeLockCountTotal); + if (which == STATS_SINCE_UNPLUGGED) { dumpLine(pw, 0 /* uid */, category, BATTERY_LEVEL_DATA, getDischargeStartLevel(), getDischargeCurrentLevel()); @@ -3497,14 +3538,16 @@ public abstract class BatteryStats implements Parcelable { getDischargeStartLevel()-getDischargeCurrentLevel(), getDischargeAmountScreenOn(), getDischargeAmountScreenOff(), dischargeCount / 1000, dischargeScreenOffCount / 1000, - getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000); + getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000, + dischargeLightDozeCount / 1000, dischargeDeepDozeCount / 1000); } else { dumpLine(pw, 0 /* uid */, category, BATTERY_DISCHARGE_DATA, getLowDischargeAmountSinceCharge(), getHighDischargeAmountSinceCharge(), getDischargeAmountScreenOnSinceCharge(), getDischargeAmountScreenOffSinceCharge(), dischargeCount / 1000, dischargeScreenOffCount / 1000, - getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000); + getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000, + dischargeLightDozeCount / 1000, dischargeDeepDozeCount / 1000); } if (reqUid < 0) { @@ -3810,6 +3853,18 @@ public abstract class BatteryStats implements Parcelable { } } + // WiFi Multicast Wakelock Statistics + final Timer mcTimer = u.getMulticastWakelockStats(); + if (mcTimer != null) { + final long totalMcWakelockTimeMs = + mcTimer.getTotalTimeLocked(rawRealtime, which) / 1000 ; + final int countMcWakelock = mcTimer.getCountLocked(which); + if(totalMcWakelockTimeMs > 0) { + dumpLine(pw, uid, category, WIFI_MULTICAST_DATA, + totalMcWakelockTimeMs, countMcWakelock); + } + } + final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats(); for (int isy=syncs.size()-1; isy>=0; isy--) { final Timer timer = syncs.valueAt(isy); @@ -4169,6 +4224,26 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } + final long dischargeLightDozeCount = getUahDischargeLightDoze(which); + if (dischargeLightDozeCount >= 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Device light doze discharge: "); + sb.append(BatteryStatsHelper.makemAh(dischargeLightDozeCount / 1000.0)); + sb.append(" mAh"); + pw.println(sb.toString()); + } + + final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which); + if (dischargeDeepDozeCount >= 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Device deep doze discharge: "); + sb.append(BatteryStatsHelper.makemAh(dischargeDeepDozeCount / 1000.0)); + sb.append(" mAh"); + pw.println(sb.toString()); + } + pw.print(" Start clock time: "); pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", getStartClockTime()).toString()); @@ -4289,15 +4364,18 @@ public abstract class BatteryStats implements Parcelable { pw.print(" Connectivity changes: "); pw.println(connChanges); } - // Calculate wakelock times across all uids. + // Calculate both wakelock and wifi multicast wakelock times across all uids. long fullWakeLockTimeTotalMicros = 0; long partialWakeLockTimeTotalMicros = 0; + long multicastWakeLockTimeTotalMicros = 0; + int multicastWakeLockCountTotal = 0; final ArrayList<TimerEntry> timers = new ArrayList<>(); for (int iu = 0; iu < NU; iu++) { final Uid u = uidStats.valueAt(iu); + // First calculate wakelock statistics final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); for (int iw=wakelocks.size()-1; iw>=0; iw--) { @@ -4325,6 +4403,13 @@ public abstract class BatteryStats implements Parcelable { } } } + + // Next calculate wifi multicast wakelock statistics + final Timer mcTimer = u.getMulticastWakelockStats(); + if (mcTimer != null) { + multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which); + multicastWakeLockCountTotal += mcTimer.getCountLocked(which); + } } final long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); @@ -4354,6 +4439,20 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } + if (multicastWakeLockTimeTotalMicros != 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Total WiFi Multicast wakelock Count: "); + sb.append(multicastWakeLockCountTotal); + pw.println(sb.toString()); + + sb.setLength(0); + sb.append(prefix); + sb.append(" Total WiFi Multicast wakelock time: "); + formatTimeMsNoSpace(sb, (multicastWakeLockTimeTotalMicros + 500) / 1000); + pw.println(sb.toString()); + } + pw.println(""); pw.print(prefix); sb.setLength(0); @@ -5271,6 +5370,24 @@ public abstract class BatteryStats implements Parcelable { } } + // Calculate multicast wakelock stats + final Timer mcTimer = u.getMulticastWakelockStats(); + if (mcTimer != null) { + final long multicastWakeLockTimeMicros = mcTimer.getTotalTimeLocked(rawRealtime, which); + final int multicastWakeLockCount = mcTimer.getCountLocked(which); + + if (multicastWakeLockTimeMicros > 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" WiFi Multicast Wakelock"); + sb.append(" count = "); + sb.append(multicastWakeLockCount); + sb.append(" time = "); + formatTimeMsNoSpace(sb, (multicastWakeLockTimeMicros + 500) / 1000); + pw.println(sb.toString()); + } + } + final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats(); for (int isy=syncs.size()-1; isy>=0; isy--) { final Timer timer = syncs.valueAt(isy); @@ -7047,6 +7164,10 @@ public abstract class BatteryStats implements Parcelable { proto.end(wToken); } + // Wifi Multicast Wakelock (WIFI_MULTICAST_WAKELOCK_DATA) + dumpTimer(proto, UidProto.WIFI_MULTICAST_WAKELOCK, u.getMulticastWakelockStats(), + rawRealtimeUs, which); + // Wakeup alarms (WAKEUP_ALARM_DATA) for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) { final Uid.Pkg ps = packageStats.valueAt(ipkg); @@ -7131,6 +7252,10 @@ public abstract class BatteryStats implements Parcelable { getUahDischargeScreenOff(which) / 1000); proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE, getUahDischargeScreenDoze(which) / 1000); + proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_LIGHT_DOZE, + getUahDischargeLightDoze(which) / 1000); + proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_DEEP_DOZE, + getUahDischargeDeepDoze(which) / 1000); proto.end(bdToken); // Time remaining @@ -7299,6 +7424,30 @@ public abstract class BatteryStats implements Parcelable { getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT)); proto.end(mToken); + // Wifi multicast wakelock total stats (WIFI_MULTICAST_WAKELOCK_TOTAL_DATA) + // Calculate multicast wakelock stats across all uids. + long multicastWakeLockTimeTotalUs = 0; + int multicastWakeLockCountTotal = 0; + + for (int iu = 0; iu < uidStats.size(); iu++) { + final Uid u = uidStats.valueAt(iu); + + final Timer mcTimer = u.getMulticastWakelockStats(); + + if (mcTimer != null) { + multicastWakeLockTimeTotalUs += + mcTimer.getTotalTimeLocked(rawRealtimeUs, which); + multicastWakeLockCountTotal += mcTimer.getCountLocked(which); + } + } + + final long wmctToken = proto.start(SystemProto.WIFI_MULTICAST_WAKELOCK_TOTAL); + proto.write(SystemProto.WifiMulticastWakelockTotal.DURATION_MS, + multicastWakeLockTimeTotalUs / 1000); + proto.write(SystemProto.WifiMulticastWakelockTotal.COUNT, + multicastWakeLockCountTotal); + proto.end(wmctToken); + // Power use item (POWER_USE_ITEM_DATA) final List<BatterySipper> sippers = helper.getUsageList(); if (sippers != null) { diff --git a/android/os/Build.java b/android/os/Build.java index 935f5f3b..02c7bd64 100644 --- a/android/os/Build.java +++ b/android/os/Build.java @@ -19,9 +19,11 @@ package android.os; import android.Manifest; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.app.Application; import android.content.Context; import android.text.TextUtils; import android.util.Slog; +import android.view.View; import com.android.internal.telephony.TelephonyProperties; @@ -103,7 +105,7 @@ public class Build { /** * A hardware serial number, if available. Alphanumeric only, case-insensitive. - * For apps targeting SDK higher than {@link Build.VERSION_CODES#N_MR1} this + * For apps targeting SDK higher than {@link Build.VERSION_CODES#O_MR1} this * field is set to {@link Build#UNKNOWN}. * * @deprecated Use {@link #getSerial()} instead. @@ -761,6 +763,80 @@ public class Build { * <p>Applications targeting this or a later release will get these * new changes in behavior:</p> * <ul> + * <li><a href="{@docRoot}about/versions/oreo/background.html">Background execution limits</a> + * are applied to the application.</li> + * <li>The behavior of AccountManager's + * {@link android.accounts.AccountManager#getAccountsByType}, + * {@link android.accounts.AccountManager#getAccountsByTypeAndFeatures}, and + * {@link android.accounts.AccountManager#hasFeatures} has changed as documented there.</li> + * <li>{@link android.app.ActivityManager.RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE_PRE_26} + * is now returned as + * {@link android.app.ActivityManager.RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}.</li> + * <li>The {@link android.app.NotificationManager} now requires the use of notification + * channels.</li> + * <li>Changes to the strict mode that are set in + * {@link Application#onCreate Application.onCreate} will no longer be clobbered after + * that function returns.</li> + * <li>A shared library apk with native code will have that native code included in + * the library path of its clients.</li> + * <li>{@link android.content.Context#getSharedPreferences Context.getSharedPreferences} + * in credential encrypted storage will throw an exception before the user is unlocked.</li> + * <li>Attempting to retrieve a {@link Context#FINGERPRINT_SERVICE} on a device that + * does not support that feature will now throw a runtime exception.</li> + * <li>{@link android.app.Fragment} will stop any active view animations when + * the fragment is stopped.</li> + * <li>Some compatibility code in Resources that attempts to use the default Theme + * the app may be using will be turned off, requiring the app to explicitly request + * resources with the right theme.</li> + * <li>{@link android.content.ContentResolver#notifyChange ContentResolver.notifyChange} and + * {@link android.content.ContentResolver#registerContentObserver + * ContentResolver.registerContentObserver} + * will throw a SecurityException if the caller does not have permission to access + * the provider (or the provider doesn't exit); otherwise the call will be silently + * ignored.</li> + * <li>{@link android.hardware.camera2.CameraDevice#createCaptureRequest + * CameraDevice.createCaptureRequest} will enable + * {@link android.hardware.camera2.CaptureRequest#CONTROL_ENABLE_ZSL} by default for + * still image capture.</li> + * <li>WallpaperManager's {@link android.app.WallpaperManager#getWallpaperFile}, + * {@link android.app.WallpaperManager#getDrawable}, + * {@link android.app.WallpaperManager#getFastDrawable}, + * {@link android.app.WallpaperManager#peekDrawable}, and + * {@link android.app.WallpaperManager#peekFastDrawable} will throw an exception + * if you can not access the wallpaper.</li> + * <li>The behavior of + * {@link android.hardware.usb.UsbDeviceConnection#requestWait UsbDeviceConnection.requestWait} + * is modified as per the documentation there.</li> + * <li>{@link StrictMode.VmPolicy.Builder#detectAll StrictMode.VmPolicy.Builder.detectAll} + * will also enable {@link StrictMode.VmPolicy.Builder#detectContentUriWithoutPermission} + * and {@link StrictMode.VmPolicy.Builder#detectUntaggedSockets}.</li> + * <li>{@link StrictMode.ThreadPolicy.Builder#detectAll StrictMode.ThreadPolicy.Builder.detectAll} + * will also enable {@link StrictMode.ThreadPolicy.Builder#detectUnbufferedIo}.</li> + * <li>{@link android.provider.DocumentsContract}'s various methods will throw failure + * exceptions back to the caller instead of returning null. + * <li>{@link View#hasFocusable View.hasFocusable} now includes auto-focusable views.</li> + * <li>{@link android.view.SurfaceView} will no longer always change the underlying + * Surface object when something about it changes; apps need to look at the current + * state of the object to determine which things they are interested in have changed.</li> + * <li>{@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} must be + * used for overlay windows, other system overlay window types are not allowed.</li> + * <li>{@link android.view.ViewTreeObserver#addOnDrawListener + * ViewTreeObserver.addOnDrawListener} will throw an exception if called from within + * onDraw.</li> + * <li>{@link android.graphics.Canvas#setBitmap Canvas.setBitmap} will no longer preserve + * the current matrix and clip stack of the canvas.</li> + * <li>{@link android.widget.ListPopupWindow#setHeight ListPopupWindow.setHeight} + * will throw an exception if a negative height is supplied.</li> + * <li>{@link android.widget.TextView} will use internationalized input for numbers, + * dates, and times.</li> + * <li>{@link android.widget.Toast} must be used for showing toast windows; the toast + * window type can not be directly used.</li> + * <li>{@link android.net.wifi.WifiManager#getConnectionInfo WifiManager.getConnectionInfo} + * requires that the caller hold the location permission to return BSSID/SSID</li> + * <li>{@link android.net.wifi.p2p.WifiP2pManager#requestPeers WifiP2pManager.requestPeers} + * requires the caller hold the location permission.</li> + * <li>{@link android.R.attr#maxAspectRatio} defaults to 0, meaning there is no restriction + * on the app's maximum aspect ratio (so it can be stretched to fill larger screens).</li> * <li>{@link android.R.attr#focusable} defaults to a new state ({@code auto}) where it will * inherit the value of {@link android.R.attr#clickable} unless explicitly overridden.</li> * <li>A default theme-appropriate focus-state highlight will be supplied to all Views @@ -772,6 +848,15 @@ public class Build { /** * O MR1. + * + * <p>Applications targeting this or a later release will get these + * new changes in behavior:</p> + * <ul> + * <li>Apps exporting and linking to apk shared libraries must explicitly + * enumerate all signing certificates in a consistent order.</li> + * <li>{@link android.R.attr#screenOrientation} can not be used to request a fixed + * orientation if the associated activity is not fullscreen and opaque.</li> + * </ul> */ public static final int O_MR1 = 27; diff --git a/android/os/GraphicsEnvironment.java b/android/os/GraphicsEnvironment.java index 07c60551..f2e0bddb 100644 --- a/android/os/GraphicsEnvironment.java +++ b/android/os/GraphicsEnvironment.java @@ -22,6 +22,7 @@ import android.content.pm.PackageManager; import android.opengl.EGL14; import android.os.Build; import android.os.SystemProperties; +import android.provider.Settings; import android.util.Log; import dalvik.system.VMRuntime; @@ -29,18 +30,110 @@ import dalvik.system.VMRuntime; import java.io.File; /** @hide */ -public final class GraphicsEnvironment { +public class GraphicsEnvironment { + + private static final GraphicsEnvironment sInstance = new GraphicsEnvironment(); + + /** + * Returns the shared {@link GraphicsEnvironment} instance. + */ + public static GraphicsEnvironment getInstance() { + return sInstance; + } private static final boolean DEBUG = false; private static final String TAG = "GraphicsEnvironment"; private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0"; + private ClassLoader mClassLoader; + private String mLayerPath; + private String mDebugLayerPath; + + /** + * Set up GraphicsEnvironment + */ + public void setup(Context context) { + setupGpuLayers(context); + chooseDriver(context); + } + + /** + * Check whether application is debuggable + */ + private static boolean isDebuggable(Context context) { + return (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) > 0; + } + + /** + * Store the layer paths available to the loader. + */ + public void setLayerPaths(ClassLoader classLoader, + String layerPath, + String debugLayerPath) { + // We have to store these in the class because they are set up before we + // have access to the Context to properly set up GraphicsEnvironment + mClassLoader = classLoader; + mLayerPath = layerPath; + mDebugLayerPath = debugLayerPath; + } + + /** + * Set up layer search paths for all apps + * If debuggable, check for additional debug settings + */ + private void setupGpuLayers(Context context) { + + String layerPaths = ""; + + // Only enable additional debug functionality if the following conditions are met: + // 1. App is debuggable + // 2. ENABLE_GPU_DEBUG_LAYERS is true + // 3. Package name is equal to GPU_DEBUG_APP + + if (isDebuggable(context)) { + + int enable = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0); + + if (enable != 0) { + + String gpuDebugApp = Settings.Global.getString(context.getContentResolver(), + Settings.Global.GPU_DEBUG_APP); + + String packageName = context.getPackageName(); + + if ((gpuDebugApp != null && packageName != null) + && (!gpuDebugApp.isEmpty() && !packageName.isEmpty()) + && gpuDebugApp.equals(packageName)) { + Log.i(TAG, "GPU debug layers enabled for " + packageName); + + // Prepend the debug layer path as a searchable path. + // This will ensure debug layers added will take precedence over + // the layers specified by the app. + layerPaths = mDebugLayerPath + ":"; + + String layers = Settings.Global.getString(context.getContentResolver(), + Settings.Global.GPU_DEBUG_LAYERS); + + Log.i(TAG, "Debug layer list: " + layers); + if (layers != null && !layers.isEmpty()) { + setDebugLayers(layers); + } + } + } + + } + + // Include the app's lib directory in all cases + layerPaths += mLayerPath; + + setLayerPaths(mClassLoader, layerPaths); + } + /** * Choose whether the current process should use the builtin or an updated driver. - * - * @hide */ - public static void chooseDriver(Context context) { + private static void chooseDriver(Context context) { String driverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER); if (driverPackageName == null || driverPackageName.isEmpty()) { return; @@ -99,8 +192,6 @@ public final class GraphicsEnvironment { * on a separate thread, it can usually be finished well before the UI is ready to be drawn. * * Should only be called after chooseDriver(). - * - * @hide */ public static void earlyInitEGL() { Thread eglInitThread = new Thread( @@ -124,6 +215,7 @@ public final class GraphicsEnvironment { return null; } + private static native void setLayerPaths(ClassLoader classLoader, String layerPaths); + private static native void setDebugLayers(String layers); private static native void setDriverPath(String path); - } diff --git a/android/os/HidlSupport.java b/android/os/HidlSupport.java index 3544ea1e..a080c8dc 100644 --- a/android/os/HidlSupport.java +++ b/android/os/HidlSupport.java @@ -179,4 +179,9 @@ public class HidlSupport { } return Objects.equals(lft.asBinder(), ((IHwInterface) rgt).asBinder()); } + + /** + * Return PID of process if sharable to clients. + */ + public static native int getPidIfSharable(); } diff --git a/android/os/Parcel.java b/android/os/Parcel.java index 10adb5a6..62bb3854 100644 --- a/android/os/Parcel.java +++ b/android/os/Parcel.java @@ -20,6 +20,7 @@ import android.annotation.Nullable; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.ExceptionUtils; import android.util.Log; import android.util.Size; import android.util.SizeF; @@ -191,6 +192,7 @@ import java.util.Set; * {@link #readSparseArray(ClassLoader)}. */ public final class Parcel { + private static final boolean DEBUG_RECYCLE = false; private static final boolean DEBUG_ARRAY_MAP = false; private static final String TAG = "Parcel"; @@ -209,6 +211,12 @@ public final class Parcel { private RuntimeException mStack; + /** + * Whether or not to parcel the stack trace of an exception. This has a performance + * impact, so should only be included in specific processes and only on debug builds. + */ + private static boolean sParcelExceptionStackTrace; + private static final int POOL_SIZE = 6; private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE]; private static final Parcel[] sHolderPool = new Parcel[POOL_SIZE]; @@ -325,6 +333,11 @@ public final class Parcel { private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName); private static native void nativeEnforceInterface(long nativePtr, String interfaceName); + /** Last time exception with a stack trace was written */ + private static volatile long sLastWriteExceptionStackTrace; + /** Used for throttling of writing stack trace, which is costly */ + private static final int WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS = 1000; + @CriticalNative private static native long nativeGetBlobAshmemSize(long nativePtr); @@ -1696,6 +1709,11 @@ public final class Parcel { } } + /** @hide For debugging purposes */ + public static void setStackTraceParceling(boolean enabled) { + sParcelExceptionStackTrace = enabled; + } + /** * Special function for writing an exception result at the header of * a parcel, to be used when returning an exception from a transaction. @@ -1753,6 +1771,27 @@ public final class Parcel { throw new RuntimeException(e); } writeString(e.getMessage()); + final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0; + if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace + > WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) { + sLastWriteExceptionStackTrace = timeNow; + final int sizePosition = dataPosition(); + writeInt(0); // Header size will be filled in later + StackTraceElement[] stackTrace = e.getStackTrace(); + final int truncatedSize = Math.min(stackTrace.length, 5); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < truncatedSize; i++) { + sb.append("\tat ").append(stackTrace[i]).append('\n'); + } + writeString(sb.toString()); + final int payloadPosition = dataPosition(); + setDataPosition(sizePosition); + // Write stack trace header size. Used in native side to skip the header + writeInt(payloadPosition - sizePosition); + setDataPosition(payloadPosition); + } else { + writeInt(0); + } switch (code) { case EX_SERVICE_SPECIFIC: writeInt(((ServiceSpecificException) e).errorCode); @@ -1818,7 +1857,26 @@ public final class Parcel { int code = readExceptionCode(); if (code != 0) { String msg = readString(); - readException(code, msg); + String remoteStackTrace = null; + final int remoteStackPayloadSize = readInt(); + if (remoteStackPayloadSize > 0) { + remoteStackTrace = readString(); + } + Exception e = createException(code, msg); + // Attach remote stack trace if availalble + if (remoteStackTrace != null) { + RemoteException cause = new RemoteException( + "Remote stack trace:\n" + remoteStackTrace, null, false, false); + try { + Throwable rootCause = ExceptionUtils.getRootCause(e); + if (rootCause != null) { + rootCause.initCause(cause); + } + } catch (RuntimeException ex) { + Log.e(TAG, "Cannot set cause " + cause + " for " + e, ex); + } + } + SneakyThrow.sneakyThrow(e); } } @@ -1863,32 +1921,41 @@ public final class Parcel { * @param msg The exception message. */ public final void readException(int code, String msg) { + SneakyThrow.sneakyThrow(createException(code, msg)); + } + + /** + * Creates an exception with the given message. + * + * @param code Used to determine which exception class to throw. + * @param msg The exception message. + */ + private Exception createException(int code, String msg) { switch (code) { case EX_PARCELABLE: if (readInt() > 0) { - SneakyThrow.sneakyThrow( - (Exception) readParcelable(Parcelable.class.getClassLoader())); + return (Exception) readParcelable(Parcelable.class.getClassLoader()); } else { - throw new RuntimeException(msg + " [missing Parcelable]"); + return new RuntimeException(msg + " [missing Parcelable]"); } case EX_SECURITY: - throw new SecurityException(msg); + return new SecurityException(msg); case EX_BAD_PARCELABLE: - throw new BadParcelableException(msg); + return new BadParcelableException(msg); case EX_ILLEGAL_ARGUMENT: - throw new IllegalArgumentException(msg); + return new IllegalArgumentException(msg); case EX_NULL_POINTER: - throw new NullPointerException(msg); + return new NullPointerException(msg); case EX_ILLEGAL_STATE: - throw new IllegalStateException(msg); + return new IllegalStateException(msg); case EX_NETWORK_MAIN_THREAD: - throw new NetworkOnMainThreadException(); + return new NetworkOnMainThreadException(); case EX_UNSUPPORTED_OPERATION: - throw new UnsupportedOperationException(msg); + return new UnsupportedOperationException(msg); case EX_SERVICE_SPECIFIC: - throw new ServiceSpecificException(readInt(), msg); + return new ServiceSpecificException(readInt(), msg); } - throw new RuntimeException("Unknown exception code: " + code + return new RuntimeException("Unknown exception code: " + code + " msg " + msg); } diff --git a/android/os/ParcelPerfTest.java b/android/os/ParcelPerfTest.java index a92597f1..6e4c9c54 100644 --- a/android/os/ParcelPerfTest.java +++ b/android/os/ParcelPerfTest.java @@ -27,6 +27,10 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + @RunWith(AndroidJUnit4.class) @LargeTest public class ParcelPerfTest { @@ -167,4 +171,80 @@ public class ParcelPerfTest { Parcel.obtain().recycle(); } } + + @Test + public void timeWriteException() { + timeWriteException(false); + } + + @Test + public void timeWriteExceptionWithStackTraceParceling() { + timeWriteException(true); + } + + @Test + public void timeReadException() { + timeReadException(false); + } + + @Test + public void timeReadExceptionWithStackTraceParceling() { + timeReadException(true); + } + + private void timeWriteException(boolean enableParceling) { + if (enableParceling) { + Parcel.setStackTraceParceling(true); + } + try { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + Parcel p = Parcel.obtain(); + SecurityException e = new SecurityException("TestMessage"); + while (state.keepRunning()) { + p.setDataPosition(0); + p.writeException(e); + } + } finally { + if (enableParceling) { + Parcel.setStackTraceParceling(false); + } + } + } + + private void timeReadException(boolean enableParceling) { + if (enableParceling) { + Parcel.setStackTraceParceling(true); + } + try { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + Parcel p = Parcel.obtain(); + String msg = "TestMessage"; + p.writeException(new SecurityException(msg)); + p.setDataPosition(0); + // First verify that remote cause is set (if parceling is enabled) + try { + p.readException(); + } catch (SecurityException e) { + assertEquals(e.getMessage(), msg); + if (enableParceling) { + assertTrue(e.getCause() instanceof RemoteException); + } else { + assertNull(e.getCause()); + } + } + + while (state.keepRunning()) { + p.setDataPosition(0); + try { + p.readException(); + } catch (SecurityException expected) { + } + } + } finally { + if (enableParceling) { + Parcel.setStackTraceParceling(false); + } + } + } + } diff --git a/android/os/PowerManager.java b/android/os/PowerManager.java index dd4825ef..068f5f7f 100644 --- a/android/os/PowerManager.java +++ b/android/os/PowerManager.java @@ -387,6 +387,12 @@ public final class PowerManager { public static final int GO_TO_SLEEP_REASON_SLEEP_BUTTON = 6; /** + * Go to sleep reason code: Going to sleep by request of an accessibility service + * @hide + */ + public static final int GO_TO_SLEEP_REASON_ACCESSIBILITY = 7; + + /** * Go to sleep flag: Skip dozing state and directly go to full sleep. * @hide */ @@ -527,8 +533,7 @@ public final class PowerManager { ServiceType.SOUND, ServiceType.BATTERY_STATS, ServiceType.DATA_SAVER, - ServiceType.FORCE_ALL_APPS_STANDBY_JOBS, - ServiceType.FORCE_ALL_APPS_STANDBY_ALARMS, + ServiceType.FORCE_ALL_APPS_STANDBY, ServiceType.OPTIONAL_SENSORS, }) public @interface ServiceType { @@ -545,14 +550,14 @@ public final class PowerManager { int DATA_SAVER = 10; /** - * Whether the job scheduler should force app standby on all apps on battery saver or not. + * Whether to enable force-app-standby on all apps or not. */ - int FORCE_ALL_APPS_STANDBY_JOBS = 11; + int FORCE_ALL_APPS_STANDBY = 11; /** - * Whether the alarm manager should force app standby on all apps on battery saver or not. + * Whether to enable background check on all apps or not. */ - int FORCE_ALL_APPS_STANDBY_ALARMS = 12; + int FORCE_BACKGROUND_CHECK = 12; /** * Whether to disable non-essential sensors. (e.g. edge sensors.) diff --git a/android/os/Process.java b/android/os/Process.java index b5d62e55..0874d93e 100644 --- a/android/os/Process.java +++ b/android/os/Process.java @@ -151,6 +151,9 @@ public class Process { */ public static final int OTA_UPDATE_UID = 1061; + /** {@hide} */ + public static final int NOBODY_UID = 9999; + /** * Defines the start of a range of UIDs (and GIDs), going from this * number to {@link #LAST_APPLICATION_UID} that are reserved for assigning diff --git a/android/os/RemoteException.java b/android/os/RemoteException.java index 6d25fc17..4e8b9716 100644 --- a/android/os/RemoteException.java +++ b/android/os/RemoteException.java @@ -30,6 +30,12 @@ public class RemoteException extends AndroidException { super(message); } + /** @hide */ + public RemoteException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + /** {@hide} */ public RuntimeException rethrowAsRuntimeException() { throw new RuntimeException(this); diff --git a/android/os/ShellCallback.java b/android/os/ShellCallback.java index ad9fbfbf..6a62424c 100644 --- a/android/os/ShellCallback.java +++ b/android/os/ShellCallback.java @@ -105,6 +105,9 @@ public class ShellCallback implements Parcelable { ShellCallback(Parcel in) { mLocal = false; mShellCallback = IShellCallback.Stub.asInterface(in.readStrongBinder()); + if (mShellCallback != null) { + Binder.allowBlocking(mShellCallback.asBinder()); + } } public static final Parcelable.Creator<ShellCallback> CREATOR diff --git a/android/os/ShellCommand.java b/android/os/ShellCommand.java index d75219fd..fa05a5e1 100644 --- a/android/os/ShellCommand.java +++ b/android/os/ShellCommand.java @@ -91,7 +91,13 @@ public abstract class ShellCommand { mCmd = cmd; mResultReceiver = resultReceiver; - if (DEBUG) Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget); + if (DEBUG) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget, here); + Slog.d(TAG, "Calling uid=" + Binder.getCallingUid() + + " pid=" + Binder.getCallingPid() + " ShellCallback=" + getShellCallback()); + } int res = -1; try { res = onCommand(mCmd); @@ -227,15 +233,19 @@ public abstract class ShellCommand { * @hide */ public ParcelFileDescriptor openFileForSystem(String path, String mode) { + if (DEBUG) Slog.d(TAG, "openFileForSystem: " + path + " mode=" + mode); try { ParcelFileDescriptor pfd = getShellCallback().openFile(path, "u:r:system_server:s0", mode); if (pfd != null) { + if (DEBUG) Slog.d(TAG, "Got file: " + pfd); return pfd; } } catch (RuntimeException e) { + if (DEBUG) Slog.d(TAG, "Failure opening file: " + e.getMessage()); getErrPrintWriter().println("Failure opening file: " + e.getMessage()); } + if (DEBUG) Slog.d(TAG, "Error: Unable to open file: " + path); getErrPrintWriter().println("Error: Unable to open file: " + path); getErrPrintWriter().println("Consider using a file under /data/local/tmp/"); return null; diff --git a/android/os/UEventObserver.java b/android/os/UEventObserver.java index 5c80ca65..69a39225 100644 --- a/android/os/UEventObserver.java +++ b/android/os/UEventObserver.java @@ -108,7 +108,7 @@ public abstract class UEventObserver { * UEventObserver after this call. Repeated calls have no effect. */ public final void stopObserving() { - final UEventThread t = getThread(); + final UEventThread t = peekThread(); if (t != null) { t.removeObserver(this); } diff --git a/android/os/storage/StorageManager.java b/android/os/storage/StorageManager.java index 0b007ddf..4796712f 100644 --- a/android/os/storage/StorageManager.java +++ b/android/os/storage/StorageManager.java @@ -1123,6 +1123,15 @@ public class StorageManager { return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()); } + /** {@hide} */ + public void mkdirs(File file) { + try { + mStorageManager.mkdirs(mContext.getOpPackageName(), file.getAbsolutePath()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** @removed */ public @NonNull StorageVolume[] getVolumeList() { return getVolumeList(mContext.getUserId(), 0); diff --git a/android/provider/SearchIndexableData.java b/android/provider/SearchIndexableData.java index 5e0a76de..a60be536 100644 --- a/android/provider/SearchIndexableData.java +++ b/android/provider/SearchIndexableData.java @@ -56,6 +56,8 @@ public abstract class SearchIndexableData { /** * The key for the data. This is application specific. Should be unique per data as the data * should be able to be retrieved by the key. + * <p/> + * This is required for indexing to work. */ public String key; diff --git a/android/provider/SearchIndexablesContract.java b/android/provider/SearchIndexablesContract.java index ff8b9dd7..adf437ce 100644 --- a/android/provider/SearchIndexablesContract.java +++ b/android/provider/SearchIndexablesContract.java @@ -62,11 +62,25 @@ public class SearchIndexablesContract { public static final String NON_INDEXABLES_KEYS = "non_indexables_key"; /** + * Site map pairs data key + * + * @hide + */ + public static final String SITE_MAP_PAIRS_KEYS = "site_map_pairs"; + + /** * ContentProvider path for non indexable data keys. */ public static final String NON_INDEXABLES_KEYS_PATH = SETTINGS + "/" + NON_INDEXABLES_KEYS; /** + * ContentProvider path for sitemap keys. + * + * @hide + */ + public static final String SITE_MAP_PAIRS_PATH = SETTINGS + "/" + SITE_MAP_PAIRS_KEYS; + + /** * Indexable xml resources columns. */ public static final String[] INDEXABLES_XML_RES_COLUMNS = new String[] { @@ -113,6 +127,18 @@ public class SearchIndexablesContract { }; /** + * Columns for site map queries. + * + * @hide + */ + public static final String[] SITE_MAP_COLUMNS = new String[] { + SiteMapColumns.PARENT_CLASS, + SiteMapColumns.PARENT_TITLE, + SiteMapColumns.CHILD_CLASS, + SiteMapColumns.CHILD_TITLE, + }; + + /** * Indexable raw data columns indices. */ public static final int COLUMN_INDEX_RAW_RANK = 0; @@ -169,6 +195,16 @@ public class SearchIndexablesContract { } /** + * @hide + */ + public static final class SiteMapColumns { + public static final String PARENT_CLASS = "parent_class"; + public static final String CHILD_CLASS = "child_class"; + public static final String PARENT_TITLE = "parent_title"; + public static final String CHILD_TITLE = "child_title"; + } + + /** * Constants related to a {@link SearchIndexableData}. * * This is the raw data that is stored into an Index. This is related to diff --git a/android/provider/SearchIndexablesProvider.java b/android/provider/SearchIndexablesProvider.java index 3120e543..138e77b6 100644 --- a/android/provider/SearchIndexablesProvider.java +++ b/android/provider/SearchIndexablesProvider.java @@ -72,6 +72,7 @@ public abstract class SearchIndexablesProvider extends ContentProvider { private static final int MATCH_RES_CODE = 1; private static final int MATCH_RAW_CODE = 2; private static final int MATCH_NON_INDEXABLE_KEYS_CODE = 3; + private static final int MATCH_SITE_MAP_PAIRS_CODE = 4; /** * Implementation is provided by the parent class. @@ -87,6 +88,8 @@ public abstract class SearchIndexablesProvider extends ContentProvider { MATCH_RAW_CODE); mMatcher.addURI(mAuthority, SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH, MATCH_NON_INDEXABLE_KEYS_CODE); + mMatcher.addURI(mAuthority, SearchIndexablesContract.SITE_MAP_PAIRS_PATH, + MATCH_SITE_MAP_PAIRS_CODE); // Sanity check our setup if (!info.exported) { @@ -112,6 +115,8 @@ public abstract class SearchIndexablesProvider extends ContentProvider { return queryRawData(null); case MATCH_NON_INDEXABLE_KEYS_CODE: return queryNonIndexableKeys(null); + case MATCH_SITE_MAP_PAIRS_CODE: + return querySiteMapPairs(); default: throw new UnsupportedOperationException("Unknown Uri " + uri); } @@ -150,6 +155,17 @@ public abstract class SearchIndexablesProvider extends ContentProvider { */ public abstract Cursor queryNonIndexableKeys(String[] projection); + /** + * Returns a {@link Cursor}, where rows are [parent class, child class] entries to form a site + * map. The list of pairs should be as complete as possible. + * + * @hide + */ + public Cursor querySiteMapPairs() { + // By default no-op. + return null; + } + @Override public String getType(Uri uri) { switch (mMatcher.match(uri)) { diff --git a/android/provider/Settings.java b/android/provider/Settings.java index 6decc305..1e0948a4 100644 --- a/android/provider/Settings.java +++ b/android/provider/Settings.java @@ -9379,6 +9379,9 @@ public final class Settings { /** {@hide} */ public static final String BLUETOOTH_PAN_PRIORITY_PREFIX = "bluetooth_pan_priority_"; + /** {@hide} */ + public static final String + BLUETOOTH_HEARING_AID_PRIORITY_PREFIX = "bluetooth_hearing_aid_priority_"; /** * Activity manager specific settings. @@ -9745,6 +9748,14 @@ public final class Settings { } /** + * Get the key that retrieves a bluetooth hearing aid priority. + * @hide + */ + public static final String getBluetoothHearingAidPriorityKey(String address) { + return BLUETOOTH_HEARING_AID_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); + } + + /** * Get the key that retrieves a bluetooth map priority. * @hide */ @@ -9854,6 +9865,27 @@ public final class Settings { public static final String WAIT_FOR_DEBUGGER = "wait_for_debugger"; /** + * Allow GPU debug layers? + * 0 = no + * 1 = yes + * @hide + */ + public static final String ENABLE_GPU_DEBUG_LAYERS = "enable_gpu_debug_layers"; + + /** + * App allowed to load GPU debug layers + * @hide + */ + public static final String GPU_DEBUG_APP = "gpu_debug_app"; + + /** + * Ordered GPU debug layer list + * i.e. <layer1>:<layer2>:...:<layerN> + * @hide + */ + public static final String GPU_DEBUG_LAYERS = "gpu_debug_layers"; + + /** * Control whether the process CPU usage meter should be shown. * * @deprecated This functionality is no longer available as of @@ -10424,6 +10456,15 @@ public final class Settings { "storage_settings_clobber_threshold"; /** + * If set to 1, {@link Secure#LOCATION_MODE} will be set to {@link Secure#LOCATION_MODE_OFF} + * temporarily for all users. + * + * @hide + */ + public static final String LOCATION_GLOBAL_KILL_SWITCH = + "location_global_kill_switch"; + + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. * diff --git a/android/security/KeyChain.java b/android/security/KeyChain.java index 3fe730fd..2daf733d 100644 --- a/android/security/KeyChain.java +++ b/android/security/KeyChain.java @@ -40,6 +40,7 @@ import android.security.keystore.KeyProperties; import java.io.ByteArrayInputStream; import java.io.Closeable; +import java.security.KeyPair; import java.security.Principal; import java.security.PrivateKey; import java.security.UnrecoverableKeyException; @@ -418,6 +419,18 @@ public final class KeyChain { @Nullable @WorkerThread public static PrivateKey getPrivateKey(@NonNull Context context, @NonNull String alias) throws KeyChainException, InterruptedException { + KeyPair keyPair = getKeyPair(context, alias); + if (keyPair != null) { + return keyPair.getPrivate(); + } + + return null; + } + + /** @hide */ + @Nullable @WorkerThread + public static KeyPair getKeyPair(@NonNull Context context, @NonNull String alias) + throws KeyChainException, InterruptedException { if (alias == null) { throw new NullPointerException("alias == null"); } @@ -439,7 +452,7 @@ public final class KeyChain { return null; } else { try { - return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore( + return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore( KeyStore.getInstance(), keyId, KeyStore.UID_SELF); } catch (RuntimeException | UnrecoverableKeyException e) { throw new KeyChainException(e); diff --git a/android/service/autofill/AutofillService.java b/android/service/autofill/AutofillService.java index 953501c7..cd362c71 100644 --- a/android/service/autofill/AutofillService.java +++ b/android/service/autofill/AutofillService.java @@ -438,8 +438,21 @@ import com.android.internal.os.SomeArgs; * AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString(); * * save(username, password); - * * </pre> + * + * + * <a name="Privacy"></a> + * <h3>Privacy</h3> + * + * <p>The {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} method is called + * without the user content. The Android system strips some properties of the + * {@link android.app.assist.AssistStructure.ViewNode view nodes} passed to these calls, but not all + * of them. For example, the data provided in the {@link android.view.ViewStructure.HtmlInfo} + * objects set by {@link android.webkit.WebView} is never stripped out. + * + * <p>Because this data could contain PII (Personally Identifiable Information, such as username or + * email address), the service should only use it locally (i.e., in the app's process) for + * heuristics purposes, but it should not be sent to external servers. */ public abstract class AutofillService extends Service { private static final String TAG = "AutofillService"; diff --git a/android/service/autofill/Dataset.java b/android/service/autofill/Dataset.java index 331130e7..cb20e71b 100644 --- a/android/service/autofill/Dataset.java +++ b/android/service/autofill/Dataset.java @@ -216,7 +216,12 @@ public final class Dataset implements Parcelable { } /** - * Requires a dataset authentication before autofilling the activity with this dataset. + * Triggers a custom UI before before autofilling the screen with the contents of this + * dataset. + * + * <p><b>Note:</b> Although the name of this method suggests that it should be used just for + * authentication flow, it can be used for other advanced flows; see {@link AutofillService} + * for examples. * * <p>This method is called when you need to provide an authentication * UI for the data set. For example, when a data set contains credit card information @@ -335,9 +340,11 @@ public final class Dataset implements Parcelable { /** * Sets the value of a field using an <a href="#Filtering">explicit filter</a>. * - * <p>This method is typically used when the dataset is not authenticated and the field - * value is not {@link AutofillValue#isText() text} but the service still wants to allow - * the user to filter it out. + * <p>This method is typically used when the dataset is authenticated and the service + * does not know its value but wants to hide the dataset after the user enters a minimum + * number of characters. For example, if the dataset represents a credit card number and the + * service does not want to show the "Tap to authenticate" message until the user tapped + * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}. * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. @@ -364,11 +371,11 @@ public final class Dataset implements Parcelable { * Sets the value of a field, using a custom {@link RemoteViews presentation} to * visualize it and a <a href="#Filtering">explicit filter</a>. * - * <p>Typically used to allow filtering on - * {@link Dataset.Builder#setAuthentication(IntentSender) authenticated datasets}. For - * example, if the dataset represents a credit card number and the service does not want to - * show the "Tap to authenticate" message until the user tapped 4 digits, in which case - * the filter would be {@code Pattern.compile("\\d.{4,}")}. + * <p>This method is typically used when the dataset is authenticated and the service + * does not know its value but wants to hide the dataset after the user enters a minimum + * number of characters. For example, if the dataset represents a credit card number and the + * service does not want to show the "Tap to authenticate" message until the user tapped + * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}. * * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. diff --git a/android/service/autofill/FillResponse.java b/android/service/autofill/FillResponse.java index 4e6a8845..84a0974d 100644 --- a/android/service/autofill/FillResponse.java +++ b/android/service/autofill/FillResponse.java @@ -180,8 +180,12 @@ public final class FillResponse implements Parcelable { private boolean mDestroyed; /** - * Requires a fill response authentication before autofilling the screen with - * any data set in this response. + * Triggers a custom UI before before autofilling the screen with any data set in this + * response. + * + * <p><b>Note:</b> Although the name of this method suggests that it should be used just for + * authentication flow, it can be used for other advanced flows; see {@link AutofillService} + * for examples. * * <p>This is typically useful when a user interaction is required to unlock their * data vault if you encrypt the data set labels and data set data. It is recommended @@ -319,6 +323,7 @@ public final class FillResponse implements Parcelable { */ public Builder setClientState(@Nullable Bundle clientState) { throwIfDestroyed(); + throwIfDisableAutofillCalled(); mClientState = clientState; return this; } @@ -385,8 +390,9 @@ public final class FillResponse implements Parcelable { * * @throws IllegalArgumentException if {@code duration} is not a positive number. * @throws IllegalStateException if either {@link #addDataset(Dataset)}, - * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, or - * {@link #setSaveInfo(SaveInfo)} was already called. + * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, + * {@link #setSaveInfo(SaveInfo)}, or {@link #setClientState(Bundle)} + * was already called. */ public Builder disableAutofill(long duration) { throwIfDestroyed(); @@ -394,7 +400,7 @@ public final class FillResponse implements Parcelable { throw new IllegalArgumentException("duration must be greater than 0"); } if (mAuthentication != null || mDatasets != null || mSaveInfo != null - || mFieldsDetection != null) { + || mFieldsDetection != null || mClientState != null) { throw new IllegalStateException("disableAutofill() must be the only method called"); } @@ -410,7 +416,8 @@ public final class FillResponse implements Parcelable { * <li>{@link #build()} was already called. * <li>No call was made to {@link #addDataset(Dataset)}, * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, - * {@link #setSaveInfo(SaveInfo)}, or {@link #disableAutofill(long)}. + * {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)}, + * or {@link #setClientState(Bundle)}. * </ol> * * @return A built response. @@ -418,10 +425,10 @@ public final class FillResponse implements Parcelable { public FillResponse build() { throwIfDestroyed(); if (mAuthentication == null && mDatasets == null && mSaveInfo == null - && mDisableDuration == 0 && mFieldsDetection == null) { + && mDisableDuration == 0 && mFieldsDetection == null && mClientState == null) { throw new IllegalStateException("need to provide: at least one DataSet, or a " + "SaveInfo, or an authentication with a presentation, " - + "or a FieldsDetection, or disable autofill"); + + "or a FieldsDetection, or a client state, or disable autofill"); } mDestroyed = true; return new FillResponse(this); diff --git a/android/service/autofill/InternalSanitizer.java b/android/service/autofill/InternalSanitizer.java index 95d2f660..d77e41e3 100644 --- a/android/service/autofill/InternalSanitizer.java +++ b/android/service/autofill/InternalSanitizer.java @@ -16,6 +16,7 @@ package android.service.autofill; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; import android.os.Parcelable; import android.view.autofill.AutofillValue; @@ -32,7 +33,11 @@ public abstract class InternalSanitizer implements Sanitizer, Parcelable { /** * Sanitizes an {@link AutofillValue}. * + * @return sanitized value or {@code null} if value could not be sanitized (for example: didn't + * match regex, it's an invalid type, regex failed, etc). + * * @hide */ + @Nullable public abstract AutofillValue sanitize(@NonNull AutofillValue value); } diff --git a/android/service/autofill/NegationValidator.java b/android/service/autofill/NegationValidator.java new file mode 100644 index 00000000..a963f9f9 --- /dev/null +++ b/android/service/autofill/NegationValidator.java @@ -0,0 +1,79 @@ +/* + * 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.service.autofill; + +import static android.view.autofill.Helper.sDebug; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * Validator used to implement a {@code NOT} logical operation. + * + * @hide + */ +final class NegationValidator extends InternalValidator { + @NonNull private final InternalValidator mValidator; + + NegationValidator(@NonNull InternalValidator validator) { + mValidator = Preconditions.checkNotNull(validator); + } + + @Override + public boolean isValid(@NonNull ValueFinder finder) { + return !mValidator.isValid(finder); + } + + ///////////////////////////////////// + // Object "contract" methods. // + ///////////////////////////////////// + @Override + public String toString() { + if (!sDebug) return super.toString(); + + return "NegationValidator: [validator=" + mValidator + "]"; + } + + ///////////////////////////////////// + // Parcelable "contract" methods. // + ///////////////////////////////////// + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mValidator, flags); + } + + public static final Parcelable.Creator<NegationValidator> CREATOR = + new Parcelable.Creator<NegationValidator>() { + @Override + public NegationValidator createFromParcel(Parcel parcel) { + return new NegationValidator(parcel.readParcelable(null)); + } + + @Override + public NegationValidator[] newArray(int size) { + return new NegationValidator[size]; + } + }; +} diff --git a/android/service/autofill/SaveCallback.java b/android/service/autofill/SaveCallback.java index 7207f1df..855981a5 100644 --- a/android/service/autofill/SaveCallback.java +++ b/android/service/autofill/SaveCallback.java @@ -16,9 +16,14 @@ package android.service.autofill; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Activity; +import android.content.IntentSender; import android.os.RemoteException; +import com.android.internal.util.Preconditions; + /** * Handles save requests from the {@link AutofillService} into the {@link Activity} being * autofilled. @@ -36,18 +41,33 @@ public final class SaveCallback { * Notifies the Android System that an * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully handled * by the service. + */ + public void onSuccess() { + onSuccessInternal(null); + } + + /** + * Notifies the Android System that an + * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully handled + * by the service. * - * <p>If the service could not handle the request right away—for example, because it must - * launch an activity asking the user to authenticate first or because the network is - * down—it should still call {@link #onSuccess()}. + * <p>This method is useful when the service requires extra work—for example, launching an + * activity asking the user to authenticate first —before it can process the request, + * as the intent will be launched from the context of the activity being autofilled and hence + * will be part of that activity's stack. * - * @throws RuntimeException if an error occurred while calling the Android System. + * @param intentSender intent that will be launched from the context of activity being + * autofilled. */ - public void onSuccess() { + public void onSuccess(@NonNull IntentSender intentSender) { + onSuccessInternal(Preconditions.checkNotNull(intentSender)); + } + + private void onSuccessInternal(@Nullable IntentSender intentSender) { assertNotCalled(); mCalled = true; try { - mCallback.onSuccess(); + mCallback.onSuccess(intentSender); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } @@ -63,11 +83,10 @@ public final class SaveCallback { * the {@link SaveRequest} and call {@link #onSuccess()} instead. * * <p><b>Note:</b> The Android System displays an UI with the supplied error message; if - * you prefer to show your own message, call {@link #onSuccess()} instead. + * you prefer to show your own message, call {@link #onSuccess()} or + * {@link #onSuccess(IntentSender)} instead. * * @param message error message to be displayed to the user. - * - * @throws RuntimeException if an error occurred while calling the Android System. */ public void onFailure(CharSequence message) { assertNotCalled(); diff --git a/android/service/autofill/SaveInfo.java b/android/service/autofill/SaveInfo.java index 9a1dcbb2..0b50f074 100644 --- a/android/service/autofill/SaveInfo.java +++ b/android/service/autofill/SaveInfo.java @@ -599,9 +599,9 @@ public final class SaveInfo implements Parcelable { * credit card number: * * <pre class="prettyprint"> - * builder.addSanitizer( - * new TextValueSanitizer(Pattern.compile("^(\\d{4}\s?\\d{4}\s?\\d{4}\s?\\d{4})$"), - * "$1$2$3$4"), ccNumberId); + * builder.addSanitizer(new TextValueSanitizer( + * Pattern.compile("^(\\d{4})\\s?(\\d{4})\\s?(\\d{4})\\s?(\\d{4})$", "$1$2$3$4")), + * ccNumberId); * </pre> * * <p>The same sanitizer can be reused to sanitize multiple fields. For example, to trim @@ -613,6 +613,11 @@ public final class SaveInfo implements Parcelable { * usernameId, passwordId); * </pre> * + * <p>The sanitizer can also be used as an alternative for a + * {@link #setValidator(Validator) validator}. If any of the {@code ids} is a + * {@link #SaveInfo.Builder(int, AutofillId[]) required id} and the {@code sanitizer} fails + * because of it, then the save UI is not shown. + * * @param sanitizer an implementation provided by the Android System. * @param ids id of fields whose value will be sanitized. * @return this builder. diff --git a/android/service/autofill/TextValueSanitizer.java b/android/service/autofill/TextValueSanitizer.java index 12e85b1d..e5ad77a1 100644 --- a/android/service/autofill/TextValueSanitizer.java +++ b/android/service/autofill/TextValueSanitizer.java @@ -19,6 +19,7 @@ package android.service.autofill; import static android.view.autofill.Helper.sDebug; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -36,7 +37,8 @@ import java.util.regex.Pattern; * <p>For example, to remove spaces from groups of 4-digits in a credit card: * * <pre class="prettyprint"> - * new TextValueSanitizer(Pattern.compile("^(\\d{4}\s?\\d{4}\s?\\d{4}\s?\\d{4})$"), "$1$2$3$4") + * new TextValueSanitizer(Pattern.compile("^(\\d{4})\\s?(\\d{4})\\s?(\\d{4})\\s?(\\d{4})$", + * "$1$2$3$4") * </pre> */ public final class TextValueSanitizer extends InternalSanitizer implements @@ -62,24 +64,31 @@ public final class TextValueSanitizer extends InternalSanitizer implements /** @hide */ @Override @TestApi + @Nullable public AutofillValue sanitize(@NonNull AutofillValue value) { if (value == null) { Slog.w(TAG, "sanitize() called with null value"); return null; } - if (!value.isText()) return value; + if (!value.isText()) { + if (sDebug) Slog.d(TAG, "sanitize() called with non-text value: " + value); + return null; + } final CharSequence text = value.getTextValue(); try { final Matcher matcher = mRegex.matcher(text); - if (!matcher.matches()) return value; + if (!matcher.matches()) { + if (sDebug) Slog.d(TAG, "sanitize(): " + mRegex + " failed for " + value); + return null; + } final CharSequence sanitized = matcher.replaceAll(mSubst); return AutofillValue.forText(sanitized); } catch (Exception e) { Slog.w(TAG, "Exception evaluating " + mRegex + "/" + mSubst + ": " + e); - return value; + return null; } } diff --git a/android/service/autofill/Validators.java b/android/service/autofill/Validators.java index 51b503c2..1c838687 100644 --- a/android/service/autofill/Validators.java +++ b/android/service/autofill/Validators.java @@ -52,6 +52,19 @@ public final class Validators { return new OptionalValidators(getInternalValidators(validators)); } + /** + * Creates a validator that is valid only if {@code validator} is not. + * + * @throws IllegalArgumentException if {@code validator} is an instance of a class that is not + * provided by the Android System. + */ + @NonNull + public static Validator not(@NonNull Validator validator) { + Preconditions.checkArgument(validator instanceof InternalValidator, + "validator not provided by Android System: " + validator); + return new NegationValidator((InternalValidator) validator); + } + private static InternalValidator[] getInternalValidators(Validator[] validators) { Preconditions.checkArrayElementsNotNull(validators, "validators"); diff --git a/android/service/euicc/EuiccService.java b/android/service/euicc/EuiccService.java index cd233b83..df0842f7 100644 --- a/android/service/euicc/EuiccService.java +++ b/android/service/euicc/EuiccService.java @@ -105,6 +105,13 @@ public abstract class EuiccService extends Service { public static final String EXTRA_RESOLUTION_CALLING_PACKAGE = "android.service.euicc.extra.RESOLUTION_CALLING_PACKAGE"; + /** + * Intent extra set for resolution requests containing a boolean indicating whether to ask the + * user to retry another confirmation code. + */ + public static final String EXTRA_RESOLUTION_CONFIRMATION_CODE_RETRIED = + "android.service.euicc.extra.RESOLUTION_CONFIRMATION_CODE_RETRIED"; + /** Result code for a successful operation. */ public static final int RESULT_OK = 0; /** Result code indicating that an active SIM must be deactivated to perform the operation. */ diff --git a/android/service/wallpaper/WallpaperService.java b/android/service/wallpaper/WallpaperService.java index 1c6275fb..dd0ae339 100644 --- a/android/service/wallpaper/WallpaperService.java +++ b/android/service/wallpaper/WallpaperService.java @@ -889,7 +889,8 @@ public abstract class WallpaperService extends Service { mFinalStableInsets.set(mDispatchedStableInsets); WindowInsets insets = new WindowInsets(mFinalSystemInsets, null, mFinalStableInsets, - getResources().getConfiguration().isScreenRound(), false); + getResources().getConfiguration().isScreenRound(), false, + null /* displayCutout */); if (DEBUG) { Log.v(TAG, "dispatching insets=" + insets); } diff --git a/android/support/LibraryGroups.java b/android/support/LibraryGroups.java index feaefbc6..19c0a927 100644 --- a/android/support/LibraryGroups.java +++ b/android/support/LibraryGroups.java @@ -27,4 +27,5 @@ public class LibraryGroups { public static final String ARCH_CORE = "android.arch.core"; public static final String PAGING = "android.arch.paging"; public static final String NAVIGATION = "android.arch.navigation"; + public static final String SLICES = "androidx.app.slice"; } diff --git a/android/support/car/widget/PagedListView.java b/android/support/car/widget/PagedListView.java index 4695c45c..67a6247a 100644 --- a/android/support/car/widget/PagedListView.java +++ b/android/support/car/widget/PagedListView.java @@ -286,38 +286,6 @@ public class PagedListView extends FrameLayout { return mLayoutManager.getPosition(v); } - private void scroll(int direction) { - View focusedView = mRecyclerView.getFocusedChild(); - if (focusedView != null) { - int position = mLayoutManager.getPosition(focusedView); - int newPosition = - Math.max(Math.min(position + direction, mLayoutManager.getItemCount() - 1), 0); - if (newPosition != position) { - // newPosition/position are adapter positions. - // Convert to layout position by subtracting adapter position of view at layout - // position 0. - View childAt = mRecyclerView.getChildAt( - newPosition - mLayoutManager.getPosition(mLayoutManager.getChildAt(0))); - if (childAt != null) { - childAt.requestFocus(); - } - } - } - } - - private boolean canScroll(int direction) { - View focusedView = mRecyclerView.getFocusedChild(); - if (focusedView != null) { - int position = mLayoutManager.getPosition(focusedView); - int newPosition = - Math.max(Math.min(position + direction, mLayoutManager.getItemCount() - 1), 0); - if (newPosition != position) { - return true; - } - } - return false; - } - @NonNull public CarRecyclerView getRecyclerView() { return mRecyclerView; diff --git a/android/support/design/widget/BottomSheetBehavior.java b/android/support/design/widget/BottomSheetBehavior.java index aaa9b804..00ce8f90 100644 --- a/android/support/design/widget/BottomSheetBehavior.java +++ b/android/support/design/widget/BottomSheetBehavior.java @@ -559,7 +559,7 @@ public class BottomSheetBehavior<V extends View> extends CoordinatorLayout.Behav * Gets the current state of the bottom sheet. * * @return One of {@link #STATE_EXPANDED}, {@link #STATE_COLLAPSED}, {@link #STATE_DRAGGING}, - * and {@link #STATE_SETTLING}. + * {@link #STATE_SETTLING}, and {@link #STATE_HIDDEN}. */ @State public final int getState() { diff --git a/android/support/design/widget/CollapsingToolbarLayout.java b/android/support/design/widget/CollapsingToolbarLayout.java index 0051de9e..8c9b7d49 100644 --- a/android/support/design/widget/CollapsingToolbarLayout.java +++ b/android/support/design/widget/CollapsingToolbarLayout.java @@ -44,6 +44,7 @@ import android.support.v4.util.ObjectsCompat; import android.support.v4.view.GravityCompat; import android.support.v4.view.ViewCompat; import android.support.v4.view.WindowInsetsCompat; +import android.support.v4.widget.ViewGroupUtils; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.util.AttributeSet; diff --git a/android/support/design/widget/CoordinatorLayout.java b/android/support/design/widget/CoordinatorLayout.java index 477a8d62..c45810ef 100644 --- a/android/support/design/widget/CoordinatorLayout.java +++ b/android/support/design/widget/CoordinatorLayout.java @@ -56,6 +56,8 @@ import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewCompat.NestedScrollType; import android.support.v4.view.ViewCompat.ScrollAxis; import android.support.v4.view.WindowInsetsCompat; +import android.support.v4.widget.DirectedAcyclicGraph; +import android.support.v4.widget.ViewGroupUtils; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; diff --git a/android/support/design/widget/DirectedAcyclicGraphTest.java b/android/support/design/widget/DirectedAcyclicGraphTest.java deleted file mode 100644 index ec7687d5..00000000 --- a/android/support/design/widget/DirectedAcyclicGraphTest.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.design.widget; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import android.support.annotation.NonNull; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.util.List; - -@RunWith(JUnit4.class) -public class DirectedAcyclicGraphTest { - - private DirectedAcyclicGraph<TestNode> mGraph; - - @Before - public void setup() { - mGraph = new DirectedAcyclicGraph<>(); - } - - @Test - public void test_addNode() { - final TestNode node = new TestNode("node"); - mGraph.addNode(node); - assertEquals(1, mGraph.size()); - assertTrue(mGraph.contains(node)); - } - - @Test - public void test_addNodeAgain() { - final TestNode node = new TestNode("node"); - mGraph.addNode(node); - mGraph.addNode(node); - - assertEquals(1, mGraph.size()); - assertTrue(mGraph.contains(node)); - } - - @Test - public void test_addEdge() { - final TestNode node = new TestNode("node"); - final TestNode edge = new TestNode("edge"); - - mGraph.addNode(node); - mGraph.addNode(edge); - mGraph.addEdge(node, edge); - } - - @Test(expected = IllegalArgumentException.class) - public void test_addEdgeWithNotAddedEdgeNode() { - final TestNode node = new TestNode("node"); - final TestNode edge = new TestNode("edge"); - - // Add the node, but not the edge node - mGraph.addNode(node); - - // Now add the link - mGraph.addEdge(node, edge); - } - - @Test - public void test_getIncomingEdges() { - final TestNode node = new TestNode("node"); - final TestNode edge = new TestNode("edge"); - mGraph.addNode(node); - mGraph.addNode(edge); - mGraph.addEdge(node, edge); - - final List<TestNode> incomingEdges = mGraph.getIncomingEdges(node); - assertNotNull(incomingEdges); - assertEquals(1, incomingEdges.size()); - assertEquals(edge, incomingEdges.get(0)); - } - - @Test - public void test_getOutgoingEdges() { - final TestNode node = new TestNode("node"); - final TestNode edge = new TestNode("edge"); - mGraph.addNode(node); - mGraph.addNode(edge); - mGraph.addEdge(node, edge); - - // Now assert the getOutgoingEdges returns a list which has one element (node) - final List<TestNode> outgoingEdges = mGraph.getOutgoingEdges(edge); - assertNotNull(outgoingEdges); - assertEquals(1, outgoingEdges.size()); - assertTrue(outgoingEdges.contains(node)); - } - - @Test - public void test_getOutgoingEdgesMultiple() { - final TestNode node1 = new TestNode("1"); - final TestNode node2 = new TestNode("2"); - final TestNode edge = new TestNode("edge"); - mGraph.addNode(node1); - mGraph.addNode(node2); - mGraph.addNode(edge); - - mGraph.addEdge(node1, edge); - mGraph.addEdge(node2, edge); - - // Now assert the getOutgoingEdges returns a list which has 2 elements (node1 & node2) - final List<TestNode> outgoingEdges = mGraph.getOutgoingEdges(edge); - assertNotNull(outgoingEdges); - assertEquals(2, outgoingEdges.size()); - assertTrue(outgoingEdges.contains(node1)); - assertTrue(outgoingEdges.contains(node2)); - } - - @Test - public void test_hasOutgoingEdges() { - final TestNode node = new TestNode("node"); - final TestNode edge = new TestNode("edge"); - mGraph.addNode(node); - mGraph.addNode(edge); - - // There is no edge currently and assert that fact - assertFalse(mGraph.hasOutgoingEdges(edge)); - // Now add the edge - mGraph.addEdge(node, edge); - // and assert that the methods returns true; - assertTrue(mGraph.hasOutgoingEdges(edge)); - } - - @Test - public void test_clear() { - final TestNode node1 = new TestNode("1"); - final TestNode node2 = new TestNode("2"); - final TestNode edge = new TestNode("edge"); - mGraph.addNode(node1); - mGraph.addNode(node2); - mGraph.addNode(edge); - - // Now clear the graph - mGraph.clear(); - - // Now assert the graph is empty and that contains returns false - assertEquals(0, mGraph.size()); - assertFalse(mGraph.contains(node1)); - assertFalse(mGraph.contains(node2)); - assertFalse(mGraph.contains(edge)); - } - - @Test - public void test_getSortedList() { - final TestNode node1 = new TestNode("A"); - final TestNode node2 = new TestNode("B"); - final TestNode node3 = new TestNode("C"); - final TestNode node4 = new TestNode("D"); - - // Now we'll add the nodes - mGraph.addNode(node1); - mGraph.addNode(node2); - mGraph.addNode(node3); - mGraph.addNode(node4); - - // Now we'll add edges so that 4 <- 2, 2 <- 3, 3 <- 1 (where <- denotes a dependency) - mGraph.addEdge(node4, node2); - mGraph.addEdge(node2, node3); - mGraph.addEdge(node3, node1); - - final List<TestNode> sorted = mGraph.getSortedList(); - // Assert that it is the correct size - assertEquals(4, sorted.size()); - // Assert that all of the nodes are present and in their sorted order - assertEquals(node1, sorted.get(0)); - assertEquals(node3, sorted.get(1)); - assertEquals(node2, sorted.get(2)); - assertEquals(node4, sorted.get(3)); - } - - private static class TestNode { - private final String mLabel; - - TestNode(@NonNull String label) { - mLabel = label; - } - - @Override - public String toString() { - return "TestNode: " + mLabel; - } - } - -} diff --git a/android/support/design/widget/FloatingActionButton.java b/android/support/design/widget/FloatingActionButton.java index b9388366..f37b3798 100644 --- a/android/support/design/widget/FloatingActionButton.java +++ b/android/support/design/widget/FloatingActionButton.java @@ -36,6 +36,7 @@ import android.support.annotation.VisibleForTesting; import android.support.design.R; import android.support.design.widget.FloatingActionButtonImpl.InternalVisibilityChangedListener; import android.support.v4.view.ViewCompat; +import android.support.v4.widget.ViewGroupUtils; import android.support.v7.widget.AppCompatImageHelper; import android.util.AttributeSet; import android.util.Log; @@ -116,6 +117,11 @@ public class FloatingActionButton extends VisibilityAwareImageButton { public static final int SIZE_AUTO = -1; /** + * Indicates that FloatingActionButton should not have a custom size. + */ + public static final int NO_CUSTOM_SIZE = 0; + + /** * The switch point for the largest screen edge where SIZE_AUTO switches from mini to normal. */ private static final int AUTO_MINI_LARGEST_SCREEN_WIDTH = 470; @@ -132,6 +138,7 @@ public class FloatingActionButton extends VisibilityAwareImageButton { private int mBorderWidth; private int mRippleColor; private int mSize; + private int mCustomSize; int mImagePadding; private int mMaxImageSize; @@ -164,6 +171,8 @@ public class FloatingActionButton extends VisibilityAwareImageButton { R.styleable.FloatingActionButton_backgroundTintMode, -1), null); mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0); mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_AUTO); + mCustomSize = a.getDimensionPixelSize(R.styleable.FloatingActionButton_fabCustomSize, + 0); mBorderWidth = a.getDimensionPixelSize(R.styleable.FloatingActionButton_borderWidth, 0); final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f); final float pressedTranslationZ = a.getDimension( @@ -430,12 +439,41 @@ public class FloatingActionButton extends VisibilityAwareImageButton { }; } + /** + * Sets the size of the button to be a custom value in pixels. If set to + * {@link #NO_CUSTOM_SIZE}, custom size will not be used and size will be calculated according + * to {@link #setSize(int)} method. + * + * @param size preferred size in pixels, or zero + * + * @attr ref android.support.design.R.styleable#FloatingActionButton_fabCustomSize + */ + public void setCustomSize(int size) { + if (size < 0) { + throw new IllegalArgumentException("Custom size should be non-negative."); + } + mCustomSize = size; + } + + /** + * Returns the custom size for this button. + * + * @return size in pixels, or {@link #NO_CUSTOM_SIZE} + */ + public int getCustomSize() { + return mCustomSize; + } + int getSizeDimension() { return getSizeDimension(mSize); } private int getSizeDimension(@Size final int size) { final Resources res = getResources(); + // If custom size is set, return it + if (mCustomSize != NO_CUSTOM_SIZE) { + return mCustomSize; + } switch (size) { case SIZE_AUTO: // If we're set to auto, grab the size from resources and refresh diff --git a/android/support/design/widget/TextInputEditText.java b/android/support/design/widget/TextInputEditText.java index 7235ec22..ee6c32cd 100644 --- a/android/support/design/widget/TextInputEditText.java +++ b/android/support/design/widget/TextInputEditText.java @@ -18,6 +18,7 @@ package android.support.design.widget; import android.content.Context; import android.support.v7.widget.AppCompatEditText; +import android.support.v7.widget.WithHint; import android.util.AttributeSet; import android.view.View; import android.view.ViewParent; @@ -48,12 +49,12 @@ public class TextInputEditText extends AppCompatEditText { public InputConnection onCreateInputConnection(EditorInfo outAttrs) { final InputConnection ic = super.onCreateInputConnection(outAttrs); if (ic != null && outAttrs.hintText == null) { - // If we don't have a hint and our parent is a TextInputLayout, use it's hint for the + // If we don't have a hint and our parent implements WithHint, use its hint for the // EditorInfo. This allows us to display a hint in 'extract mode'. ViewParent parent = getParent(); while (parent instanceof View) { - if (parent instanceof TextInputLayout) { - outAttrs.hintText = ((TextInputLayout) parent).getHint(); + if (parent instanceof WithHint) { + outAttrs.hintText = ((WithHint) parent).getHint(); break; } parent = parent.getParent(); diff --git a/android/support/design/widget/TextInputLayout.java b/android/support/design/widget/TextInputLayout.java index c9e8010d..0540678e 100644 --- a/android/support/design/widget/TextInputLayout.java +++ b/android/support/design/widget/TextInputLayout.java @@ -49,10 +49,12 @@ import android.support.v4.view.ViewCompat; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; import android.support.v4.widget.Space; import android.support.v4.widget.TextViewCompat; +import android.support.v4.widget.ViewGroupUtils; import android.support.v7.content.res.AppCompatResources; import android.support.v7.widget.AppCompatDrawableManager; import android.support.v7.widget.AppCompatTextView; import android.support.v7.widget.TintTypedArray; +import android.support.v7.widget.WithHint; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -113,7 +115,7 @@ import android.widget.TextView; * may not return the TextInputLayout itself, but rather an intermediate View. If you need * to access a View directly, set an {@code android:id} and use {@link View#findViewById(int)}. */ -public class TextInputLayout extends LinearLayout { +public class TextInputLayout extends LinearLayout implements WithHint { private static final int ANIMATION_DURATION = 200; private static final int INVALID_MAX_LENGTH = -1; @@ -497,6 +499,7 @@ public class TextInputLayout extends LinearLayout { * * @attr ref android.support.design.R.styleable#TextInputLayout_android_hint */ + @Override @Nullable public CharSequence getHint() { return mHintEnabled ? mHint : null; diff --git a/android/support/doclava/DoclavaJavadocOptionFileOption.java b/android/support/doclava/DoclavaJavadocOptionFileOption.java deleted file mode 100644 index db3f3188..00000000 --- a/android/support/doclava/DoclavaJavadocOptionFileOption.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2014 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.support.doclava; - -import org.gradle.external.javadoc.internal.AbstractJavadocOptionFileOption; -import org.gradle.external.javadoc.internal.JavadocOptionFileWriterContext; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; - -/** - * This class is used to hold complex argument(s) to doclava - */ -public class DoclavaJavadocOptionFileOption extends - AbstractJavadocOptionFileOption<Iterable<String>> { - - public DoclavaJavadocOptionFileOption(String option) { - super(option, null); - } - - public DoclavaJavadocOptionFileOption(String option, Iterable<String> value) { - super(option, value); - } - - @Override - public void write(JavadocOptionFileWriterContext writerContext) throws IOException { - writerContext.writeOptionHeader(getOption()); - - final Iterable<String> args = getValue(); - if (args != null) { - final Iterator<String> iter = args.iterator(); - while (true) { - writerContext.writeValue(iter.next()); - if (!iter.hasNext()) { - break; - } - writerContext.write(" "); - } - } - - writerContext.newLine(); - } - - /** - * @return a deep copy of the option - */ - public DoclavaJavadocOptionFileOption duplicate() { - final Iterable<String> value = getValue(); - final ArrayList<String> valueCopy; - if (value != null) { - valueCopy = new ArrayList<>(); - for (String item : value) { - valueCopy.add(item); - } - } else { - valueCopy = null; - } - return new DoclavaJavadocOptionFileOption(getOption(), valueCopy); - } -} diff --git a/android/support/graphics/drawable/VectorDrawableCompat.java b/android/support/graphics/drawable/VectorDrawableCompat.java index 2c7ae41c..a34fe2b8 100644 --- a/android/support/graphics/drawable/VectorDrawableCompat.java +++ b/android/support/graphics/drawable/VectorDrawableCompat.java @@ -173,6 +173,10 @@ import java.util.Stack; * <dd>Sets the lineJoin for a stroked path: miter,round,bevel. Default is miter.</dd> * <dt><code>android:strokeMiterLimit</code></dt> * <dd>Sets the Miter limit for a stroked path. Default is 4.</dd> + * <dt><code>android:fillType</code></dt> + * <dd>Sets the fillType for a path. The types can be either "evenOdd" or "nonZero". They behave the + * same as SVG's "fill-rule" properties. Default is nonZero. For more details, see + * <a href="https://www.w3.org/TR/SVG/painting.html#FillRuleProperty">FillRuleProperty</a></dd> * </dl></dd> * </dl> * diff --git a/android/support/mediacompat/testlib/MediaBrowserConstants.java b/android/support/mediacompat/testlib/MediaBrowserConstants.java index 86024d90..f961308d 100644 --- a/android/support/mediacompat/testlib/MediaBrowserConstants.java +++ b/android/support/mediacompat/testlib/MediaBrowserConstants.java @@ -31,14 +31,16 @@ public class MediaBrowserConstants { public static final int SET_SESSION_TOKEN = 7; public static final String MEDIA_ID_ROOT = "test_media_id_root"; - - public static final String EXTRAS_KEY = "test_extras_key"; - public static final String EXTRAS_VALUE = "test_extras_value"; - public static final String MEDIA_ID_INVALID = "test_media_id_invalid"; public static final String MEDIA_ID_CHILDREN_DELAYED = "test_media_id_children_delayed"; public static final String MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED = "test_media_id_on_load_item_not_implemented"; + public static final String MEDIA_ID_INCLUDE_METADATA = "test_media_id_include_metadata"; + + public static final String EXTRAS_KEY = "test_extras_key"; + public static final String EXTRAS_VALUE = "test_extras_value"; + + public static final String MEDIA_METADATA = "test_media_metadata"; public static final String SEARCH_QUERY = "children_2"; public static final String SEARCH_QUERY_FOR_NO_RESULT = "query no result"; diff --git a/android/support/mediacompat/testlib/MediaControllerConstants.java b/android/support/mediacompat/testlib/MediaControllerConstants.java index 5fa086b3..49788882 100644 --- a/android/support/mediacompat/testlib/MediaControllerConstants.java +++ b/android/support/mediacompat/testlib/MediaControllerConstants.java @@ -26,6 +26,8 @@ public class MediaControllerConstants { public static final int ADD_QUEUE_ITEM = 202; public static final int ADD_QUEUE_ITEM_WITH_INDEX = 203; public static final int REMOVE_QUEUE_ITEM = 204; + public static final int SET_VOLUME_TO = 205; + public static final int ADJUST_VOLUME = 206; // TransportControls methods. public static final int PLAY = 301; diff --git a/android/support/mediacompat/testlib/MediaSessionConstants.java b/android/support/mediacompat/testlib/MediaSessionConstants.java index cbdccc1b..c0a64d4d 100644 --- a/android/support/mediacompat/testlib/MediaSessionConstants.java +++ b/android/support/mediacompat/testlib/MediaSessionConstants.java @@ -51,6 +51,8 @@ public class MediaSessionConstants { public static final long TEST_QUEUE_ID_2 = 20L; public static final String TEST_MEDIA_ID_1 = "media_id_1"; public static final String TEST_MEDIA_ID_2 = "media_id_2"; + public static final String TEST_MEDIA_TITLE_1 = "media_title_1"; + public static final String TEST_MEDIA_TITLE_2 = "media_title_2"; public static final long TEST_ACTION = 55L; public static final int TEST_ERROR_CODE = 0x3; diff --git a/android/support/mediacompat/testlib/VersionConstants.java b/android/support/mediacompat/testlib/VersionConstants.java index 6533ee17..4b217b10 100644 --- a/android/support/mediacompat/testlib/VersionConstants.java +++ b/android/support/mediacompat/testlib/VersionConstants.java @@ -22,4 +22,7 @@ package android.support.mediacompat.testlib; public class VersionConstants { public static final String KEY_CLIENT_VERSION = "client_version"; public static final String KEY_SERVICE_VERSION = "service_version"; + + public static final String VERSION_TOT = "tot"; + public static final String VERSION_PREVIOUS = "previous"; } diff --git a/android/support/mediacompat/testlib/util/IntentUtil.java b/android/support/mediacompat/testlib/util/IntentUtil.java index bbf97524..8d58a6ff 100644 --- a/android/support/mediacompat/testlib/util/IntentUtil.java +++ b/android/support/mediacompat/testlib/util/IntentUtil.java @@ -30,12 +30,13 @@ import java.util.ArrayList; */ public class IntentUtil { + public static final String SERVICE_PACKAGE_NAME = "android.support.mediacompat.service.test"; + public static final String CLIENT_PACKAGE_NAME = "android.support.mediacompat.client.test"; + public static final ComponentName SERVICE_RECEIVER_COMPONENT_NAME = new ComponentName( - "android.support.mediacompat.service.test", - "android.support.mediacompat.service.ServiceBroadcastReceiver"); + SERVICE_PACKAGE_NAME, "android.support.mediacompat.service.ServiceBroadcastReceiver"); public static final ComponentName CLIENT_RECEIVER_COMPONENT_NAME = new ComponentName( - "android.support.mediacompat.client.test", - "android.support.mediacompat.client.ClientBroadcastReceiver"); + CLIENT_PACKAGE_NAME, "android.support.mediacompat.client.ClientBroadcastReceiver"); public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD = "android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD"; diff --git a/android/support/text/emoji/EmojiCompat.java b/android/support/text/emoji/EmojiCompat.java index f258c12d..5436aa20 100644 --- a/android/support/text/emoji/EmojiCompat.java +++ b/android/support/text/emoji/EmojiCompat.java @@ -221,6 +221,7 @@ public class EmojiCompat { * * @see EmojiCompat.Config */ + @SuppressWarnings("GuardedBy") public static EmojiCompat init(@NonNull final Config config) { if (sInstance == null) { synchronized (sInstanceLock) { @@ -238,6 +239,7 @@ public class EmojiCompat { * * @hide */ + @SuppressWarnings("GuardedBy") @RestrictTo(LIBRARY_GROUP) @VisibleForTesting public static EmojiCompat reset(@NonNull final Config config) { @@ -252,6 +254,7 @@ public class EmojiCompat { * * @hide */ + @SuppressWarnings("GuardedBy") @RestrictTo(LIBRARY_GROUP) @VisibleForTesting public static EmojiCompat reset(final EmojiCompat emojiCompat) { diff --git a/android/support/text/emoji/MetadataListReader.java b/android/support/text/emoji/MetadataListReader.java index 6034726d..1008c171 100644 --- a/android/support/text/emoji/MetadataListReader.java +++ b/android/support/text/emoji/MetadataListReader.java @@ -275,7 +275,7 @@ class MetadataListReader { @Override public void skip(int numOfBytes) throws IOException { while (numOfBytes > 0) { - long skipped = mInputStream.skip(numOfBytes); + int skipped = (int) mInputStream.skip(numOfBytes); if (skipped < 1) { throw new IOException("Skip didn't move at least 1 byte forward"); } diff --git a/android/support/text/emoji/widget/EmojiEditableFactory.java b/android/support/text/emoji/widget/EmojiEditableFactory.java index 9793c9da..20cde4f9 100644 --- a/android/support/text/emoji/widget/EmojiEditableFactory.java +++ b/android/support/text/emoji/widget/EmojiEditableFactory.java @@ -55,6 +55,7 @@ final class EmojiEditableFactory extends Editable.Factory { } } + @SuppressWarnings("GuardedBy") public static Editable.Factory getInstance() { if (sInstance == null) { synchronized (sInstanceLock) { diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java index d7020e91..9d159eca 100644 --- a/android/support/v17/leanback/widget/GridLayoutManager.java +++ b/android/support/v17/leanback/widget/GridLayoutManager.java @@ -2726,40 +2726,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } } - // Observer is registered on Adapter to invalidate saved instance state - final RecyclerView.AdapterDataObserver mObServer = new RecyclerView.AdapterDataObserver() { - @Override - public void onChanged() { - mChildrenStates.clear(); - } - - @Override - public void onItemRangeChanged(int positionStart, int itemCount) { - if (DEBUG) { - Log.v(getTag(), "onItemRangeChanged positionStart " - + positionStart + " itemCount " + itemCount); - } - for (int i = positionStart, end = positionStart + itemCount; i < end; i++) { - mChildrenStates.remove(i); - } - } - - @Override - public void onItemRangeInserted(int positionStart, int itemCount) { - mChildrenStates.clear(); - } - - @Override - public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { - mChildrenStates.clear(); - } - - @Override - public void onItemRangeRemoved(int positionStart, int itemCount) { - mChildrenStates.clear(); - } - }; - @Override public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) { if (DEBUG) Log.v(getTag(), "onItemsAdded positionStart " @@ -2771,12 +2737,14 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { mFocusPositionOffset += itemCount; } } + mChildrenStates.clear(); } @Override public void onItemsChanged(RecyclerView recyclerView) { if (DEBUG) Log.v(getTag(), "onItemsChanged"); mFocusPositionOffset = 0; + mChildrenStates.clear(); } @Override @@ -2797,6 +2765,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } } } + mChildrenStates.clear(); } @Override @@ -2817,6 +2786,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { mFocusPositionOffset += itemCount; } } + mChildrenStates.clear(); + } + + @Override + public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) { + if (DEBUG) Log.v(getTag(), "onItemsUpdated positionStart " + + positionStart + " itemCount " + itemCount); + for (int i = positionStart, end = positionStart + itemCount; i < end; i++) { + mChildrenStates.remove(i); + } } @Override @@ -3515,16 +3494,12 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { mFocusPosition = NO_POSITION; mFocusPositionOffset = 0; mChildrenStates.clear(); - oldAdapter.unregisterAdapterDataObserver(mObServer); } if (newAdapter instanceof FacetProviderAdapter) { mFacetProviderAdapter = (FacetProviderAdapter) newAdapter; } else { mFacetProviderAdapter = null; } - if (newAdapter != null) { - newAdapter.registerAdapterDataObserver(mObServer); - } super.onAdapterChanged(oldAdapter, newAdapter); } diff --git a/android/support/v4/graphics/TypefaceCompatApi26Impl.java b/android/support/v4/graphics/TypefaceCompatApi26Impl.java index 00e31a1a..f23ac0d4 100644 --- a/android/support/v4/graphics/TypefaceCompatApi26Impl.java +++ b/android/support/v4/graphics/TypefaceCompatApi26Impl.java @@ -189,10 +189,9 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl { /** * Call FontFamily#abortCreation() */ - private boolean abortCreation(Object family) { + private void abortCreation(Object family) { try { - Boolean result = (Boolean) mAbortCreation.invoke(family); - return result.booleanValue(); + mAbortCreation.invoke(family); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } diff --git a/android/support/v4/media/MediaBrowserCompat.java b/android/support/v4/media/MediaBrowserCompat.java index 7adf7d78..1b279253 100644 --- a/android/support/v4/media/MediaBrowserCompat.java +++ b/android/support/v4/media/MediaBrowserCompat.java @@ -41,10 +41,12 @@ import static android.support.v4.media.MediaBrowserProtocol.DATA_SEARCH_EXTRAS; import static android.support.v4.media.MediaBrowserProtocol.DATA_SEARCH_QUERY; import static android.support.v4.media.MediaBrowserProtocol.EXTRA_CLIENT_VERSION; import static android.support.v4.media.MediaBrowserProtocol.EXTRA_MESSENGER_BINDER; +import static android.support.v4.media.MediaBrowserProtocol.EXTRA_SERVICE_VERSION; import static android.support.v4.media.MediaBrowserProtocol.EXTRA_SESSION_BINDER; import static android.support.v4.media.MediaBrowserProtocol.SERVICE_MSG_ON_CONNECT; import static android.support.v4.media.MediaBrowserProtocol.SERVICE_MSG_ON_CONNECT_FAILED; import static android.support.v4.media.MediaBrowserProtocol.SERVICE_MSG_ON_LOAD_CHILDREN; +import static android.support.v4.media.MediaBrowserProtocol.SERVICE_VERSION_2; import android.content.ComponentName; import android.content.Context; @@ -1581,6 +1583,7 @@ public final class MediaBrowserCompat { protected final CallbackHandler mHandler = new CallbackHandler(this); private final ArrayMap<String, Subscription> mSubscriptions = new ArrayMap<>(); + protected int mServiceVersion; protected ServiceBinderWrapper mServiceBinderWrapper; protected Messenger mCallbacksMessenger; private MediaSessionCompat.Token mMediaSessionToken; @@ -1850,6 +1853,7 @@ public final class MediaBrowserCompat { if (extras == null) { return; } + mServiceVersion = extras.getInt(EXTRA_SERVICE_VERSION, 0); IBinder serviceBinder = BundleCompat.getBinder(extras, EXTRA_MESSENGER_BINDER); if (serviceBinder != null) { mServiceBinderWrapper = new ServiceBinderWrapper(serviceBinder, mRootHints); @@ -1956,7 +1960,9 @@ public final class MediaBrowserCompat { @Override public void subscribe(@NonNull String parentId, @Nullable Bundle options, @NonNull SubscriptionCallback callback) { - if (mServiceBinderWrapper == null) { + // From service v2, we use compat code when subscribing. + // This is to prevent ClassNotFoundException when options has Parcelable in it. + if (mServiceBinderWrapper == null || mServiceVersion < SERVICE_VERSION_2) { if (options == null) { MediaBrowserCompatApi21.subscribe( mBrowserObj, parentId, callback.mSubscriptionCallbackObj); @@ -1971,7 +1977,9 @@ public final class MediaBrowserCompat { @Override public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) { - if (mServiceBinderWrapper == null) { + // From service v2, we use compat code when subscribing. + // This is to prevent ClassNotFoundException when options has Parcelable in it. + if (mServiceBinderWrapper == null || mServiceVersion < SERVICE_VERSION_2) { if (callback == null) { MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId); } else { diff --git a/android/support/v4/media/MediaBrowserProtocol.java b/android/support/v4/media/MediaBrowserProtocol.java index 7c23d261..8ed152df 100644 --- a/android/support/v4/media/MediaBrowserProtocol.java +++ b/android/support/v4/media/MediaBrowserProtocol.java @@ -45,7 +45,15 @@ class MediaBrowserProtocol { * MediaBrowserServiceCompat. */ public static final int SERVICE_VERSION_1 = 1; - public static final int SERVICE_VERSION_CURRENT = SERVICE_VERSION_1; + + /** + * To prevent ClassNotFoundException from Parcelables, MediaBrowser(Service)Compat tries to + * avoid using framework code as much as possible (b/62648808). For backward compatibility, + * service v2 is introduced so that the browser can distinguish whether the service supports + * subscribing through compat code. + */ + public static final int SERVICE_VERSION_2 = 2; + public static final int SERVICE_VERSION_CURRENT = SERVICE_VERSION_2; /* * Messages sent from the media browser service compat to the media browser compat. diff --git a/android/support/v4/media/MediaBrowserServiceCompat.java b/android/support/v4/media/MediaBrowserServiceCompat.java index debc66e8..27bf0e30 100644 --- a/android/support/v4/media/MediaBrowserServiceCompat.java +++ b/android/support/v4/media/MediaBrowserServiceCompat.java @@ -278,28 +278,8 @@ public abstract class MediaBrowserServiceCompat extends Service { @Override public void notifyChildrenChanged(final String parentId, final Bundle options) { - if (mMessenger == null) { - MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId); - } else { - mHandler.post(new Runnable() { - @Override - public void run() { - for (IBinder binder : mConnections.keySet()) { - ConnectionRecord connection = mConnections.get(binder); - List<Pair<IBinder, Bundle>> callbackList = - connection.subscriptions.get(parentId); - if (callbackList != null) { - for (Pair<IBinder, Bundle> callback : callbackList) { - if (MediaBrowserCompatUtils.hasDuplicatedItems( - options, callback.second)) { - performLoadChildren(parentId, connection, callback.second); - } - } - } - } - } - }); - } + notifyChildrenChangedForFramework(parentId, options); + notifyChildrenChangedForCompat(parentId, options); } @Override @@ -373,6 +353,31 @@ public abstract class MediaBrowserServiceCompat extends Service { }; MediaBrowserServiceCompat.this.onLoadChildren(parentId, result); } + + void notifyChildrenChangedForFramework(final String parentId, final Bundle options) { + MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId); + } + + void notifyChildrenChangedForCompat(final String parentId, final Bundle options) { + mHandler.post(new Runnable() { + @Override + public void run() { + for (IBinder binder : mConnections.keySet()) { + ConnectionRecord connection = mConnections.get(binder); + List<Pair<IBinder, Bundle>> callbackList = + connection.subscriptions.get(parentId); + if (callbackList != null) { + for (Pair<IBinder, Bundle> callback : callbackList) { + if (MediaBrowserCompatUtils.hasDuplicatedItems( + options, callback.second)) { + performLoadChildren(parentId, connection, callback.second); + } + } + } + } + } + }); + } } @RequiresApi(23) @@ -421,20 +426,6 @@ public abstract class MediaBrowserServiceCompat extends Service { } @Override - public void notifyChildrenChanged(final String parentId, final Bundle options) { - if (mMessenger == null) { - if (options == null) { - MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId); - } else { - MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId, - options); - } - } else { - super.notifyChildrenChanged(parentId, options); - } - } - - @Override public void onLoadChildren(String parentId, final MediaBrowserServiceCompatApi26.ResultWrapper resultWrapper, Bundle options) { final Result<List<MediaBrowserCompat.MediaItem>> result @@ -470,6 +461,16 @@ public abstract class MediaBrowserServiceCompat extends Service { } return MediaBrowserServiceCompatApi26.getBrowserRootHints(mServiceObj); } + + @Override + void notifyChildrenChangedForFramework(final String parentId, final Bundle options) { + if (options != null) { + MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId, + options); + } else { + super.notifyChildrenChangedForFramework(parentId, options); + } + } } private final class ServiceHandler extends Handler { diff --git a/android/support/v4/view/NestedScrollingParent2.java b/android/support/v4/view/NestedScrollingParent2.java index db41c461..2ab463ec 100644 --- a/android/support/v4/view/NestedScrollingParent2.java +++ b/android/support/v4/view/NestedScrollingParent2.java @@ -18,7 +18,6 @@ package android.support.v4.view; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.support.v4.view.ViewCompat.NestedScrollType; import android.support.v4.view.ViewCompat.ScrollAxis; import android.view.MotionEvent; @@ -144,7 +143,7 @@ public interface NestedScrollingParent2 extends NestedScrollingParent { * @param consumed Output. The horizontal and vertical scroll distance consumed by this parent * @param type the type of input which cause this scroll event */ - void onNestedPreScroll(@NonNull View target, int dx, int dy, @Nullable int[] consumed, + void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, @NestedScrollType int type); } diff --git a/android/support/v4/view/PagerAdapter.java b/android/support/v4/view/PagerAdapter.java index a8fb099c..af8e076f 100644 --- a/android/support/v4/view/PagerAdapter.java +++ b/android/support/v4/view/PagerAdapter.java @@ -131,6 +131,7 @@ public abstract class PagerAdapter { /** * Called to inform the adapter of which item is currently considered to * be the "primary", that is the one show to the user as the current page. + * This method will not be invoked when the adapter contains no items. * * @param container The containing View from which the page will be removed. * @param position The page position that is now the primary. diff --git a/android/support/v4/view/ViewPager.java b/android/support/v4/view/ViewPager.java index 36d8696c..350fe955 100644 --- a/android/support/v4/view/ViewPager.java +++ b/android/support/v4/view/ViewPager.java @@ -1224,6 +1224,8 @@ public class ViewPager extends ViewGroup { } calculatePageOffsets(curItem, curIndex, oldCurInfo); + + mAdapter.setPrimaryItem(this, mCurItem, curItem.object); } if (DEBUG) { @@ -1233,8 +1235,6 @@ public class ViewPager extends ViewGroup { } } - mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null); - mAdapter.finishUpdate(this); // Check width measurement of current pages and drawing sort order. diff --git a/android/support/design/widget/DirectedAcyclicGraph.java b/android/support/v4/widget/DirectedAcyclicGraph.java index 85a32cd5..83c62c06 100644 --- a/android/support/design/widget/DirectedAcyclicGraph.java +++ b/android/support/v4/widget/DirectedAcyclicGraph.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * 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. @@ -14,10 +14,13 @@ * limitations under the License. */ -package android.support.design.widget; +package android.support.v4.widget; + +import static android.support.annotation.RestrictTo.Scope.LIBRARY; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.RestrictTo; import android.support.v4.util.Pools; import android.support.v4.util.SimpleArrayMap; @@ -27,8 +30,13 @@ import java.util.List; /** * A class which represents a simple directed acyclic graph. + * + * @param <T> Class for the data objects of this graph. + * + * @hide */ -final class DirectedAcyclicGraph<T> { +@RestrictTo(LIBRARY) +public final class DirectedAcyclicGraph<T> { private final Pools.Pool<ArrayList<T>> mListPool = new Pools.SimplePool<>(10); private final SimpleArrayMap<T, ArrayList<T>> mGraph = new SimpleArrayMap<>(); @@ -42,7 +50,7 @@ final class DirectedAcyclicGraph<T> { * * @param node the node to add */ - void addNode(@NonNull T node) { + public void addNode(@NonNull T node) { if (!mGraph.containsKey(node)) { mGraph.put(node, null); } @@ -51,7 +59,7 @@ final class DirectedAcyclicGraph<T> { /** * Returns true if the node is already present in the graph, false otherwise. */ - boolean contains(@NonNull T node) { + public boolean contains(@NonNull T node) { return mGraph.containsKey(node); } @@ -64,7 +72,7 @@ final class DirectedAcyclicGraph<T> { * @param node the parent node * @param incomingEdge the node which has is an incoming edge to {@code node} */ - void addEdge(@NonNull T node, @NonNull T incomingEdge) { + public void addEdge(@NonNull T node, @NonNull T incomingEdge) { if (!mGraph.containsKey(node) || !mGraph.containsKey(incomingEdge)) { throw new IllegalArgumentException("All nodes must be present in the graph before" + " being added as an edge"); @@ -86,7 +94,7 @@ final class DirectedAcyclicGraph<T> { * @return a list containing any incoming edges, or null if there are none. */ @Nullable - List getIncomingEdges(@NonNull T node) { + public List getIncomingEdges(@NonNull T node) { return mGraph.get(node); } @@ -97,7 +105,7 @@ final class DirectedAcyclicGraph<T> { * @return a list containing any outgoing edges, or null if there are none. */ @Nullable - List<T> getOutgoingEdges(@NonNull T node) { + public List<T> getOutgoingEdges(@NonNull T node) { ArrayList<T> result = null; for (int i = 0, size = mGraph.size(); i < size; i++) { ArrayList<T> edges = mGraph.valueAt(i); @@ -111,7 +119,14 @@ final class DirectedAcyclicGraph<T> { return result; } - boolean hasOutgoingEdges(@NonNull T node) { + /** + * Checks whether we have any outgoing edges for the given node (i.e. nodes which have + * an incoming edge from the given node). + * + * @return <code>true</code> if the node has any outgoing edges, <code>false</code> + * otherwise. + */ + public boolean hasOutgoingEdges(@NonNull T node) { for (int i = 0, size = mGraph.size(); i < size; i++) { ArrayList<T> edges = mGraph.valueAt(i); if (edges != null && edges.contains(node)) { @@ -124,7 +139,7 @@ final class DirectedAcyclicGraph<T> { /** * Clears the internal graph, and releases resources to pools. */ - void clear() { + public void clear() { for (int i = 0, size = mGraph.size(); i < size; i++) { ArrayList<T> edges = mGraph.valueAt(i); if (edges != null) { @@ -143,7 +158,7 @@ final class DirectedAcyclicGraph<T> { * of the graph. The node at the end of the list will have no dependencies on other nodes.</p> */ @NonNull - ArrayList<T> getSortedList() { + public ArrayList<T> getSortedList() { mSortResult.clear(); mSortTmpMarked.clear(); @@ -198,4 +213,4 @@ final class DirectedAcyclicGraph<T> { list.clear(); mListPool.release(list); } -}
\ No newline at end of file +} diff --git a/android/support/design/widget/ViewGroupUtils.java b/android/support/v4/widget/ViewGroupUtils.java index 5d8b5c74..986b4c20 100644 --- a/android/support/design/widget/ViewGroupUtils.java +++ b/android/support/v4/widget/ViewGroupUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * 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. @@ -14,16 +14,23 @@ * limitations under the License. */ -package android.support.design.widget; +package android.support.v4.widget; + +import static android.support.annotation.RestrictTo.Scope.LIBRARY; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; +import android.support.annotation.RestrictTo; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; -class ViewGroupUtils { +/** + * @hide + */ +@RestrictTo(LIBRARY) +public class ViewGroupUtils { private static final ThreadLocal<Matrix> sMatrix = new ThreadLocal<>(); private static final ThreadLocal<RectF> sRectF = new ThreadLocal<>(); @@ -65,7 +72,7 @@ class ViewGroupUtils { * @param descendant descendant view to reference * @param out rect to set to the bounds of the descendant view */ - static void getDescendantRect(ViewGroup parent, View descendant, Rect out) { + public static void getDescendantRect(ViewGroup parent, View descendant, Rect out) { out.set(0, 0, descendant.getWidth(), descendant.getHeight()); offsetDescendantRect(parent, descendant, out); } diff --git a/android/support/v7/preference/CollapsiblePreferenceGroupController.java b/android/support/v7/preference/CollapsiblePreferenceGroupController.java index e15ca18f..b63ff75b 100644 --- a/android/support/v7/preference/CollapsiblePreferenceGroupController.java +++ b/android/support/v7/preference/CollapsiblePreferenceGroupController.java @@ -166,7 +166,7 @@ final class CollapsiblePreferenceGroupController CharSequence summary = null; for (int i = collapsedIndex; i < flattenedPreferenceList.size(); i++) { final Preference preference = flattenedPreferenceList.get(i); - if (preference instanceof PreferenceGroup) { + if (preference instanceof PreferenceGroup || !preference.isVisible()) { continue; } final CharSequence title = preference.getTitle(); diff --git a/android/support/v7/util/SortedList.java b/android/support/v7/util/SortedList.java index af000a1e..bd07b01e 100644 --- a/android/support/v7/util/SortedList.java +++ b/android/support/v7/util/SortedList.java @@ -16,6 +16,7 @@ package android.support.v7.util; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import java.lang.reflect.Array; @@ -51,17 +52,23 @@ public class SortedList<T> { T[] mData; /** - * A copy of the previous list contents used during the merge phase of addAll. + * A reference to the previous set of data that is kept during a mutation operation (addAll or + * replaceAll). */ private T[] mOldData; + + /** + * The current index into mOldData that has not yet been processed during a mutation operation + * (addAll or replaceAll). + */ private int mOldDataStart; private int mOldDataSize; /** - * The size of the valid portion of mData during the merge phase of addAll. + * The current index into the new data that has not yet been processed during a mutation + * operation (addAll or replaceAll). */ - private int mMergedSize; - + private int mNewDataStart; /** * The callback instance that controls the behavior of the SortedList and get notified when @@ -133,7 +140,7 @@ public class SortedList<T> { * @see Callback#areContentsTheSame(Object, Object)} */ public int add(T item) { - throwIfMerging(); + throwIfInMutationOperation(); return add(item, true); } @@ -142,30 +149,30 @@ public class SortedList<T> { * except the callback events may be in a different order/granularity since addAll can batch * them for better performance. * <p> - * If allowed, may modify the input array and even take the ownership over it in order - * to avoid extra memory allocation during sorting and deduplication. - * </p> + * If allowed, will reference the input array during, and possibly after, the operation to avoid + * extra memory allocation, in which case you should not continue to reference or modify the + * array yourself. + * <p> * @param items Array of items to be added into the list. - * @param mayModifyInput If true, SortedList is allowed to modify the input. - * @see SortedList#addAll(Object[] items) + * @param mayModifyInput If true, SortedList is allowed to modify and permanently reference the + * input array. + * @see SortedList#addAll(T[] items) */ public void addAll(T[] items, boolean mayModifyInput) { - throwIfMerging(); + throwIfInMutationOperation(); if (items.length == 0) { return; } + if (mayModifyInput) { addAllInternal(items); } else { - T[] copy = (T[]) Array.newInstance(mTClass, items.length); - System.arraycopy(items, 0, copy, 0, items.length); - addAllInternal(copy); + addAllInternal(copyArray(items)); } - } /** - * Adds the given items to the list. Does not modify the input. + * Adds the given items to the list. Does not modify or retain the input. * * @see SortedList#addAll(T[] items, boolean mayModifyInput) * @@ -176,7 +183,7 @@ public class SortedList<T> { } /** - * Adds the given items to the list. Does not modify the input. + * Adds the given items to the list. Does not modify or retain the input. * * @see SortedList#addAll(T[] items, boolean mayModifyInput) * @@ -187,27 +194,134 @@ public class SortedList<T> { addAll(items.toArray(copy), true); } - private void addAllInternal(T[] newItems) { - final boolean forceBatchedUpdates = !(mCallback instanceof BatchedCallback); - if (forceBatchedUpdates) { - beginBatchedUpdates(); + /** + * Replaces the current items with the new items, dispatching {@link ListUpdateCallback} events + * for each change detected as appropriate. + * <p> + * If allowed, will reference the input array during, and possibly after, the operation to avoid + * extra memory allocation, in which case you should not continue to reference or modify the + * array yourself. + * <p> + * Note: this method does not detect moves or dispatch + * {@link ListUpdateCallback#onMoved(int, int)} events. It instead treats moves as a remove + * followed by an add and therefore dispatches {@link ListUpdateCallback#onRemoved(int, int)} + * and {@link ListUpdateCallback#onRemoved(int, int)} events. See {@link DiffUtil} if you want + * your implementation to dispatch move events. + * <p> + * @param items Array of items to replace current items. + * @param mayModifyInput If true, SortedList is allowed to modify and permanently reference the + * input array. + * @see #replaceAll(T[]) + */ + public void replaceAll(@NonNull T[] items, boolean mayModifyInput) { + throwIfInMutationOperation(); + + if (mayModifyInput) { + replaceAllInternal(items); + } else { + replaceAllInternal(copyArray(items)); } + } - mOldData = mData; - mOldDataStart = 0; - mOldDataSize = mSize; + /** + * Replaces the current items with the new items, dispatching {@link ListUpdateCallback} events + * for each change detected as appropriate. Does not modify or retain the input. + * + * @see #replaceAll(T[], boolean) + * + * @param items Array of items to replace current items. + */ + public void replaceAll(@NonNull T... items) { + replaceAll(items, false); + } - Arrays.sort(newItems, mCallback); // Arrays.sort is stable. + /** + * Replaces the current items with the new items, dispatching {@link ListUpdateCallback} events + * for each change detected as appropriate. Does not modify or retain the input. + * + * @see #replaceAll(T[], boolean) + * + * @param items Array of items to replace current items. + */ + public void replaceAll(@NonNull Collection<T> items) { + T[] copy = (T[]) Array.newInstance(mTClass, items.size()); + replaceAll(items.toArray(copy), true); + } + + private void addAllInternal(T[] newItems) { + if (newItems.length < 1) { + return; + } + + final int newSize = sortAndDedup(newItems); - final int newSize = deduplicate(newItems); if (mSize == 0) { mData = newItems; mSize = newSize; - mMergedSize = newSize; mCallback.onInserted(0, newSize); } else { merge(newItems, newSize); } + } + + private void replaceAllInternal(@NonNull T[] newData) { + final boolean forceBatchedUpdates = !(mCallback instanceof BatchedCallback); + if (forceBatchedUpdates) { + beginBatchedUpdates(); + } + + mOldDataStart = 0; + mOldDataSize = mSize; + mOldData = mData; + + mNewDataStart = 0; + int newSize = sortAndDedup(newData); + mData = (T[]) Array.newInstance(mTClass, newSize); + + while (mNewDataStart < newSize || mOldDataStart < mOldDataSize) { + if (mOldDataStart >= mOldDataSize) { + int insertIndex = mNewDataStart; + int itemCount = newSize - mNewDataStart; + System.arraycopy(newData, insertIndex, mData, insertIndex, itemCount); + mNewDataStart += itemCount; + mSize += itemCount; + mCallback.onInserted(insertIndex, itemCount); + break; + } + if (mNewDataStart >= newSize) { + int itemCount = mOldDataSize - mOldDataStart; + mSize -= itemCount; + mCallback.onRemoved(mNewDataStart, itemCount); + break; + } + + T oldItem = mOldData[mOldDataStart]; + T newItem = newData[mNewDataStart]; + + int result = mCallback.compare(oldItem, newItem); + if (result < 0) { + replaceAllRemove(); + } else if (result > 0) { + replaceAllInsert(newItem); + } else { + if (!mCallback.areItemsTheSame(oldItem, newItem)) { + // The items aren't the same even though they were supposed to occupy the same + // place, so both notify to remove and add an item in the current location. + replaceAllRemove(); + replaceAllInsert(newItem); + } else { + mData[mNewDataStart] = newItem; + mOldDataStart++; + mNewDataStart++; + if (!mCallback.areContentsTheSame(oldItem, newItem)) { + // The item is the same but the contents have changed, so notify that an + // onChanged event has occurred. + mCallback.onChanged(mNewDataStart - 1, 1, + mCallback.getChangePayload(oldItem, newItem)); + } + } + } + } mOldData = null; @@ -216,17 +330,33 @@ public class SortedList<T> { } } + private void replaceAllInsert(T newItem) { + mData[mNewDataStart] = newItem; + mNewDataStart++; + mSize++; + mCallback.onInserted(mNewDataStart - 1, 1); + } + + private void replaceAllRemove() { + mSize--; + mOldDataStart++; + mCallback.onRemoved(mNewDataStart, 1); + } + /** - * Remove duplicate items, leaving only the last item from each group of "same" items. - * Move the remaining items to the beginning of the array. + * Sorts and removes duplicate items, leaving only the last item from each group of "same" + * items. Move the remaining items to the beginning of the array. * * @return Number of deduplicated items at the beginning of the array. */ - private int deduplicate(T[] items) { + private int sortAndDedup(@NonNull T[] items) { if (items.length == 0) { - throw new IllegalArgumentException("Input array must be non-empty"); + return 0; } + // Arrays.sort is stable. + Arrays.sort(items, mCallback); + // Keep track of the range of equal items at the end of the output. // Start with the range containing just the first item. int rangeStart = 0; @@ -236,9 +366,6 @@ public class SortedList<T> { T currentItem = items[i]; int compare = mCallback.compare(items[rangeStart], currentItem); - if (compare > 0) { - throw new IllegalArgumentException("Input must be sorted in ascending order."); - } if (compare == 0) { // The range of equal items continues, update it. @@ -278,27 +405,36 @@ public class SortedList<T> { * This method assumes that newItems are sorted and deduplicated. */ private void merge(T[] newData, int newDataSize) { + final boolean forceBatchedUpdates = !(mCallback instanceof BatchedCallback); + if (forceBatchedUpdates) { + beginBatchedUpdates(); + } + + mOldData = mData; + mOldDataStart = 0; + mOldDataSize = mSize; + final int mergedCapacity = mSize + newDataSize + CAPACITY_GROWTH; mData = (T[]) Array.newInstance(mTClass, mergedCapacity); - mMergedSize = 0; + mNewDataStart = 0; int newDataStart = 0; while (mOldDataStart < mOldDataSize || newDataStart < newDataSize) { if (mOldDataStart == mOldDataSize) { // No more old items, copy the remaining new items. int itemCount = newDataSize - newDataStart; - System.arraycopy(newData, newDataStart, mData, mMergedSize, itemCount); - mMergedSize += itemCount; + System.arraycopy(newData, newDataStart, mData, mNewDataStart, itemCount); + mNewDataStart += itemCount; mSize += itemCount; - mCallback.onInserted(mMergedSize - itemCount, itemCount); + mCallback.onInserted(mNewDataStart - itemCount, itemCount); break; } if (newDataStart == newDataSize) { // No more new items, copy the remaining old items. int itemCount = mOldDataSize - mOldDataStart; - System.arraycopy(mOldData, mOldDataStart, mData, mMergedSize, itemCount); - mMergedSize += itemCount; + System.arraycopy(mOldData, mOldDataStart, mData, mNewDataStart, itemCount); + mNewDataStart += itemCount; break; } @@ -307,36 +443,47 @@ public class SortedList<T> { int compare = mCallback.compare(oldItem, newItem); if (compare > 0) { // New item is lower, output it. - mData[mMergedSize++] = newItem; + mData[mNewDataStart++] = newItem; mSize++; newDataStart++; - mCallback.onInserted(mMergedSize - 1, 1); + mCallback.onInserted(mNewDataStart - 1, 1); } else if (compare == 0 && mCallback.areItemsTheSame(oldItem, newItem)) { // Items are the same. Output the new item, but consume both. - mData[mMergedSize++] = newItem; + mData[mNewDataStart++] = newItem; newDataStart++; mOldDataStart++; if (!mCallback.areContentsTheSame(oldItem, newItem)) { - mCallback.onChanged(mMergedSize - 1, 1, + mCallback.onChanged(mNewDataStart - 1, 1, mCallback.getChangePayload(oldItem, newItem)); } } else { // Old item is lower than or equal to (but not the same as the new). Output it. // New item with the same sort order will be inserted later. - mData[mMergedSize++] = oldItem; + mData[mNewDataStart++] = oldItem; mOldDataStart++; } } + + mOldData = null; + + if (forceBatchedUpdates) { + endBatchedUpdates(); + } } - private void throwIfMerging() { + /** + * Throws an exception if called while we are in the middle of a mutation operation (addAll or + * replaceAll). + */ + private void throwIfInMutationOperation() { if (mOldData != null) { - throw new IllegalStateException("Cannot call this method from within addAll"); + throw new IllegalStateException("Data cannot be mutated in the middle of a batch " + + "update operation such as addAll or replaceAll."); } } /** - * Batches adapter updates that happen between calling this method until calling + * Batches adapter updates that happen after calling this method and before calling * {@link #endBatchedUpdates()}. For example, if you add multiple items in a loop * and they are placed into consecutive indices, SortedList calls * {@link Callback#onInserted(int, int)} only once with the proper item count. If an event @@ -368,7 +515,7 @@ public class SortedList<T> { * has no effect. */ public void beginBatchedUpdates() { - throwIfMerging(); + throwIfInMutationOperation(); if (mCallback instanceof BatchedCallback) { return; } @@ -382,7 +529,7 @@ public class SortedList<T> { * Ends the update transaction and dispatches any remaining event to the callback. */ public void endBatchedUpdates() { - throwIfMerging(); + throwIfInMutationOperation(); if (mCallback instanceof BatchedCallback) { ((BatchedCallback) mCallback).dispatchLastEvent(); } @@ -424,7 +571,7 @@ public class SortedList<T> { * @return True if item is removed, false if item cannot be found in the list. */ public boolean remove(T item) { - throwIfMerging(); + throwIfInMutationOperation(); return remove(item, true); } @@ -436,7 +583,7 @@ public class SortedList<T> { * @return The removed item. */ public T removeItemAt(int index) { - throwIfMerging(); + throwIfInMutationOperation(); T item = get(index); removeItemAtIndex(index, true); return item; @@ -481,7 +628,7 @@ public class SortedList<T> { * @see #add(Object) */ public void updateItemAt(int index, T item) { - throwIfMerging(); + throwIfInMutationOperation(); final T existing = get(index); // assume changed if the same object is given back boolean contentsChanged = existing == item || !mCallback.areContentsTheSame(existing, item); @@ -535,7 +682,7 @@ public class SortedList<T> { * @see #add(Object) */ public void recalculatePositionOfItemAt(int index) { - throwIfMerging(); + throwIfInMutationOperation(); // TODO can be improved final T item = get(index); removeItemAtIndex(index, false); @@ -562,8 +709,8 @@ public class SortedList<T> { if (mOldData != null) { // The call is made from a callback during addAll execution. The data is split // between mData and mOldData. - if (index >= mMergedSize) { - return mOldData[index - mMergedSize + mOldDataStart]; + if (index >= mNewDataStart) { + return mOldData[index - mNewDataStart + mOldDataStart]; } } return mData[index]; @@ -579,13 +726,13 @@ public class SortedList<T> { */ public int indexOf(T item) { if (mOldData != null) { - int index = findIndexOf(item, mData, 0, mMergedSize, LOOKUP); + int index = findIndexOf(item, mData, 0, mNewDataStart, LOOKUP); if (index != INVALID_POSITION) { return index; } index = findIndexOf(item, mOldData, mOldDataStart, mOldDataSize, LOOKUP); if (index != INVALID_POSITION) { - return index - mOldDataStart + mMergedSize; + return index - mOldDataStart + mNewDataStart; } return INVALID_POSITION; } @@ -662,11 +809,17 @@ public class SortedList<T> { mSize++; } + private T[] copyArray(T[] items) { + T[] copy = (T[]) Array.newInstance(mTClass, items.length); + System.arraycopy(items, 0, copy, 0, items.length); + return copy; + } + /** * Removes all items from the SortedList. */ public void clear() { - throwIfMerging(); + throwIfInMutationOperation(); if (mSize == 0) { return; } @@ -722,8 +875,8 @@ public class SortedList<T> { * so * that you can change its behavior depending on your UI. * <p> - * For example, if you are using SortedList with a {@link android.support.v7.widget.RecyclerView.Adapter - * RecyclerView.Adapter}, you should + * For example, if you are using SortedList with a + * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should * return whether the items' visual representations are the same or not. * * @param oldItem The previous representation of the object. @@ -734,7 +887,7 @@ public class SortedList<T> { abstract public boolean areContentsTheSame(T2 oldItem, T2 newItem); /** - * Called by the SortedList to decide whether two object represent the same Item or not. + * Called by the SortedList to decide whether two objects represent the same Item or not. * <p> * For example, if your items have unique ids, this method should check their equality. * diff --git a/android/support/v7/util/SortedListTest.java b/android/support/v7/util/SortedListTest.java index 47d2ac0f..f8bc496c 100644 --- a/android/support/v7/util/SortedListTest.java +++ b/android/support/v7/util/SortedListTest.java @@ -27,11 +27,15 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.LinkedList; import java.util.List; +import java.util.Queue; import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; @RunWith(JUnit4.class) @SmallTest @@ -44,6 +48,8 @@ public class SortedListTest extends TestCase { List<Pair> mUpdates = new ArrayList<Pair>(); private boolean mPayloadChanges = false; List<PayloadChange> mPayloadUpdates = new ArrayList<>(); + Queue<AssertListStateRunnable> mCallbackRunnables; + List<Event> mEvents = new ArrayList<>(); private SortedList.Callback<Item> mCallback; InsertedCallback<Item> mInsertedCallback; ChangedCallback<Item> mChangedCallback; @@ -67,6 +73,7 @@ public class SortedListTest extends TestCase { @Before public void setUp() throws Exception { super.setUp(); + mCallback = new SortedList.Callback<Item>() { @Override public int compare(Item o1, Item o2) { @@ -75,28 +82,35 @@ public class SortedListTest extends TestCase { @Override public void onInserted(int position, int count) { + mEvents.add(new Event(TYPE.ADD, position, count)); mAdditions.add(new Pair(position, count)); if (mInsertedCallback != null) { mInsertedCallback.onInserted(position, count); } + pollAndRun(mCallbackRunnables); } @Override public void onRemoved(int position, int count) { + mEvents.add(new Event(TYPE.REMOVE, position, count)); mRemovals.add(new Pair(position, count)); + pollAndRun(mCallbackRunnables); } @Override public void onMoved(int fromPosition, int toPosition) { + mEvents.add(new Event(TYPE.MOVE, fromPosition, toPosition)); mMoves.add(new Pair(fromPosition, toPosition)); } @Override public void onChanged(int position, int count) { + mEvents.add(new Event(TYPE.CHANGE, position, count)); mUpdates.add(new Pair(position, count)); if (mChangedCallback != null) { mChangedCallback.onChanged(position, count); } + pollAndRun(mCallbackRunnables); } @Override @@ -110,7 +124,7 @@ public class SortedListTest extends TestCase { @Override public boolean areContentsTheSame(Item oldItem, Item newItem) { - return oldItem.cmpField == newItem.cmpField && oldItem.data == newItem.data; + return oldItem.data == newItem.data; } @Override @@ -127,11 +141,41 @@ public class SortedListTest extends TestCase { return null; } }; - mInsertedCallback = null; - mChangedCallback = null; mList = new SortedList<Item>(Item.class, mCallback); } + private void pollAndRun(Queue<AssertListStateRunnable> queue) { + if (queue != null) { + Runnable runnable = queue.poll(); + assertNotNull(runnable); + runnable.run(); + } + } + + @Test + public void testValidMethodsDuringOnInsertedCallbackFromEmptyList() { + + final Item[] items = + new Item[] {new Item(0), new Item(1), new Item(2)}; + + final AtomicInteger atomicInteger = new AtomicInteger(0); + mInsertedCallback = new InsertedCallback<Item>() { + @Override + public void onInserted(int position, int count) { + for (int i = 0; i < count; i++) { + assertEquals(mList.get(i), items[i]); + assertEquals(mList.indexOf(items[i]), i); + atomicInteger.incrementAndGet(); + } + } + }; + + mList.add(items[0]); + mList.clear(); + mList.addAll(items, false); + assertEquals(4, atomicInteger.get()); + } + @Test public void testEmpty() { assertEquals("empty", mList.size(), 0); @@ -139,16 +183,16 @@ public class SortedListTest extends TestCase { @Test public void testAdd() { - Item item = new Item(); + Item item = new Item(1); assertEquals(insert(item), 0); assertEquals(size(), 1); assertTrue(mAdditions.contains(new Pair(0, 1))); - Item item2 = new Item(); + Item item2 = new Item(2); item2.cmpField = item.cmpField + 1; assertEquals(insert(item2), 1); assertEquals(size(), 2); assertTrue(mAdditions.contains(new Pair(1, 1))); - Item item3 = new Item(); + Item item3 = new Item(3); item3.cmpField = item.cmpField - 1; mAdditions.clear(); assertEquals(insert(item3), 0); @@ -158,9 +202,8 @@ public class SortedListTest extends TestCase { @Test public void testAddDuplicate() { - Item item = new Item(); - Item item2 = new Item(item.id, item.cmpField); - item2.data = item.data; + Item item = new Item(1); + Item item2 = new Item(item.id); insert(item); assertEquals(0, insert(item2)); assertEquals(1, size()); @@ -170,7 +213,7 @@ public class SortedListTest extends TestCase { @Test public void testRemove() { - Item item = new Item(); + Item item = new Item(1); assertFalse(remove(item)); assertEquals(0, mRemovals.size()); insert(item); @@ -184,8 +227,8 @@ public class SortedListTest extends TestCase { @Test public void testRemove2() { - Item item = new Item(); - Item item2 = new Item(item.cmpField); + Item item = new Item(1); + Item item2 = new Item(2, 1, 1); insert(item); assertFalse(remove(item2)); assertEquals(0, mRemovals.size()); @@ -218,11 +261,12 @@ public class SortedListTest extends TestCase { Random random = new Random(System.nanoTime()); List<Item> copy = new ArrayList<Item>(); StringBuilder log = new StringBuilder(); + int id = 1; try { for (int i = 0; i < 10000; i++) { switch (random.nextInt(3)) { case 0://ADD - Item item = new Item(); + Item item = new Item(id++); copy.add(item); insert(item); log.append("add ").append(item).append("\n"); @@ -241,12 +285,13 @@ public class SortedListTest extends TestCase { int index = random.nextInt(mList.size()); item = mList.get(index); // TODO this cannot work - Item newItem = new Item(item.id, item.cmpField); - log.append("update ").append(item).append(" to ").append(newItem) - .append("\n"); + Item newItem = + new Item(item.id, item.cmpField, random.nextInt(1000)); while (newItem.data == item.data) { newItem.data = random.nextInt(1000); } + log.append("update ").append(item).append(" to ").append(newItem) + .append("\n"); int itemIndex = mList.add(newItem); copy.remove(item); copy.add(newItem); @@ -258,10 +303,12 @@ public class SortedListTest extends TestCase { if (copy.size() > 0) { int index = random.nextInt(mList.size()); item = mList.get(index); - Item newItem = new Item(item.id, random.nextInt()); + Item newItem = new Item(item.id, random.nextInt(), random.nextInt()); mList.updateItemAt(index, newItem); copy.remove(item); copy.add(newItem); + log.append("update at ").append(index).append(" ").append(item) + .append(" to ").append(newItem).append("\n"); } } int lastCmp = Integer.MIN_VALUE; @@ -299,14 +346,21 @@ public class SortedListTest extends TestCase { Item[] items = new Item[count]; int id = idFrom; for (int i = 0; i < count; i++) { - Item item = new Item(id, id); - item.data = id; + Item item = new Item(id); items[i] = item; id += idStep; } return items; } + private static Item[] createItemsFromInts(int ... ints) { + Item[] items = new Item[ints.length]; + for (int i = ints.length - 1; i >= 0; i--) { + items[i] = new Item(ints[i]); + } + return items; + } + private static Item[] shuffle(Item[] items) { Random random = new Random(System.nanoTime()); final int count = items.length; @@ -493,8 +547,7 @@ public class SortedListTest extends TestCase { int uniqueId = 0; for (int cmpField = 0; cmpField < maxCmpField; cmpField++) { for (int id = 0; id < idsPerCmpField; id++) { - Item item = new Item(uniqueId++, cmpField); - item.data = generation; + Item item = new Item(uniqueId++, cmpField, generation); items[index++] = item; } } @@ -548,13 +601,13 @@ public class SortedListTest extends TestCase { @Test public void testAddAllStableSort() { int id = 0; - Item item = new Item(id++, 0); + Item item = new Item(id++, 0, 0); mList.add(item); // Create a few items with the same sort order. Item[] items = new Item[3]; for (int i = 0; i < 3; i++) { - items[i] = new Item(id++, item.cmpField); + items[i] = new Item(id++, item.cmpField, 0); assertEquals(0, mCallback.compare(item, items[i])); } @@ -576,6 +629,7 @@ public class SortedListTest extends TestCase { item.data = 1; } + mInsertedCallback = new InsertedCallback<Item>() { @Override public void onInserted(int position, int count) { @@ -585,6 +639,7 @@ public class SortedListTest extends TestCase { assertEquals(i * 2, mList.get(i).id); } assertIntegrity(5, "onInserted(" + position + ", " + count + ")"); + } }; @@ -639,7 +694,7 @@ public class SortedListTest extends TestCase { @Override public void onInserted(int position, int count) { try { - mList.add(new Item()); + mList.add(new Item(1)); fail("add must throw from within a callback"); } catch (IllegalStateException e) { } @@ -729,14 +784,14 @@ public class SortedListTest extends TestCase { @Test public void testAddExistingItemCallsChangeWithPayload() { mList.addAll( - new Item(1, 10), - new Item(2, 20), - new Item(3, 30) + new Item(1), + new Item(2), + new Item(3) ); mPayloadChanges = true; // add an item with the same id but a new data field i.e. send an update - final Item twoUpdate = new Item(2, 20); + final Item twoUpdate = new Item(2); twoUpdate.data = 1337; mList.add(twoUpdate); assertEquals(1, mPayloadUpdates.size()); @@ -750,14 +805,14 @@ public class SortedListTest extends TestCase { @Test public void testUpdateItemCallsChangeWithPayload() { mList.addAll( - new Item(1, 10), - new Item(2, 20), - new Item(3, 30) + new Item(1), + new Item(2), + new Item(3) ); mPayloadChanges = true; // add an item with the same id but a new data field i.e. send an update - final Item twoUpdate = new Item(2, 20); + final Item twoUpdate = new Item(2); twoUpdate.data = 1337; mList.updateItemAt(1, twoUpdate); assertEquals(1, mPayloadUpdates.size()); @@ -772,16 +827,16 @@ public class SortedListTest extends TestCase { @Test public void testAddMultipleExistingItemCallsChangeWithPayload() { mList.addAll( - new Item(1, 10), - new Item(2, 20), - new Item(3, 30) + new Item(1), + new Item(2), + new Item(3) ); mPayloadChanges = true; // add two items with the same ids but a new data fields i.e. send two updates - final Item twoUpdate = new Item(2, 20); + final Item twoUpdate = new Item(2); twoUpdate.data = 222; - final Item threeUpdate = new Item(3, 30); + final Item threeUpdate = new Item(3); threeUpdate.data = 333; mList.addAll(twoUpdate, threeUpdate); assertEquals(2, mPayloadUpdates.size()); @@ -796,6 +851,648 @@ public class SortedListTest extends TestCase { assertEquals(3, size()); } + @Test + public void replaceAll_mayModifyInputFalse_doesNotModify() { + mList.addAll( + new Item(1), + new Item(2) + ); + Item replacement0 = new Item(4); + Item replacement1 = new Item(3); + Item[] replacements = new Item[]{ + replacement0, + replacement1 + }; + + mList.replaceAll(replacements, false); + + assertSame(replacement0, replacements[0]); + assertSame(replacement1, replacements[1]); + } + + @Test + public void replaceAll_varArgs_isEquivalentToDefault() { + mList.addAll( + new Item(1), + new Item(2) + ); + Item replacement0 = new Item(3); + Item replacement1 = new Item(4); + + mList.replaceAll(replacement0, replacement1); + + assertEquals(mList.get(0), replacement0); + assertEquals(mList.get(1), replacement1); + assertEquals(2, mList.size()); + } + + @Test + public void replaceAll_collection_isEquivalentToDefaultWithMayModifyInputFalse() { + mList.addAll( + new Item(1), + new Item(2) + ); + Item replacement0 = new Item(4); + Item replacement1 = new Item(3); + List<Item> replacements = new ArrayList<>(); + replacements.add(replacement0); + replacements.add(replacement1); + + mList.replaceAll(replacements); + + assertEquals(mList.get(0), replacement1); + assertEquals(mList.get(1), replacement0); + assertSame(replacements.get(0), replacement0); + assertSame(replacements.get(1), replacement1); + assertEquals(2, mList.size()); + } + + @Test + public void replaceAll_callsChangeWithPayload() { + mList.addAll( + new Item(1), + new Item(2), + new Item(3) + ); + mPayloadChanges = true; + final Item twoUpdate = new Item(2); + twoUpdate.data = 222; + final Item threeUpdate = new Item(3); + threeUpdate.data = 333; + + mList.replaceAll(twoUpdate, threeUpdate); + + assertEquals(2, mPayloadUpdates.size()); + final PayloadChange update1 = mPayloadUpdates.get(0); + assertEquals(0, update1.position); + assertEquals(1, update1.count); + assertEquals(222, update1.payload); + final PayloadChange update2 = mPayloadUpdates.get(1); + assertEquals(1, update2.position); + assertEquals(1, update2.count); + assertEquals(333, update2.payload); + } + + @Test + public void replaceAll_totallyEquivalentData_worksCorrectly() { + Item[] items1 = createItemsFromInts(1, 2, 3); + Item[] items2 = createItemsFromInts(1, 2, 3); + mList.addAll(items1); + mEvents.clear(); + + mList.replaceAll(items2); + + assertEquals(0, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + } + + @Test + public void replaceAll_removalsAndAdds1_worksCorrectly() { + Item[] items1 = createItemsFromInts(1, 3, 5); + Item[] items2 = createItemsFromInts(2, 4); + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 3, 5))); + mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 5))); + mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 4, 5))); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0)); + assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(1)); + assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(2)); + assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(3)); + assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(4)); + assertEquals(5, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_removalsAndAdds2_worksCorrectly() { + Item[] items1 = createItemsFromInts(2, 4); + Item[] items2 = createItemsFromInts(1, 3, 5); + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 4))); + mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3, 4))); + mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3))); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0)); + assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(1)); + assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(2)); + assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(3)); + assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(4)); + assertEquals(5, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_removalsAndAdds3_worksCorrectly() { + Item[] items1 = createItemsFromInts(1, 3, 5); + Item[] items2 = createItemsFromInts(2, 3, 4); + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 3, 5))); + mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 3, 4, 5))); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0)); + assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(1)); + assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(2)); + assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(3)); + assertEquals(4, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_removalsAndAdds4_worksCorrectly() { + Item[] items1 = createItemsFromInts(2, 3, 4); + Item[] items2 = createItemsFromInts(1, 3, 5); + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3, 4))); + mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3))); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0)); + assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(1)); + assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(2)); + assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(3)); + assertEquals(4, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_removalsAndAdds5_worksCorrectly() { + Item[] items1 = createItemsFromInts(1, 2, 3); + Item[] items2 = createItemsFromInts(3, 4, 5); + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.REMOVE, 0, 2), mEvents.get(0)); + assertEquals(new Event(TYPE.ADD, 1, 2), mEvents.get(1)); + assertEquals(2, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_removalsAndAdds6_worksCorrectly() { + Item[] items1 = createItemsFromInts(3, 4, 5); + Item[] items2 = createItemsFromInts(1, 2, 3); + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.ADD, 0, 2), mEvents.get(0)); + assertEquals(new Event(TYPE.REMOVE, 3, 2), mEvents.get(1)); + assertEquals(2, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_move1_worksCorrectly() { + Item[] items1 = createItemsFromInts(1, 2, 3); + Item[] items2 = new Item[]{ + new Item(2), + new Item(3), + new Item(1, 4, 1)}; + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0)); + assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(1)); + assertEquals(2, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_move2_worksCorrectly() { + Item[] items1 = createItemsFromInts(1, 2, 3); + Item[] items2 = new Item[]{ + new Item(3, 0, 3), + new Item(1), + new Item(2)}; + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0)); + assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(1)); + assertEquals(2, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_move3_worksCorrectly() { + Item[] items1 = createItemsFromInts(1, 3, 5, 7, 9); + Item[] items2 = new Item[]{ + new Item(3, 0, 3), + new Item(1), + new Item(5), + new Item(9), + new Item(7, 10, 7), + }; + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(3, 0, 3), + new Item(1), + new Item(5), + new Item(7), + new Item(9) + )); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(3, 0, 3), + new Item(1), + new Item(5), + new Item(9) + )); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0)); + assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(1)); + assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(2)); + assertEquals(new Event(TYPE.ADD, 4, 1), mEvents.get(3)); + assertEquals(4, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_move4_worksCorrectly() { + Item[] items1 = createItemsFromInts(1, 3, 5, 7, 9); + Item[] items2 = new Item[]{ + new Item(3), + new Item(1, 4, 1), + new Item(5), + new Item(9, 6, 9), + new Item(7), + }; + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(3), + new Item(1, 4, 1), + new Item(5), + new Item(7), + new Item(9) + )); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(3), + new Item(1, 4, 1), + new Item(5), + new Item(9, 6, 9), + new Item(7), + new Item(9) + )); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0)); + assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(1)); + assertEquals(new Event(TYPE.ADD, 3, 1), mEvents.get(2)); + assertEquals(new Event(TYPE.REMOVE, 5, 1), mEvents.get(3)); + assertEquals(4, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_move5_worksCorrectly() { + Item[] items1 = createItemsFromInts(1, 3, 5, 7, 9); + Item[] items2 = new Item[]{ + new Item(9, 1, 9), + new Item(7, 3, 7), + new Item(5), + new Item(3, 7, 3), + new Item(1, 9, 1), + }; + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(9, 1, 9), + new Item(3), + new Item(5), + new Item(7), + new Item(9) + )); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(9, 1, 9), + new Item(5), + new Item(7), + new Item(9) + )); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(9, 1, 9), + new Item(7, 3, 7), + new Item(5), + new Item(7), + new Item(9) + )); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(9, 1, 9), + new Item(7, 3, 7), + new Item(5), + new Item(9) + )); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(9, 1, 9), + new Item(7, 3, 7), + new Item(5), + new Item(3, 7, 3), + new Item(9) + )); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(9, 1, 9), + new Item(7, 3, 7), + new Item(5), + new Item(3, 7, 3) + )); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0)); + assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(1)); + assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(2)); + assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(3)); + assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(4)); + assertEquals(new Event(TYPE.ADD, 3, 1), mEvents.get(5)); + assertEquals(new Event(TYPE.REMOVE, 4, 1), mEvents.get(6)); + assertEquals(new Event(TYPE.ADD, 4, 1), mEvents.get(7)); + assertEquals(8, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_orderSameItemDifferent_worksCorrectly() { + Item[] items1 = new Item[]{ + new Item(1), + new Item(2, 3, 2), + new Item(5) + }; + Item[] items2 = new Item[]{ + new Item(1), + new Item(4, 3, 4), + new Item(5) + }; + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(0)); + assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(1)); + assertEquals(2, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_orderSameItemSameContentsDifferent_worksCorrectly() { + Item[] items1 = new Item[]{ + new Item(1), + new Item(3, 3, 2), + new Item(5) + }; + Item[] items2 = new Item[]{ + new Item(1), + new Item(3, 3, 4), + new Item(5) + }; + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.CHANGE, 1, 1), mEvents.get(0)); + assertEquals(1, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_allTypesOfChanges1_worksCorrectly() { + Item[] items1 = createItemsFromInts(2, 5, 6); + Item[] items2 = new Item[]{ + new Item(1), + new Item(3, 2, 3), + new Item(6, 6, 7) + }; + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 5, 6))); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(1), + new Item(3, 2, 3), + new Item(5), + new Item(6) + )); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(1), + new Item(3, 2, 3), + new Item(6) + )); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0)); + assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(1)); + assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(2)); + assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(3)); + assertEquals(new Event(TYPE.CHANGE, 2, 1), mEvents.get(4)); + assertEquals(5, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_allTypesOfChanges2_worksCorrectly() { + Item[] items1 = createItemsFromInts(1, 4, 6); + Item[] items2 = new Item[]{ + new Item(1, 1, 2), + new Item(3), + new Item(5, 4, 5) + }; + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(1, 1, 2), + new Item(3), + new Item(4), + new Item(6) + )); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(1, 1, 2), + new Item(3), + new Item(6) + )); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(1, 1, 2), + new Item(3), + new Item(5, 4, 5), + new Item(6) + )); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.CHANGE, 0, 1), mEvents.get(0)); + assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(1)); + assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(2)); + assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(3)); + assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(4)); + assertEquals(5, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_allTypesOfChanges3_worksCorrectly() { + Item[] items1 = createItemsFromInts(1, 2); + Item[] items2 = new Item[]{ + new Item(2, 2, 3), + new Item(3, 2, 4), + new Item(5) + }; + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable( + new Item(2, 2, 3) + )); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.replaceAll(items2); + + assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0)); + assertEquals(new Event(TYPE.CHANGE, 0, 1), mEvents.get(1)); + assertEquals(new Event(TYPE.ADD, 1, 2), mEvents.get(2)); + assertEquals(3, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + + @Test + public void replaceAll_newItemsAreIdentical_resultIsDeduped() { + Item[] items = createItemsFromInts(1, 1); + mList.replaceAll(items); + + assertEquals(new Item(1), mList.get(0)); + assertEquals(1, mList.size()); + } + + @Test + public void replaceAll_newItemsUnsorted_resultIsSorted() { + Item[] items = createItemsFromInts(2, 1); + mList.replaceAll(items); + + assertEquals(new Item(1), mList.get(0)); + assertEquals(new Item(2), mList.get(1)); + assertEquals(2, mList.size()); + } + + @Test + public void replaceAll_calledAfterBeginBatchedUpdates_worksCorrectly() { + Item[] items1 = createItemsFromInts(1, 2, 3); + Item[] items2 = createItemsFromInts(4, 5, 6); + mList.addAll(items1); + mEvents.clear(); + + mCallbackRunnables = new LinkedList<>(); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + mCallbackRunnables.add(new AssertListStateRunnable(items2)); + + mList.beginBatchedUpdates(); + mList.replaceAll(items2); + mList.endBatchedUpdates(); + + assertEquals(new Event(TYPE.REMOVE, 0, 3), mEvents.get(0)); + assertEquals(new Event(TYPE.ADD, 0, 3), mEvents.get(1)); + assertEquals(2, mEvents.size()); + assertTrue(sortedListEquals(mList, items2)); + assertTrue(mCallbackRunnables.isEmpty()); + } + private int size() { return mList.size(); } @@ -810,63 +1507,33 @@ public class SortedListTest extends TestCase { static class Item { - static int idCounter = 0; final int id; - int cmpField; + int data; - int data = (int) (Math.random() * 1000);//used for comparison - - public Item() { - id = idCounter++; - cmpField = (int) (Math.random() * 1000); + Item(int allFields) { + this(allFields, allFields, allFields); } - public Item(int cmpField) { - id = idCounter++; - this.cmpField = cmpField; - } - - public Item(int id, int cmpField) { + Item(int id, int compField, int data) { this.id = id; - this.cmpField = cmpField; + this.cmpField = compField; + this.data = data; } @Override public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; Item item = (Item) o; - if (cmpField != item.cmpField) { - return false; - } - if (id != item.id) { - return false; - } - - return true; - } - - @Override - public int hashCode() { - int result = id; - result = 31 * result + cmpField; - return result; + return id == item.id && cmpField == item.cmpField && data == item.data; } @Override public String toString() { - return "Item{" + - "id=" + id + - ", cmpField=" + cmpField + - ", data=" + data + - '}'; + return "Item(id=" + id + ", cmpField=" + cmpField + ", data=" + data + ')'; } } @@ -913,6 +1580,85 @@ public class SortedListTest extends TestCase { } } + private enum TYPE { + ADD, REMOVE, MOVE, CHANGE + } + + private final class AssertListStateRunnable implements Runnable { + + private Item[] mExpectedItems; + + AssertListStateRunnable(Item... expectedItems) { + this.mExpectedItems = expectedItems; + } + + @Override + public void run() { + try { + assertEquals(mExpectedItems.length, mList.size()); + for (int i = mExpectedItems.length - 1; i >= 0; i--) { + assertEquals(mExpectedItems[i], mList.get(i)); + assertEquals(i, mList.indexOf(mExpectedItems[i])); + } + } catch (AssertionError assertionError) { + throw new AssertionError( + assertionError.getMessage() + + "\nExpected: " + + Arrays.toString(mExpectedItems) + + "\nActual: " + + sortedListToString(mList)); + } + } + } + + private static final class Event { + private final TYPE mType; + private final int mVal1; + private final int mVal2; + + Event(TYPE type, int val1, int val2) { + this.mType = type; + this.mVal1 = val1; + this.mVal2 = val2; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Event that = (Event) o; + return mType == that.mType && mVal1 == that.mVal1 && mVal2 == that.mVal2; + } + + @Override + public String toString() { + return "Event(" + mType + ", " + mVal1 + ", " + mVal2 + ")"; + } + } + + private <T> boolean sortedListEquals(SortedList<T> sortedList, T[] array) { + if (sortedList.size() != array.length) { + return false; + } + for (int i = sortedList.size() - 1; i >= 0; i--) { + if (!sortedList.get(i).equals(array[i])) { + return false; + } + } + return true; + } + + private static String sortedListToString(SortedList sortedList) { + StringBuilder stringBuilder = new StringBuilder("["); + int size = sortedList.size(); + for (int i = 0; i < size; i++) { + stringBuilder.append(sortedList.get(i).toString() + ", "); + } + stringBuilder.delete(stringBuilder.length() - 2, stringBuilder.length()); + stringBuilder.append("]"); + return stringBuilder.toString(); + } + private static final class PayloadChange { public final int position; public final int count; diff --git a/android/support/v7/view/ContextThemeWrapper.java b/android/support/v7/view/ContextThemeWrapper.java index aa5b36e9..cc634804 100644 --- a/android/support/v7/view/ContextThemeWrapper.java +++ b/android/support/v7/view/ContextThemeWrapper.java @@ -16,26 +16,19 @@ package android.support.v7.view; -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - import android.content.Context; import android.content.ContextWrapper; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; -import android.support.annotation.RestrictTo; import android.support.annotation.StyleRes; import android.support.v7.appcompat.R; import android.view.LayoutInflater; /** - * A ContextWrapper that allows you to modify the theme from what is in the - * wrapped context. - * - * @hide + * A context wrapper that allows you to modify or replace the theme of the wrapped context. */ -@RestrictTo(LIBRARY_GROUP) public class ContextThemeWrapper extends ContextWrapper { private int mThemeResource; private Resources.Theme mTheme; @@ -110,15 +103,6 @@ public class ContextThemeWrapper extends ContextWrapper { mOverrideConfiguration = new Configuration(overrideConfiguration); } - /** - * Used by ActivityThread to apply the overridden configuration to onConfigurationChange - * callbacks. - * @hide - */ - public Configuration getOverrideConfiguration() { - return mOverrideConfiguration; - } - @Override public Resources getResources() { return getResourcesInternal(); @@ -144,6 +128,10 @@ public class ContextThemeWrapper extends ContextWrapper { } } + /** + * Returns the resource ID of the theme that is to be applied on top of the base context's + * theme. + */ public int getThemeResId() { return mThemeResource; } diff --git a/android/support/v7/widget/AppCompatAutoCompleteTextView.java b/android/support/v7/widget/AppCompatAutoCompleteTextView.java index 5b0a2f8d..e41bec75 100644 --- a/android/support/v7/widget/AppCompatAutoCompleteTextView.java +++ b/android/support/v7/widget/AppCompatAutoCompleteTextView.java @@ -29,6 +29,8 @@ import android.support.v4.view.TintableBackgroundView; import android.support.v7.appcompat.R; import android.support.v7.content.res.AppCompatResources; import android.util.AttributeSet; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; import android.widget.AutoCompleteTextView; /** @@ -177,4 +179,10 @@ public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implemen mTextHelper.onSetTextAppearance(context, resId); } } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs), + outAttrs, this); + } } diff --git a/android/support/v7/widget/AppCompatCheckedTextView.java b/android/support/v7/widget/AppCompatCheckedTextView.java index 921f0a26..dca409c9 100644 --- a/android/support/v7/widget/AppCompatCheckedTextView.java +++ b/android/support/v7/widget/AppCompatCheckedTextView.java @@ -20,6 +20,8 @@ import android.content.Context; import android.support.annotation.DrawableRes; import android.support.v7.content.res.AppCompatResources; import android.util.AttributeSet; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; import android.widget.CheckedTextView; /** @@ -79,4 +81,10 @@ public class AppCompatCheckedTextView extends CheckedTextView { mTextHelper.applyCompoundDrawablesTints(); } } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs), + outAttrs, this); + } } diff --git a/android/support/v7/widget/AppCompatEditText.java b/android/support/v7/widget/AppCompatEditText.java index 406e364e..6831fcbf 100644 --- a/android/support/v7/widget/AppCompatEditText.java +++ b/android/support/v7/widget/AppCompatEditText.java @@ -28,6 +28,8 @@ import android.support.annotation.RestrictTo; import android.support.v4.view.TintableBackgroundView; import android.support.v7.appcompat.R; import android.util.AttributeSet; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; import android.widget.EditText; /** @@ -159,4 +161,10 @@ public class AppCompatEditText extends EditText implements TintableBackgroundVie mTextHelper.onSetTextAppearance(context, resId); } } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs), + outAttrs, this); + } } diff --git a/android/support/v7/widget/AppCompatHintHelper.java b/android/support/v7/widget/AppCompatHintHelper.java new file mode 100644 index 00000000..0d30fb7c --- /dev/null +++ b/android/support/v7/widget/AppCompatHintHelper.java @@ -0,0 +1,43 @@ +/* + * 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.support.v7.widget; + +import android.view.View; +import android.view.ViewParent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +class AppCompatHintHelper { + + static InputConnection onCreateInputConnection(InputConnection ic, EditorInfo outAttrs, + View view) { + if (ic != null && outAttrs.hintText == null) { + // If we don't have a hint and the parent implements WithHint, use its hint for the + // EditorInfo. This allows us to display a hint in 'extract mode'. + ViewParent parent = view.getParent(); + while (parent instanceof View) { + if (parent instanceof WithHint) { + outAttrs.hintText = ((WithHint) parent).getHint(); + break; + } + parent = parent.getParent(); + } + } + return ic; + } + +} diff --git a/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java b/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java index 8060d7d1..b71b08a5 100644 --- a/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java +++ b/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java @@ -29,6 +29,8 @@ import android.support.v4.view.TintableBackgroundView; import android.support.v7.appcompat.R; import android.support.v7.content.res.AppCompatResources; import android.util.AttributeSet; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; import android.widget.MultiAutoCompleteTextView; /** @@ -177,4 +179,10 @@ public class AppCompatMultiAutoCompleteTextView extends MultiAutoCompleteTextVie mTextHelper.onSetTextAppearance(context, resId); } } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs), + outAttrs, this); + } } diff --git a/android/support/v7/widget/AppCompatTextView.java b/android/support/v7/widget/AppCompatTextView.java index cfa6a2a9..d8132770 100644 --- a/android/support/v7/widget/AppCompatTextView.java +++ b/android/support/v7/widget/AppCompatTextView.java @@ -31,6 +31,8 @@ import android.support.v4.widget.AutoSizeableTextView; import android.support.v4.widget.TextViewCompat; import android.support.v7.appcompat.R; import android.util.AttributeSet; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; import android.widget.TextView; /** @@ -361,4 +363,10 @@ public class AppCompatTextView extends TextView implements TintableBackgroundVie } return new int[0]; } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs), + outAttrs, this); + } } diff --git a/android/support/v7/widget/RecyclerView.java b/android/support/v7/widget/RecyclerView.java index 4bc17a86..84c28b10 100644 --- a/android/support/v7/widget/RecyclerView.java +++ b/android/support/v7/widget/RecyclerView.java @@ -386,8 +386,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro private List<OnChildAttachStateChangeListener> mOnChildAttachStateListeners; /** - * Set to true when an adapter data set changed notification is received. - * In that case, we cannot run any animations since we don't know what happened until layout. + * True after an event occurs that signals that the entire data set has changed. In that case, + * we cannot run any animations since we don't know what happened until layout. * * Attached items are invalid until next layout, at which point layout will animate/replace * items as necessary, building up content from the (effectively) new adapter from scratch. @@ -395,11 +395,20 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * Cached items must be discarded when setting this to true, so that the cache may be freely * used by prefetching until the next layout occurs. * - * @see #setDataSetChangedAfterLayout() + * @see #processDataSetCompletelyChanged(boolean) */ boolean mDataSetHasChangedAfterLayout = false; /** + * True after the data set has completely changed and + * {@link LayoutManager#onItemsChanged(RecyclerView)} should be called during the subsequent + * measure/layout. + * + * @see #processDataSetCompletelyChanged(boolean) + */ + boolean mDispatchItemsChangedEvent = false; + + /** * This variable is incremented during a dispatchLayout and/or scroll. * Some methods should not be called during these periods (e.g. adapter data change). * Doing so will create hard to find bugs so we better check it and throw an exception. @@ -1044,6 +1053,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro // bail out if layout is frozen setLayoutFrozen(false); setAdapterInternal(adapter, true, removeAndRecycleExistingViews); + processDataSetCompletelyChanged(true); requestLayout(); } /** @@ -1059,6 +1069,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro // bail out if layout is frozen setLayoutFrozen(false); setAdapterInternal(adapter, false, true); + processDataSetCompletelyChanged(false); requestLayout(); } @@ -1112,7 +1123,6 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); mState.mStructureChanged = true; - setDataSetChangedAfterLayout(); } /** @@ -2509,9 +2519,17 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro if (next == null || next == this) { return false; } + // panic, result view is not a child anymore, maybe workaround b/37864393 + if (findContainingItemView(next) == null) { + return false; + } if (focused == null) { return true; } + // panic, focused view is not a child anymore, maybe workaround b/37864393 + if (findContainingItemView(focused) == null) { + return true; + } mTempRect.set(0, 0, focused.getWidth(), focused.getHeight()); mTempRect2.set(0, 0, next.getWidth(), next.getHeight()); @@ -3221,7 +3239,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } /** - * Used when onMeasure is called before layout manager is set + * An implementation of {@link View#onMeasure(int, int)} to fall back to in various scenarios + * where this RecyclerView is otherwise lacking better information. */ void defaultOnMeasure(int widthSpec, int heightSpec) { // calling LayoutManager here is not pretty but that API is already public and it is better @@ -3398,7 +3417,9 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro // Processing these items have no value since data set changed unexpectedly. // Instead, we just reset it. mAdapterHelper.reset(); - mLayout.onItemsChanged(this); + if (mDispatchItemsChangedEvent) { + mLayout.onItemsChanged(this); + } } // simple animations are a subset of advanced animations (which will cause a // pre-layout step) @@ -3821,6 +3842,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro mLayout.removeAndRecycleScrapInt(mRecycler); mState.mPreviousLayoutItemCount = mState.mItemCount; mDataSetHasChangedAfterLayout = false; + mDispatchItemsChangedEvent = false; mState.mRunSimpleAnimations = false; mState.mRunPredictiveAnimations = false; @@ -4288,19 +4310,21 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro viewHolder.getUnmodifiedPayloads()); } - /** - * Call this method to signal that *all* adapter content has changed (generally, because of - * setAdapter, swapAdapter, or notifyDataSetChanged), and that once layout occurs, all - * attached items should be discarded or animated. + * Processes the fact that, as far as we can tell, the data set has completely changed. * - * Attached items are labeled as invalid, and all cached items are discarded. + * <ul> + * <li>Once layout occurs, all attached items should be discarded or animated. + * <li>Attached items are labeled as invalid. + * <li>Because items may still be prefetched between a "data set completely changed" + * event and a layout event, all cached items are discarded. + * </ul> * - * It is still possible for items to be prefetched while mDataSetHasChangedAfterLayout == true, - * so this method must always discard all cached views so that the only valid items that remain - * in the cache, once layout occurs, are valid prefetched items. + * @param dispatchItemsChanged Whether to call + * {@link LayoutManager#onItemsChanged(RecyclerView)} during measure/layout. */ - void setDataSetChangedAfterLayout() { + void processDataSetCompletelyChanged(boolean dispatchItemsChanged) { + mDispatchItemsChangedEvent |= dispatchItemsChanged; mDataSetHasChangedAfterLayout = true; markKnownViewsInvalid(); } @@ -5110,7 +5134,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro assertNotInLayoutOrScroll(null); mState.mStructureChanged = true; - setDataSetChangedAfterLayout(); + processDataSetCompletelyChanged(true); if (!mAdapterHelper.hasPendingUpdates()) { requestLayout(); } @@ -7419,9 +7443,10 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * wants to handle the layout measurements itself. * <p> * This method is usually called by the LayoutManager with value {@code true} if it wants - * to support WRAP_CONTENT. If you are using a public LayoutManager but want to customize - * the measurement logic, you can call this method with {@code false} and override - * {@link LayoutManager#onMeasure(int, int)} to implement your custom measurement logic. + * to support {@link ViewGroup.LayoutParams#WRAP_CONTENT}. If you are using a public + * LayoutManager but want to customize the measurement logic, you can call this method with + * {@code false} and override {@link LayoutManager#onMeasure(Recycler, State, int, int)} to + * implement your custom measurement logic. * <p> * AutoMeasure is a convenience mechanism for LayoutManagers to easily wrap their content or * handle various specs provided by the RecyclerView's parent. @@ -7495,24 +7520,26 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } /** - * Returns whether this LayoutManager supports automatic item animations. - * A LayoutManager wishing to support item animations should obey certain - * rules as outlined in {@link #onLayoutChildren(Recycler, State)}. - * The default return value is <code>false</code>, so subclasses of LayoutManager - * will not get predictive item animations by default. - * - * <p>Whether item animations are enabled in a RecyclerView is determined both - * by the return value from this method and the + * Returns whether this LayoutManager supports "predictive item animations". + * <p> + * "Predictive item animations" are automatically created animations that show + * where items came from, and where they are going to, as items are added, removed, + * or moved within a layout. + * <p> + * A LayoutManager wishing to support predictive item animations must override this + * method to return true (the default implementation returns false) and must obey certain + * behavioral contracts outlined in {@link #onLayoutChildren(Recycler, State)}. + * <p> + * Whether item animations actually occur in a RecyclerView is actually determined by both + * the return value from this method and the * {@link RecyclerView#setItemAnimator(ItemAnimator) ItemAnimator} set on the * RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this - * method returns false, then simple item animations will be enabled, in which - * views that are moving onto or off of the screen are simply faded in/out. If - * the RecyclerView has a non-null ItemAnimator and this method returns true, - * then there will be two calls to {@link #onLayoutChildren(Recycler, State)} to - * setup up the information needed to more intelligently predict where appearing - * and disappearing views should be animated from/to.</p> + * method returns false, then only "simple item animations" will be enabled in the + * RecyclerView, in which views whose position are changing are simply faded in/out. If the + * RecyclerView has a non-null ItemAnimator and this method returns true, then predictive + * item animations will be enabled in the RecyclerView. * - * @return true if predictive item animations should be enabled, false otherwise + * @return true if this LayoutManager supports predictive item animations, false otherwise. */ public boolean supportsPredictiveItemAnimations() { return false; @@ -9491,9 +9518,11 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } /** - * Called if the RecyclerView this LayoutManager is bound to has a different adapter set. - * The LayoutManager may use this opportunity to clear caches and configure state such - * that it can relayout appropriately with the new data and potentially new view types. + * Called if the RecyclerView this LayoutManager is bound to has a different adapter set via + * {@link RecyclerView#setAdapter(Adapter)} or + * {@link RecyclerView#swapAdapter(Adapter, boolean)}. The LayoutManager may use this + * opportunity to clear caches and configure state such that it can relayout appropriately + * with the new data and potentially new view types. * * <p>The default implementation removes all currently attached views.</p> * @@ -9535,8 +9564,9 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } /** - * Called when {@link Adapter#notifyDataSetChanged()} is triggered instead of giving - * detailed information on what has actually changed. + * Called in response to a call to {@link Adapter#notifyDataSetChanged()} or + * {@link RecyclerView#swapAdapter(Adapter, boolean)} ()} and signals that the the entire + * data set has changed. * * @param recyclerView */ @@ -10042,7 +10072,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro if (vScroll == 0 && hScroll == 0) { return false; } - mRecyclerView.scrollBy(hScroll, vScroll); + mRecyclerView.smoothScrollBy(hScroll, vScroll); return true; } @@ -11794,6 +11824,11 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro boolean mStructureChanged = false; + /** + * True if the associated {@link RecyclerView} is in the pre-layout step where it is having + * its {@link LayoutManager} layout items where they will be at the beginning of a set of + * predictive item animations. + */ boolean mInPreLayout = false; boolean mTrackOldChangeHolders = false; @@ -11869,8 +11904,9 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } /** - * Returns true if - * @return + * Returns true if the {@link RecyclerView} is in the pre-layout step where it is having its + * {@link LayoutManager} layout items where they will be at the beginning of a set of + * predictive item animations. */ public boolean isPreLayout() { return mInPreLayout; diff --git a/android/support/v7/widget/Toolbar.java b/android/support/v7/widget/Toolbar.java index 45e25830..f383e90c 100644 --- a/android/support/v7/widget/Toolbar.java +++ b/android/support/v7/widget/Toolbar.java @@ -56,6 +56,7 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewParent; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; @@ -2366,12 +2367,20 @@ public class Toolbar extends ViewGroup { @Override public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { ensureCollapseButtonView(); - if (mCollapseButtonView.getParent() != Toolbar.this) { + ViewParent collapseButtonParent = mCollapseButtonView.getParent(); + if (collapseButtonParent != Toolbar.this) { + if (collapseButtonParent instanceof ViewGroup) { + ((ViewGroup) collapseButtonParent).removeView(mCollapseButtonView); + } addView(mCollapseButtonView); } mExpandedActionView = item.getActionView(); mCurrentExpandedItem = item; - if (mExpandedActionView.getParent() != Toolbar.this) { + ViewParent expandedActionParent = mExpandedActionView.getParent(); + if (expandedActionParent != Toolbar.this) { + if (expandedActionParent instanceof ViewGroup) { + ((ViewGroup) expandedActionParent).removeView(mExpandedActionView); + } final LayoutParams lp = generateDefaultLayoutParams(); lp.gravity = GravityCompat.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); lp.mViewType = LayoutParams.EXPANDED; diff --git a/android/support/v7/widget/TooltipPopup.java b/android/support/v7/widget/TooltipPopup.java index dc20aa1f..396fe058 100644 --- a/android/support/v7/widget/TooltipPopup.java +++ b/android/support/v7/widget/TooltipPopup.java @@ -31,6 +31,7 @@ import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.view.WindowManager; import android.widget.TextView; @@ -99,6 +100,7 @@ class TooltipPopup { private void computePosition(View anchorView, int anchorX, int anchorY, boolean fromTouch, WindowManager.LayoutParams outParams) { + outParams.token = anchorView.getApplicationWindowToken(); final int tooltipPreciseAnchorThreshold = mContext.getResources().getDimensionPixelOffset( R.dimen.tooltip_precise_anchor_threshold); @@ -157,7 +159,7 @@ class TooltipPopup { mTmpAnchorPos[1] -= mTmpAppPos[1]; // mTmpAnchorPos is now relative to the main app window. - outParams.x = mTmpAnchorPos[0] + offsetX - mTmpDisplayFrame.width() / 2; + outParams.x = mTmpAnchorPos[0] + offsetX - appView.getWidth() / 2; final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); mContentView.measure(spec, spec); @@ -181,6 +183,16 @@ class TooltipPopup { } private static View getAppRootView(View anchorView) { + View rootView = anchorView.getRootView(); + ViewGroup.LayoutParams lp = rootView.getLayoutParams(); + if (lp instanceof WindowManager.LayoutParams + && (((WindowManager.LayoutParams) lp).type + == WindowManager.LayoutParams.TYPE_APPLICATION)) { + // This covers regular app windows and Dialog windows. + return rootView; + } + // For non-application window types (such as popup windows) try to find the main app window + // through the context. Context context = anchorView.getContext(); while (context instanceof ContextWrapper) { if (context instanceof Activity) { @@ -189,6 +201,8 @@ class TooltipPopup { context = ((ContextWrapper) context).getBaseContext(); } } - return anchorView.getRootView(); + // Main app window not found, fall back to the anchor's root view. There is no guarantee + // that the tooltip position will be computed correctly. + return rootView; } } diff --git a/android/support/v7/widget/WithHint.java b/android/support/v7/widget/WithHint.java new file mode 100644 index 00000000..d14f483a --- /dev/null +++ b/android/support/v7/widget/WithHint.java @@ -0,0 +1,36 @@ +/* + * 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.support.v7.widget; + +import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; + +import android.support.annotation.Nullable; +import android.support.annotation.RestrictTo; + +/** + * @hide + */ +@RestrictTo(LIBRARY_GROUP) +public interface WithHint { + /** + * Returns the hint which is displayed in the floating label, if enabled. + * + * @return the hint, or null if there isn't one set, or the hint is not enabled. + */ + @Nullable + CharSequence getHint(); +} diff --git a/android/support/wear/ambient/AmbientMode.java b/android/support/wear/ambient/AmbientMode.java index 5db93830..0077a5bd 100644 --- a/android/support/wear/ambient/AmbientMode.java +++ b/android/support/wear/ambient/AmbientMode.java @@ -21,7 +21,9 @@ import android.app.FragmentManager; import android.content.Context; import android.os.Bundle; import android.support.annotation.CallSuper; +import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; +import android.util.Log; import com.google.android.wearable.compat.WearableActivityController; @@ -48,6 +50,7 @@ import java.io.PrintWriter; * }</pre> */ public final class AmbientMode extends Fragment { + private static final String TAG = "AmbientMode"; /** * Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate @@ -104,9 +107,6 @@ public final class AmbientMode extends Fragment { * running (after onResume, before onPause). All drawing should complete by the conclusion * of this method. Note that {@code invalidate()} calls will be executed before resuming * lower-power mode. - * <p> - * <p><em>Derived classes must call through to the super class's implementation of this - * method. If they do not, an exception will be thrown.</em> * * @param ambientDetails bundle containing information about the display being used. * It includes information about low-bit color and burn-in protection. @@ -122,9 +122,6 @@ public final class AmbientMode extends Fragment { /** * Called when an activity should exit ambient mode. This event is sent while an activity is * running (after onResume, before onPause). - * <p> - * <p><em>Derived classes must call through to the super class's implementation of this - * method. If they do not, an exception will be thrown.</em> */ public void onExitAmbient() {} } @@ -133,20 +130,27 @@ public final class AmbientMode extends Fragment { new AmbientDelegate.AmbientCallback() { @Override public void onEnterAmbient(Bundle ambientDetails) { - mSuppliedCallback.onEnterAmbient(ambientDetails); + if (mSuppliedCallback != null) { + mSuppliedCallback.onEnterAmbient(ambientDetails); + } } @Override public void onExitAmbient() { - mSuppliedCallback.onExitAmbient(); + if (mSuppliedCallback != null) { + mSuppliedCallback.onExitAmbient(); + } } @Override public void onUpdateAmbient() { - mSuppliedCallback.onUpdateAmbient(); + if (mSuppliedCallback != null) { + mSuppliedCallback.onUpdateAmbient(); + } } }; private AmbientDelegate mDelegate; + @Nullable private AmbientCallback mSuppliedCallback; private AmbientController mController; @@ -166,8 +170,7 @@ public final class AmbientMode extends Fragment { if (context instanceof AmbientCallbackProvider) { mSuppliedCallback = ((AmbientCallbackProvider) context).getAmbientCallback(); } else { - throw new IllegalArgumentException( - "fragment should attach to an activity that implements AmbientCallback"); + Log.w(TAG, "No callback provided - enabling only smart resume"); } } @@ -176,7 +179,9 @@ public final class AmbientMode extends Fragment { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mDelegate.onCreate(); - mDelegate.setAmbientEnabled(); + if (mSuppliedCallback != null) { + mDelegate.setAmbientEnabled(); + } } @Override @@ -215,15 +220,19 @@ public final class AmbientMode extends Fragment { } /** - * Attach ambient support to the given activity. + * Attach ambient support to the given activity. Calling this method with an Activity + * implementing the {@link AmbientCallbackProvider} interface will provide you with an + * opportunity to react to ambient events such as {@code onEnterAmbient}. Alternatively, + * you can call this method with an Activity which does not implement + * the {@link AmbientCallbackProvider} interface and that will only enable the auto-resume + * functionality. This is equivalent to providing (@code null} from + * the {@link AmbientCallbackProvider}. * - * @param activity the activity to attach ambient support to. This activity has to also - * implement {@link AmbientCallbackProvider} + * @param activity the activity to attach ambient support to. * @return the associated {@link AmbientController} which can be used to query the state of * ambient mode. */ - public static <T extends Activity & AmbientCallbackProvider> AmbientController - attachAmbientSupport(T activity) { + public static <T extends Activity> AmbientController attachAmbientSupport(T activity) { FragmentManager fragmentManager = activity.getFragmentManager(); AmbientMode ambientFragment = (AmbientMode) fragmentManager.findFragmentByTag(FRAGMENT_TAG); if (ambientFragment == null) { diff --git a/android/system/Os.java b/android/system/Os.java index fe8f7d45..cc24cc5e 100644 --- a/android/system/Os.java +++ b/android/system/Os.java @@ -16,8 +16,6 @@ package android.system; -import android.util.MutableInt; -import android.util.MutableLong; import java.io.FileDescriptor; import java.io.InterruptedIOException; import java.net.InetAddress; @@ -473,27 +471,6 @@ public final class Os { /** * See <a href="http://man7.org/linux/man-pages/man2/sendfile.2.html">sendfile(2)</a>. - * - * @deprecated This method will be removed in a future version of Android. Use - * {@link #sendfile(FileDescriptor, FileDescriptor, Int64Ref, long)} instead. - */ - @Deprecated - public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException { - if (inOffset == null) { - return Libcore.os.sendfile(outFd, inFd, null, byteCount); - } else { - libcore.util.MutableLong internalInOffset = new libcore.util.MutableLong( - inOffset.value); - try { - return Libcore.os.sendfile(outFd, inFd, internalInOffset, byteCount); - } finally { - inOffset.value = internalInOffset.value; - } - } - } - - /** - * See <a href="http://man7.org/linux/man-pages/man2/sendfile.2.html">sendfile(2)</a>. */ public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, Int64Ref inOffset, long byteCount) throws ErrnoException { if (inOffset == null) { @@ -672,26 +649,6 @@ public final class Os { /** * See <a href="http://man7.org/linux/man-pages/man2/waitpid.2.html">waitpid(2)</a>. * - * @deprecated This method will be removed in a future version of Android. Use - * {@link #waitpid(int, Int32Ref, int)} instead. - */ - @Deprecated - public static int waitpid(int pid, MutableInt status, int options) throws ErrnoException { - if (status == null) { - return Libcore.os.waitpid(pid, null, options); - } else { - libcore.util.MutableInt internalStatus = new libcore.util.MutableInt(status.value); - try { - return Libcore.os.waitpid(pid, internalStatus, options); - } finally { - status.value = internalStatus.value; - } - } - } - - /** - * See <a href="http://man7.org/linux/man-pages/man2/waitpid.2.html">waitpid(2)</a>. - * * @throws IllegalArgumentException if {@code status != null && status.length != 1} */ public static int waitpid(int pid, Int32Ref status, int options) throws ErrnoException { diff --git a/android/telecom/RemoteConnection.java b/android/telecom/RemoteConnection.java index f30d7bde..05480dc3 100644 --- a/android/telecom/RemoteConnection.java +++ b/android/telecom/RemoteConnection.java @@ -1065,7 +1065,7 @@ public final class RemoteConnection { * * @param state The audio state of this {@code RemoteConnection}. * @hide - * @deprecated Use {@link #setCallAudioState(CallAudioState) instead. + * @deprecated Use {@link #setCallAudioState(CallAudioState)} instead. */ @SystemApi @Deprecated diff --git a/android/telecom/TelecomManager.java b/android/telecom/TelecomManager.java index da32e0be..92d458f1 100644 --- a/android/telecom/TelecomManager.java +++ b/android/telecom/TelecomManager.java @@ -1346,7 +1346,7 @@ public class TelecomManager { /** * Returns whether TTY is supported on this device. */ - + @SystemApi @RequiresPermission(anyOf = { android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE diff --git a/android/telephony/CarrierConfigManager.java b/android/telephony/CarrierConfigManager.java index 1db6ef7b..69371a18 100644 --- a/android/telephony/CarrierConfigManager.java +++ b/android/telephony/CarrierConfigManager.java @@ -816,6 +816,14 @@ public class CarrierConfigManager { public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int"; /** + * Determines whether manage IMS conference calls is supported by a carrier. When {@code true}, + * manage IMS conference call is supported, {@code false otherwise}. + * @hide + */ + public static final String KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL = + "support_manage_ims_conference_call_bool"; + + /** * Determines whether High Definition audio property is displayed in the dialer UI. * If {@code false}, remove the HD audio property from the connection so that HD audio related * UI is not displayed. If {@code true}, keep HD audio property as it is configured. @@ -1814,6 +1822,7 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0); sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true); + sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false); sDefaults.putBoolean(KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL, false); sDefaults.putInt(KEY_IMS_CONFERENCE_SIZE_LIMIT_INT, 5); diff --git a/android/telephony/CellIdentityGsm.java b/android/telephony/CellIdentityGsm.java index 6276626a..c9684062 100644 --- a/android/telephony/CellIdentityGsm.java +++ b/android/telephony/CellIdentityGsm.java @@ -101,9 +101,6 @@ public final class CellIdentityGsm implements Parcelable { * @param alphal long alpha Operator Name String or Enhanced Operator Name String * @param alphas short alpha Operator Name String or Enhanced Operator Name String * - * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is - * not a 2 or 3-digit code. - * * @hide */ public CellIdentityGsm (int lac, int cid, int arfcn, int bsic, String mccStr, @@ -115,22 +112,29 @@ public final class CellIdentityGsm implements Parcelable { // for inbound parcels mBsic = (bsic == 0xFF) ? Integer.MAX_VALUE : bsic; + // Only allow INT_MAX if unknown string mcc/mnc if (mccStr == null || mccStr.matches("^[0-9]{3}$")) { mMccStr = mccStr; - } else if (mccStr.isEmpty()) { - // If the mccStr parsed from Parcel is empty, set it as null. + } else if (mccStr.isEmpty() || mccStr.equals(String.valueOf(Integer.MAX_VALUE))) { + // If the mccStr is empty or unknown, set it as null. mMccStr = null; } else { - throw new IllegalArgumentException("invalid MCC format"); + // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MCC format + // after the bug got fixed. + mMccStr = null; + log("invalid MCC format: " + mccStr); } if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) { mMncStr = mncStr; - } else if (mncStr.isEmpty()) { - // If the mncStr parsed from Parcel is empty, set it as null. + } else if (mncStr.isEmpty() || mncStr.equals(String.valueOf(Integer.MAX_VALUE))) { + // If the mncStr is empty or unknown, set it as null. mMncStr = null; } else { - throw new IllegalArgumentException("invalid MNC format"); + // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MNC format + // after the bug got fixed. + mMncStr = null; + log("invalid MNC format: " + mncStr); } mAlphaLong = alphal; diff --git a/android/telephony/CellIdentityLte.java b/android/telephony/CellIdentityLte.java index 74d2966b..825dcc33 100644 --- a/android/telephony/CellIdentityLte.java +++ b/android/telephony/CellIdentityLte.java @@ -102,9 +102,6 @@ public final class CellIdentityLte implements Parcelable { * @param alphal long alpha Operator Name String or Enhanced Operator Name String * @param alphas short alpha Operator Name String or Enhanced Operator Name String * - * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is - * not a 2 or 3-digit code. - * * @hide */ public CellIdentityLte (int ci, int pci, int tac, int earfcn, String mccStr, @@ -114,22 +111,29 @@ public final class CellIdentityLte implements Parcelable { mTac = tac; mEarfcn = earfcn; + // Only allow INT_MAX if unknown string mcc/mnc if (mccStr == null || mccStr.matches("^[0-9]{3}$")) { mMccStr = mccStr; - } else if (mccStr.isEmpty()) { - // If the mccStr parsed from Parcel is empty, set it as null. + } else if (mccStr.isEmpty() || mccStr.equals(String.valueOf(Integer.MAX_VALUE))) { + // If the mccStr is empty or unknown, set it as null. mMccStr = null; } else { - throw new IllegalArgumentException("invalid MCC format"); + // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MCC format + // after the bug got fixed. + mMccStr = null; + log("invalid MCC format: " + mccStr); } if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) { mMncStr = mncStr; - } else if (mncStr.isEmpty()) { - // If the mncStr parsed from Parcel is empty, set it as null. + } else if (mncStr.isEmpty() || mncStr.equals(String.valueOf(Integer.MAX_VALUE))) { + // If the mncStr is empty or unknown, set it as null. mMncStr = null; } else { - throw new IllegalArgumentException("invalid MNC format"); + // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MNC format + // after the bug got fixed. + mMncStr = null; + log("invalid MNC format: " + mncStr); } mAlphaLong = alphal; diff --git a/android/telephony/CellIdentityWcdma.java b/android/telephony/CellIdentityWcdma.java index 51b11aa8..e74b5701 100644 --- a/android/telephony/CellIdentityWcdma.java +++ b/android/telephony/CellIdentityWcdma.java @@ -102,9 +102,6 @@ public final class CellIdentityWcdma implements Parcelable { * @param alphal long alpha Operator Name String or Enhanced Operator Name String * @param alphas short alpha Operator Name String or Enhanced Operator Name String * - * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is - * not a 2 or 3-digit code. - * * @hide */ public CellIdentityWcdma (int lac, int cid, int psc, int uarfcn, @@ -114,22 +111,29 @@ public final class CellIdentityWcdma implements Parcelable { mPsc = psc; mUarfcn = uarfcn; + // Only allow INT_MAX if unknown string mcc/mnc if (mccStr == null || mccStr.matches("^[0-9]{3}$")) { mMccStr = mccStr; - } else if (mccStr.isEmpty()) { - // If the mccStr parsed from Parcel is empty, set it as null. + } else if (mccStr.isEmpty() || mccStr.equals(String.valueOf(Integer.MAX_VALUE))) { + // If the mccStr is empty or unknown, set it as null. mMccStr = null; } else { - throw new IllegalArgumentException("invalid MCC format"); + // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MCC format + // after the bug got fixed. + mMccStr = null; + log("invalid MCC format: " + mccStr); } if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) { mMncStr = mncStr; - } else if (mncStr.isEmpty()) { - // If the mncStr parsed from Parcel is empty, set it as null. + } else if (mncStr.isEmpty() || mncStr.equals(String.valueOf(Integer.MAX_VALUE))) { + // If the mncStr is empty or unknown, set it as null. mMncStr = null; } else { - throw new IllegalArgumentException("invalid MNC format"); + // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MNC format + // after the bug got fixed. + mMncStr = null; + log("invalid MNC format: " + mncStr); } mAlphaLong = alphal; diff --git a/android/telephony/SmsManager.java b/android/telephony/SmsManager.java index 5d039268..fdedf758 100644 --- a/android/telephony/SmsManager.java +++ b/android/telephony/SmsManager.java @@ -350,6 +350,7 @@ public final class SmsManager { * * @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent) */ + @SystemApi public void sendTextMessageWithoutPersisting( String destinationAddress, String scAddress, String text, PendingIntent sentIntent, PendingIntent deliveryIntent) { diff --git a/android/telephony/SubscriptionManager.java b/android/telephony/SubscriptionManager.java index 2f39ddb1..1e6abf22 100644 --- a/android/telephony/SubscriptionManager.java +++ b/android/telephony/SubscriptionManager.java @@ -28,6 +28,7 @@ import android.content.res.Resources; import android.net.INetworkPolicyManager; import android.net.Uri; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; @@ -361,6 +362,9 @@ public class SubscriptionManager { /** * TelephonyProvider column name for enable Volte. + * + * If this setting is not initialized (set to -1) then we use the Carrier Config value + * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}. *@hide */ public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled"; @@ -446,7 +450,15 @@ public class SubscriptionManager { * for #onSubscriptionsChanged to be invoked. */ public static class OnSubscriptionsChangedListener { - private final Handler mHandler = new Handler() { + private class OnSubscriptionsChangedListenerHandler extends Handler { + OnSubscriptionsChangedListenerHandler() { + super(); + } + + OnSubscriptionsChangedListenerHandler(Looper looper) { + super(looper); + } + @Override public void handleMessage(Message msg) { if (DBG) { @@ -454,7 +466,22 @@ public class SubscriptionManager { } OnSubscriptionsChangedListener.this.onSubscriptionsChanged(); } - }; + } + + private final Handler mHandler; + + public OnSubscriptionsChangedListener() { + mHandler = new OnSubscriptionsChangedListenerHandler(); + } + + /** + * Allow a listener to be created with a custom looper + * @param looper the looper that the underlining handler should run on + * @hide + */ + public OnSubscriptionsChangedListener(Looper looper) { + mHandler = new OnSubscriptionsChangedListenerHandler(looper); + } /** * Callback invoked when there is any change to any SubscriptionInfo. Typically diff --git a/android/telephony/TelephonyManager.java b/android/telephony/TelephonyManager.java index 4ffb3c32..81806e52 100644 --- a/android/telephony/TelephonyManager.java +++ b/android/telephony/TelephonyManager.java @@ -53,8 +53,9 @@ import android.telephony.VisualVoicemailService.VisualVoicemailTask; import android.telephony.ims.feature.ImsFeature; import android.util.Log; -import com.android.ims.internal.IImsServiceController; -import com.android.ims.internal.IImsServiceFeatureListener; +import com.android.ims.internal.IImsMMTelFeature; +import com.android.ims.internal.IImsRcsFeature; +import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telecom.ITelecomService; import com.android.internal.telephony.CellNetworkScanResult; @@ -408,9 +409,12 @@ public class TelephonyManager { * Open the voicemail settings activity to make changes to voicemail configuration. * * <p> + * The {@link #EXTRA_PHONE_ACCOUNT_HANDLE} extra indicates which {@link PhoneAccountHandle} to + * configure voicemail. * The {@link #EXTRA_HIDE_PUBLIC_SETTINGS} hides settings the dialer will modify through public * API if set. * + * @see #EXTRA_PHONE_ACCOUNT_HANDLE * @see #EXTRA_HIDE_PUBLIC_SETTINGS */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) @@ -775,8 +779,9 @@ public class TelephonyManager { "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION"; /** - * The extra used with an {@link #ACTION_SHOW_VOICEMAIL_NOTIFICATION} {@code Intent} to specify - * the {@link PhoneAccountHandle} the notification is for. + * The extra used with an {@link #ACTION_CONFIGURE_VOICEMAIL} and + * {@link #ACTION_SHOW_VOICEMAIL_NOTIFICATION} {@code Intent} to specify the + * {@link PhoneAccountHandle} the configuration or notification is for. * <p class="note"> * Retrieve with {@link android.content.Intent#getParcelableExtra(String)}. */ @@ -4659,27 +4664,78 @@ public class TelephonyManager { public @interface Feature {} /** - * Returns the {@link IImsServiceController} that corresponds to the given slot Id and IMS - * feature or {@link null} if the service is not available. If an ImsServiceController is - * available, the {@link IImsServiceFeatureListener} callback is registered as a listener for - * feature updates. - * @param slotIndex The SIM slot that we are requesting the {@link IImsServiceController} for. - * @param feature The IMS Feature we are requesting, corresponding to {@link ImsFeature}. + * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id and MMTel + * feature or {@link null} if the service is not available. If an MMTelFeature is available, the + * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates. + * @param slotIndex The SIM slot that we are requesting the {@link IImsMMTelFeature} for. + * @param callback Listener that will send updates to ImsManager when there are updates to + * ImsServiceController. + * @return {@link IImsMMTelFeature} interface for the feature specified or {@code null} if + * it is unavailable. + * @hide + */ + public @Nullable IImsMMTelFeature getImsMMTelFeatureAndListen(int slotIndex, + IImsServiceFeatureCallback callback) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getMMTelFeatureAndListen(slotIndex, callback); + } + } catch (RemoteException e) { + Rlog.e(TAG, "getImsMMTelFeatureAndListen, RemoteException: " + + e.getMessage()); + } + return null; + } + + /** + * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id and MMTel + * feature for emergency calling or {@link null} if the service is not available. If an + * MMTelFeature is available, the {@link IImsServiceFeatureCallback} callback is registered as a + * listener for feature updates. + * @param slotIndex The SIM slot that we are requesting the {@link IImsMMTelFeature} for. + * @param callback Listener that will send updates to ImsManager when there are updates to + * ImsServiceController. + * @return {@link IImsMMTelFeature} interface for the feature specified or {@code null} if + * it is unavailable. + * @hide + */ + public @Nullable IImsMMTelFeature getImsEmergencyMMTelFeatureAndListen(int slotIndex, + IImsServiceFeatureCallback callback) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getEmergencyMMTelFeatureAndListen(slotIndex, callback); + } + } catch (RemoteException e) { + Rlog.e(TAG, "getImsEmergencyMMTelFeatureAndListen, RemoteException: " + + e.getMessage()); + } + return null; + } + + /** + * Returns the {@link IImsRcsFeature} that corresponds to the given slot Id and RCS + * feature for emergency calling or {@link null} if the service is not available. If an + * RcsFeature is available, the {@link IImsServiceFeatureCallback} callback is registered as a + * listener for feature updates. + * @param slotIndex The SIM slot that we are requesting the {@link IImsRcsFeature} for. * @param callback Listener that will send updates to ImsManager when there are updates to * ImsServiceController. - * @return {@link IImsServiceController} interface for the feature specified or {@link null} if + * @return {@link IImsRcsFeature} interface for the feature specified or {@code null} if * it is unavailable. * @hide */ - public IImsServiceController getImsServiceControllerAndListen(int slotIndex, @Feature int feature, - IImsServiceFeatureListener callback) { + public @Nullable IImsRcsFeature getImsRcsFeatureAndListen(int slotIndex, + IImsServiceFeatureCallback callback) { try { ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.getImsServiceControllerAndListen(slotIndex, feature, callback); + return telephony.getRcsFeatureAndListen(slotIndex, callback); } } catch (RemoteException e) { - Rlog.e(TAG, "getImsServiceControllerAndListen, RemoteException: " + e.getMessage()); + Rlog.e(TAG, "getImsRcsFeatureAndListen, RemoteException: " + + e.getMessage()); } return null; } diff --git a/android/telephony/data/DataProfile.java b/android/telephony/data/DataProfile.java new file mode 100644 index 00000000..41c1430e --- /dev/null +++ b/android/telephony/data/DataProfile.java @@ -0,0 +1,280 @@ +/* + * 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.telephony.data; + +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import com.android.internal.telephony.RILConstants; + +/** + * Description of a mobile data profile used for establishing + * data connections. + * + * @hide + */ +@SystemApi +public final class DataProfile implements Parcelable { + + // The types indicating the data profile is used on GSM (3GPP) or CDMA (3GPP2) network. + public static final int TYPE_COMMON = 0; + public static final int TYPE_3GPP = 1; + public static final int TYPE_3GPP2 = 2; + + private final int mProfileId; + + private final String mApn; + + private final String mProtocol; + + private final int mAuthType; + + private final String mUserName; + + private final String mPassword; + + private final int mType; + + private final int mMaxConnsTime; + + private final int mMaxConns; + + private final int mWaitTime; + + private final boolean mEnabled; + + private final int mSupportedApnTypesBitmap; + + private final String mRoamingProtocol; + + private final int mBearerBitmap; + + private final int mMtu; + + private final String mMvnoType; + + private final String mMvnoMatchData; + + private final boolean mModemCognitive; + + public DataProfile(int profileId, String apn, String protocol, int authType, + String userName, String password, int type, int maxConnsTime, int maxConns, + int waitTime, boolean enabled, int supportedApnTypesBitmap, String roamingProtocol, + int bearerBitmap, int mtu, String mvnoType, String mvnoMatchData, + boolean modemCognitive) { + + this.mProfileId = profileId; + this.mApn = apn; + this.mProtocol = protocol; + if (authType == -1) { + authType = TextUtils.isEmpty(userName) ? RILConstants.SETUP_DATA_AUTH_NONE + : RILConstants.SETUP_DATA_AUTH_PAP_CHAP; + } + this.mAuthType = authType; + this.mUserName = userName; + this.mPassword = password; + this.mType = type; + this.mMaxConnsTime = maxConnsTime; + this.mMaxConns = maxConns; + this.mWaitTime = waitTime; + this.mEnabled = enabled; + + this.mSupportedApnTypesBitmap = supportedApnTypesBitmap; + this.mRoamingProtocol = roamingProtocol; + this.mBearerBitmap = bearerBitmap; + this.mMtu = mtu; + this.mMvnoType = mvnoType; + this.mMvnoMatchData = mvnoMatchData; + this.mModemCognitive = modemCognitive; + } + + public DataProfile(Parcel source) { + mProfileId = source.readInt(); + mApn = source.readString(); + mProtocol = source.readString(); + mAuthType = source.readInt(); + mUserName = source.readString(); + mPassword = source.readString(); + mType = source.readInt(); + mMaxConnsTime = source.readInt(); + mMaxConns = source.readInt(); + mWaitTime = source.readInt(); + mEnabled = source.readBoolean(); + mSupportedApnTypesBitmap = source.readInt(); + mRoamingProtocol = source.readString(); + mBearerBitmap = source.readInt(); + mMtu = source.readInt(); + mMvnoType = source.readString(); + mMvnoMatchData = source.readString(); + mModemCognitive = source.readBoolean(); + } + + /** + * @return Id of the data profile. + */ + public int getProfileId() { return mProfileId; } + + /** + * @return The APN to establish data connection. + */ + public String getApn() { return mApn; } + + /** + * @return The connection protocol, should be one of the PDP_type values in TS 27.007 section + * 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP". + */ + public String getProtocol() { return mProtocol; } + + /** + * @return The authentication protocol used for this PDP context + * (None: 0, PAP: 1, CHAP: 2, PAP&CHAP: 3) + */ + public int getAuthType() { return mAuthType; } + + /** + * @return The username for APN. Can be null. + */ + public String getUserName() { return mUserName; } + + /** + * @return The password for APN. Can be null. + */ + public String getPassword() { return mPassword; } + + /** + * @return The profile type. Could be one of TYPE_COMMON, TYPE_3GPP, or TYPE_3GPP2. + */ + public int getType() { return mType; } + + /** + * @return The period in seconds to limit the maximum connections. + */ + public int getMaxConnsTime() { return mMaxConnsTime; } + + /** + * @return The maximum connections allowed. + */ + public int getMaxConns() { return mMaxConns; } + + /** + * @return The required wait time in seconds after a successful UE initiated disconnect of a + * given PDN connection before the device can send a new PDN connection request for that given + * PDN. + */ + public int getWaitTime() { return mWaitTime; } + + /** + * @return True if the profile is enabled. + */ + public boolean isEnabled() { return mEnabled; } + + /** + * @return The supported APN types bitmap. See RIL_ApnTypes for the value of each bit. + */ + public int getSupportedApnTypesBitmap() { return mSupportedApnTypesBitmap; } + + /** + * @return The connection protocol on roaming network, should be one of the PDP_type values in + * TS 27.007 section 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP". + */ + public String getRoamingProtocol() { return mRoamingProtocol; } + + /** + * @return The bearer bitmap. See RIL_RadioAccessFamily for the value of each bit. + */ + public int getBearerBitmap() { return mBearerBitmap; } + + /** + * @return The maximum transmission unit (MTU) size in bytes. + */ + public int getMtu() { return mMtu; } + + /** + * @return The MVNO type: possible values are "imsi", "gid", "spn". + */ + public String getMvnoType() { return mMvnoType; } + + /** + * @return The MVNO match data. For example, + * SPN: A MOBILE, BEN NL, ... + * IMSI: 302720x94, 2060188, ... + * GID: 4E, 33, ... + */ + public String getMvnoMatchData() { return mMvnoMatchData; } + + /** + * @return True if the data profile was sent to the modem through setDataProfile earlier. + */ + public boolean isModemCognitive() { return mModemCognitive; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "DataProfile=" + mProfileId + "/" + mApn + "/" + mProtocol + "/" + mAuthType + + "/" + mUserName + "/" + mPassword + "/" + mType + "/" + mMaxConnsTime + + "/" + mMaxConns + "/" + mWaitTime + "/" + mEnabled + "/" + + mSupportedApnTypesBitmap + "/" + mRoamingProtocol + "/" + mBearerBitmap + "/" + + mMtu + "/" + mMvnoType + "/" + mMvnoMatchData + "/" + mModemCognitive; + } + + @Override + public boolean equals(Object o) { + if (o instanceof DataProfile == false) return false; + return (o == this || toString().equals(o.toString())); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mProfileId); + dest.writeString(mApn); + dest.writeString(mProtocol); + dest.writeInt(mAuthType); + dest.writeString(mUserName); + dest.writeString(mPassword); + dest.writeInt(mType); + dest.writeInt(mMaxConnsTime); + dest.writeInt(mMaxConns); + dest.writeInt(mWaitTime); + dest.writeBoolean(mEnabled); + dest.writeInt(mSupportedApnTypesBitmap); + dest.writeString(mRoamingProtocol); + dest.writeInt(mBearerBitmap); + dest.writeInt(mMtu); + dest.writeString(mMvnoType); + dest.writeString(mMvnoMatchData); + dest.writeBoolean(mModemCognitive); + } + + public static final Parcelable.Creator<DataProfile> CREATOR = + new Parcelable.Creator<DataProfile>() { + @Override + public DataProfile createFromParcel(Parcel source) { + return new DataProfile(source); + } + + @Override + public DataProfile[] newArray(int size) { + return new DataProfile[size]; + } + }; +} diff --git a/android/telephony/ims/ImsService.java b/android/telephony/ims/ImsService.java index 9d91cc33..8230eafc 100644 --- a/android/telephony/ims/ImsService.java +++ b/android/telephony/ims/ImsService.java @@ -16,13 +16,11 @@ package android.telephony.ims; +import android.annotation.Nullable; import android.annotation.SystemApi; -import android.app.PendingIntent; import android.app.Service; import android.content.Intent; -import android.content.pm.PackageManager; import android.os.IBinder; -import android.os.Message; import android.os.RemoteException; import android.telephony.CarrierConfigManager; import android.telephony.ims.feature.ImsFeature; @@ -31,22 +29,13 @@ import android.telephony.ims.feature.RcsFeature; import android.util.Log; import android.util.SparseArray; -import com.android.ims.ImsCallProfile; -import com.android.ims.internal.IImsCallSession; -import com.android.ims.internal.IImsCallSessionListener; -import com.android.ims.internal.IImsConfig; -import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsFeatureStatusCallback; -import com.android.ims.internal.IImsMultiEndpoint; -import com.android.ims.internal.IImsRegistrationListener; +import com.android.ims.internal.IImsMMTelFeature; +import com.android.ims.internal.IImsRcsFeature; import com.android.ims.internal.IImsServiceController; -import com.android.ims.internal.IImsServiceFeatureListener; -import com.android.ims.internal.IImsUt; import com.android.internal.annotations.VisibleForTesting; import static android.Manifest.permission.MODIFY_PHONE_STATE; -import static android.Manifest.permission.READ_PHONE_STATE; -import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE; /** * Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend @@ -92,247 +81,38 @@ public class ImsService extends Service { */ public static final String SERVICE_INTERFACE = "android.telephony.ims.ImsService"; - // A map of slot Id -> Set of features corresponding to that slot. - private final SparseArray<SparseArray<ImsFeature>> mFeatures = new SparseArray<>(); + // A map of slot Id -> map of features (indexed by ImsFeature feature id) corresponding to that + // slot. + // We keep track of this to facilitate cleanup of the IImsFeatureStatusCallback and + // call ImsFeature#onFeatureRemoved. + private final SparseArray<SparseArray<ImsFeature>> mFeaturesBySlot = new SparseArray<>(); /** * @hide */ - // Implements all supported features as a flat interface. protected final IBinder mImsServiceController = new IImsServiceController.Stub() { @Override - public void createImsFeature(int slotId, int feature, IImsFeatureStatusCallback c) - throws RemoteException { - synchronized (mFeatures) { - enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createImsFeature"); - onCreateImsFeatureInternal(slotId, feature, c); - } + public IImsMMTelFeature createEmergencyMMTelFeature(int slotId, + IImsFeatureStatusCallback c) { + return createEmergencyMMTelFeatureInternal(slotId, c); } @Override - public void removeImsFeature(int slotId, int feature, IImsFeatureStatusCallback c) - throws RemoteException { - synchronized (mFeatures) { - enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "removeImsFeature"); - onRemoveImsFeatureInternal(slotId, feature, c); - } + public IImsMMTelFeature createMMTelFeature(int slotId, IImsFeatureStatusCallback c) { + return createMMTelFeatureInternal(slotId, c); } @Override - public int startSession(int slotId, int featureType, PendingIntent incomingCallIntent, - IImsRegistrationListener listener) throws RemoteException { - enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "startSession"); - synchronized (mFeatures) { - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - return feature.startSession(incomingCallIntent, listener); - } - } - return 0; + public IImsRcsFeature createRcsFeature(int slotId, IImsFeatureStatusCallback c) { + return createRcsFeatureInternal(slotId, c); } @Override - public void endSession(int slotId, int featureType, int sessionId) throws RemoteException { - synchronized (mFeatures) { - enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "endSession"); - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - feature.endSession(sessionId); - } - } - } - - @Override - public boolean isConnected(int slotId, int featureType, int callSessionType, int callType) + public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) throws RemoteException { - enforceReadPhoneStatePermission("isConnected"); - synchronized (mFeatures) { - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - return feature.isConnected(callSessionType, callType); - } - } - return false; - } - - @Override - public boolean isOpened(int slotId, int featureType) throws RemoteException { - enforceReadPhoneStatePermission("isOpened"); - synchronized (mFeatures) { - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - return feature.isOpened(); - } - } - return false; - } - - @Override - public int getFeatureStatus(int slotId, int featureType) throws RemoteException { - enforceReadPhoneStatePermission("getFeatureStatus"); - int status = ImsFeature.STATE_NOT_AVAILABLE; - synchronized (mFeatures) { - SparseArray<ImsFeature> featureMap = mFeatures.get(slotId); - if (featureMap != null) { - ImsFeature feature = getImsFeatureFromType(featureMap, featureType); - if (feature != null) { - status = feature.getFeatureState(); - } - } - } - return status; - } - - @Override - public void addRegistrationListener(int slotId, int featureType, - IImsRegistrationListener listener) throws RemoteException { - enforceReadPhoneStatePermission("addRegistrationListener"); - synchronized (mFeatures) { - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - feature.addRegistrationListener(listener); - } - } + ImsService.this.removeImsFeature(slotId, featureType, c); } - - @Override - public void removeRegistrationListener(int slotId, int featureType, - IImsRegistrationListener listener) throws RemoteException { - enforceReadPhoneStatePermission("removeRegistrationListener"); - synchronized (mFeatures) { - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - feature.removeRegistrationListener(listener); - } - } - } - - @Override - public ImsCallProfile createCallProfile(int slotId, int featureType, int sessionId, - int callSessionType, int callType) throws RemoteException { - enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createCallProfile"); - synchronized (mFeatures) { - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - return feature.createCallProfile(sessionId, callSessionType, callType); - } - } - return null; - } - - @Override - public IImsCallSession createCallSession(int slotId, int featureType, int sessionId, - ImsCallProfile profile, IImsCallSessionListener listener) throws RemoteException { - enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createCallSession"); - synchronized (mFeatures) { - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - return feature.createCallSession(sessionId, profile, listener); - } - } - return null; - } - - @Override - public IImsCallSession getPendingCallSession(int slotId, int featureType, int sessionId, - String callId) throws RemoteException { - enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getPendingCallSession"); - synchronized (mFeatures) { - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - return feature.getPendingCallSession(sessionId, callId); - } - } - return null; - } - - @Override - public IImsUt getUtInterface(int slotId, int featureType) - throws RemoteException { - enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getUtInterface"); - synchronized (mFeatures) { - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - return feature.getUtInterface(); - } - } - return null; - } - - @Override - public IImsConfig getConfigInterface(int slotId, int featureType) - throws RemoteException { - enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getConfigInterface"); - synchronized (mFeatures) { - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - return feature.getConfigInterface(); - } - } - return null; - } - - @Override - public void turnOnIms(int slotId, int featureType) throws RemoteException { - enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "turnOnIms"); - synchronized (mFeatures) { - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - feature.turnOnIms(); - } - } - } - - @Override - public void turnOffIms(int slotId, int featureType) throws RemoteException { - enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "turnOffIms"); - synchronized (mFeatures) { - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - feature.turnOffIms(); - } - } - } - - @Override - public IImsEcbm getEcbmInterface(int slotId, int featureType) - throws RemoteException { - enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getEcbmInterface"); - synchronized (mFeatures) { - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - return feature.getEcbmInterface(); - } - } - return null; - } - - @Override - public void setUiTTYMode(int slotId, int featureType, int uiTtyMode, Message onComplete) - throws RemoteException { - enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "setUiTTYMode"); - synchronized (mFeatures) { - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - feature.setUiTTYMode(uiTtyMode, onComplete); - } - } - } - - @Override - public IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType) - throws RemoteException { - enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getMultiEndpointInterface"); - synchronized (mFeatures) { - MMTelFeature feature = resolveMMTelFeature(slotId, featureType); - if (feature != null) { - return feature.getMultiEndpointInterface(); - } - } - return null; - } - }; /** @@ -341,127 +121,93 @@ public class ImsService extends Service { @Override public IBinder onBind(Intent intent) { if(SERVICE_INTERFACE.equals(intent.getAction())) { + Log.i(LOG_TAG, "ImsService Bound."); return mImsServiceController; } return null; } /** - * Called from the ImsResolver to create the requested ImsFeature, as defined by the slot and - * featureType - * @param slotId An integer representing which SIM slot the ImsFeature is assigned to. - * @param featureType An integer representing the type of ImsFeature being created. This is - * defined in {@link ImsFeature}. + * @hide */ - // Be sure to lock on mFeatures before accessing this method - private void onCreateImsFeatureInternal(int slotId, int featureType, - IImsFeatureStatusCallback c) { - SparseArray<ImsFeature> featureMap = mFeatures.get(slotId); - if (featureMap == null) { - featureMap = new SparseArray<>(); - mFeatures.put(slotId, featureMap); - } - ImsFeature f = makeImsFeature(slotId, featureType); - if (f != null) { - f.setContext(this); - f.setSlotId(slotId); - f.addImsFeatureStatusCallback(c); - featureMap.put(featureType, f); - } - + @VisibleForTesting + public SparseArray<ImsFeature> getFeatures(int slotId) { + return mFeaturesBySlot.get(slotId); } - /** - * Called from the ImsResolver to remove an existing ImsFeature, as defined by the slot and - * featureType. - * @param slotId An integer representing which SIM slot the ImsFeature is assigned to. - * @param featureType An integer representing the type of ImsFeature being removed. This is - * defined in {@link ImsFeature}. - */ - // Be sure to lock on mFeatures before accessing this method - private void onRemoveImsFeatureInternal(int slotId, int featureType, - IImsFeatureStatusCallback c) { - SparseArray<ImsFeature> featureMap = mFeatures.get(slotId); - if (featureMap == null) { - return; - } - ImsFeature featureToRemove = getImsFeatureFromType(featureMap, featureType); - if (featureToRemove != null) { - featureMap.remove(featureType); - featureToRemove.notifyFeatureRemoved(slotId); - // Remove reference to Binder - featureToRemove.removeImsFeatureStatusCallback(c); + private IImsMMTelFeature createEmergencyMMTelFeatureInternal(int slotId, + IImsFeatureStatusCallback c) { + MMTelFeature f = onCreateEmergencyMMTelImsFeature(slotId); + if (f != null) { + setupFeature(f, slotId, ImsFeature.EMERGENCY_MMTEL, c); + return f.getBinder(); + } else { + return null; } } - // Be sure to lock on mFeatures before accessing this method - private MMTelFeature resolveMMTelFeature(int slotId, int featureType) { - SparseArray<ImsFeature> features = getImsFeatureMap(slotId); - MMTelFeature feature = null; - if (features != null) { - feature = resolveImsFeature(features, featureType, MMTelFeature.class); + private IImsMMTelFeature createMMTelFeatureInternal(int slotId, + IImsFeatureStatusCallback c) { + MMTelFeature f = onCreateMMTelImsFeature(slotId); + if (f != null) { + setupFeature(f, slotId, ImsFeature.MMTEL, c); + return f.getBinder(); + } else { + return null; } - return feature; } - // Be sure to lock on mFeatures before accessing this method - private <T extends ImsFeature> T resolveImsFeature(SparseArray<ImsFeature> set, int featureType, - Class<T> className) { - ImsFeature feature = getImsFeatureFromType(set, featureType); - if (feature == null) { + private IImsRcsFeature createRcsFeatureInternal(int slotId, + IImsFeatureStatusCallback c) { + RcsFeature f = onCreateRcsFeature(slotId); + if (f != null) { + setupFeature(f, slotId, ImsFeature.RCS, c); + return f.getBinder(); + } else { return null; } - try { - return className.cast(feature); - } catch (ClassCastException e) - { - Log.e(LOG_TAG, "Can not cast ImsFeature! Exception: " + e.getMessage()); - } - return null; } - /** - * @hide - */ - @VisibleForTesting - // Be sure to lock on mFeatures before accessing this method - public SparseArray<ImsFeature> getImsFeatureMap(int slotId) { - return mFeatures.get(slotId); - } - - /** - * @hide - */ - @VisibleForTesting - // Be sure to lock on mFeatures before accessing this method - public ImsFeature getImsFeatureFromType(SparseArray<ImsFeature> set, int featureType) { - return set.get(featureType); + private void setupFeature(ImsFeature f, int slotId, int featureType, + IImsFeatureStatusCallback c) { + f.setContext(this); + f.setSlotId(slotId); + f.addImsFeatureStatusCallback(c); + addImsFeature(slotId, featureType, f); } - private ImsFeature makeImsFeature(int slotId, int feature) { - switch (feature) { - case ImsFeature.EMERGENCY_MMTEL: { - return onCreateEmergencyMMTelImsFeature(slotId); - } - case ImsFeature.MMTEL: { - return onCreateMMTelImsFeature(slotId); - } - case ImsFeature.RCS: { - return onCreateRcsFeature(slotId); + private void addImsFeature(int slotId, int featureType, ImsFeature f) { + synchronized (mFeaturesBySlot) { + // Get SparseArray for Features, by querying slot Id + SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId); + if (features == null) { + // Populate new SparseArray of features if it doesn't exist for this slot yet. + features = new SparseArray<>(); + mFeaturesBySlot.put(slotId, features); } + features.put(featureType, f); } - // Tried to create feature that is not defined. - return null; } - /** - * Check for both READ_PHONE_STATE and READ_PRIVILEGED_PHONE_STATE. READ_PHONE_STATE is a - * public permission and READ_PRIVILEGED_PHONE_STATE is only granted to system apps. - */ - private void enforceReadPhoneStatePermission(String fn) { - if (checkCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE) - != PackageManager.PERMISSION_GRANTED) { - enforceCallingOrSelfPermission(READ_PHONE_STATE, fn); + private void removeImsFeature(int slotId, int featureType, + IImsFeatureStatusCallback c) { + synchronized (mFeaturesBySlot) { + // get ImsFeature associated with the slot/feature + SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId); + if (features == null) { + Log.w(LOG_TAG, "Can not remove ImsFeature. No ImsFeatures exist on slot " + + slotId); + return; + } + ImsFeature f = features.get(featureType); + if (f == null) { + Log.w(LOG_TAG, "Can not remove ImsFeature. No feature with type " + + featureType + " exists on slot " + slotId); + return; + } + f.removeImsFeatureStatusCallback(c); + f.onFeatureRemoved(); + features.remove(featureType); } } @@ -470,7 +216,7 @@ public class ImsService extends Service { * functionality. Must be able to handle emergency calls at any time as well. * @hide */ - public MMTelFeature onCreateEmergencyMMTelImsFeature(int slotId) { + public @Nullable MMTelFeature onCreateEmergencyMMTelImsFeature(int slotId) { return null; } @@ -479,7 +225,7 @@ public class ImsService extends Service { * functionality. * @hide */ - public MMTelFeature onCreateMMTelImsFeature(int slotId) { + public @Nullable MMTelFeature onCreateMMTelImsFeature(int slotId) { return null; } @@ -487,7 +233,7 @@ public class ImsService extends Service { * @return An implementation of RcsFeature that will be used by the system for RCS. * @hide */ - public RcsFeature onCreateRcsFeature(int slotId) { + public @Nullable RcsFeature onCreateRcsFeature(int slotId) { return null; } } diff --git a/android/telephony/ims/feature/ImsFeature.java b/android/telephony/ims/feature/ImsFeature.java index 9d880b73..062858d4 100644 --- a/android/telephony/ims/feature/ImsFeature.java +++ b/android/telephony/ims/feature/ImsFeature.java @@ -19,6 +19,7 @@ package android.telephony.ims.feature; import android.annotation.IntDef; import android.content.Context; import android.content.Intent; +import android.os.IInterface; import android.os.RemoteException; import android.telephony.SubscriptionManager; import android.util.Log; @@ -91,17 +92,12 @@ public abstract class ImsFeature { public static final int STATE_INITIALIZING = 1; public static final int STATE_READY = 2; - private List<INotifyFeatureRemoved> mRemovedListeners = new ArrayList<>(); private final Set<IImsFeatureStatusCallback> mStatusCallbacks = Collections.newSetFromMap( new WeakHashMap<IImsFeatureStatusCallback, Boolean>()); private @ImsState int mState = STATE_NOT_AVAILABLE; private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; private Context mContext; - public interface INotifyFeatureRemoved { - void onFeatureRemoved(int slotId); - } - public void setContext(Context context) { mContext = context; } @@ -110,26 +106,6 @@ public abstract class ImsFeature { mSlotId = slotId; } - public void addFeatureRemovedListener(INotifyFeatureRemoved listener) { - synchronized (mRemovedListeners) { - mRemovedListeners.add(listener); - } - } - - public void removeFeatureRemovedListener(INotifyFeatureRemoved listener) { - synchronized (mRemovedListeners) { - mRemovedListeners.remove(listener); - } - } - - // Not final for testing. - public void notifyFeatureRemoved(int slotId) { - synchronized (mRemovedListeners) { - mRemovedListeners.forEach(l -> l.onFeatureRemoved(slotId)); - onFeatureRemoved(); - } - } - public int getFeatureState() { return mState; } @@ -215,4 +191,9 @@ public abstract class ImsFeature { * Called when the feature is being removed and must be cleaned up. */ public abstract void onFeatureRemoved(); + + /** + * @return Binder instance + */ + public abstract IInterface getBinder(); } diff --git a/android/telephony/ims/feature/MMTelFeature.java b/android/telephony/ims/feature/MMTelFeature.java index 758c379f..e790d146 100644 --- a/android/telephony/ims/feature/MMTelFeature.java +++ b/android/telephony/ims/feature/MMTelFeature.java @@ -18,18 +18,18 @@ package android.telephony.ims.feature; import android.app.PendingIntent; import android.os.Message; +import android.os.RemoteException; import com.android.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsCallSessionListener; import com.android.ims.internal.IImsConfig; import com.android.ims.internal.IImsEcbm; +import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsMultiEndpoint; import com.android.ims.internal.IImsRegistrationListener; import com.android.ims.internal.IImsUt; - -import java.util.ArrayList; -import java.util.List; +import com.android.ims.internal.ImsCallSession; /** * Base implementation for MMTel. @@ -41,6 +41,146 @@ import java.util.List; public class MMTelFeature extends ImsFeature { + // Lock for feature synchronization + private final Object mLock = new Object(); + + private final IImsMMTelFeature mImsMMTelBinder = new IImsMMTelFeature.Stub() { + + @Override + public int startSession(PendingIntent incomingCallIntent, + IImsRegistrationListener listener) throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.startSession(incomingCallIntent, listener); + } + } + + @Override + public void endSession(int sessionId) throws RemoteException { + synchronized (mLock) { + MMTelFeature.this.endSession(sessionId); + } + } + + @Override + public boolean isConnected(int callSessionType, int callType) + throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.isConnected(callSessionType, callType); + } + } + + @Override + public boolean isOpened() throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.isOpened(); + } + } + + @Override + public int getFeatureStatus() throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.getFeatureState(); + } + } + + @Override + public void addRegistrationListener(IImsRegistrationListener listener) + throws RemoteException { + synchronized (mLock) { + MMTelFeature.this.addRegistrationListener(listener); + } + } + + @Override + public void removeRegistrationListener(IImsRegistrationListener listener) + throws RemoteException { + synchronized (mLock) { + MMTelFeature.this.removeRegistrationListener(listener); + } + } + + @Override + public ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType) + throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.createCallProfile(sessionId, callSessionType, callType); + } + } + + @Override + public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile, + IImsCallSessionListener listener) throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.createCallSession(sessionId, profile, listener); + } + } + + @Override + public IImsCallSession getPendingCallSession(int sessionId, String callId) + throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.getPendingCallSession(sessionId, callId); + } + } + + @Override + public IImsUt getUtInterface() throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.getUtInterface(); + } + } + + @Override + public IImsConfig getConfigInterface() throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.getConfigInterface(); + } + } + + @Override + public void turnOnIms() throws RemoteException { + synchronized (mLock) { + MMTelFeature.this.turnOnIms(); + } + } + + @Override + public void turnOffIms() throws RemoteException { + synchronized (mLock) { + MMTelFeature.this.turnOffIms(); + } + } + + @Override + public IImsEcbm getEcbmInterface() throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.getEcbmInterface(); + } + } + + @Override + public void setUiTTYMode(int uiTtyMode, Message onComplete) throws RemoteException { + synchronized (mLock) { + MMTelFeature.this.setUiTTYMode(uiTtyMode, onComplete); + } + } + + @Override + public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.getMultiEndpointInterface(); + } + } + }; + + /** + * @hide + */ + @Override + public final IImsMMTelFeature getBinder() { + return mImsMMTelBinder; + } + /** * Notifies the MMTel feature that you would like to start a session. This should always be * done before making/receiving IMS calls. The IMS service will register the device to the @@ -135,7 +275,7 @@ public class MMTelFeature extends ImsFeature { } /** - * Creates a {@link ImsCallSession} with the specified call profile. + * Creates an {@link ImsCallSession} with the specified call profile. * Use other methods, if applicable, instead of interacting with * {@link ImsCallSession} directly. * diff --git a/android/telephony/ims/feature/RcsFeature.java b/android/telephony/ims/feature/RcsFeature.java index 332cca3e..a82e6086 100644 --- a/android/telephony/ims/feature/RcsFeature.java +++ b/android/telephony/ims/feature/RcsFeature.java @@ -16,6 +16,8 @@ package android.telephony.ims.feature; +import com.android.ims.internal.IImsRcsFeature; + /** * Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend * this class and provide implementations of the RcsFeature methods that they support. @@ -24,6 +26,11 @@ package android.telephony.ims.feature; public class RcsFeature extends ImsFeature { + private final IImsRcsFeature mImsRcsBinder = new IImsRcsFeature.Stub() { + // Empty Default Implementation. + }; + + public RcsFeature() { super(); } @@ -32,4 +39,9 @@ public class RcsFeature extends ImsFeature { public void onFeatureRemoved() { } + + @Override + public final IImsRcsFeature getBinder() { + return mImsRcsBinder; + } } diff --git a/android/test/mock/MockPackageManager.java b/android/test/mock/MockPackageManager.java index 7e08f51c..0c562e65 100644 --- a/android/test/mock/MockPackageManager.java +++ b/android/test/mock/MockPackageManager.java @@ -26,12 +26,11 @@ import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ChangedPackages; -import android.content.pm.InstantAppInfo; import android.content.pm.FeatureInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; -import android.content.pm.IPackageInstallObserver; import android.content.pm.IPackageStatsObserver; +import android.content.pm.InstantAppInfo; import android.content.pm.InstrumentationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.KeySet; @@ -653,15 +652,6 @@ public class MockPackageManager extends PackageManager { throw new UnsupportedOperationException(); } - /** - * @hide - to match hiding in superclass - */ - @Override - public void installPackage(Uri packageURI, IPackageInstallObserver observer, - int flags, String installerPackageName) { - throw new UnsupportedOperationException(); - } - @Override public void setInstallerPackageName(String targetPackage, String installerPackageName) { diff --git a/android/test/suitebuilder/TestSuiteBuilder.java b/android/test/suitebuilder/TestSuiteBuilder.java index 6158e0cf..2857696e 100644 --- a/android/test/suitebuilder/TestSuiteBuilder.java +++ b/android/test/suitebuilder/TestSuiteBuilder.java @@ -119,6 +119,7 @@ public class TestSuiteBuilder { * * @param predicates Predicates to add to the list of requirements. * @return The builder for method chaining. + * @hide */ public TestSuiteBuilder addRequirements(List<Predicate<TestMethod>> predicates) { this.predicates.addAll(predicates); @@ -156,7 +157,7 @@ public class TestSuiteBuilder { /** * Override the default name for the suite being built. This should generally be called if you - * call {@link #addRequirements(com.android.internal.util.Predicate[])} to make it clear which + * call {@code addRequirements(com.android.internal.util.Predicate[])} to make it clear which * tests will be included. The name you specify is automatically prefixed with the package * containing the tests to be run. If more than one package is specified, the first is used. * @@ -215,6 +216,7 @@ public class TestSuiteBuilder { * * @param predicates Predicates to add to the list of requirements. * @return The builder for method chaining. + * @hide */ public final TestSuiteBuilder addRequirements(Predicate<TestMethod>... predicates) { ArrayList<Predicate<TestMethod>> list = new ArrayList<Predicate<TestMethod>>(); diff --git a/android/text/StaticLayout_Delegate.java b/android/text/StaticLayout_Delegate.java index ef7525ec..ca8743c7 100644 --- a/android/text/StaticLayout_Delegate.java +++ b/android/text/StaticLayout_Delegate.java @@ -15,6 +15,7 @@ import android.text.Layout.HyphenationFrequency; import android.text.Primitive.PrimitiveType; import android.text.StaticLayout.LineBreaks; +import java.text.CharacterIterator; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -116,7 +117,7 @@ public class StaticLayout_Delegate { // compute all possible breakpoints. BreakIterator it = BreakIterator.getLineInstance(); - it.setText(new Segment(builder.mText, 0, length)); + it.setText((CharacterIterator) new Segment(builder.mText, 0, length)); // average word length in english is 5. So, initialize the possible breaks with a guess. List<Integer> breaks = new ArrayList<Integer>((int) Math.ceil(length / 5d)); diff --git a/android/text/format/Formatter.java b/android/text/format/Formatter.java index 2c83fc4d..8c90156d 100644 --- a/android/text/format/Formatter.java +++ b/android/text/format/Formatter.java @@ -355,21 +355,21 @@ public final class Formatter { final Locale locale = localeFromContext(context); final MeasureFormat measureFormat = MeasureFormat.getInstance( locale, MeasureFormat.FormatWidth.SHORT); - if (days >= 2) { + if (days >= 2 || (days > 0 && hours == 0)) { days += (hours+12)/24; return measureFormat.format(new Measure(days, MeasureUnit.DAY)); } else if (days > 0) { return measureFormat.formatMeasures( new Measure(days, MeasureUnit.DAY), new Measure(hours, MeasureUnit.HOUR)); - } else if (hours >= 2) { + } else if (hours >= 2 || (hours > 0 && minutes == 0)) { hours += (minutes+30)/60; return measureFormat.format(new Measure(hours, MeasureUnit.HOUR)); } else if (hours > 0) { return measureFormat.formatMeasures( new Measure(hours, MeasureUnit.HOUR), new Measure(minutes, MeasureUnit.MINUTE)); - } else if (minutes >= 2) { + } else if (minutes >= 2 || (minutes > 0 && seconds == 0)) { minutes += (seconds+30)/60; return measureFormat.format(new Measure(minutes, MeasureUnit.MINUTE)); } else if (minutes > 0) { diff --git a/android/util/AndroidException.java b/android/util/AndroidException.java index dfe00c9b..1345ddf1 100644 --- a/android/util/AndroidException.java +++ b/android/util/AndroidException.java @@ -34,5 +34,11 @@ public class AndroidException extends Exception { public AndroidException(Exception cause) { super(cause); } + + /** @hide */ + protected AndroidException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } }; diff --git a/android/util/FeatureFlagUtils.java b/android/util/FeatureFlagUtils.java index 2a272208..29baea17 100644 --- a/android/util/FeatureFlagUtils.java +++ b/android/util/FeatureFlagUtils.java @@ -18,6 +18,7 @@ package android.util; import android.content.Context; import android.os.SystemProperties; +import android.provider.Settings; import android.text.TextUtils; import java.util.Map; @@ -39,13 +40,24 @@ public class FeatureFlagUtils { * @return true if the flag is enabled (either by default in system, or override by user) */ public static boolean isEnabled(Context context, String feature) { - // Tries to get feature flag from system property. - // Step 1: check if feature flag has any override. Flag name: sys.fflag.override.<feature> - String value = SystemProperties.get(FFLAG_OVERRIDE_PREFIX + feature); + // Override precedence: + // Settings.Global -> sys.fflag.override.* -> sys.fflag.* + + // Step 1: check if feature flag is set in Settings.Global. + String value; + if (context != null) { + value = Settings.Global.getString(context.getContentResolver(), feature); + if (!TextUtils.isEmpty(value)) { + return Boolean.parseBoolean(value); + } + } + + // Step 2: check if feature flag has any override. Flag name: sys.fflag.override.<feature> + value = SystemProperties.get(FFLAG_OVERRIDE_PREFIX + feature); if (!TextUtils.isEmpty(value)) { return Boolean.parseBoolean(value); } - // Step 2: check if feature flag has any default value. Flag name: sys.fflag.<feature> + // Step 3: check if feature flag has any default value. Flag name: sys.fflag.<feature> value = SystemProperties.get(FFLAG_PREFIX + feature); return Boolean.parseBoolean(value); } @@ -53,7 +65,7 @@ public class FeatureFlagUtils { /** * Override feature flag to new state. */ - public static void setEnabled(String feature, boolean enabled) { + public static void setEnabled(Context context, String feature, boolean enabled) { SystemProperties.set(FFLAG_OVERRIDE_PREFIX + feature, enabled ? "true" : "false"); } diff --git a/android/util/Log.java b/android/util/Log.java index b94e48b3..02998653 100644 --- a/android/util/Log.java +++ b/android/util/Log.java @@ -16,12 +16,45 @@ package android.util; +import android.os.DeadSystemException; + +import com.android.internal.os.RuntimeInit; +import com.android.internal.util.FastPrintWriter; +import com.android.internal.util.LineBreakBufferedWriter; + import java.io.PrintWriter; import java.io.StringWriter; +import java.io.Writer; import java.net.UnknownHostException; /** - * Mock Log implementation for testing on non android host. + * API for sending log output. + * + * <p>Generally, you should use the {@link #v Log.v()}, {@link #d Log.d()}, + * {@link #i Log.i()}, {@link #w Log.w()}, and {@link #e Log.e()} methods to write logs. + * You can then <a href="{@docRoot}studio/debug/am-logcat.html">view the logs in logcat</a>. + * + * <p>The order in terms of verbosity, from least to most is + * ERROR, WARN, INFO, DEBUG, VERBOSE. Verbose should never be compiled + * into an application except during development. Debug logs are compiled + * in but stripped at runtime. Error, warning and info logs are always kept. + * + * <p><b>Tip:</b> A good convention is to declare a <code>TAG</code> constant + * in your class: + * + * <pre>private static final String TAG = "MyActivity";</pre> + * + * and use that in subsequent calls to the log methods. + * </p> + * + * <p><b>Tip:</b> Don't forget that when you make a call like + * <pre>Log.v(TAG, "index=" + i);</pre> + * that when you're building the string to pass into Log.d, the compiler uses a + * StringBuilder and at least three allocations occur: the StringBuilder + * itself, the buffer, and the String object. Realistically, there is also + * another buffer allocation and copy, and even more pressure on the gc. + * That means that if your log message is filtered out, you might be doing + * significant work and incurring significant overhead. */ public final class Log { @@ -55,6 +88,29 @@ public final class Log { */ public static final int ASSERT = 7; + /** + * Exception class used to capture a stack trace in {@link #wtf}. + * @hide + */ + public static class TerribleFailure extends Exception { + TerribleFailure(String msg, Throwable cause) { super(msg, cause); } + } + + /** + * Interface to handle terrible failures from {@link #wtf}. + * + * @hide + */ + public interface TerribleFailureHandler { + void onTerribleFailure(String tag, TerribleFailure what, boolean system); + } + + private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() { + public void onTerribleFailure(String tag, TerribleFailure what, boolean system) { + RuntimeInit.wtf(tag, what, system); + } + }; + private Log() { } @@ -65,7 +121,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int v(String tag, String msg) { - return println(LOG_ID_MAIN, VERBOSE, tag, msg); + return println_native(LOG_ID_MAIN, VERBOSE, tag, msg); } /** @@ -76,7 +132,7 @@ public final class Log { * @param tr An exception to log */ public static int v(String tag, String msg, Throwable tr) { - return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr); } /** @@ -86,7 +142,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int d(String tag, String msg) { - return println(LOG_ID_MAIN, DEBUG, tag, msg); + return println_native(LOG_ID_MAIN, DEBUG, tag, msg); } /** @@ -97,7 +153,7 @@ public final class Log { * @param tr An exception to log */ public static int d(String tag, String msg, Throwable tr) { - return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr); } /** @@ -107,7 +163,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int i(String tag, String msg) { - return println(LOG_ID_MAIN, INFO, tag, msg); + return println_native(LOG_ID_MAIN, INFO, tag, msg); } /** @@ -118,7 +174,7 @@ public final class Log { * @param tr An exception to log */ public static int i(String tag, String msg, Throwable tr) { - return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, INFO, tag, msg, tr); } /** @@ -128,7 +184,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int w(String tag, String msg) { - return println(LOG_ID_MAIN, WARN, tag, msg); + return println_native(LOG_ID_MAIN, WARN, tag, msg); } /** @@ -139,9 +195,31 @@ public final class Log { * @param tr An exception to log */ public static int w(String tag, String msg, Throwable tr) { - return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, WARN, tag, msg, tr); } + /** + * Checks to see whether or not a log for the specified tag is loggable at the specified level. + * + * The default level of any tag is set to INFO. This means that any level above and including + * INFO will be logged. Before you make any calls to a logging method you should check to see + * if your tag should be logged. You can change the default level by setting a system property: + * 'setprop log.tag.<YOUR_LOG_TAG> <LEVEL>' + * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will + * turn off all logging for your tag. You can also create a local.prop file that with the + * following in it: + * 'log.tag.<YOUR_LOG_TAG>=<LEVEL>' + * and place that in /data/local.prop. + * + * @param tag The tag to check. + * @param level The level to check. + * @return Whether or not that this is allowed to be logged. + * @throws IllegalArgumentException is thrown if the tag.length() > 23 + * for Nougat (7.0) releases (API <= 23) and prior, there is no + * tag limit of concern after this API level. + */ + public static native boolean isLoggable(String tag, int level); + /* * Send a {@link #WARN} log message and log the exception. * @param tag Used to identify the source of a log message. It usually identifies @@ -149,7 +227,7 @@ public final class Log { * @param tr An exception to log */ public static int w(String tag, Throwable tr) { - return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, WARN, tag, "", tr); } /** @@ -159,7 +237,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int e(String tag, String msg) { - return println(LOG_ID_MAIN, ERROR, tag, msg); + return println_native(LOG_ID_MAIN, ERROR, tag, msg); } /** @@ -170,7 +248,82 @@ public final class Log { * @param tr An exception to log */ public static int e(String tag, String msg, Throwable tr) { - return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr); + } + + /** + * What a Terrible Failure: Report a condition that should never happen. + * The error will always be logged at level ASSERT with the call stack. + * Depending on system configuration, a report may be added to the + * {@link android.os.DropBoxManager} and/or the process may be terminated + * immediately with an error dialog. + * @param tag Used to identify the source of a log message. + * @param msg The message you would like logged. + */ + public static int wtf(String tag, String msg) { + return wtf(LOG_ID_MAIN, tag, msg, null, false, false); + } + + /** + * Like {@link #wtf(String, String)}, but also writes to the log the full + * call stack. + * @hide + */ + public static int wtfStack(String tag, String msg) { + return wtf(LOG_ID_MAIN, tag, msg, null, true, false); + } + + /** + * What a Terrible Failure: Report an exception that should never happen. + * Similar to {@link #wtf(String, String)}, with an exception to log. + * @param tag Used to identify the source of a log message. + * @param tr An exception to log. + */ + public static int wtf(String tag, Throwable tr) { + return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false, false); + } + + /** + * What a Terrible Failure: Report an exception that should never happen. + * Similar to {@link #wtf(String, Throwable)}, with a message as well. + * @param tag Used to identify the source of a log message. + * @param msg The message you would like logged. + * @param tr An exception to log. May be null. + */ + public static int wtf(String tag, String msg, Throwable tr) { + return wtf(LOG_ID_MAIN, tag, msg, tr, false, false); + } + + static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack, + boolean system) { + TerribleFailure what = new TerribleFailure(msg, tr); + // Only mark this as ERROR, do not use ASSERT since that should be + // reserved for cases where the system is guaranteed to abort. + // The onTerribleFailure call does not always cause a crash. + int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr); + sWtfHandler.onTerribleFailure(tag, what, system); + return bytes; + } + + static void wtfQuiet(int logId, String tag, String msg, boolean system) { + TerribleFailure what = new TerribleFailure(msg, null); + sWtfHandler.onTerribleFailure(tag, what, system); + } + + /** + * Sets the terrible failure handler, for testing. + * + * @return the old handler + * + * @hide + */ + public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) { + if (handler == null) { + throw new NullPointerException("handler == null"); + } + TerribleFailureHandler oldHandler = sWtfHandler; + sWtfHandler = handler; + return oldHandler; } /** @@ -193,7 +346,7 @@ public final class Log { } StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); + PrintWriter pw = new FastPrintWriter(sw, false, 256); tr.printStackTrace(pw); pw.flush(); return sw.toString(); @@ -208,7 +361,7 @@ public final class Log { * @return The number of bytes written. */ public static int println(int priority, String tag, String msg) { - return println(LOG_ID_MAIN, priority, tag, msg); + return println_native(LOG_ID_MAIN, priority, tag, msg); } /** @hide */ public static final int LOG_ID_MAIN = 0; @@ -217,9 +370,115 @@ public final class Log { /** @hide */ public static final int LOG_ID_SYSTEM = 3; /** @hide */ public static final int LOG_ID_CRASH = 4; - /** @hide */ @SuppressWarnings("unused") - public static int println(int bufID, - int priority, String tag, String msg) { - return 0; + /** @hide */ public static native int println_native(int bufID, + int priority, String tag, String msg); + + /** + * Return the maximum payload the log daemon accepts without truncation. + * @return LOGGER_ENTRY_MAX_PAYLOAD. + */ + private static native int logger_entry_max_payload_native(); + + /** + * Helper function for long messages. Uses the LineBreakBufferedWriter to break + * up long messages and stacktraces along newlines, but tries to write in large + * chunks. This is to avoid truncation. + * @hide + */ + public static int printlns(int bufID, int priority, String tag, String msg, + Throwable tr) { + ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag); + // Acceptable buffer size. Get the native buffer size, subtract two zero terminators, + // and the length of the tag. + // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It + // is too expensive to compute that ahead of time. + int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base. + - 2 // Two terminators. + - (tag != null ? tag.length() : 0) // Tag length. + - 32; // Some slack. + // At least assume you can print *some* characters (tag is not too large). + bufferSize = Math.max(bufferSize, 100); + + LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize); + + lbbw.println(msg); + + if (tr != null) { + // This is to reduce the amount of log spew that apps do in the non-error + // condition of the network being unavailable. + Throwable t = tr; + while (t != null) { + if (t instanceof UnknownHostException) { + break; + } + if (t instanceof DeadSystemException) { + lbbw.println("DeadSystemException: The system died; " + + "earlier logs will point to the root cause"); + break; + } + t = t.getCause(); + } + if (t == null) { + tr.printStackTrace(lbbw); + } + } + + lbbw.flush(); + + return logWriter.getWritten(); + } + + /** + * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid + * a JNI call during logging. + */ + static class PreloadHolder { + public final static int LOGGER_ENTRY_MAX_PAYLOAD = + logger_entry_max_payload_native(); + } + + /** + * Helper class to write to the logcat. Different from LogWriter, this writes + * the whole given buffer and does not break along newlines. + */ + private static class ImmediateLogWriter extends Writer { + + private int bufID; + private int priority; + private String tag; + + private int written = 0; + + /** + * Create a writer that immediately writes to the log, using the given + * parameters. + */ + public ImmediateLogWriter(int bufID, int priority, String tag) { + this.bufID = bufID; + this.priority = priority; + this.tag = tag; + } + + public int getWritten() { + return written; + } + + @Override + public void write(char[] cbuf, int off, int len) { + // Note: using String here has a bit of overhead as a Java object is created, + // but using the char[] directly is not easier, as it needs to be translated + // to a C char[] for logging. + written += println_native(bufID, priority, tag, new String(cbuf, off, len)); + } + + @Override + public void flush() { + // Ignored. + } + + @Override + public void close() { + // Ignored. + } } } diff --git a/android/util/StatsManager.java b/android/util/StatsManager.java index 55b33a61..2bcd863c 100644 --- a/android/util/StatsManager.java +++ b/android/util/StatsManager.java @@ -58,11 +58,12 @@ public final class StatsManager { try { IStatsManager service = getIStatsManagerLocked(); if (service == null) { - throw new RuntimeException("StatsD service connection lost"); + Slog.d(TAG, "Failed to find statsd when adding configuration"); + return false; } return service.addConfiguration(configKey, config, pkg, cls); } catch (RemoteException e) { - Slog.d(TAG, "Failed to connect to statsd when getting data"); + Slog.d(TAG, "Failed to connect to statsd when adding configuration"); return false; } } @@ -80,11 +81,12 @@ public final class StatsManager { try { IStatsManager service = getIStatsManagerLocked(); if (service == null) { - throw new RuntimeException("StatsD service connection lost"); + Slog.d(TAG, "Failed to find statsd when removing configuration"); + return false; } return service.removeConfiguration(configKey); } catch (RemoteException e) { - Slog.d(TAG, "Failed to connect to statsd when getting data"); + Slog.d(TAG, "Failed to connect to statsd when removing configuration"); return false; } } @@ -102,7 +104,8 @@ public final class StatsManager { try { IStatsManager service = getIStatsManagerLocked(); if (service == null) { - throw new RuntimeException("StatsD service connection lost"); + Slog.d(TAG, "Failed to find statsd when getting data"); + return null; } return service.getData(configKey); } catch (RemoteException e) { diff --git a/android/util/proto/ProtoUtils.java b/android/util/proto/ProtoUtils.java index 449baca5..85b7ec82 100644 --- a/android/util/proto/ProtoUtils.java +++ b/android/util/proto/ProtoUtils.java @@ -17,6 +17,7 @@ package android.util.proto; import android.util.AggStats; +import android.util.Duration; /** * This class contains a list of helper functions to write common proto in @@ -36,4 +37,15 @@ public class ProtoUtils { proto.write(AggStats.MAX, max); proto.end(aggStatsToken); } + + /** + * Dump Duration to ProtoOutputStream + * @hide + */ + public static void toDuration(ProtoOutputStream proto, long fieldId, long startMs, long endMs) { + final long token = proto.start(fieldId); + proto.write(Duration.START_MS, startMs); + proto.write(Duration.END_MS, endMs); + proto.end(token); + } } diff --git a/android/view/Display.java b/android/view/Display.java index 6a44cdb9..9673be01 100644 --- a/android/view/Display.java +++ b/android/view/Display.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE; import android.annotation.IntDef; import android.annotation.RequiresPermission; +import android.app.KeyguardManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -209,8 +210,8 @@ public final class Display { * </p> * * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD - * @see WindowManagerPolicy#isKeyguardSecure(int) - * @see WindowManagerPolicy#isKeyguardTrustedLw() + * @see KeyguardManager#isDeviceSecure() + * @see KeyguardManager#isDeviceLocked() * @see #getFlags * @hide */ diff --git a/android/view/DisplayCutout.java b/android/view/DisplayCutout.java new file mode 100644 index 00000000..19cd42e1 --- /dev/null +++ b/android/view/DisplayCutout.java @@ -0,0 +1,408 @@ +/* + * 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.view; + +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + +import android.annotation.NonNull; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a part of the display that is not functional for displaying content. + * + * <p>{@code DisplayCutout} is immutable. + * + * @hide will become API + */ +public final class DisplayCutout { + + private static final Rect ZERO_RECT = new Rect(0, 0, 0, 0); + private static final ArrayList<Point> EMPTY_LIST = new ArrayList<>(); + + /** + * An instance where {@link #hasCutout()} returns {@code false}. + * + * @hide + */ + public static final DisplayCutout NO_CUTOUT = + new DisplayCutout(ZERO_RECT, ZERO_RECT, EMPTY_LIST); + + private final Rect mSafeInsets; + private final Rect mBoundingRect; + private final List<Point> mBoundingPolygon; + + /** + * Creates a DisplayCutout instance. + * + * NOTE: the Rects passed into this instance are not copied and MUST remain unchanged. + * + * @hide + */ + @VisibleForTesting + public DisplayCutout(Rect safeInsets, Rect boundingRect, List<Point> boundingPolygon) { + mSafeInsets = safeInsets != null ? safeInsets : ZERO_RECT; + mBoundingRect = boundingRect != null ? boundingRect : ZERO_RECT; + mBoundingPolygon = boundingPolygon != null ? boundingPolygon : EMPTY_LIST; + } + + /** + * Returns whether there is a cutout. + * + * If false, the safe insets will all return zero, and the bounding box or polygon will be + * empty or outside the content view. + * + * @return {@code true} if there is a cutout, {@code false} otherwise + */ + public boolean hasCutout() { + return !mSafeInsets.equals(ZERO_RECT); + } + + /** Returns the inset from the top which avoids the display cutout. */ + public int getSafeInsetTop() { + return mSafeInsets.top; + } + + /** Returns the inset from the bottom which avoids the display cutout. */ + public int getSafeInsetBottom() { + return mSafeInsets.bottom; + } + + /** Returns the inset from the left which avoids the display cutout. */ + public int getSafeInsetLeft() { + return mSafeInsets.left; + } + + /** Returns the inset from the right which avoids the display cutout. */ + public int getSafeInsetRight() { + return mSafeInsets.right; + } + + /** + * Obtains the safe insets in a rect. + * + * @param out a rect which is set to the safe insets. + * @hide + */ + public void getSafeInsets(@NonNull Rect out) { + out.set(mSafeInsets); + } + + /** + * Obtains the bounding rect of the cutout. + * + * @param outRect is filled with the bounding rect of the cutout. Coordinates are relative + * to the top-left corner of the content view. + */ + public void getBoundingRect(@NonNull Rect outRect) { + outRect.set(mBoundingRect); + } + + /** + * Obtains the bounding polygon of the cutout. + * + * @param outPolygon is filled with a list of points representing the corners of a convex + * polygon which covers the cutout. Coordinates are relative to the + * top-left corner of the content view. + */ + public void getBoundingPolygon(List<Point> outPolygon) { + outPolygon.clear(); + for (int i = 0; i < mBoundingPolygon.size(); i++) { + outPolygon.add(new Point(mBoundingPolygon.get(i))); + } + } + + @Override + public int hashCode() { + int result = mSafeInsets.hashCode(); + result = result * 31 + mBoundingRect.hashCode(); + result = result * 31 + mBoundingPolygon.hashCode(); + return result; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof DisplayCutout) { + DisplayCutout c = (DisplayCutout) o; + return mSafeInsets.equals(c.mSafeInsets) + && mBoundingRect.equals(c.mBoundingRect) + && mBoundingPolygon.equals(c.mBoundingPolygon); + } + return false; + } + + @Override + public String toString() { + return "DisplayCutout{insets=" + mSafeInsets + + " bounding=" + mBoundingRect + + "}"; + } + + /** + * Insets the reference frame of the cutout in the given directions. + * + * @return a copy of this instance which has been inset + * @hide + */ + public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) { + if (mBoundingRect.isEmpty() + || insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) { + return this; + } + + Rect safeInsets = new Rect(mSafeInsets); + Rect boundingRect = new Rect(mBoundingRect); + ArrayList<Point> boundingPolygon = new ArrayList<>(); + getBoundingPolygon(boundingPolygon); + + // Note: it's not really well defined what happens when the inset is negative, because we + // don't know if the safe inset needs to expand in general. + if (insetTop > 0 || safeInsets.top > 0) { + safeInsets.top = atLeastZero(safeInsets.top - insetTop); + } + if (insetBottom > 0 || safeInsets.bottom > 0) { + safeInsets.bottom = atLeastZero(safeInsets.bottom - insetBottom); + } + if (insetLeft > 0 || safeInsets.left > 0) { + safeInsets.left = atLeastZero(safeInsets.left - insetLeft); + } + if (insetRight > 0 || safeInsets.right > 0) { + safeInsets.right = atLeastZero(safeInsets.right - insetRight); + } + + boundingRect.offset(-insetLeft, -insetTop); + offset(boundingPolygon, -insetLeft, -insetTop); + + return new DisplayCutout(safeInsets, boundingRect, boundingPolygon); + } + + /** + * Calculates the safe insets relative to the given reference frame. + * + * @return a copy of this instance with the safe insets calculated + * @hide + */ + public DisplayCutout calculateRelativeTo(Rect frame) { + if (mBoundingRect.isEmpty() || !Rect.intersects(frame, mBoundingRect)) { + return NO_CUTOUT; + } + + Rect boundingRect = new Rect(mBoundingRect); + ArrayList<Point> boundingPolygon = new ArrayList<>(); + getBoundingPolygon(boundingPolygon); + + return DisplayCutout.calculateRelativeTo(frame, boundingRect, boundingPolygon); + } + + private static DisplayCutout calculateRelativeTo(Rect frame, Rect boundingRect, + ArrayList<Point> boundingPolygon) { + Rect safeRect = new Rect(); + int bestArea = 0; + int bestVariant = 0; + for (int variant = ROTATION_0; variant <= ROTATION_270; variant++) { + int area = calculateInsetVariantArea(frame, boundingRect, variant, safeRect); + if (bestArea < area) { + bestArea = area; + bestVariant = variant; + } + } + calculateInsetVariantArea(frame, boundingRect, bestVariant, safeRect); + if (safeRect.isEmpty()) { + // The entire frame overlaps with the cutout. + safeRect.set(0, frame.height(), 0, 0); + } else { + // Convert safeRect to insets relative to frame. We're reusing the rect here to avoid + // an allocation. + safeRect.set( + Math.max(0, safeRect.left - frame.left), + Math.max(0, safeRect.top - frame.top), + Math.max(0, frame.right - safeRect.right), + Math.max(0, frame.bottom - safeRect.bottom)); + } + + boundingRect.offset(-frame.left, -frame.top); + offset(boundingPolygon, -frame.left, -frame.top); + + return new DisplayCutout(safeRect, boundingRect, boundingPolygon); + } + + private static int calculateInsetVariantArea(Rect frame, Rect boundingRect, int variant, + Rect outSafeRect) { + switch (variant) { + case ROTATION_0: + outSafeRect.set(frame.left, frame.top, frame.right, boundingRect.top); + break; + case ROTATION_90: + outSafeRect.set(frame.left, frame.top, boundingRect.left, frame.bottom); + break; + case ROTATION_180: + outSafeRect.set(frame.left, boundingRect.bottom, frame.right, frame.bottom); + break; + case ROTATION_270: + outSafeRect.set(boundingRect.right, frame.top, frame.right, frame.bottom); + break; + } + + return outSafeRect.isEmpty() ? 0 : outSafeRect.width() * outSafeRect.height(); + } + + private static int atLeastZero(int value) { + return value < 0 ? 0 : value; + } + + private static void offset(ArrayList<Point> points, int dx, int dy) { + for (int i = 0; i < points.size(); i++) { + points.get(i).offset(dx, dy); + } + } + + /** + * Creates an instance from a bounding polygon. + * + * @hide + */ + public static DisplayCutout fromBoundingPolygon(List<Point> points) { + Rect boundingRect = new Rect(Integer.MAX_VALUE, Integer.MAX_VALUE, + Integer.MIN_VALUE, Integer.MIN_VALUE); + ArrayList<Point> boundingPolygon = new ArrayList<>(); + + for (int i = 0; i < points.size(); i++) { + Point point = points.get(i); + boundingRect.left = Math.min(boundingRect.left, point.x); + boundingRect.right = Math.max(boundingRect.right, point.x); + boundingRect.top = Math.min(boundingRect.top, point.y); + boundingRect.bottom = Math.max(boundingRect.bottom, point.y); + boundingPolygon.add(new Point(point)); + } + + return new DisplayCutout(ZERO_RECT, boundingRect, boundingPolygon); + } + + /** + * Helper class for passing {@link DisplayCutout} through binder. + * + * Needed, because {@code readFromParcel} cannot be used with immutable classes. + * + * @hide + */ + public static final class ParcelableWrapper implements Parcelable { + + private DisplayCutout mInner; + + public ParcelableWrapper() { + this(NO_CUTOUT); + } + + public ParcelableWrapper(DisplayCutout cutout) { + mInner = cutout; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + if (mInner == NO_CUTOUT) { + out.writeInt(0); + } else { + out.writeInt(1); + out.writeTypedObject(mInner.mSafeInsets, flags); + out.writeTypedObject(mInner.mBoundingRect, flags); + out.writeTypedList(mInner.mBoundingPolygon, flags); + } + } + + /** + * Similar to {@link Creator#createFromParcel(Parcel)}, but reads into an existing + * instance. + * + * Needed for AIDL out parameters. + */ + public void readFromParcel(Parcel in) { + mInner = readCutout(in); + } + + public static final Creator<ParcelableWrapper> CREATOR = new Creator<ParcelableWrapper>() { + @Override + public ParcelableWrapper createFromParcel(Parcel in) { + return new ParcelableWrapper(readCutout(in)); + } + + @Override + public ParcelableWrapper[] newArray(int size) { + return new ParcelableWrapper[size]; + } + }; + + private static DisplayCutout readCutout(Parcel in) { + if (in.readInt() == 0) { + return NO_CUTOUT; + } + + ArrayList<Point> boundingPolygon = new ArrayList<>(); + + Rect safeInsets = in.readTypedObject(Rect.CREATOR); + Rect boundingRect = in.readTypedObject(Rect.CREATOR); + in.readTypedList(boundingPolygon, Point.CREATOR); + + return new DisplayCutout(safeInsets, boundingRect, boundingPolygon); + } + + public DisplayCutout get() { + return mInner; + } + + public void set(ParcelableWrapper cutout) { + mInner = cutout.get(); + } + + public void set(DisplayCutout cutout) { + mInner = cutout; + } + + @Override + public int hashCode() { + return mInner.hashCode(); + } + + @Override + public boolean equals(Object o) { + return o instanceof ParcelableWrapper + && mInner.equals(((ParcelableWrapper) o).mInner); + } + + @Override + public String toString() { + return String.valueOf(mInner); + } + } +} diff --git a/android/view/Gravity.java b/android/view/Gravity.java index 232ff255..defa58e1 100644 --- a/android/view/Gravity.java +++ b/android/view/Gravity.java @@ -446,50 +446,53 @@ public class Gravity */ public static String toString(int gravity) { final StringBuilder result = new StringBuilder(); - if ((gravity & FILL) != 0) { + if ((gravity & FILL) == FILL) { result.append("FILL").append(' '); } else { - if ((gravity & FILL_VERTICAL) != 0) { + if ((gravity & FILL_VERTICAL) == FILL_VERTICAL) { result.append("FILL_VERTICAL").append(' '); } else { - if ((gravity & TOP) != 0) { + if ((gravity & TOP) == TOP) { result.append("TOP").append(' '); } - if ((gravity & BOTTOM) != 0) { + if ((gravity & BOTTOM) == BOTTOM) { result.append("BOTTOM").append(' '); } } - if ((gravity & FILL_HORIZONTAL) != 0) { + if ((gravity & FILL_HORIZONTAL) == FILL_HORIZONTAL) { result.append("FILL_HORIZONTAL").append(' '); } else { - if ((gravity & START) != 0) { + if ((gravity & START) == START) { result.append("START").append(' '); - } else if ((gravity & LEFT) != 0) { + } else if ((gravity & LEFT) == LEFT) { result.append("LEFT").append(' '); } - if ((gravity & END) != 0) { + if ((gravity & END) == END) { result.append("END").append(' '); - } else if ((gravity & RIGHT) != 0) { + } else if ((gravity & RIGHT) == RIGHT) { result.append("RIGHT").append(' '); } } } - if ((gravity & CENTER) != 0) { + if ((gravity & CENTER) == CENTER) { result.append("CENTER").append(' '); } else { - if ((gravity & CENTER_VERTICAL) != 0) { + if ((gravity & CENTER_VERTICAL) == CENTER_VERTICAL) { result.append("CENTER_VERTICAL").append(' '); } - if ((gravity & CENTER_HORIZONTAL) != 0) { + if ((gravity & CENTER_HORIZONTAL) == CENTER_HORIZONTAL) { result.append("CENTER_HORIZONTAL").append(' '); } } - if ((gravity & DISPLAY_CLIP_VERTICAL) != 0) { - result.append("DISPLAY_CLIP_VERTICAL").append(' '); + if (result.length() == 0) { + result.append("NO GRAVITY").append(' '); } - if ((gravity & DISPLAY_CLIP_VERTICAL) != 0) { + if ((gravity & DISPLAY_CLIP_VERTICAL) == DISPLAY_CLIP_VERTICAL) { result.append("DISPLAY_CLIP_VERTICAL").append(' '); } + if ((gravity & DISPLAY_CLIP_HORIZONTAL) == DISPLAY_CLIP_HORIZONTAL) { + result.append("DISPLAY_CLIP_HORIZONTAL").append(' '); + } result.deleteCharAt(result.length() - 1); return result.toString(); } diff --git a/android/view/InputFilter.java b/android/view/InputFilter.java index d0dab400..0ab4dc02 100644 --- a/android/view/InputFilter.java +++ b/android/view/InputFilter.java @@ -72,21 +72,21 @@ import android.os.RemoteException; * </p><p> * The early policy interception decides whether an input event should be delivered * to applications or dropped. The policy indicates its decision by setting the - * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} policy flag. The input filter may + * {@link WindowManagerPolicyConstants#FLAG_PASS_TO_USER} policy flag. The input filter may * sometimes receive events that do not have this flag set. It should take note of * the fact that the policy intends to drop the event, clean up its state, and * then send appropriate cancellation events to the dispatcher if needed. * </p><p> * For example, suppose the input filter is processing a gesture and one of the touch events - * it receives does not have the {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag set. + * it receives does not have the {@link WindowManagerPolicyConstants#FLAG_PASS_TO_USER} flag set. * The input filter should clear its internal state about the gesture and then send key or * motion events to the dispatcher to cancel any keys or pointers that are down. * </p><p> * Corollary: Events that get sent to the dispatcher should usually include the - * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag. Otherwise, they will be dropped! + * {@link WindowManagerPolicyConstants#FLAG_PASS_TO_USER} flag. Otherwise, they will be dropped! * </p><p> * It may be prudent to disable automatic key repeating for synthetic key events - * by setting the {@link WindowManagerPolicy#FLAG_DISABLE_KEY_REPEAT} policy flag. + * by setting the {@link WindowManagerPolicyConstants#FLAG_DISABLE_KEY_REPEAT} policy flag. * </p> * * @hide diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java index 5641009c..3d01ec23 100644 --- a/android/view/SurfaceControl.java +++ b/android/view/SurfaceControl.java @@ -56,11 +56,15 @@ public class SurfaceControl { Rect sourceCrop, int width, int height, int minLayer, int maxLayer, boolean allLayers, boolean useIdentityTransform); private static native void nativeCaptureLayers(IBinder layerHandleToken, Surface consumer, - int rotation); + Rect sourceCrop, float frameScale); + private static native GraphicBuffer nativeCaptureLayers(IBinder layerHandleToken, + Rect sourceCrop, float frameScale); private static native long nativeCreateTransaction(); private static native long nativeGetNativeTransactionFinalizer(); private static native void nativeApplyTransaction(long transactionObj, boolean sync); + private static native void nativeMergeTransaction(long transactionObj, + long otherTransactionObj); private static native void nativeSetAnimationTransaction(long transactionObj); private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder); @@ -654,6 +658,19 @@ public class SurfaceControl { } } + /** + * Merge the supplied transaction in to the deprecated "global" transaction. + * This clears the supplied transaction in an identical fashion to {@link Transaction#merge}. + * <p> + * This is a utility for interop with legacy-code and will go away with the Global Transaction. + */ + @Deprecated + public static void mergeToGlobalTransaction(Transaction t) { + synchronized(sGlobalTransaction) { + sGlobalTransaction.merge(t); + } + } + /** end a transaction */ public static void closeTransaction() { closeTransaction(false); @@ -1162,14 +1179,24 @@ public class SurfaceControl { * Captures a layer and its children into the provided {@link Surface}. * * @param layerHandleToken The root layer to capture. - * @param consumer The {@link Surface} to capture the layer into. - * @param rotation Apply a custom clockwise rotation to the screenshot, i.e. - * Surface.ROTATION_0,90,180,270. Surfaceflinger will always capture in its - * native portrait orientation by default, so this is useful for returning - * captures that are independent of device orientation. + * @param consumer The {@link Surface} to capture the layer into. + * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new + * Rect()' or null if no cropping is desired. + * @param frameScale The desired scale of the returned buffer; the raw + * screen will be scaled up/down. + */ + public static void captureLayers(IBinder layerHandleToken, Surface consumer, Rect sourceCrop, + float frameScale) { + nativeCaptureLayers(layerHandleToken, consumer, sourceCrop, frameScale); + } + + /** + * Same as {@link #captureLayers(IBinder, Surface, Rect, float)} except this + * captures to a {@link GraphicBuffer} instead of a {@link Surface}. */ - public static void captureLayers(IBinder layerHandleToken, Surface consumer, int rotation) { - nativeCaptureLayers(layerHandleToken, consumer, rotation); + public static GraphicBuffer captureLayersToBuffer(IBinder layerHandleToken, Rect sourceCrop, + float frameScale) { + return nativeCaptureLayers(layerHandleToken, sourceCrop, frameScale); } public static class Transaction implements Closeable { @@ -1368,7 +1395,7 @@ public class SurfaceControl { * Sets the security of the surface. Setting the flag is equivalent to creating the * Surface with the {@link #SECURE} flag. */ - Transaction setSecure(SurfaceControl sc, boolean isSecure) { + public Transaction setSecure(SurfaceControl sc, boolean isSecure) { sc.checkNotReleased(); if (isSecure) { nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE); @@ -1449,5 +1476,14 @@ public class SurfaceControl { nativeSetAnimationTransaction(mNativeObject); return this; } + + /** + * Merge the other transaction into this transaction, clearing the + * other transaction as if it had been applied. + */ + public Transaction merge(Transaction other) { + nativeMergeTransaction(mNativeObject, other.mNativeObject); + return this; + } } } diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java index 4eab496e..578679b1 100644 --- a/android/view/SurfaceView.java +++ b/android/view/SurfaceView.java @@ -17,9 +17,9 @@ package android.view; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; -import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER; -import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER; -import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER; +import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_OVERLAY_SUBLAYER; +import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_SUBLAYER; +import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAYER; import android.content.Context; import android.content.res.CompatibilityInfo.Translator; diff --git a/android/view/View.java b/android/view/View.java index be09fe86..0525ab16 100644 --- a/android/view/View.java +++ b/android/view/View.java @@ -2929,6 +2929,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 1 PFLAG3_TEMPORARY_DETACH * 1 PFLAG3_NO_REVEAL_ON_FOCUS * 1 PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT + * 1 PFLAG3_SCREEN_READER_FOCUSABLE * |-------|-------|-------|-------| */ @@ -3209,6 +3210,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT = 0x8000000; + /** + * Works like focusable for screen readers, but without the side effects on input focus. + * @see #setScreenReaderFocusable(boolean) + */ + private static final int PFLAG3_SCREEN_READER_FOCUSABLE = 0x10000000; + /* End of masks for mPrivateFlags3 */ /** @@ -4245,6 +4252,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, OnApplyWindowInsetsListener mOnApplyWindowInsetsListener; OnCapturedPointerListener mOnCapturedPointerListener; + + private ArrayList<OnKeyFallbackListener> mKeyFallbackListeners; } ListenerInfo mListenerInfo; @@ -5342,6 +5351,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setDefaultFocusHighlightEnabled(a.getBoolean(attr, true)); } break; + case R.styleable.View_screenReaderFocusable: + if (a.peekValue(attr) != null) { + setScreenReaderFocusable(a.getBoolean(attr, false)); + } + break; } } @@ -7194,7 +7208,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param text The announcement text. */ public void announceForAccessibility(CharSequence text) { - if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) { + if (AccessibilityManager.getInstance(mContext).isObservedEventType( + AccessibilityEvent.TYPE_ANNOUNCEMENT) && mParent != null) { AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_ANNOUNCEMENT); onInitializeAccessibilityEvent(event); @@ -8392,6 +8407,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, info.setEnabled(isEnabled()); info.setClickable(isClickable()); info.setFocusable(isFocusable()); + info.setScreenReaderFocusable(isScreenReaderFocusable()); info.setFocused(isFocused()); info.setAccessibilityFocused(isAccessibilityFocused()); info.setSelected(isSelected()); @@ -10348,6 +10364,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Returns whether the view should be treated as a focusable unit by screen reader + * accessibility tools. + * @see #setScreenReaderFocusable(boolean) + * + * @return Whether the view should be treated as a focusable unit by screen reader. + */ + public boolean isScreenReaderFocusable() { + return (mPrivateFlags3 & PFLAG3_SCREEN_READER_FOCUSABLE) != 0; + } + + /** + * When screen readers (one type of accessibility tool) decide what should be read to the + * user, they typically look for input focusable ({@link #isFocusable()}) parents of + * non-focusable text items, and read those focusable parents and their non-focusable children + * as a unit. In some situations, this behavior is desirable for views that should not take + * input focus. Setting an item to be screen reader focusable requests that the view be + * treated as a unit by screen readers without any effect on input focusability. The default + * value of {@code false} lets screen readers use other signals, like focusable, to determine + * how to group items. + * + * @param screenReaderFocusable Whether the view should be treated as a unit by screen reader + * accessibility tools. + */ + public void setScreenReaderFocusable(boolean screenReaderFocusable) { + int pflags3 = mPrivateFlags3; + if (screenReaderFocusable) { + pflags3 |= PFLAG3_SCREEN_READER_FOCUSABLE; + } else { + pflags3 &= ~PFLAG3_SCREEN_READER_FOCUSABLE; + } + + if (pflags3 != mPrivateFlags3) { + mPrivateFlags3 = pflags3; + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + } + } + + /** * Find the nearest view in the specified direction that can take focus. * This does not actually give focus to that view. * @@ -10913,7 +10968,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0) { mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_FOCUSED; invalidate(); - if (AccessibilityManager.getInstance(mContext).isEnabled()) { + if (AccessibilityManager.getInstance(mContext).isObservedEventType( + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED)) { AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); event.setAction(action); @@ -11738,7 +11794,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private void sendViewTextTraversedAtGranularityEvent(int action, int granularity, int fromIndex, int toIndex) { - if (mParent == null) { + if (mParent == null || !AccessibilityManager.getInstance(mContext).isObservedEventType( + AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY)) { return; } AccessibilityEvent event = AccessibilityEvent.obtain( @@ -25194,6 +25251,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Interface definition for a callback to be invoked when a hardware key event is + * dispatched to this view during the fallback phase. This means no view in the hierarchy + * has handled this event. + */ + public interface OnKeyFallbackListener { + /** + * Called when a hardware key is dispatched to a view in the fallback phase. This allows + * listeners to respond to events after the view hierarchy has had a chance to respond. + * <p>Key presses in software keyboards will generally NOT trigger this method, + * although some may elect to do so in some situations. Do not assume a + * software input method has to be key-based; even if it is, it may use key presses + * in a different way than you expect, so there is no way to reliably catch soft + * input key presses. + * + * @param v The view the key has been dispatched to. + * @param event The KeyEvent object containing full information about + * the event. + * @return True if the listener has consumed the event, false otherwise. + */ + boolean onKeyFallback(View v, KeyEvent event); + } + + /** * Interface definition for a callback to be invoked when a touch event is * dispatched to this view. The callback will be invoked before the touch * event is given to the view. @@ -26105,7 +26185,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @Override public void run() { - if (AccessibilityManager.getInstance(mContext).isEnabled()) { + if (AccessibilityManager.getInstance(mContext).isObservedEventType( + AccessibilityEvent.TYPE_VIEW_SCROLLED)) { AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_VIEW_SCROLLED); event.setScrollDeltaX(mDeltaX); @@ -26866,4 +26947,56 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } return mTooltipInfo.mTooltipPopup.getContentView(); } + + /** + * Allows this view to handle {@link KeyEvent}s which weren't handled by normal dispatch. This + * occurs after the normal view hierarchy dispatch, but before the window callback. By default, + * this will dispatch into all the listeners registered via + * {@link #addKeyFallbackListener(OnKeyFallbackListener)} in last-in-first-out order (most + * recently added will receive events first). + * + * @param event A not-previously-handled event. + * @return {@code true} if the event was handled, {@code false} otherwise. + * @see #addKeyFallbackListener + */ + public boolean onKeyFallback(@NonNull KeyEvent event) { + if (mListenerInfo != null && mListenerInfo.mKeyFallbackListeners != null) { + for (int i = mListenerInfo.mKeyFallbackListeners.size() - 1; i >= 0; --i) { + if (mListenerInfo.mKeyFallbackListeners.get(i).onKeyFallback(this, event)) { + return true; + } + } + } + return false; + } + + /** + * Adds a listener which will receive unhandled {@link KeyEvent}s. + * @param listener the receiver of fallback {@link KeyEvent}s. + * @see #onKeyFallback(KeyEvent) + */ + public void addKeyFallbackListener(OnKeyFallbackListener listener) { + ArrayList<OnKeyFallbackListener> fallbacks = getListenerInfo().mKeyFallbackListeners; + if (fallbacks == null) { + fallbacks = new ArrayList<>(); + getListenerInfo().mKeyFallbackListeners = fallbacks; + } + fallbacks.add(listener); + } + + /** + * Removes a listener which will receive unhandled {@link KeyEvent}s. + * @param listener the receiver of fallback {@link KeyEvent}s. + * @see #onKeyFallback(KeyEvent) + */ + public void removeKeyFallbackListener(OnKeyFallbackListener listener) { + if (mListenerInfo != null) { + if (mListenerInfo.mKeyFallbackListeners != null) { + mListenerInfo.mKeyFallbackListeners.remove(listener); + if (mListenerInfo.mKeyFallbackListeners.isEmpty()) { + mListenerInfo.mKeyFallbackListeners = null; + } + } + } + } } diff --git a/android/view/ViewGroup.java b/android/view/ViewGroup.java index 929beaea..122df934 100644 --- a/android/view/ViewGroup.java +++ b/android/view/ViewGroup.java @@ -2591,7 +2591,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { - // If the event is targeting accessiiblity focus we give it to the + // If the event is targeting accessibility focus we give it to the // view that has accessibility focus and if it does not handle it // we clear the flag and dispatch the event to all children as usual. // We are looking up the accessibility focused host to avoid keeping diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java index e30496fb..1c9d8639 100644 --- a/android/view/ViewRootImpl.java +++ b/android/view/ViewRootImpl.java @@ -73,6 +73,7 @@ import android.util.Log; import android.util.MergedConfiguration; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.TimeUtils; import android.util.TypedValue; import android.view.Surface.OutOfResourcesException; @@ -362,6 +363,8 @@ public final class ViewRootImpl implements ViewParent, InputStage mFirstPostImeInputStage; InputStage mSyntheticInputStage; + private final KeyFallbackManager mKeyFallbackManager = new KeyFallbackManager(); + boolean mWindowAttributesChanged = false; int mWindowAttributesChangesFlag = 0; @@ -1560,7 +1563,7 @@ public final class ViewRootImpl implements ViewParent, mLastWindowInsets = new WindowInsets(contentInsets, null /* windowDecorInsets */, stableInsets, mContext.getResources().getConfiguration().isScreenRound(), - mAttachInfo.mAlwaysConsumeNavBar); + mAttachInfo.mAlwaysConsumeNavBar, null /* displayCutout */); } return mLastWindowInsets; } @@ -4764,6 +4767,13 @@ public final class ViewRootImpl implements ViewParent, private int processKeyEvent(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; + mKeyFallbackManager.mDispatched = false; + + if (mKeyFallbackManager.hasFocus() + && mKeyFallbackManager.dispatchUnique(mView, event)) { + return FINISH_HANDLED; + } + // Deliver the key to the view hierarchy. if (mView.dispatchKeyEvent(event)) { return FINISH_HANDLED; @@ -4773,6 +4783,10 @@ public final class ViewRootImpl implements ViewParent, return FINISH_NOT_HANDLED; } + if (mKeyFallbackManager.dispatchUnique(mView, event)) { + return FINISH_HANDLED; + } + int groupNavigationDirection = 0; if (event.getAction() == KeyEvent.ACTION_DOWN @@ -7529,6 +7543,16 @@ public final class ViewRootImpl implements ViewParent, } } + /** + * Dispatches a KeyEvent to all registered key fallback handlers. + * + * @param event + * @return {@code true} if the event was handled, {@code false} otherwise. + */ + public boolean dispatchKeyFallbackEvent(KeyEvent event) { + return mKeyFallbackManager.dispatch(mView, event); + } + class TakenSurfaceHolder extends BaseSurfaceHolder { @Override public boolean onAllowLockCanvas() { @@ -8093,4 +8117,92 @@ public final class ViewRootImpl implements ViewParent, run(); } } + + private static class KeyFallbackManager { + + // This is used to ensure that key-fallback events are only dispatched once. We attempt + // to dispatch more than once in order to achieve a certain order. Specifically, if we + // are in an Activity or Dialog (and have a Window.Callback), the keyfallback events should + // be dispatched after the view hierarchy, but before the Activity. However, if we aren't + // in an activity, we still want key fallbacks to be dispatched. + boolean mDispatched = false; + + SparseBooleanArray mCapturedKeys = new SparseBooleanArray(); + WeakReference<View> mFallbackReceiver = null; + int mVisitCount = 0; + + private void updateCaptureState(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + mCapturedKeys.append(event.getKeyCode(), true); + } + if (event.getAction() == KeyEvent.ACTION_UP) { + mCapturedKeys.delete(event.getKeyCode()); + } + } + + boolean dispatch(View root, KeyEvent event) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "KeyFallback dispatch"); + mDispatched = true; + + updateCaptureState(event); + + if (mFallbackReceiver != null) { + View target = mFallbackReceiver.get(); + if (mCapturedKeys.size() == 0) { + mFallbackReceiver = null; + } + if (target != null && target.isAttachedToWindow()) { + return target.onKeyFallback(event); + } + // consume anyways so that we don't feed uncaptured key events to other views + return true; + } + + boolean result = dispatchInZOrder(root, event); + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + return result; + } + + private boolean dispatchInZOrder(View view, KeyEvent evt) { + if (view instanceof ViewGroup) { + ViewGroup vg = (ViewGroup) view; + ArrayList<View> orderedViews = vg.buildOrderedChildList(); + if (orderedViews != null) { + try { + for (int i = orderedViews.size() - 1; i >= 0; --i) { + View v = orderedViews.get(i); + if (dispatchInZOrder(v, evt)) { + return true; + } + } + } finally { + orderedViews.clear(); + } + } else { + for (int i = vg.getChildCount() - 1; i >= 0; --i) { + View v = vg.getChildAt(i); + if (dispatchInZOrder(v, evt)) { + return true; + } + } + } + } + if (view.onKeyFallback(evt)) { + mFallbackReceiver = new WeakReference<>(view); + return true; + } + return false; + } + + boolean hasFocus() { + return mFallbackReceiver != null; + } + + boolean dispatchUnique(View root, KeyEvent event) { + if (mDispatched) { + return false; + } + return dispatch(root, event); + } + } } diff --git a/android/view/WindowInsets.java b/android/view/WindowInsets.java index 750931ab..df124ac5 100644 --- a/android/view/WindowInsets.java +++ b/android/view/WindowInsets.java @@ -17,6 +17,7 @@ package android.view; +import android.annotation.NonNull; import android.graphics.Rect; /** @@ -36,6 +37,7 @@ public final class WindowInsets { private Rect mStableInsets; private Rect mTempRect; private boolean mIsRound; + private DisplayCutout mDisplayCutout; /** * In multi-window we force show the navigation bar. Because we don't want that the surface size @@ -47,6 +49,7 @@ public final class WindowInsets { private boolean mSystemWindowInsetsConsumed = false; private boolean mWindowDecorInsetsConsumed = false; private boolean mStableInsetsConsumed = false; + private boolean mCutoutConsumed = false; private static final Rect EMPTY_RECT = new Rect(0, 0, 0, 0); @@ -59,12 +62,12 @@ public final class WindowInsets { public static final WindowInsets CONSUMED; static { - CONSUMED = new WindowInsets(null, null, null, false, false); + CONSUMED = new WindowInsets(null, null, null, false, false, null); } /** @hide */ public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets, Rect stableInsets, - boolean isRound, boolean alwaysConsumeNavBar) { + boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) { mSystemWindowInsetsConsumed = systemWindowInsets == null; mSystemWindowInsets = mSystemWindowInsetsConsumed ? EMPTY_RECT : systemWindowInsets; @@ -76,6 +79,9 @@ public final class WindowInsets { mIsRound = isRound; mAlwaysConsumeNavBar = alwaysConsumeNavBar; + + mCutoutConsumed = displayCutout == null; + mDisplayCutout = mCutoutConsumed ? DisplayCutout.NO_CUTOUT : displayCutout; } /** @@ -92,11 +98,13 @@ public final class WindowInsets { mStableInsetsConsumed = src.mStableInsetsConsumed; mIsRound = src.mIsRound; mAlwaysConsumeNavBar = src.mAlwaysConsumeNavBar; + mDisplayCutout = src.mDisplayCutout; + mCutoutConsumed = src.mCutoutConsumed; } /** @hide */ public WindowInsets(Rect systemWindowInsets) { - this(systemWindowInsets, null, null, false, false); + this(systemWindowInsets, null, null, false, false, null); } /** @@ -260,9 +268,34 @@ public final class WindowInsets { * @return true if any inset values are nonzero */ public boolean hasInsets() { - return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets(); + return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets() + || mDisplayCutout.hasCutout(); + } + + /** + * @return the display cutout + * @see DisplayCutout + * @hide pending API + */ + @NonNull + public DisplayCutout getDisplayCutout() { + return mDisplayCutout; + } + + /** + * Returns a copy of this WindowInsets with the cutout fully consumed. + * + * @return A modified copy of this WindowInsets + * @hide pending API + */ + public WindowInsets consumeCutout() { + final WindowInsets result = new WindowInsets(this); + result.mDisplayCutout = DisplayCutout.NO_CUTOUT; + result.mCutoutConsumed = true; + return result; } + /** * Check if these insets have been fully consumed. * @@ -277,7 +310,8 @@ public final class WindowInsets { * @return true if the insets have been fully consumed. */ public boolean isConsumed() { - return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed; + return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed + && mCutoutConsumed; } /** @@ -495,7 +529,9 @@ public final class WindowInsets { public String toString() { return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets + " windowDecorInsets=" + mWindowDecorInsets - + " stableInsets=" + mStableInsets + - (isRound() ? " round}" : "}"); + + " stableInsets=" + mStableInsets + + (mDisplayCutout.hasCutout() ? " cutout=" + mDisplayCutout : "") + + (isRound() ? " round" : "") + + "}"; } } diff --git a/android/view/WindowManager.java b/android/view/WindowManager.java index eb5fc92e..905c0715 100644 --- a/android/view/WindowManager.java +++ b/android/view/WindowManager.java @@ -604,8 +604,10 @@ public interface WindowManager extends ViewManager { public static final int TYPE_DRAG = FIRST_SYSTEM_WINDOW+16; /** - * Window type: panel that slides out from under the status bar - * In multiuser systems shows on all users' windows. + * Window type: panel that slides out from over the status bar + * In multiuser systems shows on all users' windows. These windows + * are displayed on top of the stauts bar and any {@link #TYPE_STATUS_BAR_PANEL} + * windows. * @hide */ public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17; diff --git a/android/view/WindowManagerPolicyConstants.java b/android/view/WindowManagerPolicyConstants.java new file mode 100644 index 00000000..21943bd6 --- /dev/null +++ b/android/view/WindowManagerPolicyConstants.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2006 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.view; + +import static android.view.Display.DEFAULT_DISPLAY; + +import android.annotation.SystemApi; + +/** + * Constants for interfacing with WindowManagerService and WindowManagerPolicyInternal. + * @hide + */ +public interface WindowManagerPolicyConstants { + // Policy flags. These flags are also defined in frameworks/base/include/ui/Input.h. + int FLAG_WAKE = 0x00000001; + int FLAG_VIRTUAL = 0x00000002; + + int FLAG_INJECTED = 0x01000000; + int FLAG_TRUSTED = 0x02000000; + int FLAG_FILTERED = 0x04000000; + int FLAG_DISABLE_KEY_REPEAT = 0x08000000; + + int FLAG_INTERACTIVE = 0x20000000; + int FLAG_PASS_TO_USER = 0x40000000; + + // Flags for IActivityManager.keyguardGoingAway() + int KEYGUARD_GOING_AWAY_FLAG_TO_SHADE = 1 << 0; + int KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS = 1 << 1; + int KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER = 1 << 2; + + // Flags used for indicating whether the internal and/or external input devices + // of some type are available. + int PRESENCE_INTERNAL = 1 << 0; + int PRESENCE_EXTERNAL = 1 << 1; + + /** + * Sticky broadcast of the current HDMI plugged state. + */ + String ACTION_HDMI_PLUGGED = "android.intent.action.HDMI_PLUGGED"; + + /** + * Extra in {@link #ACTION_HDMI_PLUGGED} indicating the state: true if + * plugged in to HDMI, false if not. + */ + String EXTRA_HDMI_PLUGGED_STATE = "state"; + + /** + * Set to {@code true} when intent was invoked from pressing the home key. + * @hide + */ + @SystemApi + String EXTRA_FROM_HOME_KEY = "android.intent.extra.FROM_HOME_KEY"; + + // TODO: move this to a more appropriate place. + interface PointerEventListener { + /** + * 1. onPointerEvent will be called on the service.UiThread. + * 2. motionEvent will be recycled after onPointerEvent returns so if it is needed later a + * copy() must be made and the copy must be recycled. + **/ + void onPointerEvent(MotionEvent motionEvent); + + /** + * @see #onPointerEvent(MotionEvent) + **/ + default void onPointerEvent(MotionEvent motionEvent, int displayId) { + if (displayId == DEFAULT_DISPLAY) { + onPointerEvent(motionEvent); + } + } + } + + /** Screen turned off because of a device admin */ + int OFF_BECAUSE_OF_ADMIN = 1; + /** Screen turned off because of power button */ + int OFF_BECAUSE_OF_USER = 2; + /** Screen turned off because of timeout */ + int OFF_BECAUSE_OF_TIMEOUT = 3; + + int APPLICATION_LAYER = 2; + int APPLICATION_MEDIA_SUBLAYER = -2; + int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1; + int APPLICATION_PANEL_SUBLAYER = 1; + int APPLICATION_SUB_PANEL_SUBLAYER = 2; + int APPLICATION_ABOVE_SUB_PANEL_SUBLAYER = 3; + + /** + * Convert the off reason to a human readable format. + */ + static String offReasonToString(int why) { + switch (why) { + case OFF_BECAUSE_OF_ADMIN: + return "OFF_BECAUSE_OF_ADMIN"; + case OFF_BECAUSE_OF_USER: + return "OFF_BECAUSE_OF_USER"; + case OFF_BECAUSE_OF_TIMEOUT: + return "OFF_BECAUSE_OF_TIMEOUT"; + default: + return Integer.toString(why); + } + } +} diff --git a/android/view/accessibility/AccessibilityCache.java b/android/view/accessibility/AccessibilityCache.java index d7851171..da5a1cd6 100644 --- a/android/view/accessibility/AccessibilityCache.java +++ b/android/view/accessibility/AccessibilityCache.java @@ -23,8 +23,6 @@ import android.util.LongArray; import android.util.LongSparseArray; import android.util.SparseArray; -import com.android.internal.annotations.VisibleForTesting; - import java.util.ArrayList; import java.util.List; @@ -33,8 +31,7 @@ import java.util.List; * It is updated when windows change or nodes change. * @hide */ -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) -public final class AccessibilityCache { +public class AccessibilityCache { private static final String LOG_TAG = "AccessibilityCache"; @@ -329,6 +326,8 @@ public final class AccessibilityCache { final long oldParentId = oldInfo.getParentNodeId(); if (info.getParentNodeId() != oldParentId) { clearSubTreeLocked(windowId, oldParentId); + } else { + oldInfo.recycle(); } } diff --git a/android/view/accessibility/AccessibilityEvent.java b/android/view/accessibility/AccessibilityEvent.java index 5adea669..1d19a9f5 100644 --- a/android/view/accessibility/AccessibilityEvent.java +++ b/android/view/accessibility/AccessibilityEvent.java @@ -16,6 +16,7 @@ package android.view.accessibility; +import android.annotation.IntDef; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -23,6 +24,8 @@ import android.util.Pools.SynchronizedPool; import com.android.internal.util.BitUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @@ -709,6 +712,38 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par */ public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004; + + /** @hide */ + @IntDef(flag = true, prefix = { "TYPE_" }, value = { + TYPE_VIEW_CLICKED, + TYPE_VIEW_LONG_CLICKED, + TYPE_VIEW_SELECTED, + TYPE_VIEW_FOCUSED, + TYPE_VIEW_TEXT_CHANGED, + TYPE_WINDOW_STATE_CHANGED, + TYPE_NOTIFICATION_STATE_CHANGED, + TYPE_VIEW_HOVER_ENTER, + TYPE_VIEW_HOVER_EXIT, + TYPE_TOUCH_EXPLORATION_GESTURE_START, + TYPE_TOUCH_EXPLORATION_GESTURE_END, + TYPE_WINDOW_CONTENT_CHANGED, + TYPE_VIEW_SCROLLED, + TYPE_VIEW_TEXT_SELECTION_CHANGED, + TYPE_ANNOUNCEMENT, + TYPE_VIEW_ACCESSIBILITY_FOCUSED, + TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, + TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY, + TYPE_GESTURE_DETECTION_START, + TYPE_GESTURE_DETECTION_END, + TYPE_TOUCH_INTERACTION_START, + TYPE_TOUCH_INTERACTION_END, + TYPE_WINDOWS_CHANGED, + TYPE_VIEW_CONTEXT_CLICKED, + TYPE_ASSIST_READING_CONTEXT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EventType {} + /** * Mask for {@link AccessibilityEvent} all types. * @@ -741,7 +776,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par private static final SynchronizedPool<AccessibilityEvent> sPool = new SynchronizedPool<AccessibilityEvent>(MAX_POOL_SIZE); - private int mEventType; + private @EventType int mEventType; private CharSequence mPackageName; private long mEventTime; int mMovementGranularity; @@ -833,7 +868,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * * @return The event type. */ - public int getEventType() { + public @EventType int getEventType() { return mEventType; } @@ -890,7 +925,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * * @throws IllegalStateException If called from an AccessibilityService. */ - public void setEventType(int eventType) { + public void setEventType(@EventType int eventType) { enforceNotSealed(); mEventType = eventType; } diff --git a/android/view/accessibility/AccessibilityInteractionClient.java b/android/view/accessibility/AccessibilityInteractionClient.java index c3d6c695..d890f329 100644 --- a/android/view/accessibility/AccessibilityInteractionClient.java +++ b/android/view/accessibility/AccessibilityInteractionClient.java @@ -28,6 +28,8 @@ import android.util.Log; import android.util.LongSparseArray; import android.util.SparseArray; +import com.android.internal.annotations.VisibleForTesting; + import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -86,6 +88,12 @@ public final class AccessibilityInteractionClient private static final LongSparseArray<AccessibilityInteractionClient> sClients = new LongSparseArray<>(); + private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache = + new SparseArray<>(); + + private static AccessibilityCache sAccessibilityCache = + new AccessibilityCache(new AccessibilityCache.AccessibilityNodeRefresher()); + private final AtomicInteger mInteractionIdCounter = new AtomicInteger(); private final Object mInstanceLock = new Object(); @@ -100,12 +108,6 @@ public final class AccessibilityInteractionClient private Message mSameThreadMessage; - private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache = - new SparseArray<>(); - - private static final AccessibilityCache sAccessibilityCache = - new AccessibilityCache(new AccessibilityCache.AccessibilityNodeRefresher()); - /** * @return The client for the current thread. */ @@ -133,6 +135,50 @@ public final class AccessibilityInteractionClient } } + /** + * Gets a cached accessibility service connection. + * + * @param connectionId The connection id. + * @return The cached connection if such. + */ + public static IAccessibilityServiceConnection getConnection(int connectionId) { + synchronized (sConnectionCache) { + return sConnectionCache.get(connectionId); + } + } + + /** + * Adds a cached accessibility service connection. + * + * @param connectionId The connection id. + * @param connection The connection. + */ + public static void addConnection(int connectionId, IAccessibilityServiceConnection connection) { + synchronized (sConnectionCache) { + sConnectionCache.put(connectionId, connection); + } + } + + /** + * Removes a cached accessibility service connection. + * + * @param connectionId The connection id. + */ + public static void removeConnection(int connectionId) { + synchronized (sConnectionCache) { + sConnectionCache.remove(connectionId); + } + } + + /** + * This method is only for testing. Replacing the cache is a generally terrible idea, but + * tests need to be able to verify this class's interactions with the cache + */ + @VisibleForTesting + public static void setCache(AccessibilityCache cache) { + sAccessibilityCache = cache; + } + private AccessibilityInteractionClient() { /* reducing constructor visibility */ } @@ -300,7 +346,7 @@ public final class AccessibilityInteractionClient if (success) { List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, bypassCache); if (infos != null && !infos.isEmpty()) { for (int i = 1; i < infos.size(); i++) { infos.get(i).recycle(); @@ -356,7 +402,7 @@ public final class AccessibilityInteractionClient List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); if (infos != null) { - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, false); return infos; } } @@ -409,7 +455,7 @@ public final class AccessibilityInteractionClient List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); if (infos != null) { - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, false); return infos; } } @@ -460,7 +506,7 @@ public final class AccessibilityInteractionClient if (success) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false); return info; } } else { @@ -509,7 +555,7 @@ public final class AccessibilityInteractionClient if (success) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false); return info; } } else { @@ -731,13 +777,17 @@ public final class AccessibilityInteractionClient * * @param info The info. * @param connectionId The id of the connection to the system. + * @param bypassCache Whether or not to bypass the cache. The node is added to the cache if + * this value is {@code false} */ private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, - int connectionId) { + int connectionId, boolean bypassCache) { if (info != null) { info.setConnectionId(connectionId); info.setSealed(true); - sAccessibilityCache.add(info); + if (!bypassCache) { + sAccessibilityCache.add(info); + } } } @@ -746,14 +796,16 @@ public final class AccessibilityInteractionClient * * @param infos The {@link AccessibilityNodeInfo}s. * @param connectionId The id of the connection to the system. + * @param bypassCache Whether or not to bypass the cache. The nodes are added to the cache if + * this value is {@code false} */ private void finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos, - int connectionId) { + int connectionId, boolean bypassCache) { if (infos != null) { final int infosCount = infos.size(); for (int i = 0; i < infosCount; i++) { AccessibilityNodeInfo info = infos.get(i); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, bypassCache); } } } @@ -773,41 +825,6 @@ public final class AccessibilityInteractionClient } /** - * Gets a cached accessibility service connection. - * - * @param connectionId The connection id. - * @return The cached connection if such. - */ - public IAccessibilityServiceConnection getConnection(int connectionId) { - synchronized (sConnectionCache) { - return sConnectionCache.get(connectionId); - } - } - - /** - * Adds a cached accessibility service connection. - * - * @param connectionId The connection id. - * @param connection The connection. - */ - public void addConnection(int connectionId, IAccessibilityServiceConnection connection) { - synchronized (sConnectionCache) { - sConnectionCache.put(connectionId, connection); - } - } - - /** - * Removes a cached accessibility service connection. - * - * @param connectionId The connection id. - */ - public void removeConnection(int connectionId) { - synchronized (sConnectionCache) { - sConnectionCache.remove(connectionId); - } - } - - /** * Checks whether the infos are a fully connected tree with no duplicates. * * @param infos The result list to check. diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java index 35f6acba..0375635f 100644 --- a/android/view/accessibility/AccessibilityManager.java +++ b/android/view/accessibility/AccessibilityManager.java @@ -24,6 +24,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; @@ -187,6 +188,7 @@ public final class AccessibilityManager { * * @hide */ + @TestApi public interface AccessibilityServicesStateChangeListener { /** @@ -452,6 +454,18 @@ public final class AccessibilityManager { } /** + * Returns whether there are observers registered for this event type. If + * this method returns false you shuold not generate events of this type + * to conserve resources. + * + * @param type The event type. + * @return Whether the event is being observed. + */ + public boolean isObservedEventType(@AccessibilityEvent.EventType int type) { + return mIsEnabled && (mRelevantEventTypes & type) != 0; + } + + /** * Requests feedback interruption from all accessibility services. */ public void interrupt() { @@ -683,6 +697,7 @@ public final class AccessibilityManager { * for a callback on the process's main handler. * @hide */ + @TestApi public void addAccessibilityServicesStateChangeListener( @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) { synchronized (mLock) { @@ -698,6 +713,7 @@ public final class AccessibilityManager { * * @hide */ + @TestApi public void removeAccessibilityServicesStateChangeListener( @NonNull AccessibilityServicesStateChangeListener listener) { // Final CopyOnWriteArrayList - no lock needed. diff --git a/android/view/accessibility/AccessibilityNodeInfo.java b/android/view/accessibility/AccessibilityNodeInfo.java index 9bdd3ffc..faea9200 100644 --- a/android/view/accessibility/AccessibilityNodeInfo.java +++ b/android/view/accessibility/AccessibilityNodeInfo.java @@ -635,6 +635,8 @@ public class AccessibilityNodeInfo implements Parcelable { private static final int BOOLEAN_PROPERTY_IMPORTANCE = 0x0040000; + private static final int BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE = 0x0080000; + private static final int BOOLEAN_PROPERTY_IS_SHOWING_HINT = 0x0100000; /** @@ -2321,6 +2323,37 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Returns whether the node is explicitly marked as a focusable unit by a screen reader. Note + * that {@code false} indicates that it is not explicitly marked, not that the node is not + * a focusable unit. Screen readers should generally used other signals, such as + * {@link #isFocusable()}, or the presence of text in a node, to determine what should receive + * focus. + * + * @return {@code true} if the node is specifically marked as a focusable unit for screen + * readers, {@code false} otherwise. + * + * @see View#isScreenReaderFocusable() + */ + public boolean isScreenReaderFocusable() { + return getBooleanProperty(BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE); + } + + /** + * Sets whether the node should be considered a focusable unit by a screen reader. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param screenReaderFocusable {@code true} if the node is a focusable unit for screen readers, + * {@code false} otherwise. + */ + public void setScreenReaderFocusable(boolean screenReaderFocusable) { + setBooleanProperty(BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE, screenReaderFocusable); + } + + /** * Returns whether the node's text represents a hint for the user to enter text. It should only * be {@code true} if the node has editable text. * diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java index 9241ec00..547e0db9 100644 --- a/android/view/autofill/AutofillManager.java +++ b/android/view/autofill/AutofillManager.java @@ -908,6 +908,7 @@ public final class AutofillManager { } synchronized (mLock) { if (mSaveOnFinish) { + if (sDebug) Log.d(TAG, "Committing session on finish() as requested by service"); commitLocked(); } else { if (sDebug) Log.d(TAG, "Cancelling session on finish() as requested by service"); @@ -955,6 +956,7 @@ public final class AutofillManager { * methods such as {@link android.app.Activity#finish()}. */ public void cancel() { + if (sVerbose) Log.v(TAG, "cancel() called by app"); if (!hasAutofillFeature()) { return; } diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java index 2779aa2d..f675c355 100644 --- a/android/view/textclassifier/TextClassification.java +++ b/android/view/textclassifier/TextClassification.java @@ -31,6 +31,7 @@ import com.android.internal.util.Preconditions; import java.util.ArrayList; import java.util.List; +import java.util.Locale; /** * Information for generating a widget to handle classified text. @@ -42,7 +43,7 @@ import java.util.List; * * <pre>{@code * // Called preferably outside the UiThread. - * TextClassification classification = textClassifier.classifyText(allText, 10, 25, null); + * TextClassification classification = textClassifier.classifyText(allText, 10, 25); * * // Called on the UiThread. * Button button = new Button(context); @@ -55,7 +56,7 @@ import java.util.List; * * <pre>{@code * // Called preferably outside the UiThread. - * final TextClassification classification = textClassifier.classifyText(allText, 10, 25, null); + * final TextClassification classification = textClassifier.classifyText(allText, 10, 25); * * // Called on the UiThread. * view.startActionMode(new ActionMode.Callback() { @@ -281,8 +282,8 @@ public final class TextClassification { @Override public String toString() { - return String.format("TextClassification {" - + "text=%s, entities=%s, labels=%s, intents=%s}", + return String.format(Locale.US, + "TextClassification {text=%s, entities=%s, labels=%s, intents=%s}", mText, mEntityConfidence, mLabels, mIntents); } @@ -421,7 +422,7 @@ public final class TextClassification { } /** - * Ensures that we have at we have storage for the default action. + * Ensures that we have storage for the default action. */ private void ensureDefaultActionAvailable() { if (mIntents.isEmpty()) mIntents.add(null); @@ -441,7 +442,7 @@ public final class TextClassification { } /** - * TextClassification optional input parameters. + * Optional input parameters for generating TextClassification. */ public static final class Options { diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java index aeb84897..5aaa5ad1 100644 --- a/android/view/textclassifier/TextClassifier.java +++ b/android/view/textclassifier/TextClassifier.java @@ -23,6 +23,8 @@ import android.annotation.StringDef; import android.annotation.WorkerThread; import android.os.LocaleList; +import com.android.internal.util.Preconditions; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -37,8 +39,7 @@ public interface TextClassifier { /** @hide */ String DEFAULT_LOG_TAG = "TextClassifierImpl"; - /** @hide */ - String TYPE_UNKNOWN = ""; // TODO: Make this public API. + String TYPE_UNKNOWN = ""; String TYPE_OTHER = "other"; String TYPE_EMAIL = "email"; String TYPE_PHONE = "phone"; @@ -70,6 +71,8 @@ public interface TextClassifier { * * @throws IllegalArgumentException if text is null; selectionStartIndex is negative; * selectionEndIndex is greater than text.length() or not greater than selectionStartIndex + * + * @see #suggestSelection(CharSequence, int, int) */ @WorkerThread @NonNull @@ -78,13 +81,46 @@ public interface TextClassifier { @IntRange(from = 0) int selectionStartIndex, @IntRange(from = 0) int selectionEndIndex, @Nullable TextSelection.Options options) { + Utils.validateInput(text, selectionStartIndex, selectionEndIndex); return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build(); } /** + * Returns suggested text selection start and end indices, recognized entity types, and their + * associated confidence scores. The entity types are ordered from highest to lowest scoring. + * + * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls + * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. If that method + * calls this method, a stack overflow error will happen. + * + * @param text text providing context for the selected text (which is specified + * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex) + * @param selectionStartIndex start index of the selected part of text + * @param selectionEndIndex end index of the selected part of text + * + * @throws IllegalArgumentException if text is null; selectionStartIndex is negative; + * selectionEndIndex is greater than text.length() or not greater than selectionStartIndex + * * @see #suggestSelection(CharSequence, int, int, TextSelection.Options) */ - // TODO: Consider deprecating (b/68846316) + @WorkerThread + @NonNull + default TextSelection suggestSelection( + @NonNull CharSequence text, + @IntRange(from = 0) int selectionStartIndex, + @IntRange(from = 0) int selectionEndIndex) { + return suggestSelection(text, selectionStartIndex, selectionEndIndex, + (TextSelection.Options) null); + } + + /** + * See {@link #suggestSelection(CharSequence, int, int)} or + * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. + * + * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls + * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. If that method + * calls this method, a stack overflow error will happen. + */ @WorkerThread @NonNull default TextSelection suggestSelection( @@ -92,7 +128,10 @@ public interface TextClassifier { @IntRange(from = 0) int selectionStartIndex, @IntRange(from = 0) int selectionEndIndex, @Nullable LocaleList defaultLocales) { - return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build(); + final TextSelection.Options options = (defaultLocales != null) + ? new TextSelection.Options().setDefaultLocales(defaultLocales) + : null; + return suggestSelection(text, selectionStartIndex, selectionEndIndex, options); } /** @@ -107,6 +146,8 @@ public interface TextClassifier { * * @throws IllegalArgumentException if text is null; startIndex is negative; * endIndex is greater than text.length() or not greater than startIndex + * + * @see #classifyText(CharSequence, int, int) */ @WorkerThread @NonNull @@ -115,13 +156,45 @@ public interface TextClassifier { @IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex, @Nullable TextClassification.Options options) { + Utils.validateInput(text, startIndex, endIndex); return TextClassification.EMPTY; } /** + * Classifies the specified text and returns a {@link TextClassification} object that can be + * used to generate a widget for handling the classified text. + * + * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls + * {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method + * calls this method, a stack overflow error will happen. + * + * @param text text providing context for the text to classify (which is specified + * by the sub sequence starting at startIndex and ending at endIndex) + * @param startIndex start index of the text to classify + * @param endIndex end index of the text to classify + * + * @throws IllegalArgumentException if text is null; startIndex is negative; + * endIndex is greater than text.length() or not greater than startIndex + * * @see #classifyText(CharSequence, int, int, TextClassification.Options) */ - // TODO: Consider deprecating (b/68846316) + @WorkerThread + @NonNull + default TextClassification classifyText( + @NonNull CharSequence text, + @IntRange(from = 0) int startIndex, + @IntRange(from = 0) int endIndex) { + return classifyText(text, startIndex, endIndex, (TextClassification.Options) null); + } + + /** + * See {@link #classifyText(CharSequence, int, int, TextClassification.Options)} or + * {@link #classifyText(CharSequence, int, int)}. + * + * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls + * {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method + * calls this method, a stack overflow error will happen. + */ @WorkerThread @NonNull default TextClassification classifyText( @@ -129,7 +202,10 @@ public interface TextClassifier { @IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex, @Nullable LocaleList defaultLocales) { - return TextClassification.EMPTY; + final TextClassification.Options options = (defaultLocales != null) + ? new TextClassification.Options().setDefaultLocales(defaultLocales) + : null; + return classifyText(text, startIndex, endIndex, options); } /** @@ -137,17 +213,39 @@ public interface TextClassifier { * information. * * @param text the text to generate annotations for - * @param options configuration for link generation. If null, defaults will be used. + * @param options configuration for link generation * * @throws IllegalArgumentException if text is null + * + * @see #generateLinks(CharSequence) */ @WorkerThread default TextLinks generateLinks( @NonNull CharSequence text, @Nullable TextLinks.Options options) { + Utils.validateInput(text); return new TextLinks.Builder(text.toString()).build(); } /** + * Returns a {@link TextLinks} that may be applied to the text to annotate it with links + * information. + * + * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls + * {@link #generateLinks(CharSequence, TextLinks.Options)}. If that method calls this method, + * a stack overflow error will happen. + * + * @param text the text to generate annotations for + * + * @throws IllegalArgumentException if text is null + * + * @see #generateLinks(CharSequence, TextLinks.Options) + */ + @WorkerThread + default TextLinks generateLinks(@NonNull CharSequence text) { + return generateLinks(text, null); + } + + /** * Logs a TextClassifier event. * * @param source the text classifier used to generate this event @@ -164,4 +262,38 @@ public interface TextClassifier { default TextClassifierConstants getSettings() { return TextClassifierConstants.DEFAULT; } + + + /** + * Utility functions for TextClassifier methods. + * + * <ul> + * <li>Provides validation of input parameters to TextClassifier methods + * </ul> + * + * Intended to be used only in this package. + * @hide + */ + final class Utils { + + /** + * @throws IllegalArgumentException if text is null; startIndex is negative; + * endIndex is greater than text.length() or is not greater than startIndex; + * options is null + */ + static void validateInput( + @NonNull CharSequence text, int startIndex, int endIndex) { + Preconditions.checkArgument(text != null); + Preconditions.checkArgument(startIndex >= 0); + Preconditions.checkArgument(endIndex <= text.length()); + Preconditions.checkArgument(endIndex > startIndex); + } + + /** + * @throws IllegalArgumentException if text is null or options is null + */ + static void validateInput(@NonNull CharSequence text) { + Preconditions.checkArgument(text != null); + } + } } diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java index 2ad6e02c..df5e35f0 100644 --- a/android/view/textclassifier/TextClassifierImpl.java +++ b/android/view/textclassifier/TextClassifierImpl.java @@ -90,8 +90,8 @@ final class TextClassifierImpl implements TextClassifier { @Override public TextSelection suggestSelection( @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex, - @Nullable TextSelection.Options options) { - validateInput(text, selectionStartIndex, selectionEndIndex); + @NonNull TextSelection.Options options) { + Utils.validateInput(text, selectionStartIndex, selectionEndIndex); try { if (text.length() > 0) { final LocaleList locales = (options == null) ? null : options.getDefaultLocales(); @@ -141,18 +141,10 @@ final class TextClassifierImpl implements TextClassifier { } @Override - public TextSelection suggestSelection( - @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex, - @Nullable LocaleList defaultLocales) { - return suggestSelection(text, selectionStartIndex, selectionEndIndex, - new TextSelection.Options().setDefaultLocales(defaultLocales)); - } - - @Override public TextClassification classifyText( @NonNull CharSequence text, int startIndex, int endIndex, - @Nullable TextClassification.Options options) { - validateInput(text, startIndex, endIndex); + @NonNull TextClassification.Options options) { + Utils.validateInput(text, startIndex, endIndex); try { if (text.length() > 0) { final String string = text.toString(); @@ -176,17 +168,9 @@ final class TextClassifierImpl implements TextClassifier { } @Override - public TextClassification classifyText( - @NonNull CharSequence text, int startIndex, int endIndex, - @Nullable LocaleList defaultLocales) { - return classifyText(text, startIndex, endIndex, - new TextClassification.Options().setDefaultLocales(defaultLocales)); - } - - @Override public TextLinks generateLinks( - @NonNull CharSequence text, @Nullable TextLinks.Options options) { - Preconditions.checkNotNull(text); + @NonNull CharSequence text, @NonNull TextLinks.Options options) { + Utils.validateInput(text); final String textString = text.toString(); final TextLinks.Builder builder = new TextLinks.Builder(textString); try { @@ -486,17 +470,6 @@ final class TextClassifierImpl implements TextClassifier { } /** - * @throws IllegalArgumentException if text is null; startIndex is negative; - * endIndex is greater than text.length() or is not greater than startIndex - */ - private static void validateInput(@NonNull CharSequence text, int startIndex, int endIndex) { - Preconditions.checkArgument(text != null); - Preconditions.checkArgument(startIndex >= 0); - Preconditions.checkArgument(endIndex <= text.length()); - Preconditions.checkArgument(endIndex > startIndex); - } - - /** * Creates intents based on the classification type. */ private static final class IntentFactory { diff --git a/android/view/textclassifier/TextLinks.java b/android/view/textclassifier/TextLinks.java index f3cc827f..76748d2b 100644 --- a/android/view/textclassifier/TextLinks.java +++ b/android/view/textclassifier/TextLinks.java @@ -161,39 +161,28 @@ public final class TextLinks { * Optional input parameters for generating TextLinks. */ public static final class Options { - private final LocaleList mLocaleList; - private Options(LocaleList localeList) { - this.mLocaleList = localeList; - } + private LocaleList mDefaultLocales; /** - * Builder to construct Options. + * @param defaultLocales ordered list of locale preferences that may be used to disambiguate + * the provided text. If no locale preferences exist, set this to null or an empty + * locale list. */ - public static final class Builder { - private LocaleList mLocaleList; - - /** - * Sets the LocaleList to use. - * - * @return this Builder. - */ - public Builder setLocaleList(@Nullable LocaleList localeList) { - this.mLocaleList = localeList; - return this; - } - - /** - * Builds the Options object. - */ - public Options build() { - return new Options(mLocaleList); - } + public Options setDefaultLocales(@Nullable LocaleList defaultLocales) { + mDefaultLocales = defaultLocales; + return this; } - public @Nullable LocaleList getDefaultLocales() { - return mLocaleList; + + /** + * @return ordered list of locale preferences that can be used to disambiguate + * the provided text. + */ + @Nullable + public LocaleList getDefaultLocales() { + return mDefaultLocales; } - }; + } /** * A function to create spans from TextLinks. @@ -204,13 +193,10 @@ public final class TextLinks { * @hide */ public static final Function<TextLink, ClickableSpan> DEFAULT_SPAN_FACTORY = - new Function<TextLink, ClickableSpan>() { - @Override - public ClickableSpan apply(TextLink textLink) { - // TODO: Implement. - throw new UnsupportedOperationException("Not yet implemented"); - } - }; + textLink -> { + // TODO: Implement. + throw new UnsupportedOperationException("Not yet implemented"); + }; /** * A builder to construct a TextLinks instance. diff --git a/android/view/textclassifier/TextSelection.java b/android/view/textclassifier/TextSelection.java index 0a67954a..480b27a7 100644 --- a/android/view/textclassifier/TextSelection.java +++ b/android/view/textclassifier/TextSelection.java @@ -26,6 +26,7 @@ import android.view.textclassifier.TextClassifier.EntityType; import com.android.internal.util.Preconditions; import java.util.List; +import java.util.Locale; /** * Information about where text selection should be. @@ -114,8 +115,8 @@ public final class TextSelection { @Override public String toString() { - return String.format("TextSelection {%d, %d, %s}", - mStartIndex, mEndIndex, mEntityConfidence); + return String.format(Locale.US, + "TextSelection {%d, %d, %s}", mStartIndex, mEndIndex, mEntityConfidence); } /** @@ -185,7 +186,7 @@ public final class TextSelection { } /** - * TextSelection optional input parameters. + * Optional input parameters for generating TextSelection. */ public static final class Options { diff --git a/android/webkit/UserPackage.java b/android/webkit/UserPackage.java index 4cf34618..63519a65 100644 --- a/android/webkit/UserPackage.java +++ b/android/webkit/UserPackage.java @@ -34,6 +34,8 @@ public class UserPackage { private final UserInfo mUserInfo; private final PackageInfo mPackageInfo; + public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.O_MR1; + public UserPackage(UserInfo user, PackageInfo packageInfo) { this.mUserInfo = user; this.mPackageInfo = packageInfo; @@ -83,7 +85,7 @@ public class UserPackage { * supported by the current framework version. */ public static boolean hasCorrectTargetSdkVersion(PackageInfo packageInfo) { - return packageInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O_MR1; + return packageInfo.applicationInfo.targetSdkVersion >= MINIMUM_SUPPORTED_SDK; } public UserInfo getUserInfo() { diff --git a/android/webkit/WebSettings.java b/android/webkit/WebSettings.java index 203de9c2..e4937392 100644 --- a/android/webkit/WebSettings.java +++ b/android/webkit/WebSettings.java @@ -29,10 +29,10 @@ import java.lang.annotation.Target; /** * Manages settings state for a WebView. When a WebView is first created, it * obtains a set of default settings. These default settings will be returned - * from any getter call. A WebSettings object obtained from - * WebView.getSettings() is tied to the life of the WebView. If a WebView has - * been destroyed, any method call on WebSettings will throw an - * IllegalStateException. + * from any getter call. A {@code WebSettings} object obtained from + * {@link WebView#getSettings()} is tied to the life of the WebView. If a WebView has + * been destroyed, any method call on {@code WebSettings} will throw an + * {@link IllegalStateException}. */ // This is an abstract base class: concrete WebViewProviders must // create a class derived from this, and return an instance of it in the @@ -41,13 +41,13 @@ public abstract class WebSettings { /** * Enum for controlling the layout of html. * <ul> - * <li>NORMAL means no rendering changes. This is the recommended choice for maximum + * <li>{@code NORMAL} means no rendering changes. This is the recommended choice for maximum * compatibility across different platforms and Android versions.</li> - * <li>SINGLE_COLUMN moves all content into one column that is the width of the + * <li>{@code SINGLE_COLUMN} moves all content into one column that is the width of the * view.</li> - * <li>NARROW_COLUMNS makes all columns no wider than the screen if possible. Only use + * <li>{@code NARROW_COLUMNS} makes all columns no wider than the screen if possible. Only use * this for API levels prior to {@link android.os.Build.VERSION_CODES#KITKAT}.</li> - * <li>TEXT_AUTOSIZING boosts font size of paragraphs based on heuristics to make + * <li>{@code TEXT_AUTOSIZING} boosts font size of paragraphs based on heuristics to make * the text readable when viewing a wide-viewport layout in the overview mode. * It is recommended to enable zoom support {@link #setSupportZoom} when * using this mode. Supported from API level @@ -98,9 +98,9 @@ public abstract class WebSettings { /** * Enum for specifying the WebView's desired density. * <ul> - * <li>FAR makes 100% looking like in 240dpi</li> - * <li>MEDIUM makes 100% looking like in 160dpi</li> - * <li>CLOSE makes 100% looking like in 120dpi</li> + * <li>{@code FAR} makes 100% looking like in 240dpi</li> + * <li>{@code MEDIUM} makes 100% looking like in 160dpi</li> + * <li>{@code CLOSE} makes 100% looking like in 120dpi</li> * </ul> */ public enum ZoomDensity { @@ -652,7 +652,7 @@ public abstract class WebSettings { * true, {@link WebChromeClient#onCreateWindow} must be implemented by the * host application. The default is {@code false}. * - * @param support whether to suport multiple windows + * @param support whether to support multiple windows */ public abstract void setSupportMultipleWindows(boolean support); @@ -665,7 +665,7 @@ public abstract class WebSettings { public abstract boolean supportMultipleWindows(); /** - * Sets the underlying layout algorithm. This will cause a relayout of the + * Sets the underlying layout algorithm. This will cause a re-layout of the * WebView. The default is {@link LayoutAlgorithm#NARROW_COLUMNS}. * * @param l the layout algorithm to use, as a {@link LayoutAlgorithm} value @@ -1198,7 +1198,7 @@ public abstract class WebSettings { /** * Tells JavaScript to open windows automatically. This applies to the - * JavaScript function window.open(). The default is {@code false}. + * JavaScript function {@code window.open()}. The default is {@code false}. * * @param flag {@code true} if JavaScript can open windows automatically */ @@ -1208,7 +1208,7 @@ public abstract class WebSettings { * Gets whether JavaScript can open windows automatically. * * @return {@code true} if JavaScript can open windows automatically during - * window.open() + * {@code window.open()} * @see #setJavaScriptCanOpenWindowsAutomatically */ public abstract boolean getJavaScriptCanOpenWindowsAutomatically(); diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java index 665d694e..6f992548 100644 --- a/android/webkit/WebView.java +++ b/android/webkit/WebView.java @@ -2758,7 +2758,9 @@ public class WebView extends AbsoluteLayout * <p>For example, an HTML form with 2 fields for username and password: * * <pre class="prettyprint"> + * <label>Username:</label> * <input type="text" name="username" id="user" value="Type your username" autocomplete="username" placeholder="Email or username"> + * <label>Password:</label> * <input type="password" name="password" id="pass" autocomplete="current-password" placeholder="Password"> * </pre> * @@ -2772,6 +2774,7 @@ public class WebView extends AbsoluteLayout * username.setHtmlInfo(username.newHtmlInfoBuilder("input") * .addAttribute("type", "text") * .addAttribute("name", "username") + * .addAttribute("label", "Username:") * .build()); * username.setHint("Email or username"); * username.setAutofillType(View.AUTOFILL_TYPE_TEXT); @@ -2785,6 +2788,7 @@ public class WebView extends AbsoluteLayout * password.setHtmlInfo(password.newHtmlInfoBuilder("input") * .addAttribute("type", "password") * .addAttribute("name", "password") + * .addAttribute("label", "Password:") * .build()); * password.setHint("Password"); * password.setAutofillType(View.AUTOFILL_TYPE_TEXT); diff --git a/android/webkit/WebViewClient.java b/android/webkit/WebViewClient.java index 517ad07c..46c39834 100644 --- a/android/webkit/WebViewClient.java +++ b/android/webkit/WebViewClient.java @@ -364,13 +364,13 @@ public class WebViewClient { } /** - * Notify the host application to handle a SSL client certificate - * request. The host application is responsible for showing the UI - * if desired and providing the keys. There are three ways to - * respond: proceed(), cancel() or ignore(). Webview stores the response - * in memory (for the life of the application) if proceed() or cancel() is - * called and does not call {@code onReceivedClientCertRequest()} again for the - * same host and port pair. Webview does not store the response if ignore() + * Notify the host application to handle a SSL client certificate request. The host application + * is responsible for showing the UI if desired and providing the keys. There are three ways to + * respond: {@link ClientCertRequest#proceed}, {@link ClientCertRequest#cancel}, or {@link + * ClientCertRequest#ignore}. Webview stores the response in memory (for the life of the + * application) if {@link ClientCertRequest#proceed} or {@link ClientCertRequest#cancel} is + * called and does not call {@code onReceivedClientCertRequest()} again for the same host and + * port pair. Webview does not store the response if {@link ClientCertRequest#ignore} * is called. Note that, multiple layers in chromium network stack might be * caching the responses, so the behavior for ignore is only a best case * effort. diff --git a/android/widget/Editor.java b/android/widget/Editor.java index d477ffdf..05cba1e5 100644 --- a/android/widget/Editor.java +++ b/android/widget/Editor.java @@ -119,7 +119,6 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; import com.android.internal.util.Preconditions; import com.android.internal.widget.EditableInputConnection; -import com.android.internal.widget.Magnifier; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -195,6 +194,27 @@ public class Editor { private final boolean mHapticTextHandleEnabled; private final Magnifier mMagnifier; + private final Runnable mUpdateMagnifierRunnable = new Runnable() { + @Override + public void run() { + mMagnifier.update(); + } + }; + // Update the magnifier contents whenever anything in the view hierarchy is updated. + // Note: this only captures UI thread-visible changes, so it's a known issue that an animating + // VectorDrawable or Ripple animation will not trigger capture, since they're owned by + // RenderThread. + private final ViewTreeObserver.OnDrawListener mMagnifierOnDrawListener = + new ViewTreeObserver.OnDrawListener() { + @Override + public void onDraw() { + if (mMagnifier != null) { + // Posting the method will ensure that updating the magnifier contents will + // happen right after the rendering of the current frame. + mTextView.post(mUpdateMagnifierRunnable); + } + } + }; // Used to highlight a word when it is corrected by the IME private CorrectionHighlighter mCorrectionHighlighter; @@ -415,15 +435,21 @@ public class Editor { } final ViewTreeObserver observer = mTextView.getViewTreeObserver(); - // No need to create the controller. - // The get method will add the listener on controller creation. - if (mInsertionPointCursorController != null) { - observer.addOnTouchModeChangeListener(mInsertionPointCursorController); - } - if (mSelectionModifierCursorController != null) { - mSelectionModifierCursorController.resetTouchOffsets(); - observer.addOnTouchModeChangeListener(mSelectionModifierCursorController); + if (observer.isAlive()) { + // No need to create the controller. + // The get method will add the listener on controller creation. + if (mInsertionPointCursorController != null) { + observer.addOnTouchModeChangeListener(mInsertionPointCursorController); + } + if (mSelectionModifierCursorController != null) { + mSelectionModifierCursorController.resetTouchOffsets(); + observer.addOnTouchModeChangeListener(mSelectionModifierCursorController); + } + if (FLAG_USE_MAGNIFIER) { + observer.addOnDrawListener(mMagnifierOnDrawListener); + } } + updateSpellCheckSpans(0, mTextView.getText().length(), true /* create the spell checker if needed */); @@ -472,6 +498,13 @@ public class Editor { mSpellChecker = null; } + if (FLAG_USE_MAGNIFIER) { + final ViewTreeObserver observer = mTextView.getViewTreeObserver(); + if (observer.isAlive()) { + observer.removeOnDrawListener(mMagnifierOnDrawListener); + } + } + hideCursorAndSpanControllers(); stopTextActionModeWithPreservingSelection(); } diff --git a/android/widget/Magnifier.java b/android/widget/Magnifier.java new file mode 100644 index 00000000..bd48f455 --- /dev/null +++ b/android/widget/Magnifier.java @@ -0,0 +1,221 @@ +/* + * 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.widget; + +import android.annotation.FloatRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UiThread; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.os.Handler; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.PixelCopy; +import android.view.Surface; +import android.view.SurfaceView; +import android.view.View; + +import com.android.internal.util.Preconditions; + +/** + * Android magnifier widget. Can be used by any view which is attached to a window. + */ +@UiThread +public final class Magnifier { + // Use this to specify that a previous configuration value does not exist. + private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1; + // The view to which this magnifier is attached. + private final View mView; + // The window containing the magnifier. + private final PopupWindow mWindow; + // The center coordinates of the window containing the magnifier. + private final Point mWindowCoords = new Point(); + // The width of the window containing the magnifier. + private final int mWindowWidth; + // The height of the window containing the magnifier. + private final int mWindowHeight; + // The bitmap used to display the contents of the magnifier. + private final Bitmap mBitmap; + // The center coordinates of the content that is to be magnified. + private final Point mCenterZoomCoords = new Point(); + // The callback of the pixel copy request will be invoked on this Handler when + // the copy is finished. + private final Handler mPixelCopyHandler = Handler.getMain(); + // Current magnification scale. + private final float mZoomScale; + // Variables holding previous states, used for detecting redundant calls and invalidation. + private final Point mPrevStartCoordsInSurface = new Point( + NONEXISTENT_PREVIOUS_CONFIG_VALUE, NONEXISTENT_PREVIOUS_CONFIG_VALUE); + private final PointF mPrevPosInView = new PointF( + NONEXISTENT_PREVIOUS_CONFIG_VALUE, NONEXISTENT_PREVIOUS_CONFIG_VALUE); + private final Rect mPixelCopyRequestRect = new Rect(); + + /** + * Initializes a magnifier. + * + * @param view the view for which this magnifier is attached + */ + public Magnifier(@NonNull View view) { + mView = Preconditions.checkNotNull(view); + final Context context = mView.getContext(); + final float elevation = context.getResources().getDimension( + com.android.internal.R.dimen.magnifier_elevation); + final View content = LayoutInflater.from(context).inflate( + com.android.internal.R.layout.magnifier, null); + content.findViewById(com.android.internal.R.id.magnifier_inner).setClipToOutline(true); + mWindowWidth = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.magnifier_width); + mWindowHeight = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.magnifier_height); + mZoomScale = context.getResources().getFloat( + com.android.internal.R.dimen.magnifier_zoom_scale); + + mWindow = new PopupWindow(context); + mWindow.setContentView(content); + mWindow.setWidth(mWindowWidth); + mWindow.setHeight(mWindowHeight); + mWindow.setElevation(elevation); + mWindow.setTouchable(false); + mWindow.setBackgroundDrawable(null); + + final int bitmapWidth = Math.round(mWindowWidth / mZoomScale); + final int bitmapHeight = Math.round(mWindowHeight / mZoomScale); + mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); + getImageView().setImageBitmap(mBitmap); + } + + /** + * Shows the magnifier on the screen. + * + * @param xPosInView horizontal coordinate of the center point of the magnifier source relative + * to the view. The lower end is clamped to 0 and the higher end is clamped to the view + * width. + * @param yPosInView vertical coordinate of the center point of the magnifier source + * relative to the view. The lower end is clamped to 0 and the higher end is clamped to + * the view height. + */ + public void show(@FloatRange(from = 0) float xPosInView, + @FloatRange(from = 0) float yPosInView) { + xPosInView = Math.max(0, Math.min(xPosInView, mView.getWidth())); + yPosInView = Math.max(0, Math.min(yPosInView, mView.getHeight())); + + configureCoordinates(xPosInView, yPosInView); + + // Clamp startX value to avoid distorting the rendering of the magnifier content. + final int startX = Math.max(0, Math.min( + mCenterZoomCoords.x - mBitmap.getWidth() / 2, + mView.getWidth() - mBitmap.getWidth())); + final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2; + + if (startX != mPrevStartCoordsInSurface.x || startY != mPrevStartCoordsInSurface.y) { + performPixelCopy(startX, startY); + + mPrevPosInView.x = xPosInView; + mPrevPosInView.y = yPosInView; + + if (mWindow.isShowing()) { + mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(), + mWindow.getHeight()); + } else { + mWindow.showAtLocation(mView, Gravity.NO_GRAVITY, mWindowCoords.x, mWindowCoords.y); + } + } + } + + /** + * Dismisses the magnifier from the screen. Calling this on a dismissed magnifier is a no-op. + */ + public void dismiss() { + mWindow.dismiss(); + } + + /** + * Forces the magnifier to update its content. It uses the previous coordinates passed to + * {@link #show(float, float)}. This only happens if the magnifier is currently showing. + * + * @hide + */ + public void update() { + if (mWindow.isShowing()) { + // Update the contents shown in the magnifier. + performPixelCopy(mPrevStartCoordsInSurface.x, mPrevStartCoordsInSurface.y); + } + } + + private void configureCoordinates(float xPosInView, float yPosInView) { + final float posX; + final float posY; + + if (mView instanceof SurfaceView) { + // No offset required if the backing Surface matches the size of the SurfaceView. + posX = xPosInView; + posY = yPosInView; + } else { + final int[] coordinatesInSurface = new int[2]; + mView.getLocationInSurface(coordinatesInSurface); + posX = xPosInView + coordinatesInSurface[0]; + posY = yPosInView + coordinatesInSurface[1]; + } + + mCenterZoomCoords.x = Math.round(posX); + mCenterZoomCoords.y = Math.round(posY); + + final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize( + com.android.internal.R.dimen.magnifier_offset); + mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2; + mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset; + } + + private void performPixelCopy(final int startXInSurface, final int startYInSurface) { + final Surface surface = getValidViewSurface(); + if (surface != null) { + mPixelCopyRequestRect.set(startXInSurface, startYInSurface, + startXInSurface + mBitmap.getWidth(), startYInSurface + mBitmap.getHeight()); + + PixelCopy.request(surface, mPixelCopyRequestRect, mBitmap, + result -> { + getImageView().invalidate(); + mPrevStartCoordsInSurface.x = startXInSurface; + mPrevStartCoordsInSurface.y = startYInSurface; + }, + mPixelCopyHandler); + } + } + + @Nullable + private Surface getValidViewSurface() { + final Surface surface; + if (mView instanceof SurfaceView) { + surface = ((SurfaceView) mView).getHolder().getSurface(); + } else if (mView.getViewRootImpl() != null) { + surface = mView.getViewRootImpl().mSurface; + } else { + surface = null; + } + + return (surface != null && surface.isValid()) ? surface : null; + } + + private ImageView getImageView() { + return mWindow.getContentView().findViewById( + com.android.internal.R.id.magnifier_image); + } +} diff --git a/android/widget/NumberPicker.java b/android/widget/NumberPicker.java index 4d3189ef..b3792806 100644 --- a/android/widget/NumberPicker.java +++ b/android/widget/NumberPicker.java @@ -1952,7 +1952,8 @@ public class NumberPicker extends LinearLayout { CharSequence beforeText = mInputText.getText(); if (!text.equals(beforeText.toString())) { mInputText.setText(text); - if (AccessibilityManager.getInstance(mContext).isEnabled()) { + if (AccessibilityManager.getInstance(mContext).isObservedEventType( + AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)) { AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); mInputText.onInitializeAccessibilityEvent(event); @@ -2612,7 +2613,7 @@ public class NumberPicker extends LinearLayout { } private void sendAccessibilityEventForVirtualText(int eventType) { - if (AccessibilityManager.getInstance(mContext).isEnabled()) { + if (AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) { AccessibilityEvent event = AccessibilityEvent.obtain(eventType); mInputText.onInitializeAccessibilityEvent(event); mInputText.onPopulateAccessibilityEvent(event); @@ -2623,7 +2624,7 @@ public class NumberPicker extends LinearLayout { private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType, String text) { - if (AccessibilityManager.getInstance(mContext).isEnabled()) { + if (AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) { AccessibilityEvent event = AccessibilityEvent.obtain(eventType); event.setClassName(Button.class.getName()); event.setPackageName(mContext.getPackageName()); diff --git a/android/widget/RemoteViews.java b/android/widget/RemoteViews.java index e3309161..a2c55b09 100644 --- a/android/widget/RemoteViews.java +++ b/android/widget/RemoteViews.java @@ -90,6 +90,31 @@ import java.util.concurrent.Executor; * another process. The hierarchy is inflated from a layout resource * file, and this class provides some basic operations for modifying * the content of the inflated hierarchy. + * + * <p>{@code RemoteViews} is limited to support for the following layouts:</p> + * <ul> + * <li>{@link android.widget.AdapterViewFlipper}</li> + * <li>{@link android.widget.FrameLayout}</li> + * <li>{@link android.widget.GridLayout}</li> + * <li>{@link android.widget.GridView}</li> + * <li>{@link android.widget.LinearLayout}</li> + * <li>{@link android.widget.ListView}</li> + * <li>{@link android.widget.RelativeLayout}</li> + * <li>{@link android.widget.StackView}</li> + * <li>{@link android.widget.ViewFlipper}</li> + * </ul> + * <p>And the following widgets:</p> + * <ul> + * <li>{@link android.widget.AnalogClock}</li> + * <li>{@link android.widget.Button}</li> + * <li>{@link android.widget.Chronometer}</li> + * <li>{@link android.widget.ImageButton}</li> + * <li>{@link android.widget.ImageView}</li> + * <li>{@link android.widget.ProgressBar}</li> + * <li>{@link android.widget.TextClock}</li> + * <li>{@link android.widget.TextView}</li> + * </ul> + * <p>Descendants of these classes are not supported.</p> */ public class RemoteViews implements Parcelable, Filter { diff --git a/android/widget/TextView.java b/android/widget/TextView.java index d9bc51ff..71532a72 100644 --- a/android/widget/TextView.java +++ b/android/widget/TextView.java @@ -10836,6 +10836,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount) { + if (!AccessibilityManager.getInstance(mContext).isObservedEventType( + AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)) { + return; + } AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); event.setFromIndex(fromIndex); diff --git a/android/widget/Toast.java b/android/widget/Toast.java index d8071200..bfde6ac3 100644 --- a/android/widget/Toast.java +++ b/android/widget/Toast.java @@ -504,7 +504,8 @@ public class Toast { private void trySendAccessibilityEvent() { AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mView.getContext()); - if (!accessibilityManager.isEnabled()) { + if (!accessibilityManager.isObservedEventType( + AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)) { return; } // treat toasts as notifications since they are used to diff --git a/androidx/app/slice/ArrayUtils.java b/androidx/app/slice/ArrayUtils.java new file mode 100644 index 00000000..669f66ae --- /dev/null +++ b/androidx/app/slice/ArrayUtils.java @@ -0,0 +1,75 @@ +/* + * 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 androidx.app.slice; + + +import android.support.annotation.RestrictTo; +import android.support.annotation.RestrictTo.Scope; + +import java.lang.reflect.Array; +import java.util.Objects; + +/** + * @hide + */ +@RestrictTo(Scope.LIBRARY) +class ArrayUtils { + + public static <T> boolean contains(T[] array, T item) { + for (T t : array) { + if (Objects.equals(t, item)) { + return true; + } + } + return false; + } + + public static <T> T[] appendElement(Class<T> kind, T[] array, T element) { + final T[] result; + final int end; + if (array != null) { + end = array.length; + result = (T[]) Array.newInstance(kind, end + 1); + System.arraycopy(array, 0, result, 0, end); + } else { + end = 0; + result = (T[]) Array.newInstance(kind, 1); + } + result[end] = element; + return result; + } + + public static <T> T[] removeElement(Class<T> kind, T[] array, T element) { + if (array != null) { + if (!contains(array, element)) { + return array; + } + final int length = array.length; + for (int i = 0; i < length; i++) { + if (Objects.equals(array[i], element)) { + if (length == 1) { + return null; + } + T[] result = (T[]) Array.newInstance(kind, length - 1); + System.arraycopy(array, 0, result, 0, i); + System.arraycopy(array, i + 1, result, i, length - i - 1); + return result; + } + } + } + return array; + } +} diff --git a/androidx/app/slice/Slice.java b/androidx/app/slice/Slice.java new file mode 100644 index 00000000..fb2395e1 --- /dev/null +++ b/androidx/app/slice/Slice.java @@ -0,0 +1,420 @@ +/* + * 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 androidx.app.slice; + +import static android.app.slice.Slice.HINT_ACTIONS; +import static android.app.slice.Slice.HINT_HORIZONTAL; +import static android.app.slice.Slice.HINT_LARGE; +import static android.app.slice.Slice.HINT_LIST; +import static android.app.slice.Slice.HINT_LIST_ITEM; +import static android.app.slice.Slice.HINT_NO_TINT; +import static android.app.slice.Slice.HINT_PARTIAL; +import static android.app.slice.Slice.HINT_SELECTED; +import static android.app.slice.Slice.HINT_TITLE; +import static android.app.slice.SliceItem.FORMAT_ACTION; +import static android.app.slice.SliceItem.FORMAT_COLOR; +import static android.app.slice.SliceItem.FORMAT_IMAGE; +import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT; +import static android.app.slice.SliceItem.FORMAT_SLICE; +import static android.app.slice.SliceItem.FORMAT_TEXT; +import static android.app.slice.SliceItem.FORMAT_TIMESTAMP; + +import android.annotation.TargetApi; +import android.app.PendingIntent; +import android.app.RemoteInput; +import android.content.ContentProvider; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Icon; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.RestrictTo; +import android.support.annotation.RestrictTo.Scope; +import android.support.annotation.StringDef; +import android.support.v4.os.BuildCompat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import androidx.app.slice.compat.SliceProviderCompat; +import androidx.app.slice.core.SliceHints; +import androidx.app.slice.core.SliceSpecs; + +/** + * A slice is a piece of app content and actions that can be surfaced outside of the app. + * + * <p>They are constructed using {@link Builder} in a tree structure + * that provides the OS some information about how the content should be displayed. + */ +public final class Slice { + + private static final String HINTS = "hints"; + private static final String ITEMS = "items"; + private static final String URI = "uri"; + + /** + * @hide + */ + @RestrictTo(Scope.LIBRARY) + @StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED, + HINT_HORIZONTAL, HINT_NO_TINT, HINT_PARTIAL, + SliceHints.HINT_HIDDEN, SliceHints.HINT_TOGGLE}) + public @interface SliceHint{ } + + private final SliceItem[] mItems; + private final @SliceHint String[] mHints; + private Uri mUri; + + /** + * @hide + */ + @RestrictTo(Scope.LIBRARY) + Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) { + mHints = hints; + mItems = items.toArray(new SliceItem[items.size()]); + mUri = uri; + } + + /** + * @hide + */ + @RestrictTo(Scope.LIBRARY) + public Slice(Bundle in) { + mHints = in.getStringArray(HINTS); + Parcelable[] items = in.getParcelableArray(ITEMS); + mItems = new SliceItem[items.length]; + for (int i = 0; i < mItems.length; i++) { + if (items[i] instanceof Bundle) { + mItems[i] = new SliceItem((Bundle) items[i]); + } + } + mUri = in.getParcelable(URI); + } + + /** + * @hide + */ + @RestrictTo(Scope.LIBRARY) + public Bundle toBundle() { + Bundle b = new Bundle(); + b.putStringArray(HINTS, mHints); + Parcelable[] p = new Parcelable[mItems.length]; + for (int i = 0; i < mItems.length; i++) { + p[i] = mItems[i].toBundle(); + } + b.putParcelableArray(ITEMS, p); + b.putParcelable(URI, mUri); + return b; + } + + /** + * @return The Uri that this Slice represents. + */ + public Uri getUri() { + return mUri; + } + + /** + * @return All child {@link SliceItem}s that this Slice contains. + */ + public List<SliceItem> getItems() { + return Arrays.asList(mItems); + } + + /** + * @return All hints associated with this Slice. + */ + public @SliceHint List<String> getHints() { + return Arrays.asList(mHints); + } + + /** + * @hide + */ + @RestrictTo(Scope.LIBRARY_GROUP) + public boolean hasHint(@SliceHint String hint) { + return ArrayUtils.contains(mHints, hint); + } + + /** + * A Builder used to construct {@link Slice}s + */ + public static class Builder { + + private final Uri mUri; + private ArrayList<SliceItem> mItems = new ArrayList<>(); + private @SliceHint ArrayList<String> mHints = new ArrayList<>(); + + /** + * Create a builder which will construct a {@link Slice} for the Given Uri. + * @param uri Uri to tag for this slice. + */ + public Builder(@NonNull Uri uri) { + mUri = uri; + } + + /** + * Create a builder for a {@link Slice} that is a sub-slice of the slice + * being constructed by the provided builder. + * @param parent The builder constructing the parent slice + */ + public Builder(@NonNull Slice.Builder parent) { + mUri = parent.mUri.buildUpon().appendPath("_gen") + .appendPath(String.valueOf(mItems.size())).build(); + } + + /** + * Add hints to the Slice being constructed + */ + public Builder addHints(@SliceHint String... hints) { + mHints.addAll(Arrays.asList(hints)); + return this; + } + + /** + * Add hints to the Slice being constructed + */ + public Builder addHints(@SliceHint List<String> hints) { + return addHints(hints.toArray(new String[hints.size()])); + } + + /** + * Add a sub-slice to the slice being constructed + */ + public Builder addSubSlice(@NonNull Slice slice) { + 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, String subType) { + mItems.add(new SliceItem(slice, FORMAT_SLICE, subType, slice.getHints().toArray( + new String[slice.getHints().size()]))); + return this; + } + + /** + * Add an action to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} + */ + public Slice.Builder addAction(@NonNull PendingIntent action, + @NonNull Slice s, @Nullable String subType) { + mItems.add(new SliceItem(action, s, FORMAT_ACTION, subType, new String[0])); + 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, @Nullable String subType, + @SliceHint String... hints) { + mItems.add(new SliceItem(text, FORMAT_TEXT, subType, hints)); + 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, @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, @Nullable String subType, + @SliceHint String... hints) { + mItems.add(new SliceItem(icon, FORMAT_IMAGE, subType, hints)); + return this; + } + + /** + * Add an image to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} + */ + 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, @Nullable String subType, + @SliceHint List<String> hints) { + 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, @Nullable String subType, + @SliceHint String... hints) { + mItems.add(new SliceItem(remoteInput, 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, @Nullable String subType, + @SliceHint String... hints) { + mItems.add(new SliceItem(color, 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, @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, @Nullable String subType, + @SliceHint String... hints) { + mItems.add(new SliceItem(time, 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, @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); + } + } + + /** + * @hide + * @return A string representation of this slice. + */ + @RestrictTo(Scope.LIBRARY) + @Override + public String toString() { + return toString(""); + } + + private String toString(String indent) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mItems.length; i++) { + sb.append(indent); + if (FORMAT_SLICE.equals(mItems[i].getFormat())) { + sb.append("slice:\n"); + sb.append(mItems[i].getSlice().toString(indent + " ")); + } else if (FORMAT_TEXT.equals(mItems[i].getFormat())) { + sb.append("text: "); + sb.append(mItems[i].getText()); + sb.append("\n"); + } else { + sb.append(SliceItem.typeToString(mItems[i].getFormat())); + sb.append("\n"); + } + } + return sb.toString(); + } + + /** + * Turns a slice Uri into slice content. + * + * @param context Context to be used. + * @param uri The URI to a slice provider + * @return The Slice provided by the app or null if none is given. + * @see Slice + */ + @SuppressWarnings("NewApi") + public static @Nullable Slice bindSlice(Context context, @NonNull Uri uri) { + if (BuildCompat.isAtLeastP()) { + return callBindSlice(context, uri); + } else { + return SliceProviderCompat.bindSlice(context, uri); + } + } + + @TargetApi(28) + private static Slice callBindSlice(Context context, Uri uri) { + return SliceConvert.wrap(android.app.slice.Slice.bindSlice( + context.getContentResolver(), uri, SliceSpecs.SUPPORTED_SPECS)); + } + + + /** + * Turns a slice intent into slice content. Expects an explicit intent. If there is no + * {@link ContentProvider} associated with the given intent this will throw + * {@link IllegalArgumentException}. + * + * @param context The context to use. + * @param intent The intent associated with a slice. + * @return The Slice provided by the app or null if none is given. + * @see Slice + * @see SliceProvider#onMapIntentToUri(Intent) + * @see Intent + */ + @SuppressWarnings("NewApi") + public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent) { + if (BuildCompat.isAtLeastP()) { + return callBindSlice(context, intent); + } else { + return SliceProviderCompat.bindSlice(context, intent); + } + } + + @TargetApi(28) + private static Slice callBindSlice(Context context, Intent intent) { + return SliceConvert.wrap(android.app.slice.Slice.bindSlice( + context, intent, SliceSpecs.SUPPORTED_SPECS)); + } +} diff --git a/androidx/app/slice/SliceConvert.java b/androidx/app/slice/SliceConvert.java new file mode 100644 index 00000000..edbc293a --- /dev/null +++ b/androidx/app/slice/SliceConvert.java @@ -0,0 +1,106 @@ +/* + * 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 androidx.app.slice; + + +import static android.app.slice.SliceItem.FORMAT_ACTION; +import static android.app.slice.SliceItem.FORMAT_COLOR; +import static android.app.slice.SliceItem.FORMAT_IMAGE; +import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT; +import static android.app.slice.SliceItem.FORMAT_SLICE; +import static android.app.slice.SliceItem.FORMAT_TEXT; +import static android.app.slice.SliceItem.FORMAT_TIMESTAMP; + +import android.support.annotation.RequiresApi; + +/** + * Convert between {@link androidx.app.slice.Slice} and {@link android.app.slice.Slice} + */ +@RequiresApi(28) +public class SliceConvert { + + /** + * Convert {@link androidx.app.slice.Slice} to {@link android.app.slice.Slice} + */ + public static android.app.slice.Slice unwrap(androidx.app.slice.Slice slice) { + android.app.slice.Slice.Builder builder = new android.app.slice.Slice.Builder( + slice.getUri()); + builder.addHints(slice.getHints()); + for (androidx.app.slice.SliceItem item : slice.getItems()) { + switch (item.getFormat()) { + case FORMAT_SLICE: + builder.addSubSlice(unwrap(item.getSlice()), item.getSubType()); + break; + case FORMAT_IMAGE: + builder.addIcon(item.getIcon(), item.getSubType(), item.getHints()); + break; + case FORMAT_REMOTE_INPUT: + builder.addRemoteInput(item.getRemoteInput(), item.getSubType(), + item.getHints()); + break; + case FORMAT_ACTION: + builder.addAction(item.getAction(), unwrap(item.getSlice()), item.getSubType()); + break; + case FORMAT_TEXT: + builder.addText(item.getText(), item.getSubType(), item.getHints()); + break; + case FORMAT_COLOR: + builder.addColor(item.getColor(), item.getSubType(), item.getHints()); + break; + case FORMAT_TIMESTAMP: + builder.addTimestamp(item.getTimestamp(), item.getSubType(), item.getHints()); + break; + } + } + return builder.build(); + } + + /** + * Convert {@link android.app.slice.Slice} to {@link androidx.app.slice.Slice} + */ + public static androidx.app.slice.Slice wrap(android.app.slice.Slice slice) { + androidx.app.slice.Slice.Builder builder = new androidx.app.slice.Slice.Builder( + slice.getUri()); + builder.addHints(slice.getHints()); + for (android.app.slice.SliceItem item : slice.getItems()) { + switch (item.getFormat()) { + case FORMAT_SLICE: + builder.addSubSlice(wrap(item.getSlice()), item.getSubType()); + break; + case FORMAT_IMAGE: + builder.addIcon(item.getIcon(), item.getSubType(), item.getHints()); + break; + case FORMAT_REMOTE_INPUT: + builder.addRemoteInput(item.getRemoteInput(), item.getSubType(), + item.getHints()); + break; + case FORMAT_ACTION: + builder.addAction(item.getAction(), wrap(item.getSlice()), item.getSubType()); + break; + case FORMAT_TEXT: + builder.addText(item.getText(), item.getSubType(), item.getHints()); + break; + case FORMAT_COLOR: + builder.addColor(item.getColor(), item.getSubType(), item.getHints()); + break; + case FORMAT_TIMESTAMP: + builder.addTimestamp(item.getTimestamp(), item.getSubType(), item.getHints()); + break; + } + } + return builder.build(); + } +} diff --git a/androidx/app/slice/SliceItem.java b/androidx/app/slice/SliceItem.java new file mode 100644 index 00000000..e4412d1f --- /dev/null +++ b/androidx/app/slice/SliceItem.java @@ -0,0 +1,351 @@ +/* + * 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 androidx.app.slice; + +import static android.app.slice.SliceItem.FORMAT_ACTION; +import static android.app.slice.SliceItem.FORMAT_COLOR; +import static android.app.slice.SliceItem.FORMAT_IMAGE; +import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT; +import static android.app.slice.SliceItem.FORMAT_SLICE; +import static android.app.slice.SliceItem.FORMAT_TEXT; +import static android.app.slice.SliceItem.FORMAT_TIMESTAMP; + +import android.app.PendingIntent; +import android.app.RemoteInput; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.annotation.RequiresApi; +import android.support.annotation.RestrictTo; +import android.support.annotation.RestrictTo.Scope; +import android.support.annotation.StringDef; +import android.text.TextUtils; +import android.util.Pair; + +import java.util.Arrays; +import java.util.List; + + +/** + * A SliceItem is a single unit in the tree structure of a {@link Slice}. + * <p> + * 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 android.app.slice.SliceItem#FORMAT_SLICE}</li> + * <li>{@link android.app.slice.SliceItem#FORMAT_TEXT}</li> + * <li>{@link android.app.slice.SliceItem#FORMAT_IMAGE}</li> + * <li>{@link android.app.slice.SliceItem#FORMAT_ACTION}</li> + * <li>{@link android.app.slice.SliceItem#FORMAT_COLOR}</li> + * <li>{@link android.app.slice.SliceItem#FORMAT_TIMESTAMP}</li> + * <li>{@link android.app.slice.SliceItem#FORMAT_REMOTE_INPUT}</li> + * <p> + * 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 + * are defined on {@link Slice}. + */ +public class SliceItem { + + private static final String HINTS = "hints"; + private static final String FORMAT = "format"; + private static final String SUBTYPE = "subtype"; + private static final String OBJ = "obj"; + private static final String OBJ_2 = "obj_2"; + + /** + * @hide + */ + @RestrictTo(Scope.LIBRARY) + @StringDef({FORMAT_SLICE, FORMAT_TEXT, FORMAT_IMAGE, FORMAT_ACTION, FORMAT_COLOR, + FORMAT_TIMESTAMP, FORMAT_REMOTE_INPUT}) + public @interface SliceType { + } + + /** + * @hide + */ + @RestrictTo(Scope.LIBRARY) + protected @Slice.SliceHint String[] mHints; + private final String mFormat; + private final String mSubType; + private final Object mObj; + + /** + * @hide + */ + @RestrictTo(Scope.LIBRARY) + public SliceItem(Object obj, @SliceType String format, String subType, + @Slice.SliceHint String[] hints) { + mHints = hints; + mFormat = format; + mSubType = subType; + mObj = obj; + } + + /** + * @hide + */ + @RestrictTo(Scope.LIBRARY) + public SliceItem(PendingIntent intent, Slice slice, String format, String subType, + @Slice.SliceHint String[] hints) { + this(new Pair<>(intent, slice), format, subType, hints); + } + + /** + * Gets all hints associated with this SliceItem. + * + * @return Array of hints. + */ + public @NonNull @Slice.SliceHint List<String> getHints() { + return Arrays.asList(mHints); + } + + /** + * @hide + */ + @RestrictTo(Scope.LIBRARY) + public void addHint(@Slice.SliceHint String hint) { + mHints = ArrayUtils.appendElement(String.class, mHints, hint); + } + + /** + * @hide + */ + @RestrictTo(Scope.LIBRARY) + public void removeHint(String hint) { + ArrayUtils.removeElement(String.class, mHints, hint); + } + + /** + * Get the format of this SliceItem. + * <p> + * The format will be one of the following types supported by the platform: + * <li>{@link android.app.slice.SliceItem#FORMAT_SLICE}</li> + * <li>{@link android.app.slice.SliceItem#FORMAT_TEXT}</li> + * <li>{@link android.app.slice.SliceItem#FORMAT_IMAGE}</li> + * <li>{@link android.app.slice.SliceItem#FORMAT_ACTION}</li> + * <li>{@link android.app.slice.SliceItem#FORMAT_COLOR}</li> + * <li>{@link android.app.slice.SliceItem#FORMAT_TIMESTAMP}</li> + * <li>{@link android.app.slice.SliceItem#FORMAT_REMOTE_INPUT}</li> + * @see #getSubType() () + */ + public @SliceType 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 android.app.slice.SliceItem#FORMAT_TEXT} items, but only some of them may be + * {@link android.app.slice.Slice#SUBTYPE_MESSAGE}. + * @see #getFormat() + */ + public String getSubType() { + return mSubType; + } + + /** + * @return The text held by this {@link android.app.slice.SliceItem#FORMAT_TEXT} SliceItem + */ + public CharSequence getText() { + return (CharSequence) mObj; + } + + /** + * @return The icon held by this {@link android.app.slice.SliceItem#FORMAT_IMAGE} SliceItem + */ + @RequiresApi(23) + public Icon getIcon() { + return (Icon) mObj; + } + + /** + * @return The pending intent held by this {@link android.app.slice.SliceItem#FORMAT_ACTION} + * SliceItem + */ + public PendingIntent getAction() { + return ((Pair<PendingIntent, Slice>) mObj).first; + } + + /** + * @return The remote input held by this {@link android.app.slice.SliceItem#FORMAT_REMOTE_INPUT} + * SliceItem + */ + @RequiresApi(20) + public RemoteInput getRemoteInput() { + return (RemoteInput) mObj; + } + + /** + * @return The color held by this {@link android.app.slice.SliceItem#FORMAT_COLOR} SliceItem + */ + public int getColor() { + return (Integer) mObj; + } + + /** + * @return The slice held by this {@link android.app.slice.SliceItem#FORMAT_ACTION} or + * {@link android.app.slice.SliceItem#FORMAT_SLICE} SliceItem + */ + public Slice getSlice() { + if (FORMAT_ACTION.equals(getFormat())) { + return ((Pair<PendingIntent, Slice>) mObj).second; + } + return (Slice) mObj; + } + + /** + * @return The timestamp held by this {@link android.app.slice.SliceItem#FORMAT_TIMESTAMP} + * SliceItem + */ + public long getTimestamp() { + return (Long) mObj; + } + + /** + * @param hint The hint to check for + * @return true if this item contains the given hint + */ + public boolean hasHint(@Slice.SliceHint String hint) { + return ArrayUtils.contains(mHints, hint); + } + + /** + * @hide + */ + @RestrictTo(Scope.LIBRARY) + public SliceItem(Bundle in) { + mHints = in.getStringArray(HINTS); + mFormat = in.getString(FORMAT); + mSubType = in.getString(SUBTYPE); + mObj = readObj(mFormat, in); + } + + /** + * @hide + * @return + */ + @RestrictTo(Scope.LIBRARY) + public Bundle toBundle() { + Bundle b = new Bundle(); + b.putStringArray(HINTS, mHints); + b.putString(FORMAT, mFormat); + b.putString(SUBTYPE, mSubType); + writeObj(b, mObj, mFormat); + return b; + } + + /** + * @hide + */ + @RestrictTo(Scope.LIBRARY) + public boolean hasHints(@Slice.SliceHint String[] hints) { + if (hints == null) return true; + for (String hint : hints) { + if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) { + return false; + } + } + return true; + } + + /** + * @hide + */ + @RestrictTo(Scope.LIBRARY) + public boolean hasAnyHints(@Slice.SliceHint String[] hints) { + if (hints == null) return false; + for (String hint : hints) { + if (ArrayUtils.contains(mHints, hint)) { + return true; + } + } + return false; + } + + private void writeObj(Bundle dest, Object obj, String type) { + switch (type) { + case FORMAT_IMAGE: + case FORMAT_REMOTE_INPUT: + dest.putParcelable(OBJ, (Parcelable) obj); + break; + case FORMAT_SLICE: + dest.putParcelable(OBJ, ((Slice) obj).toBundle()); + break; + case FORMAT_ACTION: + dest.putParcelable(OBJ, ((Pair<PendingIntent, Slice>) obj).first); + dest.putBundle(OBJ_2, ((Pair<PendingIntent, Slice>) obj).second.toBundle()); + break; + case FORMAT_TEXT: + dest.putCharSequence(OBJ, (CharSequence) obj); + break; + case FORMAT_COLOR: + dest.putInt(OBJ, (Integer) mObj); + break; + case FORMAT_TIMESTAMP: + dest.putLong(OBJ, (Long) mObj); + break; + } + } + + private static Object readObj(String type, Bundle in) { + switch (type) { + case FORMAT_IMAGE: + case FORMAT_REMOTE_INPUT: + return in.getParcelable(OBJ); + case FORMAT_SLICE: + return new Slice(in.getBundle(OBJ)); + case FORMAT_TEXT: + return in.getCharSequence(OBJ); + case FORMAT_ACTION: + return new Pair<>( + (PendingIntent) in.getParcelable(OBJ), + new Slice(in.getBundle(OBJ_2))); + case FORMAT_COLOR: + return in.getInt(OBJ); + case FORMAT_TIMESTAMP: + return in.getLong(OBJ); + } + throw new RuntimeException("Unsupported type " + type); + } + + /** + * @hide + */ + @RestrictTo(Scope.LIBRARY) + public static String typeToString(String format) { + switch (format) { + case FORMAT_SLICE: + return "Slice"; + case FORMAT_TEXT: + return "Text"; + case FORMAT_IMAGE: + return "Image"; + case FORMAT_ACTION: + return "Action"; + case FORMAT_COLOR: + return "Color"; + case FORMAT_TIMESTAMP: + return "Timestamp"; + case FORMAT_REMOTE_INPUT: + return "RemoteInput"; + } + return "Unrecognized format: " + format; + } +} diff --git a/androidx/app/slice/SliceProvider.java b/androidx/app/slice/SliceProvider.java new file mode 100644 index 00000000..a0c12f12 --- /dev/null +++ b/androidx/app/slice/SliceProvider.java @@ -0,0 +1,130 @@ +/* + * 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 androidx.app.slice; + +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ProviderInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.v4.os.BuildCompat; + +import androidx.app.slice.compat.ContentProviderWrapper; +import androidx.app.slice.compat.SliceProviderCompat; +import androidx.app.slice.compat.SliceProviderWrapper; + +/** + * A SliceProvider allows an app to provide content to be displayed in system spaces. This content + * is templated and can contain actions, and the behavior of how it is surfaced is specific to the + * system surface. + * <p> + * Slices are not currently live content. They are bound once and shown to the user. If the content + * changes due to a callback from user interaction, then + * {@link ContentResolver#notifyChange(Uri, ContentObserver)} should be used to notify the system. + * </p> + * <p> + * The provider needs to be declared in the manifest to provide the authority for the app. The + * authority for most slices is expected to match the package of the application. + * </p> + * + * <pre class="prettyprint"> + * {@literal + * <provider + * android:name="com.android.mypkg.MySliceProvider" + * android:authorities="com.android.mypkg" />} + * </pre> + * <p> + * Slices can be identified by a Uri or by an Intent. To link an Intent with a slice, the provider + * must have an {@link IntentFilter} matching the slice intent. When a slice is being requested via + * an intent, {@link #onMapIntentToUri(Intent)} can be called and is expected to return an + * appropriate Uri representing the slice. + * + * <pre class="prettyprint"> + * {@literal + * <provider + * android:name="com.android.mypkg.MySliceProvider" + * android:authorities="com.android.mypkg"> + * <intent-filter> + * <action android:name="android.intent.action.MY_SLICE_INTENT" /> + * </intent-filter> + * </provider>} + * </pre> + * + * @see android.app.slice.Slice + */ +public abstract class SliceProvider extends ContentProviderWrapper { + + @Override + public void attachInfo(Context context, ProviderInfo info) { + ContentProvider impl; + if (BuildCompat.isAtLeastP()) { + impl = new SliceProviderWrapper(this); + } else { + impl = new SliceProviderCompat(this); + } + super.attachInfo(context, info, impl); + } + + /** + * Implement this to initialize your slice provider on startup. + * This method is called for all registered slice providers on the + * application main thread at application launch time. It must not perform + * lengthy operations, or application startup will be delayed. + * + * <p>You should defer nontrivial initialization (such as opening, + * upgrading, and scanning databases) until the slice provider is used + * (via #onBindSlice, etc). Deferred initialization + * keeps application startup fast, avoids unnecessary work if the provider + * turns out not to be needed, and stops database errors (such as a full + * disk) from halting application launch. + * + * @return true if the provider was successfully loaded, false otherwise + */ + public abstract boolean onCreateSliceProvider(); + + /** + * Implemented to create a slice. Will be called on the main thread. + * <p> + * onBindSlice should return as quickly as possible so that the UI tied + * to this slice can be responsive. No network or other IO will be allowed + * during onBindSlice. Any loading that needs to be done should happen + * 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> + * + * @see {@link Slice}. + * @see {@link android.app.slice.Slice#HINT_PARTIAL} + */ + // TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)). + public abstract Slice onBindSlice(Uri sliceUri); + + /** + * This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider. + * In that case, this method can be called and is expected to return a non-null Uri representing + * a slice. Otherwise this will throw {@link UnsupportedOperationException}. + * + * @return Uri representing the slice associated with the provided intent. + * @see {@link android.app.slice.Slice} + */ + public @NonNull Uri onMapIntentToUri(Intent intent) { + throw new UnsupportedOperationException( + "This provider has not implemented intent to uri mapping"); + } +} diff --git a/androidx/app/slice/builders/MessagingSliceBuilder.java b/androidx/app/slice/builders/MessagingSliceBuilder.java index 29189048..424c8efb 100644 --- a/androidx/app/slice/builders/MessagingSliceBuilder.java +++ b/androidx/app/slice/builders/MessagingSliceBuilder.java @@ -18,12 +18,13 @@ package androidx.app.slice.builders; import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; -import android.app.slice.Slice; import android.graphics.drawable.Icon; import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.RestrictTo; +import androidx.app.slice.Slice; + /** * Builder to construct slice content in a messaging format. */ @@ -41,7 +42,7 @@ public class MessagingSliceBuilder extends TemplateSliceBuilder { /** * Create a {@link MessageBuilder} that will be added to this slice when - * {@link MessageBuilder#finish()} is called. + * {@link MessageBuilder#endMessage()} is called. * @return a new message builder */ public MessageBuilder startMessage() { @@ -58,7 +59,7 @@ public class MessagingSliceBuilder extends TemplateSliceBuilder { @Override public void add(SubTemplateSliceBuilder builder) { - getBuilder().addSubSlice(builder.build()); + getBuilder().addSubSlice(builder.build(), android.app.slice.Slice.SUBTYPE_MESSAGE); } /** @@ -72,14 +73,13 @@ public class MessagingSliceBuilder extends TemplateSliceBuilder { @RestrictTo(RestrictTo.Scope.LIBRARY) public MessageBuilder(MessagingSliceBuilder parent) { super(parent.createChildBuilder(), parent); - getBuilder().addHints(Slice.HINT_MESSAGE); } /** * Add the icon used to display contact in the messaging experience */ public MessageBuilder addSource(Icon source) { - getBuilder().addIcon(source, Slice.HINT_SOURCE); + getBuilder().addIcon(source, android.app.slice.Slice.SUBTYPE_SOURCE); return this; } @@ -87,7 +87,7 @@ public class MessagingSliceBuilder extends TemplateSliceBuilder { * Add the text to be used for this message. */ public MessageBuilder addText(CharSequence text) { - getBuilder().addText(text); + getBuilder().addText(text, null); return this; } @@ -95,7 +95,7 @@ public class MessagingSliceBuilder extends TemplateSliceBuilder { * Add the time at which this message arrived in ms since Unix epoch */ public MessageBuilder addTimestamp(long timestamp) { - getBuilder().addTimestamp(timestamp); + getBuilder().addTimestamp(timestamp, null); return this; } diff --git a/androidx/app/slice/builders/TemplateSliceBuilder.java b/androidx/app/slice/builders/TemplateSliceBuilder.java index 61fea7f9..102ee001 100644 --- a/androidx/app/slice/builders/TemplateSliceBuilder.java +++ b/androidx/app/slice/builders/TemplateSliceBuilder.java @@ -17,10 +17,11 @@ package androidx.app.slice.builders; -import android.app.slice.Slice; import android.net.Uri; import android.support.annotation.RestrictTo; +import androidx.app.slice.Slice; + /** * Base class of builders of various template types. */ diff --git a/androidx/app/slice/compat/ContentProviderWrapper.java b/androidx/app/slice/compat/ContentProviderWrapper.java new file mode 100644 index 00000000..944a5ac0 --- /dev/null +++ b/androidx/app/slice/compat/ContentProviderWrapper.java @@ -0,0 +1,121 @@ +/* + * 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 androidx.app.slice.compat; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.support.annotation.RestrictTo; +import android.support.annotation.RestrictTo.Scope; + +/** + * @hide + */ +// TODO: Remove as soon as we have better systems in place for this. +@RestrictTo(Scope.LIBRARY) +public class ContentProviderWrapper extends ContentProvider { + + private ContentProvider mImpl; + + /** + * Triggers an attach with the object to wrap. + */ + public void attachInfo(Context context, ProviderInfo info, ContentProvider impl) { + mImpl = impl; + mImpl.attachInfo(context, info); + super.attachInfo(context, info); + } + + @Override + public final boolean onCreate() { + return mImpl.onCreate(); + } + + @Nullable + @Override + public final Cursor query(@NonNull Uri uri, @Nullable String[] projection, + @Nullable String selection, @Nullable String[] selectionArgs, + @Nullable String sortOrder) { + return mImpl.query(uri, projection, selection, selectionArgs, sortOrder); + } + + @Nullable + @Override + @RequiresApi(28) + public final Cursor query(@NonNull Uri uri, @Nullable String[] projection, + @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) { + return mImpl.query(uri, projection, queryArgs, cancellationSignal); + } + + @Nullable + @Override + public final Cursor query(@NonNull Uri uri, @Nullable String[] projection, + @Nullable String selection, @Nullable String[] selectionArgs, + @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) { + return mImpl.query(uri, projection, selection, selectionArgs, sortOrder, + cancellationSignal); + } + + @Nullable + @Override + public final String getType(@NonNull Uri uri) { + return mImpl.getType(uri); + } + + @Nullable + @Override + public final Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { + return mImpl.insert(uri, values); + } + + @Override + public final int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) { + return mImpl.bulkInsert(uri, values); + } + + @Override + public final int delete(@NonNull Uri uri, @Nullable String selection, + @Nullable String[] selectionArgs) { + return mImpl.delete(uri, selection, selectionArgs); + } + + @Override + public final int update(@NonNull Uri uri, @Nullable ContentValues values, + @Nullable String selection, @Nullable String[] selectionArgs) { + return mImpl.update(uri, values, selection, selectionArgs); + } + + @Nullable + @Override + public final Bundle call(@NonNull String method, @Nullable String arg, + @Nullable Bundle extras) { + return mImpl.call(method, arg, extras); + } + + @Nullable + @Override + public final Uri canonicalize(@NonNull Uri url) { + return mImpl.canonicalize(url); + } +} diff --git a/androidx/app/slice/compat/SliceProviderCompat.java b/androidx/app/slice/compat/SliceProviderCompat.java new file mode 100644 index 00000000..9fcac1b5 --- /dev/null +++ b/androidx/app/slice/compat/SliceProviderCompat.java @@ -0,0 +1,266 @@ +/* + * 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 androidx.app.slice.compat; + +import static android.app.slice.SliceProvider.SLICE_TYPE; + +import android.Manifest.permission; +import android.content.ContentProvider; +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.database.Cursor; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.Looper; +import android.os.Parcelable; +import android.os.Process; +import android.os.RemoteException; +import android.os.StrictMode; +import android.os.StrictMode.ThreadPolicy; +import android.support.annotation.RestrictTo; +import android.support.annotation.RestrictTo.Scope; +import android.util.Log; + +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import androidx.app.slice.Slice; +import androidx.app.slice.SliceProvider; + +/** + * @hide + */ +@RestrictTo(Scope.LIBRARY) +public class SliceProviderCompat extends ContentProvider { + + private static final String TAG = "SliceProvider"; + + public static final String EXTRA_BIND_URI = "slice_uri"; + public static final String METHOD_SLICE = "bind_slice"; + public static final String METHOD_MAP_INTENT = "map_slice"; + public static final String EXTRA_INTENT = "slice_intent"; + public static final String EXTRA_SLICE = "slice"; + + private static final boolean DEBUG = false; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + private SliceProvider mSliceProvider; + + public SliceProviderCompat(SliceProvider provider) { + mSliceProvider = provider; + } + + @Override + public boolean onCreate() { + return mSliceProvider.onCreateSliceProvider(); + } + + @Override + public final int update(Uri uri, ContentValues values, String selection, + String[] selectionArgs) { + if (DEBUG) Log.d(TAG, "update " + uri); + return 0; + } + + @Override + public final int delete(Uri uri, String selection, String[] selectionArgs) { + if (DEBUG) Log.d(TAG, "delete " + uri); + return 0; + } + + @Override + public final Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + if (DEBUG) Log.d(TAG, "query " + uri); + return null; + } + + @Override + public final Cursor query(Uri uri, String[] projection, String selection, String[] + selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { + if (DEBUG) Log.d(TAG, "query " + uri); + return null; + } + + @Override + public final Cursor query(Uri uri, String[] projection, Bundle queryArgs, + CancellationSignal cancellationSignal) { + if (DEBUG) Log.d(TAG, "query " + uri); + return null; + } + + @Override + public final Uri insert(Uri uri, ContentValues values) { + if (DEBUG) Log.d(TAG, "insert " + uri); + return null; + } + + @Override + public final String getType(Uri uri) { + if (DEBUG) Log.d(TAG, "getFormat " + uri); + return SLICE_TYPE; + } + + @Override + public Bundle call(String method, String arg, Bundle extras) { + if (method.equals(METHOD_SLICE)) { + Uri uri = extras.getParcelable(EXTRA_BIND_URI); + if (Binder.getCallingUid() != Process.myUid()) { + getContext().enforceUriPermission(uri, permission.BIND_SLICE, + permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(), + Intent.FLAG_GRANT_WRITE_URI_PERMISSION, + "Slice binding requires the permission BIND_SLICE"); + } + + Slice s = handleBindSlice(uri); + Bundle b = new Bundle(); + b.putParcelable(EXTRA_SLICE, s.toBundle()); + return b; + } else if (method.equals(METHOD_MAP_INTENT)) { + if (Binder.getCallingUid() != Process.myUid()) { + getContext().enforceCallingPermission(permission.BIND_SLICE, + "Slice binding requires the permission BIND_SLICE"); + } + Intent intent = extras.getParcelable(EXTRA_INTENT); + Uri uri = mSliceProvider.onMapIntentToUri(intent); + Bundle b = new Bundle(); + if (uri != null) { + Slice s = handleBindSlice(uri); + b.putParcelable(EXTRA_SLICE, s.toBundle()); + } else { + b.putParcelable(EXTRA_SLICE, null); + } + return b; + } + return super.call(method, arg, extras); + } + + private Slice handleBindSlice(final Uri sliceUri) { + if (Looper.myLooper() == Looper.getMainLooper()) { + return onBindSliceStrict(sliceUri); + } else { + final CountDownLatch latch = new CountDownLatch(1); + final Slice[] output = new Slice[1]; + mHandler.post(new Runnable() { + @Override + public void run() { + output[0] = onBindSliceStrict(sliceUri); + latch.countDown(); + } + }); + try { + latch.await(); + return output[0]; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + private Slice onBindSliceStrict(Uri sliceUri) { + ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); + try { + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyDeath() + .build()); + return mSliceProvider.onBindSlice(sliceUri); + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + + /** + * Compat version of {@link Slice#bindSlice(Context, Uri)}. + */ + public static Slice bindSlice(Context context, Uri uri) { + ContentProviderClient provider = context.getContentResolver() + .acquireContentProviderClient(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URI " + uri); + } + try { + Bundle extras = new Bundle(); + extras.putParcelable(EXTRA_BIND_URI, uri); + final Bundle res = provider.call(METHOD_SLICE, null, extras); + if (res == null) { + return null; + } + Parcelable bundle = res.getParcelable(EXTRA_SLICE); + if (!(bundle instanceof Bundle)) { + return null; + } + return new Slice((Bundle) bundle); + } catch (RemoteException e) { + // Arbitrary and not worth documenting, as Activity + // Manager will kill this process shortly anyway. + return null; + } finally { + provider.close(); + } + } + + /** + * Compat version of {@link Slice#bindSlice(Context, Intent)}. + */ + public static Slice bindSlice(Context context, Intent intent) { + ContentResolver resolver = context.getContentResolver(); + + // Check if the intent has data for the slice uri on it and use that + final Uri intentData = intent.getData(); + if (intentData != null && SLICE_TYPE.equals(resolver.getType(intentData))) { + return bindSlice(context, intentData); + } + // Otherwise ask the app + List<ResolveInfo> providers = + context.getPackageManager().queryIntentContentProviders(intent, 0); + if (providers == null) { + throw new IllegalArgumentException("Unable to resolve intent " + intent); + } + String authority = providers.get(0).providerInfo.authority; + Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority).build(); + ContentProviderClient provider = resolver.acquireContentProviderClient(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URI " + uri); + } + try { + Bundle extras = new Bundle(); + extras.putParcelable(EXTRA_INTENT, intent); + final Bundle res = provider.call(METHOD_MAP_INTENT, null, extras); + if (res == null) { + return null; + } + Parcelable bundle = res.getParcelable(EXTRA_SLICE); + if (!(bundle instanceof Bundle)) { + return null; + } + return new Slice((Bundle) bundle); + } catch (RemoteException e) { + // Arbitrary and not worth documenting, as Activity + // Manager will kill this process shortly anyway. + return null; + } finally { + provider.close(); + } + } +} diff --git a/androidx/app/slice/compat/SliceProviderWrapper.java b/androidx/app/slice/compat/SliceProviderWrapper.java new file mode 100644 index 00000000..3afed2b0 --- /dev/null +++ b/androidx/app/slice/compat/SliceProviderWrapper.java @@ -0,0 +1,62 @@ +/* + * 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 androidx.app.slice.compat; + +import android.annotation.TargetApi; +import android.app.slice.Slice; +import android.app.slice.SliceProvider; +import android.app.slice.SliceSpec; +import android.content.Intent; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.RestrictTo; +import android.support.annotation.RestrictTo.Scope; + +import java.util.List; + +import androidx.app.slice.SliceConvert; + +/** + * @hide + */ +@RestrictTo(Scope.LIBRARY) +@TargetApi(28) +public class SliceProviderWrapper extends SliceProvider { + + private androidx.app.slice.SliceProvider mSliceProvider; + + public SliceProviderWrapper(androidx.app.slice.SliceProvider provider) { + mSliceProvider = provider; + } + + @Override + public boolean onCreate() { + return mSliceProvider.onCreateSliceProvider(); + } + + @Override + public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedVersions) { + return SliceConvert.unwrap(mSliceProvider.onBindSlice(sliceUri)); + } + + /** + * Maps intents to uris. + */ + @Override + public @NonNull Uri onMapIntentToUri(Intent intent) { + return mSliceProvider.onMapIntentToUri(intent); + } +} diff --git a/androidx/app/slice/builders/SliceHints.java b/androidx/app/slice/core/SliceHints.java index 5db12193..6ebcd03a 100644 --- a/androidx/app/slice/builders/SliceHints.java +++ b/androidx/app/slice/core/SliceHints.java @@ -14,15 +14,13 @@ * limitations under the License. */ -package androidx.app.slice.builders; +package androidx.app.slice.core; import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; import android.app.slice.Slice; -import android.app.slice.widget.SliceView; import android.support.annotation.RestrictTo; - /** * Temporary class to contain hint constants for slices to be used. * @hide @@ -42,9 +40,11 @@ public class SliceHints { */ public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; /** - * 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 the + * {@link androidx.app.slice.widget.SliceView#MODE_SMALL} + * and {@link androidx.app.slice.widget.SliceView#MODE_LARGE} modes of SliceView. + * This content may be used to populate + * the {@link androidx.app.slice.widget.SliceView#MODE_SHORTCUT} format of the slice. */ public static final String HINT_HIDDEN = "hidden"; } diff --git a/androidx/app/slice/core/SliceQuery.java b/androidx/app/slice/core/SliceQuery.java index e1430d05..9da5478f 100644 --- a/androidx/app/slice/core/SliceQuery.java +++ b/androidx/app/slice/core/SliceQuery.java @@ -16,8 +16,16 @@ package androidx.app.slice.core; -import android.app.slice.Slice; -import android.app.slice.SliceItem; +import static android.app.slice.Slice.HINT_ACTIONS; +import static android.app.slice.Slice.HINT_LIST; +import static android.app.slice.Slice.HINT_LIST_ITEM; +import static android.app.slice.SliceItem.FORMAT_ACTION; +import static android.app.slice.SliceItem.FORMAT_COLOR; +import static android.app.slice.SliceItem.FORMAT_IMAGE; +import static android.app.slice.SliceItem.FORMAT_SLICE; +import static android.app.slice.SliceItem.FORMAT_TIMESTAMP; + +import android.annotation.TargetApi; import android.support.annotation.RestrictTo; import android.text.TextUtils; @@ -26,17 +34,21 @@ import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.Spliterators; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import androidx.app.slice.builders.SliceHints; +import androidx.app.slice.Slice; +import androidx.app.slice.SliceItem; /** * Utilities for finding content within a Slice. * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +// TODO: Not expect 24. +@TargetApi(24) public class SliceQuery { /** @@ -44,23 +56,23 @@ public class SliceQuery { * front slot of a small slice. */ public static boolean isStartType(SliceItem item) { - final int type = item.getType(); - return !item.hasHint(SliceHints.HINT_TOGGLE) - && ((type == SliceItem.TYPE_ACTION && (find(item, SliceItem.TYPE_IMAGE) != null)) - || type == SliceItem.TYPE_IMAGE - || type == SliceItem.TYPE_TIMESTAMP); + final String type = item.getFormat(); + return (!item.hasHint(SliceHints.HINT_TOGGLE) + && (FORMAT_ACTION.equals(type) && (find(item, FORMAT_IMAGE) != null))) + || FORMAT_IMAGE.equals(type) + || FORMAT_TIMESTAMP.equals(type); } /** * @return Finds the first slice that has non-slice children. */ public static SliceItem findFirstSlice(SliceItem slice) { - if (slice.getType() != SliceItem.TYPE_SLICE) { + if (!FORMAT_SLICE.equals(slice.getFormat())) { return slice; } List<SliceItem> items = slice.getSlice().getItems(); for (int i = 0; i < items.size(); i++) { - if (items.get(i).getType() == SliceItem.TYPE_SLICE) { + if (FORMAT_SLICE.equals(items.get(i).getFormat())) { SliceItem childSlice = items.get(i); return findFirstSlice(childSlice); } else { @@ -76,14 +88,14 @@ public class SliceQuery { * @return Whether this item is a simple action, i.e. an action that only has an icon. */ public static boolean isSimpleAction(SliceItem item) { - if (item.getType() == SliceItem.TYPE_ACTION) { + if (FORMAT_ACTION.equals(item.getFormat())) { List<SliceItem> items = item.getSlice().getItems(); boolean hasImage = false; for (int i = 0; i < items.size(); i++) { SliceItem child = items.get(i); - if (child.getType() == SliceItem.TYPE_IMAGE && !hasImage) { + if (FORMAT_IMAGE.equals(child.getFormat()) && !hasImage) { hasImage = true; - } else if (child.getType() == SliceItem.TYPE_COLOR) { + } else if (FORMAT_COLOR.equals(child.getFormat())) { continue; } else { return false; @@ -136,14 +148,14 @@ public class SliceQuery { */ public static SliceItem getPrimaryIcon(Slice slice) { for (SliceItem item : slice.getItems()) { - if (item.getType() == SliceItem.TYPE_IMAGE) { + if (FORMAT_IMAGE.equals(item.getFormat())) { return item; } - if (!(item.getType() == SliceItem.TYPE_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); + if (!(FORMAT_SLICE.equals(item.getFormat()) && item.hasHint(HINT_LIST)) + && !item.hasHint(HINT_ACTIONS) + && !item.hasHint(HINT_LIST_ITEM) + && !FORMAT_ACTION.equals(item.getFormat())) { + SliceItem icon = SliceQuery.find(item, FORMAT_IMAGE); if (icon != null) { return icon; } @@ -167,86 +179,128 @@ public class SliceQuery { /** */ - private static boolean contains(SliceItem container, SliceItem item) { + private static boolean contains(SliceItem container, final SliceItem item) { if (container == null || item == null) return false; - return stream(container).filter(s -> (s == item)).findAny().isPresent(); + return stream(container).filter(new Predicate<SliceItem>() { + @Override + public boolean test(SliceItem s) { + return s == item; + } + }).findAny().isPresent(); } /** */ - public static List<SliceItem> findAll(SliceItem s, int type) { - return findAll(s, type, (String[]) null, null); + public static List<SliceItem> findAll(SliceItem s, String format) { + return findAll(s, format, (String[]) null, null); } /** */ - public static List<SliceItem> findAll(Slice s, int type, String hints, String nonHints) { - return findAll(s, type, new String[]{ hints }, new String[]{ nonHints }); + public static List<SliceItem> findAll(Slice s, String format, String hints, String nonHints) { + return findAll(s, format, new String[]{ hints }, new String[]{ nonHints }); } /** */ - public static List<SliceItem> findAll(SliceItem s, int type, String hints, String nonHints) { - return findAll(s, type, new String[]{ hints }, new String[]{ nonHints }); + public static List<SliceItem> findAll(SliceItem s, String format, String hints, + String nonHints) { + return findAll(s, format, new String[]{ hints }, new String[]{ nonHints }); } /** */ - public static List<SliceItem> findAll(Slice s, int type, String[] hints, - String[] nonHints) { - return stream(s).filter(item -> (type == -1 || item.getType() == type) - && (hasHints(item, hints) && !hasAnyHints(item, nonHints))) - .collect(Collectors.toList()); + public static List<SliceItem> findAll(Slice s, final String format, final String[] hints, + final String[] nonHints) { + return stream(s).filter(new Predicate<SliceItem>() { + @Override + public boolean test(SliceItem item) { + return checkFormat(item, format) + && (hasHints(item, hints) && !hasAnyHints(item, nonHints)); + } + }).collect(Collectors.<SliceItem>toList()); } /** */ - public static List<SliceItem> findAll(SliceItem s, int type, String[] hints, - String[] nonHints) { - return stream(s).filter(item -> (type == -1 || item.getType() == type) - && (hasHints(item, hints) && !hasAnyHints(item, nonHints))) - .collect(Collectors.toList()); + public static List<SliceItem> findAll(SliceItem s, final String format, final String[] hints, + final String[] nonHints) { + return stream(s).filter(new Predicate<SliceItem>() { + @Override + public boolean test(SliceItem item) { + return checkFormat(item, format) + && (hasHints(item, hints) && !hasAnyHints(item, nonHints)); + } + }).collect(Collectors.<SliceItem>toList()); } /** */ - public static SliceItem find(Slice s, int type, String hints, String nonHints) { - return find(s, type, new String[]{ hints }, new String[]{ nonHints }); + public static SliceItem find(Slice s, String format, String hints, String nonHints) { + return find(s, format, new String[]{ hints }, new String[]{ nonHints }); } /** */ - public static SliceItem find(Slice s, int type) { - return find(s, type, (String[]) null, null); + public static SliceItem find(Slice s, String format) { + return find(s, format, (String[]) null, null); } /** */ - public static SliceItem find(SliceItem s, int type) { - return find(s, type, (String[]) null, null); + public static SliceItem find(SliceItem s, String format) { + return find(s, format, (String[]) null, null); } /** */ - public static SliceItem find(SliceItem s, int type, String hints, String nonHints) { - return find(s, type, new String[]{ hints }, new String[]{ nonHints }); + public static SliceItem find(SliceItem s, String format, String hints, String nonHints) { + return find(s, format, new String[]{ hints }, new String[]{ nonHints }); } /** */ - public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) { - List<String> h = s.getHints(); - return stream(s).filter(item -> (item.getType() == type || type == -1) - && (hasHints(item, hints) && !hasAnyHints(item, nonHints))).findFirst() - .orElse(null); + public static SliceItem find(Slice s, final String format, final String[] hints, + final String[] nonHints) { + return stream(s).filter(new Predicate<SliceItem>() { + @Override + public boolean test(SliceItem item) { + return checkFormat(item, format) + && (hasHints(item, hints) && !hasAnyHints(item, nonHints)); + } + }).findFirst().orElse(null); } /** */ - public static SliceItem find(SliceItem s, int type, String[] hints, String[] nonHints) { - return stream(s).filter(item -> (item.getType() == type || type == -1) - && (hasHints(item, hints) && !hasAnyHints(item, nonHints))).findFirst() - .orElse(null); + public static SliceItem findSubtype(SliceItem s, final String format, final String subtype) { + return stream(s).filter(new Predicate<SliceItem>() { + @Override + public boolean test(SliceItem item) { + return checkFormat(item, format) && checkSubtype(item, subtype); + } + }).findFirst().orElse(null); + } + + /** + */ + public static SliceItem find(SliceItem s, final String format, final String[] hints, + final String[] nonHints) { + return stream(s).filter(new Predicate<SliceItem>() { + @Override + public boolean test(SliceItem item) { + return checkFormat(item, format) + && (hasHints(item, hints) && !hasAnyHints(item, nonHints)); + } + }).findFirst().orElse(null); + } + + private static boolean checkFormat(SliceItem item, String format) { + return format == null || format.equals(item.getFormat()); + } + + private static boolean checkSubtype(SliceItem item, String subtype) { + return subtype == null || subtype.equals(item.getSubType()); } /** @@ -267,7 +321,7 @@ public class SliceQuery { /** */ - private static Stream<SliceItem> getSliceItemStream(Queue<SliceItem> items) { + private static Stream<SliceItem> getSliceItemStream(final Queue<SliceItem> items) { Iterator<SliceItem> iterator = new Iterator<SliceItem>() { @Override public boolean hasNext() { @@ -277,8 +331,8 @@ public class SliceQuery { @Override public SliceItem next() { SliceItem item = items.poll(); - if (item.getType() == SliceItem.TYPE_SLICE - || item.getType() == SliceItem.TYPE_ACTION) { + if (FORMAT_SLICE.equals(item.getFormat()) + || FORMAT_ACTION.equals(item.getFormat())) { items.addAll(item.getSlice().getItems()); } return item; diff --git a/androidx/app/slice/core/SliceSpecs.java b/androidx/app/slice/core/SliceSpecs.java new file mode 100644 index 00000000..a21633ba --- /dev/null +++ b/androidx/app/slice/core/SliceSpecs.java @@ -0,0 +1,33 @@ +/* + * 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 androidx.app.slice.core; + +import android.app.slice.SliceSpec; +import android.support.annotation.RestrictTo; + +import java.util.Collections; +import java.util.List; + +/** + * @hide + */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +public class SliceSpecs { + + // TODO: Fill these in. + public static List<SliceSpec> SUPPORTED_SPECS = Collections.emptyList(); +} diff --git a/androidx/app/slice/widget/ActionRow.java b/androidx/app/slice/widget/ActionRow.java index 0a09620e..70cec4fe 100644 --- a/androidx/app/slice/widget/ActionRow.java +++ b/androidx/app/slice/widget/ActionRow.java @@ -16,11 +16,16 @@ package androidx.app.slice.widget; +import static android.app.slice.Slice.HINT_NO_TINT; +import static android.app.slice.SliceItem.FORMAT_ACTION; +import static android.app.slice.SliceItem.FORMAT_COLOR; +import static android.app.slice.SliceItem.FORMAT_IMAGE; +import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT; + +import android.annotation.TargetApi; 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.content.Context; import android.content.res.ColorStateList; import android.graphics.Color; @@ -36,12 +41,16 @@ import android.widget.ImageView.ScaleType; import android.widget.LinearLayout; import android.widget.TextView; +import java.util.function.Consumer; + +import androidx.app.slice.SliceItem; import androidx.app.slice.core.SliceQuery; /** * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY) +@TargetApi(24) public class ActionRow extends FrameLayout { private static final int MAX_ACTIONS = 5; @@ -70,7 +79,7 @@ public class ActionRow extends FrameLayout { 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); + boolean tint = !item.hasHint(HINT_NO_TINT); if (tint) { ((ImageView) view).setImageTintList(ColorStateList.valueOf(mColor)); } @@ -100,36 +109,54 @@ public class ActionRow extends FrameLayout { mActionsGroup.removeAllViews(); addView(mActionsGroup); - SliceItem color = SliceQuery.find(actionRow, SliceItem.TYPE_COLOR); + SliceItem color = SliceQuery.find(actionRow, FORMAT_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(); - } - })); + SliceQuery.findAll(actionRow, FORMAT_ACTION).forEach(new Consumer<SliceItem>() { + @Override + public void accept(final SliceItem action) { + if (mActionsGroup.getChildCount() >= MAX_ACTIONS) { + return; + } + SliceItem image = SliceQuery.find(action, FORMAT_IMAGE); + if (image == null) { + return; + } + boolean tint = !image.hasHint(HINT_NO_TINT); + final SliceItem input = SliceQuery.find(action, FORMAT_REMOTE_INPUT); + if (input != null && input.getRemoteInput().getAllowFreeFormInput()) { + addAction(image.getIcon(), tint, image).setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + handleRemoteInputClick(v, action.getAction(), + input.getRemoteInput()); + } + }); + createRemoteInputView(mColor, getContext()); + } else { + addAction(image.getIcon(), tint, image).setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + AsyncTask.execute(new Runnable() { + @Override + public void run() { + + try { + action.getAction().send(); + } catch (CanceledException e) { + e.printStackTrace(); + } + } + }); + } + }); + } } }); setVisibility(getChildCount() != 0 ? View.VISIBLE : View.GONE); diff --git a/androidx/app/slice/widget/GridView.java b/androidx/app/slice/widget/GridView.java index c320c728..2aa57bd4 100644 --- a/androidx/app/slice/widget/GridView.java +++ b/androidx/app/slice/widget/GridView.java @@ -16,11 +16,16 @@ package androidx.app.slice.widget; +import static android.app.slice.Slice.HINT_LARGE; +import static android.app.slice.Slice.HINT_TITLE; +import static android.app.slice.SliceItem.FORMAT_COLOR; +import static android.app.slice.SliceItem.FORMAT_IMAGE; +import static android.app.slice.SliceItem.FORMAT_SLICE; +import static android.app.slice.SliceItem.FORMAT_TEXT; 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.annotation.TargetApi; import android.content.Context; import android.graphics.Color; import android.support.annotation.RestrictTo; @@ -37,7 +42,9 @@ import android.widget.TextView; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; +import androidx.app.slice.SliceItem; import androidx.app.slice.core.SliceQuery; import androidx.app.slice.view.R; @@ -45,6 +52,7 @@ import androidx.app.slice.view.R; * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY) +@TargetApi(24) public class GridView extends LinearLayout implements LargeSliceAdapter.SliceListView { private static final String TAG = "GridView"; @@ -76,7 +84,7 @@ public class GridView extends LinearLayout implements LargeSliceAdapter.SliceLis mIsAllImages = true; removeAllViews(); int total = 1; - if (slice.getType() == SliceItem.TYPE_SLICE) { + if (FORMAT_SLICE.equals(slice.getFormat())) { List<SliceItem> items = slice.getSlice().getItems(); total = items.size(); for (int i = 0; i < total; i++) { @@ -100,6 +108,11 @@ public class GridView extends LinearLayout implements LargeSliceAdapter.SliceLis } } + @Override + public void setColor(SliceItem color) { + + } + private void addExtraCount(int numExtra) { View last = getChildAt(getChildCount() - 1); FrameLayout frame = new FrameLayout(getContext()); @@ -126,15 +139,15 @@ public class GridView extends LinearLayout implements LargeSliceAdapter.SliceLis /** * Returns true if this item is just an image. */ - private boolean addItem(SliceItem item) { - if (item.getType() == SliceItem.TYPE_IMAGE) { + private boolean addItem(final SliceItem item) { + if (FORMAT_IMAGE.equals(item.getFormat())) { 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()); + final LinearLayout v = new LinearLayout(getContext()); int s = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12, getContext().getResources().getDisplayMetrics()); v.setPadding(0, s, 0, 0); @@ -142,40 +155,45 @@ public class GridView extends LinearLayout implements LargeSliceAdapter.SliceLis 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) { + if (FORMAT_SLICE.equals(item.getFormat())) { items.addAll(item.getSlice().getItems()); } - items.forEach(i -> { - Context context = getContext(); - switch (i.getType()) { - case SliceItem.TYPE_TEXT: - boolean title = false; - if ((SliceQuery.hasAnyHints(item, new String[] { - Slice.HINT_LARGE, Slice.HINT_TITLE - }))) { - title = true; - } - TextView tv = (TextView) LayoutInflater.from(context).inflate(title - ? R.layout.abc_slice_title : R.layout.abc_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_COLOR: - // TODO: Support color to tint stuff here. - break; + items.forEach(new Consumer<SliceItem>() { + @Override + public void accept(SliceItem i) { + Context context = getContext(); + switch (i.getFormat()) { + case FORMAT_TEXT: + boolean title = false; + if ((SliceQuery.hasAnyHints(item, new String[]{ + HINT_LARGE, HINT_TITLE + }))) { + title = true; + } + TextView tv = (TextView) LayoutInflater.from(context).inflate(title + ? R.layout.abc_slice_title + : R.layout.abc_slice_secondary_text, + null); + tv.setText(i.getText()); + v.addView(tv); + break; + case FORMAT_IMAGE: + ImageView iv = new ImageView(context); + iv.setImageIcon(i.getIcon()); + if (item.hasHint(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 FORMAT_COLOR: + // TODO: Support color to tint stuff here. + break; + } } }); addView(v, new LayoutParams(0, WRAP_CONTENT, 1)); diff --git a/androidx/app/slice/widget/LargeSliceAdapter.java b/androidx/app/slice/widget/LargeSliceAdapter.java index ff513c5f..335eb23f 100644 --- a/androidx/app/slice/widget/LargeSliceAdapter.java +++ b/androidx/app/slice/widget/LargeSliceAdapter.java @@ -16,8 +16,14 @@ package androidx.app.slice.widget; -import android.app.slice.Slice; -import android.app.slice.SliceItem; +import static android.app.slice.Slice.HINT_HORIZONTAL; +import static android.app.slice.Slice.SUBTYPE_MESSAGE; +import static android.app.slice.Slice.SUBTYPE_SOURCE; +import static android.app.slice.SliceItem.FORMAT_COLOR; +import static android.app.slice.SliceItem.FORMAT_IMAGE; +import static android.app.slice.SliceItem.FORMAT_TEXT; + +import android.annotation.TargetApi; import android.content.Context; import android.support.annotation.RestrictTo; import android.support.v7.widget.RecyclerView; @@ -29,8 +35,11 @@ import android.view.ViewGroup.LayoutParams; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; +import androidx.app.slice.SliceItem; import androidx.app.slice.core.SliceQuery; import androidx.app.slice.view.R; @@ -38,6 +47,7 @@ import androidx.app.slice.view.R; * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY) +@TargetApi(24) public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.SliceViewHolder> { public static final int TYPE_DEFAULT = 1; @@ -62,8 +72,12 @@ public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.Sl public void setSliceItems(List<SliceItem> slices, SliceItem color) { mColor = color; mIdGen.resetUsage(); - mSlices = slices.stream().map(s -> new SliceWrapper(s, mIdGen)) - .collect(Collectors.toList()); + mSlices = slices.stream().map(new Function<SliceItem, SliceWrapper>() { + @Override + public SliceWrapper apply(SliceItem s) { + return new SliceWrapper(s, mIdGen); + } + }).collect(Collectors.<SliceWrapper>toList()); notifyDataSetChanged(); } @@ -118,20 +132,20 @@ public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.Sl public SliceWrapper(SliceItem item, IdGenerator idGen) { mItem = item; - mType = getType(item); + mType = getFormat(item); mId = idGen.getId(item); } - public static int getType(SliceItem item) { - if (item.hasHint(Slice.HINT_MESSAGE)) { + public static int getFormat(SliceItem item) { + if (SUBTYPE_MESSAGE.equals(item.getSubType())) { // TODO: Better way to determine me or not? Something more like Messaging style. - if (SliceQuery.find(item, -1, Slice.HINT_SOURCE, null) != null) { + if (SliceQuery.findSubtype(item, null, SUBTYPE_SOURCE) != null) { return TYPE_MESSAGE; } else { return TYPE_MESSAGE_LOCAL; } } - if (item.hasHint(Slice.HINT_HORIZONTAL)) { + if (item.hasHint(HINT_HORIZONTAL)) { return TYPE_GRID; } return TYPE_DEFAULT; @@ -162,9 +176,7 @@ public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.Sl /** * Set the color for the items in this view. */ - default void setColor(SliceItem color) { - - } + void setColor(SliceItem color); } private static class IdGenerator { @@ -184,21 +196,24 @@ public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.Sl } 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_IMAGE: - builder.append(i.getIcon()); - break; - case SliceItem.TYPE_TEXT: - builder.append(i.getText()); - break; - case SliceItem.TYPE_COLOR: - builder.append(i.getColor()); - break; + final StringBuilder builder = new StringBuilder(); + SliceQuery.stream(item).forEach(new Consumer<SliceItem>() { + @Override + public void accept(SliceItem i) { + builder.append(i.getFormat()); + //i.removeHint(Slice.HINT_SELECTED); + builder.append(i.getHints()); + switch (i.getFormat()) { + case FORMAT_IMAGE: + builder.append(i.getIcon()); + break; + case FORMAT_TEXT: + builder.append(i.getText()); + break; + case FORMAT_COLOR: + builder.append(i.getColor()); + break; + } } }); return builder.toString(); diff --git a/androidx/app/slice/widget/LargeTemplateView.java b/androidx/app/slice/widget/LargeTemplateView.java index c5e82aaf..0160f59f 100644 --- a/androidx/app/slice/widget/LargeTemplateView.java +++ b/androidx/app/slice/widget/LargeTemplateView.java @@ -16,10 +16,15 @@ package androidx.app.slice.widget; +import static android.app.slice.Slice.HINT_ACTIONS; +import static android.app.slice.Slice.HINT_LIST; +import static android.app.slice.Slice.HINT_LIST_ITEM; +import static android.app.slice.Slice.HINT_PARTIAL; +import static android.app.slice.SliceItem.FORMAT_COLOR; +import static android.app.slice.SliceItem.FORMAT_SLICE; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import android.app.slice.Slice; -import android.app.slice.SliceItem; +import android.annotation.TargetApi; import android.content.Context; import android.support.annotation.RestrictTo; import android.support.v7.widget.LinearLayoutManager; @@ -28,13 +33,17 @@ import android.util.TypedValue; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; +import androidx.app.slice.Slice; +import androidx.app.slice.SliceItem; import androidx.app.slice.core.SliceQuery; /** * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY) +@TargetApi(24) public class LargeTemplateView extends SliceView.SliceModeView { private final LargeSliceAdapter mAdapter; @@ -68,7 +77,7 @@ public class LargeTemplateView extends SliceView.SliceModeView { mRecyclerView.getLayoutParams().height = WRAP_CONTENT; super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mRecyclerView.getMeasuredHeight() > mMaxHeight - || (mSlice != null && SliceQuery.hasHints(mSlice, Slice.HINT_PARTIAL))) { + || (mSlice != null && SliceQuery.hasHints(mSlice, HINT_PARTIAL))) { mRecyclerView.getLayoutParams().height = mDefaultHeight; } else { mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight(); @@ -78,28 +87,31 @@ public class LargeTemplateView extends SliceView.SliceModeView { @Override public void setSlice(Slice slice) { - SliceItem color = SliceQuery.find(slice, SliceItem.TYPE_COLOR); + SliceItem color = SliceQuery.find(slice, FORMAT_COLOR); mSlice = slice; - List<SliceItem> items = new ArrayList<>(); - boolean[] hasHeader = new boolean[1]; - if (SliceQuery.hasHints(slice, Slice.HINT_LIST)) { + final List<SliceItem> items = new ArrayList<>(); + final boolean[] hasHeader = new boolean[1]; + if (SliceQuery.hasHints(slice, HINT_LIST)) { addList(slice, items); } else { - slice.getItems().forEach(item -> { - if (item.hasHint(Slice.HINT_ACTIONS)) { - return; - } else if (item.getType() == SliceItem.TYPE_COLOR) { - 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 { - items.add(item); + slice.getItems().forEach(new Consumer<SliceItem>() { + @Override + public void accept(SliceItem item) { + if (item.hasHint(HINT_ACTIONS)) { + return; + } else if (FORMAT_COLOR.equals(item.getFormat())) { + return; + } else if (FORMAT_SLICE.equals(item.getFormat()) + && item.hasHint(HINT_LIST)) { + addList(item.getSlice(), items); + } else if (item.hasHint(HINT_LIST_ITEM)) { + items.add(item); + } else if (!hasHeader[0]) { + hasHeader[0] = true; + items.add(0, item); + } else { + items.add(item); + } } }); } diff --git a/androidx/app/slice/widget/MessageView.java b/androidx/app/slice/widget/MessageView.java index 9db35bb3..cb9497a8 100644 --- a/androidx/app/slice/widget/MessageView.java +++ b/androidx/app/slice/widget/MessageView.java @@ -16,8 +16,11 @@ package androidx.app.slice.widget; -import android.app.slice.Slice; -import android.app.slice.SliceItem; +import static android.app.slice.Slice.SUBTYPE_SOURCE; +import static android.app.slice.SliceItem.FORMAT_IMAGE; +import static android.app.slice.SliceItem.FORMAT_TEXT; + +import android.annotation.TargetApi; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -30,12 +33,16 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import java.util.function.Consumer; + +import androidx.app.slice.SliceItem; import androidx.app.slice.core.SliceQuery; /** * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY) +@TargetApi(24) public class MessageView extends LinearLayout implements LargeSliceAdapter.SliceListView { private TextView mDetails; @@ -54,7 +61,7 @@ public class MessageView extends LinearLayout implements LargeSliceAdapter.Slice @Override public void setSliceItem(SliceItem slice) { - SliceItem source = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, Slice.HINT_SOURCE, null); + SliceItem source = SliceQuery.findSubtype(slice, FORMAT_IMAGE, SUBTYPE_SOURCE); if (source != null) { final int iconSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getContext().getResources().getDisplayMetrics()); @@ -66,14 +73,22 @@ public class MessageView extends LinearLayout implements LargeSliceAdapter.Slice 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'); + final SpannableStringBuilder builder = new SpannableStringBuilder(); + SliceQuery.findAll(slice, FORMAT_TEXT).forEach(new Consumer<SliceItem>() { + @Override + public void accept(SliceItem text) { + if (builder.length() != 0) { + builder.append('\n'); + } + builder.append(text.getText()); } - builder.append(text.getText()); }); mDetails.setText(builder.toString()); } + @Override + public void setColor(SliceItem color) { + + } + } diff --git a/androidx/app/slice/widget/RemoteInputView.java b/androidx/app/slice/widget/RemoteInputView.java index 9d45501d..da350183 100644 --- a/androidx/app/slice/widget/RemoteInputView.java +++ b/androidx/app/slice/widget/RemoteInputView.java @@ -17,6 +17,7 @@ package androidx.app.slice.widget; import android.animation.Animator; +import android.annotation.TargetApi; import android.app.PendingIntent; import android.app.RemoteInput; import android.content.Context; @@ -56,6 +57,7 @@ import androidx.app.slice.view.R; */ // TODO this should be unified with SystemUI RemoteInputView (b/67527720) @RestrictTo(RestrictTo.Scope.LIBRARY) +@TargetApi(23) public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher { private static final String TAG = "RemoteInput"; diff --git a/androidx/app/slice/widget/ShortcutView.java b/androidx/app/slice/widget/ShortcutView.java index b6e77cd0..f93abc6d 100644 --- a/androidx/app/slice/widget/ShortcutView.java +++ b/androidx/app/slice/widget/ShortcutView.java @@ -16,10 +16,17 @@ package androidx.app.slice.widget; +import static android.app.slice.Slice.HINT_LARGE; +import static android.app.slice.Slice.HINT_TITLE; +import static android.app.slice.Slice.SUBTYPE_SOURCE; +import static android.app.slice.SliceItem.FORMAT_ACTION; +import static android.app.slice.SliceItem.FORMAT_COLOR; +import static android.app.slice.SliceItem.FORMAT_IMAGE; +import static android.app.slice.SliceItem.FORMAT_TEXT; + +import android.annotation.TargetApi; import android.app.PendingIntent; import android.app.PendingIntent.CanceledException; -import android.app.slice.Slice; -import android.app.slice.SliceItem; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -33,6 +40,8 @@ import android.graphics.drawable.shapes.OvalShape; import android.net.Uri; import android.support.annotation.RestrictTo; +import androidx.app.slice.Slice; +import androidx.app.slice.SliceItem; import androidx.app.slice.core.SliceQuery; import androidx.app.slice.view.R; @@ -40,6 +49,7 @@ import androidx.app.slice.view.R; * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY) +@TargetApi(23) public class ShortcutView extends androidx.app.slice.widget.SliceView.SliceModeView { private static final String TAG = "ShortcutView"; @@ -61,11 +71,14 @@ public class ShortcutView extends androidx.app.slice.widget.SliceView.SliceModeV @Override public void setSlice(Slice slice) { + mLabel = null; + mIcon = null; + mAction = null; removeAllViews(); determineShortcutItems(getContext(), slice); - SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR); + SliceItem colorItem = SliceQuery.find(slice, FORMAT_COLOR); if (colorItem == null) { - colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR); + colorItem = SliceQuery.find(slice, FORMAT_COLOR); } // TODO: pick better default colour final int color = colorItem != null ? colorItem.getColor() : Color.GRAY; @@ -73,8 +86,8 @@ public class ShortcutView extends androidx.app.slice.widget.SliceView.SliceModeV circle.setTint(color); setBackground(circle); if (mIcon != null) { - final boolean isLarge = mIcon.hasHint(Slice.HINT_LARGE) - || mIcon.hasHint(Slice.HINT_SOURCE); + final boolean isLarge = mIcon.hasHint(HINT_LARGE) + || SUBTYPE_SOURCE.equals(mIcon.getSubType()); final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize; SliceViewUtil.createCircledIcon(getContext(), color, iconSize, mIcon.getIcon(), isLarge, this /* parent */); @@ -112,38 +125,38 @@ public class ShortcutView extends androidx.app.slice.widget.SliceView.SliceModeV * Looks at the slice and determines which items are best to use to compose the shortcut. */ private void determineShortcutItems(Context context, Slice slice) { - SliceItem titleItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION, - Slice.HINT_TITLE, null); + SliceItem titleItem = SliceQuery.find(slice, FORMAT_ACTION, + 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, + mIcon = SliceQuery.find(titleItem.getSlice(), FORMAT_IMAGE, HINT_TITLE, null); - mLabel = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_TEXT, Slice.HINT_TITLE, + mLabel = SliceQuery.find(titleItem.getSlice(), FORMAT_TEXT, HINT_TITLE, null); } else { // No hinted action; just use the first one - SliceItem actionItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION, (String) null, + SliceItem actionItem = SliceQuery.find(slice, FORMAT_ACTION, (String) null, null); mAction = (actionItem != null) ? actionItem.getAction() : null; } // First fallback: any hinted image and text if (mIcon == null) { - mIcon = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, Slice.HINT_TITLE, + mIcon = SliceQuery.find(slice, FORMAT_IMAGE, HINT_TITLE, null); } if (mLabel == null) { - mLabel = SliceQuery.find(slice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, + mLabel = SliceQuery.find(slice, FORMAT_TEXT, HINT_TITLE, null); } // Second fallback: first image and text if (mIcon == null) { - mIcon = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, (String) null, + mIcon = SliceQuery.find(slice, FORMAT_IMAGE, (String) null, null); } if (mLabel == null) { - mLabel = SliceQuery.find(slice, SliceItem.TYPE_TEXT, (String) null, + mLabel = SliceQuery.find(slice, FORMAT_TEXT, (String) null, null); } // Final fallback: use app info @@ -156,12 +169,12 @@ public class ShortcutView extends androidx.app.slice.widget.SliceView.SliceModeV if (mIcon == null) { Slice.Builder sb = new Slice.Builder(slice.getUri()); Drawable icon = pm.getApplicationIcon(appInfo); - sb.addIcon(SliceViewUtil.createIconFromDrawable(icon), Slice.HINT_LARGE); + sb.addIcon(SliceViewUtil.createIconFromDrawable(icon), HINT_LARGE); mIcon = sb.build().getItems().get(0); } if (mLabel == null) { Slice.Builder sb = new Slice.Builder(slice.getUri()); - sb.addText(pm.getApplicationLabel(appInfo)); + sb.addText(pm.getApplicationLabel(appInfo), null); mLabel = sb.build().getItems().get(0); } if (mAction == null) { diff --git a/androidx/app/slice/widget/SliceLiveData.java b/androidx/app/slice/widget/SliceLiveData.java index eaaef502..9b36ee12 100644 --- a/androidx/app/slice/widget/SliceLiveData.java +++ b/androidx/app/slice/widget/SliceLiveData.java @@ -15,13 +15,16 @@ */ package androidx.app.slice.widget; -import android.app.slice.Slice; import android.arch.lifecycle.LiveData; import android.content.Context; +import android.content.Intent; import android.database.ContentObserver; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; +import android.support.annotation.NonNull; + +import androidx.app.slice.Slice; /** * Class with factory methods for creating LiveData that observes slices. @@ -40,36 +43,68 @@ public final class SliceLiveData { return new SliceLiveDataImpl(context.getApplicationContext(), uri); } + /** + * Produces an {@link LiveData} that tracks a Slice for a given Intent. To use + * this method your app must have the permission to the slice Uri or hold + * {@link android.Manifest.permission#BIND_SLICE}). + */ + public static LiveData<Slice> fromIntent(@NonNull Context context, @NonNull Intent intent) { + return new SliceLiveDataImpl(context.getApplicationContext(), intent); + } + private static class SliceLiveDataImpl extends LiveData<Slice> { - private final Uri mUri; private final Context mContext; + private final Intent mIntent; + private Uri mUri; private SliceLiveDataImpl(Context context, Uri uri) { super(); mContext = context; mUri = uri; + mIntent = null; // TODO: Check if uri points at a Slice? } + private SliceLiveDataImpl(Context context, Intent intent) { + super(); + mContext = context; + mUri = null; + mIntent = intent; + } + @Override protected void onActive() { - AsyncTask.execute(this::updateSlice); - mContext.getContentResolver().registerContentObserver(mUri, false, mObserver); + AsyncTask.execute(mUpdateSlice); + if (mUri != null) { + mContext.getContentResolver().registerContentObserver(mUri, false, mObserver); + } } @Override protected void onInactive() { - mContext.getContentResolver().unregisterContentObserver(mObserver); + if (mUri != null) { + mContext.getContentResolver().unregisterContentObserver(mObserver); + } } - private void updateSlice() { - postValue(Slice.bindSlice(mContext.getContentResolver(), mUri)); - } + private final Runnable mUpdateSlice = new Runnable() { + @Override + public void run() { + Slice s = mUri != null ? Slice.bindSlice(mContext, mUri) + : Slice.bindSlice(mContext, mIntent); + if (mUri == null && s != null) { + mContext.getContentResolver().registerContentObserver(s.getUri(), + false, mObserver); + mUri = s.getUri(); + } + postValue(s); + } + }; private final ContentObserver mObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { - AsyncTask.execute(SliceLiveDataImpl.this::updateSlice); + AsyncTask.execute(mUpdateSlice); } }; } diff --git a/androidx/app/slice/widget/SliceView.java b/androidx/app/slice/widget/SliceView.java index 6e12dbab..5597ac9d 100644 --- a/androidx/app/slice/widget/SliceView.java +++ b/androidx/app/slice/widget/SliceView.java @@ -16,8 +16,10 @@ package androidx.app.slice.widget; -import android.app.slice.Slice; -import android.app.slice.SliceItem; +import static android.app.slice.Slice.HINT_ACTIONS; +import static android.app.slice.SliceItem.FORMAT_COLOR; +import static android.app.slice.SliceItem.FORMAT_SLICE; + import android.arch.lifecycle.Observer; import android.content.ContentResolver; import android.content.Context; @@ -34,6 +36,8 @@ import android.widget.FrameLayout; import java.util.List; +import androidx.app.slice.Slice; +import androidx.app.slice.SliceItem; import androidx.app.slice.core.SliceQuery; import androidx.app.slice.view.R; @@ -54,8 +58,9 @@ import androidx.app.slice.view.R; * <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. + * {@link android.app.slice.Slice#HINT_TITLE} would be placed in the title position of a template. + * A slice annotated with {@link android.app.slice.Slice#HINT_LIST} would present the child items + * of that slice in a list. * <p> * Example usage: * @@ -111,8 +116,8 @@ public class SliceView extends ViewGroup implements Observer<Slice> { public static final int MODE_LARGE = 2; /** * 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. + * icon, and label. This can be indicated by using {@link android.app.slice.Slice#HINT_TITLE} + * on an action in a slice. */ public static final int MODE_SHORTCUT = 3; @@ -261,13 +266,15 @@ public class SliceView extends ViewGroup implements Observer<Slice> { return; } // TODO: Smarter mapping here from one state to the next. - SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR); + SliceItem color = SliceQuery.find(mCurrentSlice, FORMAT_COLOR); List<SliceItem> items = mCurrentSlice.getItems(); - SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE, - Slice.HINT_ACTIONS, + SliceItem actionRow = SliceQuery.find(mCurrentSlice, FORMAT_SLICE, + HINT_ACTIONS, null); int mode = getMode(); - if (mode != mCurrentView.getMode()) { + if (mMode == mCurrentView.getMode()) { + mCurrentView.setSlice(mCurrentSlice); + } else { removeAllViews(); mCurrentView = createView(mode); addView(mCurrentView, getChildLp(mCurrentView)); diff --git a/androidx/app/slice/widget/SliceViewUtil.java b/androidx/app/slice/widget/SliceViewUtil.java index 62844c1b..c98215f3 100644 --- a/androidx/app/slice/widget/SliceViewUtil.java +++ b/androidx/app/slice/widget/SliceViewUtil.java @@ -16,6 +16,7 @@ package androidx.app.slice.widget; +import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; @@ -48,6 +49,7 @@ import java.util.Calendar; * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY) +@TargetApi(23) public class SliceViewUtil { /** @@ -152,6 +154,7 @@ public class SliceViewUtil { /** */ + @TargetApi(28) public static void createCircledIcon(@NonNull Context context, int color, int iconSizePx, Icon icon, boolean isLarge, ViewGroup parent) { ImageView v = new ImageView(context); diff --git a/androidx/app/slice/widget/SmallTemplateView.java b/androidx/app/slice/widget/SmallTemplateView.java index f430602d..1d3ab2d8 100644 --- a/androidx/app/slice/widget/SmallTemplateView.java +++ b/androidx/app/slice/widget/SmallTemplateView.java @@ -16,16 +16,29 @@ package androidx.app.slice.widget; +import static android.app.slice.Slice.HINT_LIST; +import static android.app.slice.Slice.HINT_LIST_ITEM; +import static android.app.slice.Slice.HINT_NO_TINT; +import static android.app.slice.Slice.HINT_SELECTED; +import static android.app.slice.Slice.HINT_TITLE; +import static android.app.slice.SliceItem.FORMAT_ACTION; +import static android.app.slice.SliceItem.FORMAT_COLOR; +import static android.app.slice.SliceItem.FORMAT_IMAGE; +import static android.app.slice.SliceItem.FORMAT_SLICE; +import static android.app.slice.SliceItem.FORMAT_TEXT; +import static android.app.slice.SliceItem.FORMAT_TIMESTAMP; + +import android.annotation.TargetApi; import android.app.PendingIntent; import android.app.PendingIntent.CanceledException; -import android.app.slice.Slice; -import android.app.slice.SliceItem; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; import android.support.annotation.RestrictTo; import android.util.Log; import android.view.View; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Switch; @@ -33,8 +46,11 @@ import android.widget.TextView; import java.util.ArrayList; import java.util.List; +import java.util.function.Predicate; -import androidx.app.slice.builders.SliceHints; +import androidx.app.slice.Slice; +import androidx.app.slice.SliceItem; +import androidx.app.slice.core.SliceHints; import androidx.app.slice.core.SliceQuery; import androidx.app.slice.view.R; @@ -44,6 +60,7 @@ import androidx.app.slice.view.R; * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY) +@TargetApi(23) public class SmallTemplateView extends SliceView.SliceModeView implements LargeSliceAdapter.SliceListView, View.OnClickListener { @@ -73,7 +90,7 @@ public class SmallTemplateView extends SliceView.SliceModeView implements mPadding = getContext().getResources().getDimensionPixelSize(R.dimen.abc_slice_padding); inflate(context, R.layout.abc_slice_small_template, this); - mStartContainer = (LinearLayout) findViewById(android.R.id.icon_frame); + mStartContainer = (LinearLayout) findViewById(R.id.icon_frame); mContent = (LinearLayout) findViewById(android.R.id.content); mPrimaryText = (TextView) findViewById(android.R.id.title); mSecondaryText = (TextView) findViewById(android.R.id.summary); @@ -96,6 +113,7 @@ public class SmallTemplateView extends SliceView.SliceModeView implements public void setSliceItem(SliceItem slice) { populateViews(slice, slice); } + @Override public void setSlice(Slice slice) { Slice.Builder sb = new Slice.Builder(slice.getUri()); @@ -106,15 +124,15 @@ public class SmallTemplateView extends SliceView.SliceModeView implements private SliceItem getFirstSlice(Slice slice) { List<SliceItem> items = slice.getItems(); - if (items.size() > 0 && items.get(0).getType() == SliceItem.TYPE_SLICE) { + if (items.size() > 0 && FORMAT_SLICE.equals(items.get(0).getFormat())) { // Check if this slice is appropriate to use to populate small template SliceItem firstSlice = items.get(0); - if (firstSlice.hasHint(Slice.HINT_LIST)) { + if (firstSlice.hasHint(HINT_LIST)) { // Check for header, use that if it exists - SliceItem header = SliceQuery.find(firstSlice, SliceItem.TYPE_SLICE, + SliceItem header = SliceQuery.find(firstSlice, FORMAT_SLICE, null, new String[] { - Slice.HINT_LIST_ITEM, Slice.HINT_LIST + HINT_LIST_ITEM, HINT_LIST }); if (header != null) { return SliceQuery.findFirstSlice(header); @@ -134,10 +152,11 @@ public class SmallTemplateView extends SliceView.SliceModeView implements return s.getItems().get(0); } + @TargetApi(24) private void populateViews(SliceItem fullSlice, SliceItem sliceItem) { resetViews(); ArrayList<SliceItem> items = new ArrayList<>(); - if (sliceItem.getType() == SliceItem.TYPE_SLICE) { + if (FORMAT_SLICE.equals(sliceItem.getFormat())) { items = new ArrayList<>(sliceItem.getSlice().getItems()); } else { items.add(sliceItem); @@ -152,7 +171,7 @@ public class SmallTemplateView extends SliceView.SliceModeView implements // If the first item is an action let's check if it should be used to populate the content // or if it should be in the start position. SliceItem firstSlice = items.size() > 0 ? items.get(0) : null; - if (firstSlice != null && firstSlice.getType() == SliceItem.TYPE_ACTION) { + if (firstSlice != null && FORMAT_ACTION.equals(firstSlice.getFormat())) { if (!SliceQuery.isSimpleAction(firstSlice)) { mRowAction = firstSlice; items.remove(0); @@ -168,19 +187,19 @@ public class SmallTemplateView extends SliceView.SliceModeView implements for (int i = 0; i < items.size(); i++) { SliceItem item = items.get(i); List<String> hints = item.getHints(); - int itemType = item.getType(); - if (hints.contains(Slice.HINT_TITLE)) { + String itemType = item.getFormat(); + if (hints.contains(HINT_TITLE)) { // Things with these hints could go in the title / start position - if ((startItem == null || !startItem.hasHint(Slice.HINT_TITLE)) + if ((startItem == null || !startItem.hasHint(HINT_TITLE)) && SliceQuery.isStartType(item)) { startItem = item; - } else if ((titleItem == null || !titleItem.hasHint(Slice.HINT_TITLE)) - && itemType == SliceItem.TYPE_TEXT) { + } else if ((titleItem == null || !titleItem.hasHint(HINT_TITLE)) + && FORMAT_TEXT.equals(itemType)) { titleItem = item; } else { endItems.add(item); } - } else if (item.getType() == SliceItem.TYPE_TEXT) { + } else if (FORMAT_TEXT.equals(item.getFormat())) { if (titleItem == null) { titleItem = item; } else if (subTitle == null) { @@ -188,7 +207,7 @@ public class SmallTemplateView extends SliceView.SliceModeView implements } else { endItems.add(item); } - } else if (item.getType() == SliceItem.TYPE_SLICE) { + } else if (FORMAT_SLICE.equals(item.getFormat())) { List<SliceItem> subItems = item.getSlice().getItems(); for (int j = 0; j < subItems.size(); j++) { endItems.add(subItems.get(j)); @@ -202,6 +221,7 @@ public class SmallTemplateView extends SliceView.SliceModeView implements if (startItem != null) { // TODO - check for icon, timestamp, action with icon } + mStartContainer.setVisibility(startItem != null ? View.VISIBLE : View.GONE); if (titleItem != null) { mPrimaryText.setText(titleItem.getText()); } @@ -221,8 +241,13 @@ public class SmallTemplateView extends SliceView.SliceModeView implements } // Check if we have a toggle somewhere in our end items SliceItem toggleItem = endItems.stream() - .filter(item -> (item.getType() == SliceItem.TYPE_ACTION - && SliceQuery.hasHints(item.getSlice(), SliceHints.HINT_TOGGLE))) + .filter(new Predicate<SliceItem>() { + @Override + public boolean test(SliceItem item) { + return FORMAT_ACTION.equals(item.getFormat()) + && SliceQuery.hasHints(item.getSlice(), SliceHints.HINT_TOGGLE); + } + }) .findFirst().orElse(null); if (toggleItem != null) { if (addToggle(toggleItem)) { @@ -233,7 +258,7 @@ public class SmallTemplateView extends SliceView.SliceModeView implements } } // If we're here we can still show end items - SliceItem colorItem = SliceQuery.find(fullSlice, SliceItem.TYPE_COLOR); + SliceItem colorItem = SliceQuery.find(fullSlice, FORMAT_COLOR); int color = colorItem != null ? colorItem.getColor() : (mColorItem != null) @@ -244,7 +269,7 @@ public class SmallTemplateView extends SliceView.SliceModeView implements for (int i = 0; i < items.size(); i++) { SliceItem item = items.get(i); if (itemCount <= MAX_END_ITEMS) { - if (item.getType() == SliceItem.TYPE_ACTION) { + if (FORMAT_ACTION.equals(item.getFormat())) { if (SliceQuery.hasHints(item.getSlice(), SliceHints.HINT_TOGGLE)) { if (addToggle(item)) { break; @@ -254,10 +279,10 @@ public class SmallTemplateView extends SliceView.SliceModeView implements clickableEndItem = true; itemCount++; } - } else if (item.getType() == SliceItem.TYPE_IMAGE) { + } else if (FORMAT_IMAGE.equals(item.getFormat())) { addIcon(item, color, mEndContainer); itemCount++; - } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) { + } else if (FORMAT_TIMESTAMP.equals(item.getFormat())) { TextView tv = new TextView(getContext()); tv.setText(SliceViewUtil.getRelativeTimeString(item.getTimestamp())); mEndContainer.addView(tv); @@ -273,21 +298,24 @@ public class SmallTemplateView extends SliceView.SliceModeView implements /** * @return Whether a toggle was added. */ - private boolean addToggle(SliceItem toggleItem) { - if (toggleItem.getType() != SliceItem.TYPE_ACTION + private boolean addToggle(final SliceItem toggleItem) { + if (!FORMAT_ACTION.equals(toggleItem.getFormat()) || !SliceQuery.hasHints(toggleItem.getSlice(), SliceHints.HINT_TOGGLE)) { return false; } mToggle = new Switch(getContext()); mEndContainer.addView(mToggle); - mToggle.setChecked(SliceQuery.hasHints(toggleItem.getSlice(), Slice.HINT_SELECTED)); - mToggle.setOnCheckedChangeListener((buttonView, isChecked) -> { - try { - PendingIntent pi = toggleItem.getAction(); - Intent i = new Intent().putExtra(SliceHints.EXTRA_TOGGLE_STATE, isChecked); - pi.send(getContext(), 0, i, null, null); - } catch (CanceledException e) { - mToggle.setSelected(!isChecked); + mToggle.setChecked(SliceQuery.hasHints(toggleItem.getSlice(), HINT_SELECTED)); + mToggle.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + try { + PendingIntent pi = toggleItem.getAction(); + Intent i = new Intent().putExtra(SliceHints.EXTRA_TOGGLE_STATE, isChecked); + pi.send(getContext(), 0, i, null, null); + } catch (CanceledException e) { + mToggle.setSelected(!isChecked); + } } }); return true; @@ -299,10 +327,10 @@ public class SmallTemplateView extends SliceView.SliceModeView implements 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); + if (FORMAT_ACTION.equals(sliceItem.getFormat())) { + image = SliceQuery.find(sliceItem.getSlice(), FORMAT_IMAGE); action = sliceItem; - } else if (sliceItem.getType() == SliceItem.TYPE_IMAGE) { + } else if (FORMAT_IMAGE.equals(sliceItem.getFormat())) { image = sliceItem; } if (image != null) { @@ -310,18 +338,25 @@ public class SmallTemplateView extends SliceView.SliceModeView implements 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.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + AsyncTask.execute(new Runnable() { + @Override + public void run() { + 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)) { + if (color != -1 && !sliceItem.hasHint(HINT_NO_TINT)) { iv.setColorFilter(color); } container.addView(iv); @@ -336,17 +371,20 @@ public class SmallTemplateView extends SliceView.SliceModeView implements @Override public void onClick(View view) { - if (mRowAction != null && mRowAction.getType() == SliceItem.TYPE_ACTION) { + if (mRowAction != null && FORMAT_ACTION.equals(mRowAction.getFormat())) { if (mToggle != null && SliceQuery.hasHints(mRowAction.getSlice(), SliceHints.HINT_TOGGLE)) { mToggle.toggle(); return; } - AsyncTask.execute(() -> { - try { - mRowAction.getAction().send(); - } catch (CanceledException e) { - Log.w(TAG, "PendingIntent for slice cannot be sent", e); + AsyncTask.execute(new Runnable() { + @Override + public void run() { + try { + mRowAction.getAction().send(); + } catch (CanceledException e) { + Log.w(TAG, "PendingIntent for slice cannot be sent", e); + } } }); } diff --git a/com/android/car/setupwizardlib/CarSetupWizardLayout.java b/com/android/car/setupwizardlib/CarSetupWizardLayout.java index d1838975..35863107 100644 --- a/com/android/car/setupwizardlib/CarSetupWizardLayout.java +++ b/com/android/car/setupwizardlib/CarSetupWizardLayout.java @@ -23,6 +23,7 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; +import android.widget.ProgressBar; /** @@ -41,6 +42,8 @@ public class CarSetupWizardLayout extends LinearLayout { private Button mPrimaryContinueButton; private Button mSecondaryContinueButton; + private ProgressBar mProgressBar; + public CarSetupWizardLayout(Context context) { this(context, null); } @@ -84,6 +87,8 @@ public class CarSetupWizardLayout extends LinearLayout { String secondaryContinueButtonText; boolean secondaryContinueButtonEnabled; + boolean showProgressBar; + try { showBackButton = attrArray.getBoolean( R.styleable.CarSetupWizardLayout_showBackButton, true); @@ -99,6 +104,8 @@ public class CarSetupWizardLayout extends LinearLayout { R.styleable.CarSetupWizardLayout_secondaryContinueButtonText); secondaryContinueButtonEnabled = attrArray.getBoolean( R.styleable.CarSetupWizardLayout_secondaryContinueButtonEnabled, true); + showProgressBar = attrArray.getBoolean( + R.styleable.CarSetupWizardLayout_showProgressBar, false); } finally { attrArray.recycle(); } @@ -130,14 +137,16 @@ public class CarSetupWizardLayout extends LinearLayout { setSecondaryContinueButtonVisible(false); } - // TODO: Handle loading bar logic + mProgressBar = findViewById(R.id.progress_bar); + setProgressBarVisible(showProgressBar); + } /** * Set a given button's visibility. */ - private void setViewVisible(View button, boolean visible) { - button.setVisibility(visible ? View.VISIBLE : View.GONE); + private void setViewVisible(View view, boolean visible) { + view.setVisibility(visible ? View.VISIBLE : View.GONE); } /** @@ -212,4 +221,11 @@ public class CarSetupWizardLayout extends LinearLayout { public void setSecondaryContinueButtonVisible(boolean visible) { setViewVisible(mSecondaryContinueButton, visible); } + + /** + * Set the progress bar visibility to the given visibility. + */ + public void setProgressBarVisible(boolean visible) { + setViewVisible(mProgressBar, visible); + } } diff --git a/com/android/commands/pm/Pm.java b/com/android/commands/pm/Pm.java deleted file mode 100644 index 9490880a..00000000 --- a/com/android/commands/pm/Pm.java +++ /dev/null @@ -1,822 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.commands.pm; - -import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; -import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK; -import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; -import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; -import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; - -import android.accounts.IAccountManager; -import android.app.ActivityManager; -import android.app.PackageInstallObserver; -import android.content.ComponentName; -import android.content.Context; -import android.content.IIntentReceiver; -import android.content.IIntentSender; -import android.content.Intent; -import android.content.IntentSender; -import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageDataObserver; -import android.content.pm.IPackageInstaller; -import android.content.pm.IPackageManager; -import android.content.pm.PackageInfo; -import android.content.pm.PackageInstaller; -import android.content.pm.PackageInstaller.SessionInfo; -import android.content.pm.PackageInstaller.SessionParams; -import android.content.pm.PackageManager; -import android.content.pm.PackageParser; -import android.content.pm.PackageParser.ApkLite; -import android.content.pm.PackageParser.PackageLite; -import android.content.pm.PackageParser.PackageParserException; -import android.content.pm.UserInfo; -import android.net.Uri; -import android.os.Binder; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.IUserManager; -import android.os.ParcelFileDescriptor; -import android.os.Process; -import android.os.RemoteException; -import android.os.ResultReceiver; -import android.os.SELinux; -import android.os.ServiceManager; -import android.os.ShellCallback; -import android.os.SystemClock; -import android.os.UserHandle; -import android.os.UserManager; -import android.os.storage.StorageManager; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.util.Log; -import android.util.Pair; - -import com.android.internal.content.PackageHelper; -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.SizedInputStream; - -import libcore.io.IoUtils; - -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.TimeUnit; - -public final class Pm { - private static final String TAG = "Pm"; - private static final String STDIN_PATH = "-"; - - IPackageManager mPm; - IPackageInstaller mInstaller; - IUserManager mUm; - IAccountManager mAm; - - private String[] mArgs; - private int mNextArg; - private String mCurArgData; - - private static final String PM_NOT_RUNNING_ERR = - "Error: Could not access the Package Manager. Is the system running?"; - - public static void main(String[] args) { - int exitCode = 1; - try { - exitCode = new Pm().run(args); - } catch (Exception e) { - Log.e(TAG, "Error", e); - System.err.println("Error: " + e); - if (e instanceof RemoteException) { - System.err.println(PM_NOT_RUNNING_ERR); - } - } - System.exit(exitCode); - } - - public int run(String[] args) throws RemoteException { - if (args.length < 1) { - return runShellCommand("package", mArgs); - } - mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE)); - mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE)); - mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); - - if (mPm == null) { - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - mInstaller = mPm.getPackageInstaller(); - - mArgs = args; - String op = args[0]; - mNextArg = 1; - - if ("install".equals(op)) { - return runInstall(); - } - - if ("install-create".equals(op)) { - return runInstallCreate(); - } - - if ("install-write".equals(op)) { - return runInstallWrite(); - } - - if ("install-commit".equals(op)) { - return runInstallCommit(); - } - - if ("install-abandon".equals(op) || "install-destroy".equals(op)) { - return runInstallAbandon(); - } - - return runShellCommand("package", mArgs); - } - - static final class MyShellCallback extends ShellCallback { - @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext, - String mode) { - File file = new File(path); - final ParcelFileDescriptor fd; - try { - fd = ParcelFileDescriptor.open(file, - ParcelFileDescriptor.MODE_CREATE | - ParcelFileDescriptor.MODE_TRUNCATE | - ParcelFileDescriptor.MODE_WRITE_ONLY); - } catch (FileNotFoundException e) { - String msg = "Unable to open file " + path + ": " + e; - System.err.println(msg); - throw new IllegalArgumentException(msg); - } - if (seLinuxContext != null) { - final String tcon = SELinux.getFileContext(file.getAbsolutePath()); - if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) { - try { - fd.close(); - } catch (IOException e) { - } - String msg = "System server has no access to file context " + tcon; - System.err.println(msg + " (from path " + file.getAbsolutePath() - + ", context " + seLinuxContext + ")"); - throw new IllegalArgumentException(msg); - } - } - return fd; - } - } - - private int runShellCommand(String serviceName, String[] args) { - final HandlerThread handlerThread = new HandlerThread("results"); - handlerThread.start(); - try { - ServiceManager.getService(serviceName).shellCommand( - FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, - args, new MyShellCallback(), - new ResultReceiver(new Handler(handlerThread.getLooper()))); - return 0; - } catch (RemoteException e) { - e.printStackTrace(); - } finally { - handlerThread.quitSafely(); - } - return -1; - } - - private static class LocalIntentReceiver { - private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>(); - - private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { - @Override - public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, - IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { - try { - mResult.offer(intent, 5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - }; - - public IntentSender getIntentSender() { - return new IntentSender((IIntentSender) mLocalSender); - } - - public Intent getResult() { - try { - return mResult.take(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - private int translateUserId(int userId, String logContext) { - return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), - userId, true, true, logContext, "pm command"); - } - - private static String checkAbiArgument(String abi) { - if (TextUtils.isEmpty(abi)) { - throw new IllegalArgumentException("Missing ABI argument"); - } - if ("-".equals(abi)) { - return abi; - } - final String[] supportedAbis = Build.SUPPORTED_ABIS; - for (String supportedAbi : supportedAbis) { - if (supportedAbi.equals(abi)) { - return abi; - } - } - throw new IllegalArgumentException("ABI " + abi + " not supported on this device"); - } - - /* - * Keep this around to support existing users of the "pm install" command that may not be - * able to be updated [or, at least informed the API has changed] such as ddmlib. - * - * Moving the implementation of "pm install" to "cmd package install" changes the executing - * context. Instead of being a stand alone process, "cmd package install" runs in the - * system_server process. Due to SELinux rules, system_server cannot access many directories; - * one of which being the package install staging directory [/data/local/tmp]. - * - * The use of "adb install" or "cmd package install" over "pm install" is highly encouraged. - */ - private int runInstall() throws RemoteException { - long startedTime = SystemClock.elapsedRealtime(); - final InstallParams params = makeInstallParams(); - final String inPath = nextArg(); - if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) { - File file = new File(inPath); - if (file.isFile()) { - try { - ApkLite baseApk = PackageParser.parseApkLite(file, 0); - PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null, - null, null); - params.sessionParams.setSize( - PackageHelper.calculateInstalledSize(pkgLite, - params.sessionParams.abiOverride)); - } catch (PackageParserException | IOException e) { - System.err.println("Error: Failed to parse APK file: " + e); - return 1; - } - } else { - System.err.println("Error: Can't open non-file: " + inPath); - return 1; - } - } - - final int sessionId = doCreateSession(params.sessionParams, - params.installerPackageName, params.userId); - - try { - if (inPath == null && params.sessionParams.sizeBytes == -1) { - System.err.println("Error: must either specify a package size or an APK file"); - return 1; - } - if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk", - false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) { - return 1; - } - Pair<String, Integer> status = doCommitSession(sessionId, false /*logSuccess*/); - if (status.second != PackageInstaller.STATUS_SUCCESS) { - return 1; - } - Log.i(TAG, "Package " + status.first + " installed in " + (SystemClock.elapsedRealtime() - - startedTime) + " ms"); - System.out.println("Success"); - return 0; - } finally { - try { - mInstaller.abandonSession(sessionId); - } catch (Exception ignore) { - } - } - } - - private int runInstallAbandon() throws RemoteException { - final int sessionId = Integer.parseInt(nextArg()); - return doAbandonSession(sessionId, true /*logSuccess*/); - } - - private int runInstallCommit() throws RemoteException { - final int sessionId = Integer.parseInt(nextArg()); - return doCommitSession(sessionId, true /*logSuccess*/).second; - } - - private int runInstallCreate() throws RemoteException { - final InstallParams installParams = makeInstallParams(); - final int sessionId = doCreateSession(installParams.sessionParams, - installParams.installerPackageName, installParams.userId); - - // NOTE: adb depends on parsing this string - System.out.println("Success: created install session [" + sessionId + "]"); - return PackageInstaller.STATUS_SUCCESS; - } - - private int runInstallWrite() throws RemoteException { - long sizeBytes = -1; - - String opt; - while ((opt = nextOption()) != null) { - if (opt.equals("-S")) { - sizeBytes = Long.parseLong(nextArg()); - } else { - throw new IllegalArgumentException("Unknown option: " + opt); - } - } - - final int sessionId = Integer.parseInt(nextArg()); - final String splitName = nextArg(); - final String path = nextArg(); - return doWriteSession(sessionId, path, sizeBytes, splitName, true /*logSuccess*/); - } - - private static class InstallParams { - SessionParams sessionParams; - String installerPackageName; - int userId = UserHandle.USER_ALL; - } - - private InstallParams makeInstallParams() { - final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL); - final InstallParams params = new InstallParams(); - params.sessionParams = sessionParams; - String opt; - while ((opt = nextOption()) != null) { - switch (opt) { - case "-l": - sessionParams.installFlags |= PackageManager.INSTALL_FORWARD_LOCK; - break; - case "-r": - sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; - break; - case "-i": - params.installerPackageName = nextArg(); - if (params.installerPackageName == null) { - throw new IllegalArgumentException("Missing installer package"); - } - break; - case "-t": - sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_TEST; - break; - case "-s": - sessionParams.installFlags |= PackageManager.INSTALL_EXTERNAL; - break; - case "-f": - sessionParams.installFlags |= PackageManager.INSTALL_INTERNAL; - break; - case "-d": - sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE; - break; - case "-g": - sessionParams.installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS; - break; - case "--dont-kill": - sessionParams.installFlags |= PackageManager.INSTALL_DONT_KILL_APP; - break; - case "--originating-uri": - sessionParams.originatingUri = Uri.parse(nextOptionData()); - break; - case "--referrer": - sessionParams.referrerUri = Uri.parse(nextOptionData()); - break; - case "-p": - sessionParams.mode = SessionParams.MODE_INHERIT_EXISTING; - sessionParams.appPackageName = nextOptionData(); - if (sessionParams.appPackageName == null) { - throw new IllegalArgumentException("Missing inherit package name"); - } - break; - case "--pkg": - sessionParams.appPackageName = nextOptionData(); - if (sessionParams.appPackageName == null) { - throw new IllegalArgumentException("Missing package name"); - } - break; - case "-S": - final long sizeBytes = Long.parseLong(nextOptionData()); - if (sizeBytes <= 0) { - throw new IllegalArgumentException("Size must be positive"); - } - sessionParams.setSize(sizeBytes); - break; - case "--abi": - sessionParams.abiOverride = checkAbiArgument(nextOptionData()); - break; - case "--ephemeral": - case "--instant": - sessionParams.setInstallAsInstantApp(true /*isInstantApp*/); - break; - case "--full": - sessionParams.setInstallAsInstantApp(false /*isInstantApp*/); - break; - case "--user": - params.userId = UserHandle.parseUserArg(nextOptionData()); - break; - case "--install-location": - sessionParams.installLocation = Integer.parseInt(nextOptionData()); - break; - case "--force-uuid": - sessionParams.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID; - sessionParams.volumeUuid = nextOptionData(); - if ("internal".equals(sessionParams.volumeUuid)) { - sessionParams.volumeUuid = null; - } - break; - case "--force-sdk": - sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK; - break; - default: - throw new IllegalArgumentException("Unknown option " + opt); - } - } - return params; - } - - private int doCreateSession(SessionParams params, String installerPackageName, int userId) - throws RemoteException { - userId = translateUserId(userId, "runInstallCreate"); - if (userId == UserHandle.USER_ALL) { - userId = UserHandle.USER_SYSTEM; - params.installFlags |= PackageManager.INSTALL_ALL_USERS; - } - - final int sessionId = mInstaller.createSession(params, installerPackageName, userId); - return sessionId; - } - - private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName, - boolean logSuccess) throws RemoteException { - if (STDIN_PATH.equals(inPath)) { - inPath = null; - } else if (inPath != null) { - final File file = new File(inPath); - if (file.isFile()) { - sizeBytes = file.length(); - } - } - - final SessionInfo info = mInstaller.getSessionInfo(sessionId); - - PackageInstaller.Session session = null; - InputStream in = null; - OutputStream out = null; - try { - session = new PackageInstaller.Session( - mInstaller.openSession(sessionId)); - - if (inPath != null) { - in = new FileInputStream(inPath); - } else { - in = new SizedInputStream(System.in, sizeBytes); - } - out = session.openWrite(splitName, 0, sizeBytes); - - int total = 0; - byte[] buffer = new byte[1024 * 1024]; - int c; - while ((c = in.read(buffer)) != -1) { - total += c; - out.write(buffer, 0, c); - - if (info.sizeBytes > 0) { - final float fraction = ((float) c / (float) info.sizeBytes); - session.addProgress(fraction); - } - } - session.fsync(out); - - if (logSuccess) { - System.out.println("Success: streamed " + total + " bytes"); - } - return PackageInstaller.STATUS_SUCCESS; - } catch (IOException e) { - System.err.println("Error: failed to write; " + e.getMessage()); - return PackageInstaller.STATUS_FAILURE; - } finally { - IoUtils.closeQuietly(out); - IoUtils.closeQuietly(in); - IoUtils.closeQuietly(session); - } - } - - private Pair<String, Integer> doCommitSession(int sessionId, boolean logSuccess) - throws RemoteException { - PackageInstaller.Session session = null; - try { - session = new PackageInstaller.Session( - mInstaller.openSession(sessionId)); - - final LocalIntentReceiver receiver = new LocalIntentReceiver(); - session.commit(receiver.getIntentSender()); - - final Intent result = receiver.getResult(); - final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); - if (status == PackageInstaller.STATUS_SUCCESS) { - if (logSuccess) { - System.out.println("Success"); - } - } else { - System.err.println("Failure [" - + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]"); - } - return new Pair<>(result.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME), status); - } finally { - IoUtils.closeQuietly(session); - } - } - - private int doAbandonSession(int sessionId, boolean logSuccess) throws RemoteException { - PackageInstaller.Session session = null; - try { - session = new PackageInstaller.Session(mInstaller.openSession(sessionId)); - session.abandon(); - if (logSuccess) { - System.out.println("Success"); - } - return PackageInstaller.STATUS_SUCCESS; - } finally { - IoUtils.closeQuietly(session); - } - } - - class LocalPackageInstallObserver extends PackageInstallObserver { - boolean finished; - int result; - String extraPermission; - String extraPackage; - - @Override - public void onPackageInstalled(String name, int status, String msg, Bundle extras) { - synchronized (this) { - finished = true; - result = status; - if (status == PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION) { - extraPermission = extras.getString( - PackageManager.EXTRA_FAILURE_EXISTING_PERMISSION); - extraPackage = extras.getString( - PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE); - } - notifyAll(); - } - } - } - - private static boolean isNumber(String s) { - try { - Integer.parseInt(s); - } catch (NumberFormatException nfe) { - return false; - } - return true; - } - - static class ClearCacheObserver extends IPackageDataObserver.Stub { - boolean finished; - boolean result; - - @Override - public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException { - synchronized (this) { - finished = true; - result = succeeded; - notifyAll(); - } - } - - } - - static class ClearDataObserver extends IPackageDataObserver.Stub { - boolean finished; - boolean result; - - @Override - public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException { - synchronized (this) { - finished = true; - result = succeeded; - notifyAll(); - } - } - } - - /** - * Displays the package file for a package. - * @param pckg - */ - private int displayPackageFilePath(String pckg, int userId) { - try { - PackageInfo info = mPm.getPackageInfo(pckg, 0, userId); - if (info != null && info.applicationInfo != null) { - System.out.print("package:"); - System.out.println(info.applicationInfo.sourceDir); - if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) { - for (String splitSourceDir : info.applicationInfo.splitSourceDirs) { - System.out.print("package:"); - System.out.println(splitSourceDir); - } - } - return 0; - } - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - } - return 1; - } - - private String nextOption() { - if (mNextArg >= mArgs.length) { - return null; - } - String arg = mArgs[mNextArg]; - if (!arg.startsWith("-")) { - return null; - } - mNextArg++; - if (arg.equals("--")) { - return null; - } - if (arg.length() > 1 && arg.charAt(1) != '-') { - if (arg.length() > 2) { - mCurArgData = arg.substring(2); - return arg.substring(0, 2); - } else { - mCurArgData = null; - return arg; - } - } - mCurArgData = null; - return arg; - } - - private String nextOptionData() { - if (mCurArgData != null) { - return mCurArgData; - } - if (mNextArg >= mArgs.length) { - return null; - } - String data = mArgs[mNextArg]; - mNextArg++; - return data; - } - - private String nextArg() { - if (mNextArg >= mArgs.length) { - return null; - } - String arg = mArgs[mNextArg]; - mNextArg++; - return arg; - } - - private static int showUsage() { - System.err.println("usage: pm path [--user USER_ID] PACKAGE"); - System.err.println(" pm dump PACKAGE"); - System.err.println(" pm install [-lrtsfdg] [-i PACKAGE] [--user USER_ID]"); - System.err.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]"); - System.err.println(" [--originating-uri URI] [---referrer URI]"); - System.err.println(" [--abi ABI_NAME] [--force-sdk]"); - System.err.println(" [--preload] [--instantapp] [--full] [--dont-kill]"); - System.err.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]"); - System.err.println(" pm install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID]"); - System.err.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]"); - System.err.println(" [--originating-uri URI] [---referrer URI]"); - System.err.println(" [--abi ABI_NAME] [--force-sdk]"); - System.err.println(" [--preload] [--instantapp] [--full] [--dont-kill]"); - System.err.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]"); - System.err.println(" pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]"); - System.err.println(" pm install-commit SESSION_ID"); - System.err.println(" pm install-abandon SESSION_ID"); - System.err.println(" pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE"); - System.err.println(" pm set-installer PACKAGE INSTALLER"); - System.err.println(" pm move-package PACKAGE [internal|UUID]"); - System.err.println(" pm move-primary-storage [internal|UUID]"); - System.err.println(" pm clear [--user USER_ID] PACKAGE"); - System.err.println(" pm enable [--user USER_ID] PACKAGE_OR_COMPONENT"); - System.err.println(" pm disable [--user USER_ID] PACKAGE_OR_COMPONENT"); - System.err.println(" pm disable-user [--user USER_ID] PACKAGE_OR_COMPONENT"); - System.err.println(" pm disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT"); - System.err.println(" pm default-state [--user USER_ID] PACKAGE_OR_COMPONENT"); - System.err.println(" pm set-user-restriction [--user USER_ID] RESTRICTION VALUE"); - System.err.println(" pm hide [--user USER_ID] PACKAGE_OR_COMPONENT"); - System.err.println(" pm unhide [--user USER_ID] PACKAGE_OR_COMPONENT"); - System.err.println(" pm grant [--user USER_ID] PACKAGE PERMISSION"); - System.err.println(" pm revoke [--user USER_ID] PACKAGE PERMISSION"); - System.err.println(" pm reset-permissions"); - System.err.println(" pm set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}"); - System.err.println(" pm get-app-link [--user USER_ID] PACKAGE"); - System.err.println(" pm set-install-location [0/auto] [1/internal] [2/external]"); - System.err.println(" pm get-install-location"); - System.err.println(" pm set-permission-enforced PERMISSION [true|false]"); - System.err.println(" pm trim-caches DESIRED_FREE_SPACE [internal|UUID]"); - System.err.println(" pm create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral] [--guest] USER_NAME"); - System.err.println(" pm remove-user USER_ID"); - System.err.println(" pm get-max-users"); - System.err.println(""); - System.err.println("NOTE: 'pm list' commands have moved! Run 'adb shell cmd package'"); - System.err.println(" to display the new commands."); - System.err.println(""); - System.err.println("pm path: print the path to the .apk of the given PACKAGE."); - System.err.println(""); - System.err.println("pm dump: print system state associated with the given PACKAGE."); - System.err.println(""); - System.err.println("pm install: install a single legacy package"); - System.err.println("pm install-create: create an install session"); - System.err.println(" -l: forward lock application"); - System.err.println(" -r: allow replacement of existing application"); - System.err.println(" -t: allow test packages"); - System.err.println(" -i: specify package name of installer owning the app"); - System.err.println(" -s: install application on sdcard"); - System.err.println(" -f: install application on internal flash"); - System.err.println(" -d: allow version code downgrade (debuggable packages only)"); - System.err.println(" -p: partial application install (new split on top of existing pkg)"); - System.err.println(" -g: grant all runtime permissions"); - System.err.println(" -S: size in bytes of entire session"); - System.err.println(" --dont-kill: installing a new feature split, don't kill running app"); - System.err.println(" --originating-uri: set URI where app was downloaded from"); - System.err.println(" --referrer: set URI that instigated the install of the app"); - System.err.println(" --pkg: specify expected package name of app being installed"); - System.err.println(" --abi: override the default ABI of the platform"); - System.err.println(" --instantapp: cause the app to be installed as an ephemeral install app"); - System.err.println(" --full: cause the app to be installed as a non-ephemeral full app"); - System.err.println(" --install-location: force the install location:"); - System.err.println(" 0=auto, 1=internal only, 2=prefer external"); - System.err.println(" --force-uuid: force install on to disk volume with given UUID"); - System.err.println(" --force-sdk: allow install even when existing app targets platform"); - System.err.println(" codename but new one targets a final API level"); - System.err.println(""); - System.err.println("pm install-write: write a package into existing session; path may"); - System.err.println(" be '-' to read from stdin"); - System.err.println(" -S: size in bytes of package, required for stdin"); - System.err.println(""); - System.err.println("pm install-commit: perform install of fully staged session"); - System.err.println("pm install-abandon: abandon session"); - System.err.println(""); - System.err.println("pm set-installer: set installer package name"); - System.err.println(""); - System.err.println("pm uninstall: removes a package from the system. Options:"); - System.err.println(" -k: keep the data and cache directories around after package removal."); - System.err.println(""); - System.err.println("pm clear: deletes all data associated with a package."); - System.err.println(""); - System.err.println("pm enable, disable, disable-user, disable-until-used, default-state:"); - System.err.println(" these commands change the enabled state of a given package or"); - System.err.println(" component (written as \"package/class\")."); - System.err.println(""); - System.err.println("pm grant, revoke: these commands either grant or revoke permissions"); - System.err.println(" to apps. The permissions must be declared as used in the app's"); - System.err.println(" manifest, be runtime permissions (protection level dangerous),"); - System.err.println(" and the app targeting SDK greater than Lollipop MR1."); - System.err.println(""); - System.err.println("pm reset-permissions: revert all runtime permissions to their default state."); - System.err.println(""); - System.err.println("pm get-install-location: returns the current install location."); - System.err.println(" 0 [auto]: Let system decide the best location"); - System.err.println(" 1 [internal]: Install on internal device storage"); - System.err.println(" 2 [external]: Install on external media"); - System.err.println(""); - System.err.println("pm set-install-location: changes the default install location."); - System.err.println(" NOTE: this is only intended for debugging; using this can cause"); - System.err.println(" applications to break and other undersireable behavior."); - System.err.println(" 0 [auto]: Let system decide the best location"); - System.err.println(" 1 [internal]: Install on internal device storage"); - System.err.println(" 2 [external]: Install on external media"); - System.err.println(""); - System.err.println("pm trim-caches: trim cache files to reach the given free space."); - System.err.println(""); - System.err.println("pm create-user: create a new user with the given USER_NAME,"); - System.err.println(" printing the new user identifier of the user."); - System.err.println(""); - System.err.println("pm remove-user: remove the user with the given USER_IDENTIFIER,"); - System.err.println(" deleting all data associated with that user"); - System.err.println(""); - return 1; - } -} diff --git a/com/android/datetimepicker/AccessibleLinearLayout.java b/com/android/datetimepicker/AccessibleLinearLayout.java index 629f8564..a67d8095 100644 --- a/com/android/datetimepicker/AccessibleLinearLayout.java +++ b/com/android/datetimepicker/AccessibleLinearLayout.java @@ -25,7 +25,10 @@ import android.widget.LinearLayout; /** * Fake Button class, used so TextViews can announce themselves as Buttons, for accessibility. + * + * @deprecated This module is deprecated. Do not use this class. */ +@Deprecated public class AccessibleLinearLayout extends LinearLayout { public AccessibleLinearLayout(Context context, AttributeSet attrs) { diff --git a/com/android/datetimepicker/AccessibleTextView.java b/com/android/datetimepicker/AccessibleTextView.java index 98fa7442..9a2ecbb2 100644 --- a/com/android/datetimepicker/AccessibleTextView.java +++ b/com/android/datetimepicker/AccessibleTextView.java @@ -25,7 +25,10 @@ import android.widget.TextView; /** * Fake Button class, used so TextViews can announce themselves as Buttons, for accessibility. + * + * @deprecated This module is deprecated. Do not use this class. */ +@Deprecated public class AccessibleTextView extends TextView { public AccessibleTextView(Context context, AttributeSet attrs) { diff --git a/com/android/datetimepicker/HapticFeedbackController.java b/com/android/datetimepicker/HapticFeedbackController.java index b9be63f2..96d3a24c 100644 --- a/com/android/datetimepicker/HapticFeedbackController.java +++ b/com/android/datetimepicker/HapticFeedbackController.java @@ -10,7 +10,10 @@ import android.provider.Settings; /** * A simple utility class to handle haptic feedback. + * + * @deprecated This module is deprecated. Do not use this class. */ +@Deprecated public class HapticFeedbackController { private static final int VIBRATE_DELAY_MS = 125; private static final int VIBRATE_LENGTH_MS = 5; diff --git a/com/android/datetimepicker/Utils.java b/com/android/datetimepicker/Utils.java index 4a3110c4..6c0adbef 100644 --- a/com/android/datetimepicker/Utils.java +++ b/com/android/datetimepicker/Utils.java @@ -28,7 +28,10 @@ import java.util.Calendar; /** * Utility helper functions for time and date pickers. + * + * @deprecated This module is deprecated. Do not use this class. */ +@Deprecated public class Utils { public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3; diff --git a/com/android/datetimepicker/date/AccessibleDateAnimator.java b/com/android/datetimepicker/date/AccessibleDateAnimator.java index fc022cda..4e65aead 100644 --- a/com/android/datetimepicker/date/AccessibleDateAnimator.java +++ b/com/android/datetimepicker/date/AccessibleDateAnimator.java @@ -22,7 +22,7 @@ import android.util.AttributeSet; import android.view.accessibility.AccessibilityEvent; import android.widget.ViewAnimator; -public class AccessibleDateAnimator extends ViewAnimator { +class AccessibleDateAnimator extends ViewAnimator { private long mDateMillis; public AccessibleDateAnimator(Context context, AttributeSet attrs) { diff --git a/com/android/datetimepicker/date/DatePickerController.java b/com/android/datetimepicker/date/DatePickerController.java index 42989ddc..b027d338 100644 --- a/com/android/datetimepicker/date/DatePickerController.java +++ b/com/android/datetimepicker/date/DatePickerController.java @@ -24,7 +24,7 @@ import java.util.Calendar; /** * Controller class to communicate among the various components of the date picker dialog. */ -public interface DatePickerController { +interface DatePickerController { void onYearSelected(int year); diff --git a/com/android/datetimepicker/date/DatePickerDialog.java b/com/android/datetimepicker/date/DatePickerDialog.java index 994fdf2e..9b26b48a 100644 --- a/com/android/datetimepicker/date/DatePickerDialog.java +++ b/com/android/datetimepicker/date/DatePickerDialog.java @@ -48,7 +48,10 @@ import java.util.Locale; /** * Dialog allowing users to select a date. + * + * @deprecated Use {@link android.app.DatePickerDialog}. */ +@Deprecated public class DatePickerDialog extends DialogFragment implements OnClickListener, DatePickerController { diff --git a/com/android/datetimepicker/date/DayPickerView.java b/com/android/datetimepicker/date/DayPickerView.java index 47a2aa72..9f815376 100644 --- a/com/android/datetimepicker/date/DayPickerView.java +++ b/com/android/datetimepicker/date/DayPickerView.java @@ -42,7 +42,7 @@ import java.util.Locale; /** * This displays a list of months in a calendar format with selectable days. */ -public abstract class DayPickerView extends ListView implements OnScrollListener, +abstract class DayPickerView extends ListView implements OnScrollListener, OnDateChangedListener { private static final String TAG = "MonthFragment"; diff --git a/com/android/datetimepicker/date/MonthAdapter.java b/com/android/datetimepicker/date/MonthAdapter.java index 3ed88b02..0f95e555 100644 --- a/com/android/datetimepicker/date/MonthAdapter.java +++ b/com/android/datetimepicker/date/MonthAdapter.java @@ -32,7 +32,7 @@ import java.util.HashMap; /** * An adapter for a list of {@link MonthView} items. */ -public abstract class MonthAdapter extends BaseAdapter implements OnDayClickListener { +abstract class MonthAdapter extends BaseAdapter implements OnDayClickListener { private static final String TAG = "SimpleMonthAdapter"; diff --git a/com/android/datetimepicker/date/MonthView.java b/com/android/datetimepicker/date/MonthView.java index 00711f3e..63d073c6 100644 --- a/com/android/datetimepicker/date/MonthView.java +++ b/com/android/datetimepicker/date/MonthView.java @@ -52,7 +52,7 @@ import java.util.Locale; * A calendar-like view displaying a specified month and the appropriate selectable day numbers * within the specified month. */ -public abstract class MonthView extends View { +abstract class MonthView extends View { private static final String TAG = "MonthView"; /** diff --git a/com/android/datetimepicker/date/SimpleDayPickerView.java b/com/android/datetimepicker/date/SimpleDayPickerView.java index 658c8a28..c20c754c 100644 --- a/com/android/datetimepicker/date/SimpleDayPickerView.java +++ b/com/android/datetimepicker/date/SimpleDayPickerView.java @@ -22,7 +22,7 @@ import android.util.AttributeSet; /** * A DayPickerView customized for {@link SimpleMonthAdapter} */ -public class SimpleDayPickerView extends DayPickerView { +class SimpleDayPickerView extends DayPickerView { public SimpleDayPickerView(Context context, AttributeSet attrs) { super(context, attrs); diff --git a/com/android/datetimepicker/date/SimpleMonthAdapter.java b/com/android/datetimepicker/date/SimpleMonthAdapter.java index 0c939fec..394abc20 100644 --- a/com/android/datetimepicker/date/SimpleMonthAdapter.java +++ b/com/android/datetimepicker/date/SimpleMonthAdapter.java @@ -21,7 +21,7 @@ import android.content.Context; /** * An adapter for a list of {@link SimpleMonthView} items. */ -public class SimpleMonthAdapter extends MonthAdapter { +class SimpleMonthAdapter extends MonthAdapter { public SimpleMonthAdapter(Context context, DatePickerController controller) { super(context, controller); diff --git a/com/android/datetimepicker/date/SimpleMonthView.java b/com/android/datetimepicker/date/SimpleMonthView.java index b416a45f..bb392eb9 100644 --- a/com/android/datetimepicker/date/SimpleMonthView.java +++ b/com/android/datetimepicker/date/SimpleMonthView.java @@ -21,7 +21,7 @@ import android.graphics.Canvas; import java.util.Calendar; -public class SimpleMonthView extends MonthView { +class SimpleMonthView extends MonthView { public SimpleMonthView(Context context) { super(context); diff --git a/com/android/datetimepicker/date/TextViewWithCircularIndicator.java b/com/android/datetimepicker/date/TextViewWithCircularIndicator.java index ad78746a..e4d69e14 100644 --- a/com/android/datetimepicker/date/TextViewWithCircularIndicator.java +++ b/com/android/datetimepicker/date/TextViewWithCircularIndicator.java @@ -29,7 +29,10 @@ import com.android.datetimepicker.R; /** * A text view which, when pressed or activated, displays a blue circle around the text. + * + * @deprecated This module is deprecated. Do not use this class. */ +@Deprecated public class TextViewWithCircularIndicator extends TextView { private static final int SELECTED_CIRCLE_ALPHA = 60; diff --git a/com/android/datetimepicker/date/YearPickerView.java b/com/android/datetimepicker/date/YearPickerView.java index ae14eb50..d058b36a 100644 --- a/com/android/datetimepicker/date/YearPickerView.java +++ b/com/android/datetimepicker/date/YearPickerView.java @@ -37,7 +37,7 @@ import java.util.List; /** * Displays a selectable list of years. */ -public class YearPickerView extends ListView implements OnItemClickListener, OnDateChangedListener { +class YearPickerView extends ListView implements OnItemClickListener, OnDateChangedListener { private static final String TAG = "YearPickerView"; private final DatePickerController mController; diff --git a/com/android/datetimepicker/time/AmPmCirclesView.java b/com/android/datetimepicker/time/AmPmCirclesView.java index 902abd95..bafa51c5 100644 --- a/com/android/datetimepicker/time/AmPmCirclesView.java +++ b/com/android/datetimepicker/time/AmPmCirclesView.java @@ -33,7 +33,7 @@ import java.text.DateFormatSymbols; /** * Draw the two smaller AM and PM circles next to where the larger circle will be. */ -public class AmPmCirclesView extends View { +class AmPmCirclesView extends View { private static final String TAG = "AmPmCirclesView"; // Alpha level for selected circle. diff --git a/com/android/datetimepicker/time/CircleView.java b/com/android/datetimepicker/time/CircleView.java index 1dd4eeab..0d6766fb 100644 --- a/com/android/datetimepicker/time/CircleView.java +++ b/com/android/datetimepicker/time/CircleView.java @@ -28,7 +28,7 @@ import com.android.datetimepicker.R; /** * Draws a simple white circle on which the numbers will be drawn. */ -public class CircleView extends View { +class CircleView extends View { private static final String TAG = "CircleView"; private final Paint mPaint = new Paint(); diff --git a/com/android/datetimepicker/time/RadialPickerLayout.java b/com/android/datetimepicker/time/RadialPickerLayout.java index 1d449077..6a4adeac 100644 --- a/com/android/datetimepicker/time/RadialPickerLayout.java +++ b/com/android/datetimepicker/time/RadialPickerLayout.java @@ -44,6 +44,8 @@ import com.android.datetimepicker.R; * The primary layout to hold the circular picker, and the am/pm buttons. This view well measure * itself to end up as a square. It also handles touches to be passed in to views that need to know * when they'd been touched. + * + * @deprecated This module is deprecated. Do not use this class. */ public class RadialPickerLayout extends FrameLayout implements OnTouchListener { private static final String TAG = "RadialPickerLayout"; diff --git a/com/android/datetimepicker/time/RadialSelectorView.java b/com/android/datetimepicker/time/RadialSelectorView.java index 0339dcd2..49069914 100644 --- a/com/android/datetimepicker/time/RadialSelectorView.java +++ b/com/android/datetimepicker/time/RadialSelectorView.java @@ -35,7 +35,7 @@ import com.android.datetimepicker.Utils; * View to show what number is selected. This will draw a blue circle over the number, with a blue * line coming from the center of the main circle to the edge of the blue selection. */ -public class RadialSelectorView extends View { +class RadialSelectorView extends View { private static final String TAG = "RadialSelectorView"; // Alpha level for selected circle. diff --git a/com/android/datetimepicker/time/RadialTextsView.java b/com/android/datetimepicker/time/RadialTextsView.java index 684e8f50..0b69d062 100644 --- a/com/android/datetimepicker/time/RadialTextsView.java +++ b/com/android/datetimepicker/time/RadialTextsView.java @@ -35,7 +35,7 @@ import com.android.datetimepicker.R; /** * A view to show a series of numbers in a circular pattern. */ -public class RadialTextsView extends View { +class RadialTextsView extends View { private final static String TAG = "RadialTextsView"; private final Paint mPaint = new Paint(); diff --git a/com/android/datetimepicker/time/TimePickerDialog.java b/com/android/datetimepicker/time/TimePickerDialog.java index c7661ad5..0dd13c9c 100644 --- a/com/android/datetimepicker/time/TimePickerDialog.java +++ b/com/android/datetimepicker/time/TimePickerDialog.java @@ -46,7 +46,10 @@ import java.util.Locale; /** * Dialog to set a time. + * + * @deprecated Use {@link android.app.TimePickerDialog}. */ +@Deprecated public class TimePickerDialog extends DialogFragment implements OnValueSelectedListener{ private static final String TAG = "TimePickerDialog"; diff --git a/com/android/ex/photo/ActionBarWrapper.java b/com/android/ex/photo/ActionBarWrapper.java index ae621979..6d4d4d20 100644 --- a/com/android/ex/photo/ActionBarWrapper.java +++ b/com/android/ex/photo/ActionBarWrapper.java @@ -1,8 +1,7 @@ package com.android.ex.photo; - +import android.app.ActionBar; import android.graphics.drawable.Drawable; -import android.support.v7.app.ActionBar; /** * Wrapper around {@link ActionBar}. diff --git a/com/android/ex/photo/PhotoViewActivity.java b/com/android/ex/photo/PhotoViewActivity.java index a5c4a438..7b53918f 100644 --- a/com/android/ex/photo/PhotoViewActivity.java +++ b/com/android/ex/photo/PhotoViewActivity.java @@ -21,14 +21,14 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; +import android.support.v4.app.FragmentActivity; import android.view.Menu; import android.view.MenuItem; /** * Activity to view the contents of an album. */ -public class PhotoViewActivity extends AppCompatActivity +public class PhotoViewActivity extends FragmentActivity implements PhotoViewController.ActivityInterface { private PhotoViewController mController; @@ -41,7 +41,7 @@ public class PhotoViewActivity extends AppCompatActivity mController.onCreate(savedInstanceState); } - protected PhotoViewController createController() { + public PhotoViewController createController() { return new PhotoViewController(this); } @@ -122,7 +122,7 @@ public class PhotoViewActivity extends AppCompatActivity @Override public ActionBarInterface getActionBarInterface() { if (mActionBar == null) { - mActionBar = new ActionBarWrapper(getSupportActionBar()); + mActionBar = new ActionBarWrapper(getActionBar()); } return mActionBar; } diff --git a/com/android/ims/ImsManager.java b/com/android/ims/ImsManager.java index 813118ba..e0a966a7 100644 --- a/com/android/ims/ImsManager.java +++ b/com/android/ims/ImsManager.java @@ -43,6 +43,7 @@ import android.util.Log; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsConfig; import com.android.ims.internal.IImsEcbm; +import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsMultiEndpoint; import com.android.ims.internal.IImsRegistrationListener; import com.android.ims.internal.IImsServiceController; @@ -281,22 +282,28 @@ public class ImsManager { } /** - * Returns the user configuration of Enhanced 4G LTE Mode setting for slot. If not set, it - * returns true as default value. + * Returns the user configuration of Enhanced 4G LTE Mode setting for slot. If the option is + * not editable ({@link CarrierConfigManager#KEY_EDITABLE_ENHANCED_4G_LTE_BOOL} is false), or + * the setting is not initialized, this method will return default value specified by + * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}. + * + * Note that even if the setting was set, it may no longer be editable. If this is the case we + * return the default value. */ public boolean isEnhanced4gLteModeSettingEnabledByUser() { - // If user can't edit Enhanced 4G LTE Mode, it assumes Enhanced 4G LTE Mode is always true. - // If user changes SIM from editable mode to uneditable mode, need to return true. - if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)) { - return true; - } - int setting = SubscriptionManager.getIntegerSubscriptionProperty( getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED, SUB_PROPERTY_NOT_INITIALIZED, mContext); + boolean onByDefault = getBooleanCarrierConfig( + CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL); - // If it's never set, by default we return true. - return (setting == SUB_PROPERTY_NOT_INITIALIZED || setting == 1); + // If Enhanced 4G LTE Mode is uneditable or not initialized, we use the default value + if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL) + || setting == SUB_PROPERTY_NOT_INITIALIZED) { + return onByDefault; + } else { + return (setting == ImsConfig.FeatureValueConstants.ON); + } } /** @@ -315,21 +322,26 @@ public class ImsManager { } /** - * Change persistent Enhanced 4G LTE Mode setting. If the the option is not editable + * Change persistent Enhanced 4G LTE Mode setting. If the option is not editable * ({@link CarrierConfigManager#KEY_EDITABLE_ENHANCED_4G_LTE_BOOL} is false), this method will - * always set the setting to true. + * set the setting to the default value specified by + * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}. * */ public void setEnhanced4gLteModeSetting(boolean enabled) { - // If false, we must always keep advanced 4G mode set to true. - enabled = getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL) - ? enabled : true; + // If editable=false, we must keep default advanced 4G mode. + if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)) { + enabled = getBooleanCarrierConfig( + CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL); + } int prevSetting = SubscriptionManager.getIntegerSubscriptionProperty( getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED, SUB_PROPERTY_NOT_INITIALIZED, mContext); - if (prevSetting != (enabled ? 1 : 0)) { + if (prevSetting != (enabled ? + ImsConfig.FeatureValueConstants.ON : + ImsConfig.FeatureValueConstants.OFF)) { SubscriptionManager.setSubscriptionProperty(getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED, booleanToPropertyString(enabled)); if (isNonTtyOrTtyOnVolteEnabled()) { @@ -566,7 +578,8 @@ public class ImsManager { SUB_PROPERTY_NOT_INITIALIZED, mContext); // If it's never set, by default we return true. - return (setting == SUB_PROPERTY_NOT_INITIALIZED || setting == 1); + return (setting == SUB_PROPERTY_NOT_INITIALIZED + || setting == ImsConfig.FeatureValueConstants.ON); } /** @@ -672,7 +685,7 @@ public class ImsManager { return getBooleanCarrierConfig( CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL); } else { - return setting == 1; + return setting == ImsConfig.FeatureValueConstants.ON; } } @@ -957,7 +970,7 @@ public class ImsManager { return getBooleanCarrierConfig( CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL); } else { - return (setting == 1); + return setting == ImsConfig.FeatureValueConstants.ON; } } @@ -1982,8 +1995,8 @@ public class ImsManager { serviceProxy.setStatusCallback(() -> mStatusCallbacks.forEach( ImsServiceProxy.INotifyStatusChanged::notifyStatusChanged)); // Returns null if the service is not available. - IImsServiceController b = tm.getImsServiceControllerAndListen(mPhoneId, - ImsFeature.MMTEL, serviceProxy.getListener()); + IImsMMTelFeature b = tm.getImsMMTelFeatureAndListen(mPhoneId, + serviceProxy.getListener()); if (b != null) { serviceProxy.setBinder(b.asBinder()); // Trigger the cache to be updated for feature status. @@ -2405,7 +2418,9 @@ public class ImsManager { public void factoryReset() { // Set VoLTE to default SubscriptionManager.setSubscriptionProperty(getSubId(), - SubscriptionManager.ENHANCED_4G_MODE_ENABLED, booleanToPropertyString(true)); + SubscriptionManager.ENHANCED_4G_MODE_ENABLED, + booleanToPropertyString(getBooleanCarrierConfig( + CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL))); // Set VoWiFi to default SubscriptionManager.setSubscriptionProperty(getSubId(), diff --git a/com/android/ims/ImsServiceProxy.java b/com/android/ims/ImsServiceProxy.java index 8c51202f..f3489194 100644 --- a/com/android/ims/ImsServiceProxy.java +++ b/com/android/ims/ImsServiceProxy.java @@ -27,10 +27,10 @@ import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsCallSessionListener; import com.android.ims.internal.IImsConfig; import com.android.ims.internal.IImsEcbm; +import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsMultiEndpoint; import com.android.ims.internal.IImsRegistrationListener; -import com.android.ims.internal.IImsServiceController; -import com.android.ims.internal.IImsServiceFeatureListener; +import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.ims.internal.IImsUt; /** @@ -57,8 +57,8 @@ public class ImsServiceProxy { void notifyStatusChanged(); } - private final IImsServiceFeatureListener mListenerBinder = - new IImsServiceFeatureListener.Stub() { + private final IImsServiceFeatureCallback mListenerBinder = + new IImsServiceFeatureCallback.Stub() { @Override public void imsFeatureCreated(int slotId, int feature) throws RemoteException { @@ -108,7 +108,7 @@ public class ImsServiceProxy { this(slotId, null, featureType); } - public IImsServiceFeatureListener getListener() { + public IImsServiceFeatureCallback getListener() { return mListenerBinder; } @@ -120,8 +120,7 @@ public class ImsServiceProxy { throws RemoteException { synchronized (mLock) { checkServiceIsReady(); - return getServiceInterface(mBinder).startSession(mSlotId, mSupportedFeature, - incomingCallIntent, listener); + return getServiceInterface(mBinder).startSession(incomingCallIntent, listener); } } @@ -130,7 +129,7 @@ public class ImsServiceProxy { // Only check to make sure the binder connection still exists. This method should // still be able to be called when the state is STATE_NOT_AVAILABLE. checkBinderConnection(); - getServiceInterface(mBinder).endSession(mSlotId, mSupportedFeature, sessionId); + getServiceInterface(mBinder).endSession(sessionId); } } @@ -138,15 +137,14 @@ public class ImsServiceProxy { throws RemoteException { synchronized (mLock) { checkServiceIsReady(); - return getServiceInterface(mBinder).isConnected(mSlotId, mSupportedFeature, - callServiceType, callType); + return getServiceInterface(mBinder).isConnected(callServiceType, callType); } } public boolean isOpened() throws RemoteException { synchronized (mLock) { checkServiceIsReady(); - return getServiceInterface(mBinder).isOpened(mSlotId, mSupportedFeature); + return getServiceInterface(mBinder).isOpened(); } } @@ -154,8 +152,7 @@ public class ImsServiceProxy { throws RemoteException { synchronized (mLock) { checkServiceIsReady(); - getServiceInterface(mBinder).addRegistrationListener(mSlotId, mSupportedFeature, - listener); + getServiceInterface(mBinder).addRegistrationListener(listener); } } @@ -163,8 +160,7 @@ public class ImsServiceProxy { throws RemoteException { synchronized (mLock) { checkServiceIsReady(); - getServiceInterface(mBinder).removeRegistrationListener(mSlotId, mSupportedFeature, - listener); + getServiceInterface(mBinder).removeRegistrationListener(listener); } } @@ -172,8 +168,8 @@ public class ImsServiceProxy { throws RemoteException { synchronized (mLock) { checkServiceIsReady(); - return getServiceInterface(mBinder).createCallProfile(mSlotId, mSupportedFeature, - sessionId, callServiceType, callType); + return getServiceInterface(mBinder).createCallProfile(sessionId, callServiceType, + callType); } } @@ -181,8 +177,7 @@ public class ImsServiceProxy { IImsCallSessionListener listener) throws RemoteException { synchronized (mLock) { checkServiceIsReady(); - return getServiceInterface(mBinder).createCallSession(mSlotId, mSupportedFeature, - sessionId, profile, listener); + return getServiceInterface(mBinder).createCallSession(sessionId, profile, listener); } } @@ -190,43 +185,42 @@ public class ImsServiceProxy { throws RemoteException { synchronized (mLock) { checkServiceIsReady(); - return getServiceInterface(mBinder).getPendingCallSession(mSlotId, mSupportedFeature, - sessionId, callId); + return getServiceInterface(mBinder).getPendingCallSession(sessionId, callId); } } public IImsUt getUtInterface() throws RemoteException { synchronized (mLock) { checkServiceIsReady(); - return getServiceInterface(mBinder).getUtInterface(mSlotId, mSupportedFeature); + return getServiceInterface(mBinder).getUtInterface(); } } public IImsConfig getConfigInterface() throws RemoteException { synchronized (mLock) { checkServiceIsReady(); - return getServiceInterface(mBinder).getConfigInterface(mSlotId, mSupportedFeature); + return getServiceInterface(mBinder).getConfigInterface(); } } public void turnOnIms() throws RemoteException { synchronized (mLock) { checkServiceIsReady(); - getServiceInterface(mBinder).turnOnIms(mSlotId, mSupportedFeature); + getServiceInterface(mBinder).turnOnIms(); } } public void turnOffIms() throws RemoteException { synchronized (mLock) { checkServiceIsReady(); - getServiceInterface(mBinder).turnOffIms(mSlotId, mSupportedFeature); + getServiceInterface(mBinder).turnOffIms(); } } public IImsEcbm getEcbmInterface() throws RemoteException { synchronized (mLock) { checkServiceIsReady(); - return getServiceInterface(mBinder).getEcbmInterface(mSlotId, mSupportedFeature); + return getServiceInterface(mBinder).getEcbmInterface(); } } @@ -234,16 +228,14 @@ public class ImsServiceProxy { throws RemoteException { synchronized (mLock) { checkServiceIsReady(); - getServiceInterface(mBinder).setUiTTYMode(mSlotId, mSupportedFeature, uiTtyMode, - onComplete); + getServiceInterface(mBinder).setUiTTYMode(uiTtyMode, onComplete); } } public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException { synchronized (mLock) { checkServiceIsReady(); - return getServiceInterface(mBinder).getMultiEndpointInterface(mSlotId, - mSupportedFeature); + return getServiceInterface(mBinder).getMultiEndpointInterface(); } } @@ -277,7 +269,7 @@ public class ImsServiceProxy { private Integer retrieveFeatureStatus() { if (mBinder != null) { try { - return getServiceInterface(mBinder).getFeatureStatus(mSlotId, mSupportedFeature); + return getServiceInterface(mBinder).getFeatureStatus(); } catch (RemoteException e) { // Status check failed, don't update cache } @@ -318,8 +310,8 @@ public class ImsServiceProxy { } } - private IImsServiceController getServiceInterface(IBinder b) { - return IImsServiceController.Stub.asInterface(b); + private IImsMMTelFeature getServiceInterface(IBinder b) { + return IImsMMTelFeature.Stub.asInterface(b); } protected void checkBinderConnection() throws RemoteException { diff --git a/com/android/internal/app/procstats/ProcessState.java b/com/android/internal/app/procstats/ProcessState.java index 7519fce4..fbdf17d8 100644 --- a/com/android/internal/app/procstats/ProcessState.java +++ b/com/android/internal/app/procstats/ProcessState.java @@ -102,6 +102,7 @@ public final class ProcessState { STATE_LAST_ACTIVITY, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY STATE_CACHED_ACTIVITY_CLIENT, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_RECENT STATE_CACHED_EMPTY, // ActivityManager.PROCESS_STATE_CACHED_EMPTY }; diff --git a/com/android/internal/content/NativeLibraryHelper.java b/com/android/internal/content/NativeLibraryHelper.java index 83b7d2f9..a1e6fd8e 100644 --- a/com/android/internal/content/NativeLibraryHelper.java +++ b/com/android/internal/content/NativeLibraryHelper.java @@ -43,6 +43,7 @@ import dalvik.system.VMRuntime; import java.io.Closeable; import java.io.File; +import java.io.FileDescriptor; import java.io.IOException; import java.util.List; @@ -118,6 +119,17 @@ public class NativeLibraryHelper { return new Handle(apkHandles, multiArch, extractNativeLibs, debuggable); } + public static Handle createFd(PackageLite lite, FileDescriptor fd) throws IOException { + final long[] apkHandles = new long[1]; + final String path = lite.baseCodePath; + apkHandles[0] = nativeOpenApkFd(fd, path); + if (apkHandles[0] == 0) { + throw new IOException("Unable to open APK " + path + " from fd " + fd); + } + + return new Handle(apkHandles, lite.multiArch, lite.extractNativeLibs, lite.debuggable); + } + Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs, boolean debuggable) { this.apkHandles = apkHandles; @@ -152,6 +164,7 @@ public class NativeLibraryHelper { } private static native long nativeOpenApk(String path); + private static native long nativeOpenApkFd(FileDescriptor fd, String debugPath); private static native void nativeClose(long handle); private static native long nativeSumNativeBinaries(long handle, String cpuAbi, diff --git a/com/android/internal/content/PackageHelper.java b/com/android/internal/content/PackageHelper.java index 59a7995a..e765ab1e 100644 --- a/com/android/internal/content/PackageHelper.java +++ b/com/android/internal/content/PackageHelper.java @@ -42,6 +42,7 @@ import com.android.internal.annotations.VisibleForTesting; import libcore.io.IoUtils; import java.io.File; +import java.io.FileDescriptor; import java.io.IOException; import java.util.Objects; import java.util.UUID; @@ -383,9 +384,15 @@ public class PackageHelper { public static long calculateInstalledSize(PackageLite pkg, String abiOverride) throws IOException { + return calculateInstalledSize(pkg, abiOverride, null); + } + + public static long calculateInstalledSize(PackageLite pkg, String abiOverride, + FileDescriptor fd) throws IOException { NativeLibraryHelper.Handle handle = null; try { - handle = NativeLibraryHelper.Handle.create(pkg); + handle = fd != null ? NativeLibraryHelper.Handle.createFd(pkg, fd) + : NativeLibraryHelper.Handle.create(pkg); return calculateInstalledSize(pkg, handle, abiOverride); } finally { IoUtils.closeQuietly(handle); diff --git a/com/android/internal/content/ReferrerIntent.java b/com/android/internal/content/ReferrerIntent.java index 8d9a1cf9..76dcc9bb 100644 --- a/com/android/internal/content/ReferrerIntent.java +++ b/com/android/internal/content/ReferrerIntent.java @@ -19,6 +19,8 @@ package com.android.internal.content; import android.content.Intent; import android.os.Parcel; +import java.util.Objects; + /** * Subclass of Intent that also contains referrer (as a package name) information. */ @@ -48,4 +50,21 @@ public class ReferrerIntent extends Intent { return new ReferrerIntent[size]; } }; + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ReferrerIntent)) { + return false; + } + final ReferrerIntent other = (ReferrerIntent) obj; + return filterEquals(other) && Objects.equals(mReferrer, other.mReferrer); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + filterHashCode(); + result = 31 * result + Objects.hashCode(mReferrer); + return result; + } } diff --git a/com/android/internal/os/BatteryStatsImpl.java b/com/android/internal/os/BatteryStatsImpl.java index f2483c0a..56d0bb22 100644 --- a/com/android/internal/os/BatteryStatsImpl.java +++ b/com/android/internal/os/BatteryStatsImpl.java @@ -120,7 +120,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 168 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 169 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS; @@ -599,6 +599,8 @@ public class BatteryStatsImpl extends BatteryStats { private LongSamplingCounter mDischargeScreenOffCounter; private LongSamplingCounter mDischargeScreenDozeCounter; private LongSamplingCounter mDischargeCounter; + private LongSamplingCounter mDischargeLightDozeCounter; + private LongSamplingCounter mDischargeDeepDozeCounter; static final int MAX_LEVEL_STEPS = 200; @@ -697,6 +699,16 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public long getUahDischargeLightDoze(int which) { + return mDischargeLightDozeCounter.getCountLocked(which); + } + + @Override + public long getUahDischargeDeepDoze(int which) { + return mDischargeDeepDozeCounter.getCountLocked(which); + } + + @Override public int getEstimatedBatteryCapacity() { return mEstimatedBatteryCapacity; } @@ -6008,6 +6020,11 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public Timer getMulticastWakelockStats() { + return mWifiMulticastTimer; + } + + @Override public ArrayMap<String, ? extends BatteryStats.Timer> getSyncStats() { return mSyncStats.getMap(); } @@ -9085,6 +9102,8 @@ public class BatteryStatsImpl extends BatteryStats { mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase); mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase); mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase); + mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase); + mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase); mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase); mOnBattery = mOnBatteryInternal = false; long uptime = mClocks.uptimeMillis() * 1000; @@ -9664,6 +9683,8 @@ public class BatteryStatsImpl extends BatteryStats { mChargeStepTracker.init(); mDischargeScreenOffCounter.reset(false); mDischargeScreenDozeCounter.reset(false); + mDischargeLightDozeCounter.reset(false); + mDischargeDeepDozeCounter.reset(false); mDischargeCounter.reset(false); } @@ -11263,6 +11284,11 @@ public class BatteryStatsImpl extends BatteryStats { if (isScreenDoze(mScreenState)) { mDischargeScreenDozeCounter.addCountLocked(chargeDiff); } + if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) { + mDischargeLightDozeCounter.addCountLocked(chargeDiff); + } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) { + mDischargeDeepDozeCounter.addCountLocked(chargeDiff); + } } mHistoryCur.batteryChargeUAh = chargeUAh; setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh); @@ -11308,6 +11334,11 @@ public class BatteryStatsImpl extends BatteryStats { if (isScreenDoze(mScreenState)) { mDischargeScreenDozeCounter.addCountLocked(chargeDiff); } + if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) { + mDischargeLightDozeCounter.addCountLocked(chargeDiff); + } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) { + mDischargeDeepDozeCounter.addCountLocked(chargeDiff); + } } mHistoryCur.batteryChargeUAh = chargeUAh; changed = true; @@ -12069,6 +12100,8 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter.readSummaryFromParcelLocked(in); mDischargeScreenOffCounter.readSummaryFromParcelLocked(in); mDischargeScreenDozeCounter.readSummaryFromParcelLocked(in); + mDischargeLightDozeCounter.readSummaryFromParcelLocked(in); + mDischargeDeepDozeCounter.readSummaryFromParcelLocked(in); int NPKG = in.readInt(); if (NPKG > 0) { mDailyPackageChanges = new ArrayList<>(NPKG); @@ -12493,6 +12526,8 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter.writeSummaryFromParcelLocked(out); mDischargeScreenOffCounter.writeSummaryFromParcelLocked(out); mDischargeScreenDozeCounter.writeSummaryFromParcelLocked(out); + mDischargeLightDozeCounter.writeSummaryFromParcelLocked(out); + mDischargeDeepDozeCounter.writeSummaryFromParcelLocked(out); if (mDailyPackageChanges != null) { final int NPKG = mDailyPackageChanges.size(); out.writeInt(NPKG); @@ -13044,6 +13079,8 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in); mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase, in); mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in); + mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in); + mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in); mLastWriteTime = in.readLong(); mRpmStats.clear(); @@ -13230,6 +13267,8 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter.writeToParcel(out); mDischargeScreenOffCounter.writeToParcel(out); mDischargeScreenDozeCounter.writeToParcel(out); + mDischargeLightDozeCounter.writeToParcel(out); + mDischargeDeepDozeCounter.writeToParcel(out); out.writeLong(mLastWriteTime); out.writeInt(mRpmStats.size()); diff --git a/com/android/internal/policy/DecorView.java b/com/android/internal/policy/DecorView.java index 85251d4b..5fddfba6 100644 --- a/com/android/internal/policy/DecorView.java +++ b/com/android/internal/policy/DecorView.java @@ -431,7 +431,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } - return super.dispatchKeyEvent(event); + if (super.dispatchKeyEvent(event)) { + return true; + } + + return (getViewRootImpl() != null) && getViewRootImpl().dispatchKeyFallbackEvent(event); } public boolean superDispatchKeyShortcutEvent(KeyEvent event) { diff --git a/com/android/internal/telephony/CarrierIdentifier.java b/com/android/internal/telephony/CarrierIdentifier.java new file mode 100644 index 00000000..e8be159b --- /dev/null +++ b/com/android/internal/telephony/CarrierIdentifier.java @@ -0,0 +1,569 @@ +/* + * 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 com.android.internal.telephony; + +import android.content.Context; +import android.content.SharedPreferences; +import android.database.ContentObserver; +import android.database.Cursor; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.preference.PreferenceManager; +import android.provider.Telephony; +import android.telephony.Rlog; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.LocalLog; +import android.util.Log; + +import com.android.internal.telephony.uicc.IccRecords; +import com.android.internal.telephony.uicc.UiccController; +import com.android.internal.util.IndentingPrintWriter; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static android.provider.Telephony.CarrierIdentification; + +/** + * CarrierIdentifier identifies the subscription carrier and returns a canonical carrier Id + * and a user friendly carrier name. CarrierIdentifier reads subscription info and check against + * all carrier matching rules stored in CarrierIdProvider. It is msim aware, each phone has a + * dedicated CarrierIdentifier. + */ +public class CarrierIdentifier extends Handler { + private static final String LOG_TAG = CarrierIdentifier.class.getSimpleName(); + private static final boolean DBG = true; + private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE); + + // events to trigger carrier identification + private static final int SIM_LOAD_EVENT = 1; + private static final int SIM_ABSENT_EVENT = 2; + private static final int SPN_OVERRIDE_EVENT = 3; + private static final int ICC_CHANGED_EVENT = 4; + private static final int PREFER_APN_UPDATE_EVENT = 5; + private static final int CARRIER_ID_DB_UPDATE_EVENT = 6; + + private static final Uri CONTENT_URL_PREFER_APN = Uri.withAppendedPath( + Telephony.Carriers.CONTENT_URI, "preferapn"); + private static final String OPERATOR_BRAND_OVERRIDE_PREFIX = "operator_branding_"; + private static final int INVALID_CARRIER_ID = -1; + + // cached matching rules based mccmnc to speed up resolution + private List<CarrierMatchingRule> mCarrierMatchingRulesOnMccMnc = new ArrayList<>(); + // cached carrier Id + private int mCarrierId = INVALID_CARRIER_ID; + // cached carrier name + private String mCarrierName; + // cached preferapn name + private String mPreferApn; + // cached service provider name. telephonyManager API returns empty string as default value. + // some carriers need to target devices with Empty SPN. In that case, carrier matching rule + // should specify "" spn explicitly. + private String mSpn = ""; + + private Context mContext; + private Phone mPhone; + private IccRecords mIccRecords; + private final LocalLog mCarrierIdLocalLog = new LocalLog(20); + private final TelephonyManager mTelephonyMgr; + private final SubscriptionsChangedListener mOnSubscriptionsChangedListener = + new SubscriptionsChangedListener(); + private final SharedPreferenceChangedListener mSharedPrefListener = + new SharedPreferenceChangedListener(); + + private final ContentObserver mContentObserver = new ContentObserver(this) { + @Override + public void onChange(boolean selfChange, Uri uri) { + logd("onChange URI: " + uri); + if (CONTENT_URL_PREFER_APN.equals(uri.getLastPathSegment())) { + sendEmptyMessage(PREFER_APN_UPDATE_EVENT); + } else { + sendEmptyMessage(CARRIER_ID_DB_UPDATE_EVENT); + } + } + }; + + private class SubscriptionsChangedListener + extends SubscriptionManager.OnSubscriptionsChangedListener { + final AtomicInteger mPreviousSubId = + new AtomicInteger(SubscriptionManager.INVALID_SUBSCRIPTION_ID); + /** + * Callback invoked when there is any change to any SubscriptionInfo. Typically + * this method would invoke {@link SubscriptionManager#getActiveSubscriptionInfoList} + */ + @Override + public void onSubscriptionsChanged() { + int subId = mPhone.getSubId(); + if (mPreviousSubId.getAndSet(subId) != subId) { + if (DBG) { + logd("SubscriptionListener.onSubscriptionInfoChanged subId: " + + mPreviousSubId); + } + if (SubscriptionManager.isValidSubscriptionId(subId)) { + sendEmptyMessage(SIM_LOAD_EVENT); + } else { + sendEmptyMessage(SIM_ABSENT_EVENT); + } + } + } + } + + private class SharedPreferenceChangedListener implements + SharedPreferences.OnSharedPreferenceChangeListener { + @Override + public void onSharedPreferenceChanged( + SharedPreferences sharedPreferences, String key) { + if (TextUtils.equals(key, OPERATOR_BRAND_OVERRIDE_PREFIX + + mPhone.getIccSerialNumber())) { + // SPN override from carrier privileged apps + logd("[onSharedPreferenceChanged]: " + key); + sendEmptyMessage(SPN_OVERRIDE_EVENT); + } + } + } + + public CarrierIdentifier(Phone phone) { + logd("Creating CarrierIdentifier[" + phone.getPhoneId() + "]"); + mContext = phone.getContext(); + mPhone = phone; + mTelephonyMgr = TelephonyManager.from(mContext); + + // register events + mContext.getContentResolver().registerContentObserver(CONTENT_URL_PREFER_APN, false, + mContentObserver); + mContext.getContentResolver().registerContentObserver( + Telephony.CarrierIdentification.CONTENT_URI, false, mContentObserver); + SubscriptionManager.from(mContext).addOnSubscriptionsChangedListener( + mOnSubscriptionsChangedListener); + PreferenceManager.getDefaultSharedPreferences(mContext) + .registerOnSharedPreferenceChangeListener(mSharedPrefListener); + UiccController.getInstance().registerForIccChanged(this, ICC_CHANGED_EVENT, null); + } + + /** + * Entry point for the carrier identification. + * + * 1. SIM_LOAD_EVENT + * This indicates that all SIM records has been loaded and its first entry point for the + * carrier identification. Note, there are other attributes could be changed on the fly + * like APN and SPN. We cached all carrier matching rules based on MCCMNC to speed + * up carrier resolution on following trigger events. + * + * 2. PREFER_APN_UPDATE_EVENT + * This indicates prefer apn has been changed. It could be triggered when user modified + * APN settings or when default data connection first establishes on the current carrier. + * We follow up on this by querying prefer apn sqlite and re-issue carrier identification + * with the updated prefer apn name. + * + * 3. SPN_OVERRIDE_EVENT + * This indicates that SPN value as been changed. It could be triggered from EF_SPN + * record loading, carrier config override + * {@link android.telephony.CarrierConfigManager#KEY_CARRIER_NAME_STRING} + * or carrier app override {@link TelephonyManager#setOperatorBrandOverride(String)}. + * we follow up this by checking the cached mSPN against the latest value and issue + * carrier identification only if spn changes. + * + * 4. CARRIER_ID_DB_UPDATE_EVENT + * This indicates that carrierIdentification database which stores all matching rules + * has been updated. It could be triggered from OTA or assets update. + */ + @Override + public void handleMessage(Message msg) { + if (VDBG) logd("handleMessage: " + msg.what); + switch (msg.what) { + case SIM_LOAD_EVENT: + case CARRIER_ID_DB_UPDATE_EVENT: + mSpn = mTelephonyMgr.getSimOperatorNameForPhone(mPhone.getPhoneId()); + mPreferApn = getPreferApn(); + loadCarrierMatchingRulesOnMccMnc(); + break; + case SIM_ABSENT_EVENT: + mCarrierMatchingRulesOnMccMnc.clear(); + mSpn = null; + mPreferApn = null; + updateCarrierIdAndName(INVALID_CARRIER_ID, null); + break; + case PREFER_APN_UPDATE_EVENT: + String preferApn = getPreferApn(); + if (!equals(mPreferApn, preferApn, true)) { + logd("[updatePreferApn] from:" + mPreferApn + " to:" + preferApn); + mPreferApn = preferApn; + matchCarrier(); + } + break; + case SPN_OVERRIDE_EVENT: + String spn = mTelephonyMgr.getSimOperatorNameForPhone(mPhone.getPhoneId()); + if (!equals(mSpn, spn, true)) { + logd("[updateSpn] from:" + mSpn + " to:" + spn); + mSpn = spn; + matchCarrier(); + } + break; + case ICC_CHANGED_EVENT: + IccRecords newIccRecords = UiccController.getInstance().getIccRecords( + mPhone.getPhoneId(), UiccController.APP_FAM_3GPP); + if (mIccRecords != newIccRecords) { + if (mIccRecords != null) { + logd("Removing stale icc objects."); + mIccRecords.unregisterForSpnUpdate(this); + mIccRecords = null; + } + if (newIccRecords != null) { + logd("new Icc object"); + newIccRecords.registerForSpnUpdate(this, SPN_OVERRIDE_EVENT, null); + mIccRecords = newIccRecords; + } + } + break; + default: + loge("invalid msg: " + msg.what); + break; + } + } + + private void loadCarrierMatchingRulesOnMccMnc() { + try { + String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId()); + Cursor cursor = mContext.getContentResolver().query(CarrierIdentification.CONTENT_URI, + /* projection */ null, + /* selection */ CarrierIdentification.MCCMNC + "=?", + /* selectionArgs */ new String[]{mccmnc}, null); + try { + if (cursor != null) { + if (VDBG) { + logd("[loadCarrierMatchingRules]- " + cursor.getCount() + + " Records(s) in DB" + " mccmnc: " + mccmnc); + } + mCarrierMatchingRulesOnMccMnc.clear(); + while (cursor.moveToNext()) { + mCarrierMatchingRulesOnMccMnc.add(makeCarrierMatchingRule(cursor)); + } + matchCarrier(); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + } catch (Exception ex) { + loge("[loadCarrierMatchingRules]- ex: " + ex); + } + } + + private String getPreferApn() { + Cursor cursor = mContext.getContentResolver().query( + Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "preferapn/subId/" + + mPhone.getSubId()), /* projection */ new String[]{Telephony.Carriers.APN}, + /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null); + try { + if (cursor != null) { + if (VDBG) { + logd("[getPreferApn]- " + cursor.getCount() + " Records(s) in DB"); + } + while (cursor.moveToNext()) { + String apn = cursor.getString(cursor.getColumnIndexOrThrow( + Telephony.Carriers.APN)); + logd("[getPreferApn]- " + apn); + return apn; + } + } + } catch (Exception ex) { + loge("[getPreferApn]- exception: " + ex); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + private void updateCarrierIdAndName(int cid, String name) { + boolean update = false; + if (!equals(name, mCarrierName, true)) { + logd("[updateCarrierName] from:" + mCarrierName + " to:" + name); + mCarrierName = name; + update = true; + } + if (cid != mCarrierId) { + logd("[updateCarrierId] from:" + mCarrierId + " to:" + cid); + mCarrierId = cid; + update = true; + } + if (update) { + // TODO new public intent CARRIER_ID_CHANGED + mCarrierIdLocalLog.log("[updateCarrierIdAndName] cid:" + mCarrierId + " name:" + + mCarrierName); + } + } + + private CarrierMatchingRule makeCarrierMatchingRule(Cursor cursor) { + return new CarrierMatchingRule( + cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.MCCMNC)), + cursor.getString(cursor.getColumnIndexOrThrow( + CarrierIdentification.IMSI_PREFIX_XPATTERN)), + cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.GID1)), + cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.GID2)), + cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.PLMN)), + cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.SPN)), + cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.APN)), + cursor.getInt(cursor.getColumnIndexOrThrow(CarrierIdentification.CID)), + cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.NAME))); + } + + /** + * carrier matching attributes with corresponding cid + */ + private static class CarrierMatchingRule { + /** + * These scores provide the hierarchical relationship between the attributes, intended to + * resolve conflicts in a deterministic way. The scores are constructed such that a match + * from a higher tier will beat any subsequent match which does not match at that tier, + * so MCCMNC beats everything else. This avoids problems when two (or more) carriers rule + * matches as the score helps to find the best match uniquely. e.g., + * rule 1 {mccmnc, imsi} rule 2 {mccmnc, imsi, gid1} and rule 3 {mccmnc, imsi, gid2} all + * matches with subscription data. rule 2 wins with the highest matching score. + */ + private static final int SCORE_MCCMNC = 1 << 6; + private static final int SCORE_IMSI_PREFIX = 1 << 5; + private static final int SCORE_GID1 = 1 << 4; + private static final int SCORE_GID2 = 1 << 3; + private static final int SCORE_PLMN = 1 << 2; + private static final int SCORE_SPN = 1 << 1; + private static final int SCORE_APN = 1 << 0; + + private static final int SCORE_INVALID = -1; + + // carrier matching attributes + private String mMccMnc; + private String mImsiPrefixPattern; + private String mGid1; + private String mGid2; + private String mPlmn; + private String mSpn; + private String mApn; + + // user-facing carrier name + private String mName; + // unique carrier id + private int mCid; + + private int mScore = 0; + + CarrierMatchingRule(String mccmnc, String imsiPrefixPattern, String gid1, String gid2, + String plmn, String spn, String apn, int cid, String name) { + mMccMnc = mccmnc; + mImsiPrefixPattern = imsiPrefixPattern; + mGid1 = gid1; + mGid2 = gid2; + mPlmn = plmn; + mSpn = spn; + mApn = apn; + mCid = cid; + mName = name; + } + + // Calculate matching score. Values which aren't set in the rule are considered "wild". + // All values in the rule must match in order for the subscription to be considered part of + // the carrier. otherwise, a invalid score -1 will be assigned. A match from a higher tier + // will beat any subsequent match which does not match at that tier. When there are multiple + // matches at the same tier, the longest, best match will be used. + public void match(CarrierMatchingRule subscriptionRule) { + mScore = 0; + if (mMccMnc != null) { + if (!CarrierIdentifier.equals(subscriptionRule.mMccMnc, mMccMnc, false)) { + mScore = SCORE_INVALID; + return; + } + mScore += SCORE_MCCMNC; + } + if (mImsiPrefixPattern != null) { + if (!imsiPrefixMatch(subscriptionRule.mImsiPrefixPattern, mImsiPrefixPattern)) { + mScore = SCORE_INVALID; + return; + } + mScore += SCORE_IMSI_PREFIX; + } + if (mGid1 != null) { + // full string match. carrier matching should cover the corner case that gid1 + // with garbage tail due to SIM manufacture issues. + if (!CarrierIdentifier.equals(subscriptionRule.mGid1, mGid1, true)) { + mScore = SCORE_INVALID; + return; + } + mScore += SCORE_GID1; + } + if (mGid2 != null) { + // full string match. carrier matching should cover the corner case that gid2 + // with garbage tail due to SIM manufacture issues. + if (!CarrierIdentifier.equals(subscriptionRule.mGid2, mGid2, true)) { + mScore = SCORE_INVALID; + return; + } + mScore += SCORE_GID2; + } + if (mPlmn != null) { + if (!CarrierIdentifier.equals(subscriptionRule.mPlmn, mPlmn, true)) { + mScore = SCORE_INVALID; + return; + } + mScore += SCORE_PLMN; + } + if (mSpn != null) { + if (!CarrierIdentifier.equals(subscriptionRule.mSpn, mSpn, true)) { + mScore = SCORE_INVALID; + return; + } + mScore += SCORE_SPN; + } + if (mApn != null) { + if (!CarrierIdentifier.equals(subscriptionRule.mApn, mApn, true)) { + mScore = SCORE_INVALID; + return; + } + mScore += SCORE_APN; + } + } + + private boolean imsiPrefixMatch(String imsi, String prefixXPattern) { + if (TextUtils.isEmpty(prefixXPattern)) return true; + if (TextUtils.isEmpty(imsi)) return false; + if (imsi.length() < prefixXPattern.length()) { + return false; + } + for (int i = 0; i < prefixXPattern.length(); i++) { + if ((prefixXPattern.charAt(i) != 'x') && (prefixXPattern.charAt(i) != 'X') + && (prefixXPattern.charAt(i) != imsi.charAt(i))) { + return false; + } + } + return true; + } + + public String toString() { + return "[CarrierMatchingRule] -" + + " mccmnc: " + mMccMnc + + " gid1: " + mGid1 + + " gid2: " + mGid2 + + " plmn: " + mPlmn + + " imsi_prefix: " + mImsiPrefixPattern + + " spn: " + mSpn + + " apn: " + mApn + + " name: " + mName + + " cid: " + mCid + + " score: " + mScore; + } + } + + /** + * find the best matching carrier from candidates with matched MCCMNC and notify + * all interested parties on carrier id change. + */ + private void matchCarrier() { + if (!SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())) { + logd("[matchCarrier]" + "skip before sim records loaded"); + return; + } + final String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId()); + final String gid1 = mPhone.getGroupIdLevel1(); + final String gid2 = mPhone.getGroupIdLevel2(); + final String imsi = mPhone.getSubscriberId(); + final String plmn = mPhone.getPlmn(); + final String spn = mSpn; + final String apn = mPreferApn; + + if (VDBG) { + logd("[matchCarrier]" + + " gid1: " + gid1 + + " gid2: " + gid2 + + " imsi: " + Rlog.pii(LOG_TAG, imsi) + + " plmn: " + plmn + + " spn: " + spn + + " apn: " + apn); + } + + CarrierMatchingRule subscriptionRule = new CarrierMatchingRule( + mccmnc, imsi, gid1, gid2, plmn, spn, apn, INVALID_CARRIER_ID, null); + + int maxScore = CarrierMatchingRule.SCORE_INVALID; + CarrierMatchingRule maxRule = null; + + for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) { + rule.match(subscriptionRule); + if (rule.mScore > maxScore) { + maxScore = rule.mScore; + maxRule = rule; + } + } + if (maxScore == CarrierMatchingRule.SCORE_INVALID) { + logd("[matchCarrier - no match] cid: " + INVALID_CARRIER_ID + " name: " + null); + updateCarrierIdAndName(INVALID_CARRIER_ID, null); + } else { + logd("[matchCarrier] cid: " + maxRule.mCid + " name: " + maxRule.mName); + updateCarrierIdAndName(maxRule.mCid, maxRule.mName); + } + } + + public int getCarrierId() { + return mCarrierId; + } + + public String getCarrierName() { + return mCarrierName; + } + + private static boolean equals(String a, String b, boolean ignoreCase) { + if (a == null && b == null) return true; + if (a != null && b != null) { + return (ignoreCase) ? a.equalsIgnoreCase(b) : a.equals(b); + } + return false; + } + + private static void logd(String str) { + Rlog.d(LOG_TAG, str); + } + private static void loge(String str) { + Rlog.e(LOG_TAG, str); + } + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + ipw.println("mCarrierIdLocalLogs:"); + ipw.increaseIndent(); + mCarrierIdLocalLog.dump(fd, pw, args); + ipw.decreaseIndent(); + + ipw.println("mCarrierId: " + mCarrierId); + ipw.println("mCarrierName: " + mCarrierName); + + ipw.println("mCarrierMatchingRules on mccmnc: " + + mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId())); + ipw.increaseIndent(); + for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) { + ipw.println(rule.toString()); + } + ipw.decreaseIndent(); + + ipw.println("mSpn: " + mSpn); + ipw.println("mPreferApn: " + mPreferApn); + ipw.flush(); + } +} diff --git a/com/android/internal/telephony/CarrierInfoManager.java b/com/android/internal/telephony/CarrierInfoManager.java index ebf04e89..d224c7df 100644 --- a/com/android/internal/telephony/CarrierInfoManager.java +++ b/com/android/internal/telephony/CarrierInfoManager.java @@ -38,30 +38,30 @@ public class CarrierInfoManager { /** * Returns Carrier specific information that will be used to encrypt the IMSI and IMPI. * @param keyType whether the key is being used for WLAN or ePDG. - * @param mContext + * @param context * @return ImsiEncryptionInfo which contains the information, including the public key, to be * used for encryption. */ public static ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType, - Context mContext) { + Context context) { String mcc = ""; String mnc = ""; final TelephonyManager telephonyManager = - (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - String networkOperator = telephonyManager.getNetworkOperator(); - if (!TextUtils.isEmpty(networkOperator)) { - mcc = networkOperator.substring(0, 3); - mnc = networkOperator.substring(3); + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + String simOperator = telephonyManager.getSimOperator(); + if (!TextUtils.isEmpty(simOperator)) { + mcc = simOperator.substring(0, 3); + mnc = simOperator.substring(3); Log.i(LOG_TAG, "using values for mnc, mcc: " + mnc + "," + mcc); } else { - Log.e(LOG_TAG, "Invalid networkOperator: " + networkOperator); + Log.e(LOG_TAG, "Invalid networkOperator: " + simOperator); return null; } Cursor findCursor = null; try { // In the current design, MVNOs are not supported. If we decide to support them, // we'll need to add to this CL. - ContentResolver mContentResolver = mContext.getContentResolver(); + ContentResolver mContentResolver = context.getContentResolver(); String[] columns = {Telephony.CarrierColumns.PUBLIC_KEY, Telephony.CarrierColumns.EXPIRATION_TIME, Telephony.CarrierColumns.KEY_IDENTIFIER}; @@ -95,12 +95,12 @@ public class CarrierInfoManager { /** * Inserts or update the Carrier Key in the database * @param imsiEncryptionInfo ImsiEncryptionInfo object. - * @param mContext Context. + * @param context Context. */ public static void updateOrInsertCarrierKey(ImsiEncryptionInfo imsiEncryptionInfo, - Context mContext) { + Context context) { byte[] keyBytes = imsiEncryptionInfo.getPublicKey().getEncoded(); - ContentResolver mContentResolver = mContext.getContentResolver(); + ContentResolver mContentResolver = context.getContentResolver(); // In the current design, MVNOs are not supported. If we decide to support them, // we'll need to add to this CL. ContentValues contentValues = new ContentValues(); @@ -150,12 +150,54 @@ public class CarrierInfoManager { * {@link java.security.PublicKey} and the Key Identifier. * The keyIdentifier Attribute value pair that helps a server locate * the private key to decrypt the permanent identity. - * @param mContext Context. + * @param context Context. */ public static void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo, - Context mContext) { + Context context) { Log.i(LOG_TAG, "inserting carrier key: " + imsiEncryptionInfo); - updateOrInsertCarrierKey(imsiEncryptionInfo, mContext); + updateOrInsertCarrierKey(imsiEncryptionInfo, context); //todo send key to modem. Will be done in a subsequent CL. } + + /** + * Deletes all the keys for a given Carrier from the device keystore. + * @param context Context + */ + public static void deleteCarrierInfoForImsiEncryption(Context context) { + Log.i(LOG_TAG, "deleting carrier key from db"); + String mcc = ""; + String mnc = ""; + final TelephonyManager telephonyManager = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + String simOperator = telephonyManager.getSimOperator(); + if (!TextUtils.isEmpty(simOperator)) { + mcc = simOperator.substring(0, 3); + mnc = simOperator.substring(3); + } else { + Log.e(LOG_TAG, "Invalid networkOperator: " + simOperator); + return; + } + ContentResolver mContentResolver = context.getContentResolver(); + try { + String whereClause = "mcc=? and mnc=?"; + String[] whereArgs = new String[] { mcc, mnc }; + mContentResolver.delete(Telephony.CarrierColumns.CONTENT_URI, whereClause, whereArgs); + } catch (Exception e) { + Log.e(LOG_TAG, "Delete failed" + e); + } + } + + /** + * Deletes all the keys from the device keystore. + * @param context Context + */ + public static void deleteAllCarrierKeysForImsiEncryption(Context context) { + Log.i(LOG_TAG, "deleting ALL carrier keys from db"); + ContentResolver mContentResolver = context.getContentResolver(); + try { + mContentResolver.delete(Telephony.CarrierColumns.CONTENT_URI, null, null); + } catch (Exception e) { + Log.e(LOG_TAG, "Delete failed" + e); + } + } }
\ No newline at end of file diff --git a/com/android/internal/telephony/CommandsInterface.java b/com/android/internal/telephony/CommandsInterface.java index 7026ff22..d5eb5463 100644 --- a/com/android/internal/telephony/CommandsInterface.java +++ b/com/android/internal/telephony/CommandsInterface.java @@ -23,9 +23,9 @@ import android.service.carrier.CarrierIdentifier; import android.telephony.ClientRequestStats; import android.telephony.ImsiEncryptionInfo; import android.telephony.NetworkScanRequest; +import android.telephony.data.DataProfile; import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo; -import com.android.internal.telephony.dataconnection.DataProfile; import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; import com.android.internal.telephony.uicc.IccCardStatus; diff --git a/com/android/internal/telephony/GsmCdmaPhone.java b/com/android/internal/telephony/GsmCdmaPhone.java index 47289e57..64804177 100644 --- a/com/android/internal/telephony/GsmCdmaPhone.java +++ b/com/android/internal/telephony/GsmCdmaPhone.java @@ -157,6 +157,8 @@ public class GsmCdmaPhone extends Phone { private ArrayList <MmiCode> mPendingMMIs = new ArrayList<MmiCode>(); private IccPhoneBookInterfaceManager mIccPhoneBookIntManager; private DeviceStateMonitor mDeviceStateMonitor; + // Used for identify the carrier of current subscription + private CarrierIdentifier mCarrerIdentifier; private int mPrecisePhoneType; @@ -212,6 +214,8 @@ public class GsmCdmaPhone extends Phone { mSST = mTelephonyComponentFactory.makeServiceStateTracker(this, this.mCi); // DcTracker uses SST so needs to be created after it is instantiated mDcTracker = mTelephonyComponentFactory.makeDcTracker(this); + mCarrerIdentifier = mTelephonyComponentFactory.makeCarrierIdentifier(this); + mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null); mDeviceStateMonitor = mTelephonyComponentFactory.makeDeviceStateMonitor(this); logd("GsmCdmaPhone: constructor: sub = " + mPhoneId); @@ -1537,6 +1541,16 @@ public class GsmCdmaPhone extends Phone { } @Override + public int getCarrierId() { + return mCarrerIdentifier.getCarrierId(); + } + + @Override + public String getCarrierName() { + return mCarrerIdentifier.getCarrierName(); + } + + @Override public String getGroupIdLevel1() { if (isPhoneTypeGsm()) { IccRecords r = mIccRecords.get(); @@ -1573,6 +1587,19 @@ public class GsmCdmaPhone extends Phone { } @Override + public String getPlmn() { + if (isPhoneTypeGsm()) { + IccRecords r = mIccRecords.get(); + return (r != null) ? r.getPnnHomeName() : null; + } else if (isPhoneTypeCdma()) { + loge("Plmn is not available in CDMA"); + return null; + } else { //isPhoneTypeCdmaLte() + return (mSimRecords != null) ? mSimRecords.getPnnHomeName() : null; + } + } + + @Override public String getCdmaPrlVersion() { return mSST.getPrlVersion(); } diff --git a/com/android/internal/telephony/Phone.java b/com/android/internal/telephony/Phone.java index c51a5fe4..d702c09b 100644 --- a/com/android/internal/telephony/Phone.java +++ b/com/android/internal/telephony/Phone.java @@ -2881,6 +2881,13 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { } /** + * Retrieves the EF_PNN from the UICC For GSM/UMTS phones. + */ + public String getPlmn() { + return null; + } + + /** * Get the current for the default apn DataState. No change notification * exists at this interface -- use * {@link android.telephony.PhoneStateListener} instead. @@ -2985,6 +2992,15 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { return; } + public int getCarrierId() { + // TODO remove hardcoding and expose a public API for INVALID CARRIER ID + return -1; + } + + public String getCarrierName() { + return null; + } + /** * Return if UT capability of ImsPhone is enabled or not */ diff --git a/com/android/internal/telephony/RIL.java b/com/android/internal/telephony/RIL.java index 3ddbf121..2d334982 100644 --- a/com/android/internal/telephony/RIL.java +++ b/com/android/internal/telephony/RIL.java @@ -54,6 +54,7 @@ import android.hardware.radio.V1_0.SmsWriteArgs; import android.hardware.radio.V1_0.UusInfo; import android.net.ConnectivityManager; import android.os.AsyncResult; +import android.os.Build; import android.os.Handler; import android.os.HwBinder; import android.os.Message; @@ -80,15 +81,17 @@ import android.telephony.SignalStrength; import android.telephony.SmsManager; import android.telephony.TelephonyHistogram; import android.telephony.TelephonyManager; +import android.telephony.data.DataProfile; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.cat.ComprehensionTlv; +import com.android.internal.telephony.cat.ComprehensionTlvTag; import com.android.internal.telephony.cdma.CdmaInformationRecords; import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo; import com.android.internal.telephony.dataconnection.DataCallResponse; -import com.android.internal.telephony.dataconnection.DataProfile; import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.telephony.nano.TelephonyProto.SmsSession; @@ -189,6 +192,9 @@ public class RIL extends BaseCommands implements CommandsInterface { static final int IRADIO_GET_SERVICE_DELAY_MILLIS = 4 * 1000; + static final String EMPTY_ALPHA_LONG = ""; + static final String EMPTY_ALPHA_SHORT = ""; + public static List<TelephonyHistogram> getTelephonyRILTimingHistograms() { List<TelephonyHistogram> list; synchronized (mRilTimeHistograms) { @@ -1061,23 +1067,23 @@ public class RIL extends BaseCommands implements CommandsInterface { private static DataProfileInfo convertToHalDataProfile(DataProfile dp) { DataProfileInfo dpi = new DataProfileInfo(); - dpi.profileId = dp.profileId; - dpi.apn = dp.apn; - dpi.protocol = dp.protocol; - dpi.roamingProtocol = dp.roamingProtocol; - dpi.authType = dp.authType; - dpi.user = dp.user; - dpi.password = dp.password; - dpi.type = dp.type; - dpi.maxConnsTime = dp.maxConnsTime; - dpi.maxConns = dp.maxConns; - dpi.waitTime = dp.waitTime; - dpi.enabled = dp.enabled; - dpi.supportedApnTypesBitmap = dp.supportedApnTypesBitmap; - dpi.bearerBitmap = dp.bearerBitmap; - dpi.mtu = dp.mtu; - dpi.mvnoType = convertToHalMvnoType(dp.mvnoType); - dpi.mvnoMatchData = dp.mvnoMatchData; + dpi.profileId = dp.getProfileId(); + dpi.apn = dp.getApn(); + dpi.protocol = dp.getProtocol(); + dpi.roamingProtocol = dp.getRoamingProtocol(); + dpi.authType = dp.getAuthType(); + dpi.user = dp.getUserName(); + dpi.password = dp.getPassword(); + dpi.type = dp.getType(); + dpi.maxConnsTime = dp.getMaxConnsTime(); + dpi.maxConns = dp.getMaxConns(); + dpi.waitTime = dp.getWaitTime(); + dpi.enabled = dp.isEnabled(); + dpi.supportedApnTypesBitmap = dp.getSupportedApnTypesBitmap(); + dpi.bearerBitmap = dp.getBearerBitmap(); + dpi.mtu = dp.getMtu(); + dpi.mvnoType = convertToHalMvnoType(dp.getMvnoType()); + dpi.mvnoMatchData = dp.getMvnoMatchData(); return dpi; } @@ -1143,7 +1149,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { radioProxy.setupDataCall(rr.mSerial, radioTechnology, dpi, - dataProfile.modemCognitive, allowRoaming, isRoaming); + dataProfile.isModemCognitive(), allowRoaming, isRoaming); mMetrics.writeRilSetupDataCall(mPhoneId, rr.mSerial, radioTechnology, dpi.profileId, dpi.apn, dpi.authType, dpi.protocol); } catch (RemoteException | RuntimeException e) { @@ -1167,12 +1173,16 @@ public class RIL extends BaseCommands implements CommandsInterface { mRILDefaultWorkSource); if (RILJ_LOGD) { - riljLog(rr.serialString() + "> iccIO: " - + requestToString(rr.mRequest) + " command = 0x" - + Integer.toHexString(command) + " fileId = 0x" - + Integer.toHexString(fileId) + " path = " + path + " p1 = " - + p1 + " p2 = " + p2 + " p3 = " + " data = " + data - + " aid = " + aid); + if (Build.IS_DEBUGGABLE) { + riljLog(rr.serialString() + "> iccIO: " + + requestToString(rr.mRequest) + " command = 0x" + + Integer.toHexString(command) + " fileId = 0x" + + Integer.toHexString(fileId) + " path = " + path + " p1 = " + + p1 + " p2 = " + p2 + " p3 = " + " data = " + data + + " aid = " + aid); + } else { + riljLog(rr.serialString() + "> iccIO: " + requestToString(rr.mRequest)); + } } IccIo iccIo = new IccIo(); @@ -2027,7 +2037,7 @@ public class RIL extends BaseCommands implements CommandsInterface { if (RILJ_LOGD) { riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " contents = " - + contents); + + (Build.IS_DEBUGGABLE ? contents : censoredTerminalResponse(contents))); } try { @@ -2039,6 +2049,33 @@ public class RIL extends BaseCommands implements CommandsInterface { } } + private String censoredTerminalResponse(String terminalResponse) { + try { + byte[] bytes = IccUtils.hexStringToBytes(terminalResponse); + if (bytes != null) { + List<ComprehensionTlv> ctlvs = ComprehensionTlv.decodeMany(bytes, 0); + int from = 0; + for (ComprehensionTlv ctlv : ctlvs) { + // Find text strings which might be personal information input by user, + // then replace it with "********". + if (ComprehensionTlvTag.TEXT_STRING.value() == ctlv.getTag()) { + byte[] target = Arrays.copyOfRange(ctlv.getRawValue(), from, + ctlv.getValueIndex() + ctlv.getLength()); + terminalResponse = terminalResponse.toLowerCase().replace( + IccUtils.bytesToHexString(target), "********"); + } + // The text string tag and the length field should also be hidden. + from = ctlv.getValueIndex() + ctlv.getLength(); + } + } + } catch (Exception e) { + Rlog.e(RILJ_LOG_TAG, "Could not censor the terminal response: " + e); + terminalResponse = null; + } + + return terminalResponse; + } + @Override public void sendEnvelopeWithStatus(String contents, Message result) { IRadio radioProxy = getRadioProxy(result); @@ -2864,7 +2901,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { radioProxy.setInitialAttachApn(rr.mSerial, convertToHalDataProfile(dataProfile), - dataProfile.modemCognitive, isRoaming); + dataProfile.isModemCognitive(), isRoaming); } catch (RemoteException | RuntimeException e) { handleRadioProxyExceptionForRR(rr, "setInitialAttachApn", e); } @@ -2969,9 +3006,13 @@ public class RIL extends BaseCommands implements CommandsInterface { mRILDefaultWorkSource); if (RILJ_LOGD) { - riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) - + " cla = " + cla + " instruction = " + instruction - + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data); + if (Build.IS_DEBUGGABLE) { + riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + + " cla = " + cla + " instruction = " + instruction + + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data); + } else { + riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)); + } } SimApdu msg = createSimApdu(0, cla, instruction, p1, p2, p3, data); @@ -2991,8 +3032,12 @@ public class RIL extends BaseCommands implements CommandsInterface { mRILDefaultWorkSource); if (RILJ_LOGD) { - riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " aid = " + aid - + " p2 = " + p2); + if (Build.IS_DEBUGGABLE) { + riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " aid = " + aid + + " p2 = " + p2); + } else { + riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)); + } } try { @@ -3038,9 +3083,13 @@ public class RIL extends BaseCommands implements CommandsInterface { mRILDefaultWorkSource); if (RILJ_LOGD) { - riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " channel = " - + channel + " cla = " + cla + " instruction = " + instruction - + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data); + if (Build.IS_DEBUGGABLE) { + riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " channel = " + + channel + " cla = " + cla + " instruction = " + instruction + + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data); + } else { + riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)); + } } SimApdu msg = createSimApdu(channel, cla, instruction, p1, p2, p3, data); @@ -4813,7 +4862,80 @@ public class RIL extends BaseCommands implements CommandsInterface { return capacityResponse; } - static ArrayList<CellInfo> convertHalCellInfoList( + private static void writeToParcelForGsm( + Parcel p, int lac, int cid, int arfcn, int bsic, String mcc, String mnc, + String al, String as, int ss, int ber, int ta) { + p.writeInt(lac); + p.writeInt(cid); + p.writeInt(arfcn); + p.writeInt(bsic); + p.writeString(mcc); + p.writeString(mnc); + p.writeString(al); + p.writeString(as); + p.writeInt(ss); + p.writeInt(ber); + p.writeInt(ta); + } + + private static void writeToParcelForCdma( + Parcel p, int ni, int si, int bsi, int lon, int lat, String al, String as, + int dbm, int ecio, int eDbm, int eEcio, int eSnr) { + p.writeInt(ni); + p.writeInt(si); + p.writeInt(bsi); + p.writeInt(lon); + p.writeInt(lat); + p.writeString(al); + p.writeString(as); + p.writeInt(dbm); + p.writeInt(ecio); + p.writeInt(eDbm); + p.writeInt(eEcio); + p.writeInt(eSnr); + } + + private static void writeToParcelForLte( + Parcel p, int ci, int pci, int tac, int earfcn, String mcc, String mnc, String al, + String as, int ss, int rsrp, int rsrq, int rssnr, int cqi, int ta) { + p.writeInt(ci); + p.writeInt(pci); + p.writeInt(tac); + p.writeInt(earfcn); + p.writeString(mcc); + p.writeString(mnc); + p.writeString(al); + p.writeString(as); + p.writeInt(ss); + p.writeInt(rsrp); + p.writeInt(rsrq); + p.writeInt(rssnr); + p.writeInt(cqi); + p.writeInt(ta); + } + + private static void writeToParcelForWcdma( + Parcel p, int lac, int cid, int psc, int uarfcn, String mcc, String mnc, + String al, String as, int ss, int ber) { + p.writeInt(lac); + p.writeInt(cid); + p.writeInt(psc); + p.writeInt(uarfcn); + p.writeString(mcc); + p.writeString(mnc); + p.writeString(al); + p.writeString(as); + p.writeInt(ss); + p.writeInt(ber); + } + + /** + * Convert CellInfo defined in 1.0/types.hal to CellInfo type. + * @param records List of CellInfo defined in 1.0/types.hal + * @return List of converted CellInfo object + */ + @VisibleForTesting + public static ArrayList<CellInfo> convertHalCellInfoList( ArrayList<android.hardware.radio.V1_0.CellInfo> records) { ArrayList<CellInfo> response = new ArrayList<CellInfo>(records.size()); @@ -4827,60 +4949,182 @@ public class RIL extends BaseCommands implements CommandsInterface { switch (record.cellInfoType) { case CellInfoType.GSM: { CellInfoGsm cellInfoGsm = record.gsm.get(0); - p.writeInt(Integer.parseInt(cellInfoGsm.cellIdentityGsm.mcc)); - p.writeInt(Integer.parseInt(cellInfoGsm.cellIdentityGsm.mnc)); - p.writeInt(cellInfoGsm.cellIdentityGsm.lac); - p.writeInt(cellInfoGsm.cellIdentityGsm.cid); - p.writeInt(cellInfoGsm.cellIdentityGsm.arfcn); - p.writeInt(Byte.toUnsignedInt(cellInfoGsm.cellIdentityGsm.bsic)); - p.writeInt(cellInfoGsm.signalStrengthGsm.signalStrength); - p.writeInt(cellInfoGsm.signalStrengthGsm.bitErrorRate); - p.writeInt(cellInfoGsm.signalStrengthGsm.timingAdvance); + writeToParcelForGsm( + p, + cellInfoGsm.cellIdentityGsm.lac, + cellInfoGsm.cellIdentityGsm.cid, + cellInfoGsm.cellIdentityGsm.arfcn, + Byte.toUnsignedInt(cellInfoGsm.cellIdentityGsm.bsic), + cellInfoGsm.cellIdentityGsm.mcc, + cellInfoGsm.cellIdentityGsm.mnc, + EMPTY_ALPHA_LONG, + EMPTY_ALPHA_SHORT, + cellInfoGsm.signalStrengthGsm.signalStrength, + cellInfoGsm.signalStrengthGsm.bitErrorRate, + cellInfoGsm.signalStrengthGsm.timingAdvance); break; } case CellInfoType.CDMA: { CellInfoCdma cellInfoCdma = record.cdma.get(0); - p.writeInt(cellInfoCdma.cellIdentityCdma.networkId); - p.writeInt(cellInfoCdma.cellIdentityCdma.systemId); - p.writeInt(cellInfoCdma.cellIdentityCdma.baseStationId); - p.writeInt(cellInfoCdma.cellIdentityCdma.longitude); - p.writeInt(cellInfoCdma.cellIdentityCdma.latitude); - p.writeInt(cellInfoCdma.signalStrengthCdma.dbm); - p.writeInt(cellInfoCdma.signalStrengthCdma.ecio); - p.writeInt(cellInfoCdma.signalStrengthEvdo.dbm); - p.writeInt(cellInfoCdma.signalStrengthEvdo.ecio); - p.writeInt(cellInfoCdma.signalStrengthEvdo.signalNoiseRatio); + writeToParcelForCdma( + p, + cellInfoCdma.cellIdentityCdma.networkId, + cellInfoCdma.cellIdentityCdma.systemId, + cellInfoCdma.cellIdentityCdma.baseStationId, + cellInfoCdma.cellIdentityCdma.longitude, + cellInfoCdma.cellIdentityCdma.latitude, + EMPTY_ALPHA_LONG, + EMPTY_ALPHA_SHORT, + cellInfoCdma.signalStrengthCdma.dbm, + cellInfoCdma.signalStrengthCdma.ecio, + cellInfoCdma.signalStrengthEvdo.dbm, + cellInfoCdma.signalStrengthEvdo.ecio, + cellInfoCdma.signalStrengthEvdo.signalNoiseRatio); break; } case CellInfoType.LTE: { CellInfoLte cellInfoLte = record.lte.get(0); - p.writeInt(Integer.parseInt(cellInfoLte.cellIdentityLte.mcc)); - p.writeInt(Integer.parseInt(cellInfoLte.cellIdentityLte.mnc)); - p.writeInt(cellInfoLte.cellIdentityLte.ci); - p.writeInt(cellInfoLte.cellIdentityLte.pci); - p.writeInt(cellInfoLte.cellIdentityLte.tac); - p.writeInt(cellInfoLte.cellIdentityLte.earfcn); - p.writeInt(cellInfoLte.signalStrengthLte.signalStrength); - p.writeInt(cellInfoLte.signalStrengthLte.rsrp); - p.writeInt(cellInfoLte.signalStrengthLte.rsrq); - p.writeInt(cellInfoLte.signalStrengthLte.rssnr); - p.writeInt(cellInfoLte.signalStrengthLte.cqi); - p.writeInt(cellInfoLte.signalStrengthLte.timingAdvance); + writeToParcelForLte( + p, + cellInfoLte.cellIdentityLte.ci, + cellInfoLte.cellIdentityLte.pci, + cellInfoLte.cellIdentityLte.tac, + cellInfoLte.cellIdentityLte.earfcn, + cellInfoLte.cellIdentityLte.mcc, + cellInfoLte.cellIdentityLte.mnc, + EMPTY_ALPHA_LONG, + EMPTY_ALPHA_SHORT, + cellInfoLte.signalStrengthLte.signalStrength, + cellInfoLte.signalStrengthLte.rsrp, + cellInfoLte.signalStrengthLte.rsrq, + cellInfoLte.signalStrengthLte.rssnr, + cellInfoLte.signalStrengthLte.cqi, + cellInfoLte.signalStrengthLte.timingAdvance); break; } case CellInfoType.WCDMA: { CellInfoWcdma cellInfoWcdma = record.wcdma.get(0); - p.writeInt(Integer.parseInt(cellInfoWcdma.cellIdentityWcdma.mcc)); - p.writeInt(Integer.parseInt(cellInfoWcdma.cellIdentityWcdma.mnc)); - p.writeInt(cellInfoWcdma.cellIdentityWcdma.lac); - p.writeInt(cellInfoWcdma.cellIdentityWcdma.cid); - p.writeInt(cellInfoWcdma.cellIdentityWcdma.psc); - p.writeInt(cellInfoWcdma.cellIdentityWcdma.uarfcn); - p.writeInt(cellInfoWcdma.signalStrengthWcdma.signalStrength); - p.writeInt(cellInfoWcdma.signalStrengthWcdma.bitErrorRate); + writeToParcelForWcdma( + p, + cellInfoWcdma.cellIdentityWcdma.lac, + cellInfoWcdma.cellIdentityWcdma.cid, + cellInfoWcdma.cellIdentityWcdma.psc, + cellInfoWcdma.cellIdentityWcdma.uarfcn, + cellInfoWcdma.cellIdentityWcdma.mcc, + cellInfoWcdma.cellIdentityWcdma.mnc, + EMPTY_ALPHA_LONG, + EMPTY_ALPHA_SHORT, + cellInfoWcdma.signalStrengthWcdma.signalStrength, + cellInfoWcdma.signalStrengthWcdma.bitErrorRate); + break; + } + + default: + throw new RuntimeException("unexpected cellinfotype: " + record.cellInfoType); + } + + p.setDataPosition(0); + CellInfo InfoRec = CellInfo.CREATOR.createFromParcel(p); + p.recycle(); + response.add(InfoRec); + } + + return response; + } + + /** + * Convert CellInfo defined in 1.2/types.hal to CellInfo type. + * @param records List of CellInfo defined in 1.2/types.hal + * @return List of converted CellInfo object + */ + @VisibleForTesting + public static ArrayList<CellInfo> convertHalCellInfoList_1_2( + ArrayList<android.hardware.radio.V1_2.CellInfo> records) { + ArrayList<CellInfo> response = new ArrayList<CellInfo>(records.size()); + + for (android.hardware.radio.V1_2.CellInfo record : records) { + // first convert RIL CellInfo to Parcel + Parcel p = Parcel.obtain(); + p.writeInt(record.cellInfoType); + p.writeInt(record.registered ? 1 : 0); + p.writeInt(record.timeStampType); + p.writeLong(record.timeStamp); + switch (record.cellInfoType) { + case CellInfoType.GSM: { + android.hardware.radio.V1_2.CellInfoGsm cellInfoGsm = record.gsm.get(0); + writeToParcelForGsm( + p, + cellInfoGsm.cellIdentityGsm.base.lac, + cellInfoGsm.cellIdentityGsm.base.cid, + cellInfoGsm.cellIdentityGsm.base.arfcn, + Byte.toUnsignedInt(cellInfoGsm.cellIdentityGsm.base.bsic), + cellInfoGsm.cellIdentityGsm.base.mcc, + cellInfoGsm.cellIdentityGsm.base.mnc, + cellInfoGsm.cellIdentityGsm.operatorNames.alphaLong, + cellInfoGsm.cellIdentityGsm.operatorNames.alphaShort, + cellInfoGsm.signalStrengthGsm.signalStrength, + cellInfoGsm.signalStrengthGsm.bitErrorRate, + cellInfoGsm.signalStrengthGsm.timingAdvance); + break; + } + + case CellInfoType.CDMA: { + android.hardware.radio.V1_2.CellInfoCdma cellInfoCdma = record.cdma.get(0); + writeToParcelForCdma( + p, + cellInfoCdma.cellIdentityCdma.base.networkId, + cellInfoCdma.cellIdentityCdma.base.systemId, + cellInfoCdma.cellIdentityCdma.base.baseStationId, + cellInfoCdma.cellIdentityCdma.base.longitude, + cellInfoCdma.cellIdentityCdma.base.latitude, + cellInfoCdma.cellIdentityCdma.operatorNames.alphaLong, + cellInfoCdma.cellIdentityCdma.operatorNames.alphaShort, + cellInfoCdma.signalStrengthCdma.dbm, + cellInfoCdma.signalStrengthCdma.ecio, + cellInfoCdma.signalStrengthEvdo.dbm, + cellInfoCdma.signalStrengthEvdo.ecio, + cellInfoCdma.signalStrengthEvdo.signalNoiseRatio); + break; + } + + case CellInfoType.LTE: { + android.hardware.radio.V1_2.CellInfoLte cellInfoLte = record.lte.get(0); + writeToParcelForLte( + p, + cellInfoLte.cellIdentityLte.base.ci, + cellInfoLte.cellIdentityLte.base.pci, + cellInfoLte.cellIdentityLte.base.tac, + cellInfoLte.cellIdentityLte.base.earfcn, + cellInfoLte.cellIdentityLte.base.mcc, + cellInfoLte.cellIdentityLte.base.mnc, + cellInfoLte.cellIdentityLte.operatorNames.alphaLong, + cellInfoLte.cellIdentityLte.operatorNames.alphaShort, + cellInfoLte.signalStrengthLte.signalStrength, + cellInfoLte.signalStrengthLte.rsrp, + cellInfoLte.signalStrengthLte.rsrq, + cellInfoLte.signalStrengthLte.rssnr, + cellInfoLte.signalStrengthLte.cqi, + cellInfoLte.signalStrengthLte.timingAdvance); + break; + } + + case CellInfoType.WCDMA: { + android.hardware.radio.V1_2.CellInfoWcdma cellInfoWcdma = record.wcdma.get(0); + writeToParcelForWcdma( + p, + cellInfoWcdma.cellIdentityWcdma.base.lac, + cellInfoWcdma.cellIdentityWcdma.base.cid, + cellInfoWcdma.cellIdentityWcdma.base.psc, + cellInfoWcdma.cellIdentityWcdma.base.uarfcn, + cellInfoWcdma.cellIdentityWcdma.base.mcc, + cellInfoWcdma.cellIdentityWcdma.base.mnc, + cellInfoWcdma.cellIdentityWcdma.operatorNames.alphaLong, + cellInfoWcdma.cellIdentityWcdma.operatorNames.alphaShort, + cellInfoWcdma.signalStrengthWcdma.signalStrength, + cellInfoWcdma.signalStrengthWcdma.bitErrorRate); break; } diff --git a/com/android/internal/telephony/RadioIndication.java b/com/android/internal/telephony/RadioIndication.java index a7d2418b..bcae450f 100644 --- a/com/android/internal/telephony/RadioIndication.java +++ b/com/android/internal/telephony/RadioIndication.java @@ -640,6 +640,12 @@ public class RadioIndication extends IRadioIndication.Stub { responseCellInfos(indicationType, result); } + /** Incremental network scan results with HAL V1_2 */ + public void networkScanResult_1_2(int indicationType, + android.hardware.radio.V1_2.NetworkScanResult result) { + responseCellInfos_1_2(indicationType, result); + } + public void imsNetworkStateChanged(int indicationType) { mRil.processIndication(indicationType); @@ -842,4 +848,15 @@ public class RadioIndication extends IRadioIndication.Stub { if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr); mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null)); } + + private void responseCellInfos_1_2(int indicationType, + android.hardware.radio.V1_2.NetworkScanResult result) { + mRil.processIndication(indicationType); + + NetworkScanResult nsr = null; + ArrayList<CellInfo> infos = RIL.convertHalCellInfoList_1_2(result.networkInfos); + nsr = new NetworkScanResult(result.status, result.error, infos); + if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr); + mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null)); + } } diff --git a/com/android/internal/telephony/RadioResponse.java b/com/android/internal/telephony/RadioResponse.java index 3990d241..84c1d573 100644 --- a/com/android/internal/telephony/RadioResponse.java +++ b/com/android/internal/telephony/RadioResponse.java @@ -946,6 +946,16 @@ public class RadioResponse extends IRadioResponse.Stub { /** * @param responseInfo Response info struct containing response type, serial no. and error + * @param cellInfo List of current cell information known to radio + */ + public void getCellInfoListResponse_1_2( + RadioResponseInfo responseInfo, + ArrayList<android.hardware.radio.V1_2.CellInfo> cellInfo) { + responseCellInfoList_1_2(responseInfo, cellInfo); + } + + /** + * @param responseInfo Response info struct containing response type, serial no. and error */ public void setCellInfoListRateResponse(RadioResponseInfo responseInfo) { responseVoid(responseInfo); @@ -1681,6 +1691,20 @@ public class RadioResponse extends IRadioResponse.Stub { } } + private void responseCellInfoList_1_2( + RadioResponseInfo responseInfo, + ArrayList<android.hardware.radio.V1_2.CellInfo> cellInfo) { + RILRequest rr = mRil.processResponse(responseInfo); + + if (rr != null) { + ArrayList<CellInfo> ret = RIL.convertHalCellInfoList_1_2(cellInfo); + if (responseInfo.error == RadioError.NONE) { + sendMessageResponse(rr.mResult, ret); + } + mRil.processResponseDone(rr, responseInfo, ret); + } + } + private void responseActivityData(RadioResponseInfo responseInfo, ActivityStatsInfo activityInfo) { RILRequest rr = mRil.processResponse(responseInfo); diff --git a/com/android/internal/telephony/ServiceStateTracker.java b/com/android/internal/telephony/ServiceStateTracker.java index 0f4c9e4a..a9523bbd 100644 --- a/com/android/internal/telephony/ServiceStateTracker.java +++ b/com/android/internal/telephony/ServiceStateTracker.java @@ -210,6 +210,7 @@ public class ServiceStateTracker extends Handler { protected static final int EVENT_RADIO_POWER_FROM_CARRIER = 51; protected static final int EVENT_SIM_NOT_INSERTED = 52; protected static final int EVENT_IMS_SERVICE_STATE_CHANGED = 53; + protected static final int EVENT_RADIO_POWER_OFF_DONE = 54; protected static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; @@ -1130,6 +1131,16 @@ public class ServiceStateTracker extends Handler { } break; + case EVENT_RADIO_POWER_OFF_DONE: + if (DBG) log("EVENT_RADIO_POWER_OFF_DONE"); + if (mDeviceShuttingDown && mCi.getRadioState().isAvailable()) { + // during shutdown the modem may not send radio state changed event + // as a result of radio power request + // Hence, issuing shut down regardless of radio power response + mCi.requestShutdown(null); + } + break; + // GSM case EVENT_SIM_READY: // Reset the mPreviousSubId so we treat a SIM power bounce @@ -4403,7 +4414,7 @@ public class ServiceStateTracker extends Handler { mPhone.mCT.mForegroundCall.hangupIfAlive(); } - mCi.setRadioPower(false, null); + mCi.setRadioPower(false, obtainMessage(EVENT_RADIO_POWER_OFF_DONE)); } diff --git a/com/android/internal/telephony/SubscriptionInfoUpdater.java b/com/android/internal/telephony/SubscriptionInfoUpdater.java index 1a2e09c7..983c436f 100644 --- a/com/android/internal/telephony/SubscriptionInfoUpdater.java +++ b/com/android/internal/telephony/SubscriptionInfoUpdater.java @@ -273,7 +273,8 @@ public class SubscriptionInfoUpdater extends Handler { if (ar.exception == null) { if (ar.result != null) { byte[] data = (byte[])ar.result; - mIccId[slotId] = IccUtils.bchToString(data, 0, data.length); + mIccId[slotId] = stripIccIdSuffix( + IccUtils.bchToString(data, 0, data.length)); } else { logd("Null ar"); mIccId[slotId] = ICCID_STRING_FOR_NO_SIM; @@ -399,11 +400,11 @@ public class SubscriptionInfoUpdater extends Handler { logd("handleSimLoaded: IccRecords null"); return; } - if (records.getFullIccId() == null) { + if (stripIccIdSuffix(records.getFullIccId()) == null) { logd("onRecieve: IccID null"); return; } - mIccId[slotId] = records.getFullIccId(); + mIccId[slotId] = stripIccIdSuffix(records.getFullIccId()); if (isAllIccIdQueryDone()) { updateSubscriptionInfoByIccId(); @@ -820,6 +821,15 @@ public class SubscriptionInfoUpdater extends Handler { IntentBroadcaster.getInstance().broadcastStickyIntent(i, slotId); } + // Remove trailing F's from full hexadecimal IccId, as they should be considered padding + private String stripIccIdSuffix(String hexIccId) { + if (hexIccId == null) { + return null; + } else { + return hexIccId.replaceAll("(?i)f*$", ""); + } + } + public void dispose() { logd("[dispose]"); mContext.unregisterReceiver(sReceiver); diff --git a/com/android/internal/telephony/TelephonyComponentFactory.java b/com/android/internal/telephony/TelephonyComponentFactory.java index 193d29e1..82a08238 100644 --- a/com/android/internal/telephony/TelephonyComponentFactory.java +++ b/com/android/internal/telephony/TelephonyComponentFactory.java @@ -77,6 +77,10 @@ public class TelephonyComponentFactory { return new CarrierActionAgent(phone); } + public CarrierIdentifier makeCarrierIdentifier(Phone phone) { + return new CarrierIdentifier(phone); + } + public IccPhoneBookInterfaceManager makeIccPhoneBookInterfaceManager(Phone phone) { return new IccPhoneBookInterfaceManager(phone); } diff --git a/com/android/internal/telephony/cat/ComprehensionTlv.java b/com/android/internal/telephony/cat/ComprehensionTlv.java index e2522a4f..d4ad532b 100644 --- a/com/android/internal/telephony/cat/ComprehensionTlv.java +++ b/com/android/internal/telephony/cat/ComprehensionTlv.java @@ -29,7 +29,7 @@ import java.util.List; * * {@hide} */ -class ComprehensionTlv { +public class ComprehensionTlv { private static final String LOG_TAG = "ComprehensionTlv"; private int mTag; private boolean mCr; diff --git a/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java b/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java index 5f2e561b..d27a7581 100644 --- a/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java +++ b/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java @@ -18,6 +18,7 @@ package com.android.internal.telephony.cdma.sms; import android.util.SparseBooleanArray; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.SmsAddress; import com.android.internal.telephony.cdma.sms.UserData; import com.android.internal.util.HexDump; @@ -113,8 +114,8 @@ public class CdmaSmsAddress extends SmsAddress { * share code and logic with GSM. Also, gather all DTMF/BCD * processing code in one place. */ - - private static byte[] parseToDtmf(String address) { + @VisibleForTesting + public static byte[] parseToDtmf(String address) { int digits = address.length(); byte[] result = new byte[digits]; for (int i = 0; i < digits; i++) { @@ -196,33 +197,46 @@ public class CdmaSmsAddress extends SmsAddress { public static CdmaSmsAddress parse(String address) { CdmaSmsAddress addr = new CdmaSmsAddress(); addr.address = address; - addr.ton = CdmaSmsAddress.TON_UNKNOWN; - byte[] origBytes = null; + addr.ton = TON_UNKNOWN; + addr.digitMode = DIGIT_MODE_4BIT_DTMF; + addr.numberPlan = NUMBERING_PLAN_UNKNOWN; + addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK; + + byte[] origBytes; String filteredAddr = filterNumericSugar(address); - if (filteredAddr != null) { - origBytes = parseToDtmf(filteredAddr); - } - if (origBytes != null) { - addr.digitMode = DIGIT_MODE_4BIT_DTMF; - addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK; - if (address.indexOf('+') != -1) { - addr.ton = TON_INTERNATIONAL_OR_IP; - } - } else { - filteredAddr = filterWhitespace(address); - origBytes = UserData.stringToAscii(filteredAddr); - if (origBytes == null) { - return null; - } + if (address.contains("+") || filteredAddr == null) { + // 3GPP2 C.S0015-B section 3.4.3.3 Address Parameters + // NUMBER_MODE should set to 1 for network address and email address. addr.digitMode = DIGIT_MODE_8BIT_CHAR; addr.numberMode = NUMBER_MODE_DATA_NETWORK; - if (address.indexOf('@') != -1) { + filteredAddr = filterWhitespace(address); + + if (address.contains("@")) { + // This is an email address addr.ton = TON_NATIONAL_OR_EMAIL; + } else if (address.contains("+") && filterNumericSugar(address) != null) { + // This is an international number + // 3GPP2 C.S0015-B section 3.4.3.3 Address Parameters + // digit mode is set to 1 and number mode is set to 0, type of number should set + // to the value correspond to the value in 3GPP2 C.S005-D, table2.7.1.3.2.4-2 + addr.ton = TON_INTERNATIONAL_OR_IP; + addr.numberPlan = NUMBERING_PLAN_ISDN_TELEPHONY; + addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK; + filteredAddr = filterNumericSugar(address); } + + origBytes = UserData.stringToAscii(filteredAddr); + } else { + // The address is not an international number and it only contains digit and *# + origBytes = parseToDtmf(filteredAddr); + } + + if (origBytes == null) { + return null; } + addr.origBytes = origBytes; addr.numberOfDigits = origBytes.length; return addr; } - } diff --git a/com/android/internal/telephony/dataconnection/ApnSetting.java b/com/android/internal/telephony/dataconnection/ApnSetting.java index ce8318d3..0eeed6a0 100644 --- a/com/android/internal/telephony/dataconnection/ApnSetting.java +++ b/com/android/internal/telephony/dataconnection/ApnSetting.java @@ -23,6 +23,7 @@ import android.telephony.CarrierConfigManager; import android.telephony.Rlog; import android.telephony.ServiceState; import android.text.TextUtils; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.Phone; @@ -48,6 +49,7 @@ public class ApnSetting { static final String V2_FORMAT_REGEX = "^\\[ApnSettingV2\\]\\s*"; static final String V3_FORMAT_REGEX = "^\\[ApnSettingV3\\]\\s*"; + static final String TAG = "ApnSetting"; public final String carrier; public final String apn; @@ -100,6 +102,7 @@ public class ApnSetting { * "spn": Service provider name. * "imsi": IMSI. * "gid": Group identifier level 1. + * "iccid": ICCID */ public final String mvnoType; /** @@ -107,6 +110,7 @@ public class ApnSetting { * "spn": A MOBILE, BEN NL * "imsi": 302720x94, 2060188 * "gid": 4E, 33 + * "iccid": 898603 etc. */ public final String mvnoMatchData; @@ -362,6 +366,17 @@ public class ApnSetting { return false; } + private static boolean iccidMatches(String mvnoData, String iccId) { + String[] mvnoIccidList = mvnoData.split(","); + for (String mvnoIccid : mvnoIccidList) { + if (iccId.startsWith(mvnoIccid)) { + Log.d(TAG, "mvno icc id match found"); + return true; + } + } + return false; + } + private static boolean imsiMatches(String imsiDB, String imsiSIM) { // Note: imsiDB value has digit number or 'x' character for seperating USIM information // for MVNO operator. And then digit number is matched at same order and 'x' character @@ -404,7 +419,13 @@ public class ApnSetting { gid1.substring(0, mvno_match_data_length).equalsIgnoreCase(mvnoMatchData)) { return true; } + } else if (mvnoType.equalsIgnoreCase("iccid")) { + String iccId = r.getIccId(); + if ((iccId != null) && iccidMatches(mvnoMatchData, iccId)) { + return true; + } } + return false; } diff --git a/com/android/internal/telephony/dataconnection/DataCallResponse.java b/com/android/internal/telephony/dataconnection/DataCallResponse.java index bc02b979..5196bcee 100644 --- a/com/android/internal/telephony/dataconnection/DataCallResponse.java +++ b/com/android/internal/telephony/dataconnection/DataCallResponse.java @@ -17,18 +17,9 @@ package com.android.internal.telephony.dataconnection; -import android.net.LinkAddress; -import android.net.LinkProperties; -import android.net.NetworkUtils; -import android.net.RouteInfo; import android.os.SystemProperties; -import android.telephony.Rlog; import android.text.TextUtils; -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.UnknownHostException; - /** * This is RIL_Data_Call_Response_v5 from ril.h */ @@ -44,34 +35,10 @@ public class DataCallResponse { public final String ifname; public final String [] addresses; public final String [] dnses; - // TODO: Change this to final if possible. - public String[] gateways; + public final String[] gateways; public final String [] pcscf; public final int mtu; - /** - * Class returned by onSetupConnectionCompleted. - */ - public enum SetupResult { - SUCCESS, - ERR_BadCommand, - ERR_UnacceptableParameter, - ERR_GetLastErrorFromRil, - ERR_Stale, - ERR_RilError; - - public DcFailCause mFailCause; - - SetupResult() { - mFailCause = DcFailCause.fromInt(0); - } - - @Override - public String toString() { - return name() + " SetupResult.mFailCause=" + mFailCause; - } - } - public DataCallResponse(int status, int suggestedRetryTime, int cid, int active, String type, String ifname, String addresses, String dnses, String gateways, String pcscf, int mtu) { @@ -86,7 +53,22 @@ public class DataCallResponse { } this.addresses = TextUtils.isEmpty(addresses) ? new String[0] : addresses.split(" "); this.dnses = TextUtils.isEmpty(dnses) ? new String[0] : dnses.split(" "); - this.gateways = TextUtils.isEmpty(gateways) ? new String[0] : gateways.split(" "); + + String[] myGateways = TextUtils.isEmpty(gateways) + ? new String[0] : gateways.split(" "); + + // set gateways + if (myGateways.length == 0) { + String propertyPrefix = "net." + this.ifname + "."; + String sysGateways = SystemProperties.get(propertyPrefix + "gw"); + if (sysGateways != null) { + myGateways = sysGateways.split(" "); + } else { + myGateways = new String[0]; + } + } + this.gateways = myGateways; + this.pcscf = TextUtils.isEmpty(pcscf) ? new String[0] : pcscf.split(" "); this.mtu = mtu; } @@ -129,147 +111,4 @@ public class DataCallResponse { sb.append("]}"); return sb.toString(); } - - public SetupResult setLinkProperties(LinkProperties linkProperties, - boolean okToUseSystemPropertyDns) { - SetupResult result; - - // Start with clean network properties and if we have - // a failure we'll clear again at the bottom of this code. - if (linkProperties == null) - linkProperties = new LinkProperties(); - else - linkProperties.clear(); - - if (status == DcFailCause.NONE.getErrorCode()) { - String propertyPrefix = "net." + ifname + "."; - - try { - // set interface name - linkProperties.setInterfaceName(ifname); - - // set link addresses - if (addresses != null && addresses.length > 0) { - for (String addr : addresses) { - addr = addr.trim(); - if (addr.isEmpty()) continue; - LinkAddress la; - int addrPrefixLen; - - String [] ap = addr.split("/"); - if (ap.length == 2) { - addr = ap[0]; - addrPrefixLen = Integer.parseInt(ap[1]); - } else { - addrPrefixLen = 0; - } - InetAddress ia; - try { - ia = NetworkUtils.numericToInetAddress(addr); - } catch (IllegalArgumentException e) { - throw new UnknownHostException("Non-numeric ip addr=" + addr); - } - if (! ia.isAnyLocalAddress()) { - if (addrPrefixLen == 0) { - // Assume point to point - addrPrefixLen = (ia instanceof Inet4Address) ? 32 : 128; - } - if (DBG) Rlog.d(LOG_TAG, "addr/pl=" + addr + "/" + addrPrefixLen); - try { - la = new LinkAddress(ia, addrPrefixLen); - } catch (IllegalArgumentException e) { - throw new UnknownHostException("Bad parameter for LinkAddress, ia=" - + ia.getHostAddress() + "/" + addrPrefixLen); - } - - linkProperties.addLinkAddress(la); - } - } - } else { - throw new UnknownHostException("no address for ifname=" + ifname); - } - - // set dns servers - if (dnses != null && dnses.length > 0) { - for (String addr : dnses) { - addr = addr.trim(); - if (addr.isEmpty()) continue; - InetAddress ia; - try { - ia = NetworkUtils.numericToInetAddress(addr); - } catch (IllegalArgumentException e) { - throw new UnknownHostException("Non-numeric dns addr=" + addr); - } - if (! ia.isAnyLocalAddress()) { - linkProperties.addDnsServer(ia); - } - } - } else if (okToUseSystemPropertyDns){ - String dnsServers[] = new String[2]; - dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1"); - dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2"); - for (String dnsAddr : dnsServers) { - dnsAddr = dnsAddr.trim(); - if (dnsAddr.isEmpty()) continue; - InetAddress ia; - try { - ia = NetworkUtils.numericToInetAddress(dnsAddr); - } catch (IllegalArgumentException e) { - throw new UnknownHostException("Non-numeric dns addr=" + dnsAddr); - } - if (! ia.isAnyLocalAddress()) { - linkProperties.addDnsServer(ia); - } - } - } else { - throw new UnknownHostException("Empty dns response and no system default dns"); - } - - // set gateways - if ((gateways == null) || (gateways.length == 0)) { - String sysGateways = SystemProperties.get(propertyPrefix + "gw"); - if (sysGateways != null) { - gateways = sysGateways.split(" "); - } else { - gateways = new String[0]; - } - } - for (String addr : gateways) { - addr = addr.trim(); - if (addr.isEmpty()) continue; - InetAddress ia; - try { - ia = NetworkUtils.numericToInetAddress(addr); - } catch (IllegalArgumentException e) { - throw new UnknownHostException("Non-numeric gateway addr=" + addr); - } - // Allow 0.0.0.0 or :: as a gateway; this indicates a point-to-point interface. - linkProperties.addRoute(new RouteInfo(ia)); - } - - // set interface MTU - // this may clobber the setting read from the APN db, but that's ok - linkProperties.setMtu(mtu); - - result = SetupResult.SUCCESS; - } catch (UnknownHostException e) { - Rlog.d(LOG_TAG, "setLinkProperties: UnknownHostException " + e); - e.printStackTrace(); - result = SetupResult.ERR_UnacceptableParameter; - } - } else { - result = SetupResult.ERR_RilError; - } - - // An error occurred so clear properties - if (result != SetupResult.SUCCESS) { - if(DBG) { - Rlog.d(LOG_TAG, "setLinkProperties: error clearing LinkProperties " + - "status=" + status + " result=" + result); - } - linkProperties.clear(); - } - - return result; - } } diff --git a/com/android/internal/telephony/dataconnection/DataConnection.java b/com/android/internal/telephony/dataconnection/DataConnection.java index 9e4b29cd..3dbcc3d5 100644 --- a/com/android/internal/telephony/dataconnection/DataConnection.java +++ b/com/android/internal/telephony/dataconnection/DataConnection.java @@ -19,12 +19,15 @@ package com.android.internal.telephony.dataconnection; import android.app.PendingIntent; import android.content.Context; import android.net.ConnectivityManager; +import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkAgent; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkMisc; +import android.net.NetworkUtils; import android.net.ProxyInfo; +import android.net.RouteInfo; import android.net.StringNetworkSpecifier; import android.os.AsyncResult; import android.os.Looper; @@ -34,6 +37,7 @@ import android.os.SystemProperties; import android.telephony.Rlog; import android.telephony.ServiceState; import android.telephony.TelephonyManager; +import android.telephony.data.DataProfile; import android.text.TextUtils; import android.util.LocalLog; import android.util.Pair; @@ -59,7 +63,9 @@ import com.android.internal.util.StateMachine; import java.io.FileDescriptor; import java.io.PrintWriter; import java.io.StringWriter; +import java.net.Inet4Address; import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -283,7 +289,7 @@ public class DataConnection extends StateMachine { } public static class UpdateLinkPropertyResult { - public DataCallResponse.SetupResult setupResult = DataCallResponse.SetupResult.SUCCESS; + public SetupResult setupResult = SetupResult.SUCCESS; public LinkProperties oldLp; public LinkProperties newLp; public UpdateLinkPropertyResult(LinkProperties curLp) { @@ -292,6 +298,29 @@ public class DataConnection extends StateMachine { } } + /** + * Class returned by onSetupConnectionCompleted. + */ + public enum SetupResult { + SUCCESS, + ERR_BadCommand, + ERR_UnacceptableParameter, + ERR_GetLastErrorFromRil, + ERR_Stale, + ERR_RilError; + + public DcFailCause mFailCause; + + SetupResult() { + mFailCause = DcFailCause.fromInt(0); + } + + @Override + public String toString() { + return name() + " SetupResult.mFailCause=" + mFailCause; + } + } + public boolean isIpv4Connected() { boolean ret = false; Collection <InetAddress> addresses = mLinkProperties.getAddresses(); @@ -331,12 +360,12 @@ public class DataConnection extends StateMachine { if (newState == null) return result; - DataCallResponse.SetupResult setupResult; + SetupResult setupResult; result.newLp = new LinkProperties(); // set link properties based on data call response result.setupResult = setLinkProperties(newState, result.newLp); - if (result.setupResult != DataCallResponse.SetupResult.SUCCESS) { + if (result.setupResult != SetupResult.SUCCESS) { if (DBG) log("updateLinkProperty failed : " + result.setupResult); return result; } @@ -465,7 +494,7 @@ public class DataConnection extends StateMachine { Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp); msg.obj = cp; - DataProfile dp = new DataProfile(mApnSetting, cp.mProfileId); + DataProfile dp = DcTracker.createDataProfile(mApnSetting, cp.mProfileId); // We need to use the actual modem roaming state instead of the framework roaming state // here. This flag is only passed down to ril_service for picking the correct protocol (for @@ -665,16 +694,16 @@ public class DataConnection extends StateMachine { * @param ar is the result * @return SetupResult. */ - private DataCallResponse.SetupResult onSetupConnectionCompleted(AsyncResult ar) { + private SetupResult onSetupConnectionCompleted(AsyncResult ar) { DataCallResponse response = (DataCallResponse) ar.result; ConnectionParams cp = (ConnectionParams) ar.userObj; - DataCallResponse.SetupResult result; + SetupResult result; if (cp.mTag != mTag) { if (DBG) { log("onSetupConnectionCompleted stale cp.tag=" + cp.mTag + ", mtag=" + mTag); } - result = DataCallResponse.SetupResult.ERR_Stale; + result = SetupResult.ERR_Stale; } else if (ar.exception != null) { if (DBG) { log("onSetupConnectionCompleted failed, ar.exception=" + ar.exception + @@ -684,14 +713,14 @@ public class DataConnection extends StateMachine { if (ar.exception instanceof CommandException && ((CommandException) (ar.exception)).getCommandError() == CommandException.Error.RADIO_NOT_AVAILABLE) { - result = DataCallResponse.SetupResult.ERR_BadCommand; + result = SetupResult.ERR_BadCommand; result.mFailCause = DcFailCause.RADIO_NOT_AVAILABLE; } else { - result = DataCallResponse.SetupResult.ERR_RilError; + result = SetupResult.ERR_RilError; result.mFailCause = DcFailCause.fromInt(response.status); } } else if (response.status != 0) { - result = DataCallResponse.SetupResult.ERR_RilError; + result = SetupResult.ERR_RilError; result.mFailCause = DcFailCause.fromInt(response.status); } else { if (DBG) log("onSetupConnectionCompleted received successful DataCallResponse"); @@ -995,18 +1024,139 @@ public class DataConnection extends StateMachine { return InetAddress.isNumeric(address); } - private DataCallResponse.SetupResult setLinkProperties(DataCallResponse response, - LinkProperties lp) { + private SetupResult setLinkProperties(DataCallResponse response, + LinkProperties linkProperties) { // Check if system property dns usable - boolean okToUseSystemPropertyDns = false; String propertyPrefix = "net." + response.ifname + "."; String dnsServers[] = new String[2]; dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1"); dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2"); - okToUseSystemPropertyDns = isDnsOk(dnsServers); + boolean okToUseSystemPropertyDns = isDnsOk(dnsServers); + + SetupResult result; + + // Start with clean network properties and if we have + // a failure we'll clear again at the bottom of this code. + linkProperties.clear(); + + if (response.status == DcFailCause.NONE.getErrorCode()) { + try { + // set interface name + linkProperties.setInterfaceName(response.ifname); + + // set link addresses + if (response.addresses != null && response.addresses.length > 0) { + for (String addr : response.addresses) { + addr = addr.trim(); + if (addr.isEmpty()) continue; + LinkAddress la; + int addrPrefixLen; + + String [] ap = addr.split("/"); + if (ap.length == 2) { + addr = ap[0]; + addrPrefixLen = Integer.parseInt(ap[1]); + } else { + addrPrefixLen = 0; + } + InetAddress ia; + try { + ia = NetworkUtils.numericToInetAddress(addr); + } catch (IllegalArgumentException e) { + throw new UnknownHostException("Non-numeric ip addr=" + addr); + } + if (!ia.isAnyLocalAddress()) { + if (addrPrefixLen == 0) { + // Assume point to point + addrPrefixLen = (ia instanceof Inet4Address) ? 32 : 128; + } + if (DBG) log("addr/pl=" + addr + "/" + addrPrefixLen); + try { + la = new LinkAddress(ia, addrPrefixLen); + } catch (IllegalArgumentException e) { + throw new UnknownHostException("Bad parameter for LinkAddress, ia=" + + ia.getHostAddress() + "/" + addrPrefixLen); + } - // set link properties based on data call response - return response.setLinkProperties(lp, okToUseSystemPropertyDns); + linkProperties.addLinkAddress(la); + } + } + } else { + throw new UnknownHostException("no address for ifname=" + response.ifname); + } + + // set dns servers + if (response.dnses != null && response.dnses.length > 0) { + for (String addr : response.dnses) { + addr = addr.trim(); + if (addr.isEmpty()) continue; + InetAddress ia; + try { + ia = NetworkUtils.numericToInetAddress(addr); + } catch (IllegalArgumentException e) { + throw new UnknownHostException("Non-numeric dns addr=" + addr); + } + if (!ia.isAnyLocalAddress()) { + linkProperties.addDnsServer(ia); + } + } + } else if (okToUseSystemPropertyDns) { + dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1"); + dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2"); + for (String dnsAddr : dnsServers) { + dnsAddr = dnsAddr.trim(); + if (dnsAddr.isEmpty()) continue; + InetAddress ia; + try { + ia = NetworkUtils.numericToInetAddress(dnsAddr); + } catch (IllegalArgumentException e) { + throw new UnknownHostException("Non-numeric dns addr=" + dnsAddr); + } + if (!ia.isAnyLocalAddress()) { + linkProperties.addDnsServer(ia); + } + } + } else { + throw new UnknownHostException("Empty dns response and no system default dns"); + } + + for (String addr : response.gateways) { + addr = addr.trim(); + if (addr.isEmpty()) continue; + InetAddress ia; + try { + ia = NetworkUtils.numericToInetAddress(addr); + } catch (IllegalArgumentException e) { + throw new UnknownHostException("Non-numeric gateway addr=" + addr); + } + // Allow 0.0.0.0 or :: as a gateway; this indicates a point-to-point interface. + linkProperties.addRoute(new RouteInfo(ia)); + } + + // set interface MTU + // this may clobber the setting read from the APN db, but that's ok + linkProperties.setMtu(response.mtu); + + result = SetupResult.SUCCESS; + } catch (UnknownHostException e) { + log("setLinkProperties: UnknownHostException " + e); + e.printStackTrace(); + result = SetupResult.ERR_UnacceptableParameter; + } + } else { + result = SetupResult.ERR_RilError; + } + + // An error occurred so clear properties + if (result != SetupResult.SUCCESS) { + if (DBG) { + log("setLinkProperties: error clearing LinkProperties status=" + response.status + + " result=" + result); + } + linkProperties.clear(); + } + + return result; } /** @@ -1421,8 +1571,8 @@ public class DataConnection extends StateMachine { ar = (AsyncResult) msg.obj; cp = (ConnectionParams) ar.userObj; - DataCallResponse.SetupResult result = onSetupConnectionCompleted(ar); - if (result != DataCallResponse.SetupResult.ERR_Stale) { + SetupResult result = onSetupConnectionCompleted(ar); + if (result != SetupResult.ERR_Stale) { if (mConnectionParams != cp) { loge("DcActivatingState: WEIRD mConnectionsParams:"+ mConnectionParams + " != cp:" + cp); diff --git a/com/android/internal/telephony/dataconnection/DataProfile.java b/com/android/internal/telephony/dataconnection/DataProfile.java deleted file mode 100644 index 48a8107d..00000000 --- a/com/android/internal/telephony/dataconnection/DataProfile.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.telephony.dataconnection; - -import android.telephony.ServiceState; -import android.text.TextUtils; - -import com.android.internal.telephony.RILConstants; - -public class DataProfile { - - static final int TYPE_COMMON = 0; - static final int TYPE_3GPP = 1; - static final int TYPE_3GPP2 = 2; - - //id of the data profile - public final int profileId; - //the APN to connect to - public final String apn; - //one of the PDP_type values in TS 27.007 section 10.1.1. - //For example, "IP", "IPV6", "IPV4V6", or "PPP". - public final String protocol; - //authentication protocol used for this PDP context - //(None: 0, PAP: 1, CHAP: 2, PAP&CHAP: 3) - public final int authType; - //the username for APN, or NULL - public final String user; - //the password for APN, or NULL - public final String password; - //the profile type, TYPE_COMMON, TYPE_3GPP, TYPE_3GPP2 - public final int type; - //the period in seconds to limit the maximum connections - public final int maxConnsTime; - //the maximum connections during maxConnsTime - public final int maxConns; - //the required wait time in seconds after a successful UE initiated - //disconnect of a given PDN connection before the device can send - //a new PDN connection request for that given PDN - public final int waitTime; - //true to enable the profile, false to disable - public final boolean enabled; - //supported APN types bitmap. See RIL_ApnTypes for the value of each bit. - public final int supportedApnTypesBitmap; - //one of the PDP_type values in TS 27.007 section 10.1.1 used on roaming network. - //For example, "IP", "IPV6", "IPV4V6", or "PPP". - public final String roamingProtocol; - //The bearer bitmap. See RIL_RadioAccessFamily for the value of each bit. - public final int bearerBitmap; - //maximum transmission unit (MTU) size in bytes - public final int mtu; - //the MVNO type: possible values are "imsi", "gid", "spn" - public final String mvnoType; - //MVNO match data. For example, SPN: A MOBILE, BEN NL, ... - //IMSI: 302720x94, 2060188, ... - //GID: 4E, 33, ... - public final String mvnoMatchData; - //indicating the data profile was sent to the modem through setDataProfile earlier. - public final boolean modemCognitive; - - DataProfile(int profileId, String apn, String protocol, int authType, - String user, String password, int type, int maxConnsTime, int maxConns, - int waitTime, boolean enabled, int supportedApnTypesBitmap, String roamingProtocol, - int bearerBitmap, int mtu, String mvnoType, String mvnoMatchData, - boolean modemCognitive) { - - this.profileId = profileId; - this.apn = apn; - this.protocol = protocol; - if (authType == -1) { - authType = TextUtils.isEmpty(user) ? RILConstants.SETUP_DATA_AUTH_NONE - : RILConstants.SETUP_DATA_AUTH_PAP_CHAP; - } - this.authType = authType; - this.user = user; - this.password = password; - this.type = type; - this.maxConnsTime = maxConnsTime; - this.maxConns = maxConns; - this.waitTime = waitTime; - this.enabled = enabled; - - this.supportedApnTypesBitmap = supportedApnTypesBitmap; - this.roamingProtocol = roamingProtocol; - this.bearerBitmap = bearerBitmap; - this.mtu = mtu; - this.mvnoType = mvnoType; - this.mvnoMatchData = mvnoMatchData; - this.modemCognitive = modemCognitive; - } - - public DataProfile(ApnSetting apn) { - this(apn, apn.profileId); - } - - public DataProfile(ApnSetting apn, int profileId) { - this(profileId, apn.apn, apn.protocol, - apn.authType, apn.user, apn.password, apn.bearerBitmask == 0 - ? TYPE_COMMON : (ServiceState.bearerBitmapHasCdma(apn.bearerBitmask) - ? TYPE_3GPP2 : TYPE_3GPP), - apn.maxConnsTime, apn.maxConns, apn.waitTime, apn.carrierEnabled, apn.typesBitmap, - apn.roamingProtocol, apn.bearerBitmask, apn.mtu, apn.mvnoType, apn.mvnoMatchData, - apn.modemCognitive); - } - - @Override - public String toString() { - return "DataProfile=" + profileId + "/" + apn + "/" + protocol + "/" + authType - + "/" + user + "/" + password + "/" + type + "/" + maxConnsTime - + "/" + maxConns + "/" + waitTime + "/" + enabled + "/" + supportedApnTypesBitmap - + "/" + roamingProtocol + "/" + bearerBitmap + "/" + mtu + "/" + mvnoType + "/" - + mvnoMatchData + "/" + modemCognitive; - } - - @Override - public boolean equals(Object o) { - if (o instanceof DataProfile == false) return false; - return (o == this || toString().equals(o.toString())); - } -} diff --git a/com/android/internal/telephony/dataconnection/DcTracker.java b/com/android/internal/telephony/dataconnection/DcTracker.java index fb756cd9..540ec544 100644 --- a/com/android/internal/telephony/dataconnection/DcTracker.java +++ b/com/android/internal/telephony/dataconnection/DcTracker.java @@ -65,6 +65,7 @@ import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyManager; import android.telephony.cdma.CdmaCellLocation; +import android.telephony.data.DataProfile; import android.telephony.gsm.GsmCellLocation; import android.text.TextUtils; import android.util.EventLog; @@ -2093,7 +2094,7 @@ public class DcTracker extends Handler { } else { if (DBG) log("setInitialAttachApn: X selected Apn=" + initialAttachApnSetting); - mPhone.mCi.setInitialAttachApn(new DataProfile(initialAttachApnSetting), + mPhone.mCi.setInitialAttachApn(createDataProfile(initialAttachApnSetting), mPhone.getServiceState().getDataRoamingFromRegistration(), null); } } @@ -3281,7 +3282,7 @@ public class DcTracker extends Handler { ArrayList<DataProfile> dps = new ArrayList<DataProfile>(); for (ApnSetting apn : mAllApnSettings) { if (apn.modemCognitive) { - DataProfile dp = new DataProfile(apn); + DataProfile dp = createDataProfile(apn); if (!dps.contains(dp)) { dps.add(dp); } @@ -4800,4 +4801,25 @@ public class DcTracker extends Handler { } } + private static DataProfile createDataProfile(ApnSetting apn) { + return createDataProfile(apn, apn.profileId); + } + + @VisibleForTesting + public static DataProfile createDataProfile(ApnSetting apn, int profileId) { + int profileType; + if (apn.bearerBitmask == 0) { + profileType = DataProfile.TYPE_COMMON; + } else if (ServiceState.bearerBitmapHasCdma(apn.bearerBitmask)) { + profileType = DataProfile.TYPE_3GPP2; + } else { + profileType = DataProfile.TYPE_3GPP; + } + + return new DataProfile(profileId, apn.apn, apn.protocol, + apn.authType, apn.user, apn.password, profileType, + apn.maxConnsTime, apn.maxConns, apn.waitTime, apn.carrierEnabled, apn.typesBitmap, + apn.roamingProtocol, apn.bearerBitmask, apn.mtu, apn.mvnoType, apn.mvnoMatchData, + apn.modemCognitive); + } } diff --git a/com/android/internal/telephony/euicc/EuiccController.java b/com/android/internal/telephony/euicc/EuiccController.java index ac2a0392..dc847187 100644 --- a/com/android/internal/telephony/euicc/EuiccController.java +++ b/com/android/internal/telephony/euicc/EuiccController.java @@ -38,6 +38,7 @@ import android.telephony.UiccAccessRule; import android.telephony.euicc.DownloadableSubscription; import android.telephony.euicc.EuiccInfo; import android.telephony.euicc.EuiccManager; +import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -226,6 +227,7 @@ public class EuiccController extends IEuiccController.Stub { addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM, mCallingPackage, + false /* confirmationCodeRetried */, getOperationForDeactivateSim()); break; default: @@ -306,6 +308,7 @@ public class EuiccController extends IEuiccController.Stub { Intent extrasIntent = new Intent(); addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_NO_PRIVILEGES, mCallingPackage, + false /* confirmationCodeRetried */, EuiccOperation.forDownloadNoPrivileges( mCallingToken, mSubscription, mSwitchAfterDownload, mCallingPackage)); @@ -354,6 +357,7 @@ public class EuiccController extends IEuiccController.Stub { Intent extrasIntent = new Intent(); addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_NO_PRIVILEGES, mCallingPackage, + false /* confirmationCodeRetried */, EuiccOperation.forDownloadNoPrivileges( mCallingToken, subscription, mSwitchAfterDownload, mCallingPackage)); @@ -407,15 +411,21 @@ public class EuiccController extends IEuiccController.Stub { addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM, callingPackage, + false /* confirmationCodeRetried */, EuiccOperation.forDownloadDeactivateSim( callingToken, subscription, switchAfterDownload, callingPackage)); break; case EuiccService.RESULT_NEED_CONFIRMATION_CODE: resultCode = RESOLVABLE_ERROR; + boolean retried = false; + if (!TextUtils.isEmpty(subscription.getConfirmationCode())) { + retried = true; + } addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_CONFIRMATION_CODE, callingPackage, + retried /* confirmationCodeRetried */, EuiccOperation.forDownloadConfirmationCode( callingToken, subscription, switchAfterDownload, callingPackage)); @@ -520,6 +530,7 @@ public class EuiccController extends IEuiccController.Stub { addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM, mCallingPackage, + false /* confirmationCodeRetried */, EuiccOperation.forGetDefaultListDeactivateSim( mCallingToken, mCallingPackage)); break; @@ -671,6 +682,7 @@ public class EuiccController extends IEuiccController.Stub { addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_NO_PRIVILEGES, callingPackage, + false /* confirmationCodeRetried */, EuiccOperation.forSwitchNoPrivileges( token, subscriptionId, callingPackage)); sendResult(callbackIntent, RESOLVABLE_ERROR, extrasIntent); @@ -716,6 +728,7 @@ public class EuiccController extends IEuiccController.Stub { addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM, callingPackage, + false /* confirmationCodeRetried */, EuiccOperation.forSwitchDeactivateSim( callingToken, subscriptionId, callingPackage)); break; @@ -883,11 +896,13 @@ public class EuiccController extends IEuiccController.Stub { /** Add a resolution intent to the given extras intent. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public void addResolutionIntent(Intent extrasIntent, String resolutionAction, - String callingPackage, EuiccOperation op) { + String callingPackage, boolean confirmationCodeRetried, EuiccOperation op) { Intent intent = new Intent(EuiccManager.ACTION_RESOLVE_ERROR); intent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_ACTION, resolutionAction); intent.putExtra(EuiccService.EXTRA_RESOLUTION_CALLING_PACKAGE, callingPackage); + intent.putExtra(EuiccService.EXTRA_RESOLUTION_CONFIRMATION_CODE_RETRIED, + confirmationCodeRetried); intent.putExtra(EXTRA_OPERATION, op); PendingIntent resolutionIntent = PendingIntent.getActivity( mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_ONE_SHOT); diff --git a/com/android/internal/telephony/euicc/EuiccOperation.java b/com/android/internal/telephony/euicc/EuiccOperation.java index 84df52e4..148d9dc1 100644 --- a/com/android/internal/telephony/euicc/EuiccOperation.java +++ b/com/android/internal/telephony/euicc/EuiccOperation.java @@ -309,9 +309,7 @@ public class EuiccOperation implements Parcelable { if (TextUtils.isEmpty(confirmationCode)) { fail(callbackIntent); } else { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { - mDownloadableSubscription.setConfirmationCode(confirmationCode); - } + mDownloadableSubscription.setConfirmationCode(confirmationCode); EuiccController.get() .downloadSubscription( mDownloadableSubscription, diff --git a/com/android/internal/telephony/gsm/GsmMmiCode.java b/com/android/internal/telephony/gsm/GsmMmiCode.java index b9a07f9f..3376e2b2 100644 --- a/com/android/internal/telephony/gsm/GsmMmiCode.java +++ b/com/android/internal/telephony/gsm/GsmMmiCode.java @@ -863,6 +863,13 @@ public final class GsmMmiCode extends Handler implements MmiCode { return mIsSsInfo; } + public static boolean isVoiceUnconditionalForwarding(int reason, int serviceClass) { + return (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) + || (reason == CommandsInterface.CF_REASON_ALL)) + && (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) + || (serviceClass == CommandsInterface.SERVICE_CLASS_NONE))); + } + /** Process a MMI code or short code...anything that isn't a dialing number */ public void processCode() throws CallStateException { @@ -933,12 +940,6 @@ public final class GsmMmiCode extends Handler implements MmiCode { throw new RuntimeException ("invalid action"); } - int isSettingUnconditionalVoice = - (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) || - (reason == CommandsInterface.CF_REASON_ALL)) && - (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) || - (serviceClass == CommandsInterface.SERVICE_CLASS_NONE))) ? 1 : 0; - int isEnableDesired = ((cfAction == CommandsInterface.CF_ACTION_ENABLE) || (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0; @@ -947,7 +948,7 @@ public final class GsmMmiCode extends Handler implements MmiCode { mPhone.mCi.setCallForward(cfAction, reason, serviceClass, dialingNumber, time, obtainMessage( EVENT_SET_CFF_COMPLETE, - isSettingUnconditionalVoice, + isVoiceUnconditionalForwarding(reason, serviceClass) ? 1 : 0, isEnableDesired, this)); } } else if (isServiceCodeCallBarring(mSc)) { diff --git a/com/android/internal/telephony/ims/ImsResolver.java b/com/android/internal/telephony/ims/ImsResolver.java index 2f790b7a..4a649840 100644 --- a/com/android/internal/telephony/ims/ImsResolver.java +++ b/com/android/internal/telephony/ims/ImsResolver.java @@ -38,8 +38,9 @@ import android.util.Log; import android.util.Pair; import android.util.SparseArray; -import com.android.ims.internal.IImsServiceController; -import com.android.ims.internal.IImsServiceFeatureListener; +import com.android.ims.internal.IImsMMTelFeature; +import com.android.ims.internal.IImsRcsFeature; +import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.PhoneConstants; @@ -295,21 +296,58 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal } /** - * Returns the {@link IImsServiceController} that corresponds to the given slot Id and IMS - * feature or {@link null} if the service is not available. If an ImsServiceController is - * available, the {@link IImsServiceFeatureListener} callback is registered as a listener for - * feature updates. - * @param slotId The SIM slot that we are requesting the {@link IImsServiceController} for. - * @param feature The IMS Feature we are requesting. + * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id or {@link null} if + * the service is not available. If an IImsMMTelFeature is available, the + * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates. + * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for. * @param callback Listener that will send updates to ImsManager when there are updates to - * ImsServiceController. - * @return {@link IImsServiceController} interface for the feature specified or {@link null} if - * it is unavailable. + * the feature. + * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable. */ - public IImsServiceController getImsServiceControllerAndListen(int slotId, int feature, - IImsServiceFeatureListener callback) { - if (slotId < 0 || slotId >= mNumSlots || feature <= ImsFeature.INVALID - || feature >= ImsFeature.MAX) { + public IImsMMTelFeature getMMTelFeatureAndListen(int slotId, + IImsServiceFeatureCallback callback) { + ImsServiceController controller = getImsServiceControllerAndListen(slotId, ImsFeature.MMTEL, + callback); + return (controller != null) ? controller.getMMTelFeature(slotId) : null; + } + + /** + * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id for emergency + * calling or {@link null} if the service is not available. If an IImsMMTelFeature is + * available, the {@link IImsServiceFeatureCallback} callback is registered as a listener for + * feature updates. + * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for. + * @param callback listener that will send updates to ImsManager when there are updates to + * the feature. + * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable. + */ + public IImsMMTelFeature getEmergencyMMTelFeatureAndListen(int slotId, + IImsServiceFeatureCallback callback) { + ImsServiceController controller = getImsServiceControllerAndListen(slotId, + ImsFeature.EMERGENCY_MMTEL, callback); + return (controller != null) ? controller.getEmergencyMMTelFeature(slotId) : null; + } + + /** + * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id for emergency + * calling or {@link null} if the service is not available. If an IImsMMTelFeature is + * available, the {@link IImsServiceFeatureCallback} callback is registered as a listener for + * feature updates. + * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for. + * @param callback listener that will send updates to ImsManager when there are updates to + * the feature. + * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable. + */ + public IImsRcsFeature getRcsFeatureAndListen(int slotId, IImsServiceFeatureCallback callback) { + ImsServiceController controller = getImsServiceControllerAndListen(slotId, ImsFeature.RCS, + callback); + return (controller != null) ? controller.getRcsFeature(slotId) : null; + } + + @VisibleForTesting + public ImsServiceController getImsServiceControllerAndListen(int slotId, int feature, + IImsServiceFeatureCallback callback) { + if (slotId < 0 || slotId >= mNumSlots) { return null; } ImsServiceController controller; @@ -322,7 +360,7 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal } if (controller != null) { controller.addImsServiceFeatureListener(callback); - return controller.getImsServiceController(); + return controller; } return null; } diff --git a/com/android/internal/telephony/ims/ImsServiceController.java b/com/android/internal/telephony/ims/ImsServiceController.java index 6fcefbd3..6f31b50a 100644 --- a/com/android/internal/telephony/ims/ImsServiceController.java +++ b/com/android/internal/telephony/ims/ImsServiceController.java @@ -24,14 +24,18 @@ import android.content.pm.IPackageManager; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; +import android.os.IInterface; import android.os.RemoteException; import android.os.ServiceManager; +import android.telephony.ims.feature.ImsFeature; import android.util.Log; import android.util.Pair; import com.android.ims.internal.IImsFeatureStatusCallback; +import com.android.ims.internal.IImsMMTelFeature; +import com.android.ims.internal.IImsRcsFeature; import com.android.ims.internal.IImsServiceController; -import com.android.ims.internal.IImsServiceFeatureListener; +import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.ExponentialBackoff; @@ -171,15 +175,54 @@ public class ImsServiceController { private boolean mIsBinding = false; // Set of a pair of slotId->feature private HashSet<Pair<Integer, Integer>> mImsFeatures; + // Binder interfaces to the features set in mImsFeatures; + private HashSet<ImsFeatureContainer> mImsFeatureBinders = new HashSet<>(); private IImsServiceController mIImsServiceController; // Easier for testing. private IBinder mImsServiceControllerBinder; private ImsServiceConnection mImsServiceConnection; private ImsDeathRecipient mImsDeathRecipient; - private Set<IImsServiceFeatureListener> mImsStatusCallbacks = new HashSet<>(); + private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = new HashSet<>(); // Only added or removed, never accessed on purpose. private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>(); + private class ImsFeatureContainer { + public int slotId; + public int featureType; + private IInterface mBinder; + + ImsFeatureContainer(int slotId, int featureType, IInterface binder) { + this.slotId = slotId; + this.featureType = featureType; + this.mBinder = binder; + } + + // Casts the IInterface into the binder class we are looking for. + public <T extends IInterface> T resolve(Class<T> className) { + return className.cast(mBinder); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ImsFeatureContainer that = (ImsFeatureContainer) o; + + if (slotId != that.slotId) return false; + if (featureType != that.featureType) return false; + return mBinder != null ? mBinder.equals(that.mBinder) : that.mBinder == null; + } + + @Override + public int hashCode() { + int result = slotId; + result = 31 * result + featureType; + result = 31 * result + (mBinder != null ? mBinder.hashCode() : 0); + return result; + } + } + /** * Container class for the IImsFeatureStatusCallback callback implementation. This class is * never used directly, but we need to keep track of the IImsFeatureStatusCallback @@ -373,12 +416,56 @@ public class ImsServiceController { /** * Add a callback to ImsManager that signals a new feature that the ImsServiceProxy can handle. */ - public void addImsServiceFeatureListener(IImsServiceFeatureListener callback) { + public void addImsServiceFeatureListener(IImsServiceFeatureCallback callback) { synchronized (mLock) { mImsStatusCallbacks.add(callback); } } + /** + * Return the {@Link MMTelFeature} binder on the slot associated with the slotId. + * Used for normal calling. + */ + public IImsMMTelFeature getMMTelFeature(int slotId) { + synchronized (mLock) { + ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.MMTEL); + if (f == null) { + Log.w(LOG_TAG, "Requested null MMTelFeature on slot " + slotId); + return null; + } + return f.resolve(IImsMMTelFeature.class); + } + } + + /** + * Return the {@Link MMTelFeature} binder on the slot associated with the slotId. + * Used for emergency calling only. + */ + public IImsMMTelFeature getEmergencyMMTelFeature(int slotId) { + synchronized (mLock) { + ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.EMERGENCY_MMTEL); + if (f == null) { + Log.w(LOG_TAG, "Requested null Emergency MMTelFeature on slot " + slotId); + return null; + } + return f.resolve(IImsMMTelFeature.class); + } + } + + /** + * Return the {@Link RcsFeature} binder on the slot associated with the slotId. + */ + public IImsRcsFeature getRcsFeature(int slotId) { + synchronized (mLock) { + ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.RCS); + if (f == null) { + Log.w(LOG_TAG, "Requested null RcsFeature on slot " + slotId); + return null; + } + return f.resolve(IImsRcsFeature.class); + } + } + private void removeImsServiceFeatureListener() { synchronized (mLock) { mImsStatusCallbacks.clear(); @@ -407,9 +494,9 @@ public class ImsServiceController { private void sendImsFeatureCreatedCallback(int slot, int feature) { synchronized (mLock) { - for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator(); + for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator(); i.hasNext(); ) { - IImsServiceFeatureListener callbacks = i.next(); + IImsServiceFeatureCallback callbacks = i.next(); try { callbacks.imsFeatureCreated(slot, feature); } catch (RemoteException e) { @@ -424,9 +511,9 @@ public class ImsServiceController { private void sendImsFeatureRemovedCallback(int slot, int feature) { synchronized (mLock) { - for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator(); + for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator(); i.hasNext(); ) { - IImsServiceFeatureListener callbacks = i.next(); + IImsServiceFeatureCallback callbacks = i.next(); try { callbacks.imsFeatureRemoved(slot, feature); } catch (RemoteException e) { @@ -441,9 +528,9 @@ public class ImsServiceController { private void sendImsFeatureStatusChanged(int slot, int feature, int status) { synchronized (mLock) { - for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator(); + for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator(); i.hasNext(); ) { - IImsServiceFeatureListener callbacks = i.next(); + IImsServiceFeatureCallback callbacks = i.next(); try { callbacks.imsStatusChanged(slot, feature, status); } catch (RemoteException e) { @@ -465,8 +552,8 @@ public class ImsServiceController { ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.first, featurePair.second); mFeatureStatusCallbacks.add(c); - mIImsServiceController.createImsFeature(featurePair.first, featurePair.second, - c.getCallback()); + IInterface f = createImsFeature(featurePair.first, featurePair.second, c.getCallback()); + addImsFeatureBinder(featurePair.first, featurePair.second, f); // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController mCallbacks.imsServiceFeatureCreated(featurePair.first, featurePair.second, this); // Send callback to ImsServiceProxy to change supported ImsFeatures @@ -489,6 +576,7 @@ public class ImsServiceController { } mIImsServiceController.removeImsFeature(featurePair.first, featurePair.second, (callbackToRemove != null ? callbackToRemove.getCallback() : null)); + removeImsFeatureBinder(featurePair.first, featurePair.second); // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController mCallbacks.imsServiceFeatureRemoved(featurePair.first, featurePair.second, this); // Send callback to ImsServiceProxy to change supported ImsFeatures @@ -498,6 +586,45 @@ public class ImsServiceController { sendImsFeatureRemovedCallback(featurePair.first, featurePair.second); } + // This method should only be called when already synchronized on mLock; + private IInterface createImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) + throws RemoteException { + switch (featureType) { + case ImsFeature.EMERGENCY_MMTEL: { + return mIImsServiceController.createEmergencyMMTelFeature(slotId, c); + } + case ImsFeature.MMTEL: { + return mIImsServiceController.createMMTelFeature(slotId, c); + } + case ImsFeature.RCS: { + return mIImsServiceController.createRcsFeature(slotId, c); + } + default: + return null; + } + } + + // This method should only be called when synchronized on mLock + private void addImsFeatureBinder(int slotId, int featureType, IInterface b) { + mImsFeatureBinders.add(new ImsFeatureContainer(slotId, featureType, b)); + } + + // This method should only be called when synchronized on mLock + private void removeImsFeatureBinder(int slotId, int featureType) { + ImsFeatureContainer container = mImsFeatureBinders.stream() + .filter(f-> (f.slotId == slotId && f.featureType == featureType)) + .findFirst().orElse(null); + if (container != null) { + mImsFeatureBinders.remove(container); + } + } + + private ImsFeatureContainer getImsFeatureContainer(int slotId, int featureType) { + return mImsFeatureBinders.stream() + .filter(f-> (f.slotId == slotId && f.featureType == featureType)) + .findFirst().orElse(null); + } + private void notifyAllFeaturesRemoved() { if (mCallbacks == null) { Log.w(LOG_TAG, "notifyAllFeaturesRemoved called with invalid callbacks."); diff --git a/com/android/internal/telephony/imsphone/ImsPhone.java b/com/android/internal/telephony/imsphone/ImsPhone.java index bb2c34f8..e54d7bcc 100644 --- a/com/android/internal/telephony/imsphone/ImsPhone.java +++ b/com/android/internal/telephony/imsphone/ImsPhone.java @@ -95,6 +95,7 @@ import com.android.internal.telephony.TelephonyComponentFactory; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.TelephonyProperties; import com.android.internal.telephony.UUSInfo; +import com.android.internal.telephony.gsm.GsmMmiCode; import com.android.internal.telephony.gsm.SuppServiceNotification; import com.android.internal.telephony.uicc.IccRecords; import com.android.internal.telephony.util.NotificationChannelController; @@ -876,9 +877,8 @@ public class ImsPhone extends ImsPhoneBase { if ((isValidCommandInterfaceCFAction(commandInterfaceCFAction)) && (isValidCommandInterfaceCFReason(commandInterfaceCFReason))) { Message resp; - Cf cf = new Cf(dialingNumber, - (commandInterfaceCFReason == CF_REASON_UNCONDITIONAL ? true : false), - onComplete); + Cf cf = new Cf(dialingNumber, GsmMmiCode.isVoiceUnconditionalForwarding( + commandInterfaceCFReason, serviceClass), onComplete); resp = obtainMessage(EVENT_SET_CALL_FORWARD_DONE, isCfEnable(commandInterfaceCFAction) ? 1 : 0, 0, cf); diff --git a/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java b/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java index cea8b217..c2711e8d 100644 --- a/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java +++ b/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java @@ -22,13 +22,13 @@ import android.os.Message; import android.service.carrier.CarrierIdentifier; import android.telephony.ImsiEncryptionInfo; import android.telephony.NetworkScanRequest; +import android.telephony.data.DataProfile; import com.android.internal.telephony.BaseCommands; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.RadioCapability; import com.android.internal.telephony.UUSInfo; import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo; -import com.android.internal.telephony.dataconnection.DataProfile; import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; import java.util.List; diff --git a/com/android/internal/telephony/sip/SipCommandInterface.java b/com/android/internal/telephony/sip/SipCommandInterface.java index 1264053c..59af1bbd 100644 --- a/com/android/internal/telephony/sip/SipCommandInterface.java +++ b/com/android/internal/telephony/sip/SipCommandInterface.java @@ -22,12 +22,12 @@ import android.os.Message; import android.service.carrier.CarrierIdentifier; import android.telephony.ImsiEncryptionInfo; import android.telephony.NetworkScanRequest; +import android.telephony.data.DataProfile; import com.android.internal.telephony.BaseCommands; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.UUSInfo; import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo; -import com.android.internal.telephony.dataconnection.DataProfile; import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; import java.util.List; diff --git a/com/android/internal/telephony/test/SimulatedCommands.java b/com/android/internal/telephony/test/SimulatedCommands.java index 7553b618..b90a2924 100644 --- a/com/android/internal/telephony/test/SimulatedCommands.java +++ b/com/android/internal/telephony/test/SimulatedCommands.java @@ -35,6 +35,7 @@ import android.telephony.NetworkScanRequest; import android.telephony.Rlog; import android.telephony.ServiceState; import android.telephony.SignalStrength; +import android.telephony.data.DataProfile; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.BaseCommands; @@ -49,7 +50,6 @@ import com.android.internal.telephony.SmsResponse; import com.android.internal.telephony.UUSInfo; import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo; import com.android.internal.telephony.dataconnection.DataCallResponse; -import com.android.internal.telephony.dataconnection.DataProfile; import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; import com.android.internal.telephony.gsm.SuppServiceNotification; import com.android.internal.telephony.uicc.IccCardStatus; @@ -132,6 +132,7 @@ public class SimulatedCommands extends BaseCommands private boolean mDcSuccess = true; private DataCallResponse mDcResponse; + private boolean mIsRadioPowerFailResponse = false; //***** Constructor public @@ -1188,11 +1189,17 @@ public class SimulatedCommands extends BaseCommands @Override public void setRadioPower(boolean on, Message result) { + if (mIsRadioPowerFailResponse) { + resultFail(result, null, new RuntimeException("setRadioPower failed!")); + return; + } + if(on) { setRadioState(RadioState.RADIO_ON); } else { setRadioState(RadioState.RADIO_OFF); } + resultSuccess(result, null); } @@ -2151,4 +2158,8 @@ public class SimulatedCommands extends BaseCommands super.setOnRestrictedStateChanged(h, what, obj); SimulatedCommandsVerifier.getInstance().setOnRestrictedStateChanged(h, what, obj); } + + public void setRadioPowerFailResponse(boolean fail) { + mIsRadioPowerFailResponse = fail; + } } diff --git a/com/android/internal/telephony/test/SimulatedCommandsVerifier.java b/com/android/internal/telephony/test/SimulatedCommandsVerifier.java index b03e70b6..c51b6adb 100644 --- a/com/android/internal/telephony/test/SimulatedCommandsVerifier.java +++ b/com/android/internal/telephony/test/SimulatedCommandsVerifier.java @@ -21,12 +21,12 @@ import android.os.Message; import android.service.carrier.CarrierIdentifier; import android.telephony.ImsiEncryptionInfo; import android.telephony.NetworkScanRequest; +import android.telephony.data.DataProfile; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.RadioCapability; import com.android.internal.telephony.UUSInfo; import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo; -import com.android.internal.telephony.dataconnection.DataProfile; import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; import java.util.List; diff --git a/com/android/internal/telephony/uicc/SIMRecords.java b/com/android/internal/telephony/uicc/SIMRecords.java index 8594f804..3af04a9e 100644 --- a/com/android/internal/telephony/uicc/SIMRecords.java +++ b/com/android/internal/telephony/uicc/SIMRecords.java @@ -60,9 +60,6 @@ public class SIMRecords extends IccRecords { VoiceMailConstants mVmConfig; - - SpnOverride mSpnOverride; - // ***** Cached SIM State; cleared on channel close private int mCallForwardingStatus; @@ -99,7 +96,6 @@ public class SIMRecords extends IccRecords { public String toString() { return "SimRecords: " + super.toString() + " mVmConfig" + mVmConfig - + " mSpnOverride=" + mSpnOverride + " callForwardingEnabled=" + mCallForwardingStatus + " spnState=" + mSpnState + " mCphsInfo=" + mCphsInfo @@ -214,7 +210,6 @@ public class SIMRecords extends IccRecords { mAdnCache = new AdnRecordCache(mFh); mVmConfig = new VoiceMailConstants(); - mSpnOverride = new SpnOverride(); mRecordsRequested = false; // No load request is made till SIM ready @@ -1640,57 +1635,63 @@ public class SIMRecords extends IccRecords { //***** Private methods + /** + * Override the carrier name with either carrier config or SPN + * if an override is provided. + */ private void handleCarrierNameOverride() { + final int phoneId = mParentApp.getPhoneId(); + SubscriptionController subCon = SubscriptionController.getInstance(); + final int subId = subCon.getSubIdUsingPhoneId(phoneId); + if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + loge("subId not valid for Phone " + phoneId); + return; + } + CarrierConfigManager configLoader = (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - if (configLoader != null && configLoader.getConfig().getBoolean( - CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL)) { - String carrierName = configLoader.getConfig().getString( - CarrierConfigManager.KEY_CARRIER_NAME_STRING); - setServiceProviderName(carrierName); - mTelephonyManager.setSimOperatorNameForPhone(mParentApp.getPhoneId(), - carrierName); - } else { - setSpnFromConfig(getOperatorNumeric()); + if (configLoader == null) { + loge("Failed to load a Carrier Config"); + return; } - /* update display name with carrier override */ - setDisplayName(); + PersistableBundle config = configLoader.getConfigForSubId(subId); + boolean preferCcName = config.getBoolean( + CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, false); + String ccName = config.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING); + // If carrier config is priority, use it regardless - the preference + // and the name were both set by the carrier, so this is safe; + // otherwise, if the SPN is priority but we don't have one *and* we have + // a name in carrier config, use the carrier config name as a backup. + if (preferCcName || (TextUtils.isEmpty(getServiceProviderName()) + && !TextUtils.isEmpty(ccName))) { + setServiceProviderName(ccName); + mTelephonyManager.setSimOperatorNameForPhone(phoneId, ccName); + } + + updateCarrierNameForSubscription(subCon, subId); } - private void setDisplayName() { - SubscriptionManager subManager = SubscriptionManager.from(mContext); - int[] subId = subManager.getSubId(mParentApp.getPhoneId()); + private void updateCarrierNameForSubscription(SubscriptionController subCon, int subId) { + /* update display name with carrier override */ + SubscriptionInfo subInfo = subCon.getActiveSubscriptionInfo( + subId, mContext.getOpPackageName()); - if ((subId == null) || subId.length <= 0) { - log("subId not valid for Phone " + mParentApp.getPhoneId()); + if (subInfo == null || subInfo.getNameSource() + == SubscriptionManager.NAME_SOURCE_USER_INPUT) { + // either way, there is no subinfo to update return; } - SubscriptionInfo subInfo = subManager.getActiveSubscriptionInfo(subId[0]); - if (subInfo != null && subInfo.getNameSource() - != SubscriptionManager.NAME_SOURCE_USER_INPUT) { - CharSequence oldSubName = subInfo.getDisplayName(); - String newCarrierName = mTelephonyManager.getSimOperatorName(subId[0]); + CharSequence oldSubName = subInfo.getDisplayName(); + String newCarrierName = mTelephonyManager.getSimOperatorName(subId); - if (!TextUtils.isEmpty(newCarrierName) && !newCarrierName.equals(oldSubName)) { - log("sim name[" + mParentApp.getPhoneId() + "] = " + newCarrierName); - SubscriptionController.getInstance().setDisplayName(newCarrierName, subId[0]); - } - } else { - log("SUB[" + mParentApp.getPhoneId() + "] " + subId[0] + " SubInfo not created yet"); + if (!TextUtils.isEmpty(newCarrierName) && !newCarrierName.equals(oldSubName)) { + log("sim name[" + mParentApp.getPhoneId() + "] = " + newCarrierName); + subCon.setDisplayName(newCarrierName, subId); } } - private void setSpnFromConfig(String carrier) { - if (mSpnOverride.containsCarrier(carrier)) { - setServiceProviderName(mSpnOverride.getSpn(carrier)); - mTelephonyManager.setSimOperatorNameForPhone( - mParentApp.getPhoneId(), getServiceProviderName()); - } - } - - private void setVoiceMailByCountry (String spn) { if (mVmConfig.containsCarrier(spn)) { mIsVoiceMailFixed = true; @@ -2223,7 +2224,6 @@ public class SIMRecords extends IccRecords { pw.println(" extends:"); super.dump(fd, pw, args); pw.println(" mVmConfig=" + mVmConfig); - pw.println(" mSpnOverride=" + mSpnOverride); pw.println(" mCallForwardingStatus=" + mCallForwardingStatus); pw.println(" mSpnState=" + mSpnState); pw.println(" mCphsInfo=" + mCphsInfo); diff --git a/com/android/internal/telephony/uicc/SpnOverride.java b/com/android/internal/telephony/uicc/SpnOverride.java deleted file mode 100644 index 3a01af62..00000000 --- a/com/android/internal/telephony/uicc/SpnOverride.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.telephony.uicc; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.util.HashMap; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import android.os.Environment; -import android.telephony.Rlog; -import android.util.Xml; - -import com.android.internal.util.XmlUtils; - -public class SpnOverride { - private HashMap<String, String> mCarrierSpnMap; - - - static final String LOG_TAG = "SpnOverride"; - static final String PARTNER_SPN_OVERRIDE_PATH ="etc/spn-conf.xml"; - static final String OEM_SPN_OVERRIDE_PATH = "telephony/spn-conf.xml"; - - SpnOverride () { - mCarrierSpnMap = new HashMap<String, String>(); - loadSpnOverrides(); - } - - boolean containsCarrier(String carrier) { - return mCarrierSpnMap.containsKey(carrier); - } - - String getSpn(String carrier) { - return mCarrierSpnMap.get(carrier); - } - - private void loadSpnOverrides() { - FileReader spnReader; - - File spnFile = new File(Environment.getRootDirectory(), - PARTNER_SPN_OVERRIDE_PATH); - File oemSpnFile = new File(Environment.getOemDirectory(), - OEM_SPN_OVERRIDE_PATH); - - if (oemSpnFile.exists()) { - // OEM image exist SPN xml, get the timestamp from OEM & System image for comparison. - long oemSpnTime = oemSpnFile.lastModified(); - long sysSpnTime = spnFile.lastModified(); - Rlog.d(LOG_TAG, "SPN Timestamp: oemTime = " + oemSpnTime + " sysTime = " + sysSpnTime); - - // To get the newer version of SPN from OEM image - if (oemSpnTime > sysSpnTime) { - Rlog.d(LOG_TAG, "SPN in OEM image is newer than System image"); - spnFile = oemSpnFile; - } - } else { - // No SPN in OEM image, so load it from system image. - Rlog.d(LOG_TAG, "No SPN in OEM image = " + oemSpnFile.getPath() + - " Load SPN from system image"); - } - - try { - spnReader = new FileReader(spnFile); - } catch (FileNotFoundException e) { - Rlog.w(LOG_TAG, "Can not open " + spnFile.getAbsolutePath()); - return; - } - - try { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(spnReader); - - XmlUtils.beginDocument(parser, "spnOverrides"); - - while (true) { - XmlUtils.nextElement(parser); - - String name = parser.getName(); - if (!"spnOverride".equals(name)) { - break; - } - - String numeric = parser.getAttributeValue(null, "numeric"); - String data = parser.getAttributeValue(null, "spn"); - - mCarrierSpnMap.put(numeric, data); - } - spnReader.close(); - } catch (XmlPullParserException e) { - Rlog.w(LOG_TAG, "Exception in spn-conf parser " + e); - } catch (IOException e) { - Rlog.w(LOG_TAG, "Exception in spn-conf parser " + e); - } - } - -} diff --git a/com/android/internal/util/ArrayUtils.java b/com/android/internal/util/ArrayUtils.java index 91bc6813..22bfcc34 100644 --- a/com/android/internal/util/ArrayUtils.java +++ b/com/android/internal/util/ArrayUtils.java @@ -30,6 +30,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -134,6 +135,13 @@ public class ArrayUtils { } /** + * Checks if given map is null or has zero elements. + */ + public static boolean isEmpty(@Nullable Map<?, ?> map) { + return map == null || map.isEmpty(); + } + + /** * Checks if given array is null or has zero elements. */ public static <T> boolean isEmpty(@Nullable T[] array) { diff --git a/com/android/internal/util/RingBuffer.java b/com/android/internal/util/RingBuffer.java index c22be2cc..9a6e542c 100644 --- a/com/android/internal/util/RingBuffer.java +++ b/com/android/internal/util/RingBuffer.java @@ -60,6 +60,25 @@ public class RingBuffer<T> { mBuffer[indexOf(mCursor++)] = t; } + /** + * Returns object of type <T> at the next writable slot, creating one if it is not already + * available. In case of any errors while creating the object, <code>null</code> will + * be returned. + */ + public T getNextSlot() { + final int nextSlotIdx = indexOf(mCursor++); + T item = mBuffer[nextSlotIdx]; + if (item == null) { + try { + item = (T) mBuffer.getClass().getComponentType().newInstance(); + } catch (IllegalAccessException | InstantiationException e) { + return null; + } + mBuffer[nextSlotIdx] = item; + } + return item; + } + public T[] toArray() { // Only generic way to create a T[] from another T[] T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass()); diff --git a/com/android/internal/util/UserIcons.java b/com/android/internal/util/UserIcons.java index daf745ff..bfe43237 100644 --- a/com/android/internal/util/UserIcons.java +++ b/com/android/internal/util/UserIcons.java @@ -61,17 +61,19 @@ public class UserIcons { * Returns a default user icon for the given user. * * Note that for guest users, you should pass in {@code UserHandle.USER_NULL}. + * + * @param resources resources object to fetch user icon / color. * @param userId the user id or {@code UserHandle.USER_NULL} for a non-user specific icon * @param light whether we want a light icon (suitable for a dark background) */ - public static Drawable getDefaultUserIcon(int userId, boolean light) { + public static Drawable getDefaultUserIcon(Resources resources, int userId, boolean light) { int colorResId = light ? R.color.user_icon_default_white : R.color.user_icon_default_gray; if (userId != UserHandle.USER_NULL) { // Return colored icon instead colorResId = USER_ICON_COLORS[userId % USER_ICON_COLORS.length]; } - Drawable icon = Resources.getSystem().getDrawable(R.drawable.ic_account_circle, null).mutate(); - icon.setColorFilter(Resources.getSystem().getColor(colorResId, null), Mode.SRC_IN); + Drawable icon = resources.getDrawable(R.drawable.ic_account_circle, null).mutate(); + icon.setColorFilter(resources.getColor(colorResId, null), Mode.SRC_IN); icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); return icon; } diff --git a/com/android/internal/view/TooltipPopup.java b/com/android/internal/view/TooltipPopup.java index d38ea2c1..24f0b0cc 100644 --- a/com/android/internal/view/TooltipPopup.java +++ b/com/android/internal/view/TooltipPopup.java @@ -142,7 +142,7 @@ public class TooltipPopup { mTmpAnchorPos[1] -= mTmpAppPos[1]; // mTmpAnchorPos is now relative to the main app window. - outParams.x = mTmpAnchorPos[0] + offsetX - mTmpDisplayFrame.width() / 2; + outParams.x = mTmpAnchorPos[0] + offsetX - appView.getWidth() / 2; final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); mContentView.measure(spec, spec); @@ -157,6 +157,9 @@ public class TooltipPopup { outParams.y = yBelow; } } else { + // Use mTmpDisplayFrame.height() as the lower boundary instead of appView.getHeight(), + // as the latter includes the navigation bar, and tooltips do not look good over + // the navigation bar. if (yBelow + tooltipHeight <= mTmpDisplayFrame.height()) { outParams.y = yBelow; } else { diff --git a/com/android/internal/widget/ExploreByTouchHelper.java b/com/android/internal/widget/ExploreByTouchHelper.java index 50ad547e..759a41a2 100644 --- a/com/android/internal/widget/ExploreByTouchHelper.java +++ b/com/android/internal/widget/ExploreByTouchHelper.java @@ -186,6 +186,9 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate { } final AccessibilityEvent event = createEvent(virtualViewId, eventType); + if (event == null) { + return false; + } return parent.requestSendAccessibilityEvent(mView, event); } @@ -240,6 +243,9 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate { if (parent != null) { final AccessibilityEvent event = createEvent(virtualViewId, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + if (event == null) { + return; + } event.setContentChangeTypes(changeTypes); parent.requestSendAccessibilityEvent(mView, event); } @@ -305,6 +311,9 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate { * the specified item. */ private AccessibilityEvent createEventForHost(int eventType) { + if (!AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) { + return null; + } final AccessibilityEvent event = AccessibilityEvent.obtain(eventType); mView.onInitializeAccessibilityEvent(event); @@ -325,6 +334,9 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate { * the specified item. */ private AccessibilityEvent createEventForChild(int virtualViewId, int eventType) { + if (!AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) { + return null; + } final AccessibilityEvent event = AccessibilityEvent.obtain(eventType); event.setEnabled(true); event.setClassName(DEFAULT_CLASS_NAME); diff --git a/com/android/internal/widget/Magnifier.java b/com/android/internal/widget/Magnifier.java deleted file mode 100644 index f6741c3b..00000000 --- a/com/android/internal/widget/Magnifier.java +++ /dev/null @@ -1,218 +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 com.android.internal.widget; - -import android.annotation.FloatRange; -import android.annotation.NonNull; -import android.annotation.UiThread; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.Handler; -import android.util.Log; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.PixelCopy; -import android.view.View; -import android.view.ViewRootImpl; -import android.widget.ImageView; -import android.widget.PopupWindow; - -import com.android.internal.R; -import com.android.internal.util.Preconditions; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * Android magnifier widget. Can be used by any view which is attached to window. - */ -public final class Magnifier { - private static final String LOG_TAG = "magnifier"; - private static final int MAGNIFIER_REFRESH_RATE_MS = 33; // ~30fps - // The view for which this magnifier is attached. - private final View mView; - // The window containing the magnifier. - private final PopupWindow mWindow; - // The center coordinates of the window containing the magnifier. - private final Point mWindowCoords = new Point(); - // The width of the window containing the magnifier. - private final int mWindowWidth; - // The height of the window containing the magnifier. - private final int mWindowHeight; - // The bitmap used to display the contents of the magnifier. - private final Bitmap mBitmap; - // The center coordinates of the content that is to be magnified. - private final Point mCenterZoomCoords = new Point(); - // The callback of the pixel copy request will be invoked on this Handler when - // the copy is finished. - private final Handler mPixelCopyHandler = Handler.getMain(); - // Current magnification scale. - private final float mZoomScale; - // Timer used to schedule the copy task. - private Timer mTimer; - - /** - * Initializes a magnifier. - * - * @param view the view for which this magnifier is attached - */ - @UiThread - public Magnifier(@NonNull View view) { - mView = Preconditions.checkNotNull(view); - final Context context = mView.getContext(); - final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation); - final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null); - content.findViewById(R.id.magnifier_inner).setClipToOutline(true); - mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width); - mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height); - mZoomScale = context.getResources().getFloat(R.dimen.magnifier_zoom_scale); - - mWindow = new PopupWindow(context); - mWindow.setContentView(content); - mWindow.setWidth(mWindowWidth); - mWindow.setHeight(mWindowHeight); - mWindow.setElevation(elevation); - mWindow.setTouchable(false); - mWindow.setBackgroundDrawable(null); - - final int bitmapWidth = (int) (mWindowWidth / mZoomScale); - final int bitmapHeight = (int) (mWindowHeight / mZoomScale); - mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); - getImageView().setImageBitmap(mBitmap); - } - - /** - * Shows the magnifier on the screen. - * - * @param xPosInView horizontal coordinate of the center point of the magnifier source relative - * to the view. The lower end is clamped to 0 - * @param yPosInView vertical coordinate of the center point of the magnifier source - * relative to the view. The lower end is clamped to 0 - */ - public void show(@FloatRange(from=0) float xPosInView, @FloatRange(from=0) float yPosInView) { - if (xPosInView < 0) { - xPosInView = 0; - } - - if (yPosInView < 0) { - yPosInView = 0; - } - - configureCoordinates(xPosInView, yPosInView); - - if (mTimer == null) { - mTimer = new Timer(); - mTimer.schedule(new TimerTask() { - @Override - public void run() { - performPixelCopy(); - } - }, 0 /* delay */, MAGNIFIER_REFRESH_RATE_MS); - } - - if (mWindow.isShowing()) { - mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(), - mWindow.getHeight()); - } else { - mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY, - mWindowCoords.x, mWindowCoords.y); - } - } - - /** - * Dismisses the magnifier from the screen. - */ - public void dismiss() { - mWindow.dismiss(); - - if (mTimer != null) { - mTimer.cancel(); - mTimer.purge(); - mTimer = null; - } - } - - /** - * @return the height of the magnifier window. - */ - public int getHeight() { - return mWindowHeight; - } - - /** - * @return the width of the magnifier window. - */ - public int getWidth() { - return mWindowWidth; - } - - /** - * @return the zoom scale of the magnifier. - */ - public float getZoomScale() { - return mZoomScale; - } - - private void configureCoordinates(float xPosInView, float yPosInView) { - final int[] coordinatesOnScreen = new int[2]; - mView.getLocationOnScreen(coordinatesOnScreen); - final float posXOnScreen = xPosInView + coordinatesOnScreen[0]; - final float posYOnScreen = yPosInView + coordinatesOnScreen[1]; - - mCenterZoomCoords.x = (int) posXOnScreen; - mCenterZoomCoords.y = (int) posYOnScreen; - - final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize( - R.dimen.magnifier_offset); - mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2; - mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset; - } - - private void performPixelCopy() { - final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2; - int rawStartX = mCenterZoomCoords.x - mBitmap.getWidth() / 2; - - // Clamp startX value to avoid distorting the rendering of the magnifier content. - if (rawStartX < 0) { - rawStartX = 0; - } else if (rawStartX + mBitmap.getWidth() > mView.getWidth()) { - rawStartX = mView.getWidth() - mBitmap.getWidth(); - } - - final int startX = rawStartX; - final ViewRootImpl viewRootImpl = mView.getViewRootImpl(); - - if (viewRootImpl != null && viewRootImpl.mSurface != null - && viewRootImpl.mSurface.isValid()) { - PixelCopy.request( - viewRootImpl.mSurface, - new Rect(startX, startY, startX + mBitmap.getWidth(), - startY + mBitmap.getHeight()), - mBitmap, - result -> getImageView().invalidate(), - mPixelCopyHandler); - } else { - Log.d(LOG_TAG, "Could not perform PixelCopy request"); - } - } - - private ImageView getImageView() { - return mWindow.getContentView().findViewById(R.id.magnifier_image); - } -} diff --git a/com/android/internal/widget/PointerLocationView.java b/com/android/internal/widget/PointerLocationView.java index e53162cc..5847033f 100644 --- a/com/android/internal/widget/PointerLocationView.java +++ b/com/android/internal/widget/PointerLocationView.java @@ -32,7 +32,7 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; -import android.view.WindowManagerPolicy.PointerEventListener; +import android.view.WindowManagerPolicyConstants.PointerEventListener; import android.view.MotionEvent.PointerCoords; import java.util.ArrayList; diff --git a/com/android/internal/widget/RecyclerView.java b/com/android/internal/widget/RecyclerView.java index 7abc76a8..408a4e9b 100644 --- a/com/android/internal/widget/RecyclerView.java +++ b/com/android/internal/widget/RecyclerView.java @@ -9556,7 +9556,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro if (vScroll == 0 && hScroll == 0) { return false; } - mRecyclerView.scrollBy(hScroll, vScroll); + mRecyclerView.smoothScrollBy(hScroll, vScroll); return true; } diff --git a/com/android/keyguard/KeyguardSliceView.java b/com/android/keyguard/KeyguardSliceView.java index c18f9b61..cb3d59c4 100644 --- a/com/android/keyguard/KeyguardSliceView.java +++ b/com/android/keyguard/KeyguardSliceView.java @@ -33,6 +33,8 @@ import com.android.internal.graphics.ColorUtils; import com.android.systemui.R; import com.android.systemui.keyguard.KeyguardSliceProvider; +import java.util.Collections; + /** * View visible under the clock on the lock screen and AoD. */ @@ -75,7 +77,8 @@ public class KeyguardSliceView extends LinearLayout { super.onAttachedToWindow(); // Set initial content - showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri)); + showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri, + Collections.emptyList())); // Make sure we always have the most current slice getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri, @@ -91,7 +94,7 @@ public class KeyguardSliceView extends LinearLayout { private void showSlice(Slice slice) { // Items will be wrapped into an action when they have tap targets. - SliceItem actionSlice = SliceQuery.find(slice, SliceItem.TYPE_ACTION); + SliceItem actionSlice = SliceQuery.find(slice, SliceItem.FORMAT_ACTION); if (actionSlice != null) { mSlice = actionSlice.getSlice(); mSliceAction = actionSlice.getAction(); @@ -105,7 +108,7 @@ public class KeyguardSliceView extends LinearLayout { return; } - SliceItem title = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, null); + SliceItem title = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, null); if (title == null) { mTitle.setVisibility(GONE); } else { @@ -113,7 +116,7 @@ public class KeyguardSliceView extends LinearLayout { mTitle.setText(title.getText()); } - SliceItem text = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, null, Slice.HINT_TITLE); + SliceItem text = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, null, Slice.HINT_TITLE); if (text == null) { mText.setVisibility(GONE); } else { @@ -154,7 +157,8 @@ public class KeyguardSliceView extends LinearLayout { @Override public void onChange(boolean selfChange, Uri uri) { - showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri)); + showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri, + Collections.emptyList())); } } } diff --git a/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 5a02178a..8d55eea4 100644 --- a/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -21,7 +21,7 @@ import android.media.AudioManager; import android.os.SystemClock; import android.hardware.fingerprint.FingerprintManager; import android.telephony.TelephonyManager; -import android.view.WindowManagerPolicy; +import android.view.WindowManagerPolicyConstants; import com.android.internal.telephony.IccCardConstants; @@ -171,9 +171,9 @@ public class KeyguardUpdateMonitorCallback { /** * Called when the device has finished going to sleep. - * @param why either {@link WindowManagerPolicy#OFF_BECAUSE_OF_ADMIN}, - * {@link WindowManagerPolicy#OFF_BECAUSE_OF_USER}, or - * {@link WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}. + * @param why either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_ADMIN}, + * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER}, or + * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}. * * @deprecated use {@link com.android.systemui.keyguard.WakefulnessLifecycle}. */ diff --git a/com/android/keyguard/PasswordTextView.java b/com/android/keyguard/PasswordTextView.java index 12f75bb2..0219db33 100644 --- a/com/android/keyguard/PasswordTextView.java +++ b/com/android/keyguard/PasswordTextView.java @@ -307,8 +307,9 @@ public class PasswordTextView extends View { void sendAccessibilityEventTypeViewTextChanged(String beforeText, int fromIndex, int removedCount, int addedCount) { - if (AccessibilityManager.getInstance(mContext).isEnabled() && - (isFocused() || isSelected() && isShown())) { + if (AccessibilityManager.getInstance(mContext).isObservedEventType( + AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED) + && (isFocused() || isSelected() && isShown())) { AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); event.setFromIndex(fromIndex); diff --git a/com/android/layoutlib/bridge/Bridge.java b/com/android/layoutlib/bridge/Bridge.java index 0cfc1811..5dca8e7f 100644 --- a/com/android/layoutlib/bridge/Bridge.java +++ b/com/android/layoutlib/bridge/Bridge.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2008 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. @@ -14,62 +14,659 @@ * limitations under the License. */ -package com.android.layoutlib.bridge;import com.android.ide.common.rendering.api.RenderSession; +package com.android.layoutlib.bridge; + +import com.android.ide.common.rendering.api.Capability; +import com.android.ide.common.rendering.api.DrawableParams; +import com.android.ide.common.rendering.api.Features; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.Result.Status; import com.android.ide.common.rendering.api.SessionParams; +import com.android.layoutlib.bridge.android.RenderParamsFlags; +import com.android.layoutlib.bridge.impl.RenderDrawable; +import com.android.layoutlib.bridge.impl.RenderSessionImpl; +import com.android.layoutlib.bridge.util.DynamicIdMap; +import com.android.ninepatch.NinePatchChunk; +import com.android.resources.ResourceType; +import com.android.tools.layoutlib.create.MethodAdapter; +import com.android.tools.layoutlib.create.OverrideMethod; +import com.android.util.Pair; + +import android.annotation.NonNull; +import android.content.res.BridgeAssetManager; +import android.graphics.Bitmap; +import android.graphics.FontFamily_Delegate; +import android.graphics.Typeface; +import android.graphics.Typeface_Delegate; +import android.icu.util.ULocale; +import android.os.Looper; +import android.os.Looper_Accessor; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; + +import java.io.File; +import java.lang.ref.SoftReference; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.locks.ReentrantLock; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; +import libcore.io.MemoryMappedFile_Delegate; + +import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; /** - * Legacy Bridge used in the SDK version of layoutlib + * Main entry point of the LayoutLib Bridge. + * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call + * {@link #createSession(SessionParams)} */ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { - private static final String SDK_NOT_SUPPORTED = "The SDK layoutlib version is not supported"; - private static final Result NOT_SUPPORTED_RESULT = - Status.NOT_IMPLEMENTED.createResult(SDK_NOT_SUPPORTED); - private static BufferedImage sImage; - private static class BridgeRenderSession extends RenderSession { + private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left"; - @Override - public synchronized BufferedImage getImage() { - if (sImage == null) { - sImage = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB); - Graphics2D g = sImage.createGraphics(); - g.clearRect(0, 0, 500, 500); - g.drawString(SDK_NOT_SUPPORTED, 20, 20); - g.dispose(); - } + public static class StaticMethodNotImplementedException extends RuntimeException { + private static final long serialVersionUID = 1L; - return sImage; + public StaticMethodNotImplementedException(String msg) { + super(msg); } + } + + /** + * Lock to ensure only one rendering/inflating happens at a time. + * This is due to some singleton in the Android framework. + */ + private final static ReentrantLock sLock = new ReentrantLock(); + + /** + * Maps from id to resource type/name. This is for com.android.internal.R + */ + @SuppressWarnings("deprecation") + private final static Map<Integer, Pair<ResourceType, String>> sRMap = new HashMap<>(); + /** + * Reverse map compared to sRMap, resource type -> (resource name -> id). + * This is for com.android.internal.R. + */ + private final static Map<ResourceType, Map<String, Integer>> sRevRMap = new EnumMap<>(ResourceType.class); + + // framework resources are defined as 0x01XX#### where XX is the resource type (layout, + // drawable, etc...). Using FF as the type allows for 255 resource types before we get a + // collision which should be fine. + private final static int DYNAMIC_ID_SEED_START = 0x01ff0000; + private final static DynamicIdMap sDynamicIds = new DynamicIdMap(DYNAMIC_ID_SEED_START); + + private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache = + new WeakHashMap<>(); + private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache = + new WeakHashMap<>(); + + private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = new HashMap<>(); + private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache = + new HashMap<>(); + + private static Map<String, Map<String, Integer>> sEnumValueMap; + private static Map<String, String> sPlatformProperties; + + /** + * A default log than prints to stdout/stderr. + */ + private final static LayoutLog sDefaultLog = new LayoutLog() { @Override - public Result render(long timeout, boolean forceMeasure) { - return NOT_SUPPORTED_RESULT; + public void error(String tag, String message, Object data) { + System.err.println(message); } @Override - public Result measure(long timeout) { - return NOT_SUPPORTED_RESULT; + public void error(String tag, String message, Throwable throwable, Object data) { + System.err.println(message); } @Override - public Result getResult() { - return NOT_SUPPORTED_RESULT; + public void warning(String tag, String message, Object data) { + System.out.println(message); + } + }; + + /** + * Current log. + */ + private static LayoutLog sCurrentLog = sDefaultLog; + + private static final int LAST_SUPPORTED_FEATURE = Features.THEME_PREVIEW_NAVIGATION_BAR; + + @Override + public int getApiLevel() { + return com.android.ide.common.rendering.api.Bridge.API_CURRENT; + } + + @SuppressWarnings("deprecation") + @Override + @Deprecated + public EnumSet<Capability> getCapabilities() { + // The Capability class is deprecated and frozen. All Capabilities enumerated there are + // supported by this version of LayoutLibrary. So, it's safe to use EnumSet.allOf() + return EnumSet.allOf(Capability.class); + } + + @Override + public boolean supports(int feature) { + return feature <= LAST_SUPPORTED_FEATURE; + } + + @Override + public boolean init(Map<String,String> platformProperties, + File fontLocation, + Map<String, Map<String, Integer>> enumValueMap, + LayoutLog log) { + sPlatformProperties = platformProperties; + sEnumValueMap = enumValueMap; + + BridgeAssetManager.initSystem(); + + // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener + // on static (native) methods which prints the signature on the console and + // throws an exception. + // This is useful when testing the rendering in ADT to identify static native + // methods that are ignored -- layoutlib_create makes them returns 0/false/null + // which is generally OK yet might be a problem, so this is how you'd find out. + // + // Currently layoutlib_create only overrides static native method. + // Static non-natives are not overridden and thus do not get here. + final String debug = System.getenv("DEBUG_LAYOUT"); + if (debug != null && !debug.equals("0") && !debug.equals("false")) { + + OverrideMethod.setDefaultListener(new MethodAdapter() { + @Override + public void onInvokeV(String signature, boolean isNative, Object caller) { + sDefaultLog.error(null, "Missing Stub: " + signature + + (isNative ? " (native)" : ""), null /*data*/); + + if (debug.equalsIgnoreCase("throw")) { + // Throwing this exception doesn't seem that useful. It breaks + // the layout editor yet doesn't display anything meaningful to the + // user. Having the error in the console is just as useful. We'll + // throw it only if the environment variable is "throw" or "THROW". + throw new StaticMethodNotImplementedException(signature); + } + } + }); + } + + // load the fonts. + FontFamily_Delegate.setFontLocation(fontLocation.getAbsolutePath()); + MemoryMappedFile_Delegate.setDataDir(fontLocation.getAbsoluteFile().getParentFile()); + + // now parse com.android.internal.R (and only this one as android.R is a subset of + // the internal version), and put the content in the maps. + try { + Class<?> r = com.android.internal.R.class; + // Parse the styleable class first, since it may contribute to attr values. + parseStyleable(); + + for (Class<?> inner : r.getDeclaredClasses()) { + if (inner == com.android.internal.R.styleable.class) { + // Already handled the styleable case. Not skipping attr, as there may be attrs + // that are not referenced from styleables. + continue; + } + String resTypeName = inner.getSimpleName(); + ResourceType resType = ResourceType.getEnum(resTypeName); + if (resType != null) { + Map<String, Integer> fullMap = null; + switch (resType) { + case ATTR: + fullMap = sRevRMap.get(ResourceType.ATTR); + break; + case STRING: + case STYLE: + // Slightly less than thousand entries in each. + fullMap = new HashMap<>(1280); + // no break. + default: + if (fullMap == null) { + fullMap = new HashMap<>(); + } + sRevRMap.put(resType, fullMap); + } + + for (Field f : inner.getDeclaredFields()) { + // only process static final fields. Since the final attribute may have + // been altered by layoutlib_create, we only check static + if (!isValidRField(f)) { + continue; + } + Class<?> type = f.getType(); + if (!type.isArray()) { + Integer value = (Integer) f.get(null); + //noinspection deprecation + sRMap.put(value, Pair.of(resType, f.getName())); + fullMap.put(f.getName(), value); + } + } + } + } + } catch (Exception throwable) { + if (log != null) { + log.error(LayoutLog.TAG_BROKEN, + "Failed to load com.android.internal.R from the layout library jar", + throwable, null); + } + return false; } + + return true; + } + + /** + * Tests if the field is pubic, static and one of int or int[]. + */ + private static boolean isValidRField(Field field) { + int modifiers = field.getModifiers(); + boolean isAcceptable = Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers); + Class<?> type = field.getType(); + return isAcceptable && type == int.class || + (type.isArray() && type.getComponentType() == int.class); + } + private static void parseStyleable() throws Exception { + // R.attr doesn't contain all the needed values. There are too many resources in the + // framework for all to be in the R class. Only the ones specified manually in + // res/values/symbols.xml are put in R class. Since, we need to create a map of all attr + // values, we try and find them from the styleables. + + // There were 1500 elements in this map at M timeframe. + Map<String, Integer> revRAttrMap = new HashMap<>(2048); + sRevRMap.put(ResourceType.ATTR, revRAttrMap); + // There were 2000 elements in this map at M timeframe. + Map<String, Integer> revRStyleableMap = new HashMap<>(3072); + sRevRMap.put(ResourceType.STYLEABLE, revRStyleableMap); + Class<?> c = com.android.internal.R.styleable.class; + Field[] fields = c.getDeclaredFields(); + // Sort the fields to bring all arrays to the beginning, so that indices into the array are + // able to refer back to the arrays (i.e. no forward references). + Arrays.sort(fields, (o1, o2) -> { + if (o1 == o2) { + return 0; + } + Class<?> t1 = o1.getType(); + Class<?> t2 = o2.getType(); + if (t1.isArray() && !t2.isArray()) { + return -1; + } else if (t2.isArray() && !t1.isArray()) { + return 1; + } + return o1.getName().compareTo(o2.getName()); + }); + Map<String, int[]> styleables = new HashMap<>(); + for (Field field : fields) { + if (!isValidRField(field)) { + // Only consider public static fields that are int or int[]. + // Don't check the final flag as it may have been modified by layoutlib_create. + continue; + } + String name = field.getName(); + if (field.getType().isArray()) { + int[] styleableValue = (int[]) field.get(null); + styleables.put(name, styleableValue); + continue; + } + // Not an array. + String arrayName = name; + int[] arrayValue = null; + int index; + while ((index = arrayName.lastIndexOf('_')) >= 0) { + // Find the name of the corresponding styleable. + // Search in reverse order so that attrs like LinearLayout_Layout_layout_gravity + // are mapped to LinearLayout_Layout and not to LinearLayout. + arrayName = arrayName.substring(0, index); + arrayValue = styleables.get(arrayName); + if (arrayValue != null) { + break; + } + } + index = (Integer) field.get(null); + if (arrayValue != null) { + String attrName = name.substring(arrayName.length() + 1); + int attrValue = arrayValue[index]; + //noinspection deprecation + sRMap.put(attrValue, Pair.of(ResourceType.ATTR, attrName)); + revRAttrMap.put(attrName, attrValue); + } + //noinspection deprecation + sRMap.put(index, Pair.of(ResourceType.STYLEABLE, name)); + revRStyleableMap.put(name, index); + } + } @Override + public boolean dispose() { + BridgeAssetManager.clearSystem(); + + // dispose of the default typeface. + Typeface_Delegate.resetDefaults(); + Typeface.sDynamicTypefaceCache.evictAll(); + sProject9PatchCache.clear(); + sProjectBitmapCache.clear(); + + return true; + } + + /** + * Starts a layout session by inflating and rendering it. The method returns a + * {@link RenderSession} on which further actions can be taken. + * <p/> + * If {@link SessionParams} includes the {@link RenderParamsFlags#FLAG_DO_NOT_RENDER_ON_CREATE}, + * this method will only inflate the layout but will NOT render it. + * @param params the {@link SessionParams} object with all the information necessary to create + * the scene. + * @return a new {@link RenderSession} object that contains the result of the layout. + * @since 5 + */ + @Override public RenderSession createSession(SessionParams params) { - return new BridgeRenderSession(); + try { + Result lastResult; + RenderSessionImpl scene = new RenderSessionImpl(params); + try { + prepareThread(); + lastResult = scene.init(params.getTimeout()); + if (lastResult.isSuccess()) { + lastResult = scene.inflate(); + + boolean doNotRenderOnCreate = Boolean.TRUE.equals( + params.getFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE)); + if (lastResult.isSuccess() && !doNotRenderOnCreate) { + lastResult = scene.render(true /*freshRender*/); + } + } + } finally { + scene.release(); + cleanupThread(); + } + + return new BridgeRenderSession(scene, lastResult); + } catch (Throwable t) { + // get the real cause of the exception. + Throwable t2 = t; + while (t2.getCause() != null) { + t2 = t2.getCause(); + } + return new BridgeRenderSession(null, + ERROR_UNKNOWN.createResult(t2.getMessage(), t)); + } } @Override - public int getApiLevel() { - return 0; + public Result renderDrawable(DrawableParams params) { + try { + Result lastResult; + RenderDrawable action = new RenderDrawable(params); + try { + prepareThread(); + lastResult = action.init(params.getTimeout()); + if (lastResult.isSuccess()) { + lastResult = action.render(); + } + } finally { + action.release(); + cleanupThread(); + } + + return lastResult; + } catch (Throwable t) { + // get the real cause of the exception. + Throwable t2 = t; + while (t2.getCause() != null) { + t2 = t.getCause(); + } + return ERROR_UNKNOWN.createResult(t2.getMessage(), t); + } + } + + @Override + public void clearCaches(Object projectKey) { + if (projectKey != null) { + sProjectBitmapCache.remove(projectKey); + sProject9PatchCache.remove(projectKey); + } + } + + @Override + public Result getViewParent(Object viewObject) { + if (viewObject instanceof View) { + return Status.SUCCESS.createResult(((View)viewObject).getParent()); + } + + throw new IllegalArgumentException("viewObject is not a View"); + } + + @Override + public Result getViewIndex(Object viewObject) { + if (viewObject instanceof View) { + View view = (View) viewObject; + ViewParent parentView = view.getParent(); + + if (parentView instanceof ViewGroup) { + Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view)); + } + + return Status.SUCCESS.createResult(); + } + + throw new IllegalArgumentException("viewObject is not a View"); + } + + @Override + public boolean isRtl(String locale) { + return isLocaleRtl(locale); + } + + public static boolean isLocaleRtl(String locale) { + if (locale == null) { + locale = ""; + } + ULocale uLocale = new ULocale(locale); + return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL); + } + + /** + * Returns the lock for the bridge + */ + public static ReentrantLock getLock() { + return sLock; + } + + /** + * Prepares the current thread for rendering. + * + * Note that while this can be called several time, the first call to {@link #cleanupThread()} + * will do the clean-up, and make the thread unable to do further scene actions. + */ + public synchronized static void prepareThread() { + // we need to make sure the Looper has been initialized for this thread. + // this is required for View that creates Handler objects. + if (Looper.myLooper() == null) { + Looper.prepareMainLooper(); + } + } + + /** + * Cleans up thread-specific data. After this, the thread cannot be used for scene actions. + * <p> + * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single + * call to this will prevent the thread from doing further scene actions + */ + public synchronized static void cleanupThread() { + // clean up the looper + Looper_Accessor.cleanupThread(); + } + + public static LayoutLog getLog() { + return sCurrentLog; + } + + public static void setLog(LayoutLog log) { + // check only the thread currently owning the lock can do this. + if (!sLock.isHeldByCurrentThread()) { + throw new IllegalStateException("scene must be acquired first. see #acquire(long)"); + } + + if (log != null) { + sCurrentLog = log; + } else { + sCurrentLog = sDefaultLog; + } + } + + /** + * Returns details of a framework resource from its integer value. + * @param value the integer value + * @return a Pair containing the resource type and name, or null if the id + * does not match any resource. + */ + @SuppressWarnings("deprecation") + public static Pair<ResourceType, String> resolveResourceId(int value) { + Pair<ResourceType, String> pair = sRMap.get(value); + if (pair == null) { + pair = sDynamicIds.resolveId(value); + } + return pair; + } + + /** + * Returns the integer id of a framework resource, from a given resource type and resource name. + * <p/> + * If no resource is found, it creates a dynamic id for the resource. + * + * @param type the type of the resource + * @param name the name of the resource. + * + * @return an {@link Integer} containing the resource id. + */ + @NonNull + public static Integer getResourceId(ResourceType type, String name) { + Map<String, Integer> map = sRevRMap.get(type); + Integer value = null; + if (map != null) { + value = map.get(name); + } + + return value == null ? sDynamicIds.getId(type, name) : value; + + } + + /** + * Returns the list of possible enums for a given attribute name. + */ + public static Map<String, Integer> getEnumValues(String attributeName) { + if (sEnumValueMap != null) { + return sEnumValueMap.get(attributeName); + } + + return null; + } + + /** + * Returns the platform build properties. + */ + public static Map<String, String> getPlatformProperties() { + return sPlatformProperties; + } + + /** + * Returns the bitmap for a specific path, from a specific project cache, or from the + * framework cache. + * @param value the path of the bitmap + * @param projectKey the key of the project, or null to query the framework cache. + * @return the cached Bitmap or null if not found. + */ + public static Bitmap getCachedBitmap(String value, Object projectKey) { + if (projectKey != null) { + Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); + if (map != null) { + SoftReference<Bitmap> ref = map.get(value); + if (ref != null) { + return ref.get(); + } + } + } else { + SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value); + if (ref != null) { + return ref.get(); + } + } + + return null; + } + + /** + * Sets a bitmap in a project cache or in the framework cache. + * @param value the path of the bitmap + * @param bmp the Bitmap object + * @param projectKey the key of the project, or null to put the bitmap in the framework cache. + */ + public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) { + if (projectKey != null) { + Map<String, SoftReference<Bitmap>> map = + sProjectBitmapCache.computeIfAbsent(projectKey, k -> new HashMap<>()); + + map.put(value, new SoftReference<>(bmp)); + } else { + sFrameworkBitmapCache.put(value, new SoftReference<>(bmp)); + } + } + + /** + * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the + * framework cache. + * @param value the path of the 9 patch + * @param projectKey the key of the project, or null to query the framework cache. + * @return the cached 9 patch or null if not found. + */ + public static NinePatchChunk getCached9Patch(String value, Object projectKey) { + if (projectKey != null) { + Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey); + + if (map != null) { + SoftReference<NinePatchChunk> ref = map.get(value); + if (ref != null) { + return ref.get(); + } + } + } else { + SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value); + if (ref != null) { + return ref.get(); + } + } + + return null; + } + + /** + * Sets a 9 patch chunk in a project cache or in the framework cache. + * @param value the path of the 9 patch + * @param ninePatch the 9 patch object + * @param projectKey the key of the project, or null to put the bitmap in the framework cache. + */ + public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) { + if (projectKey != null) { + Map<String, SoftReference<NinePatchChunk>> map = + sProject9PatchCache.computeIfAbsent(projectKey, k -> new HashMap<>()); + + map.put(value, new SoftReference<>(ninePatch)); + } else { + sFramework9PatchCache.put(value, new SoftReference<>(ninePatch)); + } } } diff --git a/com/android/layoutlib/bridge/android/BridgePackageManager.java b/com/android/layoutlib/bridge/android/BridgePackageManager.java index 98937ef3..37dc166f 100644 --- a/com/android/layoutlib/bridge/android/BridgePackageManager.java +++ b/com/android/layoutlib/bridge/android/BridgePackageManager.java @@ -26,12 +26,11 @@ import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ChangedPackages; -import android.content.pm.InstantAppInfo; import android.content.pm.FeatureInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; -import android.content.pm.IPackageInstallObserver; import android.content.pm.IPackageStatsObserver; +import android.content.pm.InstantAppInfo; import android.content.pm.InstrumentationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.KeySet; @@ -56,6 +55,7 @@ import android.net.Uri; import android.os.Handler; import android.os.UserHandle; import android.os.storage.VolumeInfo; + import java.util.List; /** @@ -611,11 +611,6 @@ public class BridgePackageManager extends PackageManager { } @Override - public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags, - String installerPackageName) { - } - - @Override public void installPackage(Uri packageURI, PackageInstallObserver observer, int flags, String installerPackageName) { } diff --git a/com/android/providers/settings/SettingsProtoDumpUtil.java b/com/android/providers/settings/SettingsProtoDumpUtil.java index 41b205bc..52b4f4df 100644 --- a/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -801,6 +801,9 @@ class SettingsProtoDumpUtil { Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX, GlobalSettingsProto.BLUETOOTH_PAN_PRIORITY_PREFIX); dumpSetting(s, p, + Settings.Global.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX, + GlobalSettingsProto.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX); + dumpSetting(s, p, Settings.Global.ACTIVITY_MANAGER_CONSTANTS, GlobalSettingsProto.ACTIVITY_MANAGER_CONSTANTS); dumpSetting(s, p, @@ -869,6 +872,15 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.WAIT_FOR_DEBUGGER, GlobalSettingsProto.WAIT_FOR_DEBUGGER); + dumpSetting(s, p, + Settings.Global.ENABLE_GPU_DEBUG_LAYERS, + GlobalSettingsProto.ENABLE_GPU_DEBUG_LAYERS); + dumpSetting(s, p, + Settings.Global.GPU_DEBUG_APP, + GlobalSettingsProto.GPU_DEBUG_APP); + dumpSetting(s, p, + Settings.Global.GPU_DEBUG_LAYERS, + GlobalSettingsProto.GPU_DEBUG_LAYERS); // Settings.Global.SHOW_PROCESSES intentionally excluded since it's deprecated. dumpSetting(s, p, Settings.Global.LOW_POWER_MODE, diff --git a/com/android/providers/settings/SettingsProvider.java b/com/android/providers/settings/SettingsProvider.java index 258c96cf..7fb6ede8 100644 --- a/com/android/providers/settings/SettingsProvider.java +++ b/com/android/providers/settings/SettingsProvider.java @@ -61,6 +61,7 @@ import android.os.UserManager; import android.os.UserManagerInternal; import android.provider.Settings; import android.provider.Settings.Global; +import android.provider.Settings.Secure; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -343,7 +344,8 @@ public class SettingsProvider extends ContentProvider { } case Settings.CALL_METHOD_GET_SECURE: { - Setting setting = getSecureSetting(name, requestingUserId); + Setting setting = getSecureSetting(name, requestingUserId, + /*enableOverride=*/ true); return packageValueForCallResult(setting, isTrackingGeneration(args)); } @@ -1073,6 +1075,10 @@ public class SettingsProvider extends ContentProvider { } private Setting getSecureSetting(String name, int requestingUserId) { + return getSecureSetting(name, requestingUserId, /*enableOverride=*/ false); + } + + private Setting getSecureSetting(String name, int requestingUserId, boolean enableOverride) { if (DEBUG) { Slog.v(LOG_TAG, "getSecureSetting(" + name + ", " + requestingUserId + ")"); } @@ -1102,6 +1108,14 @@ public class SettingsProvider extends ContentProvider { return getSsaidSettingLocked(callingPkg, owningUserId); } } + if (enableOverride) { + if (Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) { + final Setting overridden = getLocationProvidersAllowedSetting(owningUserId); + if (overridden != null) { + return overridden; + } + } + } // Not the SSAID; do a straight lookup synchronized (mLock) { @@ -1190,6 +1204,35 @@ public class SettingsProvider extends ContentProvider { return null; } + private Setting getLocationProvidersAllowedSetting(int owningUserId) { + synchronized (mLock) { + final Setting setting = getGlobalSetting( + Global.LOCATION_GLOBAL_KILL_SWITCH); + if (!"1".equals(setting.getValue())) { + return null; + } + // Global kill-switch is enabled. Return an empty value. + final SettingsState settingsState = mSettingsRegistry.getSettingsLocked( + SETTINGS_TYPE_SECURE, owningUserId); + return settingsState.new Setting( + Secure.LOCATION_PROVIDERS_ALLOWED, + "", // value + "", // tag + "", // default value + "", // package name + false, // from system + "0" // id + ) { + @Override + public boolean update(String value, boolean setDefault, String packageName, + String tag, boolean forceNonSystemPackage) { + Slog.wtf(LOG_TAG, "update shoudln't be called on this instance."); + return false; + } + }; + } + } + private boolean insertSecureSetting(String name, String value, String tag, boolean makeDefault, int requestingUserId, boolean forceNotify) { if (DEBUG) { @@ -2780,6 +2823,12 @@ public class SettingsProvider extends ContentProvider { } mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget(); + + // When the global kill switch is updated, send the change notification for + // the location setting. + if (isGlobalSettingsKey(key) && Global.LOCATION_GLOBAL_KILL_SWITCH.equals(name)) { + notifyLocationChangeForRunningUsers(); + } } private void maybeNotifyProfiles(int type, int userId, Uri uri, String name, @@ -2799,6 +2848,24 @@ public class SettingsProvider extends ContentProvider { } } + private void notifyLocationChangeForRunningUsers() { + final List<UserInfo> users = mUserManager.getUsers(/*excludeDying=*/ true); + + for (int i = 0; i < users.size(); i++) { + final int userId = users.get(i).id; + + if (!mUserManager.isUserRunning(UserHandle.of(userId))) { + continue; + } + + final int key = makeKey(SETTINGS_TYPE_GLOBAL, userId); + final Uri uri = getNotificationUriFor(key, Secure.LOCATION_PROVIDERS_ALLOWED); + + mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED, + userId, 0, uri).sendToTarget(); + } + } + private boolean isGlobalSettingsKey(int key) { return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL; } @@ -2885,7 +2952,7 @@ public class SettingsProvider extends ContentProvider { } catch (SecurityException e) { Slog.w(LOG_TAG, "Failed to notify for " + userId + ": " + uri, e); } - if (DEBUG) { + if (DEBUG || true) { Slog.v(LOG_TAG, "Notifying for " + userId + ": " + uri); } } break; diff --git a/com/android/server/AlarmManagerService.java b/com/android/server/AlarmManagerService.java index 3904fc96..ca152495 100644 --- a/com/android/server/AlarmManagerService.java +++ b/com/android/server/AlarmManagerService.java @@ -46,16 +46,15 @@ import android.os.Message; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.os.WorkSource; import android.provider.Settings; import android.text.TextUtils; import android.text.format.DateFormat; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.KeyValueListParser; import android.util.Log; import android.util.Slog; @@ -81,6 +80,7 @@ import java.util.Locale; import java.util.Random; import java.util.TimeZone; import java.util.TreeSet; +import java.util.function.Predicate; import static android.app.AlarmManager.RTC_WAKEUP; import static android.app.AlarmManager.RTC; @@ -88,11 +88,17 @@ import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.app.AlarmManager.ELAPSED_REALTIME; import com.android.internal.annotations.GuardedBy; -import com.android.internal.app.IAppOpsCallback; -import com.android.internal.app.IAppOpsService; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.util.LocalLog; +import com.android.server.ForceAppStandbyTracker.Listener; +/** + * Alarm manager implementaion. + * + * Unit test: + atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java + */ class AlarmManagerService extends SystemService { private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP; private static final int RTC_MASK = 1 << RTC; @@ -131,13 +137,10 @@ class AlarmManagerService extends SystemService { final LocalLog mLog = new LocalLog(TAG); AppOpsManager mAppOps; - IAppOpsService mAppOpsService; DeviceIdleController.LocalService mLocalDeviceIdleController; final Object mLock = new Object(); - ArraySet<String> mForcedAppStandbyPackages = new ArraySet<>(); - SparseBooleanArray mForegroundUids = new SparseBooleanArray(); // List of alarms per uid deferred due to user applied background restrictions on the source app SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>(); long mNativeData; @@ -184,12 +187,6 @@ class AlarmManagerService extends SystemService { int mSystemUiUid; /** - * The current set of user whitelisted apps for device idle mode, meaning these are allowed - * to freely schedule alarms. - */ - int[] mDeviceIdleUserWhitelist = new int[0]; - - /** * For each uid, this is the last time we dispatched an "allow while idle" alarm, * used to determine the earliest we can dispatch the next such alarm. Times are in the * 'elapsed' timebase. @@ -223,6 +220,8 @@ class AlarmManagerService extends SystemService { private final SparseArray<AlarmManager.AlarmClockInfo> mHandlerSparseAlarmClockArray = new SparseArray<>(); + private final ForceAppStandbyTracker mForceAppStandbyTracker; + /** * All times are in milliseconds. These constants are kept synchronized with the system * global Settings. Any access to this class or its fields should be done while @@ -757,6 +756,9 @@ class AlarmManagerService extends SystemService { public AlarmManagerService(Context context) { super(context); mConstants = new Constants(mHandler); + + mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context); + mForceAppStandbyTracker.addListener(mForceAppStandbyListener); } static long convertToElapsed(long when, int type) { @@ -894,17 +896,48 @@ class AlarmManagerService extends SystemService { deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime()); } - void sendPendingBackgroundAlarmsForAppIdLocked(int appId) { + /** + * Check all alarms in {@link #mPendingBackgroundAlarms} and send the ones that are not + * restricted. + * + * This is only called when the global "force all apps-standby" flag changes or when the + * power save whitelist changes, so it's okay to be slow. + */ + void sendAllUnrestrictedPendingBackgroundAlarmsLocked() { final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>(); - for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) { - final int uid = mPendingBackgroundAlarms.keyAt(i); - final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i); - if (UserHandle.getAppId(uid) == appId) { - alarmsToDeliver.addAll(alarmsForUid); - mPendingBackgroundAlarms.removeAt(i); + + findAllUnrestrictedPendingBackgroundAlarmsLockedInner( + mPendingBackgroundAlarms, alarmsToDeliver, this::isBackgroundRestricted); + + if (alarmsToDeliver.size() > 0) { + deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime()); + } + } + + @VisibleForTesting + static void findAllUnrestrictedPendingBackgroundAlarmsLockedInner( + SparseArray<ArrayList<Alarm>> pendingAlarms, ArrayList<Alarm> unrestrictedAlarms, + Predicate<Alarm> isBackgroundRestricted) { + + for (int uidIndex = pendingAlarms.size() - 1; uidIndex >= 0; uidIndex--) { + final int uid = pendingAlarms.keyAt(uidIndex); + final ArrayList<Alarm> alarmsForUid = pendingAlarms.valueAt(uidIndex); + + for (int alarmIndex = alarmsForUid.size() - 1; alarmIndex >= 0; alarmIndex--) { + final Alarm alarm = alarmsForUid.get(alarmIndex); + + if (isBackgroundRestricted.test(alarm)) { + continue; + } + + unrestrictedAlarms.add(alarm); + alarmsForUid.remove(alarmIndex); + } + + if (alarmsForUid.size() == 0) { + pendingAlarms.removeAt(uidIndex); } } - deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime()); } private void deliverPendingBackgroundAlarmsLocked(ArrayList<Alarm> alarms, long nowELAPSED) { @@ -1234,10 +1267,8 @@ class AlarmManagerService extends SystemService { } catch (RemoteException e) { // ignored; both services live in system_server } - mAppOpsService = IAppOpsService.Stub.asInterface( - ServiceManager.getService(Context.APP_OPS_SERVICE)); publishBinderService(Context.ALARM_SERVICE, mService); - publishLocalService(LocalService.class, new LocalService()); + mForceAppStandbyTracker.start(); } @Override @@ -1247,13 +1278,6 @@ class AlarmManagerService extends SystemService { mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); mLocalDeviceIdleController = LocalServices.getService(DeviceIdleController.LocalService.class); - try { - mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null, - new AppOpsWatcher()); - } catch (RemoteException rexc) { - // Shouldn't happen as they are in the same process. - Slog.e(TAG, "AppOps service not reachable", rexc); - } } } @@ -1582,8 +1606,7 @@ class AlarmManagerService extends SystemService { // timing restrictions. } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID || callingUid == mSystemUiUid - || Arrays.binarySearch(mDeviceIdleUserWhitelist, - UserHandle.getAppId(callingUid)) >= 0)) { + || mForceAppStandbyTracker.isUidPowerSaveWhitelisted(callingUid))) { flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE; } @@ -1660,24 +1683,14 @@ class AlarmManagerService extends SystemService { } }; - public final class LocalService { - public void setDeviceIdleUserWhitelist(int[] appids) { - setDeviceIdleUserWhitelistImpl(appids); - } - } - void dumpImpl(PrintWriter pw) { synchronized (mLock) { pw.println("Current Alarm Manager state:"); mConstants.dump(pw); pw.println(); - pw.print(" Foreground uids: ["); - for (int i = 0; i < mForegroundUids.size(); i++) { - if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " "); - } - pw.println("]"); - pw.println(" Forced app standby packages: " + mForcedAppStandbyPackages); + mForceAppStandbyTracker.dump(pw, " "); + final long nowRTC = System.currentTimeMillis(); final long nowELAPSED = SystemClock.elapsedRealtime(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @@ -1717,7 +1730,6 @@ class AlarmManagerService extends SystemService { pw.print(" set at "); TimeUtils.formatDuration(mLastWakeupSet, nowELAPSED, pw); pw.println(); pw.print(" Num time change events: "); pw.println(mNumTimeChanged); - pw.println(" mDeviceIdleUserWhitelist=" + Arrays.toString(mDeviceIdleUserWhitelist)); pw.println(); pw.println(" Next alarm clock information: "); @@ -1990,15 +2002,8 @@ class AlarmManagerService extends SystemService { mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS); - final int foregroundUidsSize = mForegroundUids.size(); - for (int i = 0; i < foregroundUidsSize; i++) { - if (mForegroundUids.valueAt(i)) { - proto.write(AlarmManagerServiceProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i)); - } - } - for (String pkg : mForcedAppStandbyPackages) { - proto.write(AlarmManagerServiceProto.FORCED_APP_STANDBY_PACKAGES, pkg); - } + mForceAppStandbyTracker.dumpProto(proto, + AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER); proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive); if (!mInteractive) { @@ -2022,9 +2027,6 @@ class AlarmManagerService extends SystemService { proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_SET_MS, nowElapsed - mLastWakeupSet); proto.write(AlarmManagerServiceProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged); - for (int i : mDeviceIdleUserWhitelist) { - proto.write(AlarmManagerServiceProto.DEVICE_IDLE_USER_WHITELIST_APP_IDS, i); - } final TreeSet<Integer> users = new TreeSet<>(); final int nextAlarmClockForUserSize = mNextAlarmClockForUser.size(); @@ -2266,28 +2268,6 @@ class AlarmManagerService extends SystemService { } } - void setDeviceIdleUserWhitelistImpl(int[] appids) { - synchronized (mLock) { - // appids are sorted, just send pending alarms for any new appids added to the whitelist - int i = 0, j = 0; - while (i < appids.length) { - while (j < mDeviceIdleUserWhitelist.length - && mDeviceIdleUserWhitelist[j] < appids[i]) { - j++; - } - if (j < mDeviceIdleUserWhitelist.length - && appids[i] != mDeviceIdleUserWhitelist[j]) { - if (DEBUG_BG_LIMIT) { - Slog.d(TAG, "Sending blocked alarms for whitelisted appid " + appids[j]); - } - sendPendingBackgroundAlarmsForAppIdLocked(appids[j]); - } - i++; - } - mDeviceIdleUserWhitelist = appids; - } - } - AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) { synchronized (mLock) { return mNextAlarmClockForUser.get(userId); @@ -2710,9 +2690,7 @@ class AlarmManagerService extends SystemService { final String sourcePackage = (alarm.operation != null) ? alarm.operation.getCreatorPackage() : alarm.packageName; final int sourceUid = alarm.creatorUid; - return mForcedAppStandbyPackages.contains(sourcePackage) && !mForegroundUids.get(sourceUid) - && Arrays.binarySearch(mDeviceIdleUserWhitelist, UserHandle.getAppId(sourceUid)) - < 0; + return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage); } private native long init(); @@ -2859,7 +2837,8 @@ class AlarmManagerService extends SystemService { } } - private static class Alarm { + @VisibleForTesting + static class Alarm { public final int type; public final long origWhen; public final boolean wakeup; @@ -3073,6 +3052,11 @@ class AlarmManagerService extends SystemService { for (int i=0; i<triggerList.size(); i++) { Alarm alarm = triggerList.get(i); final boolean allowWhileIdle = (alarm.flags&AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0; + if (alarm.wakeup) { + Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch wakeup alarm to " + alarm.packageName); + } else { + Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch non-wakeup alarm to " + alarm.packageName); + } try { if (localLOGV) { Slog.v(TAG, "sending alarm " + alarm); @@ -3092,6 +3076,7 @@ class AlarmManagerService extends SystemService { } catch (RuntimeException e) { Slog.w(TAG, "Failure sending alarm.", e); } + Trace.traceEnd(Trace.TRACE_TAG_POWER); } } @@ -3476,17 +3461,10 @@ class AlarmManagerService extends SystemService { if (disabled) { removeForStoppedLocked(uid); } - mForegroundUids.delete(uid); } } @Override public void onUidActive(int uid) { - synchronized (mLock) { - if (!mForegroundUids.get(uid)) { - mForegroundUids.put(uid, true); - sendPendingBackgroundAlarmsLocked(uid, null); - } - } } @Override public void onUidIdle(int uid, boolean disabled) { @@ -3494,7 +3472,6 @@ class AlarmManagerService extends SystemService { if (disabled) { removeForStoppedLocked(uid); } - mForegroundUids.delete(uid); } } @@ -3502,27 +3479,29 @@ class AlarmManagerService extends SystemService { } }; - private final class AppOpsWatcher extends IAppOpsCallback.Stub { + + private final Listener mForceAppStandbyListener = new Listener() { @Override - public void opChanged(int op, int uid, String packageName) throws RemoteException { + public void unblockAllUnrestrictedAlarms() { synchronized (mLock) { - final int mode = mAppOpsService.checkOperation(op, uid, packageName); - if (DEBUG_BG_LIMIT) { - Slog.d(TAG, - "Appop changed for " + uid + ", " + packageName + " to " + mode); - } - final boolean changed; - if (mode != AppOpsManager.MODE_ALLOWED) { - changed = mForcedAppStandbyPackages.add(packageName); - } else { - changed = mForcedAppStandbyPackages.remove(packageName); - } - if (changed && mode == AppOpsManager.MODE_ALLOWED) { - sendPendingBackgroundAlarmsLocked(uid, packageName); - } + sendAllUnrestrictedPendingBackgroundAlarmsLocked(); } } - } + + @Override + public void unblockAlarmsForUid(int uid) { + synchronized (mLock) { + sendPendingBackgroundAlarmsLocked(uid, null); + } + } + + @Override + public void unblockAlarmsForUidPackage(int uid, String packageName) { + synchronized (mLock) { + sendPendingBackgroundAlarmsLocked(uid, packageName); + } + } + }; private final BroadcastStats getStatsLocked(PendingIntent pi) { String pkg = pi.getCreatorPackage(); diff --git a/com/android/server/BatteryService.java b/com/android/server/BatteryService.java index ea0ed271..924e736b 100644 --- a/com/android/server/BatteryService.java +++ b/com/android/server/BatteryService.java @@ -619,6 +619,7 @@ public final class BatteryService extends SystemService { intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.batteryHealth); intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.batteryPresent); intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.batteryLevel); + intent.putExtra(BatteryManager.EXTRA_BATTERY_LOW, mSentLowBatteryBroadcast); intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE); intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon); intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType); diff --git a/com/android/server/BluetoothManagerService.java b/com/android/server/BluetoothManagerService.java index c34c30cf..04279a31 100644 --- a/com/android/server/BluetoothManagerService.java +++ b/com/android/server/BluetoothManagerService.java @@ -2150,31 +2150,26 @@ class BluetoothManagerService extends IBluetoothManager.Stub { (int)((onDuration / (1000 * 60)) % 60), (int)((onDuration / 1000) % 60), (int)(onDuration % 1000)); - writer.println(" time since enabled: " + onDurationString + "\n"); + writer.println(" time since enabled: " + onDurationString); } if (mActiveLogs.size() == 0) { - writer.println("Bluetooth never enabled!"); + writer.println("\nBluetooth never enabled!"); } else { - writer.println("Enable log:"); + writer.println("\nEnable log:"); for (ActiveLog log : mActiveLogs) { writer.println(" " + log); } } - writer.println("Bluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s")); + writer.println("\nBluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s")); if (mCrashes == CRASH_LOG_MAX_SIZE) writer.println("(last " + CRASH_LOG_MAX_SIZE + ")"); for (Long time : mCrashTimestamps) { writer.println(" " + timeToLog(time.longValue())); } - String bleAppString = "No BLE Apps registered."; - if (mBleApps.size() == 1) { - bleAppString = "1 BLE App registered:"; - } else if (mBleApps.size() > 1) { - bleAppString = mBleApps.size() + " BLE Apps registered:"; - } - writer.println("\n" + bleAppString); + writer.println("\n" + mBleApps.size() + " BLE app" + + (mBleApps.size() == 1 ? "" : "s") + "registered"); for (ClientDeathRecipient app : mBleApps.values()) { writer.println(" " + app.getPackageName()); } diff --git a/com/android/server/DeviceIdleController.java b/com/android/server/DeviceIdleController.java index 0921a00b..d7aeb8ce 100644 --- a/com/android/server/DeviceIdleController.java +++ b/com/android/server/DeviceIdleController.java @@ -119,7 +119,6 @@ public class DeviceIdleController extends SystemService private PowerManagerInternal mLocalPowerManager; private PowerManager mPowerManager; private ConnectivityService mConnectivityService; - private AlarmManagerService.LocalService mLocalAlarmManager; private INetworkPolicyManager mNetworkPolicyManager; private SensorManager mSensorManager; private Sensor mMotionSensor; @@ -1435,7 +1434,6 @@ public class DeviceIdleController extends SystemService mGoingIdleWakeLock.setReferenceCounted(true); mConnectivityService = (ConnectivityService)ServiceManager.getService( Context.CONNECTIVITY_SERVICE); - mLocalAlarmManager = getLocalService(AlarmManagerService.LocalService.class); mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface( ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); mNetworkPolicyManagerInternal = getLocalService(NetworkPolicyManagerInternal.class); @@ -1500,8 +1498,8 @@ public class DeviceIdleController extends SystemService mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); - mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray); + passWhiteListToForceAppStandbyTrackerLocked(); updateInteractivityLocked(); } updateConnectivityState(null); @@ -2477,13 +2475,7 @@ public class DeviceIdleController extends SystemService } mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); } - if (mLocalAlarmManager != null) { - if (DEBUG) { - Slog.d(TAG, "Setting alarm whitelist to " - + Arrays.toString(mPowerSaveWhitelistUserAppIdArray)); - } - mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray); - } + passWhiteListToForceAppStandbyTrackerLocked(); } private void updateTempWhitelistAppIdsLocked(int appId, boolean adding) { @@ -2509,6 +2501,7 @@ public class DeviceIdleController extends SystemService } mLocalPowerManager.setDeviceIdleTempWhitelist(mTempWhitelistAppIdArray); } + passWhiteListToForceAppStandbyTrackerLocked(); } private void reportPowerSaveWhitelistChangedLocked() { @@ -2523,6 +2516,12 @@ public class DeviceIdleController extends SystemService getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM); } + private void passWhiteListToForceAppStandbyTrackerLocked() { + ForceAppStandbyTracker.getInstance(getContext()).setPowerSaveWhitelistAppIds( + mPowerSaveWhitelistAllAppIdArray, + mTempWhitelistAppIdArray); + } + void readConfigFileLocked() { if (DEBUG) Slog.d(TAG, "Reading config from " + mConfigFile.getBaseFile()); mPowerSaveWhitelistUserApps.clear(); diff --git a/com/android/server/ForceAppStandbyTracker.java b/com/android/server/ForceAppStandbyTracker.java new file mode 100644 index 00000000..61d3833d --- /dev/null +++ b/com/android/server/ForceAppStandbyTracker.java @@ -0,0 +1,838 @@ +/* + * 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 com.android.server; + +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.app.AppOpsManager.PackageOps; +import android.app.IActivityManager; +import android.app.IUidObserver; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager.ServiceType; +import android.os.PowerManagerInternal; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.util.ArraySet; +import android.util.Pair; +import android.util.SparseBooleanArray; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.IAppOpsCallback; +import com.android.internal.app.IAppOpsService; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; +import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages; + +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.List; + +/** + * Class to keep track of the information related to "force app standby", which includes: + * - OP_RUN_ANY_IN_BACKGROUND for each package + * - UID foreground state + * - User+system power save whitelist + * - Temporary power save whitelist + * - Global "force all apps standby" mode enforced by battery saver. + * + * TODO: In general, we can reduce the number of callbacks by checking all signals before sending + * each callback. For example, even when an UID comes into the foreground, if it wasn't + * originally restricted, then there's no need to send an event. + * Doing this would be error-prone, so we punt it for now, but we should revisit it later. + * + * Test: + atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java + */ +public class ForceAppStandbyTracker { + private static final String TAG = "ForceAppStandbyTracker"; + + @GuardedBy("ForceAppStandbyTracker.class") + private static ForceAppStandbyTracker sInstance; + + private final Object mLock = new Object(); + private final Context mContext; + + @VisibleForTesting + static final int TARGET_OP = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND; + + IActivityManager mIActivityManager; + AppOpsManager mAppOpsManager; + IAppOpsService mAppOpsService; + PowerManagerInternal mPowerManagerInternal; + + private final MyHandler mHandler; + + /** + * Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed. + */ + @GuardedBy("mLock") + final ArraySet<Pair<Integer, String>> mRunAnyRestrictedPackages = new ArraySet<>(); + + @GuardedBy("mLock") + final SparseBooleanArray mForegroundUids = new SparseBooleanArray(); + + @GuardedBy("mLock") + private int[] mPowerWhitelistedAllAppIds = new int[0]; + + @GuardedBy("mLock") + private int[] mTempWhitelistedAppIds = mPowerWhitelistedAllAppIds; + + @GuardedBy("mLock") + final ArraySet<Listener> mListeners = new ArraySet<>(); + + @GuardedBy("mLock") + boolean mStarted; + + @GuardedBy("mLock") + boolean mForceAllAppsStandby; + + public static abstract class Listener { + /** + * This is called when the OP_RUN_ANY_IN_BACKGROUND appops changed for a package. + */ + private void onRunAnyAppOpsChanged(ForceAppStandbyTracker sender, + int uid, @NonNull String packageName) { + updateJobsForUidPackage(uid, packageName); + + if (!sender.areAlarmsRestricted(uid, packageName)) { + unblockAlarmsForUidPackage(uid, packageName); + } + } + + /** + * This is called when the foreground state changed for a UID. + */ + private void onUidForegroundStateChanged(ForceAppStandbyTracker sender, int uid) { + updateJobsForUid(uid); + + if (sender.isInForeground(uid)) { + unblockAlarmsForUid(uid); + } + } + + /** + * This is called when an app-id(s) is removed from the power save whitelist. + */ + private void onPowerSaveUnwhitelisted(ForceAppStandbyTracker sender) { + updateAllJobs(); + unblockAllUnrestrictedAlarms(); + } + + /** + * This is called when the power save whitelist changes, excluding the + * {@link #onPowerSaveUnwhitelisted} case. + */ + private void onPowerSaveWhitelistedChanged(ForceAppStandbyTracker sender) { + updateAllJobs(); + } + + /** + * This is called when the temp whitelist changes. + */ + private void onTempPowerSaveWhitelistChanged(ForceAppStandbyTracker sender) { + + // TODO This case happens rather frequently; consider optimizing and update jobs + // only for affected app-ids. + + updateAllJobs(); + } + + /** + * This is called when the global "force all apps standby" flag changes. + */ + private void onForceAllAppsStandbyChanged(ForceAppStandbyTracker sender) { + updateAllJobs(); + + if (!sender.isForceAllAppsStandbyEnabled()) { + unblockAllUnrestrictedAlarms(); + } + } + + /** + * Called when the job restrictions for multiple UIDs might have changed, so the job + * scheduler should re-evaluate all restrictions for all jobs. + */ + public void updateAllJobs() { + } + + /** + * Called when the job restrictions for a UID might have changed, so the job + * scheduler should re-evaluate all restrictions for all jobs. + */ + public void updateJobsForUid(int uid) { + } + + /** + * Called when the job restrictions for a UID - package might have changed, so the job + * scheduler should re-evaluate all restrictions for all jobs. + */ + public void updateJobsForUidPackage(int uid, String packageName) { + } + + /** + * Called when the job restrictions for multiple UIDs might have changed, so the alarm + * manager should re-evaluate all restrictions for all blocked jobs. + */ + public void unblockAllUnrestrictedAlarms() { + } + + /** + * Called when all jobs for a specific UID are unblocked. + */ + public void unblockAlarmsForUid(int uid) { + } + + /** + * Called when all alarms for a specific UID - package are unblocked. + */ + public void unblockAlarmsForUidPackage(int uid, String packageName) { + } + } + + @VisibleForTesting + ForceAppStandbyTracker(Context context, Looper looper) { + mContext = context; + mHandler = new MyHandler(looper); + } + + private ForceAppStandbyTracker(Context context) { + this(context, FgThread.get().getLooper()); + } + + /** + * Get the singleton instance. + */ + public static synchronized ForceAppStandbyTracker getInstance(Context context) { + if (sInstance == null) { + sInstance = new ForceAppStandbyTracker(context); + } + return sInstance; + } + + /** + * Call it when the system is ready. + */ + public void start() { + synchronized (mLock) { + if (mStarted) { + return; + } + mStarted = true; + + mIActivityManager = Preconditions.checkNotNull(injectIActivityManager()); + mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager()); + mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService()); + mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal()); + + try { + mIActivityManager.registerUidObserver(new UidObserver(), + ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE + | ActivityManager.UID_OBSERVER_ACTIVE, + ActivityManager.PROCESS_STATE_UNKNOWN, null); + mAppOpsService.startWatchingMode(TARGET_OP, null, + new AppOpsWatcher()); + } catch (RemoteException e) { + // shouldn't happen. + } + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiver(new MyReceiver(), filter); + + refreshForcedAppStandbyUidPackagesLocked(); + + mPowerManagerInternal.registerLowPowerModeObserver( + ServiceType.FORCE_ALL_APPS_STANDBY, + (state) -> updateForceAllAppsStandby(state.batterySaverEnabled)); + + updateForceAllAppsStandby(mPowerManagerInternal.getLowPowerState( + ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled); + } + } + + @VisibleForTesting + AppOpsManager injectAppOpsManager() { + return mContext.getSystemService(AppOpsManager.class); + } + + @VisibleForTesting + IAppOpsService injectIAppOpsService() { + return IAppOpsService.Stub.asInterface( + ServiceManager.getService(Context.APP_OPS_SERVICE)); + } + + @VisibleForTesting + IActivityManager injectIActivityManager() { + return ActivityManager.getService(); + } + + @VisibleForTesting + PowerManagerInternal injectPowerManagerInternal() { + return LocalServices.getService(PowerManagerInternal.class); + } + + /** + * Update {@link #mRunAnyRestrictedPackages} with the current app ops state. + */ + private void refreshForcedAppStandbyUidPackagesLocked() { + mRunAnyRestrictedPackages.clear(); + final List<PackageOps> ops = mAppOpsManager.getPackagesForOps( + new int[] {TARGET_OP}); + + if (ops == null) { + return; + } + final int size = ops.size(); + for (int i = 0; i < size; i++) { + final AppOpsManager.PackageOps pkg = ops.get(i); + final List<AppOpsManager.OpEntry> entries = ops.get(i).getOps(); + + for (int j = 0; j < entries.size(); j++) { + AppOpsManager.OpEntry ent = entries.get(j); + if (ent.getOp() != TARGET_OP) { + continue; + } + if (ent.getMode() != AppOpsManager.MODE_ALLOWED) { + mRunAnyRestrictedPackages.add(Pair.create( + pkg.getUid(), pkg.getPackageName())); + } + } + } + } + + /** + * Update {@link #mForceAllAppsStandby} and notifies the listeners. + */ + void updateForceAllAppsStandby(boolean enable) { + synchronized (mLock) { + if (enable == mForceAllAppsStandby) { + return; + } + mForceAllAppsStandby = enable; + + mHandler.notifyForceAllAppsStandbyChanged(); + } + } + + private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) { + final int size = mRunAnyRestrictedPackages.size(); + if (size > 8) { + return mRunAnyRestrictedPackages.indexOf(Pair.create(uid, packageName)); + } + for (int i = 0; i < size; i++) { + final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i); + + if ((pair.first == uid) && packageName.equals(pair.second)) { + return i; + } + } + return -1; + } + + /** + * @return whether a uid package-name pair is in mRunAnyRestrictedPackages. + */ + boolean isRunAnyRestrictedLocked(int uid, @NonNull String packageName) { + return findForcedAppStandbyUidPackageIndexLocked(uid, packageName) >= 0; + } + + /** + * Add to / remove from {@link #mRunAnyRestrictedPackages}. + */ + boolean updateForcedAppStandbyUidPackageLocked(int uid, @NonNull String packageName, + boolean restricted) { + final int index = findForcedAppStandbyUidPackageIndexLocked(uid, packageName); + final boolean wasRestricted = index >= 0; + if (wasRestricted == restricted) { + return false; + } + if (restricted) { + mRunAnyRestrictedPackages.add(Pair.create(uid, packageName)); + } else { + mRunAnyRestrictedPackages.removeAt(index); + } + return true; + } + + /** + * Puts a UID to {@link #mForegroundUids}. + */ + void uidToForeground(int uid) { + synchronized (mLock) { + if (!UserHandle.isApp(uid)) { + return; + } + // TODO This can be optimized by calling indexOfKey and sharing the index for get and + // put. + if (mForegroundUids.get(uid)) { + return; + } + mForegroundUids.put(uid, true); + mHandler.notifyUidForegroundStateChanged(uid); + } + } + + /** + * Sets false for a UID {@link #mForegroundUids}, or remove it when {@code remove} is true. + */ + void uidToBackground(int uid, boolean remove) { + synchronized (mLock) { + if (!UserHandle.isApp(uid)) { + return; + } + // TODO This can be optimized by calling indexOfKey and sharing the index for get and + // put. + if (!mForegroundUids.get(uid)) { + return; + } + if (remove) { + mForegroundUids.delete(uid); + } else { + mForegroundUids.put(uid, false); + } + mHandler.notifyUidForegroundStateChanged(uid); + } + } + + private final class UidObserver extends IUidObserver.Stub { + @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) { + } + + @Override public void onUidGone(int uid, boolean disabled) { + uidToBackground(uid, /*remove=*/ true); + } + + @Override public void onUidActive(int uid) { + uidToForeground(uid); + } + + @Override public void onUidIdle(int uid, boolean disabled) { + // Just to avoid excessive memcpy, don't remove from the array in this case. + uidToBackground(uid, /*remove=*/ false); + } + + @Override public void onUidCachedChanged(int uid, boolean cached) { + } + }; + + private final class AppOpsWatcher extends IAppOpsCallback.Stub { + @Override + public void opChanged(int op, int uid, String packageName) throws RemoteException { + boolean restricted = false; + try { + restricted = mAppOpsService.checkOperation(TARGET_OP, + uid, packageName) != AppOpsManager.MODE_ALLOWED; + } catch (RemoteException e) { + // Shouldn't happen + } + synchronized (mLock) { + if (updateForcedAppStandbyUidPackageLocked(uid, packageName, restricted)) { + mHandler.notifyRunAnyAppOpsChanged(uid, packageName); + } + } + } + } + + private final class MyReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userId > 0) { + mHandler.doUserRemoved(userId); + } + } + } + } + + private Listener[] cloneListeners() { + synchronized (mLock) { + return mListeners.toArray(new Listener[mListeners.size()]); + } + } + + private class MyHandler extends Handler { + private static final int MSG_UID_STATE_CHANGED = 1; + private static final int MSG_RUN_ANY_CHANGED = 2; + private static final int MSG_ALL_UNWHITELISTED = 3; + private static final int MSG_ALL_WHITELIST_CHANGED = 4; + private static final int MSG_TEMP_WHITELIST_CHANGED = 5; + private static final int MSG_FORCE_ALL_CHANGED = 6; + + private static final int MSG_USER_REMOVED = 7; + + public MyHandler(Looper looper) { + super(looper); + } + + public void notifyUidForegroundStateChanged(int uid) { + obtainMessage(MSG_UID_STATE_CHANGED, uid, 0).sendToTarget(); + } + public void notifyRunAnyAppOpsChanged(int uid, @NonNull String packageName) { + obtainMessage(MSG_RUN_ANY_CHANGED, uid, 0, packageName).sendToTarget(); + } + + public void notifyAllUnwhitelisted() { + obtainMessage(MSG_ALL_UNWHITELISTED).sendToTarget(); + } + + public void notifyAllWhitelistChanged() { + obtainMessage(MSG_ALL_WHITELIST_CHANGED).sendToTarget(); + } + + public void notifyTempWhitelistChanged() { + obtainMessage(MSG_TEMP_WHITELIST_CHANGED).sendToTarget(); + } + + public void notifyForceAllAppsStandbyChanged() { + obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget(); + } + + public void doUserRemoved(int userId) { + obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget(); + } + + @Override + public void dispatchMessage(Message msg) { + switch (msg.what) { + case MSG_USER_REMOVED: + handleUserRemoved(msg.arg1); + return; + } + + // Only notify the listeners when started. + synchronized (mLock) { + if (!mStarted) { + return; + } + } + final ForceAppStandbyTracker sender = ForceAppStandbyTracker.this; + + switch (msg.what) { + case MSG_UID_STATE_CHANGED: + for (Listener l : cloneListeners()) { + l.onUidForegroundStateChanged(sender, msg.arg1); + } + return; + case MSG_RUN_ANY_CHANGED: + for (Listener l : cloneListeners()) { + l.onRunAnyAppOpsChanged(sender, msg.arg1, (String) msg.obj); + } + return; + case MSG_ALL_UNWHITELISTED: + for (Listener l : cloneListeners()) { + l.onPowerSaveUnwhitelisted(sender); + } + return; + case MSG_ALL_WHITELIST_CHANGED: + for (Listener l : cloneListeners()) { + l.onPowerSaveWhitelistedChanged(sender); + } + return; + case MSG_TEMP_WHITELIST_CHANGED: + for (Listener l : cloneListeners()) { + l.onTempPowerSaveWhitelistChanged(sender); + } + return; + case MSG_FORCE_ALL_CHANGED: + for (Listener l : cloneListeners()) { + l.onForceAllAppsStandbyChanged(sender); + } + return; + case MSG_USER_REMOVED: + handleUserRemoved(msg.arg1); + return; + } + } + } + + void handleUserRemoved(int removedUserId) { + synchronized (mLock) { + for (int i = mRunAnyRestrictedPackages.size() - 1; i >= 0; i--) { + final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i); + final int uid = pair.first; + final int userId = UserHandle.getUserId(uid); + + if (userId == removedUserId) { + mRunAnyRestrictedPackages.removeAt(i); + } + } + for (int i = mForegroundUids.size() - 1; i >= 0; i--) { + final int uid = mForegroundUids.keyAt(i); + final int userId = UserHandle.getUserId(uid); + + if (userId == removedUserId) { + mForegroundUids.removeAt(i); + } + } + } + } + + /** + * Called by device idle controller to update the power save whitelists. + */ + public void setPowerSaveWhitelistAppIds( + int[] powerSaveWhitelistAllAppIdArray, int[] tempWhitelistAppIdArray) { + synchronized (mLock) { + final int[] previousWhitelist = mPowerWhitelistedAllAppIds; + final int[] previousTempWhitelist = mTempWhitelistedAppIds; + + mPowerWhitelistedAllAppIds = powerSaveWhitelistAllAppIdArray; + mTempWhitelistedAppIds = tempWhitelistAppIdArray; + + if (isAnyAppIdUnwhitelisted(previousWhitelist, mPowerWhitelistedAllAppIds)) { + mHandler.notifyAllUnwhitelisted(); + } else if (!Arrays.equals(previousWhitelist, mPowerWhitelistedAllAppIds)) { + mHandler.notifyAllWhitelistChanged(); + } + + if (!Arrays.equals(previousTempWhitelist, mTempWhitelistedAppIds)) { + mHandler.notifyTempWhitelistChanged(); + } + + } + } + + /** + * @retunr true if a sorted app-id array {@code prevArray} has at least one element + * that's not in a sorted app-id array {@code newArray}. + */ + @VisibleForTesting + static boolean isAnyAppIdUnwhitelisted(int[] prevArray, int[] newArray) { + int i1 = 0; + int i2 = 0; + boolean prevFinished; + boolean newFinished; + + for (;;) { + prevFinished = i1 >= prevArray.length; + newFinished = i2 >= newArray.length; + if (prevFinished || newFinished) { + break; + } + int a1 = prevArray[i1]; + int a2 = newArray[i2]; + + if (a1 == a2) { + i1++; + i2++; + continue; + } + if (a1 < a2) { + // prevArray has an element that's not in a2. + return true; + } + i2++; + } + if (prevFinished) { + return false; + } + return newFinished; + } + + // Public interface. + + /** + * Register a new listener. + */ + public void addListener(@NonNull Listener listener) { + synchronized (mLock) { + mListeners.add(listener); + } + } + + /** + * @return whether alarms should be restricted for a UID package-name. + */ + public boolean areAlarmsRestricted(int uid, @NonNull String packageName) { + return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ false); + } + + /** + * @return whether jobs should be restricted for a UID package-name. + */ + public boolean areJobsRestricted(int uid, @NonNull String packageName) { + return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ true); + } + + /** + * @return whether force-app-standby is effective for a UID package-name. + */ + private boolean isRestricted(int uid, @NonNull String packageName, + boolean useTempWhitelistToo) { + if (isInForeground(uid)) { + return false; + } + synchronized (mLock) { + // Whitelisted? + final int appId = UserHandle.getAppId(uid); + if (ArrayUtils.contains(mPowerWhitelistedAllAppIds, appId)) { + return false; + } + if (useTempWhitelistToo && + ArrayUtils.contains(mTempWhitelistedAppIds, appId)) { + return false; + } + + if (mForceAllAppsStandby) { + return true; + } + + return isRunAnyRestrictedLocked(uid, packageName); + } + } + + /** + * @return whether a UID is in the foreground or not. + * + * Note clients normally shouldn't need to access it. It's only for dumpsys. + */ + public boolean isInForeground(int uid) { + if (!UserHandle.isApp(uid)) { + return true; + } + synchronized (mLock) { + return mForegroundUids.get(uid); + } + } + + /** + * @return whether force all apps standby is enabled or not. + * + * Note clients normally shouldn't need to access it. + */ + boolean isForceAllAppsStandbyEnabled() { + synchronized (mLock) { + return mForceAllAppsStandby; + } + } + + /** + * @return whether a UID/package has {@code OP_RUN_ANY_IN_BACKGROUND} allowed or not. + * + * Note clients normally shouldn't need to access it. It's only for dumpsys. + */ + public boolean isRunAnyInBackgroundAppOpsAllowed(int uid, @NonNull String packageName) { + synchronized (mLock) { + return !isRunAnyRestrictedLocked(uid, packageName); + } + } + + /** + * @return whether a UID is in the user / system defined power-save whitelist or not. + * + * Note clients normally shouldn't need to access it. It's only for dumpsys. + */ + public boolean isUidPowerSaveWhitelisted(int uid) { + synchronized (mLock) { + return ArrayUtils.contains(mPowerWhitelistedAllAppIds, UserHandle.getAppId(uid)); + } + } + + /** + * @return whether a UID is in the temp power-save whitelist or not. + * + * Note clients normally shouldn't need to access it. It's only for dumpsys. + */ + public boolean isUidTempPowerSaveWhitelisted(int uid) { + synchronized (mLock) { + return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid)); + } + } + + public void dump(PrintWriter pw, String indent) { + synchronized (mLock) { + pw.print(indent); + pw.print("Force all apps standby: "); + pw.println(isForceAllAppsStandbyEnabled()); + + pw.print(indent); + pw.print("Foreground uids: ["); + + String sep = ""; + for (int i = 0; i < mForegroundUids.size(); i++) { + if (mForegroundUids.valueAt(i)) { + pw.print(sep); + pw.print(UserHandle.formatUid(mForegroundUids.keyAt(i))); + sep = " "; + } + } + pw.println("]"); + + pw.print(indent); + pw.print("Whitelist appids: "); + pw.println(Arrays.toString(mPowerWhitelistedAllAppIds)); + + pw.print(indent); + pw.print("Temp whitelist appids: "); + pw.println(Arrays.toString(mTempWhitelistedAppIds)); + + pw.print(indent); + pw.println("Restricted packages:"); + for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) { + pw.print(indent); + pw.print(" "); + pw.print(UserHandle.formatUid(uidAndPackage.first)); + pw.print(" "); + pw.print(uidAndPackage.second); + pw.println(); + } + } + } + + public void dumpProto(ProtoOutputStream proto, long fieldId) { + synchronized (mLock) { + final long token = proto.start(fieldId); + + proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY, mForceAllAppsStandby); + + for (int i = 0; i < mForegroundUids.size(); i++) { + if (mForegroundUids.valueAt(i)) { + proto.write(ForceAppStandbyTrackerProto.FOREGROUND_UIDS, + mForegroundUids.keyAt(i)); + } + } + + for (int appId : mPowerWhitelistedAllAppIds) { + proto.write(ForceAppStandbyTrackerProto.POWER_SAVE_WHITELIST_APP_IDS, appId); + } + + for (int appId : mTempWhitelistedAppIds) { + proto.write(ForceAppStandbyTrackerProto.TEMP_POWER_SAVE_WHITELIST_APP_IDS, appId); + } + + for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) { + final long token2 = proto.start( + ForceAppStandbyTrackerProto.RUN_ANY_IN_BACKGROUND_RESTRICTED_PACKAGES); + proto.write(RunAnyInBackgroundRestrictedPackages.UID, uidAndPackage.first); + proto.write(RunAnyInBackgroundRestrictedPackages.PACKAGE_NAME, + uidAndPackage.second); + proto.end(token2); + } + proto.end(token); + } + } +} diff --git a/com/android/server/GestureLauncherService.java b/com/android/server/GestureLauncherService.java index a903f3df..b7b5bd93 100644 --- a/com/android/server/GestureLauncherService.java +++ b/com/android/server/GestureLauncherService.java @@ -40,13 +40,13 @@ import android.provider.Settings; import android.util.MutableBoolean; import android.util.Slog; import android.view.KeyEvent; -import android.view.WindowManagerInternal; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.LocalServices; import com.android.server.statusbar.StatusBarManagerInternal; +import com.android.server.wm.WindowManagerInternal; /** * The service that listens for gestures detected in sensor firmware and starts the intent diff --git a/com/android/server/InputMethodManagerService.java b/com/android/server/InputMethodManagerService.java index f007bcc8..fc57a0d5 100644 --- a/com/android/server/InputMethodManagerService.java +++ b/com/android/server/InputMethodManagerService.java @@ -128,7 +128,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; -import android.view.WindowManagerInternal; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputConnection; @@ -147,6 +146,8 @@ import android.widget.Switch; import android.widget.TextView; import android.widget.Toast; +import com.android.server.wm.WindowManagerInternal; + import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; diff --git a/com/android/server/LocationManagerService.java b/com/android/server/LocationManagerService.java index 0fd59eaa..bdfccd6e 100644 --- a/com/android/server/LocationManagerService.java +++ b/com/android/server/LocationManagerService.java @@ -118,7 +118,7 @@ public class LocationManagerService extends ILocationManager.Stub { private static final String TAG = "LocationManagerService"; public static final boolean D = Log.isLoggable(TAG, Log.DEBUG); - private static final String WAKELOCK_KEY = TAG; + private static final String WAKELOCK_KEY = "*location*"; // Location resolution level: no location data whatsoever private static final int RESOLUTION_LEVEL_NONE = 0; diff --git a/com/android/server/NetworkTimeUpdateService.java b/com/android/server/NetworkTimeUpdateService.java index 1ad14047..2c247982 100644 --- a/com/android/server/NetworkTimeUpdateService.java +++ b/com/android/server/NetworkTimeUpdateService.java @@ -69,13 +69,10 @@ public class NetworkTimeUpdateService extends Binder { private static final String ACTION_POLL = "com.android.server.NetworkTimeUpdateService.action.POLL"; - private static final int NETWORK_CHANGE_EVENT_DELAY_MS = 1000; - private static int POLL_REQUEST = 0; + private static final int POLL_REQUEST = 0; private static final long NOT_SET = -1; private long mNitzTimeSetTime = NOT_SET; - // TODO: Have a way to look up the timezone we are in - private long mNitzZoneSetTime = NOT_SET; private Network mDefaultNetwork = null; private Context mContext; @@ -144,7 +141,6 @@ public class NetworkTimeUpdateService extends Binder { private void registerForTelephonyIntents() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME); - intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE); mContext.registerReceiver(mNitzReceiver, intentFilter); } @@ -257,8 +253,6 @@ public class NetworkTimeUpdateService extends Binder { if (DBG) Log.d(TAG, "Received " + action); if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) { mNitzTimeSetTime = SystemClock.elapsedRealtime(); - } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) { - mNitzZoneSetTime = SystemClock.elapsedRealtime(); } } }; diff --git a/com/android/server/StorageManagerService.java b/com/android/server/StorageManagerService.java index 3c955eb0..66b3adb6 100644 --- a/com/android/server/StorageManagerService.java +++ b/com/android/server/StorageManagerService.java @@ -2192,6 +2192,11 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, "no permission to access the crypt keeper"); + if (StorageManager.isFileEncryptedNativeOnly()) { + // Not supported on FBE devices + return -1; + } + if (type == StorageManager.CRYPT_TYPE_DEFAULT) { password = ""; } else if (TextUtils.isEmpty(password)) { @@ -2268,6 +2273,11 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, "no permission to access the crypt keeper"); + if (StorageManager.isFileEncryptedNativeOnly()) { + // Not supported on FBE devices + return; + } + try { mVold.fdeSetField(field, contents); return; @@ -2287,6 +2297,11 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, "no permission to access the crypt keeper"); + if (StorageManager.isFileEncryptedNativeOnly()) { + // Not supported on FBE devices + return null; + } + try { return mVold.fdeGetField(field); } catch (Exception e) { @@ -2568,7 +2583,7 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon } @Override - public int mkdirs(String callingPkg, String appPath) { + public void mkdirs(String callingPkg, String appPath) { final int userId = UserHandle.getUserId(Binder.getCallingUid()); final UserEnvironment userEnv = new UserEnvironment(userId); @@ -2581,8 +2596,7 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon try { appFile = new File(appPath).getCanonicalFile(); } catch (IOException e) { - Slog.e(TAG, "Failed to resolve " + appPath + ": " + e); - return -1; + throw new IllegalStateException("Failed to resolve " + appPath + ": " + e); } // Try translating the app path into a vold path, but require that it @@ -2597,9 +2611,8 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon try { mVold.mkdirs(appPath); - return 0; } catch (Exception e) { - Slog.wtf(TAG, e); + throw new IllegalStateException("Failed to prepare " + appPath + ": " + e); } } diff --git a/com/android/server/SystemServer.java b/com/android/server/SystemServer.java index 74a7bd4a..33f4e348 100644 --- a/com/android/server/SystemServer.java +++ b/com/android/server/SystemServer.java @@ -35,6 +35,7 @@ import android.os.FileUtils; import android.os.IIncidentManager; import android.os.Looper; import android.os.Message; +import android.os.Parcel; import android.os.PowerManager; import android.os.Process; import android.os.ServiceManager; @@ -360,6 +361,9 @@ public final class SystemServer { // to avoid throwing BadParcelableException. BaseBundle.setShouldDefuse(true); + // Within the system server, when parceling exceptions, include the stack trace + Parcel.setStackTraceParceling(true); + // Ensure binder calls into the system always run at foreground priority. BinderInternal.disableBackgroundScheduling(true); diff --git a/com/android/server/accessibility/AccessibilityClientConnection.java b/com/android/server/accessibility/AccessibilityClientConnection.java index df4c8ed5..22d922be 100644 --- a/com/android/server/accessibility/AccessibilityClientConnection.java +++ b/com/android/server/accessibility/AccessibilityClientConnection.java @@ -20,9 +20,7 @@ import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; -import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; -import android.accessibilityservice.GestureDescription; import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.NonNull; @@ -47,10 +45,8 @@ import android.util.SparseArray; import android.view.KeyEvent; import android.view.MagnificationSpec; import android.view.View; -import android.view.WindowManagerInternal; import android.view.accessibility.AccessibilityCache; import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; @@ -60,11 +56,13 @@ import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.server.accessibility.AccessibilityManagerService.SecurityPolicy; +import com.android.server.wm.WindowManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -106,7 +104,7 @@ abstract class AccessibilityClientConnection extends IAccessibilityServiceConnec int mFeedbackType; - Set<String> mPackageNames = new HashSet<>(); + final Set<String> mPackageNames = new HashSet<>(); boolean mIsDefault; @@ -284,40 +282,98 @@ abstract class AccessibilityClientConnection extends IAccessibilityServiceConnec return true; } - public void setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) { - mEventTypes = info.eventTypes; - mFeedbackType = info.feedbackType; - String[] packageNames = info.packageNames; - if (packageNames != null) { - mPackageNames.addAll(Arrays.asList(packageNames)); + boolean setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) { + boolean somethingChanged = false; + + if (mEventTypes != info.eventTypes) { + mEventTypes = info.eventTypes; + somethingChanged = true; } - mNotificationTimeout = info.notificationTimeout; - mIsDefault = (info.flags & DEFAULT) != 0; - if (supportsFlagForNotImportantViews(info)) { - if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) { - mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; - } else { - mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; + if (mFeedbackType != info.feedbackType) { + mFeedbackType = info.feedbackType; + somethingChanged = true; + } + + final String[] oldPackageNames = mPackageNames.toArray(new String[mPackageNames.size()]); + if (!Arrays.equals(oldPackageNames, info.packageNames)) { + mPackageNames.clear(); + if (info.packageNames != null) { + Collections.addAll(mPackageNames, info.packageNames); } + somethingChanged = true; } - if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) { - mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS; - } else { - mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS; + if (mNotificationTimeout != info.notificationTimeout) { + mNotificationTimeout = info.notificationTimeout; + somethingChanged = true; + } + + final boolean newIsDefault = (info.flags & DEFAULT) != 0; + if (mIsDefault != newIsDefault) { + mIsDefault = newIsDefault; + somethingChanged = true; } - mRequestTouchExplorationMode = (info.flags + if (supportsFlagForNotImportantViews(info)) { + somethingChanged |= updateFetchFlag(info.flags, + AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS); + } + + somethingChanged |= updateFetchFlag(info.flags, + AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS); + + final boolean newRequestTouchExplorationMode = (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0; - mRequestFilterKeyEvents = (info.flags + if (mRequestTouchExplorationMode != newRequestTouchExplorationMode) { + mRequestTouchExplorationMode = newRequestTouchExplorationMode; + somethingChanged = true; + } + + final boolean newRequestFilterKeyEvents = (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0; - mRetrieveInteractiveWindows = (info.flags + if (mRequestFilterKeyEvents != newRequestFilterKeyEvents) { + mRequestFilterKeyEvents = newRequestFilterKeyEvents; + somethingChanged = true; + } + + final boolean newRetrieveInteractiveWindows = (info.flags & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0; - mCaptureFingerprintGestures = (info.flags + if (mRetrieveInteractiveWindows != newRetrieveInteractiveWindows) { + mRetrieveInteractiveWindows = newRetrieveInteractiveWindows; + somethingChanged = true; + } + + final boolean newCaptureFingerprintGestures = (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0; - mRequestAccessibilityButton = (info.flags + if (mCaptureFingerprintGestures != newCaptureFingerprintGestures) { + mCaptureFingerprintGestures = newCaptureFingerprintGestures; + somethingChanged = true; + } + + final boolean newRequestAccessibilityButton = (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0; + if (mRequestAccessibilityButton != newRequestAccessibilityButton) { + mRequestAccessibilityButton = newRequestAccessibilityButton; + somethingChanged = true; + } + + return somethingChanged; + } + + private boolean updateFetchFlag(int allFlags, int flagToUpdate) { + if ((allFlags & flagToUpdate) != 0) { + if ((mFetchFlags & flagToUpdate) == 0) { + mFetchFlags |= flagToUpdate; + return true; + } + } else { + if ((mFetchFlags & flagToUpdate) != 0) { + mFetchFlags &= ~flagToUpdate; + return true; + } + } + return false; } protected boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) { @@ -349,14 +405,15 @@ abstract class AccessibilityClientConnection extends IAccessibilityServiceConnec // If the XML manifest had data to configure the service its info // should be already set. In such a case update only the dynamically // configurable properties. + final boolean serviceInfoChanged; AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo; if (oldInfo != null) { oldInfo.updateDynamicallyConfigurableProperties(info); - setDynamicallyConfigurableProperties(oldInfo); + serviceInfoChanged = setDynamicallyConfigurableProperties(oldInfo); } else { - setDynamicallyConfigurableProperties(info); + serviceInfoChanged = setDynamicallyConfigurableProperties(info); } - mSystemSupport.onClientChange(true); + mSystemSupport.onClientChange(serviceInfoChanged); } } finally { Binder.restoreCallingIdentity(identity); diff --git a/com/android/server/accessibility/AccessibilityInputFilter.java b/com/android/server/accessibility/AccessibilityInputFilter.java index f6fcaae4..11b2343d 100644 --- a/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/com/android/server/accessibility/AccessibilityInputFilter.java @@ -31,11 +31,11 @@ import android.view.InputEvent; import android.view.InputFilter; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.WindowManagerPolicy; import android.view.accessibility.AccessibilityEvent; import com.android.internal.util.BitUtils; import com.android.server.LocalServices; +import com.android.server.policy.WindowManagerPolicy; /** * This class is an input filter for implementing accessibility features such diff --git a/com/android/server/accessibility/AccessibilityManagerService.java b/com/android/server/accessibility/AccessibilityManagerService.java index d661754a..8b5c85a7 100644 --- a/com/android/server/accessibility/AccessibilityManagerService.java +++ b/com/android/server/accessibility/AccessibilityManagerService.java @@ -82,7 +82,6 @@ import android.view.MagnificationSpec; import android.view.View; import android.view.WindowInfo; import android.view.WindowManager; -import android.view.WindowManagerInternal; import android.view.accessibility.AccessibilityCache; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityInteractionClient; @@ -102,6 +101,7 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.IntPair; import com.android.server.LocalServices; import com.android.server.policy.AccessibilityShortcutController; +import com.android.server.wm.WindowManagerInternal; import org.xmlpull.v1.XmlPullParserException; @@ -2400,7 +2400,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private void announceNewUserIfNeeded() { synchronized (mLock) { UserState userState = getCurrentUserStateLocked(); - if (userState.isHandlingAccessibilityEvents()) { + if (userState.isHandlingAccessibilityEvents() + && userState.isObservedEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT)) { UserManager userManager = (UserManager) mContext.getSystemService( Context.USER_SERVICE); String message = mContext.getString(R.string.user_switched, @@ -3157,13 +3158,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (mWindowsForAccessibilityCallback == null) { return; } + final int userId; + synchronized (mLock) { + userId = mCurrentUserId; + final UserState userState = getUserStateLocked(userId); + if (!userState.isObservedEventType(AccessibilityEvent.TYPE_WINDOWS_CHANGED)) { + return; + } + } final long identity = Binder.clearCallingIdentity(); try { // Let the client know the windows changed. AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_WINDOWS_CHANGED); event.setEventTime(SystemClock.uptimeMillis()); - sendAccessibilityEvent(event, mCurrentUserId); + sendAccessibilityEvent(event, userId); } finally { Binder.restoreCallingIdentity(identity); } @@ -3368,6 +3377,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mUserId = userId; } + public boolean isObservedEventType(@AccessibilityEvent.EventType int type) { + return (mLastSentRelevantEventTypes & type) != 0; + } + public int getClientState() { int clientState = 0; final boolean a11yEnabled = (mUiAutomationManager.isUiAutomationRunningLocked() diff --git a/com/android/server/accessibility/AccessibilityServiceConnection.java b/com/android/server/accessibility/AccessibilityServiceConnection.java index eb267529..9cafa1e7 100644 --- a/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -33,10 +33,10 @@ import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; -import android.view.WindowManagerInternal; import com.android.server.accessibility.AccessibilityManagerService.SecurityPolicy; import com.android.server.accessibility.AccessibilityManagerService.UserState; +import com.android.server.wm.WindowManagerInternal; import java.lang.ref.WeakReference; import java.util.List; diff --git a/com/android/server/accessibility/GlobalActionPerformer.java b/com/android/server/accessibility/GlobalActionPerformer.java index 5db6f7da..3b8d4bca 100644 --- a/com/android/server/accessibility/GlobalActionPerformer.java +++ b/com/android/server/accessibility/GlobalActionPerformer.java @@ -21,14 +21,18 @@ import android.app.StatusBarManager; import android.content.Context; import android.hardware.input.InputManager; import android.os.Binder; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; +import android.view.IWindowManager; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; -import android.view.WindowManagerInternal; import com.android.server.LocalServices; import com.android.server.statusbar.StatusBarManagerInternal; +import com.android.server.wm.WindowManagerInternal; /** * Handle the back-end of AccessibilityService#performGlobalAction @@ -72,6 +76,9 @@ public class GlobalActionPerformer { case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN: { return toggleSplitScreen(); } + case AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN: { + return lockScreen(); + } } return false; } finally { @@ -153,4 +160,11 @@ public class GlobalActionPerformer { } return true; } + + private boolean lockScreen() { + mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(), + PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0); + mWindowManagerService.lockNow(); + return true; + } } diff --git a/com/android/server/accessibility/KeyEventDispatcher.java b/com/android/server/accessibility/KeyEventDispatcher.java index 33584326..b144e1c8 100644 --- a/com/android/server/accessibility/KeyEventDispatcher.java +++ b/com/android/server/accessibility/KeyEventDispatcher.java @@ -26,7 +26,8 @@ import android.util.Pools.Pool; import android.util.Slog; import android.view.InputEventConsistencyVerifier; import android.view.KeyEvent; -import android.view.WindowManagerPolicy; + +import com.android.server.policy.WindowManagerPolicy; import java.util.ArrayList; import java.util.List; diff --git a/com/android/server/accessibility/KeyboardInterceptor.java b/com/android/server/accessibility/KeyboardInterceptor.java index 77249452..bc379c20 100644 --- a/com/android/server/accessibility/KeyboardInterceptor.java +++ b/com/android/server/accessibility/KeyboardInterceptor.java @@ -22,7 +22,8 @@ import android.os.SystemClock; import android.util.Pools; import android.util.Slog; import android.view.KeyEvent; -import android.view.WindowManagerPolicy; + +import com.android.server.policy.WindowManagerPolicy; /** * Intercepts key events and forwards them to accessibility manager service. diff --git a/com/android/server/accessibility/MagnificationController.java b/com/android/server/accessibility/MagnificationController.java index a10b7a20..a70b88e8 100644 --- a/com/android/server/accessibility/MagnificationController.java +++ b/com/android/server/accessibility/MagnificationController.java @@ -34,7 +34,6 @@ import android.util.MathUtils; import android.util.Slog; import android.view.MagnificationSpec; import android.view.View; -import android.view.WindowManagerInternal; import android.view.animation.DecelerateInterpolator; import com.android.internal.R; @@ -42,6 +41,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.server.LocalServices; +import com.android.server.wm.WindowManagerInternal; import java.util.Locale; diff --git a/com/android/server/accessibility/MotionEventInjector.java b/com/android/server/accessibility/MotionEventInjector.java index b6b78129..46e3226b 100644 --- a/com/android/server/accessibility/MotionEventInjector.java +++ b/com/android/server/accessibility/MotionEventInjector.java @@ -31,9 +31,9 @@ import android.util.SparseArray; import android.util.SparseIntArray; import android.view.InputDevice; import android.view.MotionEvent; -import android.view.WindowManagerPolicy; import com.android.internal.os.SomeArgs; +import com.android.server.policy.WindowManagerPolicy; import java.util.ArrayList; import java.util.Arrays; diff --git a/com/android/server/accessibility/TouchExplorer.java b/com/android/server/accessibility/TouchExplorer.java index a32686df..62017e87 100644 --- a/com/android/server/accessibility/TouchExplorer.java +++ b/com/android/server/accessibility/TouchExplorer.java @@ -26,11 +26,12 @@ import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import android.view.ViewConfiguration; -import android.view.WindowManagerPolicy; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; +import com.android.server.policy.WindowManagerPolicy; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -790,7 +791,7 @@ class TouchExplorer extends BaseEventStreamTransformation */ private void sendAccessibilityEvent(int type) { AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext); - if (accessibilityManager.isEnabled()) { + if (accessibilityManager.isObservedEventType(type)) { AccessibilityEvent event = AccessibilityEvent.obtain(type); event.setWindowId(mAms.getActiveWindowId()); accessibilityManager.sendAccessibilityEvent(event); diff --git a/com/android/server/accessibility/UiAutomationManager.java b/com/android/server/accessibility/UiAutomationManager.java index a6b81fff..f0571126 100644 --- a/com/android/server/accessibility/UiAutomationManager.java +++ b/com/android/server/accessibility/UiAutomationManager.java @@ -26,9 +26,10 @@ import android.os.IBinder; import android.os.IBinder.DeathRecipient; import android.os.RemoteException; import android.util.Slog; -import android.view.WindowManagerInternal; import android.view.accessibility.AccessibilityEvent; +import com.android.server.wm.WindowManagerInternal; + import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/com/android/server/am/ActiveServices.java b/com/android/server/am/ActiveServices.java index 2131731d..3cd2f6ae 100644 --- a/com/android/server/am/ActiveServices.java +++ b/com/android/server/am/ActiveServices.java @@ -58,6 +58,7 @@ import com.android.internal.os.TransferPipe; import com.android.internal.util.FastPrintWriter; import com.android.server.am.ActivityManagerService.ItemMatcher; import com.android.server.am.ActivityManagerService.NeededUriGrants; +import com.android.server.am.proto.ActiveServicesProto; import android.app.ActivityManager; import android.app.AppGlobals; @@ -85,6 +86,7 @@ import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; +import android.util.proto.ProtoOutputStream; import android.webkit.WebViewZygote; public final class ActiveServices { @@ -633,7 +635,7 @@ public final class ActiveServices { sb.append("Stopping service due to app idle: "); UserHandle.formatUid(sb, service.appInfo.uid); sb.append(" "); - TimeUtils.formatDuration(service.createTime + TimeUtils.formatDuration(service.createRealTime - SystemClock.elapsedRealtime(), sb); sb.append(" "); sb.append(compName); @@ -1043,8 +1045,8 @@ public final class ActiveServices { try { if (AppGlobals.getPackageManager().checkPermission( android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE, - r.appInfo.packageName, - r.appInfo.uid) != PackageManager.PERMISSION_GRANTED) { + r.appInfo.packageName, UserHandle.getUserId(r.appInfo.uid)) + != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Instant app " + r.appInfo.packageName + " does not have permission to create foreground" + "services"); @@ -3220,7 +3222,7 @@ public final class ActiveServices { info.uid = r.appInfo.uid; info.process = r.processName; info.foreground = r.isForeground; - info.activeSince = r.createTime; + info.activeSince = r.createRealTime; info.started = r.startRequested; info.clientCount = r.connections.size(); info.crashCount = r.crashCount; @@ -3574,7 +3576,7 @@ public final class ActiveServices { pw.print(" app="); pw.println(r.app); pw.print(" created="); - TimeUtils.formatDuration(r.createTime, nowReal, pw); + TimeUtils.formatDuration(r.createRealTime, nowReal, pw); pw.print(" started="); pw.print(r.startRequested); pw.print(" connections="); @@ -3840,6 +3842,26 @@ public final class ActiveServices { return new ServiceDumper(fd, pw, args, opti, dumpAll, dumpPackage); } + protected void writeToProto(ProtoOutputStream proto) { + synchronized (mAm) { + int[] users = mAm.mUserController.getUsers(); + for (int user : users) { + ServiceMap smap = mServiceMap.get(user); + if (smap == null) { + continue; + } + long token = proto.start(ActiveServicesProto.SERVICES_BY_USERS); + proto.write(ActiveServicesProto.ServicesByUser.USER_ID, user); + ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName; + for (int i=0; i<alls.size(); i++) { + alls.valueAt(i).writeToProto(proto, + ActiveServicesProto.ServicesByUser.SERVICE_RECORDS); + } + proto.end(token); + } + } + } + /** * There are three ways to call this: * - no service specified: dump all the services diff --git a/com/android/server/am/ActivityDisplay.java b/com/android/server/am/ActivityDisplay.java index 2289f857..b11b16e1 100644 --- a/com/android/server/am/ActivityDisplay.java +++ b/com/android/server/am/ActivityDisplay.java @@ -42,6 +42,8 @@ import android.annotation.Nullable; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; import android.app.WindowConfiguration; +import android.graphics.Point; +import android.graphics.Rect; import android.util.IntArray; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -89,6 +91,9 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { private ActivityStack mPinnedStack = null; private ActivityStack mSplitScreenPrimaryStack = null; + // Used in updating the display size + private Point mTmpDisplaySize = new Point(); + ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) { mSupervisor = supervisor; mDisplayId = displayId; @@ -97,6 +102,13 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { throw new IllegalStateException("Display does not exist displayId=" + displayId); } mDisplay = display; + + updateBounds(); + } + + void updateBounds() { + mDisplay.getSize(mTmpDisplaySize); + setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); } void addChild(ActivityStack stack, int position) { @@ -387,6 +399,16 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { otherStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); } } finally { + if (mHomeStack != null && !isTopStack(mHomeStack)) { + // Whenever split-screen is dismissed we want the home stack directly behind the + // currently top stack so it shows up when the top stack is finished. + final ActivityStack topStack = getTopStack(); + // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however + // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch + // once we have that. + mHomeStack.moveToFront("onSplitScreenModeDismissed"); + topStack.moveToFront("onSplitScreenModeDismissed"); + } mSupervisor.mWindowManager.continueSurfaceLayout(); } } diff --git a/com/android/server/am/ActivityManagerService.java b/com/android/server/am/ActivityManagerService.java index 43618564..fe992daf 100644 --- a/com/android/server/am/ActivityManagerService.java +++ b/com/android/server/am/ActivityManagerService.java @@ -246,6 +246,7 @@ import android.app.admin.DevicePolicyManager; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; import android.app.backup.IBackupManager; +import android.app.servertransaction.ConfigurationChangeItem; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.appwidget.AppWidgetManager; @@ -317,6 +318,7 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.PowerManager; +import android.os.PowerManager.ServiceType; import android.os.PowerManagerInternal; import android.os.Process; import android.os.RemoteCallbackList; @@ -351,7 +353,6 @@ import android.util.AtomicFile; import android.util.StatsLog; import android.util.TimingsTraceLog; import android.util.DebugUtils; -import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.Pair; @@ -415,6 +416,8 @@ import com.android.server.Watchdog; import com.android.server.am.ActivityStack.ActivityState; import com.android.server.am.proto.ActivityManagerServiceProto; import com.android.server.am.proto.BroadcastProto; +import com.android.server.am.proto.GrantUriProto; +import com.android.server.am.proto.NeededUriGrantsProto; import com.android.server.am.proto.StickyBroadcastProto; import com.android.server.firewall.IntentFirewall; import com.android.server.job.JobSchedulerInternal; @@ -630,6 +633,8 @@ public class ActivityManagerService extends IActivityManager.Stub final ActivityStarter mActivityStarter; + final ClientLifecycleManager mLifecycleManager; + final TaskChangeNotificationController mTaskChangeNotificationController; final InstrumentationReporter mInstrumentationReporter = new InstrumentationReporter(); @@ -712,8 +717,16 @@ public class ActivityManagerService extends IActivityManager.Stub final UserController mUserController; + /** + * Packages that are being allowed to perform unrestricted app switches. Mapping is + * User -> Type -> uid. + */ + final SparseArray<ArrayMap<String, Integer>> mAllowAppSwitchUids = new SparseArray<>(); + final AppErrors mAppErrors; + final AppWarnings mAppWarnings; + /** * Dump of the activity state at the time of the last ANR. Cleared after * {@link WindowManagerService#LAST_ANR_LIFETIME_DURATION_MSECS} @@ -1191,6 +1204,13 @@ public class ActivityManagerService extends IActivityManager.Stub return result; } + public void writeToProto(ProtoOutputStream proto, long fieldId) { + long token = proto.start(fieldId); + proto.write(GrantUriProto.URI, uri.toString()); + proto.write(GrantUriProto.SOURCE_USER_ID, sourceUserId); + proto.end(token); + } + public static GrantUri resolve(int defaultSourceUserHandle, Uri uri) { return new GrantUri(ContentProvider.getUserIdFromUri(uri, defaultSourceUserHandle), ContentProvider.getUriWithoutUserId(uri), false); @@ -1717,7 +1737,6 @@ public class ActivityManagerService extends IActivityManager.Stub static final int IDLE_UIDS_MSG = 58; static final int LOG_STACK_STATE = 60; static final int VR_MODE_CHANGE_MSG = 61; - static final int SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG = 62; static final int HANDLE_TRUST_STORAGE_UPDATE_MSG = 63; static final int NOTIFY_VR_SLEEPING_MSG = 65; static final int SERVICE_FOREGROUND_TIMEOUT_MSG = 66; @@ -1736,7 +1755,6 @@ public class ActivityManagerService extends IActivityManager.Stub static KillHandler sKillHandler = null; CompatModeDialog mCompatModeDialog; - UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog; long mLastMemUsageReportTime = 0; /** @@ -1764,6 +1782,11 @@ public class ActivityManagerService extends IActivityManager.Stub final boolean mPermissionReviewRequired; + /** + * Whether to force background check on all apps (for battery saver) or not. + */ + boolean mForceBackgroundCheck; + private static String sTheRealBuildSerial = Build.UNKNOWN; /** @@ -1918,23 +1941,6 @@ public class ActivityManagerService extends IActivityManager.Stub } break; } - case SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG: { - synchronized (ActivityManagerService.this) { - final ActivityRecord ar = (ActivityRecord) msg.obj; - if (mUnsupportedDisplaySizeDialog != null) { - mUnsupportedDisplaySizeDialog.dismiss(); - mUnsupportedDisplaySizeDialog = null; - } - if (ar != null && mCompatModePackages.getPackageNotifyUnsupportedZoomLocked( - ar.packageName)) { - // TODO(multi-display): Show dialog on appropriate display. - mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog( - ActivityManagerService.this, mUiContext, ar.info.applicationInfo); - mUnsupportedDisplaySizeDialog.show(); - } - } - break; - } case DISMISS_DIALOG_UI_MSG: { final Dialog d = (Dialog) msg.obj; d.dismiss(); @@ -2503,7 +2509,7 @@ public class ActivityManagerService extends IActivityManager.Stub DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO); ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats); ServiceManager.addService("meminfo", new MemBinder(this), /* allowIsolated= */ false, - DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL); + DUMP_FLAG_PRIORITY_HIGH); ServiceManager.addService("gfxinfo", new GraphicsBinder(this)); ServiceManager.addService("dbinfo", new DbBinder(this)); if (MONITOR_CPU_USAGE) { @@ -2561,9 +2567,15 @@ public class ActivityManagerService extends IActivityManager.Stub private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() { @Override - public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, + public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { if (asProto) return; + mActivityManagerService.dumpApplicationMemoryUsage(fd, + pw, " ", new String[] {"-a"}, false, null); + } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { + if (asProto) return; mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, " ", args, false, null); } }; @@ -2667,6 +2679,7 @@ public class ActivityManagerService extends IActivityManager.Stub GL_ES_VERSION = 0; mActivityStarter = null; mAppErrors = null; + mAppWarnings = null; mAppOpsService = mInjector.getAppOpsService(null, null); mBatteryStatsService = null; mCompatModePackages = null; @@ -2689,6 +2702,7 @@ public class ActivityManagerService extends IActivityManager.Stub mUserController = null; mVrController = null; mLockTaskController = null; + mLifecycleManager = null; } // Note: This method is invoked on the main thread but may need to attach various @@ -2734,10 +2748,13 @@ public class ActivityManagerService extends IActivityManager.Stub mProviderMap = new ProviderMap(this); mAppErrors = new AppErrors(mUiContext, this); - // TODO: Move creation of battery stats service outside of activity manager service. File dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); systemDir.mkdirs(); + + mAppWarnings = new AppWarnings(this, mUiContext, mHandler, mUiHandler, systemDir); + + // TODO: Move creation of battery stats service outside of activity manager service. mBatteryStatsService = new BatteryStatsService(systemContext, systemDir, mHandler); mBatteryStatsService.getActiveStatistics().readLocked(); mBatteryStatsService.scheduleWriteToDisk(); @@ -2784,10 +2801,11 @@ public class ActivityManagerService extends IActivityManager.Stub mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler); mTaskChangeNotificationController = new TaskChangeNotificationController(this, mStackSupervisor, mHandler); - mActivityStarter = new ActivityStarter(this, AppGlobals.getPackageManager()); + mActivityStarter = new ActivityStarter(this); mRecentTasks = createRecentTasks(); mStackSupervisor.setRecentTasks(mRecentTasks); mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler); + mLifecycleManager = new ClientLifecycleManager(); mProcessCpuThread = new Thread("CpuTracker") { @Override @@ -2871,6 +2889,7 @@ public class ActivityManagerService extends IActivityManager.Stub void onUserStoppedLocked(int userId) { mRecentTasks.unloadUserDataFromMemoryLocked(userId); + mAllowAppSwitchUids.remove(userId); } public void initPowerManagement() { @@ -3301,22 +3320,25 @@ public class ActivityManagerService extends IActivityManager.Stub mUiHandler.sendMessage(msg); } - final void showUnsupportedZoomDialogIfNeededLocked(ActivityRecord r) { - final Configuration globalConfig = getGlobalConfiguration(); - if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE - && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) { - final Message msg = Message.obtain(); - msg.what = SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG; - msg.obj = r; - mUiHandler.sendMessage(msg); - } + final AppWarnings getAppWarningsLocked() { + return mAppWarnings; + } + + /** + * Shows app warning dialogs, if necessary. + * + * @param r activity record for which the warnings may be displayed + */ + final void showAppWarningsIfNeededLocked(ActivityRecord r) { + mAppWarnings.showUnsupportedCompileSdkDialogIfNeeded(r); + mAppWarnings.showUnsupportedDisplaySizeDialogIfNeeded(r); } private int updateLruProcessInternalLocked(ProcessRecord app, long now, int index, String what, Object obj, ProcessRecord srcApp) { app.lastActivityTime = now; - if (app.activities.size() > 0) { + if (app.activities.size() > 0 || app.recentTasks.size() > 0) { // Don't want to touch dependent processes that are hosting activities. return index; } @@ -3380,7 +3402,7 @@ public class ActivityManagerService extends IActivityManager.Stub final void updateLruProcessLocked(ProcessRecord app, boolean activityChange, ProcessRecord client) { final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities - || app.treatLikeActivity; + || app.treatLikeActivity || app.recentTasks.size() > 0; final boolean hasService = false; // not impl yet. app.services.size() > 0; if (!activityChange && hasActivity) { // The process has activities, so we are only allowing activity-based adjustments @@ -3484,7 +3506,8 @@ public class ActivityManagerService extends IActivityManager.Stub int nextIndex; if (hasActivity) { final int N = mLruProcesses.size(); - if (app.activities.size() == 0 && mLruProcessActivityStart < (N - 1)) { + if ((app.activities.size() == 0 || app.recentTasks.size() > 0) + && mLruProcessActivityStart < (N - 1)) { // Process doesn't have activities, but has clients with // activities... move it up, but one below the top (the top // should always have a real activity). @@ -5116,7 +5139,8 @@ public class ActivityManagerService extends IActivityManager.Stub // because we don't support returning them across task boundaries. Also, to // keep backwards compatibility we remove the task from recents when finishing // task with root activity. - res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity); + res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, + finishWithRootActivity, "finish-activity"); if (!res) { Slog.i(TAG, "Removing task failed to finish activity"); } @@ -5321,6 +5345,8 @@ public class ActivityManagerService extends IActivityManager.Stub // Remove this application's activities from active lists. boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app); + app.clearRecentTasks(); + app.activities.clear(); if (app.instr != null) { @@ -8091,7 +8117,7 @@ public class ActivityManagerService extends IActivityManager.Stub return false; } // An activity is consider to be in multi-window mode if its task isn't fullscreen. - return !r.getTask().mFullscreen; + return r.inMultiWindowMode(); } } finally { Binder.restoreCallingIdentity(origId); @@ -8600,6 +8626,16 @@ public class ActivityManagerService extends IActivityManager.Stub } switch (appop) { case AppOpsManager.MODE_ALLOWED: + // If force-background-check is enabled, restrict all apps that aren't whitelisted. + if (mForceBackgroundCheck && + UserHandle.isApp(uid) && + !isOnDeviceIdleWhitelistLocked(uid)) { + if (DEBUG_BACKGROUND_CHECK) { + Slog.i(TAG, "Force background check: " + + uid + "/" + packageName + " restricted"); + } + return ActivityManager.APP_START_MODE_DELAYED; + } return ActivityManager.APP_START_MODE_NORMAL; case AppOpsManager.MODE_IGNORED: return ActivityManager.APP_START_MODE_DELAYED; @@ -8699,6 +8735,9 @@ public class ActivityManagerService extends IActivityManager.Stub return ActivityManager.APP_START_MODE_NORMAL; } + /** + * @return whether a UID is in the system, user or temp doze whitelist. + */ boolean isOnDeviceIdleWhitelistLocked(int uid) { final int appId = UserHandle.getAppId(uid); return Arrays.binarySearch(mDeviceIdleWhitelist, appId) >= 0 @@ -9044,6 +9083,19 @@ public class ActivityManagerService extends IActivityManager.Stub this.targetUid = targetUid; this.flags = flags; } + + void writeToProto(ProtoOutputStream proto, long fieldId) { + long token = proto.start(fieldId); + proto.write(NeededUriGrantsProto.TARGET_PACKAGE, targetPkg); + proto.write(NeededUriGrantsProto.TARGET_UID, targetUid); + proto.write(NeededUriGrantsProto.FLAGS, flags); + + final int N = this.size(); + for (int i=0; i<N; i++) { + this.get(i).writeToProto(proto, NeededUriGrantsProto.GRANTS); + } + proto.end(token); + } } /** @@ -10097,8 +10149,8 @@ public class ActivityManagerService extends IActivityManager.Stub } else { // Task isn't in window manager yet since it isn't associated with a stack. // Return the persist value from activity manager - if (task.mBounds != null) { - rect.set(task.mBounds); + if (!task.matchParentBounds()) { + rect.set(task.getBounds()); } else if (task.mLastNonFullscreenBounds != null) { rect.set(task.mLastNonFullscreenBounds); } @@ -10279,7 +10331,8 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { final long ident = Binder.clearCallingIdentity(); try { - return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS); + return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS, + "remove-task"); } finally { Binder.restoreCallingIdentity(ident); } @@ -10469,12 +10522,12 @@ public class ActivityManagerService extends IActivityManager.Stub + " non-standard task " + taskId + " to windowing mode=" + windowingMode); } - final ActivityDisplay display = task.getStack().getDisplay(); - final ActivityStack stack = display.getOrCreateStack(windowingMode, - task.getStack().getActivityType(), toTop); - // TODO: Use ActivityStack.setWindowingMode instead of re-parenting. - task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, - "moveTaskToStack"); + + final ActivityStack stack = task.getStack(); + if (toTop) { + stack.moveToFront("setTaskWindowingMode", task); + } + stack.setWindowingMode(windowingMode); } finally { Binder.restoreCallingIdentity(ident); } @@ -11693,6 +11746,15 @@ public class ActivityManagerService extends IActivityManager.Stub return true; } + /** + * Returns the PackageManager. Used by classes hosted by {@link ActivityManagerService}. The + * PackageManager could be unavailable at construction time and therefore needs to be accessed + * on demand. + */ + IPackageManager getPackageManager() { + return AppGlobals.getPackageManager(); + } + PackageManagerInternal getPackageManagerInternalLocked() { if (mPackageManagerInt == null) { mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class); @@ -12582,6 +12644,18 @@ public class ActivityManagerService extends IActivityManager.Stub } } + boolean checkAllowAppSwitchUid(int uid) { + ArrayMap<String, Integer> types = mAllowAppSwitchUids.get(UserHandle.getUserId(uid)); + if (types != null) { + for (int i = types.size() - 1; i >= 0; i--) { + if (types.valueAt(i).intValue() == uid) { + return true; + } + } + } + return false; + } + boolean checkAppSwitchAllowedLocked(int sourcePid, int sourceUid, int callingPid, int callingUid, String name) { if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) { @@ -12594,6 +12668,9 @@ public class ActivityManagerService extends IActivityManager.Stub if (perm == PackageManager.PERMISSION_GRANTED) { return true; } + if (checkAllowAppSwitchUid(sourceUid)) { + return true; + } // If the actual IPC caller is different from the logical source, then // also see if they are allowed to control app switches. @@ -12604,6 +12681,9 @@ public class ActivityManagerService extends IActivityManager.Stub if (perm == PackageManager.PERMISSION_GRANTED) { return true; } + if (checkAllowAppSwitchUid(callingUid)) { + return true; + } } Slog.w(TAG, name + " request from " + sourceUid + " stopped"); @@ -14115,6 +14195,16 @@ public class ActivityManagerService extends IActivityManager.Stub readGrantedUriPermissionsLocked(); } + final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); + if (pmi != null) { + pmi.registerLowPowerModeObserver(ServiceType.FORCE_BACKGROUND_CHECK, + state -> updateForceBackgroundCheck(state.batterySaverEnabled)); + updateForceBackgroundCheck( + pmi.getLowPowerState(ServiceType.FORCE_BACKGROUND_CHECK).batterySaverEnabled); + } else { + Slog.wtf(TAG, "PowerManagerInternal not found."); + } + if (goingCallback != null) goingCallback.run(); traceLog.traceBegin("ActivityManagerStartApps"); mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START, @@ -14213,6 +14303,23 @@ public class ActivityManagerService extends IActivityManager.Stub } } + private void updateForceBackgroundCheck(boolean enabled) { + synchronized (this) { + if (mForceBackgroundCheck != enabled) { + mForceBackgroundCheck = enabled; + + if (DEBUG_BACKGROUND_CHECK) { + Slog.i(TAG, "Force background check " + (enabled ? "enabled" : "disabled")); + } + + if (mForceBackgroundCheck) { + // Stop background services for idle UIDs. + doStopUidForIdleUidsLocked(); + } + } + } + } + void killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) { synchronized (this) { mAppErrors.killAppAtUserRequestLocked(app, fromDialog); @@ -14544,6 +14651,18 @@ public class ActivityManagerService extends IActivityManager.Stub final String dropboxTag = processClass(process) + "_" + eventType; if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return; + // Log to StatsLog before the rate-limiting. + // The logging below is adapated from appendDropboxProcessHeaders. + StatsLog.write(StatsLog.DROPBOX_ERROR_CHANGED, + process != null ? process.uid : -1, + dropboxTag, + processName, + process != null ? process.pid : -1, + (process != null && process.info != null) ? + (process.info.isInstantApp() ? 1 : 0) : -1, + activity != null ? activity.shortComponentName : null, + process != null ? (process.isInterestingToUserLocked() ? 1 : 0) : -1); + // Rate-limit how often we're willing to do the heavy lifting below to // collect and record logs; currently 5 logs per 10 second period. final long now = SystemClock.elapsedRealtime(); @@ -14868,6 +14987,7 @@ public class ActivityManagerService extends IActivityManager.Stub boolean dumpVisibleStacksOnly = false; boolean dumpFocusedStackOnly = false; String dumpPackage = null; + int dumpAppId = -1; int opti = 0; while (opti < args.length) { @@ -14922,6 +15042,25 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { writeBroadcastsToProtoLocked(proto); } + } else if ("provider".equals(cmd)) { + String[] newArgs; + String name; + if (opti >= args.length) { + name = null; + newArgs = EMPTY_STRING_ARRAY; + } else { + name = args[opti]; + opti++; + newArgs = new String[args.length - opti]; + if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, + args.length - opti); + } + if (!dumpProviderProto(fd, pw, name, newArgs)) { + pw.println("No providers match: " + name); + pw.println("Use -h for help."); + } + } else if ("service".equals(cmd)) { + mServices.writeToProto(proto); } else { // default option, dump everything, output is ActivityManagerServiceProto synchronized (this) { @@ -14932,6 +15071,10 @@ public class ActivityManagerService extends IActivityManager.Stub long broadcastToken = proto.start(ActivityManagerServiceProto.BROADCASTS); writeBroadcastsToProtoLocked(proto); proto.end(broadcastToken); + + long serviceToken = proto.start(ActivityManagerServiceProto.SERVICES); + mServices.writeToProto(proto); + proto.end(serviceToken); } } proto.flush(); @@ -14939,6 +15082,16 @@ public class ActivityManagerService extends IActivityManager.Stub return; } + if (dumpPackage != null) { + try { + ApplicationInfo info = mContext.getPackageManager().getApplicationInfo( + dumpPackage, 0); + dumpAppId = UserHandle.getAppId(info.uid); + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + } + boolean more = false; // Is the caller requesting to dump a particular piece of data? if (opti < args.length) { @@ -15046,7 +15199,7 @@ public class ActivityManagerService extends IActivityManager.Stub args.length - opti); } synchronized (this) { - dumpProcessesLocked(fd, pw, args, opti, true, dumpPackage); + dumpProcessesLocked(fd, pw, args, opti, true, dumpPackage, dumpAppId); } } else if ("oom".equals(cmd) || "o".equals(cmd)) { synchronized (this) { @@ -15232,7 +15385,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); } - dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage); + dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId); } } else { @@ -15309,7 +15462,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); } - dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage); + dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId); } } Binder.restoreCallingIdentity(origId); @@ -15464,22 +15617,12 @@ public class ActivityManagerService extends IActivityManager.Stub } } - boolean dumpUids(PrintWriter pw, String dumpPackage, SparseArray<UidRecord> uids, + boolean dumpUids(PrintWriter pw, String dumpPackage, int dumpAppId, SparseArray<UidRecord> uids, String header, boolean needSep) { boolean printed = false; - int whichAppId = -1; - if (dumpPackage != null) { - try { - ApplicationInfo info = mContext.getPackageManager().getApplicationInfo( - dumpPackage, 0); - whichAppId = UserHandle.getAppId(info.uid); - } catch (NameNotFoundException e) { - e.printStackTrace(); - } - } for (int i=0; i<uids.size(); i++) { UidRecord uidRec = uids.valueAt(i); - if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) { + if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != dumpAppId) { continue; } if (!printed) { @@ -15526,9 +15669,8 @@ public class ActivityManagerService extends IActivityManager.Stub } void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args, - int opti, boolean dumpAll, String dumpPackage) { + int opti, boolean dumpAll, String dumpPackage, int dumpAppId) { boolean needSep = false; - boolean printedAnything = false; int numPers = 0; pw.println("ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)"); @@ -15546,7 +15688,6 @@ public class ActivityManagerService extends IActivityManager.Stub if (!needSep) { pw.println(" All known processes:"); needSep = true; - printedAnything = true; } pw.print(r.persistent ? " *PERS*" : " *APP*"); pw.print(" UID "); pw.print(procs.keyAt(ia)); @@ -15571,7 +15712,6 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(); } pw.println(" Isolated process list (sorted by uid):"); - printedAnything = true; printed = true; needSep = true; } @@ -15593,7 +15733,6 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(); } pw.println(" Active instrumentation:"); - printedAnything = true; printed = true; needSep = true; } @@ -15604,14 +15743,15 @@ public class ActivityManagerService extends IActivityManager.Stub } if (mActiveUids.size() > 0) { - if (dumpUids(pw, dumpPackage, mActiveUids, "UID states:", needSep)) { - printedAnything = needSep = true; + if (dumpUids(pw, dumpPackage, dumpAppId, mActiveUids, "UID states:", needSep)) { + needSep = true; } } if (dumpAll) { if (mValidateUids.size() > 0) { - if (dumpUids(pw, dumpPackage, mValidateUids, "UID validation:", needSep)) { - printedAnything = needSep = true; + if (dumpUids(pw, dumpPackage, dumpAppId, mValidateUids, "UID validation:", + needSep)) { + needSep = true; } } } @@ -15628,7 +15768,6 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println("):"); dumpProcessOomList(pw, this, mLruProcesses, " ", "Proc", "PERS", false, dumpPackage); needSep = true; - printedAnything = true; } if (dumpAll || dumpPackage != null) { @@ -15644,7 +15783,6 @@ public class ActivityManagerService extends IActivityManager.Stub needSep = true; pw.println(" PID mappings:"); printed = true; - printedAnything = true; } pw.print(" PID #"); pw.print(mPidsSelfLocked.keyAt(i)); pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i)); @@ -15667,7 +15805,6 @@ public class ActivityManagerService extends IActivityManager.Stub needSep = true; pw.println(" Foreground Processes:"); printed = true; - printedAnything = true; } pw.print(" PID #"); pw.print(mImportantProcesses.keyAt(i)); pw.print(": "); pw.println(mImportantProcesses.valueAt(i)); @@ -15678,7 +15815,6 @@ public class ActivityManagerService extends IActivityManager.Stub if (mPersistentStartingProcesses.size() > 0) { if (needSep) pw.println(); needSep = true; - printedAnything = true; pw.println(" Persisent processes that are starting:"); dumpProcessList(pw, this, mPersistentStartingProcesses, " ", "Starting Norm", "Restarting PERS", dumpPackage); @@ -15687,7 +15823,6 @@ public class ActivityManagerService extends IActivityManager.Stub if (mRemovedProcesses.size() > 0) { if (needSep) pw.println(); needSep = true; - printedAnything = true; pw.println(" Processes that are being removed:"); dumpProcessList(pw, this, mRemovedProcesses, " ", "Removed Norm", "Removed PERS", dumpPackage); @@ -15696,7 +15831,6 @@ public class ActivityManagerService extends IActivityManager.Stub if (mProcessesOnHold.size() > 0) { if (needSep) pw.println(); needSep = true; - printedAnything = true; pw.println(" Processes that are on old until the system is ready:"); dumpProcessList(pw, this, mProcessesOnHold, " ", "OnHold Norm", "OnHold PERS", dumpPackage); @@ -15705,9 +15839,6 @@ public class ActivityManagerService extends IActivityManager.Stub needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, dumpPackage); needSep = mAppErrors.dumpLocked(fd, pw, needSep, dumpPackage); - if (needSep) { - printedAnything = true; - } if (dumpPackage == null) { pw.println(); @@ -15914,6 +16045,32 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(" mNativeDebuggingApp=" + mNativeDebuggingApp); } } + if (mAllowAppSwitchUids.size() > 0) { + boolean printed = false; + for (int i = 0; i < mAllowAppSwitchUids.size(); i++) { + ArrayMap<String, Integer> types = mAllowAppSwitchUids.valueAt(i); + for (int j = 0; j < types.size(); j++) { + if (dumpPackage == null || + UserHandle.getAppId(types.valueAt(j).intValue()) == dumpAppId) { + if (needSep) { + pw.println(); + needSep = false; + } + if (!printed) { + pw.println(" mAllowAppSwitchUids:"); + printed = true; + } + pw.print(" User "); + pw.print(mAllowAppSwitchUids.keyAt(i)); + pw.print(": Type "); + pw.print(types.keyAt(j)); + pw.print(" = "); + UserHandle.formatUid(pw, types.valueAt(j).intValue()); + pw.println(); + } + } + } + } if (dumpPackage == null) { if (mAlwaysFinishActivities) { pw.println(" mAlwaysFinishActivities=" + mAlwaysFinishActivities); @@ -15953,10 +16110,7 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(); } } - - if (!printedAnything) { - pw.println(" (nothing)"); - } + pw.println(" mForceBackgroundCheck=" + mForceBackgroundCheck); } boolean dumpProcessesToGc(FileDescriptor fd, PrintWriter pw, String[] args, @@ -16063,6 +16217,15 @@ public class ActivityManagerService extends IActivityManager.Stub return mProviderMap.dumpProvider(fd, pw, name, args, opti, dumpAll); } + /** + * Similar to the dumpProvider, but only dumps the first matching provider. + * The provider is responsible for dumping as proto. + */ + protected boolean dumpProviderProto(FileDescriptor fd, PrintWriter pw, String name, + String[] args) { + return mProviderMap.dumpProviderProto(fd, pw, name, args); + } + static class ItemMatcher { ArrayList<ComponentName> components; ArrayList<String> strings; @@ -17883,7 +18046,7 @@ public class ActivityManagerService extends IActivityManager.Stub PrintWriter catPw = new FastPrintWriter(catSw, false, 256); String[] emptyArgs = new String[] { }; catPw.println(); - dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null); + dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null, -1); catPw.println(); mServices.newServiceDumperLocked(null, catPw, emptyArgs, 0, false, null).dumpLocked(); @@ -19296,13 +19459,7 @@ public class ActivityManagerService extends IActivityManager.Stub mRecentTasks.removeTasksByPackageName(ssp, userId); mServices.forceStopPackageLocked(ssp, userId); - - // Hide the "unsupported display" dialog if necessary. - if (mUnsupportedDisplaySizeDialog != null && ssp.equals( - mUnsupportedDisplaySizeDialog.getPackageName())) { - mUnsupportedDisplaySizeDialog.dismiss(); - mUnsupportedDisplaySizeDialog = null; - } + mAppWarnings.onPackageUninstalled(ssp); mCompatModePackages.handlePackageUninstalledLocked(ssp); mBatteryStatsService.notePackageUninstalled(ssp); } @@ -19381,13 +19538,8 @@ public class ActivityManagerService extends IActivityManager.Stub Uri data = intent.getData(); String ssp; if (data != null && (ssp = data.getSchemeSpecificPart()) != null) { - // Hide the "unsupported display" dialog if necessary. - if (mUnsupportedDisplaySizeDialog != null && ssp.equals( - mUnsupportedDisplaySizeDialog.getPackageName())) { - mUnsupportedDisplaySizeDialog.dismiss(); - mUnsupportedDisplaySizeDialog = null; - } mCompatModePackages.handlePackageDataClearedLocked(ssp); + mAppWarnings.onPackageDataCleared(ssp); } break; } @@ -20447,9 +20599,11 @@ public class ActivityManagerService extends IActivityManager.Stub if (app.thread != null) { if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc " + app.processName + " new config " + configCopy); - app.thread.scheduleConfigurationChanged(configCopy); + mLifecycleManager.scheduleTransaction(app.thread, + new ConfigurationChangeItem(configCopy)); } } catch (Exception e) { + Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change", e); } } @@ -20577,8 +20731,7 @@ public class ActivityManagerService extends IActivityManager.Stub final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0; if (isDensityChange && displayId == DEFAULT_DISPLAY) { - // Reset the unsupported display size dialog. - mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG); + mAppWarnings.onDensityChanged(); killAllBackgroundProcessesExcept(N, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); @@ -20959,7 +21112,7 @@ public class ActivityManagerService extends IActivityManager.Stub + " instead of expected " + app); if (r.app == null || (r.app.uid == app.uid)) { // Only fix things up when they look sane - r.app = app; + r.setProcess(app); } else { continue; } @@ -21038,6 +21191,11 @@ public class ActivityManagerService extends IActivityManager.Stub adj += minLayer; } } + if (procState > ActivityManager.PROCESS_STATE_CACHED_RECENT && app.recentTasks.size() > 0) { + procState = ActivityManager.PROCESS_STATE_CACHED_RECENT; + app.adjType = "cch-rec"; + if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Raise to cached recent: " + app); + } if (adj > ProcessList.PERCEPTIBLE_APP_ADJ || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { @@ -22598,6 +22756,7 @@ public class ActivityManagerService extends IActivityManager.Stub switch (app.curProcState) { case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: + case ActivityManager.PROCESS_STATE_CACHED_RECENT: // This process is a cached process holding activities... // assign it the next cached value for that type, and then // step that cached level. @@ -23212,6 +23371,24 @@ public class ActivityManagerService extends IActivityManager.Stub } } + /** + * Call {@link #doStopUidLocked} (which will also stop background services) for all idle UIDs. + */ + void doStopUidForIdleUidsLocked() { + final int size = mActiveUids.size(); + for (int i = 0; i < size; i++) { + final int uid = mActiveUids.keyAt(i); + if (!UserHandle.isApp(uid)) { + continue; + } + final UidRecord uidRec = mActiveUids.valueAt(i); + if (!uidRec.idle) { + continue; + } + doStopUidLocked(uidRec.uid, uidRec); + } + } + final void doStopUidLocked(int uid, final UidRecord uidRec) { mServices.stopInBackgroundLocked(uid); enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE); @@ -23322,7 +23499,7 @@ public class ActivityManagerService extends IActivityManager.Stub // has been removed. for (i=mRemovedProcesses.size()-1; i>=0; i--) { final ProcessRecord app = mRemovedProcesses.get(i); - if (app.activities.size() == 0 + if (app.activities.size() == 0 && app.recentTasks.size() == 0 && app.curReceivers.isEmpty() && app.services.size() == 0) { Slog.i( TAG, "Exiting empty application process " @@ -24189,6 +24366,32 @@ public class ActivityManagerService extends IActivityManager.Stub } } } + + @Override + public void setAllowAppSwitches(@NonNull String type, int uid, int userId) { + synchronized (ActivityManagerService.this) { + if (mUserController.isUserRunning(userId, ActivityManager.FLAG_OR_STOPPED)) { + ArrayMap<String, Integer> types = mAllowAppSwitchUids.get(userId); + if (types == null) { + if (uid < 0) { + return; + } + types = new ArrayMap<>(); + mAllowAppSwitchUids.put(userId, types); + } + if (uid < 0) { + types.remove(type); + } else { + types.put(type, uid); + } + } + } + } + + @Override + public boolean isRuntimeRestarted() { + return mSystemServiceManager.isRuntimeRestarted(); + } } /** diff --git a/com/android/server/am/ActivityRecord.java b/com/android/server/am/ActivityRecord.java index 9a16745c..a089e6ce 100644 --- a/com/android/server/am/ActivityRecord.java +++ b/com/android/server/am/ActivityRecord.java @@ -79,7 +79,6 @@ import static android.os.Build.VERSION_CODES.HONEYCOMB; import static android.os.Build.VERSION_CODES.O; import static android.os.Process.SYSTEM_UID; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; -import static android.view.WindowManagerPolicy.NAV_BAR_LEFT; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SAVED_STATE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STATES; @@ -117,6 +116,7 @@ import static com.android.server.am.proto.ActivityRecordProto.IDENTIFIER; import static com.android.server.am.proto.ActivityRecordProto.PROC_ID; import static com.android.server.am.proto.ActivityRecordProto.STATE; import static com.android.server.am.proto.ActivityRecordProto.VISIBLE; +import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT; import static com.android.server.wm.proto.IdentifierProto.HASH_CODE; import static com.android.server.wm.proto.IdentifierProto.TITLE; import static com.android.server.wm.proto.IdentifierProto.USER_ID; @@ -131,6 +131,12 @@ import android.app.ActivityOptions; import android.app.PendingIntent; import android.app.PictureInPictureParams; import android.app.ResultInfo; +import android.app.servertransaction.MoveToDisplayItem; +import android.app.servertransaction.MultiWindowModeChangeItem; +import android.app.servertransaction.NewIntentItem; +import android.app.servertransaction.PipModeChangeItem; +import android.app.servertransaction.WindowVisibilityItem; +import android.app.servertransaction.ActivityConfigurationChangeItem; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -165,7 +171,6 @@ import android.view.IApplicationToken; import android.view.WindowManager.LayoutParams; import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ResolverActivity; import com.android.internal.content.ReferrerIntent; import com.android.internal.util.XmlUtils; @@ -343,12 +348,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo // on the window. int mRotationAnimationHint = -1; - // The bounds of this activity. Mainly used for aspect-ratio compatibility. - // TODO(b/36505427): Every level on ConfigurationContainer now has bounds information, which - // directly affects the configuration. We should probably move this into that class and have it - // handle calculating override configuration from the bounds. - private final Rect mBounds = new Rect(); - private boolean mShowWhenLocked; private boolean mTurnScreenOn; @@ -414,8 +413,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo if (!getOverrideConfiguration().equals(EMPTY)) { pw.println(prefix + "OverrideConfiguration=" + getOverrideConfiguration()); } - if (!mBounds.isEmpty()) { - pw.println(prefix + "mBounds=" + mBounds); + if (!matchParentBounds()) { + pw.println(prefix + "bounds=" + getBounds()); } if (resultTo != null || resultWho != null) { pw.print(prefix); pw.print("resultTo="); pw.print(resultTo); @@ -618,8 +617,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo "Reporting activity moved to display" + ", activityRecord=" + this + ", displayId=" + displayId + ", config=" + config); - app.thread.scheduleActivityMovedToDisplay(appToken, displayId, - new Configuration(config)); + service.mLifecycleManager.scheduleTransaction(app.thread, appToken, + new MoveToDisplayItem(displayId, config)); } catch (RemoteException e) { // If process died, whatever. } @@ -636,7 +635,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + this + ", config: " + config); - app.thread.scheduleActivityConfigurationChanged(appToken, new Configuration(config)); + service.mLifecycleManager.scheduleTransaction(app.thread, appToken, + new ActivityConfigurationChangeItem(config)); } catch (RemoteException e) { // If process died, whatever. } @@ -647,8 +647,13 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo return; } + if (task.getStack().deferScheduleMultiWindowModeChanged()) { + // Don't do anything if we are currently deferring multi-window mode change. + return; + } + // An activity is considered to be in multi-window mode if its task isn't fullscreen. - final boolean inMultiWindowMode = !task.mFullscreen; + final boolean inMultiWindowMode = inMultiWindowMode(); if (inMultiWindowMode != mLastReportedMultiWindowMode) { mLastReportedMultiWindowMode = inMultiWindowMode; scheduleMultiWindowModeChanged(getConfiguration()); @@ -657,8 +662,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo private void scheduleMultiWindowModeChanged(Configuration overrideConfig) { try { - app.thread.scheduleMultiWindowModeChanged(appToken, mLastReportedMultiWindowMode, - overrideConfig); + service.mLifecycleManager.scheduleTransaction(app.thread, appToken, + new MultiWindowModeChangeItem(mLastReportedMultiWindowMode, + overrideConfig)); } catch (Exception e) { // If process died, I don't care. } @@ -674,7 +680,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo // Picture-in-picture mode changes also trigger a multi-window mode change as well, so // update that here in order mLastReportedPictureInPictureMode = inPictureInPictureMode; - mLastReportedMultiWindowMode = inPictureInPictureMode; + mLastReportedMultiWindowMode = inMultiWindowMode(); final Configuration newConfig = task.computeNewOverrideConfigurationForBounds( targetStackBounds, null); schedulePictureInPictureModeChanged(newConfig); @@ -684,8 +690,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo private void schedulePictureInPictureModeChanged(Configuration overrideConfig) { try { - app.thread.schedulePictureInPictureModeChanged(appToken, - mLastReportedPictureInPictureMode, overrideConfig); + service.mLifecycleManager.scheduleTransaction(app.thread, appToken, + new PipModeChangeItem(mLastReportedPictureInPictureMode, + overrideConfig)); } catch (Exception e) { // If process died, no one cares. } @@ -934,6 +941,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } } + void setProcess(ProcessRecord proc) { + app = proc; + final ActivityRecord root = task != null ? task.getRootActivity() : null; + if (root == this) { + task.setRootProcess(proc); + } + } + AppWindowContainerController getWindowContainerController() { return mWindowContainerController; } @@ -958,14 +973,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, info.configChanges, task.voiceSession != null, mLaunchTaskBehind, isAlwaysFocusable(), appInfo.targetSdkVersion, mRotationAnimationHint, - ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L, mBounds); + ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L); task.addActivityToTop(this); // When an activity is started directly into a split-screen fullscreen stack, we need to // update the initial multi-window modes so that the callbacks are scheduled correctly when // the user leaves that mode. - mLastReportedMultiWindowMode = !task.mFullscreen; + mLastReportedMultiWindowMode = inMultiWindowMode(); mLastReportedPictureInPictureMode = inPinnedWindowingMode(); } @@ -1364,8 +1379,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo try { ArrayList<ReferrerIntent> ar = new ArrayList<>(1); ar.add(rintent); - app.thread.scheduleNewIntent( - ar, appToken, state == PAUSED /* andPause */); + service.mLifecycleManager.scheduleTransaction(app.thread, appToken, + new NewIntentItem(ar, state == PAUSED)); unsent = false; } catch (RemoteException e) { Slog.w(TAG, "Exception thrown sending new intent to " + this, e); @@ -1587,7 +1602,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo setVisible(true); sleeping = false; app.pendingUiClean = true; - app.thread.scheduleWindowVisibility(appToken, true /* showWindow */); + service.mLifecycleManager.scheduleTransaction(app.thread, appToken, + new WindowVisibilityItem(true /* showWindow */)); // The activity may be waiting for stop, but that is no longer appropriate for it. mStackSupervisor.mStoppingActivities.remove(this); mStackSupervisor.mGoingToSleepActivities.remove(this); @@ -2164,33 +2180,25 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo mLastReportedConfiguration.setConfiguration(global, override); } - @Override - public void onOverrideConfigurationChanged(Configuration newConfig) { - final Configuration currentConfig = getOverrideConfiguration(); - if (currentConfig.equals(newConfig)) { - return; - } - super.onOverrideConfigurationChanged(newConfig); - if (mWindowContainerController == null) { - return; - } - mWindowContainerController.onOverrideConfigurationChanged(newConfig, mBounds); - } - // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. private void updateOverrideConfiguration() { mTmpConfig.unset(); computeBounds(mTmpBounds); - if (mTmpBounds.equals(mBounds)) { + + if (mTmpBounds.equals(getOverrideBounds())) { return; } - mBounds.set(mTmpBounds); + setBounds(mTmpBounds); + + final Rect updatedBounds = getOverrideBounds(); + // Bounds changed...update configuration to match. - if (!mBounds.isEmpty()) { - task.computeOverrideConfiguration(mTmpConfig, mBounds, null /* insetBounds */, + if (!matchParentBounds()) { + task.computeOverrideConfiguration(mTmpConfig, updatedBounds, null /* insetBounds */, false /* overrideWidth */, false /* overrideHeight */); } + onOverrideConfigurationChanged(mTmpConfig); } @@ -2217,7 +2225,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo outBounds.setEmpty(); final float maxAspectRatio = info.maxAspectRatio; final ActivityStack stack = getStack(); - if (task == null || stack == null || !task.mFullscreen || maxAspectRatio == 0 + if (task == null || stack == null || task.inMultiWindowMode() || maxAspectRatio == 0 || isInVrUiMode(getConfiguration())) { // We don't set override configuration if that activity task isn't fullscreen. I.e. the // activity is in multi-window mode. Or, there isn't a max aspect ratio specified for @@ -2248,11 +2256,11 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo if (containingAppWidth <= maxActivityWidth && containingAppHeight <= maxActivityHeight) { // The display matches or is less than the activity aspect ratio, so nothing else to do. // Return the existing bounds. If this method is running for the first time, - // {@link mBounds} will be empty (representing no override). If the method has run - // before, then effect of {@link mBounds} will already have been applied to the + // {@link #getOverrideBounds()} will be empty (representing no override). If the method has run + // before, then effect of {@link #getOverrideBounds()} will already have been applied to the // value returned from {@link getConfiguration}. Refer to // {@link TaskRecord#computeOverrideConfiguration}. - outBounds.set(mBounds); + outBounds.set(getOverrideBounds()); return; } @@ -2264,12 +2272,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo outBounds.offsetTo(left, 0 /* top */); } - /** Get bounds of the activity. */ - @VisibleForTesting - Rect getBounds() { - return new Rect(mBounds); - } - /** * Make sure the given activity matches the current configuration. Returns false if the activity * had to be destroyed. Returns true if the configuration is the same, or the activity will @@ -2549,7 +2551,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } results = null; newIntents = null; - service.showUnsupportedZoomDialogIfNeededLocked(this); + service.getAppWarningsLocked().onResumeActivity(this); service.showAskCompatModeDialogLocked(this); } else { service.mHandler.removeMessages(PAUSE_TIMEOUT_MSG, this); diff --git a/com/android/server/am/ActivityStack.java b/com/android/server/am/ActivityStack.java index c086c529..bdfd82f4 100644 --- a/com/android/server/am/ActivityStack.java +++ b/com/android/server/am/ActivityStack.java @@ -104,6 +104,13 @@ import android.app.IActivityController; import android.app.ResultInfo; import android.app.WindowConfiguration.ActivityType; import android.app.WindowConfiguration.WindowingMode; +import android.app.servertransaction.ActivityResultItem; +import android.app.servertransaction.NewIntentItem; +import android.app.servertransaction.WindowVisibilityItem; +import android.app.servertransaction.DestroyActivityItem; +import android.app.servertransaction.PauseActivityItem; +import android.app.servertransaction.ResumeActivityItem; +import android.app.servertransaction.StopActivityItem; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -156,7 +163,6 @@ import java.util.Set; */ class ActivityStack<T extends StackWindowController> extends ConfigurationContainer implements StackWindowListener { - private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_AM; private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE; private static final String TAG_APP = TAG + POSTFIX_APP; @@ -322,11 +328,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai */ boolean mForceHidden = false; - // Whether or not this stack covers the entire screen; by default stacks are fullscreen - boolean mFullscreen = true; - // Current bounds of the stack or null if fullscreen. - Rect mBounds = null; - private boolean mUpdateBoundsDeferred; private boolean mUpdateBoundsDeferredCalled; private final Rect mDeferredBounds = new Rect(); @@ -342,8 +343,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai /** The attached Display's unique identifier, or -1 if detached */ int mDisplayId; - /** Temp variables used during override configuration update. */ - private final SparseArray<Configuration> mTmpConfigs = new SparseArray<>(); private final SparseArray<Rect> mTmpBounds = new SparseArray<>(); private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>(); private final Rect mTmpRect2 = new Rect(); @@ -495,16 +494,18 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // Need to make sure windowing mode is supported. int windowingMode = display.resolveWindowingMode( - null /* ActivityRecord */, mTmpOptions, topTask, getActivityType());; + null /* ActivityRecord */, mTmpOptions, topTask, getActivityType()); if (splitScreenStack == this && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { // Resolution to split-screen secondary for the primary split-screen stack means we want // to go fullscreen. windowingMode = WINDOWING_MODE_FULLSCREEN; } + final boolean alreadyInSplitScreenMode = display.hasSplitScreenPrimaryStack(); + // Take any required action due to us not supporting the preferred windowing mode. if (windowingMode != preferredWindowingMode && isActivityTypeStandardOrUndefined()) { - if (display.hasSplitScreenPrimaryStack() + if (alreadyInSplitScreenMode && (preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) { // Looks like we can't launch in split screen mode, go ahead an dismiss split-screen @@ -574,11 +575,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } - if (!Objects.equals(mBounds, mTmpRect2)) { + if (!Objects.equals(getOverrideBounds(), mTmpRect2)) { resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */); } } finally { - if (mDisplayId == DEFAULT_DISPLAY + if (!alreadyInSplitScreenMode && mDisplayId == DEFAULT_DISPLAY && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { // Make sure recents stack exist when creating a dock stack as it normally needs to // be on the other side of the docked stack and we make visibility decisions based @@ -641,9 +642,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai */ private void postAddToDisplay(ActivityDisplay activityDisplay, Rect bounds, boolean onTop) { mDisplayId = activityDisplay.mDisplayId; - mBounds = bounds != null ? new Rect(bounds) : null; - mFullscreen = mBounds == null; - + setBounds(bounds); onParentChanged(); activityDisplay.addChild(this, onTop ? POSITION_TOP : POSITION_BOTTOM); @@ -651,7 +650,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // If we created a docked stack we want to resize it so it resizes all other stacks // in the system. mStackSupervisor.resizeDockedStackLocked( - mBounds, null, null, null, null, PRESERVE_WINDOWS); + getOverrideBounds(), null, null, null, null, PRESERVE_WINDOWS); } } @@ -766,8 +765,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return false; } - void setBounds(Rect bounds) { - mBounds = mFullscreen ? null : new Rect(bounds); + @Override + public int setBounds(Rect bounds) { + return super.setBounds(!inMultiWindowMode() ? null : bounds); } ActivityRecord topRunningActivityLocked() { @@ -1428,8 +1428,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai prev.userId, System.identityHashCode(prev), prev.shortComponentName); mService.updateUsageStats(prev, false); - prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing, - userLeaving, prev.configChangeFlags, pauseImmediately); + + mService.mLifecycleManager.scheduleTransaction(prev.app.thread, prev.appToken, + new PauseActivityItem(prev.finishing, userLeaving, + prev.configChangeFlags, pauseImmediately)); } catch (Exception e) { // Ignore exception, if process died other code will cleanup. Slog.w(TAG, "Exception thrown during pause", e); @@ -1678,12 +1680,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return true; } - /** Returns true if the stack is currently considered visible. */ - boolean isVisible() { - return mWindowContainerController != null && mWindowContainerController.isVisible() - && !mForceHidden; - } - boolean isTopStackOnDisplay() { return getDisplay().isTopStack(this); } @@ -2064,7 +2060,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (r.app != null && r.app.thread != null) { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Scheduling invisibility: " + r); - r.app.thread.scheduleWindowVisibility(r.appToken, false); + mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken, + new WindowVisibilityItem(false /* showWindow */)); } // Reset the flag indicating that an app can enter picture-in-picture once the @@ -2495,7 +2492,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // apps, maybeUpdateTransitToWallpaper() will fail to identify this as a // TRANSIT_WALLPAPER_OPEN animation, and run some funny animation. final boolean lastActivityTranslucent = lastStack != null - && (!lastStack.mFullscreen + && (lastStack.inMultiWindowMode() || (lastStack.mLastPausedActivity != null && !lastStack.mLastPausedActivity.fullscreen)); @@ -2584,13 +2581,15 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (!next.finishing && N > 0) { if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a); - next.app.thread.scheduleSendResult(next.appToken, a); + mService.mLifecycleManager.scheduleTransaction(next.app.thread, + next.appToken, new ActivityResultItem(a)); } } if (next.newIntents != null) { - next.app.thread.scheduleNewIntent( - next.newIntents, next.appToken, false /* andPause */); + mService.mLifecycleManager.scheduleTransaction(next.app.thread, + next.appToken, new NewIntentItem(next.newIntents, + false /* andPause */)); } // Well the app will no longer be stopped. @@ -2602,13 +2601,14 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai next.shortComponentName); next.sleeping = false; - mService.showUnsupportedZoomDialogIfNeededLocked(next); + mService.getAppWarningsLocked().onResumeActivity(next); mService.showAskCompatModeDialogLocked(next); next.app.pendingUiClean = true; next.app.forceProcessStateUpTo(mService.mTopProcessState); next.clearOptionsLocked(); - next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState, - mService.isNextTransitionForward(), resumeAnimOptions); + mService.mLifecycleManager.scheduleTransaction(next.app.thread, next.appToken, + new ResumeActivityItem(next.app.repProcState, + mService.isNextTransitionForward())); if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed " + next); @@ -2739,8 +2739,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai position = getAdjustedPositionForTask(task, position, null /* starting */); mTaskHistory.remove(task); mTaskHistory.add(position, task); - mWindowContainerController.positionChildAt(task.getWindowContainerController(), position, - task.mBounds, task.getOverrideConfiguration()); + mWindowContainerController.positionChildAt(task.getWindowContainerController(), position); updateTaskMovement(task, true); } @@ -3259,7 +3258,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai ArrayList<ResultInfo> list = new ArrayList<ResultInfo>(); list.add(new ResultInfo(resultWho, requestCode, resultCode, data)); - r.app.thread.scheduleSendResult(r.appToken, list); + mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken, + new ActivityResultItem(list)); return; } catch (Exception e) { Slog.w(TAG, "Exception thrown sending result to " + r, e); @@ -3387,7 +3387,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } EventLogTags.writeAmStopActivity( r.userId, System.identityHashCode(r), r.shortComponentName); - r.app.thread.scheduleStopActivity(r.appToken, r.visible, r.configChangeFlags); + mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken, + new StopActivityItem(r.visible, r.configChangeFlags)); if (shouldSleepOrShutDownActivities()) { r.setSleeping(true); } @@ -4019,7 +4020,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // TODO: If the callers to removeTask() changes such that we have multiple places // where we are destroying the task, move this back into removeTask() mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */, - !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY); + !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY, reason); } // We must keep the task around until all activities are destroyed. The following @@ -4184,8 +4185,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai try { if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r); - r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing, - r.configChangeFlags); + mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken, + new DestroyActivityItem(r.finishing, r.configChangeFlags)); } catch (Exception e) { // We can just ignore exceptions here... if the process // has crashed, our death notification will clean things @@ -4602,8 +4603,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // TODO: Can only be called from special methods in ActivityStackSupervisor. // Need to consolidate those calls points into this resize method so anyone can call directly. void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds) { - bounds = TaskRecord.validateBounds(bounds); - if (!updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) { return; } @@ -4613,7 +4612,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final Rect insetBounds = tempTaskInsetBounds != null ? tempTaskInsetBounds : taskBounds; mTmpBounds.clear(); - mTmpConfigs.clear(); mTmpInsetBounds.clear(); for (int i = mTaskHistory.size() - 1; i >= 0; i--) { @@ -4624,7 +4622,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // For freeform stack we don't adjust the size of the tasks to match that // of the stack, but we do try to make sure the tasks are still contained // with the bounds of the stack. - mTmpRect2.set(task.mBounds); + mTmpRect2.set(task.getOverrideBounds()); fitWithinBounds(mTmpRect2, bounds); task.updateOverrideConfiguration(mTmpRect2); } else { @@ -4632,15 +4630,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } - mTmpConfigs.put(task.taskId, task.getOverrideConfiguration()); - mTmpBounds.put(task.taskId, task.mBounds); + mTmpBounds.put(task.taskId, task.getOverrideBounds()); if (tempTaskInsetBounds != null) { mTmpInsetBounds.put(task.taskId, tempTaskInsetBounds); } } - mFullscreen = mWindowContainerController.resize(bounds, mTmpConfigs, mTmpBounds, - mTmpInsetBounds); + mWindowContainerController.resize(bounds, mTmpBounds, mTmpInsetBounds); setBounds(bounds); } @@ -4655,7 +4651,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai * @param stackBounds Bounds within which the other bounds should remain. */ private static void fitWithinBounds(Rect bounds, Rect stackBounds) { - if (stackBounds == null || stackBounds.contains(bounds)) { + if (stackBounds == null || stackBounds.isEmpty() || stackBounds.contains(bounds)) { return; } @@ -4873,8 +4869,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai pw.println(""); } pw.println(prefix + "Task id #" + task.taskId); - pw.println(prefix + "mFullscreen=" + task.mFullscreen); - pw.println(prefix + "mBounds=" + task.mBounds); + pw.println(prefix + "mBounds=" + task.getOverrideBounds()); pw.println(prefix + "mMinWidth=" + task.mMinWidth); pw.println(prefix + "mMinHeight=" + task.mMinHeight); pw.println(prefix + "mLastNonFullscreenBounds=" + task.mLastNonFullscreenBounds); @@ -4981,7 +4976,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (isOnHomeDisplay() && mode != REMOVE_TASK_MODE_MOVING_TO_TOP && mStackSupervisor.isFocusedStack(this)) { String myReason = reason + " leftTaskHistoryEmpty"; - if (mFullscreen || !adjustFocusToNextFocusableStack(myReason)) { + if (!inMultiWindowMode() || !adjustFocusToNextFocusableStack(myReason)) { mStackSupervisor.moveHomeStackToFront(myReason); } } @@ -5004,15 +4999,24 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, boolean toTop) { + return createTaskRecord(taskId, info, intent, voiceSession, voiceInteractor, toTop, + null /*activity*/, null /*source*/, null /*options*/); + } + + TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + boolean toTop, ActivityRecord activity, ActivityRecord source, + ActivityOptions options) { final TaskRecord task = new TaskRecord(mService, taskId, info, intent, voiceSession, voiceInteractor); // add the task to stack first, mTaskPositioner might need the stack association addTask(task, toTop, "createTaskRecord"); final boolean isLockscreenShown = mService.mStackSupervisor.getKeyguardController() .isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY); - if (!mStackSupervisor.getLaunchingBoundsController().layoutTask(task, info.windowLayout) - && mBounds != null && task.isResizeable() && !isLockscreenShown) { - task.updateOverrideConfiguration(mBounds); + if (!mStackSupervisor.getLaunchingBoundsController() + .layoutTask(task, info.windowLayout, activity, source, options) + && !matchParentBounds() && task.isResizeable() && !isLockscreenShown) { + task.updateOverrideConfiguration(getOverrideBounds()); } task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0); return task; @@ -5174,10 +5178,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mResumedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY); } proto.write(DISPLAY_ID, mDisplayId); - if (mBounds != null) { - mBounds.writeToProto(proto, BOUNDS); + if (!matchParentBounds()) { + final Rect bounds = getOverrideBounds(); + bounds.writeToProto(proto, BOUNDS); } - proto.write(FULLSCREEN, mFullscreen); + + // TODO: Remove, no longer needed with windowingMode. + proto.write(FULLSCREEN, matchParentBounds()); proto.end(token); } } diff --git a/com/android/server/am/ActivityStackSupervisor.java b/com/android/server/am/ActivityStackSupervisor.java index 745e9fbc..445bf679 100644 --- a/com/android/server/am/ActivityStackSupervisor.java +++ b/com/android/server/am/ActivityStackSupervisor.java @@ -114,6 +114,7 @@ import android.app.ResultInfo; import android.app.WaitResult; import android.app.WindowConfiguration.ActivityType; import android.app.WindowConfiguration.WindowingMode; +import android.app.servertransaction.LaunchActivityItem; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -1270,7 +1271,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // schedule launch ticks to collect information about slow apps. r.startLaunchTickingLocked(); - r.app = app; + r.setProcess(app); if (mKeyguardController.isKeyguardLocked()) { r.notifyUnknownVisibilityLaunched(); @@ -1358,7 +1359,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY); r.sleeping = false; r.forceNewConfig = false; - mService.showUnsupportedZoomDialogIfNeededLocked(r); + mService.getAppWarningsLocked().onStartActivity(r); mService.showAskCompatModeDialogLocked(r); r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo); ProfilerInfo profilerInfo = null; @@ -1392,7 +1393,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D r.setLastReportedConfiguration(mergedConfiguration); logIfTransactionTooLarge(r.intent, r.icicle); - app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken, + mService.mLifecycleManager.scheduleTransaction(app.thread, r.appToken, + new LaunchActivityItem(new Intent(r.intent), System.identityHashCode(r), r.info, // TODO: Have this take the merged configuration instead of separate global // and override configs. @@ -1400,7 +1402,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mergedConfiguration.getOverrideConfiguration(), r.compat, r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results, newIntents, !andResume, - mService.isNextTransitionForward(), profilerInfo); + mService.isNextTransitionForward(), profilerInfo)); if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { // This may be a heavy-weight process! Note that the package @@ -2118,7 +2120,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) { - final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds()); + final Rect bounds = options.getLaunchBounds(); task.updateOverrideConfiguration(bounds); ActivityStack stack = getLaunchStack(null, options, task, ON_TOP); @@ -2626,7 +2628,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // TODO: Checking for isAttached might not be needed as if the user passes in null // dockedBounds then they want the docked stack to be dismissed. - if (stack.mFullscreen || (dockedBounds == null && !stack.isAttached())) { + if (stack.getWindowingMode() == WINDOWING_MODE_FULLSCREEN + || (dockedBounds == null && !stack.isAttached())) { // The dock stack either was dismissed or went fullscreen, which is kinda the same. // In this case we make all other static stacks fullscreen and move all // docked stack tasks to the fullscreen stack. @@ -2736,7 +2739,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } else { for (int i = tasks.size() - 1; i >= 0; i--) { removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */, - REMOVE_FROM_RECENTS); + REMOVE_FROM_RECENTS, "remove-stack"); } } } @@ -2769,8 +2772,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D /** * See {@link #removeTaskByIdLocked(int, boolean, boolean, boolean)} */ - boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents) { - return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY); + boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents, + String reason) { + return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY, + reason); } /** @@ -2784,10 +2789,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D * @return Returns true if the given task was found and removed. */ boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents, - boolean pauseImmediately) { + boolean pauseImmediately, String reason) { final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS); if (tr != null) { - tr.removeTaskActivitiesLocked(pauseImmediately); + tr.removeTaskActivitiesLocked(pauseImmediately, reason); cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents); mService.mLockTaskController.clearLockedTask(tr); if (tr.isPersistable) { @@ -2921,7 +2926,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D @Override public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) { - // TODO: Trim active task once b/68045330 is fixed + if (wasTrimmed) { + // Task was trimmed from the recent tasks list -- remove the active task record as well + // since the user won't really be able to go back to it + removeTaskByIdLocked(task.taskId, false /* killProcess */, + false /* removeFromRecents */, !PAUSE_IMMEDIATELY, "recent-task-trimmed"); + } task.removedFromRecents(); } @@ -3058,7 +3068,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // Resize the pinned stack to match the current size of the task the activity we are // going to be moving is currently contained in. We do this to have the right starting // animation bounds for the pinned stack to the desired bounds the caller wants. - resizeStackLocked(stack, task.mBounds, null /* tempTaskBounds */, + resizeStackLocked(stack, task.getOverrideBounds(), null /* tempTaskBounds */, null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS, true /* allowResizeInDockedMode */, !DEFER_RESUME); @@ -3782,9 +3792,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D pw.println(" Stack #" + stack.mStackId + ": type=" + activityTypeToString(stack.getActivityType()) + " mode=" + windowingModeToString(stack.getWindowingMode())); - pw.println(" mFullscreen=" + stack.mFullscreen); pw.println(" isSleeping=" + stack.shouldSleepActivities()); - pw.println(" mBounds=" + stack.mBounds); + pw.println(" mBounds=" + stack.getOverrideBounds()); printed |= stack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage, needSep); @@ -4054,6 +4063,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D private void handleDisplayChanged(int displayId) { synchronized (mService) { ActivityDisplay activityDisplay = mActivityDisplays.get(displayId); + // TODO: The following code block should be moved into {@link ActivityDisplay}. if (activityDisplay != null) { // The window policy is responsible for stopping activities on the default display if (displayId != Display.DEFAULT_DISPLAY) { @@ -4067,7 +4077,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D activityDisplay.mOffToken = null; } } - // TODO: Update the bounds. + + activityDisplay.updateBounds(); } mWindowManager.onDisplayChanged(displayId); } @@ -4289,7 +4300,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return; } - scheduleUpdatePictureInPictureModeIfNeeded(task, stack.mBounds); + scheduleUpdatePictureInPictureModeIfNeeded(task, stack.getOverrideBounds()); } void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds) { @@ -4297,6 +4308,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final ActivityRecord r = task.mActivities.get(i); if (r.app != null && r.app.thread != null) { mPipModeChangedActivities.add(r); + // If we are scheduling pip change, then remove this activity from multi-window + // change list as the processing of pip change will make sure multi-window changed + // message is processed in the right order relative to pip changed. + mMultiWindowModeChangedActivities.remove(r); } } mPipModeChangedTargetStackBounds = targetStackBounds; diff --git a/com/android/server/am/ActivityStarter.java b/com/android/server/am/ActivityStarter.java index 9b8cbc10..2fc5dda6 100644 --- a/com/android/server/am/ActivityStarter.java +++ b/com/android/server/am/ActivityStarter.java @@ -134,7 +134,6 @@ class ActivityStarter { private static final int INVALID_LAUNCH_MODE = -1; private final ActivityManagerService mService; - private final IPackageManager mPackageManager; private final ActivityStackSupervisor mSupervisor; private final ActivityStartInterceptor mInterceptor; @@ -234,9 +233,8 @@ class ActivityStarter { mIntentDelivered = false; } - ActivityStarter(ActivityManagerService service, IPackageManager packageManager) { + ActivityStarter(ActivityManagerService service) { mService = service; - mPackageManager = packageManager; mSupervisor = mService.mStackSupervisor; mInterceptor = new ActivityStartInterceptor(mService, mSupervisor); } @@ -379,7 +377,7 @@ class ActivityStarter { && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) { try { intent.addCategory(Intent.CATEGORY_VOICE); - if (!mPackageManager.activitySupportsIntent( + if (!mService.getPackageManager().activitySupportsIntent( intent.getComponent(), intent, resolvedType)) { Slog.w(TAG, "Activity being started in current voice task does not support voice: " @@ -397,7 +395,7 @@ class ActivityStarter { // If the caller is starting a new voice session, just make sure the target // is actually allowing it to run this way. try { - if (!mPackageManager.activitySupportsIntent(intent.getComponent(), + if (!mService.getPackageManager().activitySupportsIntent(intent.getComponent(), intent, resolvedType)) { Slog.w(TAG, "Activity being started in new voice task does not support: " @@ -608,21 +606,9 @@ class ActivityStarter { return; } - if (startedActivityStack.inSplitScreenPrimaryWindowingMode()) { - final ActivityStack homeStack = mSupervisor.mHomeStack; - final boolean homeStackVisible = homeStack != null && homeStack.isVisible(); - if (homeStackVisible) { - // We launch an activity while being in home stack, which means either launcher or - // recents into docked stack. We don't want the launched activity to be alone in a - // docked stack, so we want to immediately launch recents too. - if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch."); - mService.mWindowManager.showRecentApps(true /* fromHome */); - } - return; - } - - boolean clearedTask = (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) - == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK) && (mReuseTask != null); + final int clearTaskFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK; + boolean clearedTask = (mLaunchFlags & clearTaskFlags) == clearTaskFlags + && mReuseTask != null; if (startedActivityStack.inPinnedWindowingMode() && (result == START_TASK_TO_FRONT || result == START_DELIVERED_TO_TOP || clearedTask)) { @@ -1758,7 +1744,8 @@ class ActivityStarter { mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info, mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession, - mVoiceInteractor, !mLaunchTaskBehind /* toTop */); + mVoiceInteractor, !mLaunchTaskBehind /* toTop */, mStartActivity, mSourceRecord, + mOptions); addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask"); updateBounds(mStartActivity.getTask(), mLaunchBounds); @@ -1967,7 +1954,7 @@ class ActivityStarter { final ActivityRecord prev = mTargetStack.getTopActivity(); final TaskRecord task = (prev != null) ? prev.getTask() : mTargetStack.createTaskRecord( mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mStartActivity.info, - mIntent, null, null, true); + mIntent, null, null, true, mStartActivity, mSourceRecord, mOptions); addOrReparentStartingActivity(task, "setTaskToCurrentTopOrCreateNewTask"); mTargetStack.positionChildWindowContainerAtTop(task); if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity @@ -2124,18 +2111,13 @@ class ActivityStarter { return mReuseTask.getStack(); } - final int vrDisplayId = mPreferredDisplayId == mService.mVr2dDisplayId - ? mPreferredDisplayId : INVALID_DISPLAY; - final ActivityStack launchStack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP, - vrDisplayId); - - if (launchStack != null) { - return launchStack; - } - if (((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0) || mPreferredDisplayId != DEFAULT_DISPLAY) { - return null; + // We don't pass in the default display id into the get launch stack call so it can do a + // full resolution. + final int candidateDisplay = + mPreferredDisplayId != DEFAULT_DISPLAY ? mPreferredDisplayId : INVALID_DISPLAY; + return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP, candidateDisplay); } // Otherwise handle adjacent launch. @@ -2167,7 +2149,7 @@ class ActivityStarter { mSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack(); if (dockedStack != null && !dockedStack.shouldBeVisible(r)) { // There is a docked stack, but it isn't visible, so we can't launch into that. - return null; + return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP); } else { return dockedStack; } diff --git a/com/android/server/am/AppBindRecord.java b/com/android/server/am/AppBindRecord.java index df833ad8..7b385978 100644 --- a/com/android/server/am/AppBindRecord.java +++ b/com/android/server/am/AppBindRecord.java @@ -17,6 +17,9 @@ package com.android.server.am; import android.util.ArraySet; +import android.util.proto.ProtoOutputStream; + +import com.android.server.am.proto.AppBindRecordProto; import java.io.PrintWriter; @@ -60,4 +63,18 @@ final class AppBindRecord { + Integer.toHexString(System.identityHashCode(this)) + " " + service.shortName + ":" + client.processName + "}"; } + + void writeToProto(ProtoOutputStream proto, long fieldId) { + long token = proto.start(fieldId); + proto.write(AppBindRecordProto.HEX_HASH, + Integer.toHexString(System.identityHashCode(this))); + if (client != null) { + client.writeToProto(proto, AppBindRecordProto.CLIENT); + } + final int N = connections.size(); + for (int i=0; i<N; i++) { + connections.valueAt(i).writeToProto(proto, AppBindRecordProto.CONNECTIONS); + } + proto.end(token); + } } diff --git a/com/android/server/am/AppTaskImpl.java b/com/android/server/am/AppTaskImpl.java index 17626ea1..ab86dbdb 100644 --- a/com/android/server/am/AppTaskImpl.java +++ b/com/android/server/am/AppTaskImpl.java @@ -61,7 +61,7 @@ class AppTaskImpl extends IAppTask.Stub { try { // We remove the task from recents to preserve backwards if (!mService.mStackSupervisor.removeTaskByIdLocked(mTaskId, false, - REMOVE_FROM_RECENTS)) { + REMOVE_FROM_RECENTS, "finish-and-remove-task")) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } } finally { diff --git a/com/android/server/am/AppWarnings.java b/com/android/server/am/AppWarnings.java new file mode 100644 index 00000000..a3c03450 --- /dev/null +++ b/com/android/server/am/AppWarnings.java @@ -0,0 +1,498 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import android.annotation.UiThread; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.AtomicFile; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.util.FastXmlSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * Manages warning dialogs shown during application lifecycle. + */ +class AppWarnings { + private static final String TAG = "AppWarnings"; + private static final String CONFIG_FILE_NAME = "packages-warnings.xml"; + + public static final int FLAG_HIDE_DISPLAY_SIZE = 0x01; + public static final int FLAG_HIDE_COMPILE_SDK = 0x02; + + private final HashMap<String, Integer> mPackageFlags = new HashMap<>(); + + private final ActivityManagerService mAms; + private final Context mUiContext; + private final ConfigHandler mAmsHandler; + private final UiHandler mUiHandler; + private final AtomicFile mConfigFile; + + private UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog; + private UnsupportedCompileSdkDialog mUnsupportedCompileSdkDialog; + + /** + * Creates a new warning dialog manager. + * <p> + * <strong>Note:</strong> Must be called from the ActivityManagerService thread. + * + * @param ams + * @param uiContext + * @param amsHandler + * @param uiHandler + * @param systemDir + */ + public AppWarnings(ActivityManagerService ams, Context uiContext, Handler amsHandler, + Handler uiHandler, File systemDir) { + mAms = ams; + mUiContext = uiContext; + mAmsHandler = new ConfigHandler(amsHandler.getLooper()); + mUiHandler = new UiHandler(uiHandler.getLooper()); + mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME)); + + readConfigFromFileAmsThread(); + } + + /** + * Shows the "unsupported display size" warning, if necessary. + * + * @param r activity record for which the warning may be displayed + */ + public void showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r) { + final Configuration globalConfig = mAms.getGlobalConfiguration(); + if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE + && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) { + mUiHandler.showUnsupportedDisplaySizeDialog(r); + } + } + + /** + * Shows the "unsupported compile SDK" warning, if necessary. + * + * @param r activity record for which the warning may be displayed + */ + public void showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r) { + if (r.appInfo.compileSdkVersion == 0 || r.appInfo.compileSdkVersionCodename == null) { + // We don't know enough about this package. Abort! + return; + } + + // If the application was built against an pre-release SDK that's older than the current + // platform OR if the current platform is pre-release and older than the SDK against which + // the application was built OR both are pre-release with the same SDK_INT but different + // codenames (e.g. simultaneous pre-release development), then we're likely to run into + // compatibility issues. Warn the user and offer to check for an update. + final int compileSdk = r.appInfo.compileSdkVersion; + final int platformSdk = Build.VERSION.SDK_INT; + final boolean isCompileSdkPreview = !"REL".equals(r.appInfo.compileSdkVersionCodename); + final boolean isPlatformSdkPreview = !"REL".equals(Build.VERSION.CODENAME); + if ((isCompileSdkPreview && compileSdk < platformSdk) + || (isPlatformSdkPreview && platformSdk < compileSdk) + || (isCompileSdkPreview && isPlatformSdkPreview && platformSdk == compileSdk + && !Build.VERSION.CODENAME.equals(r.appInfo.compileSdkVersionCodename))) { + mUiHandler.showUnsupportedCompileSdkDialog(r); + } + } + + /** + * Called when an activity is being started. + * + * @param r record for the activity being started + */ + public void onStartActivity(ActivityRecord r) { + showUnsupportedCompileSdkDialogIfNeeded(r); + showUnsupportedDisplaySizeDialogIfNeeded(r); + } + + /** + * Called when an activity was previously started and is being resumed. + * + * @param r record for the activity being resumed + */ + public void onResumeActivity(ActivityRecord r) { + showUnsupportedDisplaySizeDialogIfNeeded(r); + } + + /** + * Called by ActivityManagerService when package data has been cleared. + * + * @param name the package whose data has been cleared + */ + public void onPackageDataCleared(String name) { + removePackageAndHideDialogs(name); + } + + /** + * Called by ActivityManagerService when a package has been uninstalled. + * + * @param name the package that has been uninstalled + */ + public void onPackageUninstalled(String name) { + removePackageAndHideDialogs(name); + } + + /** + * Called by ActivityManagerService when the default display density has changed. + */ + public void onDensityChanged() { + mUiHandler.hideUnsupportedDisplaySizeDialog(); + } + + /** + * Does what it says on the tin. + */ + private void removePackageAndHideDialogs(String name) { + mUiHandler.hideDialogsForPackage(name); + + synchronized (mPackageFlags) { + mPackageFlags.remove(name); + mAmsHandler.scheduleWrite(); + } + } + + /** + * Hides the "unsupported display size" warning. + * <p> + * <strong>Note:</strong> Must be called on the UI thread. + */ + @UiThread + private void hideUnsupportedDisplaySizeDialogUiThread() { + if (mUnsupportedDisplaySizeDialog != null) { + mUnsupportedDisplaySizeDialog.dismiss(); + mUnsupportedDisplaySizeDialog = null; + } + } + + /** + * Shows the "unsupported display size" warning for the given application. + * <p> + * <strong>Note:</strong> Must be called on the UI thread. + * + * @param ar record for the activity that triggered the warning + */ + @UiThread + private void showUnsupportedDisplaySizeDialogUiThread(ActivityRecord ar) { + if (mUnsupportedDisplaySizeDialog != null) { + mUnsupportedDisplaySizeDialog.dismiss(); + mUnsupportedDisplaySizeDialog = null; + } + if (ar != null && !hasPackageFlag( + ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) { + mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog( + AppWarnings.this, mUiContext, ar.info.applicationInfo); + mUnsupportedDisplaySizeDialog.show(); + } + } + + /** + * Shows the "unsupported compile SDK" warning for the given application. + * <p> + * <strong>Note:</strong> Must be called on the UI thread. + * + * @param ar record for the activity that triggered the warning + */ + @UiThread + private void showUnsupportedCompileSdkDialogUiThread(ActivityRecord ar) { + if (mUnsupportedCompileSdkDialog != null) { + mUnsupportedCompileSdkDialog.dismiss(); + mUnsupportedCompileSdkDialog = null; + } + if (ar != null && !hasPackageFlag( + ar.packageName, FLAG_HIDE_COMPILE_SDK)) { + mUnsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog( + AppWarnings.this, mUiContext, ar.info.applicationInfo); + mUnsupportedCompileSdkDialog.show(); + } + } + + /** + * Dismisses all warnings for the given package. + * <p> + * <strong>Note:</strong> Must be called on the UI thread. + * + * @param name the package for which warnings should be dismissed, or {@code null} to dismiss + * all warnings + */ + @UiThread + private void hideDialogsForPackageUiThread(String name) { + // Hides the "unsupported display" dialog if necessary. + if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals( + mUnsupportedDisplaySizeDialog.getPackageName()))) { + mUnsupportedDisplaySizeDialog.dismiss(); + mUnsupportedDisplaySizeDialog = null; + } + + // Hides the "unsupported compile SDK" dialog if necessary. + if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals( + mUnsupportedCompileSdkDialog.getPackageName()))) { + mUnsupportedCompileSdkDialog.dismiss(); + mUnsupportedCompileSdkDialog = null; + } + } + + /** + * Returns the value of the flag for the given package. + * + * @param name the package from which to retrieve the flag + * @param flag the bitmask for the flag to retrieve + * @return {@code true} if the flag is enabled, {@code false} otherwise + */ + boolean hasPackageFlag(String name, int flag) { + return (getPackageFlags(name) & flag) == flag; + } + + /** + * Sets the flag for the given package to the specified value. + * + * @param name the package on which to set the flag + * @param flag the bitmask for flag to set + * @param enabled the value to set for the flag + */ + void setPackageFlag(String name, int flag, boolean enabled) { + synchronized (mPackageFlags) { + final int curFlags = getPackageFlags(name); + final int newFlags = enabled ? (curFlags & ~flag) : (curFlags | flag); + if (curFlags != newFlags) { + if (newFlags != 0) { + mPackageFlags.put(name, newFlags); + } else { + mPackageFlags.remove(name); + } + mAmsHandler.scheduleWrite(); + } + } + } + + /** + * Returns the bitmask of flags set for the specified package. + */ + private int getPackageFlags(String name) { + synchronized (mPackageFlags) { + return mPackageFlags.getOrDefault(name, 0); + } + } + + /** + * Handles messages on the system process UI thread. + */ + private final class UiHandler extends Handler { + private static final int MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 1; + private static final int MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 2; + private static final int MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG = 3; + private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4; + + public UiHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG: { + final ActivityRecord ar = (ActivityRecord) msg.obj; + showUnsupportedDisplaySizeDialogUiThread(ar); + } break; + case MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG: { + hideUnsupportedDisplaySizeDialogUiThread(); + } break; + case MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG: { + final ActivityRecord ar = (ActivityRecord) msg.obj; + showUnsupportedCompileSdkDialogUiThread(ar); + } break; + case MSG_HIDE_DIALOGS_FOR_PACKAGE: { + final String name = (String) msg.obj; + hideDialogsForPackageUiThread(name); + } break; + } + } + + public void showUnsupportedDisplaySizeDialog(ActivityRecord r) { + removeMessages(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG); + obtainMessage(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG, r).sendToTarget(); + } + + public void hideUnsupportedDisplaySizeDialog() { + removeMessages(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG); + sendEmptyMessage(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG); + } + + public void showUnsupportedCompileSdkDialog(ActivityRecord r) { + removeMessages(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG); + obtainMessage(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG, r).sendToTarget(); + } + + public void hideDialogsForPackage(String name) { + obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, name).sendToTarget(); + } + } + + /** + * Handles messages on the ActivityManagerService thread. + */ + private final class ConfigHandler extends Handler { + private static final int MSG_WRITE = ActivityManagerService.FIRST_COMPAT_MODE_MSG; + + private static final int DELAY_MSG_WRITE = 10000; + + public ConfigHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_WRITE: + writeConfigToFileAmsThread(); + break; + } + } + + public void scheduleWrite() { + removeMessages(MSG_WRITE); + sendEmptyMessageDelayed(MSG_WRITE, DELAY_MSG_WRITE); + } + } + + /** + * Writes the configuration file. + * <p> + * <strong>Note:</strong> Should be called from the ActivityManagerService thread unless you + * don't care where you're doing I/O operations. But you <i>do</i> care, don't you? + */ + private void writeConfigToFileAmsThread() { + // Create a shallow copy so that we don't have to synchronize on config. + final HashMap<String, Integer> packageFlags; + synchronized (mPackageFlags) { + packageFlags = new HashMap<>(mPackageFlags); + } + + FileOutputStream fos = null; + try { + fos = mConfigFile.startWrite(); + + final XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, StandardCharsets.UTF_8.name()); + out.startDocument(null, true); + out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + out.startTag(null, "packages"); + + for (Map.Entry<String, Integer> entry : packageFlags.entrySet()) { + String pkg = entry.getKey(); + int mode = entry.getValue(); + if (mode == 0) { + continue; + } + out.startTag(null, "package"); + out.attribute(null, "name", pkg); + out.attribute(null, "flags", Integer.toString(mode)); + out.endTag(null, "package"); + } + + out.endTag(null, "packages"); + out.endDocument(); + + mConfigFile.finishWrite(fos); + } catch (java.io.IOException e1) { + Slog.w(TAG, "Error writing package metadata", e1); + if (fos != null) { + mConfigFile.failWrite(fos); + } + } + } + + /** + * Reads the configuration file and populates the package flags. + * <p> + * <strong>Note:</strong> Must be called from the constructor (and thus on the + * ActivityManagerService thread) since we don't synchronize on config. + */ + private void readConfigFromFileAmsThread() { + FileInputStream fis = null; + + try { + fis = mConfigFile.openRead(); + + final XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, StandardCharsets.UTF_8.name()); + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.START_TAG && + eventType != XmlPullParser.END_DOCUMENT) { + eventType = parser.next(); + } + if (eventType == XmlPullParser.END_DOCUMENT) { + return; + } + + String tagName = parser.getName(); + if ("packages".equals(tagName)) { + eventType = parser.next(); + do { + if (eventType == XmlPullParser.START_TAG) { + tagName = parser.getName(); + if (parser.getDepth() == 2) { + if ("package".equals(tagName)) { + final String name = parser.getAttributeValue(null, "name"); + if (name != null) { + final String flags = parser.getAttributeValue( + null, "flags"); + int flagsInt = 0; + if (flags != null) { + try { + flagsInt = Integer.parseInt(flags); + } catch (NumberFormatException e) { + } + } + mPackageFlags.put(name, flagsInt); + } + } + } + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + } + } catch (XmlPullParserException e) { + Slog.w(TAG, "Error reading package metadata", e); + } catch (java.io.IOException e) { + if (fis != null) Slog.w(TAG, "Error reading package metadata", e); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (java.io.IOException e1) { + } + } + } + } +} diff --git a/com/android/server/am/ClientLifecycleManager.java b/com/android/server/am/ClientLifecycleManager.java new file mode 100644 index 00000000..c04d103c --- /dev/null +++ b/com/android/server/am/ClientLifecycleManager.java @@ -0,0 +1,120 @@ +/* + * 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 com.android.server.am; + +import android.annotation.NonNull; +import android.app.IApplicationThread; +import android.app.servertransaction.ClientTransaction; +import android.app.servertransaction.ClientTransactionItem; +import android.app.servertransaction.ActivityLifecycleItem; +import android.os.IBinder; +import android.os.RemoteException; + +/** + * Class that is able to combine multiple client lifecycle transition requests and/or callbacks, + * and execute them as a single transaction. + * + * @see ClientTransaction + */ +class ClientLifecycleManager { + // TODO(lifecycler): Implement building transactions or global transaction. + // TODO(lifecycler): Use object pools for transactions and transaction items. + + /** + * Schedule a transaction, which may consist of multiple callbacks and a lifecycle request. + * @param transaction A sequence of client transaction items. + * @throws RemoteException + * + * @see ClientTransaction + */ + void scheduleTransaction(ClientTransaction transaction) throws RemoteException { + transaction.schedule(); + } + + /** + * Schedule a single lifecycle request or callback to client activity. + * @param client Target client. + * @param activityToken Target activity token. + * @param stateRequest A request to move target activity to a desired lifecycle state. + * @throws RemoteException + * + * @see ClientTransactionItem + */ + void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken, + @NonNull ActivityLifecycleItem stateRequest) throws RemoteException { + final ClientTransaction clientTransaction = transactionWithState(client, activityToken, + stateRequest); + scheduleTransaction(clientTransaction); + } + + /** + * Schedule a single callback delivery to client activity. + * @param client Target client. + * @param activityToken Target activity token. + * @param callback A request to deliver a callback. + * @throws RemoteException + * + * @see ClientTransactionItem + */ + void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken, + @NonNull ClientTransactionItem callback) throws RemoteException { + final ClientTransaction clientTransaction = transactionWithCallback(client, activityToken, + callback); + scheduleTransaction(clientTransaction); + } + + /** + * Schedule a single callback delivery to client application. + * @param client Target client. + * @param callback A request to deliver a callback. + * @throws RemoteException + * + * @see ClientTransactionItem + */ + void scheduleTransaction(@NonNull IApplicationThread client, + @NonNull ClientTransactionItem callback) throws RemoteException { + final ClientTransaction clientTransaction = transactionWithCallback(client, + null /* activityToken */, callback); + scheduleTransaction(clientTransaction); + } + + /** + * @return A new instance of {@link ClientTransaction} with a single lifecycle state request. + * + * @see ClientTransaction + * @see ClientTransactionItem + */ + private static ClientTransaction transactionWithState(@NonNull IApplicationThread client, + @NonNull IBinder activityToken, @NonNull ActivityLifecycleItem stateRequest) { + final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken); + clientTransaction.setLifecycleStateRequest(stateRequest); + return clientTransaction; + } + + /** + * @return A new instance of {@link ClientTransaction} with a single callback invocation. + * + * @see ClientTransaction + * @see ClientTransactionItem + */ + private static ClientTransaction transactionWithCallback(@NonNull IApplicationThread client, + IBinder activityToken, @NonNull ClientTransactionItem callback) { + final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken); + clientTransaction.addCallback(callback); + return clientTransaction; + } +} diff --git a/com/android/server/am/CompatModePackages.java b/com/android/server/am/CompatModePackages.java index bfc04565..65c4a421 100644 --- a/com/android/server/am/CompatModePackages.java +++ b/com/android/server/am/CompatModePackages.java @@ -58,8 +58,6 @@ public final class CompatModePackages { public static final int COMPAT_FLAG_DONT_ASK = 1<<0; // Compatibility state: compatibility mode is enabled. public static final int COMPAT_FLAG_ENABLED = 1<<1; - // Unsupported zoom state: don't warn the user about unsupported zoom mode. - public static final int UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY = 1<<2; private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>(); @@ -233,10 +231,6 @@ public final class CompatModePackages { return (getPackageFlags(packageName)&COMPAT_FLAG_DONT_ASK) == 0; } - public boolean getPackageNotifyUnsupportedZoomLocked(String packageName) { - return (getPackageFlags(packageName)&UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) == 0; - } - public void setFrontActivityAskCompatModeLocked(boolean ask) { ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(); if (r != null) { @@ -245,22 +239,12 @@ public final class CompatModePackages { } public void setPackageAskCompatModeLocked(String packageName, boolean ask) { - int curFlags = getPackageFlags(packageName); - int newFlags = ask ? (curFlags&~COMPAT_FLAG_DONT_ASK) : (curFlags|COMPAT_FLAG_DONT_ASK); - if (curFlags != newFlags) { - if (newFlags != 0) { - mPackages.put(packageName, newFlags); - } else { - mPackages.remove(packageName); - } - scheduleWrite(); - } + setPackageFlagLocked(packageName, COMPAT_FLAG_DONT_ASK, ask); } - public void setPackageNotifyUnsupportedZoomLocked(String packageName, boolean notify) { + private void setPackageFlagLocked(String packageName, int flag, boolean set) { final int curFlags = getPackageFlags(packageName); - final int newFlags = notify ? (curFlags&~UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) : - (curFlags|UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY); + final int newFlags = set ? (curFlags & ~flag) : (curFlags | flag); if (curFlags != newFlags) { if (newFlags != 0) { mPackages.put(packageName, newFlags); diff --git a/com/android/server/am/ConnectionRecord.java b/com/android/server/am/ConnectionRecord.java index 9b7a0c43..6df283ca 100644 --- a/com/android/server/am/ConnectionRecord.java +++ b/com/android/server/am/ConnectionRecord.java @@ -19,6 +19,9 @@ package com.android.server.am; import android.app.IServiceConnection; import android.app.PendingIntent; import android.content.Context; +import android.util.proto.ProtoOutputStream; + +import com.android.server.am.proto.ConnectionRecordProto; import java.io.PrintWriter; @@ -119,4 +122,70 @@ final class ConnectionRecord { sb.append('}'); return stringName = sb.toString(); } + + public void writeToProto(ProtoOutputStream proto, long fieldId) { + if (binding == null) return; // if binding is null, don't write data, something is wrong. + long token = proto.start(fieldId); + proto.write(ConnectionRecordProto.HEX_HASH, + Integer.toHexString(System.identityHashCode(this))); + if (binding.client != null) { + proto.write(ConnectionRecordProto.USER_ID, binding.client.userId); + } + if ((flags&Context.BIND_AUTO_CREATE) != 0) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.AUTO_CREATE); + } + if ((flags&Context.BIND_DEBUG_UNBIND) != 0) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.DEBUG_UNBIND); + } + if ((flags&Context.BIND_NOT_FOREGROUND) != 0) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.NOT_FG); + } + if ((flags&Context.BIND_IMPORTANT_BACKGROUND) != 0) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.IMPORTANT_BG); + } + if ((flags&Context.BIND_ABOVE_CLIENT) != 0) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ABOVE_CLIENT); + } + if ((flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ALLOW_OOM_MANAGEMENT); + } + if ((flags&Context.BIND_WAIVE_PRIORITY) != 0) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.WAIVE_PRIORITY); + } + if ((flags&Context.BIND_IMPORTANT) != 0) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.IMPORTANT); + } + if ((flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ADJUST_WITH_ACTIVITY); + } + if ((flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE) != 0) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.FG_SERVICE_WHILE_WAKE); + } + if ((flags&Context.BIND_FOREGROUND_SERVICE) != 0) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.FG_SERVICE); + } + if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.TREAT_LIKE_ACTIVITY); + } + if ((flags&Context.BIND_VISIBLE) != 0) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.VISIBLE); + } + if ((flags&Context.BIND_SHOWING_UI) != 0) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.SHOWING_UI); + } + if ((flags&Context.BIND_NOT_VISIBLE) != 0) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.NOT_VISIBLE); + } + if (serviceDead) { + proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.DEAD); + } + if (binding.service != null) { + proto.write(ConnectionRecordProto.SERVICE_NAME, binding.service.shortName); + } + if (conn != null) { + proto.write(ConnectionRecordProto.CONN_HEX_HASH, + Integer.toHexString(System.identityHashCode(conn.asBinder()))); + } + proto.end(token); + } } diff --git a/com/android/server/am/IntentBindRecord.java b/com/android/server/am/IntentBindRecord.java index be290e95..01ce64c6 100644 --- a/com/android/server/am/IntentBindRecord.java +++ b/com/android/server/am/IntentBindRecord.java @@ -21,6 +21,10 @@ import android.content.Intent; import android.os.IBinder; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.proto.ProtoOutputStream; + +import com.android.server.am.proto.AppBindRecordProto; +import com.android.server.am.proto.IntentBindRecordProto; import java.io.PrintWriter; @@ -106,4 +110,32 @@ final class IntentBindRecord { sb.append('}'); return stringName = sb.toString(); } + + public void writeToProto(ProtoOutputStream proto, long fieldId) { + long token = proto.start(fieldId); + proto.write(IntentBindRecordProto.HEX_HASH, + Integer.toHexString(System.identityHashCode(this))); + proto.write(IntentBindRecordProto.IS_CREATE, + (collectFlags()&Context.BIND_AUTO_CREATE) != 0); + if (intent != null) { + intent.getIntent().writeToProto(proto, + IntentBindRecordProto.INTENT, false, true, false, false); + } + if (binder != null) { + proto.write(IntentBindRecordProto.BINDER, binder.toString()); + } + proto.write(IntentBindRecordProto.REQUESTED, requested); + proto.write(IntentBindRecordProto.RECEIVED, received); + proto.write(IntentBindRecordProto.HAS_BOUND, hasBound); + proto.write(IntentBindRecordProto.DO_REBIND, doRebind); + + final int N = apps.size(); + for (int i=0; i<N; i++) { + AppBindRecord a = apps.valueAt(i); + if (a != null) { + a.writeToProto(proto, IntentBindRecordProto.APPS); + } + } + proto.end(token); + } } diff --git a/com/android/server/am/KeyguardController.java b/com/android/server/am/KeyguardController.java index 76b46796..35f4f253 100644 --- a/com/android/server/am/KeyguardController.java +++ b/com/android/server/am/KeyguardController.java @@ -19,9 +19,9 @@ package com.android.server.am; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; -import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; -import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; -import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; +import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; +import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; +import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; @@ -41,8 +41,11 @@ import android.os.RemoteException; import android.os.Trace; import android.util.Slog; import android.util.proto.ProtoOutputStream; + import com.android.internal.policy.IKeyguardDismissCallback; +import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.WindowManagerService; + import java.io.PrintWriter; /** @@ -118,7 +121,7 @@ class KeyguardController { /** * Called when Keyguard is going away. * - * @param flags See {@link android.view.WindowManagerPolicy#KEYGUARD_GOING_AWAY_FLAG_TO_SHADE} + * @param flags See {@link WindowManagerPolicy#KEYGUARD_GOING_AWAY_FLAG_TO_SHADE} * etc. */ void keyguardGoingAway(int flags) { diff --git a/com/android/server/am/LaunchingActivityPositioner.java b/com/android/server/am/LaunchingActivityPositioner.java index d5f9cf3a..793884d0 100644 --- a/com/android/server/am/LaunchingActivityPositioner.java +++ b/com/android/server/am/LaunchingActivityPositioner.java @@ -47,10 +47,10 @@ public class LaunchingActivityPositioner implements LaunchingBoundsPositioner { return RESULT_SKIP; } - final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds()); + final Rect bounds = options.getLaunchBounds(); // Bounds weren't valid. - if (bounds == null) { + if (bounds == null || bounds.isEmpty()) { return RESULT_SKIP; } diff --git a/com/android/server/am/LaunchingBoundsController.java b/com/android/server/am/LaunchingBoundsController.java index c762f7f4..5aa7f58f 100644 --- a/com/android/server/am/LaunchingBoundsController.java +++ b/com/android/server/am/LaunchingBoundsController.java @@ -101,8 +101,12 @@ class LaunchingBoundsController { * @return {@code true} if bounds were set on the task. {@code false} otherwise. */ boolean layoutTask(TaskRecord task, WindowLayout layout) { - calculateBounds(task, layout, null /*activity*/, null /*source*/, null /*options*/, - mTmpRect); + return layoutTask(task, layout, null /*activity*/, null /*source*/, null /*options*/); + } + + boolean layoutTask(TaskRecord task, WindowLayout layout, ActivityRecord activity, + ActivityRecord source, ActivityOptions options) { + calculateBounds(task, layout, activity, source, options, mTmpRect); if (mTmpRect.isEmpty()) { return false; diff --git a/com/android/server/am/LaunchingTaskPositioner.java b/com/android/server/am/LaunchingTaskPositioner.java index c958fcac..d89568e2 100644 --- a/com/android/server/am/LaunchingTaskPositioner.java +++ b/com/android/server/am/LaunchingTaskPositioner.java @@ -88,7 +88,7 @@ class LaunchingTaskPositioner implements LaunchingBoundsController.LaunchingBoun final ArrayList<TaskRecord> tasks = task.getStack().getAllTasks(); - updateAvailableRect(task, mAvailableRect); + mAvailableRect.set(task.getParent().getBounds()); if (layout == null) { positionCenter(tasks, mAvailableRect, getFreeformWidth(mAvailableRect), @@ -123,17 +123,6 @@ class LaunchingTaskPositioner implements LaunchingBoundsController.LaunchingBoun return RESULT_CONTINUE; } - private void updateAvailableRect(TaskRecord task, Rect availableRect) { - final Rect stackBounds = task.getStack().mBounds; - - if (stackBounds != null) { - availableRect.set(stackBounds); - } else { - task.getStack().getDisplay().mDisplay.getSize(mDisplaySize); - availableRect.set(0, 0, mDisplaySize.x, mDisplaySize.y); - } - } - @VisibleForTesting static int getFreeformStartLeft(Rect bounds) { return bounds.left + bounds.width() / MARGIN_SIZE_DENOMINATOR; @@ -294,9 +283,9 @@ class LaunchingTaskPositioner implements LaunchingBoundsController.LaunchingBoun private static boolean boundsConflict(Rect proposal, ArrayList<TaskRecord> tasks) { for (int i = tasks.size() - 1; i >= 0; i--) { - TaskRecord task = tasks.get(i); - if (!task.mActivities.isEmpty() && task.mBounds != null) { - Rect bounds = task.mBounds; + final TaskRecord task = tasks.get(i); + if (!task.mActivities.isEmpty() && !task.matchParentBounds()) { + final Rect bounds = task.getOverrideBounds(); if (closeLeftTopCorner(proposal, bounds) || closeRightTopCorner(proposal, bounds) || closeLeftBottomCorner(proposal, bounds) || closeRightBottomCorner(proposal, bounds)) { diff --git a/com/android/server/am/LockTaskController.java b/com/android/server/am/LockTaskController.java index e87b4e63..d77e1a20 100644 --- a/com/android/server/am/LockTaskController.java +++ b/com/android/server/am/LockTaskController.java @@ -250,7 +250,24 @@ public class LockTaskController { } /** - * @return whether the requested task is allowed to be launched. + * @return whether the requested task is allowed to be locked (either whitelisted, or declares + * lockTaskMode="always" in the manifest). + */ + boolean isTaskWhitelisted(TaskRecord task) { + switch(task.mLockTaskAuth) { + case LOCK_TASK_AUTH_WHITELISTED: + case LOCK_TASK_AUTH_LAUNCHABLE: + case LOCK_TASK_AUTH_LAUNCHABLE_PRIV: + return true; + case LOCK_TASK_AUTH_PINNABLE: + case LOCK_TASK_AUTH_DONT_LOCK: + default: + return false; + } + } + + /** + * @return whether the requested task is disallowed to be launched. */ boolean isLockTaskModeViolation(TaskRecord task) { return isLockTaskModeViolation(task, false); @@ -258,7 +275,7 @@ public class LockTaskController { /** * @param isNewClearTask whether the task would be cleared as part of the operation. - * @return whether the requested task is allowed to be launched. + * @return whether the requested task is disallowed to be launched. */ boolean isLockTaskModeViolation(TaskRecord task, boolean isNewClearTask) { if (isLockTaskModeViolationInternal(task, isNewClearTask)) { @@ -275,21 +292,18 @@ public class LockTaskController { // If the task is already at the top and won't be cleared, then allow the operation return false; } - final int lockTaskAuth = task.mLockTaskAuth; - switch (lockTaskAuth) { - case LOCK_TASK_AUTH_DONT_LOCK: - return !mLockTaskModeTasks.isEmpty(); - case LOCK_TASK_AUTH_LAUNCHABLE_PRIV: - case LOCK_TASK_AUTH_LAUNCHABLE: - case LOCK_TASK_AUTH_WHITELISTED: - return false; - case LOCK_TASK_AUTH_PINNABLE: - // Pinnable tasks can't be launched on top of locktask tasks. - return !mLockTaskModeTasks.isEmpty(); - default: - Slog.w(TAG, "isLockTaskModeViolation: invalid lockTaskAuth value=" + lockTaskAuth); - return true; + + // Allow recents activity if enabled by policy + if (task.isActivityTypeRecents() && isRecentsAllowed(task.userId)) { + return false; } + + return !(isTaskWhitelisted(task) || mLockTaskModeTasks.isEmpty()); + } + + private boolean isRecentsAllowed(int userId) { + return (getLockTaskFeaturesForUser(userId) + & DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS) != 0; } /** @@ -491,6 +505,7 @@ public class LockTaskController { } if (mLockTaskModeTasks.isEmpty()) { + mSupervisor.mRecentTasks.onLockTaskModeStateChanged(lockTaskModeState, task.userId); // Start lock task on the handler thread mHandler.post(() -> performStartLockTask( task.intent.getComponent().getPackageName(), diff --git a/com/android/server/am/ProcessList.java b/com/android/server/am/ProcessList.java index 7810c5e4..6fb3dbb7 100644 --- a/com/android/server/am/ProcessList.java +++ b/com/android/server/am/ProcessList.java @@ -40,7 +40,7 @@ import android.view.Display; /** * Activity manager code dealing with processes. */ -final class ProcessList { +public final class ProcessList { private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM; // The minimum time we allow between crashes, for us to consider this @@ -400,6 +400,9 @@ final class ProcessList { case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: procState = "CACC"; break; + case ActivityManager.PROCESS_STATE_CACHED_RECENT: + procState = "CRE "; + break; case ActivityManager.PROCESS_STATE_CACHED_EMPTY: procState = "CEM "; break; @@ -494,6 +497,7 @@ final class ProcessList { PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_RECENT PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_EMPTY }; @@ -515,6 +519,7 @@ final class ProcessList { PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY }; @@ -536,6 +541,7 @@ final class ProcessList { PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY }; @@ -557,6 +563,7 @@ final class ProcessList { PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY }; @@ -578,6 +585,7 @@ final class ProcessList { PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY }; diff --git a/com/android/server/am/ProcessRecord.java b/com/android/server/am/ProcessRecord.java index e8477231..9d3c2ae3 100644 --- a/com/android/server/am/ProcessRecord.java +++ b/com/android/server/am/ProcessRecord.java @@ -164,6 +164,8 @@ final class ProcessRecord { // all activities running in the process final ArrayList<ActivityRecord> activities = new ArrayList<>(); + // any tasks this process had run root activities in + final ArrayList<TaskRecord> recentTasks = new ArrayList<>(); // all ServiceRecord running in this process final ArraySet<ServiceRecord> services = new ArraySet<>(); // services that are currently executing code (need to remain foreground). @@ -396,6 +398,12 @@ final class ProcessRecord { pw.print(prefix); pw.print(" - "); pw.println(activities.get(i)); } } + if (recentTasks.size() > 0) { + pw.print(prefix); pw.println("Recent Tasks:"); + for (int i=0; i<recentTasks.size(); i++) { + pw.print(prefix); pw.print(" - "); pw.println(recentTasks.get(i)); + } + } if (services.size() > 0) { pw.print(prefix); pw.println("Services:"); for (int i=0; i<services.size(); i++) { @@ -512,6 +520,13 @@ final class ProcessRecord { } } + public void clearRecentTasks() { + for (int i = recentTasks.size() - 1; i >= 0; i--) { + recentTasks.get(i).clearRootProcess(); + } + recentTasks.clear(); + } + /** * This method returns true if any of the activities within the process record are interesting * to the user. See HistoryRecord.isInterestingToUserLocked() diff --git a/com/android/server/am/ProviderMap.java b/com/android/server/am/ProviderMap.java index 32d03dae..8a905f8c 100644 --- a/com/android/server/am/ProviderMap.java +++ b/com/android/server/am/ProviderMap.java @@ -28,6 +28,7 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -322,8 +323,7 @@ public final class ProviderMap { return needSep; } - protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args, - int opti, boolean dumpAll) { + private ArrayList<ContentProviderRecord> getProvidersForName(String name) { ArrayList<ContentProviderRecord> allProviders = new ArrayList<ContentProviderRecord>(); ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>(); @@ -365,6 +365,12 @@ public final class ProviderMap { } } } + return providers; + } + + protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args, + int opti, boolean dumpAll) { + ArrayList<ContentProviderRecord> providers = getProvidersForName(name); if (providers.size() <= 0) { return false; @@ -417,6 +423,33 @@ public final class ProviderMap { } /** + * Similar to the dumpProvider, but only dumps the first matching provider. + * The provider is responsible for dumping as proto. + */ + protected boolean dumpProviderProto(FileDescriptor fd, PrintWriter pw, String name, + String[] args) { + //add back the --proto arg, which was stripped out by PriorityDump + String[] newArgs = Arrays.copyOf(args, args.length + 1); + newArgs[args.length] = "--proto"; + + ArrayList<ContentProviderRecord> providers = getProvidersForName(name); + + if (providers.size() <= 0) { + return false; + } + + // Only dump the first provider, since we are dumping in proto format + for (int i = 0; i < providers.size(); i++) { + final ContentProviderRecord r = providers.get(i); + if (r.proc != null && r.proc.thread != null) { + dumpToTransferPipe(null, fd, pw, r, newArgs); + return true; + } + } + return false; + } + + /** * Invokes IApplicationThread.dumpProvider() on the thread of the specified provider without * any meta string (e.g., provider info, indentation) written to the file descriptor. */ diff --git a/com/android/server/am/RecentTasks.java b/com/android/server/am/RecentTasks.java index d35c37b5..abb296e9 100644 --- a/com/android/server/am/RecentTasks.java +++ b/com/android/server/am/RecentTasks.java @@ -80,6 +80,20 @@ import java.util.concurrent.TimeUnit; /** * Class for managing the recent tasks list. The list is ordered by most recent (index 0) to the * least recent. + * + * The trimming logic can be boiled down to the following. For recent task list with a number of + * tasks, the visible tasks are an interleaving subset of tasks that would normally be presented to + * the user. Non-visible tasks are not considered for trimming. Of the visible tasks, only a + * sub-range are presented to the user, based on the device type, last task active time, or other + * task state. Tasks that are not in the visible range and are not returnable from the SystemUI + * (considering the back stack) are considered trimmable. If the device does not support recent + * tasks, then trimming is completely disabled. + * + * eg. + * L = [TTTTTTTTTTTTTTTTTTTTTTTTTT] // list of tasks + * [VVV VV VVVV V V V ] // Visible tasks + * [RRR RR XXXX X X X ] // Visible range tasks, eg. if the device only shows 5 tasks, + * // 'X' tasks are trimmed. */ class RecentTasks { private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM; @@ -488,6 +502,18 @@ class RecentTasks { } } + void onLockTaskModeStateChanged(int lockTaskModeState, int userId) { + if (lockTaskModeState != ActivityManager.LOCK_TASK_MODE_LOCKED) { + return; + } + for (int i = mTasks.size() - 1; i >= 0; --i) { + final TaskRecord tr = mTasks.get(i); + if (tr.userId == userId && !mService.mLockTaskController.isTaskWhitelisted(tr)) { + remove(tr); + } + } + } + void removeTasksByPackageName(String packageName, int userId) { for (int i = mTasks.size() - 1; i >= 0; --i) { final TaskRecord tr = mTasks.get(i); @@ -496,7 +522,8 @@ class RecentTasks { if (tr.userId != userId) return; if (!taskPackageName.equals(packageName)) return; - mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS); + mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS, + "remove-package-task"); } } @@ -513,7 +540,7 @@ class RecentTasks { && (filterByClasses == null || filterByClasses.contains(cn.getClassName())); if (sameComponent) { mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, - REMOVE_FROM_RECENTS); + REMOVE_FROM_RECENTS, "disabled-package"); } } } @@ -1001,12 +1028,13 @@ class RecentTasks { continue; } else { numVisibleTasks++; - if (isInVisibleRange(task, numVisibleTasks)) { + if (isInVisibleRange(task, numVisibleTasks) || !isTrimmable(task)) { // Keep visible tasks in range i++; continue; } else { - // Fall through to trim visible tasks that are no longer in range + // Fall through to trim visible tasks that are no longer in range and + // trimmable if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming out-of-range visible task=" + task); } @@ -1122,6 +1150,28 @@ class RecentTasks { } /** + * @return whether the given task can be trimmed even if it is outside the visible range. + */ + protected boolean isTrimmable(TaskRecord task) { + final ActivityStack stack = task.getStack(); + final ActivityStack homeStack = mService.mStackSupervisor.mHomeStack; + + // No stack for task, just trim it + if (stack == null) { + return true; + } + + // Ignore tasks from different displays + if (stack.getDisplay() != homeStack.getDisplay()) { + return false; + } + + // Trim tasks that are in stacks that are behind the home stack + final ActivityDisplay display = stack.getDisplay(); + return display.getIndexOf(stack) < display.getIndexOf(homeStack); + } + + /** * If needed, remove oldest existing entries in recents that are for the same kind * of task as the given one. */ @@ -1426,9 +1476,6 @@ class RecentTasks { * Creates a new RecentTaskInfo from a TaskRecord. */ ActivityManager.RecentTaskInfo createRecentTaskInfo(TaskRecord tr) { - // Update the task description to reflect any changes in the task stack - tr.updateTaskDescription(); - // Compose the recent task info ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); rti.id = tr.getTopActivity() == null ? INVALID_TASK_ID : tr.taskId; @@ -1444,8 +1491,8 @@ class RecentTasks { rti.affiliatedTaskId = tr.mAffiliatedTaskId; rti.affiliatedTaskColor = tr.mAffiliatedTaskColor; rti.numActivities = 0; - if (tr.mBounds != null) { - rti.bounds = new Rect(tr.mBounds); + if (!tr.matchParentBounds()) { + rti.bounds = new Rect(tr.getOverrideBounds()); } rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreenWindowingMode(); rti.resizeMode = tr.mResizeMode; diff --git a/com/android/server/am/ServiceRecord.java b/com/android/server/am/ServiceRecord.java index 16995e50..b6eff003 100644 --- a/com/android/server/am/ServiceRecord.java +++ b/com/android/server/am/ServiceRecord.java @@ -19,6 +19,7 @@ package com.android.server.am; import com.android.internal.app.procstats.ServiceState; import com.android.internal.os.BatteryStatsImpl; import com.android.server.LocalServices; +import com.android.server.am.proto.ServiceRecordProto; import com.android.server.notification.NotificationManagerInternal; import android.app.INotificationManager; @@ -42,6 +43,8 @@ import android.provider.Settings; import android.util.ArrayMap; import android.util.Slog; import android.util.TimeUtils; +import android.util.proto.ProtoOutputStream; +import android.util.proto.ProtoUtils; import java.io.PrintWriter; import java.util.ArrayList; @@ -79,7 +82,7 @@ final class ServiceRecord extends Binder { final String permission;// permission needed to access service final boolean exported; // from ServiceInfo.exported final Runnable restarter; // used to schedule retries of starting the service - final long createTime; // when this service was created + final long createRealTime; // when this service was created final ArrayMap<Intent.FilterComparison, IntentBindRecord> bindings = new ArrayMap<Intent.FilterComparison, IntentBindRecord>(); // All active bindings to the service. @@ -103,7 +106,7 @@ final class ServiceRecord extends Binder { boolean startRequested; // someone explicitly called start? boolean delayedStop; // service has been stopped but is in a delayed start? boolean stopIfKilled; // last onStart() said to stop if service killed? - boolean callStart; // last onStart() has asked to alway be called on restart. + boolean callStart; // last onStart() has asked to always be called on restart. int executeNesting; // number of outstanding operations keeping foreground. boolean executeFg; // should we be executing in the foreground? long executingStart; // start time of last execute request. @@ -159,6 +162,27 @@ final class ServiceRecord extends Binder { } } + public void writeToProto(ProtoOutputStream proto, long fieldId, long now) { + long token = proto.start(fieldId); + proto.write(ServiceRecordProto.StartItemProto.ID, id); + ProtoUtils.toDuration(proto, + ServiceRecordProto.StartItemProto.DURATION, deliveredTime, now); + proto.write(ServiceRecordProto.StartItemProto.DELIVERY_COUNT, deliveryCount); + proto.write(ServiceRecordProto.StartItemProto.DONE_EXECUTING_COUNT, doneExecutingCount); + if (intent != null) { + intent.writeToProto(proto, ServiceRecordProto.StartItemProto.INTENT, true, true, + true, false); + } + if (neededGrants != null) { + neededGrants.writeToProto(proto, ServiceRecordProto.StartItemProto.NEEDED_GRANTS); + } + if (uriPermissions != null) { + uriPermissions.writeToProto(proto, + ServiceRecordProto.StartItemProto.URI_PERMISSIONS); + } + proto.end(token); + } + public String toString() { if (stringName != null) { return stringName; @@ -209,6 +233,117 @@ final class ServiceRecord extends Binder { } } + void writeToProto(ProtoOutputStream proto, long fieldId) { + long token = proto.start(fieldId); + proto.write(ServiceRecordProto.SHORT_NAME, this.shortName); + proto.write(ServiceRecordProto.HEX_HASH, + Integer.toHexString(System.identityHashCode(this))); + proto.write(ServiceRecordProto.IS_RUNNING, app != null); + if (app != null) { + proto.write(ServiceRecordProto.PID, app.pid); + } + if (intent != null) { + intent.getIntent().writeToProto(proto, ServiceRecordProto.INTENT, false, true, false, + true); + } + proto.write(ServiceRecordProto.PACKAGE_NAME, packageName); + proto.write(ServiceRecordProto.PROCESS_NAME, processName); + proto.write(ServiceRecordProto.PERMISSION, permission); + + long now = SystemClock.uptimeMillis(); + long nowReal = SystemClock.elapsedRealtime(); + if (appInfo != null) { + long appInfoToken = proto.start(ServiceRecordProto.APPINFO); + proto.write(ServiceRecordProto.AppInfo.BASE_DIR, appInfo.sourceDir); + if (!Objects.equals(appInfo.sourceDir, appInfo.publicSourceDir)) { + proto.write(ServiceRecordProto.AppInfo.RES_DIR, appInfo.publicSourceDir); + } + proto.write(ServiceRecordProto.AppInfo.DATA_DIR, appInfo.dataDir); + proto.end(appInfoToken); + } + if (app != null) { + app.writeToProto(proto, ServiceRecordProto.APP); + } + if (isolatedProc != null) { + isolatedProc.writeToProto(proto, ServiceRecordProto.ISOLATED_PROC); + } + proto.write(ServiceRecordProto.WHITELIST_MANAGER, whitelistManager); + proto.write(ServiceRecordProto.DELAYED, delayed); + if (isForeground || foregroundId != 0) { + long fgToken = proto.start(ServiceRecordProto.FOREGROUND); + proto.write(ServiceRecordProto.Foreground.ID, foregroundId); + foregroundNoti.writeToProto(proto, ServiceRecordProto.Foreground.NOTIFICATION); + proto.end(fgToken); + } + ProtoUtils.toDuration(proto, ServiceRecordProto.CREATE_REAL_TIME, createRealTime, nowReal); + ProtoUtils.toDuration(proto, + ServiceRecordProto.STARTING_BG_TIMEOUT, startingBgTimeout, now); + ProtoUtils.toDuration(proto, ServiceRecordProto.LAST_ACTIVITY_TIME, lastActivity, now); + ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now); + proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg); + + if (startRequested || delayedStop || lastStartId != 0) { + long startToken = proto.start(ServiceRecordProto.START); + proto.write(ServiceRecordProto.Start.START_REQUESTED, startRequested); + proto.write(ServiceRecordProto.Start.DELAYED_STOP, delayedStop); + proto.write(ServiceRecordProto.Start.STOP_IF_KILLED, stopIfKilled); + proto.write(ServiceRecordProto.Start.LAST_START_ID, lastStartId); + proto.end(startToken); + } + + if (executeNesting != 0) { + long executNestingToken = proto.start(ServiceRecordProto.EXECUTE); + proto.write(ServiceRecordProto.ExecuteNesting.EXECUTE_NESTING, executeNesting); + proto.write(ServiceRecordProto.ExecuteNesting.EXECUTE_FG, executeFg); + ProtoUtils.toDuration(proto, + ServiceRecordProto.ExecuteNesting.EXECUTING_START, executingStart, now); + proto.end(executNestingToken); + } + if (destroying || destroyTime != 0) { + ProtoUtils.toDuration(proto, ServiceRecordProto.DESTORY_TIME, destroyTime, now); + } + if (crashCount != 0 || restartCount != 0 || restartDelay != 0 || nextRestartTime != 0) { + long crashToken = proto.start(ServiceRecordProto.CRASH); + proto.write(ServiceRecordProto.Crash.RESTART_COUNT, restartCount); + ProtoUtils.toDuration(proto, ServiceRecordProto.Crash.RESTART_DELAY, restartDelay, now); + ProtoUtils.toDuration(proto, + ServiceRecordProto.Crash.NEXT_RESTART_TIME, nextRestartTime, now); + proto.write(ServiceRecordProto.Crash.CRASH_COUNT, crashCount); + proto.end(crashToken); + } + + if (deliveredStarts.size() > 0) { + final int N = deliveredStarts.size(); + for (int i = 0; i < N; i++) { + deliveredStarts.get(i).writeToProto(proto, + ServiceRecordProto.DELIVERED_STARTS, now); + } + } + if (pendingStarts.size() > 0) { + final int N = pendingStarts.size(); + for (int i = 0; i < N; i++) { + pendingStarts.get(i).writeToProto(proto, ServiceRecordProto.PENDING_STARTS, now); + } + } + if (bindings.size() > 0) { + final int N = bindings.size(); + for (int i=0; i<N; i++) { + IntentBindRecord b = bindings.valueAt(i); + b.writeToProto(proto, ServiceRecordProto.BINDINGS); + } + } + if (connections.size() > 0) { + final int N = connections.size(); + for (int conni=0; conni<N; conni++) { + ArrayList<ConnectionRecord> c = connections.valueAt(conni); + for (int i=0; i<c.size(); i++) { + c.get(i).writeToProto(proto, ServiceRecordProto.CONNECTIONS); + } + } + } + proto.end(token); + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("intent={"); pw.print(intent.getIntent().toShortString(false, true, false, true)); @@ -243,7 +378,7 @@ final class ServiceRecord extends Binder { pw.print(" foregroundNoti="); pw.println(foregroundNoti); } pw.print(prefix); pw.print("createTime="); - TimeUtils.formatDuration(createTime, nowReal, pw); + TimeUtils.formatDuration(createRealTime, nowReal, pw); pw.print(" startingBgTimeout="); TimeUtils.formatDuration(startingBgTimeout, now, pw); pw.println(); @@ -329,7 +464,7 @@ final class ServiceRecord extends Binder { permission = sInfo.permission; exported = sInfo.exported; this.restarter = restarter; - createTime = SystemClock.elapsedRealtime(); + createRealTime = SystemClock.elapsedRealtime(); lastActivity = SystemClock.uptimeMillis(); userId = UserHandle.getUserId(appInfo.uid); createdFromFg = callerIsFg; diff --git a/com/android/server/am/TaskRecord.java b/com/android/server/am/TaskRecord.java index 949f51fe..83965ee4 100644 --- a/com/android/server/am/TaskRecord.java +++ b/com/android/server/am/TaskRecord.java @@ -19,9 +19,6 @@ package com.android.server.am; import static android.app.ActivityManager.RESIZE_MODE_FORCED; import static android.app.ActivityManager.RESIZE_MODE_SYSTEM; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; @@ -257,6 +254,11 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi /** Current stack. Setter must always be used to update the value. */ private ActivityStack mStack; + /** The process that had previously hosted the root activity of this task. + * Used to know that we should try harder to keep this process around, in case the + * user wants to return to it. */ + private ProcessRecord mRootProcess; + /** Takes on same value as first root activity */ boolean isPersistable = false; int maxRecents; @@ -288,11 +290,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi final ActivityManagerService mService; - // Whether or not this task covers the entire screen; by default tasks are fullscreen. - boolean mFullscreen = true; - - // Bounds of the Task. null for fullscreen tasks. - Rect mBounds = null; private final Rect mTmpStableBounds = new Rect(); private final Rect mTmpNonDecorBounds = new Rect(); private final Rect mTmpRect = new Rect(); @@ -475,68 +472,76 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } boolean resize(Rect bounds, int resizeMode, boolean preserveWindow, boolean deferResume) { - if (!isResizeable()) { - Slog.w(TAG, "resizeTask: task " + this + " not resizeable."); - return true; - } - - // If this is a forced resize, let it go through even if the bounds is not changing, - // as we might need a relayout due to surface size change (to/from fullscreen). - final boolean forced = (resizeMode & RESIZE_MODE_FORCED) != 0; - if (Objects.equals(mBounds, bounds) && !forced) { - // Nothing to do here... - return true; - } - bounds = validateBounds(bounds); + mService.mWindowManager.deferSurfaceLayout(); - if (mWindowContainerController == null) { - // Task doesn't exist in window manager yet (e.g. was restored from recents). - // All we can do for now is update the bounds so it can be used when the task is - // added to window manager. - updateOverrideConfiguration(bounds); - if (!inFreeformWindowingMode()) { - // re-restore the task so it can have the proper stack association. - mService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP); + try { + if (!isResizeable()) { + Slog.w(TAG, "resizeTask: task " + this + " not resizeable."); + return true; } - return true; - } - if (!canResizeToBounds(bounds)) { - throw new IllegalArgumentException("resizeTask: Can not resize task=" + this - + " to bounds=" + bounds + " resizeMode=" + mResizeMode); - } + // If this is a forced resize, let it go through even if the bounds is not changing, + // as we might need a relayout due to surface size change (to/from fullscreen). + final boolean forced = (resizeMode & RESIZE_MODE_FORCED) != 0; + if (equivalentOverrideBounds(bounds) && !forced) { + // Nothing to do here... + return true; + } - // Do not move the task to another stack here. - // This method assumes that the task is already placed in the right stack. - // we do not mess with that decision and we only do the resize! + if (mWindowContainerController == null) { + // Task doesn't exist in window manager yet (e.g. was restored from recents). + // All we can do for now is update the bounds so it can be used when the task is + // added to window manager. + updateOverrideConfiguration(bounds); + if (!inFreeformWindowingMode()) { + // re-restore the task so it can have the proper stack association. + mService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP); + } + return true; + } - Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + taskId); + if (!canResizeToBounds(bounds)) { + throw new IllegalArgumentException("resizeTask: Can not resize task=" + this + + " to bounds=" + bounds + " resizeMode=" + mResizeMode); + } - final boolean updatedConfig = updateOverrideConfiguration(bounds); - // This variable holds information whether the configuration didn't change in a significant - // way and the activity was kept the way it was. If it's false, it means the activity had - // to be relaunched due to configuration change. - boolean kept = true; - if (updatedConfig) { - final ActivityRecord r = topRunningActivityLocked(); - if (r != null && !deferResume) { - kept = r.ensureActivityConfigurationLocked(0 /* globalChanges */, preserveWindow); - mService.mStackSupervisor.ensureActivitiesVisibleLocked(r, 0, !PRESERVE_WINDOWS); - if (!kept) { - mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); + // Do not move the task to another stack here. + // This method assumes that the task is already placed in the right stack. + // we do not mess with that decision and we only do the resize! + + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + taskId); + + final boolean updatedConfig = updateOverrideConfiguration(bounds); + // This variable holds information whether the configuration didn't change in a significant + + // way and the activity was kept the way it was. If it's false, it means the activity + // had + // to be relaunched due to configuration change. + boolean kept = true; + if (updatedConfig) { + final ActivityRecord r = topRunningActivityLocked(); + if (r != null && !deferResume) { + kept = r.ensureActivityConfigurationLocked(0 /* globalChanges */, + preserveWindow); + mService.mStackSupervisor.ensureActivitiesVisibleLocked(r, 0, + !PRESERVE_WINDOWS); + if (!kept) { + mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); + } } } - } - mWindowContainerController.resize(mBounds, getOverrideConfiguration(), kept, forced); + mWindowContainerController.resize(kept, forced); - Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); - return kept; + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + return kept; + } finally { + mService.mWindowManager.continueSurfaceLayout(); + } } // TODO: Investigate combining with the resize() method above. void resizeWindowContainer() { - mWindowContainerController.resize(mBounds, getOverrideConfiguration(), false /* relayout */, - false /* forced */); + mWindowContainerController.resize(false /* relayout */, false /* forced */); } void getWindowContainerBounds(Rect bounds) { @@ -681,16 +686,17 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi // Make sure the task has the appropriate bounds/size for the stack it is in. final boolean toStackSplitScreenPrimary = toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; + final Rect configBounds = getOverrideBounds(); if ((toStackWindowingMode == WINDOWING_MODE_FULLSCREEN || toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) - && !Objects.equals(mBounds, toStack.mBounds)) { - kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow, + && !Objects.equals(configBounds, toStack.getOverrideBounds())) { + kept = resize(toStack.getOverrideBounds(), RESIZE_MODE_SYSTEM, !mightReplaceWindow, deferResume); } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) { Rect bounds = getLaunchBounds(); if (bounds == null) { mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null); - bounds = mBounds; + bounds = configBounds; } kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume); } else if (toStackSplitScreenPrimary || toStackWindowingMode == WINDOWING_MODE_PINNED) { @@ -699,7 +705,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi // mode mService.mStackSupervisor.moveRecentsStackToFront(reason); } - kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow, + kept = resize(toStack.getOverrideBounds(), RESIZE_MODE_SYSTEM, !mightReplaceWindow, deferResume); } } finally { @@ -962,6 +968,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi mService.notifyTaskPersisterLocked(this, false); } + clearRootProcess(); + // TODO: Use window container controller once tasks are better synced between AM and WM mService.mWindowManager.notifyTaskRemovedFromRecents(taskId, userId); } @@ -1303,7 +1311,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi * Completely remove all activities associated with an existing * task starting at a specified index. */ - final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately) { + final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately, + String reason) { int numActivities = mActivities.size(); for ( ; activityNdx < numActivities; ++activityNdx) { final ActivityRecord r = mActivities.get(activityNdx); @@ -1317,7 +1326,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi --activityNdx; --numActivities; } else if (mStack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, - "clear-task-index", false, pauseImmediately)) { + reason, false, pauseImmediately)) { --activityNdx; --numActivities; } @@ -1329,7 +1338,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi */ void performClearTaskLocked() { mReuseTask = true; - performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY); + performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY, "clear-task-all"); mReuseTask = false; } @@ -1400,9 +1409,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi return null; } - void removeTaskActivitiesLocked(boolean pauseImmediately) { + void removeTaskActivitiesLocked(boolean pauseImmediately, String reason) { // Just remove the entire task. - performClearTaskAtIndexLocked(0, pauseImmediately); + performClearTaskAtIndexLocked(0, pauseImmediately, reason); } String lockTaskAuthToString() { @@ -1494,8 +1503,10 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi return true; } final boolean landscape = bounds.width() > bounds.height(); + final Rect configBounds = getOverrideBounds(); if (mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION) { - return mBounds == null || landscape == (mBounds.width() > mBounds.height()); + return configBounds.isEmpty() + || landscape == (configBounds.width() > configBounds.height()); } return (mResizeMode != RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY || !landscape) && (mResizeMode != RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY || landscape); @@ -1616,6 +1627,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi final int effectiveRootIndex = findEffectiveRootIndex(); final ActivityRecord r = mActivities.get(effectiveRootIndex); setIntent(r); + + // Update the task description when the activities change + updateTaskDescription(); } void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException { @@ -1913,8 +1927,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi return; } + final Rect configBounds = getOverrideBounds(); if (adjustWidth) { - if (mBounds != null && bounds.right == mBounds.right) { + if (!configBounds.isEmpty() && bounds.right == configBounds.right) { bounds.left = bounds.right - minWidth; } else { // Either left bounds match, or neither match, or the previous bounds were @@ -1923,7 +1938,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } } if (adjustHeight) { - if (mBounds != null && bounds.bottom == mBounds.bottom) { + if (!configBounds.isEmpty() && bounds.bottom == configBounds.bottom) { bounds.top = bounds.bottom - minHeight; } else { // Either top bounds match, or neither match, or the previous bounds were @@ -1970,42 +1985,45 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi * @return True if the override configuration was updated. */ boolean updateOverrideConfiguration(Rect bounds, @Nullable Rect insetBounds) { - if (Objects.equals(mBounds, bounds)) { + if (equivalentOverrideBounds(bounds)) { return false; } + final Rect currentBounds = getOverrideBounds(); + mTmpConfig.setTo(getOverrideConfiguration()); - final boolean oldFullscreen = mFullscreen; final Configuration newConfig = getOverrideConfiguration(); - mFullscreen = bounds == null; + final boolean matchParentBounds = bounds == null || bounds.isEmpty(); final boolean persistBounds = getWindowConfiguration().persistTaskBounds(); - if (mFullscreen) { - if (mBounds != null && persistBounds) { - mLastNonFullscreenBounds = mBounds; + if (matchParentBounds) { + if (!currentBounds.isEmpty() && persistBounds) { + mLastNonFullscreenBounds = currentBounds; } - mBounds = null; + setBounds(null); newConfig.unset(); } else { mTmpRect.set(bounds); adjustForMinimalTaskDimensions(mTmpRect); - if (mBounds == null) { - mBounds = new Rect(mTmpRect); - } else { - mBounds.set(mTmpRect); - } + setBounds(mTmpRect); + if (mStack == null || persistBounds) { - mLastNonFullscreenBounds = mBounds; + mLastNonFullscreenBounds = getOverrideBounds(); } computeOverrideConfiguration(newConfig, mTmpRect, insetBounds, mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom); } onOverrideConfigurationChanged(newConfig); + return !mTmpConfig.equals(newConfig); + } - if (mFullscreen != oldFullscreen) { + @Override + public void onConfigurationChanged(Configuration newParentConfig) { + final boolean wasInMultiWindowMode = inMultiWindowMode(); + super.onConfigurationChanged(newParentConfig); + if (wasInMultiWindowMode != inMultiWindowMode()) { mService.mStackSupervisor.scheduleUpdateMultiWindowMode(this); } - - return !mTmpConfig.equals(newConfig); + // TODO: Should also take care of Pip mode changes here. } /** Clears passed config and fills it with new override values. */ @@ -2046,23 +2064,19 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi final int longSize = Math.max(compatScreenHeightDp, compatScreenWidthDp); final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp); config.screenLayout = Configuration.reduceScreenLayout(sl, longSize, shortSize); - } Rect updateOverrideConfigurationFromLaunchBounds() { - final Rect bounds = validateBounds(getLaunchBounds()); + final Rect bounds = getLaunchBounds(); updateOverrideConfiguration(bounds); - if (bounds != null) { - bounds.set(mBounds); + if (bounds != null && !bounds.isEmpty()) { + // TODO: Review if we actually want to do this - we are setting the launch bounds + // directly here. + bounds.set(getOverrideBounds()); } return bounds; } - static Rect validateBounds(Rect bounds) { - // TODO: Not needed once we have bounds in WindowConfiguration. - return (bounds != null && bounds.isEmpty()) ? null : bounds; - } - /** Updates the task's bounds and override configuration to match what is expected for the * input stack. */ void updateOverrideConfigurationForStack(ActivityStack inStack) { @@ -2075,7 +2089,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi throw new IllegalArgumentException("Can not position non-resizeable task=" + this + " in stack=" + inStack); } - if (mBounds != null) { + if (!matchParentBounds()) { return; } if (mLastNonFullscreenBounds != null) { @@ -2084,7 +2098,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null); } } else { - updateOverrideConfiguration(inStack.mBounds); + updateOverrideConfiguration(inStack.getOverrideBounds()); } } @@ -2098,9 +2112,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi if (!isActivityTypeStandardOrUndefined() || windowingMode == WINDOWING_MODE_FULLSCREEN || (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && !isResizeable())) { - return isResizeable() ? mStack.mBounds : null; + return isResizeable() ? mStack.getOverrideBounds() : null; } else if (!getWindowConfiguration().persistTaskBounds()) { - return mStack.mBounds; + return mStack.getOverrideBounds(); } return mLastNonFullscreenBounds; } @@ -2114,6 +2128,22 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } } + void setRootProcess(ProcessRecord proc) { + clearRootProcess(); + if (intent != null && + (intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0) { + mRootProcess = proc; + proc.recentTasks.add(this); + } + } + + void clearRootProcess() { + if (mRootProcess != null) { + mRootProcess.recentTasks.remove(this); + mRootProcess = null; + } + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("userId="); pw.print(userId); pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid); @@ -2198,6 +2228,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi if (lastDescription != null) { pw.print(prefix); pw.print("lastDescription="); pw.println(lastDescription); } + if (mRootProcess != null) { + pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess); + } pw.print(prefix); pw.print("stackId="); pw.println(getStackId()); pw.print(prefix + "hasBeenVisible=" + hasBeenVisible); pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode)); @@ -2261,9 +2294,12 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } proto.write(ACTIVITY_TYPE, getActivityType()); proto.write(RESIZE_MODE, mResizeMode); - proto.write(FULLSCREEN, mFullscreen); - if (mBounds != null) { - mBounds.writeToProto(proto, BOUNDS); + // TODO: Remove, no longer needed with windowingMode. + proto.write(FULLSCREEN, matchParentBounds()); + + if (!matchParentBounds()) { + final Rect bounds = getOverrideBounds(); + bounds.writeToProto(proto, BOUNDS); } proto.write(MIN_WIDTH, mMinWidth); proto.write(MIN_HEIGHT, mMinHeight); diff --git a/com/android/server/am/UnsupportedCompileSdkDialog.java b/com/android/server/am/UnsupportedCompileSdkDialog.java new file mode 100644 index 00000000..b6f6ae6b --- /dev/null +++ b/com/android/server/am/UnsupportedCompileSdkDialog.java @@ -0,0 +1,83 @@ +/* + * 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 com.android.server.am; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.view.Window; +import android.view.WindowManager; +import android.widget.CheckBox; + +import com.android.internal.R; +import com.android.server.utils.AppInstallerUtil; + +public class UnsupportedCompileSdkDialog { + private final AlertDialog mDialog; + private final String mPackageName; + + public UnsupportedCompileSdkDialog(final AppWarnings manager, Context context, + ApplicationInfo appInfo) { + mPackageName = appInfo.packageName; + + final PackageManager pm = context.getPackageManager(); + final CharSequence label = appInfo.loadSafeLabel(pm); + final CharSequence message = context.getString(R.string.unsupported_compile_sdk_message, + label); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context) + .setPositiveButton(R.string.ok, null) + .setMessage(message) + .setView(R.layout.unsupported_compile_sdk_dialog_content); + + // If we might be able to update the app, show a button. + final Intent installerIntent = AppInstallerUtil.createIntent(context, appInfo.packageName); + if (installerIntent != null) { + builder.setNeutralButton(R.string.unsupported_compile_sdk_check_update, + (dialog, which) -> context.startActivity(installerIntent)); + } + + // Ensure the content view is prepared. + mDialog = builder.create(); + mDialog.create(); + + final Window window = mDialog.getWindow(); + window.setType(WindowManager.LayoutParams.TYPE_PHONE); + + // DO NOT MODIFY. Used by CTS to verify the dialog is displayed. + window.getAttributes().setTitle("UnsupportedCompileSdkDialog"); + + final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox); + alwaysShow.setChecked(true); + alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag( + mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked)); + } + + public String getPackageName() { + return mPackageName; + } + + public void show() { + mDialog.show(); + } + + public void dismiss() { + mDialog.dismiss(); + } +} diff --git a/com/android/server/am/UnsupportedDisplaySizeDialog.java b/com/android/server/am/UnsupportedDisplaySizeDialog.java index 501cd6bb..88506632 100644 --- a/com/android/server/am/UnsupportedDisplaySizeDialog.java +++ b/com/android/server/am/UnsupportedDisplaySizeDialog.java @@ -30,7 +30,7 @@ public class UnsupportedDisplaySizeDialog { private final AlertDialog mDialog; private final String mPackageName; - public UnsupportedDisplaySizeDialog(final ActivityManagerService service, Context context, + public UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context, ApplicationInfo appInfo) { mPackageName = appInfo.packageName; @@ -54,14 +54,10 @@ public class UnsupportedDisplaySizeDialog { // DO NOT MODIFY. Used by CTS to verify the dialog is displayed. window.getAttributes().setTitle("UnsupportedDisplaySizeDialog"); - final CheckBox alwaysShow = (CheckBox) mDialog.findViewById(R.id.ask_checkbox); + final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox); alwaysShow.setChecked(true); - alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> { - synchronized (service) { - service.mCompatModePackages.setPackageNotifyUnsupportedZoomLocked( - mPackageName, isChecked); - } - }); + alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag( + mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked)); } public String getPackageName() { diff --git a/com/android/server/am/UriPermissionOwner.java b/com/android/server/am/UriPermissionOwner.java index 28344df2..fc07c1ab 100644 --- a/com/android/server/am/UriPermissionOwner.java +++ b/com/android/server/am/UriPermissionOwner.java @@ -20,6 +20,9 @@ import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.util.ArraySet; +import android.util.proto.ProtoOutputStream; + +import com.android.server.am.proto.UriPermissionOwnerProto; import com.google.android.collect.Sets; @@ -139,6 +142,26 @@ final class UriPermissionOwner { } } + public void writeToProto(ProtoOutputStream proto, long fieldId) { + long token = proto.start(fieldId); + proto.write(UriPermissionOwnerProto.OWNER, owner.toString()); + if (mReadPerms != null) { + synchronized (mReadPerms) { + for (UriPermission p : mReadPerms) { + p.uri.writeToProto(proto, UriPermissionOwnerProto.READ_PERMS); + } + } + } + if (mWritePerms != null) { + synchronized (mWritePerms) { + for (UriPermission p : mWritePerms) { + p.uri.writeToProto(proto, UriPermissionOwnerProto.WRITE_PERMS); + } + } + } + proto.end(token); + } + @Override public String toString() { return owner.toString(); diff --git a/com/android/server/am/UserController.java b/com/android/server/am/UserController.java index 2df5dc93..4e3d8d27 100644 --- a/com/android/server/am/UserController.java +++ b/com/android/server/am/UserController.java @@ -89,6 +89,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; import com.android.internal.widget.LockPatternUtils; +import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemServiceManager; import com.android.server.pm.UserManagerService; @@ -355,27 +356,35 @@ class UserController implements Handler.Callback { // Only keep marching forward if user is actually unlocked if (!StorageManager.isUserKeyUnlocked(userId)) return; synchronized (mLock) { - // Bail if we ended up with a stale user - if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return; - - // Do not proceed if unexpected state - if (!uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) { + // Do not proceed if unexpected state or a stale user + if (mStartedUsers.get(userId) != uss || uss.state != STATE_RUNNING_LOCKED) { return; } } - mInjector.getUserManagerInternal().setUserState(userId, uss.state); uss.mUnlockProgress.start(); // Prepare app storage before we go any further uss.mUnlockProgress.setProgress(5, mInjector.getContext().getString(R.string.android_start_title)); - mInjector.getUserManager().onBeforeUnlockUser(userId); - uss.mUnlockProgress.setProgress(20); - // Dispatch unlocked to system services; when fully dispatched, - // that calls through to the next "unlocked" phase - mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss) - .sendToTarget(); + // Call onBeforeUnlockUser on a worker thread that allows disk I/O + FgThread.getHandler().post(() -> { + mInjector.getUserManager().onBeforeUnlockUser(userId); + synchronized (mLock) { + // Do not proceed if unexpected state + if (!uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) { + return; + } + } + mInjector.getUserManagerInternal().setUserState(userId, uss.state); + + uss.mUnlockProgress.setProgress(20); + + // Dispatch unlocked to system services; when fully dispatched, + // that calls through to the next "unlocked" phase + mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss) + .sendToTarget(); + }); } /** @@ -1819,7 +1828,10 @@ class UserController implements Handler.Callback { case SYSTEM_USER_UNLOCK_MSG: final int userId = msg.arg1; mInjector.getSystemServiceManager().unlockUser(userId); - mInjector.loadUserRecents(userId); + // Loads recents on a worker thread that allows disk I/O + FgThread.getHandler().post(() -> { + mInjector.loadUserRecents(userId); + }); if (userId == UserHandle.USER_SYSTEM) { mInjector.startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_UNAWARE); } diff --git a/com/android/server/autofill/RemoteFillService.java b/com/android/server/autofill/RemoteFillService.java index af55807f..831c488e 100644 --- a/com/android/server/autofill/RemoteFillService.java +++ b/com/android/server/autofill/RemoteFillService.java @@ -26,6 +26,7 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentSender; import android.content.ServiceConnection; import android.os.Handler; import android.os.IBinder; @@ -100,7 +101,8 @@ final class RemoteFillService implements DeathRecipient { @NonNull String servicePackageName); void onFillRequestFailure(@Nullable CharSequence message, @NonNull String servicePackageName); - void onSaveRequestSuccess(@NonNull String servicePackageName); + void onSaveRequestSuccess(@NonNull String servicePackageName, + @Nullable IntentSender intentSender); void onSaveRequestFailure(@Nullable CharSequence message, @NonNull String servicePackageName); void onServiceDied(RemoteFillService service); @@ -308,10 +310,11 @@ final class RemoteFillService implements DeathRecipient { }); } - private void dispatchOnSaveRequestSuccess(PendingRequest pendingRequest) { + private void dispatchOnSaveRequestSuccess(PendingRequest pendingRequest, + IntentSender intentSender) { mHandler.getHandler().post(() -> { if (handleResponseCallbackCommon(pendingRequest)) { - mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName()); + mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName(), intentSender); } }); } @@ -624,12 +627,13 @@ final class RemoteFillService implements DeathRecipient { mCallback = new ISaveCallback.Stub() { @Override - public void onSuccess() { + public void onSuccess(IntentSender intentSender) { if (!finish()) return; final RemoteFillService remoteService = getService(); if (remoteService != null) { - remoteService.dispatchOnSaveRequestSuccess(PendingSaveRequest.this); + remoteService.dispatchOnSaveRequestSuccess(PendingSaveRequest.this, + intentSender); } } diff --git a/com/android/server/autofill/Session.java b/com/android/server/autofill/Session.java index af4668a6..99b92b9c 100644 --- a/com/android/server/autofill/Session.java +++ b/com/android/server/autofill/Session.java @@ -561,7 +561,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // FillServiceCallbacks @Override - public void onSaveRequestSuccess(@NonNull String servicePackageName) { + public void onSaveRequestSuccess(@NonNull String servicePackageName, + @Nullable IntentSender intentSender) { synchronized (mLock) { mIsSaving = false; @@ -572,8 +573,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName) - .setType(MetricsEvent.TYPE_SUCCESS); + .setType(intentSender == null ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_OPEN); mMetricsLogger.write(log); + if (intentSender != null) { + if (sDebug) Slog.d(TAG, "Starting intent sender on save()"); + startIntentSender(intentSender); + } // Nothing left to do... removeSelf(); @@ -1167,7 +1172,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState break; } } + value = getSanitizedValue(sanitizers, id, value); + if (value == null) { + if (sDebug) { + Slog.d(TAG, "value of required field " + id + " failed sanitization"); + } + allRequiredAreNotEmpty = false; + break; + } + viewState.setSanitizedValue(value); currentValues.put(id, value); final AutofillValue filledValue = viewState.getAutofilledValue(); @@ -1337,7 +1351,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return sanitizers; } - @NonNull + @Nullable private AutofillValue getSanitizedValue( @Nullable ArrayMap<AutofillId, InternalSanitizer> sanitizers, @NonNull AutofillId id, @@ -1431,10 +1445,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + context); for (int viewStateNum = 0; viewStateNum < mViewStates.size(); viewStateNum++) { - final ViewState state = mViewStates.valueAt(viewStateNum); + final ViewState viewState = mViewStates.valueAt(viewStateNum); - final AutofillId id = state.id; - final AutofillValue value = state.getCurrentValue(); + final AutofillId id = viewState.id; + final AutofillValue value = viewState.getCurrentValue(); if (value == null) { if (sVerbose) Slog.v(TAG, "callSaveLocked(): skipping " + id); continue; @@ -1446,9 +1460,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value); - final AutofillValue sanitizedValue = getSanitizedValue(sanitizers, id, value); + AutofillValue sanitizedValue = viewState.getSanitizedValue(); - node.updateAutofillValue(sanitizedValue); + if (sanitizedValue == null) { + // Field is optional and haven't been sanitized yet. + sanitizedValue = getSanitizedValue(sanitizers, id, value); + } + if (sanitizedValue != null) { + node.updateAutofillValue(sanitizedValue); + } else if (sDebug) { + Slog.d(TAG, "Not updating field " + id + " because it failed sanitization"); + } } // Sanitize structure before it's sent to service. diff --git a/com/android/server/autofill/ViewState.java b/com/android/server/autofill/ViewState.java index 832a66b2..0dbdc13e 100644 --- a/com/android/server/autofill/ViewState.java +++ b/com/android/server/autofill/ViewState.java @@ -76,6 +76,7 @@ final class ViewState { private FillResponse mResponse; private AutofillValue mCurrentValue; private AutofillValue mAutofilledValue; + private AutofillValue mSanitizedValue; private Rect mVirtualBounds; private int mState; private String mDatasetId; @@ -117,6 +118,15 @@ final class ViewState { } @Nullable + AutofillValue getSanitizedValue() { + return mSanitizedValue; + } + + void setSanitizedValue(@Nullable AutofillValue value) { + mSanitizedValue = value; + } + + @Nullable FillResponse getResponse() { return mResponse; } @@ -218,6 +228,7 @@ final class ViewState { } pw.print(prefix); pw.print("currentValue:" ); pw.println(mCurrentValue); pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue); + pw.print(prefix); pw.print("sanitizedValue:" ); pw.println(mSanitizedValue); pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds); } }
\ No newline at end of file diff --git a/com/android/server/backup/RefactoredBackupManagerService.java b/com/android/server/backup/RefactoredBackupManagerService.java index a45a4f0c..2788218d 100644 --- a/com/android/server/backup/RefactoredBackupManagerService.java +++ b/com/android/server/backup/RefactoredBackupManagerService.java @@ -48,7 +48,6 @@ import android.app.backup.IBackupObserver; import android.app.backup.IFullBackupRestoreObserver; import android.app.backup.IRestoreSession; import android.app.backup.ISelectBackupTransportCallback; -import android.app.backup.SelectBackupTransportCallback; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -103,6 +102,7 @@ import com.android.server.backup.fullbackup.PerformFullTransportBackupTask; import com.android.server.backup.internal.BackupHandler; import com.android.server.backup.internal.BackupRequest; import com.android.server.backup.internal.ClearDataObserver; +import com.android.server.backup.internal.OnTaskFinishedListener; import com.android.server.backup.internal.Operation; import com.android.server.backup.internal.PerformInitializeTask; import com.android.server.backup.internal.ProvisionedObserver; @@ -117,6 +117,7 @@ import com.android.server.backup.params.ClearRetryParams; import com.android.server.backup.params.RestoreParams; import com.android.server.backup.restore.ActiveRestoreSession; import com.android.server.backup.restore.PerformUnifiedRestoreTask; +import com.android.server.backup.transport.TransportClient; import com.android.server.backup.utils.AppBackupUtils; import com.android.server.backup.utils.BackupManagerMonitorUtils; import com.android.server.backup.utils.BackupObserverUtils; @@ -1585,8 +1586,27 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter return BackupManager.ERROR_BACKUP_NOT_ALLOWED; } + // We're using pieces of the new binding on-demand infra-structure and the old always-bound + // infra-structure below this comment. The TransportManager.getCurrentTransportClient() line + // is using the new one and TransportManager.getCurrentTransportBinder() is using the old. + // This is weird but there is a reason. + // This is the natural place to put TransportManager.getCurrentTransportClient() because of + // the null handling below that should be the same for TransportClient. + // TransportClient.connect() would return a IBackupTransport for us (instead of using the + // old infra), but it may block and we don't want this in this thread. + // The only usage of transport in this method is for transport.transportDirName(). When the + // push-from-transport part of binding on-demand is in place we will replace the calls for + // IBackupTransport.transportDirName() with calls for + // TransportManager.transportDirName(transportName) or similar. So we'll leave the old piece + // here until we implement that. + // TODO(brufino): Remove always-bound code mTransportManager.getCurrentTransportBinder() + TransportClient transportClient = + mTransportManager.getCurrentTransportClient("BMS.requestBackup()"); IBackupTransport transport = mTransportManager.getCurrentTransportBinder(); - if (transport == null) { + if (transportClient == null || transport == null) { + if (transportClient != null) { + mTransportManager.disposeOfTransportClient(transportClient, "BMS.requestBackup()"); + } BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED); monitor = BackupManagerMonitorUtils.monitorEvent(monitor, BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL, @@ -1594,6 +1614,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter return BackupManager.ERROR_TRANSPORT_ABORTED; } + OnTaskFinishedListener listener = + caller -> mTransportManager.disposeOfTransportClient(transportClient, caller); + ArrayList<String> fullBackupList = new ArrayList<>(); ArrayList<String> kvBackupList = new ArrayList<>(); for (String packageName : packages) { @@ -1640,8 +1663,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0; Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP); - msg.obj = new BackupParams(transport, dirName, kvBackupList, fullBackupList, observer, - monitor, true, nonIncrementalBackup); + msg.obj = new BackupParams(transportClient, dirName, kvBackupList, fullBackupList, observer, + monitor, listener, true, nonIncrementalBackup); mBackupHandler.sendMessage(msg); return BackupManager.SUCCESS; } @@ -2135,8 +2158,17 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter mFullBackupQueue.remove(0); CountDownLatch latch = new CountDownLatch(1); String[] pkg = new String[]{entry.packageName}; - mRunningFullBackupTask = new PerformFullTransportBackupTask(this, null, pkg, true, - scheduledJob, latch, null, null, false /* userInitiated */); + mRunningFullBackupTask = PerformFullTransportBackupTask.newWithCurrentTransport( + this, + /* observer */ null, + pkg, + /* updateSchedule */ true, + scheduledJob, + latch, + /* backupObserver */ null, + /* monitor */ null, + /* userInitiated */ false, + "BMS.beginFullBackup()"); // Acquiring wakelock for PerformFullTransportBackupTask before its start. mWakelock.acquire(); (new Thread(mRunningFullBackupTask)).start(); @@ -2490,8 +2522,17 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter final long oldId = Binder.clearCallingIdentity(); try { CountDownLatch latch = new CountDownLatch(1); - PerformFullTransportBackupTask task = new PerformFullTransportBackupTask(this, null, - pkgNames, false, null, latch, null, null, false /* userInitiated */); + Runnable task = PerformFullTransportBackupTask.newWithCurrentTransport( + this, + /* observer */ null, + pkgNames, + /* updateSchedule */ false, + /* runningJob */ null, + latch, + /* backupObserver */ null, + /* monitor */ null, + /* userInitiated */ false, + "BMS.fullTransportBackup()"); // Acquiring wakelock for PerformFullTransportBackupTask before its start. mWakelock.acquire(); (new Thread(task, "full-transport-master")).start(); diff --git a/com/android/server/backup/TransportManager.java b/com/android/server/backup/TransportManager.java index 7a0173f6..a2b5cb88 100644 --- a/com/android/server/backup/TransportManager.java +++ b/com/android/server/backup/TransportManager.java @@ -16,8 +16,9 @@ package com.android.server.backup; +import android.annotation.Nullable; import android.app.backup.BackupManager; -import android.app.backup.SelectBackupTransportCallback; +import android.app.backup.BackupTransport; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -44,9 +45,11 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.backup.IBackupTransport; import com.android.server.EventLogTags; +import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportClientManager; +import com.android.server.backup.transport.TransportConnectionListener; import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -60,8 +63,7 @@ public class TransportManager { private static final String TAG = "BackupTransportManager"; @VisibleForTesting - /* package */ static final String SERVICE_ACTION_TRANSPORT_HOST = - "android.backup.TRANSPORT_HOST"; + public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins @@ -72,6 +74,7 @@ public class TransportManager { private final PackageManager mPackageManager; private final Set<ComponentName> mTransportWhitelist; private final Handler mHandler; + private final TransportClientManager mTransportClientManager; /** * This listener is called after we bind to any transport. If it returns true, this is a valid @@ -95,6 +98,10 @@ public class TransportManager { @GuardedBy("mTransportLock") private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>(); + /** Names of transports we've bound to at least once */ + @GuardedBy("mTransportLock") + private final Map<String, ComponentName> mTransportsByName = new ArrayMap<>(); + /** * Callback interface for {@link #ensureTransportReady(ComponentName, TransportReadyCallback)}. */ @@ -123,6 +130,7 @@ public class TransportManager { mCurrentTransportName = defaultTransport; mTransportBoundListener = listener; mHandler = new RebindOnTimeoutHandler(looper); + mTransportClientManager = new TransportClientManager(context); } void onPackageAdded(String packageName) { @@ -204,6 +212,67 @@ public class TransportManager { return null; } + /** + * Returns the transport name associated with {@param transportClient} or {@code null} if not + * found. + */ + @Nullable + public String getTransportName(TransportClient transportClient) { + ComponentName transportComponent = transportClient.getTransportComponent(); + synchronized (mTransportLock) { + for (Map.Entry<String, ComponentName> transportEntry : mTransportsByName.entrySet()) { + if (transportEntry.getValue().equals(transportComponent)) { + return transportEntry.getKey(); + } + } + return null; + } + } + + /** + * Returns a {@link TransportClient} for {@param transportName} or {@code null} if not found. + * + * @param transportName The name of the transport as returned by {@link BackupTransport#name()}. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient} or null if not found. + */ + @Nullable + public TransportClient getTransportClient(String transportName, String caller) { + ComponentName transportComponent = mTransportsByName.get(transportName); + if (transportComponent == null) { + Slog.w(TAG, "Transport " + transportName + " not registered"); + return null; + } + return mTransportClientManager.getTransportClient(transportComponent, caller); + } + + /** + * Returns a {@link TransportClient} for the current transport or null if not found. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient} or null if not found. + */ + @Nullable + public TransportClient getCurrentTransportClient(String caller) { + return getTransportClient(mCurrentTransportName, caller); + } + + /** + * Disposes of the {@link TransportClient}. + * + * @param transportClient The {@link TransportClient} to be disposed of. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + */ + public void disposeOfTransportClient(TransportClient transportClient, String caller) { + mTransportClientManager.disposeOfTransportClient(transportClient, caller); + } + String[] getBoundTransportNames() { synchronized (mTransportLock) { return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]); @@ -374,6 +443,7 @@ public class TransportManager { String componentShortString = component.flattenToShortString().intern(); if (success) { Slog.d(TAG, "Bound to transport: " + componentShortString); + mTransportsByName.put(mTransportName, component); mBoundTransports.put(mTransportName, component); for (TransportReadyCallback listener : mListeners) { listener.onSuccess(mTransportName); @@ -528,7 +598,7 @@ public class TransportManager { // These only exists to make it testable with Robolectric, which is not updated to API level 24 // yet. // TODO: Get rid of this once Robolectric is updated. - private static UserHandle createSystemUserHandle() { + public static UserHandle createSystemUserHandle() { return new UserHandle(UserHandle.USER_SYSTEM); } } diff --git a/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java index 90134e1a..d5b3d98f 100644 --- a/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java +++ b/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java @@ -24,6 +24,7 @@ import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_B import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP_WAIT; import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL; +import android.annotation.Nullable; import android.app.IBackupAgent; import android.app.backup.BackupManager; import android.app.backup.BackupManagerMonitor; @@ -46,7 +47,11 @@ import com.android.server.EventLogTags; import com.android.server.backup.BackupRestoreTask; import com.android.server.backup.FullBackupJob; import com.android.server.backup.RefactoredBackupManagerService; +import com.android.server.backup.TransportManager; +import com.android.server.backup.internal.OnTaskFinishedListener; import com.android.server.backup.internal.Operation; +import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.utils.AppBackupUtils; import com.android.server.backup.utils.BackupManagerMonitorUtils; import com.android.server.backup.utils.BackupObserverUtils; @@ -89,6 +94,36 @@ import java.util.concurrent.atomic.AtomicLong; * mBackupRunner.getBackupResultBlocking(). */ public class PerformFullTransportBackupTask extends FullBackupTask implements BackupRestoreTask { + public static PerformFullTransportBackupTask newWithCurrentTransport( + RefactoredBackupManagerService backupManagerService, + IFullBackupRestoreObserver observer, + String[] whichPackages, + boolean updateSchedule, + FullBackupJob runningJob, + CountDownLatch latch, + IBackupObserver backupObserver, + IBackupManagerMonitor monitor, + boolean userInitiated, + String caller) { + TransportManager transportManager = backupManagerService.getTransportManager(); + TransportClient transportClient = transportManager.getCurrentTransportClient(caller); + OnTaskFinishedListener listener = + listenerCaller -> + transportManager.disposeOfTransportClient(transportClient, listenerCaller); + return new PerformFullTransportBackupTask( + backupManagerService, + transportClient, + observer, + whichPackages, + updateSchedule, + runningJob, + latch, + backupObserver, + monitor, + listener, + userInitiated); + } + private static final String TAG = "PFTBT"; private RefactoredBackupManagerService backupManagerService; @@ -102,9 +137,10 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba IBackupObserver mBackupObserver; IBackupManagerMonitor mMonitor; boolean mUserInitiated; - private volatile IBackupTransport mTransport; SinglePackageBackupRunner mBackupRunner; private final int mBackupRunnerOpToken; + private final OnTaskFinishedListener mListener; + private final TransportClient mTransportClient; // This is true when a backup operation for some package is in progress. private volatile boolean mIsDoingBackup; @@ -112,18 +148,22 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba private final int mCurrentOpToken; public PerformFullTransportBackupTask(RefactoredBackupManagerService backupManagerService, + TransportClient transportClient, IFullBackupRestoreObserver observer, String[] whichPackages, boolean updateSchedule, FullBackupJob runningJob, CountDownLatch latch, IBackupObserver backupObserver, - IBackupManagerMonitor monitor, boolean userInitiated) { + IBackupManagerMonitor monitor, @Nullable OnTaskFinishedListener listener, + boolean userInitiated) { super(observer); this.backupManagerService = backupManagerService; + mTransportClient = transportClient; mUpdateSchedule = updateSchedule; mLatch = latch; mJob = runningJob; mPackages = new ArrayList<>(whichPackages.length); mBackupObserver = backupObserver; mMonitor = monitor; + mListener = (listener != null) ? listener : OnTaskFinishedListener.NOP; mUserInitiated = userInitiated; mCurrentOpToken = backupManagerService.generateRandomIntegerToken(); mBackupRunnerOpToken = backupManagerService.generateRandomIntegerToken(); @@ -241,8 +281,11 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba if (mIsDoingBackup) { backupManagerService.handleCancel(mBackupRunnerOpToken, cancelAll); try { - mTransport.cancelFullBackup(); - } catch (RemoteException e) { + // If we're running a backup we should be connected to a transport + IBackupTransport transport = + mTransportClient.getConnectedTransport("PFTBT.handleCancel()"); + transport.cancelFullBackup(); + } catch (RemoteException | TransportNotAvailableException e) { Slog.w(TAG, "Error calling cancelFullBackup() on transport: " + e); // Can't do much. } @@ -291,8 +334,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba return; } - mTransport = backupManagerService.getTransportManager().getCurrentTransportBinder(); - if (mTransport == null) { + IBackupTransport transport = mTransportClient.connect("PFTBT.run()"); + if (transport == null) { Slog.w(TAG, "Transport not present; full data backup not performed"); backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED; mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor, @@ -325,17 +368,17 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba if (mCancelAll) { break; } - backupPackageStatus = mTransport.performFullBackup(currentPackage, + backupPackageStatus = transport.performFullBackup(currentPackage, transportPipes[0], flags); if (backupPackageStatus == BackupTransport.TRANSPORT_OK) { - quota = mTransport.getBackupQuota(currentPackage.packageName, + quota = transport.getBackupQuota(currentPackage.packageName, true /* isFullBackup */); // Now set up the backup engine / data source end of things enginePipes = ParcelFileDescriptor.createPipe(); mBackupRunner = new SinglePackageBackupRunner(enginePipes[1], currentPackage, - mTransport, quota, mBackupRunnerOpToken); + mTransportClient, quota, mBackupRunnerOpToken); // The runner dup'd the pipe half, so we close it here enginePipes[1].close(); enginePipes[1] = null; @@ -389,7 +432,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba out.write(buffer, 0, nRead); synchronized (mCancelLock) { if (!mCancelAll) { - backupPackageStatus = mTransport.sendBackupData(nRead); + backupPackageStatus = transport.sendBackupData(nRead); } } totalRead += nRead; @@ -425,12 +468,12 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba // result based on what finishBackup() returns. If we're in a // failure case already, preserve that result and ignore whatever // finishBackup() reports. - final int finishResult = mTransport.finishBackup(); + final int finishResult = transport.finishBackup(); if (backupPackageStatus == BackupTransport.TRANSPORT_OK) { backupPackageStatus = finishResult; } } else { - mTransport.cancelFullBackup(); + transport.cancelFullBackup(); } } } @@ -469,7 +512,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba // Also ask the transport how long it wants us to wait before // moving on to the next package, if any. - backoff = mTransport.requestFullBackupTime(); + backoff = transport.requestFullBackupTime(); if (DEBUG_SCHEDULING) { Slog.i(TAG, "Transport suggested backoff=" + backoff); } @@ -591,6 +634,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba backupManagerService.setRunningFullBackupTask(null); } + mListener.onFinished("PFTBT.run()"); + mLatch.countDown(); // Now that we're actually done with schedule-driven work, reschedule @@ -633,12 +678,13 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba class SinglePackageBackupPreflight implements BackupRestoreTask, FullBackupPreflight { final AtomicLong mResult = new AtomicLong(BackupTransport.AGENT_ERROR); final CountDownLatch mLatch = new CountDownLatch(1); - final IBackupTransport mTransport; + final TransportClient mTransportClient; final long mQuota; private final int mCurrentOpToken; - SinglePackageBackupPreflight(IBackupTransport transport, long quota, int currentOpToken) { - mTransport = transport; + SinglePackageBackupPreflight( + TransportClient transportClient, long quota, int currentOpToken) { + mTransportClient = transportClient; mQuota = quota; mCurrentOpToken = currentOpToken; } @@ -672,7 +718,9 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba Slog.v(TAG, "Got preflight response; size=" + totalSize); } - result = mTransport.checkFullBackupSize(totalSize); + IBackupTransport transport = + mTransportClient.connectOrThrow("PFTBT$SPBP.preflightFullBackup()"); + result = transport.checkFullBackupSize(totalSize); if (result == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) { if (MORE_DEBUG) { Slog.d(TAG, "Package hit quota limit on preflight " + @@ -739,12 +787,13 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba private volatile boolean mIsCancelled; SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target, - IBackupTransport transport, long quota, int currentOpToken) throws IOException { + TransportClient transportClient, long quota, int currentOpToken) + throws IOException { mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor()); mTarget = target; mCurrentOpToken = currentOpToken; mEphemeralToken = backupManagerService.generateRandomIntegerToken(); - mPreflight = new SinglePackageBackupPreflight(transport, quota, mEphemeralToken); + mPreflight = new SinglePackageBackupPreflight(transportClient, quota, mEphemeralToken); mPreflightLatch = new CountDownLatch(1); mBackupLatch = new CountDownLatch(1); mPreflightResult = BackupTransport.AGENT_ERROR; diff --git a/com/android/server/backup/internal/BackupHandler.java b/com/android/server/backup/internal/BackupHandler.java index 8f823004..9011b95c 100644 --- a/com/android/server/backup/internal/BackupHandler.java +++ b/com/android/server/backup/internal/BackupHandler.java @@ -38,6 +38,8 @@ import com.android.server.EventLogTags; import com.android.server.backup.BackupRestoreTask; import com.android.server.backup.DataChangedJournal; import com.android.server.backup.RefactoredBackupManagerService; +import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.TransportManager; import com.android.server.backup.fullbackup.PerformAdbBackupTask; import com.android.server.backup.fullbackup.PerformFullTransportBackupTask; import com.android.server.backup.params.AdbBackupParams; @@ -51,10 +53,8 @@ import com.android.server.backup.params.RestoreParams; import com.android.server.backup.restore.PerformAdbRestoreTask; import com.android.server.backup.restore.PerformUnifiedRestoreTask; -import java.io.File; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; /** * Asynchronous backup/restore handler thread. @@ -81,7 +81,7 @@ public class BackupHandler extends Handler { public static final int MSG_BACKUP_RESTORE_STEP = 20; public static final int MSG_OP_COMPLETE = 21; - private RefactoredBackupManagerService backupManagerService; + private final RefactoredBackupManagerService backupManagerService; public BackupHandler( RefactoredBackupManagerService backupManagerService, Looper looper) { @@ -91,13 +91,23 @@ public class BackupHandler extends Handler { public void handleMessage(Message msg) { + TransportManager transportManager = backupManagerService.getTransportManager(); switch (msg.what) { case MSG_RUN_BACKUP: { backupManagerService.setLastBackupPass(System.currentTimeMillis()); + String callerLogString = "BH/MSG_RUN_BACKUP"; + TransportClient transportClient = + transportManager.getCurrentTransportClient(callerLogString); IBackupTransport transport = - backupManagerService.getTransportManager().getCurrentTransportBinder(); + transportClient != null + ? transportClient.connect(callerLogString) + : null; if (transport == null) { + if (transportClient != null) { + transportManager + .disposeOfTransportClient(transportClient, callerLogString); + } Slog.v(TAG, "Backup requested but no transport available"); synchronized (backupManagerService.getQueueLock()) { backupManagerService.setBackupRunning(false); @@ -138,9 +148,13 @@ public class BackupHandler extends Handler { // Spin up a backup state sequence and set it running try { String dirName = transport.transportDirName(); + OnTaskFinishedListener listener = + caller -> + transportManager + .disposeOfTransportClient(transportClient, caller); PerformBackupTask pbt = new PerformBackupTask( - backupManagerService, transport, dirName, queue, - oldJournal, null, null, Collections.<String>emptyList(), false, + backupManagerService, transportClient, dirName, queue, + oldJournal, null, null, listener, Collections.emptyList(), false, false /* nonIncremental */); Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt); sendMessage(pbtMessage); @@ -157,6 +171,7 @@ public class BackupHandler extends Handler { } if (!staged) { + transportManager.disposeOfTransportClient(transportClient, callerLogString); // if we didn't actually hand off the wakelock, rewind until next time synchronized (backupManagerService.getQueueLock()) { backupManagerService.setBackupRunning(false); @@ -382,9 +397,9 @@ public class BackupHandler extends Handler { PerformBackupTask pbt = new PerformBackupTask( backupManagerService, - params.transport, params.dirName, - kvQueue, null, params.observer, params.monitor, params.fullPackages, true, - params.nonIncrementalBackup); + params.transportClient, params.dirName, + kvQueue, null, params.observer, params.monitor, params.listener, + params.fullPackages, true, params.nonIncrementalBackup); Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt); sendMessage(pbtMessage); break; diff --git a/com/android/server/backup/internal/OnTaskFinishedListener.java b/com/android/server/backup/internal/OnTaskFinishedListener.java new file mode 100644 index 00000000..e417f06c --- /dev/null +++ b/com/android/server/backup/internal/OnTaskFinishedListener.java @@ -0,0 +1,34 @@ +/* + * 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 com.android.server.backup.internal; + +import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnectionListener; + +/** Listener to be called when a task finishes, successfully or not. */ +public interface OnTaskFinishedListener { + OnTaskFinishedListener NOP = caller -> {}; + + /** + * Called when a task finishes, successfully or not. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + */ + void onFinished(String caller); +} diff --git a/com/android/server/backup/internal/PerformBackupTask.java b/com/android/server/backup/internal/PerformBackupTask.java index c0caa557..5be1b390 100644 --- a/com/android/server/backup/internal/PerformBackupTask.java +++ b/com/android/server/backup/internal/PerformBackupTask.java @@ -63,6 +63,8 @@ import com.android.server.backup.KeyValueBackupJob; import com.android.server.backup.PackageManagerBackupAgent; import com.android.server.backup.RefactoredBackupManagerService; import com.android.server.backup.fullbackup.PerformFullTransportBackupTask; +import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportUtils; import com.android.server.backup.utils.AppBackupUtils; import com.android.server.backup.utils.BackupManagerMonitorUtils; import com.android.server.backup.utils.BackupObserverUtils; @@ -112,7 +114,6 @@ public class PerformBackupTask implements BackupRestoreTask { private RefactoredBackupManagerService backupManagerService; private final Object mCancelLock = new Object(); - IBackupTransport mTransport; ArrayList<BackupRequest> mQueue; ArrayList<BackupRequest> mOriginalQueue; File mStateDir; @@ -122,6 +123,8 @@ public class PerformBackupTask implements BackupRestoreTask { IBackupObserver mObserver; IBackupManagerMonitor mMonitor; + private final TransportClient mTransportClient; + private final OnTaskFinishedListener mListener; private final PerformFullTransportBackupTask mFullBackupTask; private final int mCurrentOpToken; private volatile int mEphemeralOpToken; @@ -143,17 +146,19 @@ public class PerformBackupTask implements BackupRestoreTask { private volatile boolean mCancelAll; public PerformBackupTask(RefactoredBackupManagerService backupManagerService, - IBackupTransport transport, String dirName, + TransportClient transportClient, String dirName, ArrayList<BackupRequest> queue, @Nullable DataChangedJournal journal, IBackupObserver observer, IBackupManagerMonitor monitor, - List<String> pendingFullBackups, boolean userInitiated, boolean nonIncremental) { + @Nullable OnTaskFinishedListener listener, List<String> pendingFullBackups, + boolean userInitiated, boolean nonIncremental) { this.backupManagerService = backupManagerService; - mTransport = transport; + mTransportClient = transportClient; mOriginalQueue = queue; mQueue = new ArrayList<>(); mJournal = journal; mObserver = observer; mMonitor = monitor; + mListener = (listener != null) ? listener : OnTaskFinishedListener.NOP; mPendingFullBackups = pendingFullBackups; mUserInitiated = userInitiated; mNonIncremental = nonIncremental; @@ -179,11 +184,11 @@ public class PerformBackupTask implements BackupRestoreTask { mPendingFullBackups.toArray(new String[mPendingFullBackups.size()]); mFullBackupTask = new PerformFullTransportBackupTask(backupManagerService, - /*fullBackupRestoreObserver*/ - null, + transportClient, + /*fullBackupRestoreObserver*/ null, fullBackups, /*updateSchedule*/ false, /*runningJob*/ null, latch, - mObserver, mMonitor, mUserInitiated); + mObserver, mMonitor, mListener, mUserInitiated); registerTask(); backupManagerService.addBackupTrace("STATE => INITIAL"); @@ -289,10 +294,10 @@ public class PerformBackupTask implements BackupRestoreTask { if (DEBUG) { Slog.v(TAG, "Beginning backup of " + mQueue.size() + " targets"); } - File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL); try { - final String transportName = mTransport.transportDirName(); + IBackupTransport transport = mTransportClient.connectOrThrow("PBT.beginBackup()"); + final String transportName = transport.transportDirName(); EventLog.writeEvent(EventLogTags.BACKUP_START, transportName); // If we haven't stored package manager metadata yet, we must init the transport. @@ -300,7 +305,7 @@ public class PerformBackupTask implements BackupRestoreTask { Slog.i(TAG, "Initializing (wiping) backup state and transport storage"); backupManagerService.addBackupTrace("initializing transport " + transportName); backupManagerService.resetBackupState(mStateDir); // Just to make sure. - mStatus = mTransport.initializeDevice(); + mStatus = transport.initializeDevice(); backupManagerService.addBackupTrace("transport.initializeDevice() == " + mStatus); if (mStatus == BackupTransport.TRANSPORT_OK) { @@ -324,7 +329,7 @@ public class PerformBackupTask implements BackupRestoreTask { PackageManagerBackupAgent pmAgent = backupManagerService.makeMetadataAgent(); mStatus = invokeAgentForBackup( PACKAGE_MANAGER_SENTINEL, - IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport); + IBackupAgent.Stub.asInterface(pmAgent.onBind())); backupManagerService.addBackupTrace("PMBA invoke: " + mStatus); // Because the PMBA is a local instance, it has already executed its @@ -445,7 +450,7 @@ public class PerformBackupTask implements BackupRestoreTask { backupManagerService.addBackupTrace("agent bound; a? = " + (agent != null)); if (agent != null) { mAgentBinder = agent; - mStatus = invokeAgentForBackup(request.packageName, agent, mTransport); + mStatus = invokeAgentForBackup(request.packageName, agent); // at this point we'll either get a completion callback from the // agent, or a timeout message on the main handler. either way, we're // done here as long as we're successful so far. @@ -526,11 +531,14 @@ public class PerformBackupTask implements BackupRestoreTask { // If everything actually went through and this is the first time we've // done a backup, we can now record what the current backup dataset token // is. + String callerLogString = "PBT.finalizeBackup()"; if ((backupManagerService.getCurrentToken() == 0) && (mStatus == BackupTransport.TRANSPORT_OK)) { backupManagerService.addBackupTrace("success; recording token"); try { - backupManagerService.setCurrentToken(mTransport.getCurrentRestoreSet()); + IBackupTransport transport = + mTransportClient.connectOrThrow(callerLogString); + backupManagerService.setCurrentToken(transport.getCurrentRestoreSet()); backupManagerService.writeRestoreTokens(); } catch (Exception e) { // nothing for it at this point, unfortunately, but this will be @@ -553,13 +561,13 @@ public class PerformBackupTask implements BackupRestoreTask { backupManagerService.addBackupTrace("init required; rerunning"); try { final String name = backupManagerService.getTransportManager().getTransportName( - mTransport); + mTransportClient); if (name != null) { backupManagerService.getPendingInits().add(name); } else { if (DEBUG) { - Slog.w(TAG, "Couldn't find name of transport " + mTransport - + " for init"); + Slog.w(TAG, "Couldn't find name of transport " + + mTransportClient.getTransportComponent() + " for init"); } } } catch (Exception e) { @@ -580,14 +588,18 @@ public class PerformBackupTask implements BackupRestoreTask { Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups); // Acquiring wakelock for PerformFullTransportBackupTask before its start. backupManagerService.getWakelock().acquire(); + // The full-backup task is now responsible for calling onFinish() on mListener, which + // was the listener we passed it. (new Thread(mFullBackupTask, "full-transport-requested")).start(); } else if (mCancelAll) { + mListener.onFinished(callerLogString); if (mFullBackupTask != null) { mFullBackupTask.unregisterTask(); } BackupObserverUtils.sendBackupFinished(mObserver, BackupManager.ERROR_BACKUP_CANCELLED); } else { + mListener.onFinished(callerLogString); mFullBackupTask.unregisterTask(); switch (mStatus) { case BackupTransport.TRANSPORT_OK: @@ -619,8 +631,7 @@ public class PerformBackupTask implements BackupRestoreTask { // Invoke an agent's doBackup() and start a timeout message spinning on the main // handler in case it doesn't get back to us. - int invokeAgentForBackup(String packageName, IBackupAgent agent, - IBackupTransport transport) { + int invokeAgentForBackup(String packageName, IBackupAgent agent) { if (DEBUG) { Slog.d(TAG, "invokeAgentForBackup on " + packageName); } @@ -671,7 +682,10 @@ public class PerformBackupTask implements BackupRestoreTask { ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE); - final long quota = mTransport.getBackupQuota(packageName, false /* isFullBackup */); + IBackupTransport transport = + mTransportClient.connectOrThrow("PBT.invokeAgentForBackup()"); + + final long quota = transport.getBackupQuota(packageName, false /* isFullBackup */); callingAgent = true; // Initiate the target's backup pass @@ -888,10 +902,12 @@ public class PerformBackupTask implements BackupRestoreTask { clearAgentState(); backupManagerService.addBackupTrace("operation complete"); + IBackupTransport transport = mTransportClient.connect("PBT.operationComplete()"); ParcelFileDescriptor backupData = null; mStatus = BackupTransport.TRANSPORT_OK; long size = 0; try { + TransportUtils.checkTransport(transport); size = mBackupDataName.length(); if (size > 0) { if (mStatus == BackupTransport.TRANSPORT_OK) { @@ -899,7 +915,7 @@ public class PerformBackupTask implements BackupRestoreTask { ParcelFileDescriptor.MODE_READ_ONLY); backupManagerService.addBackupTrace("sending data to transport"); int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0; - mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags); + mStatus = transport.performBackup(mCurrentPackage, backupData, flags); } // TODO - We call finishBackup() for each application backed up, because @@ -910,7 +926,7 @@ public class PerformBackupTask implements BackupRestoreTask { backupManagerService.addBackupTrace("data delivered: " + mStatus); if (mStatus == BackupTransport.TRANSPORT_OK) { backupManagerService.addBackupTrace("finishing op on transport"); - mStatus = mTransport.finishBackup(); + mStatus = transport.finishBackup(); backupManagerService.addBackupTrace("finished: " + mStatus); } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) { backupManagerService.addBackupTrace("transport rejected package"); @@ -981,8 +997,8 @@ public class PerformBackupTask implements BackupRestoreTask { } if (mAgentBinder != null) { try { - long quota = mTransport.getBackupQuota(mCurrentPackage.packageName, - false); + TransportUtils.checkTransport(transport); + long quota = transport.getBackupQuota(mCurrentPackage.packageName, false); mAgentBinder.doQuotaExceeded(size, quota); } catch (Exception e) { Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage()); @@ -1052,7 +1068,9 @@ public class PerformBackupTask implements BackupRestoreTask { // by way of retry/backoff time. long delay; try { - delay = mTransport.requestBackupTime(); + IBackupTransport transport = + mTransportClient.connectOrThrow("PBT.revertAndEndBackup()"); + delay = transport.requestBackupTime(); } catch (Exception e) { Slog.w(TAG, "Unable to contact transport for recommended backoff: " + e.getMessage()); delay = 0; // use the scheduler's default diff --git a/com/android/server/backup/params/BackupParams.java b/com/android/server/backup/params/BackupParams.java index 4fd7ddbc..2ba8ec16 100644 --- a/com/android/server/backup/params/BackupParams.java +++ b/com/android/server/backup/params/BackupParams.java @@ -19,30 +19,34 @@ package com.android.server.backup.params; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; -import com.android.internal.backup.IBackupTransport; +import com.android.server.backup.internal.OnTaskFinishedListener; +import com.android.server.backup.transport.TransportClient; import java.util.ArrayList; public class BackupParams { - public IBackupTransport transport; + public TransportClient transportClient; public String dirName; public ArrayList<String> kvPackages; public ArrayList<String> fullPackages; public IBackupObserver observer; public IBackupManagerMonitor monitor; + public OnTaskFinishedListener listener; public boolean userInitiated; public boolean nonIncrementalBackup; - public BackupParams(IBackupTransport transport, String dirName, ArrayList<String> kvPackages, - ArrayList<String> fullPackages, IBackupObserver observer, - IBackupManagerMonitor monitor, boolean userInitiated, boolean nonIncrementalBackup) { - this.transport = transport; + public BackupParams(TransportClient transportClient, String dirName, + ArrayList<String> kvPackages, ArrayList<String> fullPackages, IBackupObserver observer, + IBackupManagerMonitor monitor, OnTaskFinishedListener listener, boolean userInitiated, + boolean nonIncrementalBackup) { + this.transportClient = transportClient; this.dirName = dirName; this.kvPackages = kvPackages; this.fullPackages = fullPackages; this.observer = observer; this.monitor = monitor; + this.listener = listener; this.userInitiated = userInitiated; this.nonIncrementalBackup = nonIncrementalBackup; } diff --git a/com/android/server/backup/transport/TransportClient.java b/com/android/server/backup/transport/TransportClient.java new file mode 100644 index 00000000..65f95022 --- /dev/null +++ b/com/android/server/backup/transport/TransportClient.java @@ -0,0 +1,487 @@ +/* + * 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 com.android.server.backup.transport; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.DeadObjectException; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.backup.IBackupTransport; +import com.android.internal.util.Preconditions; +import com.android.server.backup.TransportManager; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +/** + * A {@link TransportClient} manages the connection to an {@link IBackupTransport} service, obtained + * via the {@param bindIntent} parameter provided in the constructor. A {@link TransportClient} is + * responsible for only one connection to the transport service, not more. + * + * <p>After retrieved using {@link TransportManager#getTransportClient(String, String)}, you can + * call either {@link #connect(String)}, if you can block your thread, or {@link + * #connectAsync(TransportConnectionListener, String)}, otherwise, to obtain a {@link + * IBackupTransport} instance. It's meant to be passed around as a token to a connected transport. + * When the connection is not needed anymore you should call {@link #unbind(String)} or indirectly + * via {@link TransportManager#disposeOfTransportClient(TransportClient, String)}. + * + * <p>DO NOT forget to unbind otherwise there will be dangling connections floating around. + * + * <p>This class is thread-safe. + * + * @see TransportManager + */ +public class TransportClient { + private static final String TAG = "TransportClient"; + + private final Context mContext; + private final Intent mBindIntent; + private final String mIdentifier; + private final ComponentName mTransportComponent; + private final Handler mListenerHandler; + private final String mPrefixForLog; + private final Object mStateLock = new Object(); + + @GuardedBy("mStateLock") + private final Map<TransportConnectionListener, String> mListeners = new ArrayMap<>(); + + @GuardedBy("mStateLock") + @State + private int mState = State.IDLE; + + @GuardedBy("mStateLock") + private volatile IBackupTransport mTransport; + + TransportClient( + Context context, + Intent bindIntent, + ComponentName transportComponent, + String identifier) { + this(context, bindIntent, transportComponent, identifier, Handler.getMain()); + } + + @VisibleForTesting + TransportClient( + Context context, + Intent bindIntent, + ComponentName transportComponent, + String identifier, + Handler listenerHandler) { + mContext = context; + mTransportComponent = transportComponent; + mBindIntent = bindIntent; + mIdentifier = identifier; + mListenerHandler = listenerHandler; + + // For logging + String classNameForLog = mTransportComponent.getShortClassName().replaceFirst(".*\\.", ""); + mPrefixForLog = classNameForLog + "#" + mIdentifier + ": "; + } + + public ComponentName getTransportComponent() { + return mTransportComponent; + } + + // Calls to onServiceDisconnected() or onBindingDied() turn TransportClient UNUSABLE. After one + // of these calls, if a binding happen again the new service can be a different instance. Since + // transports are stateful, we don't want a new instance responding for an old instance's state. + private ServiceConnection mConnection = + new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, IBinder binder) { + IBackupTransport transport = IBackupTransport.Stub.asInterface(binder); + synchronized (mStateLock) { + checkStateIntegrityLocked(); + + if (mState != State.UNUSABLE) { + log(Log.DEBUG, "Transport connected"); + setStateLocked(State.CONNECTED, transport); + notifyListenersAndClearLocked(transport); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + synchronized (mStateLock) { + log(Log.ERROR, "Service disconnected: client UNUSABLE"); + setStateLocked(State.UNUSABLE, null); + // After unbindService() no calls back to mConnection + mContext.unbindService(this); + } + } + + @Override + public void onBindingDied(ComponentName name) { + synchronized (mStateLock) { + checkStateIntegrityLocked(); + + log(Log.ERROR, "Binding died: client UNUSABLE"); + // After unbindService() no calls back to mConnection + switch (mState) { + case State.UNUSABLE: + break; + case State.IDLE: + log(Log.ERROR, "Unexpected state transition IDLE => UNUSABLE"); + setStateLocked(State.UNUSABLE, null); + break; + case State.BOUND_AND_CONNECTING: + setStateLocked(State.UNUSABLE, null); + mContext.unbindService(this); + notifyListenersAndClearLocked(null); + break; + case State.CONNECTED: + setStateLocked(State.UNUSABLE, null); + mContext.unbindService(this); + break; + } + } + } + }; + + /** + * Attempts to connect to the transport (if needed). + * + * <p>Note that being bound is not the same as connected. To be connected you also need to be + * bound. You go from nothing to bound, then to bound and connected. To have a usable transport + * binder instance you need to be connected. This method will attempt to connect and return an + * usable transport binder regardless of the state of the object, it may already be connected, + * or bound but not connected, not bound at all or even unusable. + * + * <p>So, a {@link Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)} (or + * one of its variants) can be called or not depending on the inner state. However, it won't be + * called again if we're already bound. For example, if one was already requested but the + * framework has not yet returned (meaning we're bound but still trying to connect) it won't + * trigger another one, just piggyback on the original request. + * + * <p>It's guaranteed that you are going to get a call back to {@param listener} after this + * call. However, the {@param IBackupTransport} parameter, the transport binder, is not + * guaranteed to be non-null, or if it's non-null it's not guaranteed to be usable - i.e. it can + * throw {@link DeadObjectException}s on method calls. You should check for both in your code. + * The reasons for a null transport binder are: + * + * <ul> + * <li>Some code called {@link #unbind(String)} before you got a callback. + * <li>The framework had already called {@link + * ServiceConnection#onServiceDisconnected(ComponentName)} or {@link + * ServiceConnection#onBindingDied(ComponentName)} on this object's connection before. + * Check the documentation of those methods for when that happens. + * <li>The framework returns false for {@link Context#bindServiceAsUser(Intent, + * ServiceConnection, int, UserHandle)} (or one of its variants). Check documentation for + * when this happens. + * </ul> + * + * For unusable transport binders check {@link DeadObjectException}. + * + * @param listener The listener that will be called with the (possibly null or unusable) {@link + * IBackupTransport} instance and this {@link TransportClient} object. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. This + * should be a human-readable short string that is easily identifiable in the logs. Ideally + * TAG.methodName(), where TAG is the one used in logcat. In cases where this is is not very + * descriptive like MyHandler.handleMessage() you should put something that someone reading + * the code would understand, like MyHandler/MSG_FOO. + * @see #connect(String) + * @see DeadObjectException + * @see ServiceConnection#onServiceConnected(ComponentName, IBinder) + * @see ServiceConnection#onServiceDisconnected(ComponentName) + * @see Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle) + */ + public void connectAsync(TransportConnectionListener listener, String caller) { + synchronized (mStateLock) { + checkStateIntegrityLocked(); + + switch (mState) { + case State.UNUSABLE: + log(Log.DEBUG, caller, "Async connect: UNUSABLE client"); + notifyListener(listener, null, caller); + break; + case State.IDLE: + boolean hasBound = + mContext.bindServiceAsUser( + mBindIntent, + mConnection, + Context.BIND_AUTO_CREATE, + TransportManager.createSystemUserHandle()); + if (hasBound) { + // We don't need to set a time-out because we are guaranteed to get a call + // back in ServiceConnection, either an onServiceConnected() or + // onBindingDied(). + log(Log.DEBUG, caller, "Async connect: service bound, connecting"); + setStateLocked(State.BOUND_AND_CONNECTING, null); + mListeners.put(listener, caller); + } else { + log(Log.ERROR, "Async connect: bindService returned false"); + // mState remains State.IDLE + mContext.unbindService(mConnection); + notifyListener(listener, null, caller); + } + break; + case State.BOUND_AND_CONNECTING: + log(Log.DEBUG, caller, "Async connect: already connecting, adding listener"); + mListeners.put(listener, caller); + break; + case State.CONNECTED: + log(Log.DEBUG, caller, "Async connect: reusing transport"); + notifyListener(listener, mTransport, caller); + break; + } + } + } + + /** + * Removes the transport binding. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link #connectAsync(TransportConnectionListener, String)} for more details. + */ + public void unbind(String caller) { + synchronized (mStateLock) { + checkStateIntegrityLocked(); + + log(Log.DEBUG, caller, "Unbind requested (was " + stateToString(mState) + ")"); + switch (mState) { + case State.UNUSABLE: + case State.IDLE: + break; + case State.BOUND_AND_CONNECTING: + setStateLocked(State.IDLE, null); + // After unbindService() no calls back to mConnection + mContext.unbindService(mConnection); + notifyListenersAndClearLocked(null); + break; + case State.CONNECTED: + setStateLocked(State.IDLE, null); + mContext.unbindService(mConnection); + break; + } + } + } + + /** + * Attempts to connect to the transport (if needed) and returns it. + * + * <p>Synchronous version of {@link #connectAsync(TransportConnectionListener, String)}. The + * same observations about state are valid here. Also, what was said about the {@link + * IBackupTransport} parameter of {@link TransportConnectionListener} now apply to the return + * value of this method. + * + * <p>This is a potentially blocking operation, so be sure to call this carefully on the correct + * threads. You can't call this from the process main-thread (it throws an exception if you do + * so). + * + * <p>In most cases only the first call to this method will block, the following calls should + * return instantly. However, this is not guaranteed. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link #connectAsync(TransportConnectionListener, String)} for more details. + * @return A {@link IBackupTransport} transport binder instance or null. If it's non-null it can + * still be unusable - throws {@link DeadObjectException} on method calls + */ + @WorkerThread + @Nullable + public IBackupTransport connect(String caller) { + // If called on the main-thread this could deadlock waiting because calls to + // ServiceConnection are on the main-thread as well + Preconditions.checkState( + !Looper.getMainLooper().isCurrentThread(), "Can't call connect() on main thread"); + + IBackupTransport transport = mTransport; + if (transport != null) { + log(Log.DEBUG, caller, "Sync connect: reusing transport"); + return transport; + } + + // If it's already UNUSABLE we return straight away, no need to go to main-thread + synchronized (mStateLock) { + if (mState == State.UNUSABLE) { + log(Log.DEBUG, caller, "Sync connect: UNUSABLE client"); + return null; + } + } + + CompletableFuture<IBackupTransport> transportFuture = new CompletableFuture<>(); + TransportConnectionListener requestListener = + (requestedTransport, transportClient) -> + transportFuture.complete(requestedTransport); + + log(Log.DEBUG, caller, "Sync connect: calling async"); + connectAsync(requestListener, caller); + + try { + return transportFuture.get(); + } catch (InterruptedException | ExecutionException e) { + String error = e.getClass().getSimpleName(); + log(Log.ERROR, caller, error + " while waiting for transport: " + e.getMessage()); + return null; + } + } + + /** + * Tries to connect to the transport, if it fails throws {@link TransportNotAvailableException}. + * + * <p>Same as {@link #connect(String)} except it throws instead of returning null. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link #connectAsync(TransportConnectionListener, String)} for more details. + * @return A {@link IBackupTransport} transport binder instance. + * @see #connect(String) + * @throws TransportNotAvailableException if connection attempt fails. + */ + @WorkerThread + public IBackupTransport connectOrThrow(String caller) throws TransportNotAvailableException { + IBackupTransport transport = connect(caller); + if (transport == null) { + log(Log.ERROR, caller, "Transport connection failed"); + throw new TransportNotAvailableException(); + } + return transport; + } + + /** + * If the {@link TransportClient} is already connected to the transport, returns the transport, + * otherwise throws {@link TransportNotAvailableException}. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link #connectAsync(TransportConnectionListener, String)} for more details. + * @return A {@link IBackupTransport} transport binder instance. + * @throws TransportNotAvailableException if not connected. + */ + public IBackupTransport getConnectedTransport(String caller) + throws TransportNotAvailableException { + IBackupTransport transport = mTransport; + if (transport == null) { + log(Log.ERROR, caller, "Transport not connected"); + throw new TransportNotAvailableException(); + } + return transport; + } + + @Override + public String toString() { + return "TransportClient{" + + mTransportComponent.flattenToShortString() + + "#" + + mIdentifier + + "}"; + } + + private void notifyListener( + TransportConnectionListener listener, IBackupTransport transport, String caller) { + log(Log.VERBOSE, caller, "Notifying listener of transport = " + transport); + mListenerHandler.post(() -> listener.onTransportConnectionResult(transport, this)); + } + + @GuardedBy("mStateLock") + private void notifyListenersAndClearLocked(IBackupTransport transport) { + for (Map.Entry<TransportConnectionListener, String> entry : mListeners.entrySet()) { + TransportConnectionListener listener = entry.getKey(); + String caller = entry.getValue(); + notifyListener(listener, transport, caller); + } + mListeners.clear(); + } + + @GuardedBy("mStateLock") + private void setStateLocked(@State int state, @Nullable IBackupTransport transport) { + log(Log.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state)); + mState = state; + mTransport = transport; + } + + @GuardedBy("mStateLock") + private void checkStateIntegrityLocked() { + switch (mState) { + case State.UNUSABLE: + checkState(mListeners.isEmpty(), "Unexpected listeners when state = UNUSABLE"); + checkState( + mTransport == null, "Transport expected to be null when state = UNUSABLE"); + case State.IDLE: + checkState(mListeners.isEmpty(), "Unexpected listeners when state = IDLE"); + checkState(mTransport == null, "Transport expected to be null when state = IDLE"); + break; + case State.BOUND_AND_CONNECTING: + checkState( + mTransport == null, + "Transport expected to be null when state = BOUND_AND_CONNECTING"); + break; + case State.CONNECTED: + checkState(mListeners.isEmpty(), "Unexpected listeners when state = CONNECTED"); + checkState( + mTransport != null, + "Transport expected to be non-null when state = CONNECTED"); + break; + default: + checkState(false, "Unexpected state = " + stateToString(mState)); + } + } + + private void checkState(boolean assertion, String message) { + if (!assertion) { + log(Log.ERROR, message); + } + } + + private String stateToString(@State int state) { + switch (state) { + case State.UNUSABLE: + return "UNUSABLE"; + case State.IDLE: + return "IDLE"; + case State.BOUND_AND_CONNECTING: + return "BOUND_AND_CONNECTING"; + case State.CONNECTED: + return "CONNECTED"; + default: + return "<UNKNOWN = " + state + ">"; + } + } + + private void log(int priority, String message) { + TransportUtils.log(priority, TAG, message); + } + + private void log(int priority, String caller, String msg) { + TransportUtils.log(priority, TAG, mPrefixForLog, caller, msg); + // TODO(brufino): Log in internal list for dump + // CharSequence time = DateFormat.format("yyyy-MM-dd HH:mm:ss", System.currentTimeMillis()); + } + + @IntDef({State.UNUSABLE, State.IDLE, State.BOUND_AND_CONNECTING, State.CONNECTED}) + @Retention(RetentionPolicy.SOURCE) + private @interface State { + int UNUSABLE = 0; + int IDLE = 1; + int BOUND_AND_CONNECTING = 2; + int CONNECTED = 3; + } +} diff --git a/com/android/server/backup/transport/TransportClientManager.java b/com/android/server/backup/transport/TransportClientManager.java new file mode 100644 index 00000000..1cbe7471 --- /dev/null +++ b/com/android/server/backup/transport/TransportClientManager.java @@ -0,0 +1,83 @@ +/* + * 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 com.android.server.backup.transport; + +import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import com.android.server.backup.TransportManager; + +/** + * Manages the creation and disposal of {@link TransportClient}s. The only class that should use + * this is {@link TransportManager}, all the other usages should go to {@link TransportManager}. + * + * <p>TODO(brufino): Implement pool of TransportClients + */ +public class TransportClientManager { + private static final String TAG = "TransportClientManager"; + + private final Context mContext; + private final Object mTransportClientsLock = new Object(); + private int mTransportClientsCreated = 0; + + public TransportClientManager(Context context) { + mContext = context; + } + + /** + * Retrieves a {@link TransportClient} for the transport identified by {@param + * transportComponent}. + * + * @param transportComponent The {@link ComponentName} of the transport. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient}. + */ + public TransportClient getTransportClient(ComponentName transportComponent, String caller) { + Intent bindIntent = + new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent); + synchronized (mTransportClientsLock) { + TransportClient transportClient = + new TransportClient( + mContext, + bindIntent, + transportComponent, + Integer.toString(mTransportClientsCreated)); + mTransportClientsCreated++; + TransportUtils.log(Log.DEBUG, TAG, caller, "Retrieving " + transportClient); + return transportClient; + } + } + + /** + * Disposes of the {@link TransportClient}. + * + * @param transportClient The {@link TransportClient} to be disposed of. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + */ + public void disposeOfTransportClient(TransportClient transportClient, String caller) { + TransportUtils.log(Log.DEBUG, TAG, caller, "Disposing of " + transportClient); + transportClient.unbind(caller); + } +} diff --git a/com/android/server/backup/transport/TransportClientTest.java b/com/android/server/backup/transport/TransportClientTest.java new file mode 100644 index 00000000..54d233a9 --- /dev/null +++ b/com/android/server/backup/transport/TransportClientTest.java @@ -0,0 +1,240 @@ +/* + * 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 com.android.server.backup.transport; + +import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; + +import com.android.internal.backup.IBackupTransport; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLooper; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE, sdk = 23) +@Presubmit +public class TransportClientTest { + private static final String PACKAGE_NAME = "some.package.name"; + private static final ComponentName TRANSPORT_COMPONENT = + new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".transport.Transport"); + + @Mock private Context mContext; + @Mock private TransportConnectionListener mTransportConnectionListener; + @Mock private TransportConnectionListener mTransportConnectionListener2; + @Mock private IBackupTransport.Stub mIBackupTransport; + private TransportClient mTransportClient; + private Intent mBindIntent; + private ShadowLooper mShadowLooper; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + Looper mainLooper = Looper.getMainLooper(); + mShadowLooper = shadowOf(mainLooper); + mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(TRANSPORT_COMPONENT); + mTransportClient = + new TransportClient( + mContext, mBindIntent, TRANSPORT_COMPONENT, "1", new Handler(mainLooper)); + + when(mContext.bindServiceAsUser( + eq(mBindIntent), + any(ServiceConnection.class), + anyInt(), + any(UserHandle.class))) + .thenReturn(true); + } + + // TODO: Testing implementation? Remove? + @Test + public void testConnectAsync_callsBindService() throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + + verify(mContext) + .bindServiceAsUser( + eq(mBindIntent), + any(ServiceConnection.class), + anyInt(), + any(UserHandle.class)); + } + + @Test + public void testConnectAsync_callsListenerWhenConnected() throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + + // Simulate framework connecting + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportConnectionListener) + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_whenPendingConnection_callsAllListenersWhenConnected() + throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + + mTransportClient.connectAsync(mTransportConnectionListener2, "caller2"); + + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportConnectionListener) + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + verify(mTransportConnectionListener2) + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_whenAlreadyConnected_callsListener() throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + + mTransportClient.connectAsync(mTransportConnectionListener2, "caller2"); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportConnectionListener2) + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_whenFrameworkDoesntBind_callsListener() throws Exception { + when(mContext.bindServiceAsUser( + eq(mBindIntent), + any(ServiceConnection.class), + anyInt(), + any(UserHandle.class))) + .thenReturn(false); + + mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportConnectionListener) + .onTransportConnectionResult(isNull(), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_whenFrameworkDoesntBind_releasesConnection() throws Exception { + when(mContext.bindServiceAsUser( + eq(mBindIntent), + any(ServiceConnection.class), + anyInt(), + any(UserHandle.class))) + .thenReturn(false); + + mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + verify(mContext).unbindService(eq(connection)); + } + + @Test + public void testConnectAsync_afterServiceDisconnectedBeforeNewConnection_callsListener() + throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + connection.onServiceDisconnected(TRANSPORT_COMPONENT); + + mTransportClient.connectAsync(mTransportConnectionListener2, "caller1"); + + verify(mTransportConnectionListener2) + .onTransportConnectionResult(isNull(), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_afterServiceDisconnectedAfterNewConnection_callsListener() + throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + connection.onServiceDisconnected(TRANSPORT_COMPONENT); + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + + mTransportClient.connectAsync(mTransportConnectionListener2, "caller1"); + + // Yes, it should return null because the object became unusable, check design doc + verify(mTransportConnectionListener2) + .onTransportConnectionResult(isNull(), eq(mTransportClient)); + } + + // TODO(b/69153972): Support SDK 26 API (ServiceConnection.inBindingDied) for transport tests + /*@Test + public void testConnectAsync_callsListenerIfBindingDies() throws Exception { + mTransportClient.connectAsync(mTransportListener, "caller"); + + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onBindingDied(TRANSPORT_COMPONENT); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_whenPendingConnection_callsListenersIfBindingDies() + throws Exception { + mTransportClient.connectAsync(mTransportListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + + mTransportClient.connectAsync(mTransportListener2, "caller2"); + + connection.onBindingDied(TRANSPORT_COMPONENT); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient)); + verify(mTransportListener2).onTransportBound(isNull(), eq(mTransportClient)); + }*/ + + private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) { + ArgumentCaptor<ServiceConnection> connectionCaptor = + ArgumentCaptor.forClass(ServiceConnection.class); + verify(context) + .bindServiceAsUser( + any(Intent.class), + connectionCaptor.capture(), + anyInt(), + any(UserHandle.class)); + return connectionCaptor.getValue(); + } +} diff --git a/com/android/server/backup/transport/TransportConnectionListener.java b/com/android/server/backup/transport/TransportConnectionListener.java new file mode 100644 index 00000000..1ccffd01 --- /dev/null +++ b/com/android/server/backup/transport/TransportConnectionListener.java @@ -0,0 +1,37 @@ +/* + * 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 com.android.server.backup.transport; + +import android.annotation.Nullable; + +import com.android.internal.backup.IBackupTransport; + +/** + * Listener to be called by {@link TransportClient#connectAsync(TransportConnectionListener, + * String)}. + */ +public interface TransportConnectionListener { + /** + * Called when {@link TransportClient} has a transport binder available or that it decided it + * couldn't obtain one, in which case {@param transport} is null. + * + * @param transport A {@link IBackupTransport} transport binder or null. + * @param transportClient The {@link TransportClient} used to retrieve this transport binder. + */ + void onTransportConnectionResult( + @Nullable IBackupTransport transport, TransportClient transportClient); +} diff --git a/com/android/server/backup/transport/TransportNotAvailableException.java b/com/android/server/backup/transport/TransportNotAvailableException.java new file mode 100644 index 00000000..c4e5a1dd --- /dev/null +++ b/com/android/server/backup/transport/TransportNotAvailableException.java @@ -0,0 +1,32 @@ +/* + * 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 com.android.server.backup.transport; + +import com.android.internal.backup.IBackupTransport; + +/** + * Exception thrown when the {@link IBackupTransport} is not available. This happen when a {@link + * TransportClient} connection attempt fails. Check {@link + * TransportClient#connectAsync(TransportConnectionListener, String)} for when that happens. + * + * @see TransportClient#connectAsync(TransportConnectionListener, String) + */ +public class TransportNotAvailableException extends Exception { + TransportNotAvailableException() { + super("Transport not available"); + } +} diff --git a/com/android/server/backup/transport/TransportUtils.java b/com/android/server/backup/transport/TransportUtils.java new file mode 100644 index 00000000..85599b78 --- /dev/null +++ b/com/android/server/backup/transport/TransportUtils.java @@ -0,0 +1,62 @@ +/* + * 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 com.android.server.backup.transport; + +import android.annotation.Nullable; +import android.os.DeadObjectException; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.backup.IBackupTransport; + +/** Utility methods for transport-related operations. */ +public class TransportUtils { + private static final String TAG = "TransportUtils"; + + /** + * Throws {@link TransportNotAvailableException} if {@param transport} is null. The semantics is + * similar to a {@link DeadObjectException} coming from a dead transport binder. + */ + public static IBackupTransport checkTransport(@Nullable IBackupTransport transport) + throws TransportNotAvailableException { + if (transport == null) { + log(Log.ERROR, TAG, "Transport not available"); + throw new TransportNotAvailableException(); + } + return transport; + } + + static void log(int priority, String tag, String message) { + log(priority, tag, null, message); + } + + static void log(int priority, String tag, @Nullable String caller, String message) { + log(priority, tag, "", caller, message); + } + + static void log( + int priority, String tag, String prefix, @Nullable String caller, String message) { + if (Log.isLoggable(tag, priority)) { + if (caller != null) { + prefix += "[" + caller + "] "; + } + Slog.println(priority, tag, prefix + message); + } + } + + private TransportUtils() {} +} diff --git a/com/android/server/broadcastradio/Tuner.java b/com/android/server/broadcastradio/Tuner.java index e6ae320c..2ea42718 100644 --- a/com/android/server/broadcastradio/Tuner.java +++ b/com/android/server/broadcastradio/Tuner.java @@ -27,8 +27,10 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; class Tuner extends ITuner.Stub { private static final String TAG = "BroadcastRadioService.Tuner"; @@ -96,6 +98,10 @@ class Tuner extends ITuner.Stub { private native boolean nativeIsAnalogForced(long nativeContext); private native void nativeSetAnalogForced(long nativeContext, boolean isForced); + private native Map<String, String> nativeSetParameters(long nativeContext, + Map<String, String> parameters); + private native Map<String, String> nativeGetParameters(long nativeContext, List<String> keys); + private native boolean nativeIsAntennaConnected(long nativeContext); @Override @@ -273,6 +279,31 @@ class Tuner extends ITuner.Stub { } @Override + public Map setParameters(Map parameters) { + Map<String, String> results; + synchronized (mLock) { + checkNotClosedLocked(); + results = nativeSetParameters(mNativeContext, Objects.requireNonNull(parameters)); + } + if (results == null) return Collections.emptyMap(); + return results; + } + + @Override + public Map getParameters(List<String> keys) { + if (keys == null) { + throw new IllegalArgumentException("The argument must not be a null pointer"); + } + Map<String, String> results; + synchronized (mLock) { + checkNotClosedLocked(); + results = nativeGetParameters(mNativeContext, keys); + } + if (results == null) return Collections.emptyMap(); + return results; + } + + @Override public boolean isAntennaConnected() { synchronized (mLock) { checkNotClosedLocked(); diff --git a/com/android/server/broadcastradio/TunerCallback.java b/com/android/server/broadcastradio/TunerCallback.java index a87ae8d6..2460c67a 100644 --- a/com/android/server/broadcastradio/TunerCallback.java +++ b/com/android/server/broadcastradio/TunerCallback.java @@ -26,6 +26,9 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import java.util.List; +import java.util.Map; + class TunerCallback implements ITunerCallback { private static final String TAG = "BroadcastRadioService.TunerCallback"; @@ -121,6 +124,11 @@ class TunerCallback implements ITunerCallback { } @Override + public void onParametersUpdated(Map parameters) { + dispatch(() -> mClientCallback.onParametersUpdated(parameters)); + } + + @Override public IBinder asBinder() { throw new RuntimeException("Not a binder"); } diff --git a/com/android/server/connectivity/NetdEventListenerService.java b/com/android/server/connectivity/NetdEventListenerService.java index 4bdbbe39..e243e56c 100644 --- a/com/android/server/connectivity/NetdEventListenerService.java +++ b/com/android/server/connectivity/NetdEventListenerService.java @@ -21,6 +21,7 @@ import static android.util.TimeUtils.NANOS_PER_MS; import android.content.Context; import android.net.ConnectivityManager; import android.net.INetdEventCallback; +import android.net.MacAddress; import android.net.Network; import android.net.NetworkCapabilities; import android.net.metrics.ConnectStats; @@ -35,6 +36,7 @@ import android.text.format.DateUtils; import android.util.Log; import android.util.ArrayMap; import android.util.SparseArray; +import android.util.StatsLog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -242,13 +244,17 @@ public class NetdEventListenerService extends INetdEventListener.Stub { event.timestampMs = timestampMs; event.uid = uid; event.ethertype = ethertype; - event.dstHwAddr = dstHw; + event.dstHwAddr = new MacAddress(dstHw); event.srcIp = srcIp; event.dstIp = dstIp; event.ipNextHeader = ipNextHeader; event.srcPort = srcPort; event.dstPort = dstPort; addWakeupEvent(event); + + String dstMac = event.dstHwAddr.toString(); + StatsLog.write(StatsLog.PACKET_WAKEUP_OCCURRED, + uid, iface, ethertype, dstMac, srcIp, dstIp, ipNextHeader, srcPort, dstPort); } private void addWakeupEvent(WakeupEvent event) { diff --git a/com/android/server/devicepolicy/DevicePolicyManagerService.java b/com/android/server/devicepolicy/DevicePolicyManagerService.java index 2d8a0ee0..60c36d1b 100644 --- a/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -4917,7 +4917,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean installKeyPair(ComponentName who, String callerPackage, byte[] privKey, - byte[] cert, byte[] chain, String alias, boolean requestAccess) { + byte[] cert, byte[] chain, String alias, boolean requestAccess, + boolean isUserSelectable) { enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, DELEGATION_CERT_INSTALL); @@ -4935,6 +4936,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (requestAccess) { keyChain.setGrant(callingUid, alias, true); } + keyChain.setUserSelectable(alias, isUserSelectable); return true; } catch (RemoteException e) { Log.e(LOG_TAG, "Installing certificate", e); diff --git a/com/android/server/devicepolicy/NetworkLoggingHandler.java b/com/android/server/devicepolicy/NetworkLoggingHandler.java index 60863549..4a6bee5c 100644 --- a/com/android/server/devicepolicy/NetworkLoggingHandler.java +++ b/com/android/server/devicepolicy/NetworkLoggingHandler.java @@ -64,6 +64,8 @@ final class NetworkLoggingHandler extends Handler { private final DevicePolicyManagerService mDpm; private final AlarmManager mAlarmManager; + private long mId; + private final OnAlarmListener mBatchTimeoutAlarmListener = new OnAlarmListener() { @Override public void onAlarm() { @@ -185,6 +187,10 @@ final class NetworkLoggingHandler extends Handler { private Bundle finalizeBatchAndBuildDeviceOwnerMessageLocked() { Bundle notificationExtras = null; if (mNetworkEvents.size() > 0) { + // Assign ids to the events. + for (NetworkEvent event : mNetworkEvents) { + event.setId(mId++); + } // Finalize the batch and start a new one from scratch. if (mBatches.size() >= MAX_BATCHES) { // Remove the oldest batch if we hit the limit. diff --git a/com/android/server/display/BrightnessIdleJob.java b/com/android/server/display/BrightnessIdleJob.java new file mode 100644 index 00000000..876acf45 --- /dev/null +++ b/com/android/server/display/BrightnessIdleJob.java @@ -0,0 +1,81 @@ +/* + * 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 com.android.server.display; + + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.hardware.display.DisplayManagerInternal; +import android.util.Slog; + +import com.android.server.LocalServices; + +import java.util.concurrent.TimeUnit; + +/** + * JobService used to persists brightness slider events when the device + * is idle and charging. + */ +public class BrightnessIdleJob extends JobService { + + // Must be unique within the system server uid. + private static final int JOB_ID = 3923512; + + public static void scheduleJob(Context context) { + JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); + + JobInfo pending = jobScheduler.getPendingJob(JOB_ID); + JobInfo jobInfo = + new JobInfo.Builder(JOB_ID, new ComponentName(context, BrightnessIdleJob.class)) + .setRequiresDeviceIdle(true) + .setRequiresCharging(true) + .setPeriodic(TimeUnit.HOURS.toMillis(24)).build(); + + if (pending != null && !pending.equals(jobInfo)) { + jobScheduler.cancel(JOB_ID); + pending = null; + } + + if (pending == null) { + jobScheduler.schedule(jobInfo); + } + } + + public static void cancelJob(Context context) { + JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); + jobScheduler.cancel(JOB_ID); + } + + @Override + public boolean onStartJob(JobParameters params) { + if (BrightnessTracker.DEBUG) { + Slog.d(BrightnessTracker.TAG, "Scheduled write of brightness events"); + } + DisplayManagerInternal dmi = LocalServices.getService(DisplayManagerInternal.class); + dmi.persistBrightnessSliderEvents(); + return false; + } + + @Override + public boolean onStopJob(JobParameters params) { + return false; + } +}
\ No newline at end of file diff --git a/com/android/server/display/BrightnessTracker.java b/com/android/server/display/BrightnessTracker.java index 361d9284..2c6fe948 100644 --- a/com/android/server/display/BrightnessTracker.java +++ b/com/android/server/display/BrightnessTracker.java @@ -34,6 +34,7 @@ import android.net.Uri; import android.os.BatteryManager; import android.os.Environment; import android.os.Handler; +import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; @@ -61,12 +62,12 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; -import java.util.List; import java.util.concurrent.TimeUnit; /** @@ -75,8 +76,8 @@ import java.util.concurrent.TimeUnit; */ public class BrightnessTracker { - private static final String TAG = "BrightnessTracker"; - private static final boolean DEBUG = false; + static final String TAG = "BrightnessTracker"; + static final boolean DEBUG = false; private static final String EVENTS_FILE = "brightness_events.xml"; private static final int MAX_EVENTS = 100; @@ -103,6 +104,8 @@ public class BrightnessTracker { @GuardedBy("mEventsLock") private RingBuffer<BrightnessChangeEvent> mEvents = new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS); + @GuardedBy("mEventsLock") + private boolean mEventsDirty; private final Runnable mEventsWriter = () -> writeEvents(); private volatile boolean mWriteEventsScheduled; @@ -160,7 +163,10 @@ public class BrightnessTracker { UserHandle.USER_CURRENT); mSensorListener = new SensorListener(); - mInjector.registerSensorListener(mContext, mSensorListener); + + if (mInjector.isInteractive(mContext)) { + mInjector.registerSensorListener(mContext, mSensorListener, mBgHandler); + } mSettingsObserver = new SettingsObserver(mBgHandler); mInjector.registerBrightnessObserver(mContentResolver, mSettingsObserver); @@ -168,8 +174,12 @@ public class BrightnessTracker { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_SHUTDOWN); intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); + intentFilter.addAction(Intent.ACTION_SCREEN_ON); + intentFilter.addAction(Intent.ACTION_SCREEN_OFF); mBroadcastReceiver = new Receiver(); mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter); + + mInjector.scheduleIdleJob(mContext); } /** Stop listening for events */ @@ -181,6 +191,7 @@ public class BrightnessTracker { mInjector.unregisterSensorListener(mContext, mSensorListener); mInjector.unregisterReceiver(mContext, mBroadcastReceiver); mInjector.unregisterBrightnessObserver(mContext, mSettingsObserver); + mInjector.cancelIdleJob(mContext); } /** @@ -211,6 +222,10 @@ public class BrightnessTracker { brightness, userId); } + public void persistEvents() { + scheduleWriteEvents(); + } + private void handleBrightnessChanged() { if (DEBUG) { Slog.d(TAG, "Brightness change"); @@ -278,6 +293,7 @@ public class BrightnessTracker { Slog.d(TAG, "Event " + event.brightness + " " + event.packageName); } synchronized (mEventsLock) { + mEventsDirty = true; mEvents.append(event); } } @@ -291,8 +307,12 @@ public class BrightnessTracker { private void writeEvents() { mWriteEventsScheduled = false; - // TODO kick off write on handler thread e.g. every 24 hours. synchronized (mEventsLock) { + if (!mEventsDirty) { + // Nothing to write + return; + } + final AtomicFile writeTo = mInjector.getFile(); if (writeTo == null) { return; @@ -301,12 +321,14 @@ public class BrightnessTracker { if (writeTo.exists()) { writeTo.delete(); } + mEventsDirty = false; } else { FileOutputStream output = null; try { output = writeTo.startWrite(); writeEventsLocked(output); writeTo.finishWrite(output); + mEventsDirty = false; } catch (IOException e) { writeTo.failWrite(output); Slog.e(TAG, "Failed to write change mEvents.", e); @@ -317,6 +339,8 @@ public class BrightnessTracker { private void readEvents() { synchronized (mEventsLock) { + // Read might prune events so mark as dirty. + mEventsDirty = true; mEvents.clear(); final AtomicFile readFrom = mInjector.getFile(); if (readFrom != null && readFrom.exists()) { @@ -344,13 +368,16 @@ public class BrightnessTracker { out.startTag(null, TAG_EVENTS); BrightnessChangeEvent[] toWrite = mEvents.toArray(); + // Clear events, code below will add back the ones that are still within the time window. + mEvents.clear(); if (DEBUG) { Slog.d(TAG, "Writing events " + toWrite.length); } - final long timeCutOff = System.currentTimeMillis() - MAX_EVENT_AGE; + final long timeCutOff = mInjector.currentTimeMillis() - MAX_EVENT_AGE; for (int i = 0; i < toWrite.length; ++i) { int userSerialNo = mInjector.getUserSerialNumber(mUserManager, toWrite[i].userId); if (userSerialNo != -1 && toWrite[i].timeStamp > timeCutOff) { + mEvents.append(toWrite[i]); out.startTag(null, TAG_EVENT); out.attribute(null, ATTR_BRIGHTNESS, Integer.toString(toWrite[i].brightness)); out.attribute(null, ATTR_TIMESTAMP, Long.toString(toWrite[i].timeStamp)); @@ -465,6 +492,17 @@ public class BrightnessTracker { } } + public void dump(PrintWriter pw) { + synchronized (mEventsLock) { + pw.println("BrightnessTracker state:"); + pw.println(" mEvents.size=" + mEvents.size()); + pw.println(" mEventsDirty=" + mEventsDirty); + } + synchronized (mDataCollectionLock) { + pw.println(" mLastSensorReadings.size=" + mLastSensorReadings.size()); + } + } + // Not allowed to keep the SensorEvent so used to copy the data we care about. private static class LightData { public float lux; @@ -552,6 +590,11 @@ public class BrightnessTracker { if (level != -1 && scale != 0) { batteryLevelChanged(level, scale); } + } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { + mInjector.unregisterSensorListener(mContext, mSensorListener); + } else if (Intent.ACTION_SCREEN_ON.equals(action)) { + mInjector.registerSensorListener(mContext, mSensorListener, + mInjector.getBackgroundHandler()); } } } @@ -559,11 +602,11 @@ public class BrightnessTracker { @VisibleForTesting static class Injector { public void registerSensorListener(Context context, - SensorEventListener sensorListener) { + SensorEventListener sensorListener, Handler handler) { SensorManager sensorManager = context.getSystemService(SensorManager.class); Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); sensorManager.registerListener(sensorListener, - lightSensor, SensorManager.SENSOR_DELAY_NORMAL); + lightSensor, SensorManager.SENSOR_DELAY_NORMAL, handler); } public void unregisterSensorListener(Context context, SensorEventListener sensorListener) { @@ -635,5 +678,17 @@ public class BrightnessTracker { public ActivityManager.StackInfo getFocusedStack() throws RemoteException { return ActivityManager.getService().getFocusedStackInfo(); } + + public void scheduleIdleJob(Context context) { + BrightnessIdleJob.scheduleJob(context); + } + + public void cancelIdleJob(Context context) { + BrightnessIdleJob.cancelJob(context); + } + + public boolean isInteractive(Context context) { + return context.getSystemService(PowerManager.class).isInteractive(); + } } } diff --git a/com/android/server/display/DisplayManagerService.java b/com/android/server/display/DisplayManagerService.java index f1e20116..379aaadc 100644 --- a/com/android/server/display/DisplayManagerService.java +++ b/com/android/server/display/DisplayManagerService.java @@ -68,13 +68,13 @@ import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; import android.view.Surface; -import android.view.WindowManagerInternal; import com.android.server.AnimationThread; import com.android.server.DisplayThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.UiThread; +import com.android.server.wm.WindowManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -1285,6 +1285,9 @@ public final class DisplayManagerService extends SystemService { pw.println(); mPersistentDataStore.dump(pw); + + pw.println(); + mBrightnessTracker.dump(pw); } } @@ -1921,5 +1924,10 @@ public final class DisplayManagerService extends SystemService { public boolean isUidPresentOnDisplay(int uid, int displayId) { return isUidPresentOnDisplayInternal(uid, displayId); } + + @Override + public void persistBrightnessSliderEvents() { + mBrightnessTracker.persistEvents(); + } } } diff --git a/com/android/server/display/DisplayPowerController.java b/com/android/server/display/DisplayPowerController.java index 600bc42e..29a007a3 100644 --- a/com/android/server/display/DisplayPowerController.java +++ b/com/android/server/display/DisplayPowerController.java @@ -20,6 +20,7 @@ import android.app.ActivityManager; import com.android.internal.app.IBatteryStats; import com.android.server.LocalServices; import com.android.server.am.BatteryStatsService; +import com.android.server.policy.WindowManagerPolicy; import android.animation.Animator; import android.animation.ObjectAnimator; @@ -43,7 +44,6 @@ import android.util.Slog; import android.util.Spline; import android.util.TimeUtils; import android.view.Display; -import android.view.WindowManagerPolicy; import java.io.PrintWriter; diff --git a/com/android/server/input/InputManagerService.java b/com/android/server/input/InputManagerService.java index fa9b1078..4050790c 100644 --- a/com/android/server/input/InputManagerService.java +++ b/com/android/server/input/InputManagerService.java @@ -33,6 +33,7 @@ import com.android.internal.util.XmlUtils; import com.android.server.DisplayThread; import com.android.server.LocalServices; import com.android.server.Watchdog; +import com.android.server.policy.WindowManagerPolicy; import org.xmlpull.v1.XmlPullParser; @@ -98,7 +99,6 @@ import android.view.KeyEvent; import android.view.PointerIcon; import android.view.Surface; import android.view.ViewConfiguration; -import android.view.WindowManagerPolicy; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; diff --git a/com/android/server/job/JobSchedulerInternal.java b/com/android/server/job/JobSchedulerInternal.java index 095526dc..9bcf208a 100644 --- a/com/android/server/job/JobSchedulerInternal.java +++ b/com/android/server/job/JobSchedulerInternal.java @@ -26,6 +26,18 @@ import java.util.List; */ public interface JobSchedulerInternal { + // Bookkeeping about app standby bucket scheduling + + /** + * The current bucket heartbeat ordinal + */ + long currentHeartbeat(); + + /** + * Heartbeat ordinal at which the given standby bucket's jobs next become runnable + */ + long nextHeartbeatForBucket(int bucket); + /** * Returns a list of pending jobs scheduled by the system service. */ diff --git a/com/android/server/job/JobSchedulerService.java b/com/android/server/job/JobSchedulerService.java index b5497681..4af86a05 100644 --- a/com/android/server/job/JobSchedulerService.java +++ b/com/android/server/job/JobSchedulerService.java @@ -29,6 +29,9 @@ import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobService; import android.app.job.JobWorkItem; +import android.app.usage.AppStandby; +import android.app.usage.UsageStatsManagerInternal; +import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -37,6 +40,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ServiceInfo; import android.database.ContentObserver; @@ -46,7 +50,6 @@ import android.os.Binder; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; @@ -65,6 +68,7 @@ import android.util.TimeUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.app.procstats.ProcessStats; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.server.DeviceIdleController; @@ -111,6 +115,7 @@ public final class JobSchedulerService extends com.android.server.SystemService implements StateChangedListener, JobCompletedListener { static final String TAG = "JobSchedulerService"; public static final boolean DEBUG = false; + public static final boolean DEBUG_STANDBY = DEBUG || false; /** The maximum number of concurrent jobs we run at one time. */ private static final int MAX_JOB_CONTEXTS_COUNT = 16; @@ -130,6 +135,8 @@ public final class JobSchedulerService extends com.android.server.SystemService final Object mLock = new Object(); /** Master list of jobs. */ final JobStore mJobs; + /** Tracking the standby bucket state of each app */ + final StandbyTracker mStandbyTracker; /** Tracking amount of time each package runs for. */ final JobPackageTracker mJobPackageTracker = new JobPackageTracker(); @@ -151,6 +158,7 @@ public final class JobSchedulerService extends com.android.server.SystemService StorageController mStorageController; /** Need directly for sending uid state changes */ private BackgroundJobsController mBackgroundJobsController; + private DeviceIdleJobsController mDeviceIdleJobsController; /** * Queue of pending jobs. The JobServiceContext class will receive jobs from this list * when ready to execute them. @@ -162,8 +170,8 @@ public final class JobSchedulerService extends com.android.server.SystemService final JobHandler mHandler; final JobSchedulerStub mJobSchedulerStub; + PackageManagerInternal mLocalPM; IBatteryStats mBatteryStats; - PowerManager mPowerManager; DeviceIdleController.LocalService mLocalDeviceIdleController; /** @@ -192,6 +200,17 @@ public final class JobSchedulerService extends com.android.server.SystemService */ final SparseIntArray mBackingUpUids = new SparseIntArray(); + /** + * Count standby heartbeats, and keep track of which beat each bucket's jobs will + * next become runnable. Index into this array is by normalized bucket: + * { ACTIVE, WORKING, FREQUENT, RARE, NEVER }. The ACTIVE and NEVER bucket + * milestones are not updated: ACTIVE apps get jobs whenever they ask for them, + * and NEVER apps don't get them at all. + */ + final long[] mNextBucketHeartbeat = { 0, 0, 0, 0, Long.MAX_VALUE }; + long mHeartbeat = 0; + long mLastHeartbeatTime = 0; + // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked -- /** @@ -236,6 +255,10 @@ public final class JobSchedulerService extends com.android.server.SystemService private static final String KEY_MAX_WORK_RESCHEDULE_COUNT = "max_work_reschedule_count"; private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time"; private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time"; + private static final String KEY_STANDBY_HEARTBEAT_TIME = "standby_heartbeat_time"; + private static final String KEY_STANDBY_WORKING_BEATS = "standby_working_beats"; + private static final String KEY_STANDBY_FREQUENT_BEATS = "standby_frequent_beats"; + private static final String KEY_STANDBY_RARE_BEATS = "standby_rare_beats"; private static final int DEFAULT_MIN_IDLE_COUNT = 1; private static final int DEFAULT_MIN_CHARGING_COUNT = 1; @@ -255,6 +278,10 @@ public final class JobSchedulerService extends com.android.server.SystemService private static final int DEFAULT_MAX_WORK_RESCHEDULE_COUNT = Integer.MAX_VALUE; private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS; private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS; + private static final long DEFAULT_STANDBY_HEARTBEAT_TIME = 11 * 60 * 1000L; + private static final int DEFAULT_STANDBY_WORKING_BEATS = 5; // ~ 1 hour, with 11-min beats + private static final int DEFAULT_STANDBY_FREQUENT_BEATS = 31; // ~ 6 hours + private static final int DEFAULT_STANDBY_RARE_BEATS = 130; // ~ 24 hours /** * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things @@ -343,6 +370,27 @@ public final class JobSchedulerService extends com.android.server.SystemService * The minimum backoff time to allow for exponential backoff. */ long MIN_EXP_BACKOFF_TIME = DEFAULT_MIN_EXP_BACKOFF_TIME; + /** + * How often we recalculate runnability based on apps' standby bucket assignment. + * This should be prime relative to common time interval lengths such as a quarter- + * hour or day, so that the heartbeat drifts relative to wall-clock milestones. + */ + long STANDBY_HEARTBEAT_TIME = DEFAULT_STANDBY_HEARTBEAT_TIME; + + /** + * Mapping: standby bucket -> number of heartbeats between each sweep of that + * bucket's jobs. + * + * Bucket assignments as recorded in the JobStatus objects are normalized to be + * indices into this array, rather than the raw constants used + * by AppIdleHistory. + */ + final int[] STANDBY_BEATS = { + 0, + DEFAULT_STANDBY_WORKING_BEATS, + DEFAULT_STANDBY_FREQUENT_BEATS, + DEFAULT_STANDBY_RARE_BEATS + }; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -422,6 +470,14 @@ public final class JobSchedulerService extends com.android.server.SystemService DEFAULT_MIN_LINEAR_BACKOFF_TIME); MIN_EXP_BACKOFF_TIME = mParser.getLong(KEY_MIN_EXP_BACKOFF_TIME, DEFAULT_MIN_EXP_BACKOFF_TIME); + STANDBY_HEARTBEAT_TIME = mParser.getLong(KEY_STANDBY_HEARTBEAT_TIME, + DEFAULT_STANDBY_HEARTBEAT_TIME); + STANDBY_BEATS[1] = mParser.getInt(KEY_STANDBY_WORKING_BEATS, + DEFAULT_STANDBY_WORKING_BEATS); + STANDBY_BEATS[2] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS, + DEFAULT_STANDBY_FREQUENT_BEATS); + STANDBY_BEATS[3] = mParser.getInt(KEY_STANDBY_RARE_BEATS, + DEFAULT_STANDBY_RARE_BEATS); } } @@ -481,6 +537,17 @@ public final class JobSchedulerService extends com.android.server.SystemService pw.print(" "); pw.print(KEY_MIN_EXP_BACKOFF_TIME); pw.print("="); pw.print(MIN_EXP_BACKOFF_TIME); pw.println(); + + pw.print(" "); pw.print(KEY_STANDBY_HEARTBEAT_TIME); pw.print("="); + pw.print(STANDBY_HEARTBEAT_TIME); pw.println(); + + pw.print(" standby_beats={"); + pw.print(STANDBY_BEATS[0]); + for (int i = 1; i < STANDBY_BEATS.length; i++) { + pw.print(", "); + pw.print(STANDBY_BEATS[i]); + } + pw.println('}'); } } @@ -623,13 +690,13 @@ public final class JobSchedulerService extends com.android.server.SystemService cancelJobsForUid(uid, "uid gone"); } synchronized (mLock) { - mBackgroundJobsController.setUidActiveLocked(uid, false); + mDeviceIdleJobsController.setUidActiveLocked(uid, false); } } @Override public void onUidActive(int uid) throws RemoteException { synchronized (mLock) { - mBackgroundJobsController.setUidActiveLocked(uid, true); + mDeviceIdleJobsController.setUidActiveLocked(uid, true); } } @@ -638,7 +705,7 @@ public final class JobSchedulerService extends com.android.server.SystemService cancelJobsForUid(uid, "app uid idle"); } synchronized (mLock) { - mBackgroundJobsController.setUidActiveLocked(uid, false); + mDeviceIdleJobsController.setUidActiveLocked(uid, false); } } @@ -683,6 +750,7 @@ public final class JobSchedulerService extends com.android.server.SystemService } } catch (RemoteException e) { } + synchronized (mLock) { final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId()); @@ -934,9 +1002,22 @@ public final class JobSchedulerService extends com.android.server.SystemService */ public JobSchedulerService(Context context) { super(context); + + mLocalPM = LocalServices.getService(PackageManagerInternal.class); + mHandler = new JobHandler(context.getMainLooper()); mConstants = new Constants(mHandler); mJobSchedulerStub = new JobSchedulerStub(); + + // Set up the app standby bucketing tracker + UsageStatsManagerInternal usageStats = LocalServices.getService(UsageStatsManagerInternal.class); + mStandbyTracker = new StandbyTracker(usageStats); + usageStats.addAppIdleStateChangeListener(mStandbyTracker); + + // The job store needs to call back + publishLocalService(JobSchedulerInternal.class, new LocalService()); + + // Initialize the job store and set up any persisted jobs mJobs = JobStore.initAndGet(this); // Create the controllers. @@ -948,11 +1029,11 @@ public final class JobSchedulerService extends com.android.server.SystemService mControllers.add(mBatteryController); mStorageController = StorageController.get(this); mControllers.add(mStorageController); - mBackgroundJobsController = BackgroundJobsController.get(this); - mControllers.add(mBackgroundJobsController); + mControllers.add(BackgroundJobsController.get(this)); mControllers.add(AppIdleController.get(this)); mControllers.add(ContentObserverController.get(this)); - mControllers.add(DeviceIdleJobsController.get(this)); + mDeviceIdleJobsController = DeviceIdleJobsController.get(this); + mControllers.add(mDeviceIdleJobsController); // If the job store determined that it can't yet reschedule persisted jobs, // we need to start watching the clock. @@ -1006,7 +1087,6 @@ public final class JobSchedulerService extends com.android.server.SystemService @Override public void onStart() { - publishLocalService(JobSchedulerInternal.class, new LocalService()); publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub); } @@ -1026,7 +1106,6 @@ public final class JobSchedulerService extends com.android.server.SystemService final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED); getContext().registerReceiverAsUser( mBroadcastReceiver, UserHandle.ALL, userFilter, null, null); - mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE); try { ActivityManager.getService().registerUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE @@ -1206,7 +1285,8 @@ public final class JobSchedulerService extends com.android.server.SystemService } delayMillis = Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS); - JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis, + JobStatus newJob = new JobStatus(failureToReschedule, getCurrentHeartbeat(), + elapsedNowMillis + delayMillis, JobStatus.NO_LATEST_RUNTIME, backoffAttempts, failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis()); for (int ic=0; ic<mControllers.size(); ic++) { @@ -1220,10 +1300,12 @@ public final class JobSchedulerService extends com.android.server.SystemService * Called after a periodic has executed so we can reschedule it. We take the last execution * time of the job to be the time of completion (i.e. the time at which this function is * called). - * This could be inaccurate b/c the job can run for as long as + * <p>This could be inaccurate b/c the job can run for as long as * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead * to underscheduling at least, rather than if we had taken the last execution time to be the * start of the execution. + * <p>Unlike a reschedule prior to execution, in this case we advance the next-heartbeat + * tracking as though the job were newly-scheduled. * @return A new job representing the execution criteria for this instantiation of the * recurring job. */ @@ -1245,8 +1327,9 @@ public final class JobSchedulerService extends com.android.server.SystemService Slog.v(TAG, "Rescheduling executed periodic. New execution window [" + newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s"); } - return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed, - newLatestRuntimeElapsed, 0 /* backoffAttempt */, + return new JobStatus(periodicToReschedule, getCurrentHeartbeat(), + newEarliestRunTimeElapsed, newLatestRuntimeElapsed, + 0 /* backoffAttempt */, sSystemClock.millis() /* lastSuccessfulRunTime */, periodicToReschedule.getLastFailedRunTime()); } @@ -1393,7 +1476,9 @@ public final class JobSchedulerService extends com.android.server.SystemService noteJobsNonpending(mPendingJobs); mPendingJobs.clear(); stopNonReadyActiveJobsLocked(); + boolean updated = updateStandbyHeartbeatLocked(); mJobs.forEachJob(mReadyQueueFunctor); + if (updated) updateNextStandbyHeartbeatsLocked(); mReadyQueueFunctor.postProcess(); if (DEBUG) { @@ -1547,16 +1632,45 @@ public final class JobSchedulerService extends com.android.server.SystemService noteJobsNonpending(mPendingJobs); mPendingJobs.clear(); stopNonReadyActiveJobsLocked(); + boolean updated = updateStandbyHeartbeatLocked(); mJobs.forEachJob(mMaybeQueueFunctor); + if (updated) updateNextStandbyHeartbeatsLocked(); mMaybeQueueFunctor.postProcess(); } + private boolean updateStandbyHeartbeatLocked() { + final long sinceLast = sElapsedRealtimeClock.millis() - mLastHeartbeatTime; + final long beatsElapsed = sinceLast / mConstants.STANDBY_HEARTBEAT_TIME; + if (beatsElapsed > 0) { + mHeartbeat += beatsElapsed; + mLastHeartbeatTime += beatsElapsed * mConstants.STANDBY_HEARTBEAT_TIME; + if (DEBUG_STANDBY) { + Slog.v(TAG, "Advancing standby heartbeat by " + beatsElapsed + " to " + mHeartbeat); + } + return true; + } + return false; + } + + private void updateNextStandbyHeartbeatsLocked() { + // don't update ACTIVE or NEVER bucket milestones + for (int i = 1; i < mNextBucketHeartbeat.length - 1; i++) { + while (mHeartbeat >= mNextBucketHeartbeat[i]) { + mNextBucketHeartbeat[i] += mConstants.STANDBY_BEATS[i]; + } + if (DEBUG_STANDBY) { + Slog.v(TAG, " Bucket " + i + " next heartbeat " + mNextBucketHeartbeat[i]); + } + } + } + /** * Criteria for moving a job into the pending queue: * - It's ready. * - It's not pending. * - It's not already running on a JSC. * - The user that requested the job is running. + * - The job's standby bucket has come due to be runnable. * - The component is enabled and runnable. */ private boolean isReadyToBeExecutedLocked(JobStatus job) { @@ -1605,6 +1719,22 @@ public final class JobSchedulerService extends com.android.server.SystemService return false; } + // If the app is in a non-active standby bucket, make sure we've waited + // an appropriate amount of time since the last invocation + if (mHeartbeat < mNextBucketHeartbeat[job.getStandbyBucket()]) { + // TODO: log/trace that we're deferring the job due to bucketing if we hit this + if (job.getWhenStandbyDeferred() == 0) { + if (DEBUG_STANDBY) { + Slog.v(TAG, "Bucket deferral: " + mHeartbeat + " < " + + mNextBucketHeartbeat[job.getStandbyBucket()] + " for " + job); + } + job.setWhenStandbyDeferred(sElapsedRealtimeClock.millis()); + } + return false; + } + + // The expensive check last: validate that the defined package+service is + // still present & viable. final boolean componentPresent; try { componentPresent = (AppGlobals.getPackageManager().getServiceInfo( @@ -1818,6 +1948,22 @@ public final class JobSchedulerService extends com.android.server.SystemService final class LocalService implements JobSchedulerInternal { /** + * The current bucket heartbeat ordinal + */ + public long currentHeartbeat() { + return getCurrentHeartbeat(); + } + + /** + * Heartbeat ordinal at which the given standby bucket's jobs next become runnable + */ + public long nextHeartbeatForBucket(int bucket) { + synchronized (mLock) { + return mNextBucketHeartbeat[bucket]; + } + } + + /** * Returns a list of all pending jobs. A running job is not considered pending. Periodic * jobs are always considered pending. */ @@ -1878,6 +2024,79 @@ public final class JobSchedulerService extends com.android.server.SystemService } /** + * Tracking of app assignments to standby buckets + */ + final class StandbyTracker extends AppIdleStateChangeListener { + final UsageStatsManagerInternal mUsageStats; + + StandbyTracker(UsageStatsManagerInternal usageStats) { + mUsageStats = usageStats; + } + + // AppIdleStateChangeListener interface for live updates + + @Override + public void onAppIdleStateChanged(final String packageName, final int userId, + boolean idle, int bucket) { + final int uid = mLocalPM.getPackageUid(packageName, + PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); + if (uid < 0) { + if (DEBUG_STANDBY) { + Slog.i(TAG, "App idle state change for unknown app " + + packageName + "/" + userId); + } + return; + } + + final int bucketIndex = standbyBucketToBucketIndex(bucket); + // update job bookkeeping out of band + BackgroundThread.getHandler().post(() -> { + if (DEBUG_STANDBY) { + Slog.i(TAG, "Moving uid " + uid + " to bucketIndex " + bucketIndex); + } + synchronized (mLock) { + // TODO: update to be more efficient once we can slice by source UID + mJobs.forEachJob((JobStatus job) -> { + if (job.getSourceUid() == uid) { + job.setStandbyBucket(bucketIndex); + } + }); + onControllerStateChanged(); + } + }); + } + + @Override + public void onParoleStateChanged(boolean isParoleOn) { + // Unused + } + } + + public static int standbyBucketToBucketIndex(int bucket) { + // Normalize AppStandby constants to indices into our bookkeeping + if (bucket == AppStandby.STANDBY_BUCKET_NEVER) return 4; + else if (bucket >= AppStandby.STANDBY_BUCKET_RARE) return 3; + else if (bucket >= AppStandby.STANDBY_BUCKET_FREQUENT) return 2; + else if (bucket >= AppStandby.STANDBY_BUCKET_WORKING_SET) return 1; + else return 0; + } + + public static int standbyBucketForPackage(String packageName, int userId, long elapsedNow) { + UsageStatsManagerInternal usageStats = LocalServices.getService( + UsageStatsManagerInternal.class); + int bucket = usageStats != null + ? usageStats.getAppStandbyBucket(packageName, userId, elapsedNow) + : 0; + + bucket = standbyBucketToBucketIndex(bucket); + + if (DEBUG_STANDBY) { + Slog.v(TAG, packageName + "/" + userId + " standby bucket index: " + bucket); + } + return bucket; + } + + /** * Binder stub trampoline implementation */ final class JobSchedulerStub extends IJobScheduler.Stub { @@ -1942,6 +2161,7 @@ public final class JobSchedulerService extends com.android.server.SystemService } final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); + final int userId = UserHandle.getUserId(uid); enforceValidJobRequest(uid, job); if (job.isPersisted()) { @@ -1958,7 +2178,8 @@ public final class JobSchedulerService extends com.android.server.SystemService long ident = Binder.clearCallingIdentity(); try { - return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, -1, null); + return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId, + null); } finally { Binder.restoreCallingIdentity(ident); } @@ -1970,8 +2191,8 @@ public final class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "Enqueueing job: " + job.toString() + " work: " + work); } - final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); + final int userId = UserHandle.getUserId(uid); enforceValidJobRequest(uid, job); if (job.isPersisted()) { @@ -1988,7 +2209,8 @@ public final class JobSchedulerService extends com.android.server.SystemService long ident = Binder.clearCallingIdentity(); try { - return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, -1, null); + return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, userId, + null); } finally { Binder.restoreCallingIdentity(ident); } @@ -2000,7 +2222,7 @@ public final class JobSchedulerService extends com.android.server.SystemService final int callerUid = Binder.getCallingUid(); if (DEBUG) { Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString() - + " on behalf of " + packageName); + + " on behalf of " + packageName + "/"); } if (packageName == null) { @@ -2234,6 +2456,12 @@ public final class JobSchedulerService extends com.android.server.SystemService } } + long getCurrentHeartbeat() { + synchronized (mLock) { + return mHeartbeat; + } + } + int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) { try { final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0, diff --git a/com/android/server/job/JobServiceContext.java b/com/android/server/job/JobServiceContext.java index ac7297e6..6a3fd04a 100644 --- a/com/android/server/job/JobServiceContext.java +++ b/com/android/server/job/JobServiceContext.java @@ -64,6 +64,8 @@ import com.android.server.job.controllers.JobStatus; */ public final class JobServiceContext implements ServiceConnection { private static final boolean DEBUG = JobSchedulerService.DEBUG; + private static final boolean DEBUG_STANDBY = JobSchedulerService.DEBUG_STANDBY; + private static final String TAG = "JobServiceContext"; /** Amount of time a job is allowed to execute for before being considered timed-out. */ public static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000; // 10mins. @@ -220,6 +222,17 @@ public final class JobServiceContext implements ServiceConnection { isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network); mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis(); + if (DEBUG_STANDBY) { + final long whenDeferred = job.getWhenStandbyDeferred(); + if (whenDeferred > 0) { + StringBuilder sb = new StringBuilder(128); + sb.append("Starting job deferred for standby by "); + TimeUtils.formatDuration(mExecutionStartTimeElapsed - whenDeferred, sb); + sb.append(" : "); + sb.append(job.toShortString()); + Slog.v(TAG, sb.toString()); + } + } // Once we'e begun executing a job, we by definition no longer care whether // it was inflated from disk with not-yet-coherent delay/deadline bounds. job.clearPersistedUtcTimes(); diff --git a/com/android/server/job/JobStore.java b/com/android/server/job/JobStore.java index 1af3b39e..219bc611 100644 --- a/com/android/server/job/JobStore.java +++ b/com/android/server/job/JobStore.java @@ -43,6 +43,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.BitUtils; import com.android.internal.util.FastXmlSerializer; import com.android.server.IoThread; +import com.android.server.LocalServices; import com.android.server.job.JobSchedulerInternal.JobStorePersistStats; import com.android.server.job.controllers.JobStatus; @@ -174,7 +175,8 @@ public final class JobStore { if (utcTimes != null) { Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(utcTimes, elapsedNow); - toAdd.add(new JobStatus(job, elapsedRuntimes.first, elapsedRuntimes.second, + toAdd.add(new JobStatus(job, job.getBaseHeartbeat(), + elapsedRuntimes.first, elapsedRuntimes.second, 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime())); toRemove.add(job); } @@ -250,7 +252,7 @@ public final class JobStore { /** * @param userHandle User for whom we are querying the list of jobs. - * @return A list of all the jobs scheduled by the provided user. Never null. + * @return A list of all the jobs scheduled for the provided user. Never null. */ public List<JobStatus> getJobsByUser(int userHandle) { return mJobSet.getJobsByUser(userHandle); @@ -287,6 +289,10 @@ public final class JobStore { mJobSet.forEachJob(uid, functor); } + public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) { + mJobSet.forEachJobForSourceUid(sourceUid, functor); + } + public interface JobStatusFunctor { public void process(JobStatus jobStatus); } @@ -842,8 +848,13 @@ public final class JobStore { } // And now we're done + JobSchedulerInternal service = LocalServices.getService(JobSchedulerInternal.class); + final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName, + sourceUserId, elapsedNow); + long currentHeartbeat = service != null ? service.currentHeartbeat() : 0; JobStatus js = new JobStatus( - jobBuilder.build(), uid, sourcePackageName, sourceUserId, sourceTag, + jobBuilder.build(), uid, sourcePackageName, sourceUserId, + appBucket, currentHeartbeat, sourceTag, elapsedRuntimes.first, elapsedRuntimes.second, lastSuccessfulRunTime, lastFailedRunTime, (rtcIsGood) ? null : rtcRuntimes); @@ -979,9 +990,12 @@ public final class JobStore { static final class JobSet { // Key is the getUid() originator of the jobs in each sheaf private SparseArray<ArraySet<JobStatus>> mJobs; + // Same data but with the key as getSourceUid() of the jobs in each sheaf + private SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid; public JobSet() { mJobs = new SparseArray<ArraySet<JobStatus>>(); + mJobsPerSourceUid = new SparseArray<>(); } public List<JobStatus> getJobsByUid(int uid) { @@ -995,10 +1009,10 @@ public final class JobStore { // By user, not by uid, so we need to traverse by key and check public List<JobStatus> getJobsByUser(int userId) { - ArrayList<JobStatus> result = new ArrayList<JobStatus>(); - for (int i = mJobs.size() - 1; i >= 0; i--) { - if (UserHandle.getUserId(mJobs.keyAt(i)) == userId) { - ArraySet<JobStatus> jobs = mJobs.valueAt(i); + final ArrayList<JobStatus> result = new ArrayList<JobStatus>(); + for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) { + if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) { + final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i); if (jobs != null) { result.addAll(jobs); } @@ -1009,32 +1023,60 @@ public final class JobStore { public boolean add(JobStatus job) { final int uid = job.getUid(); + final int sourceUid = job.getSourceUid(); ArraySet<JobStatus> jobs = mJobs.get(uid); if (jobs == null) { jobs = new ArraySet<JobStatus>(); mJobs.put(uid, jobs); } - return jobs.add(job); + ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid); + if (jobsForSourceUid == null) { + jobsForSourceUid = new ArraySet<>(); + mJobsPerSourceUid.put(sourceUid, jobsForSourceUid); + } + return jobs.add(job) && jobsForSourceUid.add(job); } public boolean remove(JobStatus job) { final int uid = job.getUid(); - ArraySet<JobStatus> jobs = mJobs.get(uid); - boolean didRemove = (jobs != null) ? jobs.remove(job) : false; - if (didRemove && jobs.size() == 0) { - // no more jobs for this uid; let the now-empty set object be GC'd. - mJobs.remove(uid); + final ArraySet<JobStatus> jobs = mJobs.get(uid); + final int sourceUid = job.getSourceUid(); + final ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid); + boolean didRemove = jobs != null && jobs.remove(job) && jobsForSourceUid.remove(job); + if (didRemove) { + if (jobs.size() == 0) { + // no more jobs for this uid; let the now-empty set object be GC'd. + mJobs.remove(uid); + } + if (jobsForSourceUid.size() == 0) { + mJobsPerSourceUid.remove(sourceUid); + } + return true; } - return didRemove; + return false; } - // Remove the jobs all users not specified by the whitelist of user ids + /** + * Removes the jobs of all users not specified by the whitelist of user ids. + * The jobs scheduled by non existent users will not be removed if they were + */ public void removeJobsOfNonUsers(int[] whitelist) { - for (int jobIndex = mJobs.size() - 1; jobIndex >= 0; jobIndex--) { - int jobUserId = UserHandle.getUserId(mJobs.keyAt(jobIndex)); - // check if job's user id is not in the whitelist + for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) { + final int jobUserId = UserHandle.getUserId(mJobsPerSourceUid.keyAt(jobSetIndex)); if (!ArrayUtils.contains(whitelist, jobUserId)) { - mJobs.removeAt(jobIndex); + mJobsPerSourceUid.removeAt(jobSetIndex); + } + } + for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) { + final ArraySet<JobStatus> jobsForUid = mJobs.valueAt(jobSetIndex); + for (int jobIndex = jobsForUid.size() - 1; jobIndex >= 0; jobIndex--) { + final int jobUserId = jobsForUid.valueAt(jobIndex).getUserId(); + if (!ArrayUtils.contains(whitelist, jobUserId)) { + jobsForUid.removeAt(jobIndex); + } + } + if (jobsForUid.size() == 0) { + mJobs.removeAt(jobSetIndex); } } } @@ -1077,6 +1119,7 @@ public final class JobStore { public void clear() { mJobs.clear(); + mJobsPerSourceUid.clear(); } public int size() { @@ -1112,8 +1155,17 @@ public final class JobStore { } } - public void forEachJob(int uid, JobStatusFunctor functor) { - ArraySet<JobStatus> jobs = mJobs.get(uid); + public void forEachJob(int callingUid, JobStatusFunctor functor) { + ArraySet<JobStatus> jobs = mJobs.get(callingUid); + if (jobs != null) { + for (int i = jobs.size() - 1; i >= 0; i--) { + functor.process(jobs.valueAt(i)); + } + } + } + + public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) { + final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid); if (jobs != null) { for (int i = jobs.size() - 1; i >= 0; i--) { functor.process(jobs.valueAt(i)); diff --git a/com/android/server/job/controllers/AppIdleController.java b/com/android/server/job/controllers/AppIdleController.java index caa85220..a7ed2f56 100644 --- a/com/android/server/job/controllers/AppIdleController.java +++ b/com/android/server/job/controllers/AppIdleController.java @@ -180,6 +180,7 @@ public final class AppIdleController extends StateController { if (mAppIdleParoleOn) { return; } + PackageUpdateFunc update = new PackageUpdateFunc(userId, packageName, idle); mJobSchedulerService.getJobStore().forEachJob(update); if (update.mChanged) { diff --git a/com/android/server/job/controllers/BackgroundJobsController.java b/com/android/server/job/controllers/BackgroundJobsController.java index 78b4160e..fc4015d0 100644 --- a/com/android/server/job/controllers/BackgroundJobsController.java +++ b/com/android/server/job/controllers/BackgroundJobsController.java @@ -16,24 +16,16 @@ package com.android.server.job.controllers; -import android.app.AppOpsManager; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.os.IDeviceIdleController; -import android.os.PowerManager; -import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemClock; import android.os.UserHandle; -import android.util.ArraySet; import android.util.Slog; -import android.util.SparseArray; -import android.util.SparseBooleanArray; -import com.android.internal.app.IAppOpsCallback; -import com.android.internal.app.IAppOpsService; import com.android.internal.util.ArrayUtils; +import com.android.server.ForceAppStandbyTracker; +import com.android.server.ForceAppStandbyTracker.Listener; import com.android.server.job.JobSchedulerService; import com.android.server.job.JobStore; @@ -49,18 +41,10 @@ public final class BackgroundJobsController extends StateController { private static volatile BackgroundJobsController sController; private final JobSchedulerService mJobSchedulerService; - private final IAppOpsService mAppOpsService; private final IDeviceIdleController mDeviceIdleController; - private final SparseBooleanArray mForegroundUids; - private int[] mPowerWhitelistedUserAppIds; - private int[] mTempWhitelistedAppIds; - /** - * Only tracks jobs for which source package app op RUN_ANY_IN_BACKGROUND is not ALLOWED. - * Maps jobs to the sourceUid unlike the global {@link JobSchedulerService#mJobs JobStore} - * which uses callingUid. - */ - private SparseArray<ArraySet<JobStatus>> mTrackedJobs; + private final ForceAppStandbyTracker mForceAppStandbyTracker; + public static BackgroundJobsController get(JobSchedulerService service) { synchronized (sCreationLock) { @@ -72,232 +56,148 @@ public final class BackgroundJobsController extends StateController { } } - private BroadcastReceiver mDozeWhitelistReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - synchronized (mLock) { - try { - switch (intent.getAction()) { - case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED: - mPowerWhitelistedUserAppIds = - mDeviceIdleController.getAppIdUserWhitelist(); - break; - case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED: - mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist(); - break; - } - } catch (RemoteException rexc) { - Slog.e(LOG_TAG, "Device idle controller not reachable"); - } - if (checkAllTrackedJobsLocked()) { - mStateChangedListener.onControllerStateChanged(); - } - } - } - }; - private BackgroundJobsController(JobSchedulerService service, Context context, Object lock) { super(service, context, lock); mJobSchedulerService = service; - mAppOpsService = IAppOpsService.Stub.asInterface( - ServiceManager.getService(Context.APP_OPS_SERVICE)); mDeviceIdleController = IDeviceIdleController.Stub.asInterface( ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); - mForegroundUids = new SparseBooleanArray(); - mTrackedJobs = new SparseArray<>(); - try { - mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null, - new AppOpsWatcher()); - mPowerWhitelistedUserAppIds = mDeviceIdleController.getAppIdUserWhitelist(); - mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist(); - } catch (RemoteException rexc) { - // Shouldn't happen as they are in the same process. - Slog.e(LOG_TAG, "AppOps or DeviceIdle service not reachable", rexc); - } - IntentFilter powerWhitelistFilter = new IntentFilter(); - powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); - powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED); - context.registerReceiverAsUser(mDozeWhitelistReceiver, UserHandle.ALL, powerWhitelistFilter, - null, null); + mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context); + + mForceAppStandbyTracker.addListener(mForceAppStandbyListener); + mForceAppStandbyTracker.start(); } @Override public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { - final int uid = jobStatus.getSourceUid(); - final String packageName = jobStatus.getSourcePackageName(); - try { - final int mode = mAppOpsService.checkOperation(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, - uid, packageName); - if (mode == AppOpsManager.MODE_ALLOWED) { - jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true); - return; - } - } catch (RemoteException rexc) { - Slog.e(LOG_TAG, "Cannot reach app ops service", rexc); - } - jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRunJobLocked(uid)); - startTrackingJobLocked(jobStatus); + updateSingleJobRestrictionLocked(jobStatus); } @Override public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) { - stopTrackingJobLocked(jobStatus); - } - - /* Called by JobSchedulerService to report uid state changes between active and idle */ - public void setUidActiveLocked(int uid, boolean active) { - final boolean changed = (active != mForegroundUids.get(uid)); - if (!changed) { - return; - } - if (DEBUG) { - Slog.d(LOG_TAG, "uid " + uid + " going to " + (active ? "fg" : "bg")); - } - if (active) { - mForegroundUids.put(uid, true); - } else { - mForegroundUids.delete(uid); - } - if (checkTrackedJobsForUidLocked(uid)) { - mStateChangedListener.onControllerStateChanged(); - } } @Override public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) { pw.println("BackgroundJobsController"); - pw.print("Foreground uids: ["); - for (int i = 0; i < mForegroundUids.size(); i++) { - if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " "); - } - pw.println("]"); - mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() { - @Override - public void process(JobStatus jobStatus) { - if (!jobStatus.shouldDump(filterUid)) { - return; - } - final int uid = jobStatus.getSourceUid(); - pw.print(" #"); - jobStatus.printUniqueId(pw); - pw.print(" from "); - UserHandle.formatUid(pw, uid); - pw.print(mForegroundUids.get(uid) ? " foreground" : " background"); - if (isWhitelistedLocked(uid)) { - pw.print(", whitelisted"); - } - pw.print(": "); - pw.print(jobStatus.getSourcePackageName()); - pw.print(" [background restrictions"); - final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid); - pw.print(jobsForUid != null && jobsForUid.contains(jobStatus) ? " on]" : " off]"); - if ((jobStatus.satisfiedConstraints - & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) { - pw.println(" RUNNABLE"); - } else { - pw.println(" WAITING"); - } + + mForceAppStandbyTracker.dump(pw, ""); + + pw.println("Job state:"); + mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> { + if (!jobStatus.shouldDump(filterUid)) { + return; + } + final int uid = jobStatus.getSourceUid(); + pw.print(" #"); + jobStatus.printUniqueId(pw); + pw.print(" from "); + UserHandle.formatUid(pw, uid); + pw.print(mForceAppStandbyTracker.isInForeground(uid) ? " foreground" : " background"); + if (mForceAppStandbyTracker.isUidPowerSaveWhitelisted(uid) || + mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(uid)) { + pw.print(", whitelisted"); + } + pw.print(": "); + pw.print(jobStatus.getSourcePackageName()); + + pw.print(" [RUN_ANY_IN_BACKGROUND "); + pw.print(mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed( + jobStatus.getSourceUid(), jobStatus.getSourcePackageName()) + ? "allowed]" : "disallowed]"); + + if ((jobStatus.satisfiedConstraints + & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) { + pw.println(" RUNNABLE"); + } else { + pw.println(" WAITING"); } }); } - void startTrackingJobLocked(JobStatus jobStatus) { - final int uid = jobStatus.getSourceUid(); - ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid); - if (jobsForUid == null) { - jobsForUid = new ArraySet<>(); - mTrackedJobs.put(uid, jobsForUid); - } - jobsForUid.add(jobStatus); + private void updateAllJobRestrictionsLocked() { + updateJobRestrictionsLocked(/*filterUid=*/ -1); } - void stopTrackingJobLocked(JobStatus jobStatus) { - final int uid = jobStatus.getSourceUid(); - ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid); - if (jobsForUid != null) { - jobsForUid.remove(jobStatus); - } + private void updateJobRestrictionsForUidLocked(int uid) { + + // TODO Use forEachJobForSourceUid() once we have it. + + updateJobRestrictionsLocked(/*filterUid=*/ uid); } - boolean checkAllTrackedJobsLocked() { - boolean changed = false; - for (int i = 0; i < mTrackedJobs.size(); i++) { - changed |= checkTrackedJobsForUidLocked(mTrackedJobs.keyAt(i)); + private void updateJobRestrictionsLocked(int filterUid) { + final UpdateJobFunctor updateTrackedJobs = + new UpdateJobFunctor(filterUid); + + final long start = DEBUG ? SystemClock.elapsedRealtimeNanos() : 0; + + mJobSchedulerService.getJobStore().forEachJob(updateTrackedJobs); + + final long time = DEBUG ? (SystemClock.elapsedRealtimeNanos() - start) : 0; + if (DEBUG) { + Slog.d(LOG_TAG, String.format( + "Job status updated: %d/%d checked/total jobs, %d us", + updateTrackedJobs.mCheckedCount, + updateTrackedJobs.mTotalCount, + (time / 1000) + )); } - return changed; - } - private boolean checkTrackedJobsForUidLocked(int uid) { - final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid); - boolean changed = false; - if (jobsForUid != null) { - for (int i = 0; i < jobsForUid.size(); i++) { - JobStatus jobStatus = jobsForUid.valueAt(i); - changed |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied( - canRunJobLocked(uid)); - } + if (updateTrackedJobs.mChanged) { + mStateChangedListener.onControllerStateChanged(); } - return changed; } - boolean isWhitelistedLocked(int uid) { - return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid)) - || ArrayUtils.contains(mPowerWhitelistedUserAppIds, UserHandle.getAppId(uid)); - } + boolean updateSingleJobRestrictionLocked(JobStatus jobStatus) { - boolean canRunJobLocked(int uid) { - return mForegroundUids.get(uid) || isWhitelistedLocked(uid); - } + final int uid = jobStatus.getSourceUid(); + final String packageName = jobStatus.getSourcePackageName(); - private final class AppOpsWatcher extends IAppOpsCallback.Stub { - @Override - public void opChanged(int op, int uid, String packageName) throws RemoteException { - synchronized (mLock) { - final int mode = mAppOpsService.checkOperation(op, uid, packageName); - if (DEBUG) { - Slog.d(LOG_TAG, - "Appop changed for " + uid + ", " + packageName + " to " + mode); - } - final boolean shouldTrack = (mode != AppOpsManager.MODE_ALLOWED); - UpdateTrackedJobsFunc updateTrackedJobs = new UpdateTrackedJobsFunc(uid, - packageName, shouldTrack); - mJobSchedulerService.getJobStore().forEachJob(updateTrackedJobs); - if (updateTrackedJobs.mChanged) { - mStateChangedListener.onControllerStateChanged(); - } - } - } + final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName); + + return jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun); } - private final class UpdateTrackedJobsFunc implements JobStore.JobStatusFunctor { - private final String mPackageName; - private final int mUid; - private final boolean mShouldTrack; - private boolean mChanged = false; + private final class UpdateJobFunctor implements JobStore.JobStatusFunctor { + private final int mFilterUid; + + boolean mChanged = false; + int mTotalCount = 0; + int mCheckedCount = 0; - UpdateTrackedJobsFunc(int uid, String packageName, boolean shouldTrack) { - mUid = uid; - mPackageName = packageName; - mShouldTrack = shouldTrack; + UpdateJobFunctor(int filterUid) { + mFilterUid = filterUid; } @Override public void process(JobStatus jobStatus) { - final String packageName = jobStatus.getSourcePackageName(); - final int uid = jobStatus.getSourceUid(); - if (mUid != uid || !mPackageName.equals(packageName)) { + mTotalCount++; + if ((mFilterUid > 0) && (mFilterUid != jobStatus.getSourceUid())) { return; } - if (mShouldTrack) { - mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied( - canRunJobLocked(uid)); - startTrackingJobLocked(jobStatus); - } else { - mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true); - stopTrackingJobLocked(jobStatus); + mCheckedCount++; + if (updateSingleJobRestrictionLocked(jobStatus)) { + mChanged = true; } } } + + private final Listener mForceAppStandbyListener = new Listener() { + @Override + public void updateAllJobs() { + updateAllJobRestrictionsLocked(); + } + + @Override + public void updateJobsForUid(int uid) { + updateJobRestrictionsForUidLocked(uid); + } + + @Override + public void updateJobsForUidPackage(int uid, String packageName) { + updateJobRestrictionsForUidLocked(uid); + } + }; } diff --git a/com/android/server/job/controllers/DeviceIdleJobsController.java b/com/android/server/job/controllers/DeviceIdleJobsController.java index 374ab43c..b7eb9e06 100644 --- a/com/android/server/job/controllers/DeviceIdleJobsController.java +++ b/com/android/server/job/controllers/DeviceIdleJobsController.java @@ -16,14 +16,19 @@ package com.android.server.job.controllers; +import android.app.job.JobInfo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.PowerManager; import android.os.UserHandle; import android.util.ArraySet; import android.util.Slog; +import android.util.SparseBooleanArray; import com.android.internal.util.ArrayUtils; import com.android.server.DeviceIdleController; @@ -42,11 +47,22 @@ public final class DeviceIdleJobsController extends StateController { private static final String LOG_TAG = "DeviceIdleJobsController"; private static final boolean LOG_DEBUG = false; + private static final long BACKGROUND_JOBS_DELAY = 3000; + + static final int PROCESS_BACKGROUND_JOBS = 1; // Singleton factory private static Object sCreationLock = new Object(); private static DeviceIdleJobsController sController; + /** + * These are jobs added with a special flag to indicate that they should be exempted from doze + * when the app is temp whitelisted or in the foreground. + */ + private final ArraySet<JobStatus> mAllowInIdleJobs; + private final SparseBooleanArray mForegroundUids; + private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor; + private final DeviceIdleJobsDelayHandler mHandler; private final JobSchedulerService mJobSchedulerService; private final PowerManager mPowerManager; private final DeviceIdleController.LocalService mLocalDeviceIdleController; @@ -57,14 +73,6 @@ public final class DeviceIdleJobsController extends StateController { private boolean mDeviceIdleMode; private int[] mDeviceIdleWhitelistAppIds; private int[] mPowerSaveTempWhitelistAppIds; - // These jobs were added when the app was in temp whitelist, these should be exempted from doze - private final ArraySet<JobStatus> mTempWhitelistedJobs; - - final JobStore.JobStatusFunctor mUpdateFunctor = new JobStore.JobStatusFunctor() { - @Override public void process(JobStatus jobStatus) { - updateTaskStateLocked(jobStatus); - } - }; /** * Returns a singleton for the DeviceIdleJobsController @@ -108,8 +116,8 @@ public final class DeviceIdleJobsController extends StateController { + Arrays.toString(mPowerSaveTempWhitelistAppIds)); } boolean changed = false; - for (int i = 0; i < mTempWhitelistedJobs.size(); i ++) { - changed |= updateTaskStateLocked(mTempWhitelistedJobs.valueAt(i)); + for (int i = 0; i < mAllowInIdleJobs.size(); i++) { + changed |= updateTaskStateLocked(mAllowInIdleJobs.valueAt(i)); } if (changed) { mStateChangedListener.onControllerStateChanged(); @@ -125,6 +133,7 @@ public final class DeviceIdleJobsController extends StateController { super(jobSchedulerService, context, lock); mJobSchedulerService = jobSchedulerService; + mHandler = new DeviceIdleJobsDelayHandler(context.getMainLooper()); // Register for device idle mode changes mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mLocalDeviceIdleController = @@ -132,7 +141,9 @@ public final class DeviceIdleJobsController extends StateController { mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds(); mPowerSaveTempWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds(); - mTempWhitelistedJobs = new ArraySet<>(); + mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor(); + mAllowInIdleJobs = new ArraySet<>(); + mForegroundUids = new SparseBooleanArray(); final IntentFilter filter = new IntentFilter(); filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED); @@ -150,7 +161,20 @@ public final class DeviceIdleJobsController extends StateController { } mDeviceIdleMode = enabled; if (LOG_DEBUG) Slog.d(LOG_TAG, "mDeviceIdleMode=" + mDeviceIdleMode); - mJobSchedulerService.getJobStore().forEachJob(mUpdateFunctor); + if (enabled) { + mHandler.removeMessages(PROCESS_BACKGROUND_JOBS); + mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor); + } else { + // When coming out of doze, process all foreground uids immediately, while others + // will be processed after a delay of 3 seconds. + for (int i = 0; i < mForegroundUids.size(); i++) { + if (mForegroundUids.valueAt(i)) { + mJobSchedulerService.getJobStore().forEachJobForSourceUid( + mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor); + } + } + mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY); + } } // Inform the job scheduler service about idle mode changes if (changed) { @@ -159,11 +183,30 @@ public final class DeviceIdleJobsController extends StateController { } /** + * Called by jobscheduler service to report uid state changes between active and idle + */ + public void setUidActiveLocked(int uid, boolean active) { + final boolean changed = (active != mForegroundUids.get(uid)); + if (!changed) { + return; + } + if (LOG_DEBUG) { + Slog.d(LOG_TAG, "uid " + uid + " going " + (active ? "active" : "inactive")); + } + mForegroundUids.put(uid, active); + mDeviceIdleUpdateFunctor.mChanged = false; + mJobSchedulerService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor); + if (mDeviceIdleUpdateFunctor.mChanged) { + mStateChangedListener.onControllerStateChanged(); + } + } + + /** * Checks if the given job's scheduling app id exists in the device idle user whitelist. */ boolean isWhitelistedLocked(JobStatus job) { - return ArrayUtils.contains(mDeviceIdleWhitelistAppIds, - UserHandle.getAppId(job.getSourceUid())); + return Arrays.binarySearch(mDeviceIdleWhitelistAppIds, + UserHandle.getAppId(job.getSourceUid())) >= 0; } /** @@ -175,31 +218,33 @@ public final class DeviceIdleJobsController extends StateController { } private boolean updateTaskStateLocked(JobStatus task) { - final boolean whitelisted = isWhitelistedLocked(task) - || (mTempWhitelistedJobs.contains(task) && isTempWhitelistedLocked(task)); - final boolean enableTask = !mDeviceIdleMode || whitelisted; + final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) + && (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task)); + final boolean whitelisted = isWhitelistedLocked(task); + final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle; return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted); } @Override public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { - if (isTempWhitelistedLocked(jobStatus)) { - mTempWhitelistedJobs.add(jobStatus); - jobStatus.setDeviceNotDozingConstraintSatisfied(true, true); - } else { - updateTaskStateLocked(jobStatus); + if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) { + mAllowInIdleJobs.add(jobStatus); } + updateTaskStateLocked(jobStatus); } @Override public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) { - mTempWhitelistedJobs.remove(jobStatus); + if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) { + mAllowInIdleJobs.remove(jobStatus); + } } @Override public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) { pw.println("DeviceIdleJobsController"); + pw.println("mDeviceIdleMode=" + mDeviceIdleMode); mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() { @Override public void process(JobStatus jobStatus) { if (!jobStatus.shouldDump(filterUid)) { @@ -217,8 +262,42 @@ public final class DeviceIdleJobsController extends StateController { if (jobStatus.dozeWhitelisted) { pw.print(" WHITELISTED"); } + if (mAllowInIdleJobs.contains(jobStatus)) { + pw.print(" ALLOWED_IN_DOZE"); + } pw.println(); } }); } + + final class DeviceIdleUpdateFunctor implements JobStore.JobStatusFunctor { + boolean mChanged; + + @Override + public void process(JobStatus jobStatus) { + mChanged |= updateTaskStateLocked(jobStatus); + } + } + + final class DeviceIdleJobsDelayHandler extends Handler { + public DeviceIdleJobsDelayHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case PROCESS_BACKGROUND_JOBS: + // Just process all the jobs, the ones in foreground should already be running. + synchronized (mLock) { + mDeviceIdleUpdateFunctor.mChanged = false; + mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor); + if (mDeviceIdleUpdateFunctor.mChanged) { + mStateChangedListener.onControllerStateChanged(); + } + } + break; + } + } + } }
\ No newline at end of file diff --git a/com/android/server/job/controllers/JobStatus.java b/com/android/server/job/controllers/JobStatus.java index a5906cb1..e71b8ec4 100644 --- a/com/android/server/job/controllers/JobStatus.java +++ b/com/android/server/job/controllers/JobStatus.java @@ -34,7 +34,9 @@ import android.util.Pair; import android.util.Slog; import android.util.TimeUtils; +import com.android.server.LocalServices; import com.android.server.job.GrantedUriPermissions; +import com.android.server.job.JobSchedulerInternal; import com.android.server.job.JobSchedulerService; import java.io.PrintWriter; @@ -120,6 +122,24 @@ public final class JobStatus { /** How many times this job has failed, used to compute back-off. */ private final int numFailures; + /** + * Current standby heartbeat when this job was scheduled or last ran. Used to + * pin the runnability check regardless of the job's app moving between buckets. + */ + private final long baseHeartbeat; + + /** + * Which app standby bucket this job's app is in. Updated when the app is moved to a + * different bucket. + */ + private int standbyBucket; + + /** + * Debugging: timestamp if we ever defer this job based on standby bucketing, this + * is when we did so. + */ + private long whenStandbyDeferred; + // Constraints. final int requiredConstraints; int satisfiedConstraints = 0; @@ -221,10 +241,13 @@ public final class JobStatus { } private JobStatus(JobInfo job, int callingUid, String sourcePackageName, - int sourceUserId, String tag, int numFailures, long earliestRunTimeElapsedMillis, - long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime) { + int sourceUserId, int standbyBucket, long heartbeat, String tag, int numFailures, + long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, + long lastSuccessfulRunTime, long lastFailedRunTime) { this.job = job; this.callingUid = callingUid; + this.standbyBucket = standbyBucket; + this.baseHeartbeat = heartbeat; int tempSourceUid = -1; if (sourceUserId != -1 && sourcePackageName != null) { @@ -283,6 +306,7 @@ public final class JobStatus { public JobStatus(JobStatus jobStatus) { this(jobStatus.getJob(), jobStatus.getUid(), jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(), + jobStatus.getStandbyBucket(), jobStatus.getBaseHeartbeat(), jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(), jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime()); @@ -299,13 +323,17 @@ public final class JobStatus { * {@link android.app.job.JobInfo} time criteria because we can load a persisted periodic job * from the {@link com.android.server.job.JobStore} and still want to respect its * wallclock runtime rather than resetting it on every boot. - * We consider a freshly loaded job to no longer be in back-off. + * We consider a freshly loaded job to no longer be in back-off, and the associated + * standby bucket is whatever the OS thinks it should be at this moment. */ - public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId, - String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, + public JobStatus(JobInfo job, int callingUid, String sourcePkgName, int sourceUserId, + int standbyBucket, long baseHeartbeat, String sourceTag, + long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime, Pair<Long, Long> persistedExecutionTimesUTC) { - this(job, callingUid, sourcePackageName, sourceUserId, sourceTag, 0, + this(job, callingUid, sourcePkgName, sourceUserId, + standbyBucket, baseHeartbeat, + sourceTag, 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, lastSuccessfulRunTime, lastFailedRunTime); @@ -322,11 +350,13 @@ public final class JobStatus { } /** Create a new job to be rescheduled with the provided parameters. */ - public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis, + public JobStatus(JobStatus rescheduling, long newBaseHeartbeat, + long newEarliestRuntimeElapsedMillis, long newLatestRuntimeElapsedMillis, int backoffAttempt, long lastSuccessfulRunTime, long lastFailedRunTime) { this(rescheduling.job, rescheduling.getUid(), rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(), + rescheduling.getStandbyBucket(), newBaseHeartbeat, rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis, newLatestRuntimeElapsedMillis, lastSuccessfulRunTime, lastFailedRunTime); @@ -335,11 +365,12 @@ public final class JobStatus { /** * Create a newly scheduled job. * @param callingUid Uid of the package that scheduled this job. - * @param sourcePackageName Package name on whose behalf this job is scheduled. Null indicates + * @param sourcePkg Package name on whose behalf this job is scheduled. Null indicates * the calling package is the source. * @param sourceUserId User id for whom this job is scheduled. -1 indicates this is same as the + * caller. */ - public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePackageName, + public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePkg, int sourceUserId, String tag) { final long elapsedNow = sElapsedRealtimeClock.millis(); final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis; @@ -352,7 +383,14 @@ public final class JobStatus { latestRunTimeElapsedMillis = job.hasLateConstraint() ? elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME; } - return new JobStatus(job, callingUid, sourcePackageName, sourceUserId, tag, 0, + String jobPackage = (sourcePkg != null) ? sourcePkg : job.getService().getPackageName(); + + int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage, + sourceUserId, elapsedNow); + JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class); + long currentHeartbeat = js != null ? js.currentHeartbeat() : 0; + return new JobStatus(job, callingUid, sourcePkg, sourceUserId, + standbyBucket, currentHeartbeat, tag, 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */); } @@ -528,6 +566,29 @@ public final class JobStatus { return UserHandle.getUserId(callingUid); } + public int getStandbyBucket() { + return standbyBucket; + } + + public long getBaseHeartbeat() { + return baseHeartbeat; + } + + // Called only by the standby monitoring code + public void setStandbyBucket(int newBucket) { + standbyBucket = newBucket; + } + + // Called only by the standby monitoring code + public long getWhenStandbyDeferred() { + return whenStandbyDeferred; + } + + // Called only by the standby monitoring code + public void setWhenStandbyDeferred(long now) { + whenStandbyDeferred = now; + } + public String getSourceTag() { return sourceTag; } @@ -950,6 +1011,19 @@ public final class JobStatus { } } + // normalized bucket indices, not the AppStandby constants + private String bucketName(int bucket) { + switch (bucket) { + case 0: return "ACTIVE"; + case 1: return "WORKING_SET"; + case 2: return "FREQUENT"; + case 3: return "RARE"; + case 4: return "NEVER"; + default: + return "Unknown: " + bucket; + } + } + // Dumpsys infrastructure public void dump(PrintWriter pw, String prefix, boolean full, long elapsedRealtimeMillis) { pw.print(prefix); UserHandle.formatUid(pw, callingUid); @@ -1098,6 +1172,8 @@ public final class JobStatus { dumpJobWorkItem(pw, prefix, executingWork.get(i), i); } } + pw.print(prefix); pw.print("Standby bucket: "); + pw.println(bucketName(standbyBucket)); pw.print(prefix); pw.print("Enqueue time: "); TimeUtils.formatDuration(enqueueTime, elapsedRealtimeMillis, pw); pw.println(); diff --git a/com/android/server/location/ContextHubClientBroker.java b/com/android/server/location/ContextHubClientBroker.java new file mode 100644 index 00000000..41d9feb9 --- /dev/null +++ b/com/android/server/location/ContextHubClientBroker.java @@ -0,0 +1,226 @@ +/* + * 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 com.android.server.location; + +import android.content.Context; +import android.hardware.contexthub.V1_0.ContextHubMsg; +import android.hardware.contexthub.V1_0.IContexthub; +import android.hardware.contexthub.V1_0.Result; +import android.hardware.location.ContextHubTransaction; +import android.hardware.location.IContextHubClient; +import android.hardware.location.IContextHubClientCallback; +import android.hardware.location.NanoAppMessage; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A class that acts as a broker for the ContextHubClient, which handles messaging and life-cycle + * notification callbacks. This class implements the IContextHubClient object, and the implemented + * APIs must be thread-safe. + * + * @hide + */ +public class ContextHubClientBroker extends IContextHubClient.Stub + implements IBinder.DeathRecipient { + private static final String TAG = "ContextHubClientBroker"; + + /* + * The context of the service. + */ + private final Context mContext; + + /* + * The proxy to talk to the Context Hub HAL. + */ + private final IContexthub mContextHubProxy; + + /* + * The manager that registered this client. + */ + private final ContextHubClientManager mClientManager; + + /* + * The ID of the hub that this client is attached to. + */ + private final int mAttachedContextHubId; + + /* + * The host end point ID of this client. + */ + private final short mHostEndPointId; + + /* + * The remote callback interface for this client. + */ + private final IContextHubClientCallback mCallbackInterface; + + /* + * false if the connection has been closed by the client, true otherwise. + */ + private final AtomicBoolean mConnectionOpen = new AtomicBoolean(true); + + /* package */ ContextHubClientBroker( + Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager, + int contextHubId, short hostEndPointId, IContextHubClientCallback callback) { + mContext = context; + mContextHubProxy = contextHubProxy; + mClientManager = clientManager; + mAttachedContextHubId = contextHubId; + mHostEndPointId = hostEndPointId; + mCallbackInterface = callback; + } + + /** + * Attaches a death recipient for this client + * + * @throws RemoteException if the client has already died + */ + /* package */ void attachDeathRecipient() throws RemoteException { + mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */); + } + + /** + * Sends from this client to a nanoapp. + * + * @param message the message to send + * @return the error code of sending the message + */ + @ContextHubTransaction.Result + @Override + public int sendMessageToNanoApp(NanoAppMessage message) { + ContextHubServiceUtil.checkPermissions(mContext); + + int result; + if (mConnectionOpen.get()) { + ContextHubMsg messageToNanoApp = ContextHubServiceUtil.createHidlContextHubMessage( + mHostEndPointId, message); + + try { + result = mContextHubProxy.sendMessageToHub(mAttachedContextHubId, messageToNanoApp); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = " + + mAttachedContextHubId + ")", e); + result = Result.UNKNOWN_FAILURE; + } + } else { + Log.e(TAG, "Failed to send message to nanoapp: client connection is closed"); + result = Result.UNKNOWN_FAILURE; + } + + return ContextHubServiceUtil.toTransactionResult(result); + } + + /** + * Closes the connection for this client with the service. + */ + @Override + public void close() { + if (mConnectionOpen.getAndSet(false)) { + mClientManager.unregisterClient(mHostEndPointId); + } + } + + /** + * Invoked when the underlying binder of this broker has died at the client process. + */ + public void binderDied() { + try { + IContextHubClient.Stub.asInterface(this).close(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while closing client on death", e); + } + } + + /** + * @return the ID of the context hub this client is attached to + */ + /* package */ int getAttachedContextHubId() { + return mAttachedContextHubId; + } + + /** + * @return the host endpoint ID of this client + */ + /* package */ short getHostEndPointId() { + return mHostEndPointId; + } + + /** + * Sends a message to the client associated with this object. + * + * @param message the message that came from a nanoapp + */ + /* package */ void sendMessageToClient(NanoAppMessage message) { + if (mConnectionOpen.get()) { + try { + mCallbackInterface.onMessageFromNanoApp(message); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while sending message to client (host endpoint ID = " + + mHostEndPointId + ")", e); + } + } + } + + /** + * Handles a nanoapp load event. + * + * @param nanoAppId the ID of the nanoapp that was loaded. + */ + /* package */ void onNanoAppLoaded(long nanoAppId) { + if (mConnectionOpen.get()) { + try { + mCallbackInterface.onNanoAppLoaded(nanoAppId); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while calling onNanoAppLoaded on client" + + " (host endpoint ID = " + mHostEndPointId + ")", e); + } + } + } + + /** + * Handles a nanoapp unload event. + * + * @param nanoAppId the ID of the nanoapp that was unloaded. + */ + /* package */ void onNanoAppUnloaded(long nanoAppId) { + if (mConnectionOpen.get()) { + try { + mCallbackInterface.onNanoAppUnloaded(nanoAppId); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while calling onNanoAppUnloaded on client" + + " (host endpoint ID = " + mHostEndPointId + ")", e); + } + } + } + + /** + * Handles a hub reset for this client. + */ + /* package */ void onHubReset() { + if (mConnectionOpen.get()) { + try { + mCallbackInterface.onHubReset(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while calling onHubReset on client" + + " (host endpoint ID = " + mHostEndPointId + ")", e); + } + } + } +} diff --git a/com/android/server/location/ContextHubClientManager.java b/com/android/server/location/ContextHubClientManager.java new file mode 100644 index 00000000..d58a7460 --- /dev/null +++ b/com/android/server/location/ContextHubClientManager.java @@ -0,0 +1,237 @@ +/* + * 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 com.android.server.location; + +import android.content.Context; +import android.hardware.contexthub.V1_0.ContextHubMsg; +import android.hardware.contexthub.V1_0.IContexthub; +import android.hardware.location.IContextHubClient; +import android.hardware.location.IContextHubClientCallback; +import android.hardware.location.NanoAppMessage; +import android.os.RemoteException; +import android.util.Log; + +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +/** + * A class that manages registration/unregistration of clients and manages messages to/from clients. + * + * @hide + */ +/* package */ class ContextHubClientManager { + private static final String TAG = "ContextHubClientManager"; + + /* + * The maximum host endpoint ID value that a client can be assigned. + */ + private static final int MAX_CLIENT_ID = 0x7fff; + + /* + * Local flag to enable debug logging. + */ + private static final boolean DEBUG_LOG_ENABLED = true; + + /* + * The context of the service. + */ + private final Context mContext; + + /* + * The proxy to talk to the Context Hub. + */ + private final IContexthub mContextHubProxy; + + /* + * A mapping of host endpoint IDs to the ContextHubClientBroker object of registered clients. + * A concurrent data structure is used since the registration/unregistration can occur in + * multiple threads. + */ + private final ConcurrentHashMap<Short, ContextHubClientBroker> mHostEndPointIdToClientMap = + new ConcurrentHashMap<>(); + + /* + * The next host endpoint ID to start iterating for the next available host endpoint ID. + */ + private int mNextHostEndpointId = 0; + + /* package */ ContextHubClientManager( + Context context, IContexthub contextHubProxy) { + mContext = context; + mContextHubProxy = contextHubProxy; + } + + /** + * Registers a new client with the service. + * + * @param clientCallback the callback interface of the client to register + * @param contextHubId the ID of the hub this client is attached to + * + * @return the client interface + * + * @throws IllegalStateException if max number of clients have already registered + */ + /* package */ IContextHubClient registerClient( + IContextHubClientCallback clientCallback, int contextHubId) { + ContextHubClientBroker broker = createNewClientBroker(clientCallback, contextHubId); + + try { + broker.attachDeathRecipient(); + } catch (RemoteException e) { + // The client process has died, so we close the connection and return null. + Log.e(TAG, "Failed to attach death recipient to client"); + broker.close(); + return null; + } + + Log.d(TAG, "Registered client with host endpoint ID " + broker.getHostEndPointId()); + return IContextHubClient.Stub.asInterface(broker); + } + + /** + * Handles a message sent from a nanoapp. + * + * @param contextHubId the ID of the hub where the nanoapp sent the message from + * @param message the message send by a nanoapp + */ + /* package */ void onMessageFromNanoApp(int contextHubId, ContextHubMsg message) { + NanoAppMessage clientMessage = ContextHubServiceUtil.createNanoAppMessage(message); + + if (DEBUG_LOG_ENABLED) { + String targetAudience = clientMessage.isBroadcastMessage() ? "broadcast" : "unicast"; + Log.v(TAG, "Received a " + targetAudience + " message from nanoapp 0x" + + Long.toHexString(clientMessage.getNanoAppId())); + } + + if (clientMessage.isBroadcastMessage()) { + broadcastMessage(contextHubId, clientMessage); + } else { + ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(message.hostEndPoint); + if (proxy != null) { + proxy.sendMessageToClient(clientMessage); + } else { + Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = " + + message.hostEndPoint + ")"); + } + } + } + + /** + * Unregisters a client from the service. + * + * This method should be invoked as a result of a client calling the ContextHubClient.close(), + * or if the client process has died. + * + * @param hostEndPointId the host endpoint ID of the client that has died + */ + /* package */ void unregisterClient(short hostEndPointId) { + if (mHostEndPointIdToClientMap.remove(hostEndPointId) != null) { + Log.d(TAG, "Unregistered client with host endpoint ID " + hostEndPointId); + } else { + Log.e(TAG, "Cannot unregister non-existing client with host endpoint ID " + + hostEndPointId); + } + } + + /** + * Handles a nanoapp load event. + * + * @param contextHubId the ID of the hub where the nanoapp was loaded. + * @param nanoAppId the ID of the nanoapp that was loaded. + */ + /* package */ void onNanoAppLoaded(int contextHubId, long nanoAppId) { + forEachClientOfHub(contextHubId, client -> client.onNanoAppLoaded(nanoAppId)); + } + + /** + * Handles a nanoapp unload event. + * + * @param contextHubId the ID of the hub where the nanoapp was unloaded. + * @param nanoAppId the ID of the nanoapp that was unloaded. + */ + /* package */ void onNanoAppUnloaded(int contextHubId, long nanoAppId) { + forEachClientOfHub(contextHubId, client -> client.onNanoAppUnloaded(nanoAppId)); + } + + /** + * Handles a hub reset. + * + * @param contextHubId the ID of the hub that has reset. + */ + /* package */ void onHubReset(int contextHubId) { + forEachClientOfHub(contextHubId, client -> client.onHubReset()); + } + + /** + * Creates a new ContextHubClientBroker object for a client and registers it with the client + * manager. + * + * @param clientCallback the callback interface of the client to register + * @param contextHubId the ID of the hub this client is attached to + * + * @return the ContextHubClientBroker object + * + * @throws IllegalStateException if max number of clients have already registered + */ + private synchronized ContextHubClientBroker createNewClientBroker( + IContextHubClientCallback clientCallback, int contextHubId) { + if (mHostEndPointIdToClientMap.size() == MAX_CLIENT_ID + 1) { + throw new IllegalStateException("Could not register client - max limit exceeded"); + } + + ContextHubClientBroker broker = null; + int id = mNextHostEndpointId; + for (int i = 0; i <= MAX_CLIENT_ID; i++) { + if (!mHostEndPointIdToClientMap.containsKey(id)) { + broker = new ContextHubClientBroker( + mContext, mContextHubProxy, this, contextHubId, (short)id, clientCallback); + mHostEndPointIdToClientMap.put((short)id, broker); + mNextHostEndpointId = (id == MAX_CLIENT_ID) ? 0 : id + 1; + break; + } + + id = (id == MAX_CLIENT_ID) ? 0 : id + 1; + } + + return broker; + } + + /** + * Broadcasts a message from a nanoapp to all clients attached to the associated hub. + * + * @param contextHubId the ID of the hub where the nanoapp sent the message from + * @param message the message send by a nanoapp + */ + private void broadcastMessage(int contextHubId, NanoAppMessage message) { + forEachClientOfHub(contextHubId, client -> client.sendMessageToClient(message)); + } + + /** + * Runs a command for each client that is attached to a hub with the given ID. + * + * @param contextHubId the ID of the hub + * @param callback the command to invoke for the client + */ + private void forEachClientOfHub(int contextHubId, Consumer<ContextHubClientBroker> callback) { + for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) { + if (broker.getAttachedContextHubId() == contextHubId) { + callback.accept(broker); + } + } + } +} diff --git a/com/android/server/location/ContextHubService.java b/com/android/server/location/ContextHubService.java index 5e9f3550..e08c6596 100644 --- a/com/android/server/location/ContextHubService.java +++ b/com/android/server/location/ContextHubService.java @@ -16,17 +16,29 @@ package com.android.server.location; -import android.Manifest; import android.content.Context; -import android.content.pm.PackageManager; +import android.hardware.contexthub.V1_0.AsyncEventType; +import android.hardware.contexthub.V1_0.ContextHub; +import android.hardware.contexthub.V1_0.ContextHubMsg; +import android.hardware.contexthub.V1_0.HubAppInfo; +import android.hardware.contexthub.V1_0.IContexthub; +import android.hardware.contexthub.V1_0.IContexthubCallback; +import android.hardware.contexthub.V1_0.Result; +import android.hardware.contexthub.V1_0.TransactionResult; import android.hardware.location.ContextHubInfo; -import android.hardware.location.ContextHubManager; import android.hardware.location.ContextHubMessage; -import android.hardware.location.IContextHubService; +import android.hardware.location.ContextHubTransaction; import android.hardware.location.IContextHubCallback; -import android.hardware.location.NanoAppFilter; +import android.hardware.location.IContextHubClient; +import android.hardware.location.IContextHubClientCallback; +import android.hardware.location.IContextHubService; +import android.hardware.location.IContextHubTransactionCallback; import android.hardware.location.NanoApp; +import android.hardware.location.NanoAppBinary; +import android.hardware.location.NanoAppFilter; import android.hardware.location.NanoAppInstanceInfo; +import android.hardware.location.NanoAppMessage; +import android.hardware.location.NanoAppState; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; @@ -38,65 +50,230 @@ import java.io.PrintWriter; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Collections; import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; /** * @hide */ public class ContextHubService extends IContextHubService.Stub { private static final String TAG = "ContextHubService"; - private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE; - private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '" - + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware"; - public static final int ANY_HUB = -1; - public static final int MSG_LOAD_NANO_APP = 3; + /* + * Constants for the type of transaction that is defined by ContextHubService. + * This is used to report the transaction callback to clients, and is different from + * ContextHubTransaction.Type. + */ + public static final int MSG_ENABLE_NANO_APP = 1; + public static final int MSG_DISABLE_NANO_APP = 2; + public static final int MSG_LOAD_NANO_APP = 3; public static final int MSG_UNLOAD_NANO_APP = 4; + public static final int MSG_QUERY_NANO_APPS = 5; + public static final int MSG_QUERY_MEMORY = 6; + public static final int MSG_HUB_RESET = 7; private static final String PRE_LOADED_GENERIC_UNKNOWN = "Preloaded app, unknown"; private static final String PRE_LOADED_APP_NAME = PRE_LOADED_GENERIC_UNKNOWN; private static final String PRE_LOADED_APP_PUBLISHER = PRE_LOADED_GENERIC_UNKNOWN; private static final int PRE_LOADED_APP_MEM_REQ = 0; - private static final int MSG_HEADER_SIZE = 4; - private static final int HEADER_FIELD_MSG_TYPE = 0; - private static final int HEADER_FIELD_MSG_VERSION = 1; - private static final int HEADER_FIELD_HUB_HANDLE = 2; - private static final int HEADER_FIELD_APP_INSTANCE = 3; - - private static final int HEADER_FIELD_LOAD_APP_ID_LO = MSG_HEADER_SIZE; - private static final int HEADER_FIELD_LOAD_APP_ID_HI = MSG_HEADER_SIZE + 1; - private static final int MSG_LOAD_APP_HEADER_SIZE = MSG_HEADER_SIZE + 2; - private static final int OS_APP_INSTANCE = -1; private final Context mContext; + + // TODO(b/69270990): Remove once old ContextHubManager API is deprecated + // Service cache maintaining of instance ID to nanoapp infos private final ConcurrentHashMap<Integer, NanoAppInstanceInfo> mNanoAppHash = new ConcurrentHashMap<>(); + // The next available instance ID (managed by the service) to assign to a nanoapp + private int mNextAvailableInstanceId = 0; + // A map of the long nanoapp ID to instance ID managed by the service + private final ConcurrentHashMap<Long, Integer> mNanoAppIdToInstanceMap = + new ConcurrentHashMap<>(); + private final ContextHubInfo[] mContextHubInfo; private final RemoteCallbackList<IContextHubCallback> mCallbacksList = new RemoteCallbackList<>(); - private native int nativeSendMessage(int[] header, byte[] data); - private native ContextHubInfo[] nativeInitialize(); + // Proxy object to communicate with the Context Hub HAL + private final IContexthub mContextHubProxy; + + // The manager for transaction queue + private final ContextHubTransactionManager mTransactionManager; + + // The manager for sending messages to/from clients + private final ContextHubClientManager mClientManager; + + // The default client for old API clients + private final Map<Integer, IContextHubClient> mDefaultClientMap; + + /** + * Class extending the callback to register with a Context Hub. + */ + private class ContextHubServiceCallback extends IContexthubCallback.Stub { + private final int mContextHubId; + + ContextHubServiceCallback(int contextHubId) { + mContextHubId = contextHubId; + } + + @Override + public void handleClientMsg(ContextHubMsg message) { + handleClientMessageCallback(mContextHubId, message); + } + + @Override + public void handleTxnResult(int transactionId, int result) { + handleTransactionResultCallback(mContextHubId, transactionId, result); + } + + @Override + public void handleHubEvent(int eventType) { + handleHubEventCallback(mContextHubId, eventType); + } + + @Override + public void handleAppAbort(long nanoAppId, int abortCode) { + handleAppAbortCallback(mContextHubId, nanoAppId, abortCode); + } + + @Override + public void handleAppsInfo(ArrayList<HubAppInfo> nanoAppInfoList) { + handleQueryAppsCallback(mContextHubId, nanoAppInfoList); + } + } public ContextHubService(Context context) { mContext = context; - mContextHubInfo = nativeInitialize(); + + mContextHubProxy = getContextHubProxy(); + if (mContextHubProxy == null) { + mTransactionManager = null; + mClientManager = null; + mDefaultClientMap = Collections.EMPTY_MAP; + mContextHubInfo = new ContextHubInfo[0]; + return; + } + + mClientManager = new ContextHubClientManager(mContext, mContextHubProxy); + mTransactionManager = new ContextHubTransactionManager(mContextHubProxy, mClientManager); + + List<ContextHub> hubList; + try { + hubList = mContextHubProxy.getHubs(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while getting Context Hub info", e); + hubList = Collections.emptyList(); + } + mContextHubInfo = ContextHubServiceUtil.createContextHubInfoArray(hubList); + + HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>(); + for (ContextHubInfo contextHubInfo : mContextHubInfo) { + int contextHubId = contextHubInfo.getId(); + + IContextHubClient client = mClientManager.registerClient( + createDefaultClientCallback(contextHubId), contextHubId); + defaultClientMap.put(contextHubId, client); + } + mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap); + + for (ContextHubInfo contextHubInfo : mContextHubInfo) { + int contextHubId = contextHubInfo.getId(); + try { + mContextHubProxy.registerCallback( + contextHubId, new ContextHubServiceCallback(contextHubId)); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while registering service callback for hub (ID = " + + contextHubId + ")", e); + } + } + + // Do a query to initialize the service cache list of nanoapps + // TODO(b/69270990): Remove this when old API is deprecated + for (ContextHubInfo contextHubInfo : mContextHubInfo) { + queryNanoAppsInternal(contextHubInfo.getId()); + } for (int i = 0; i < mContextHubInfo.length; i++) { Log.d(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId() - + ", name: " + mContextHubInfo[i].getName()); + + ", name: " + mContextHubInfo[i].getName()); } } + /** + * Creates a default client callback for old API clients. + * + * @param contextHubId the ID of the hub to attach this client to + * @return the internal callback interface + */ + private IContextHubClientCallback createDefaultClientCallback(int contextHubId) { + return new IContextHubClientCallback.Stub() { + @Override + public void onMessageFromNanoApp(NanoAppMessage message) { + int nanoAppInstanceId = + mNanoAppIdToInstanceMap.containsKey(message.getNanoAppId()) ? + mNanoAppIdToInstanceMap.get(message.getNanoAppId()) : -1; + + onMessageReceiptOldApi( + message.getMessageType(), contextHubId, nanoAppInstanceId, + message.getMessageBody()); + } + + @Override + public void onHubReset() { + byte[] data = {TransactionResult.SUCCESS}; + onMessageReceiptOldApi(MSG_HUB_RESET, contextHubId, OS_APP_INSTANCE, data); + } + + @Override + public void onNanoAppAborted(long nanoAppId, int abortCode) { + } + + @Override + public void onNanoAppLoaded(long nanoAppId) { + } + + @Override + public void onNanoAppUnloaded(long nanoAppId) { + } + + @Override + public void onNanoAppEnabled(long nanoAppId) { + } + + @Override + public void onNanoAppDisabled(long nanoAppId) { + } + }; + } + + /** + * @return the IContexthub proxy interface + */ + private IContexthub getContextHubProxy() { + IContexthub proxy = null; + try { + proxy = IContexthub.getService(true /* retry */); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while attaching to Context Hub HAL proxy", e); + } catch (NoSuchElementException e) { + Log.i(TAG, "Context Hub HAL service not found"); + } + + return proxy; + } + @Override public int registerCallback(IContextHubCallback callback) throws RemoteException { checkPermissions(); mCallbacksList.register(callback); + Log.d(TAG, "Added callback, total callbacks " + - mCallbacksList.getRegisteredCallbackCount()); + mCallbacksList.getRegisteredCallbackCount()); return 0; } @@ -109,29 +286,112 @@ public class ContextHubService extends IContextHubService.Stub { for (int i = 0; i < returnArray.length; ++i) { returnArray[i] = i; Log.d(TAG, String.format("Hub %s is mapped to %d", - mContextHubInfo[i].getName(), returnArray[i])); + mContextHubInfo[i].getName(), returnArray[i])); } return returnArray; } @Override - public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException { + public ContextHubInfo getContextHubInfo(int contextHubId) throws RemoteException { checkPermissions(); - if (!(contextHubHandle >= 0 && contextHubHandle < mContextHubInfo.length)) { - Log.e(TAG, "Invalid context hub handle " + contextHubHandle); + if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) { + Log.e(TAG, "Invalid context hub handle " + contextHubId); return null; // null means fail } - return mContextHubInfo[contextHubHandle]; + return mContextHubInfo[contextHubId]; + } + + /** + * Creates an internal load transaction callback to be used for old API clients + * + * @param contextHubId the ID of the hub to load the binary + * @param nanoAppBinary the binary to load + * @return the callback interface + */ + private IContextHubTransactionCallback createLoadTransactionCallback( + int contextHubId, NanoAppBinary nanoAppBinary) { + return new IContextHubTransactionCallback.Stub() { + @Override + public void onTransactionComplete(int result) { + handleLoadResponseOldApi(contextHubId, result, nanoAppBinary); + } + + @Override + public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) { + } + }; + } + + /** + * Creates an internal unload transaction callback to be used for old API clients + * + * @param contextHubId the ID of the hub to unload the nanoapp + * @param nanoAppId the ID of the nanoapp to unload + * @return the callback interface + */ + private IContextHubTransactionCallback createUnloadTransactionCallback( + int contextHubId, long nanoAppId) { + return new IContextHubTransactionCallback.Stub() { + @Override + public void onTransactionComplete(int result) { + handleUnloadResponseOldApi(contextHubId, result, nanoAppId); + } + + @Override + public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) { + } + }; + } + + /** + * Creates an internal query transaction callback to be used for old API clients + * + * @param contextHubId the ID of the hub to query + * @return the callback interface + */ + private IContextHubTransactionCallback createQueryTransactionCallback(int contextHubId) { + return new IContextHubTransactionCallback.Stub() { + @Override + public void onTransactionComplete(int result) { + } + + @Override + public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) { + byte[] data = {(byte) result}; + onMessageReceiptOldApi(MSG_QUERY_NANO_APPS, contextHubId, OS_APP_INSTANCE, data); + } + }; + } + + /** + * Adds a new transaction to the transaction manager queue + * + * @param transaction the transaction to add + * @return the result of adding the transaction + */ + private int addTransaction(ContextHubServiceTransaction transaction) { + int result = Result.OK; + try { + mTransactionManager.addTransaction(transaction); + } catch (IllegalStateException e) { + Log.e(TAG, e.getMessage()); + result = Result.TRANSACTION_PENDING; /* failed */ + } + + return result; } @Override - public int loadNanoApp(int contextHubHandle, NanoApp app) throws RemoteException { + public int loadNanoApp(int contextHubId, NanoApp app) throws RemoteException { checkPermissions(); + if (mContextHubProxy == null) { + return -1; + } - if (!(contextHubHandle >= 0 && contextHubHandle < mContextHubInfo.length)) { - Log.e(TAG, "Invalid contextHubhandle " + contextHubHandle); + if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) { + Log.e(TAG, "Invalid contextHubhandle " + contextHubId); return -1; } if (app == null) { @@ -139,20 +399,17 @@ public class ContextHubService extends IContextHubService.Stub { return -1; } - int[] msgHeader = new int[MSG_LOAD_APP_HEADER_SIZE]; - msgHeader[HEADER_FIELD_HUB_HANDLE] = contextHubHandle; - msgHeader[HEADER_FIELD_APP_INSTANCE] = OS_APP_INSTANCE; - msgHeader[HEADER_FIELD_MSG_VERSION] = 0; - msgHeader[HEADER_FIELD_MSG_TYPE] = MSG_LOAD_NANO_APP; - - long appId = app.getAppId(); + // Create an internal IContextHubTransactionCallback for the old API clients + NanoAppBinary nanoAppBinary = new NanoAppBinary(app.getAppBinary()); + IContextHubTransactionCallback onCompleteCallback = + createLoadTransactionCallback(contextHubId, nanoAppBinary); - msgHeader[HEADER_FIELD_LOAD_APP_ID_LO] = (int)(appId & 0xFFFFFFFF); - msgHeader[HEADER_FIELD_LOAD_APP_ID_HI] = (int)((appId >> 32) & 0xFFFFFFFF); + ContextHubServiceTransaction transaction = mTransactionManager.createLoadTransaction( + contextHubId, nanoAppBinary, onCompleteCallback); - int errVal = nativeSendMessage(msgHeader, app.getAppBinary()); - if (errVal != 0) { - Log.e(TAG, "Send Message returns error" + contextHubHandle); + int result = addTransaction(transaction); + if (result != Result.OK) { + Log.e(TAG, "Failed to load nanoapp with error code " + result); return -1; } @@ -163,23 +420,26 @@ public class ContextHubService extends IContextHubService.Stub { @Override public int unloadNanoApp(int nanoAppInstanceHandle) throws RemoteException { checkPermissions(); + if (mContextHubProxy == null) { + return -1; + } + NanoAppInstanceInfo info = mNanoAppHash.get(nanoAppInstanceHandle); if (info == null) { Log.e(TAG, "Cannot find app with handle " + nanoAppInstanceHandle); return -1; //means failed } - // Call Native interface here - int[] msgHeader = new int[MSG_HEADER_SIZE]; - msgHeader[HEADER_FIELD_HUB_HANDLE] = ANY_HUB; - msgHeader[HEADER_FIELD_APP_INSTANCE] = nanoAppInstanceHandle; - msgHeader[HEADER_FIELD_MSG_VERSION] = 0; - msgHeader[HEADER_FIELD_MSG_TYPE] = MSG_UNLOAD_NANO_APP; + int contextHubId = info.getContexthubId(); + long nanoAppId = info.getAppId(); + IContextHubTransactionCallback onCompleteCallback = + createUnloadTransactionCallback(contextHubId, nanoAppId); + ContextHubServiceTransaction transaction = mTransactionManager.createUnloadTransaction( + contextHubId, nanoAppId, onCompleteCallback); - byte msg[] = new byte[0]; - - if (nativeSendMessage(msgHeader, msg) != 0) { - Log.e(TAG, "native send message fails"); + int result = addTransaction(transaction); + if (result != Result.OK) { + Log.e(TAG, "Failed to unload nanoapp with error code " + result); return -1; } @@ -189,7 +449,7 @@ public class ContextHubService extends IContextHubService.Stub { @Override public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppInstanceHandle) - throws RemoteException { + throws RemoteException { checkPermissions(); // This assumes that all the nanoAppInfo is current. This is reasonable // for the use cases for tightly controlled nanoApps. @@ -206,7 +466,7 @@ public class ContextHubService extends IContextHubService.Stub { checkPermissions(); ArrayList<Integer> foundInstances = new ArrayList<Integer>(); - for (Integer nanoAppInstance: mNanoAppHash.keySet()) { + for (Integer nanoAppInstance : mNanoAppHash.keySet()) { NanoAppInstanceInfo info = mNanoAppHash.get(nanoAppInstance); if (filter.testMatch(info)) { @@ -223,23 +483,259 @@ public class ContextHubService extends IContextHubService.Stub { return retArray; } + /** + * Performs a query at the specified hub. + * + * This method should only be invoked internally by the service, either to update the service + * cache or as a result of an explicit query requested by a client through the sendMessage API. + * + * @param contextHubId the ID of the hub to do the query + * @return the result of the query + */ + private int queryNanoAppsInternal(int contextHubId) { + if (mContextHubProxy == null) { + return Result.UNKNOWN_FAILURE; + } + + IContextHubTransactionCallback onCompleteCallback = + createQueryTransactionCallback(contextHubId); + ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction( + contextHubId, onCompleteCallback); + + return addTransaction(transaction); + } + @Override - public int sendMessage(int hubHandle, int nanoAppHandle, ContextHubMessage msg) - throws RemoteException { + public int sendMessage( + int hubHandle, int nanoAppHandle, ContextHubMessage msg) throws RemoteException { checkPermissions(); - - if (msg == null || msg.getData() == null) { - Log.w(TAG, "null ptr"); + if (mContextHubProxy == null) { + return -1; + } + if (msg == null) { + Log.e(TAG, "ContextHubMessage cannot be null"); + return -1; + } + if (msg.getData() == null) { + Log.e(TAG, "ContextHubMessage message body cannot be null"); + return -1; + } + if (!mDefaultClientMap.containsKey(hubHandle)) { + Log.e(TAG, "Hub with ID " + hubHandle + " does not exist"); return -1; } - int[] msgHeader = new int[MSG_HEADER_SIZE]; - msgHeader[HEADER_FIELD_HUB_HANDLE] = hubHandle; - msgHeader[HEADER_FIELD_APP_INSTANCE] = nanoAppHandle; - msgHeader[HEADER_FIELD_MSG_VERSION] = msg.getVersion(); - msgHeader[HEADER_FIELD_MSG_TYPE] = msg.getMsgType(); + boolean success = false; + if (nanoAppHandle == OS_APP_INSTANCE) { + if (msg.getMsgType() == MSG_QUERY_NANO_APPS) { + success = (queryNanoAppsInternal(hubHandle) == Result.OK); + } else { + Log.e(TAG, "Invalid OS message params of type " + msg.getMsgType()); + } + } else { + NanoAppInstanceInfo info = getNanoAppInstanceInfo(nanoAppHandle); + if (info != null) { + NanoAppMessage message = NanoAppMessage.createMessageToNanoApp( + info.getAppId(), msg.getMsgType(), msg.getData()); + + IContextHubClient client = mDefaultClientMap.get(hubHandle); + success = (client.sendMessageToNanoApp(message) == + ContextHubTransaction.TRANSACTION_SUCCESS); + } else { + Log.e(TAG, "Failed to send nanoapp message - nanoapp with instance ID " + + nanoAppHandle + " does not exist."); + } + } + + return success ? 0 : -1; + } - return nativeSendMessage(msgHeader, msg.getData()); + /** + * Handles a unicast or broadcast message from a nanoapp. + * + * @param contextHubId the ID of the hub the message came from + * @param message the message contents + */ + private void handleClientMessageCallback(int contextHubId, ContextHubMsg message) { + mClientManager.onMessageFromNanoApp(contextHubId, message); + } + + /** + * A helper function to handle a load response from the Context Hub for the old API. + * + * TODO(b/69270990): Remove this once the old APIs are obsolete. + */ + private void handleLoadResponseOldApi( + int contextHubId, int result, NanoAppBinary nanoAppBinary) { + if (nanoAppBinary == null) { + Log.e(TAG, "Nanoapp binary field was null for a load transaction"); + return; + } + + // NOTE: The legacy JNI code used to do a query right after a load success + // to synchronize the service cache. Instead store the binary that was requested to + // load to update the cache later without doing a query. + int instanceId = 0; + long nanoAppId = nanoAppBinary.getNanoAppId(); + int nanoAppVersion = nanoAppBinary.getNanoAppVersion(); + if (result == TransactionResult.SUCCESS) { + if (mNanoAppIdToInstanceMap.containsKey(nanoAppId)) { + instanceId = mNanoAppIdToInstanceMap.get(nanoAppId); + } else { + instanceId = mNextAvailableInstanceId++; + mNanoAppIdToInstanceMap.put(nanoAppId, instanceId); + } + + addAppInstance(contextHubId, instanceId, nanoAppId, nanoAppVersion); + } + + byte[] data = new byte[5]; + data[0] = (byte) result; + ByteBuffer.wrap(data, 1, 4).order(ByteOrder.nativeOrder()).putInt(instanceId); + + onMessageReceiptOldApi(MSG_LOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data); + } + + /** + * A helper function to handle an unload response from the Context Hub for the old API. + * + * TODO(b/69270990): Remove this once the old APIs are obsolete. + */ + private void handleUnloadResponseOldApi( + int contextHubId, int result, long nanoAppId) { + if (result == TransactionResult.SUCCESS) { + int instanceId = mNanoAppIdToInstanceMap.get(nanoAppId); + deleteAppInstance(instanceId); + mNanoAppIdToInstanceMap.remove(nanoAppId); + } + + byte[] data = new byte[1]; + data[0] = (byte) result; + onMessageReceiptOldApi(MSG_UNLOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data); + } + + /** + * Handles a transaction response from a Context Hub. + * + * @param contextHubId the ID of the hub the response came from + * @param transactionId the ID of the transaction + * @param result the result of the transaction reported by the hub + */ + private void handleTransactionResultCallback(int contextHubId, int transactionId, int result) { + mTransactionManager.onTransactionResponse(transactionId, result); + } + + /** + * Handles an asynchronous event from a Context Hub. + * + * @param contextHubId the ID of the hub the response came from + * @param eventType the type of the event as defined in Context Hub HAL AsyncEventType + */ + private void handleHubEventCallback(int contextHubId, int eventType) { + if (eventType == AsyncEventType.RESTARTED) { + mTransactionManager.onHubReset(); + queryNanoAppsInternal(contextHubId); + + mClientManager.onHubReset(contextHubId); + } else { + Log.i(TAG, "Received unknown hub event (hub ID = " + contextHubId + ", type = " + + eventType + ")"); + } + } + + /** + * Handles an asynchronous abort event of a nanoapp. + * + * @param contextHubId the ID of the hub that the nanoapp aborted in + * @param nanoAppId the ID of the aborted nanoapp + * @param abortCode the nanoapp-specific abort code + */ + private void handleAppAbortCallback(int contextHubId, long nanoAppId, int abortCode) { + // TODO(b/31049861): Implement this + } + + /** + * Handles a query response from a Context Hub. + * + * @param contextHubId the ID of the hub of the response + * @param nanoAppInfoList the list of loaded nanoapps + */ + private void handleQueryAppsCallback(int contextHubId, List<HubAppInfo> nanoAppInfoList) { + List<NanoAppState> nanoAppStateList = + ContextHubServiceUtil.createNanoAppStateList(nanoAppInfoList); + + updateServiceCache(contextHubId, nanoAppInfoList); + mTransactionManager.onQueryResponse(nanoAppStateList); + } + + /** + * Updates the service's cache of the list of loaded nanoapps using a nanoapp list response. + * + * TODO(b/69270990): Remove this when the old API functionality is removed. + * + * @param contextHubId the ID of the hub the response came from + * @param nanoAppInfoList the list of loaded nanoapps + */ + private void updateServiceCache(int contextHubId, List<HubAppInfo> nanoAppInfoList) { + synchronized (mNanoAppHash) { + for (int instanceId : mNanoAppHash.keySet()) { + if (mNanoAppHash.get(instanceId).getContexthubId() == contextHubId) { + deleteAppInstance(instanceId); + } + } + + for (HubAppInfo appInfo : nanoAppInfoList) { + int instanceId; + long nanoAppId = appInfo.appId; + if (mNanoAppIdToInstanceMap.containsKey(nanoAppId)) { + instanceId = mNanoAppIdToInstanceMap.get(nanoAppId); + } else { + instanceId = mNextAvailableInstanceId++; + mNanoAppIdToInstanceMap.put(nanoAppId, instanceId); + } + + addAppInstance(contextHubId, instanceId, nanoAppId, appInfo.version); + } + } + } + + /** + * @param contextHubId the hub ID to validate + * @return {@code true} if the ID represents that of an available hub, {@code false} otherwise + */ + private boolean isValidContextHubId(int contextHubId) { + for (ContextHubInfo hubInfo : mContextHubInfo) { + if (hubInfo.getId() == contextHubId) { + return true; + } + } + + return false; + } + + /** + * Creates and registers a client at the service for the specified Context Hub. + * + * @param clientCallback the client interface to register with the service + * @param contextHubId the ID of the hub this client is attached to + * @return the generated client interface, null if registration was unsuccessful + * + * @throws IllegalArgumentException if contextHubId is not a valid ID + * @throws IllegalStateException if max number of clients have already registered + * @throws NullPointerException if clientCallback is null + */ + @Override + public IContextHubClient createClient( + IContextHubClientCallback clientCallback, int contextHubId) throws RemoteException { + checkPermissions(); + if (!isValidContextHubId(contextHubId)) { + throw new IllegalArgumentException("Invalid context hub ID " + contextHubId); + } + if (clientCallback == null) { + throw new NullPointerException("Cannot register client with null callback"); + } + + return mClientManager.registerClient(clientCallback, contextHubId); } @Override @@ -257,7 +753,7 @@ public class ContextHubService extends IContextHubService.Stub { pw.println(""); pw.println("=================== NANOAPPS ===================="); // Dump nanoAppHash - for (Integer nanoAppInstance: mNanoAppHash.keySet()) { + for (Integer nanoAppInstance : mNanoAppHash.keySet()) { pw.println(nanoAppInstance + " : " + mNanoAppHash.get(nanoAppInstance).toString()); } @@ -265,22 +761,18 @@ public class ContextHubService extends IContextHubService.Stub { } private void checkPermissions() { - mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE); + ContextHubServiceUtil.checkPermissions(mContext); } - private int onMessageReceipt(int[] header, byte[] data) { - if (header == null || data == null || header.length < MSG_HEADER_SIZE) { - return -1; + private int onMessageReceiptOldApi(int msgType, int hubHandle, int appInstance, byte[] data) { + if (data == null) { + return -1; } + int msgVersion = 0; int callbacksCount = mCallbacksList.beginBroadcast(); - int msgType = header[HEADER_FIELD_MSG_TYPE]; - int msgVersion = header[HEADER_FIELD_MSG_VERSION]; - int hubHandle = header[HEADER_FIELD_HUB_HANDLE]; - int appInstance = header[HEADER_FIELD_APP_INSTANCE]; - Log.d(TAG, "Sending message " + msgType + " version " + msgVersion + " from hubHandle " + - hubHandle + ", appInstance " + appInstance + ", callBackCount " + callbacksCount); + hubHandle + ", appInstance " + appInstance + ", callBackCount " + callbacksCount); if (callbacksCount < 1) { Log.v(TAG, "No message callbacks registered."); @@ -323,8 +815,8 @@ public class ContextHubService extends IContextHubService.Stub { } mNanoAppHash.put(appInstanceHandle, appInfo); - Log.d(TAG, action + " app instance " + appInstanceHandle + " with id " - + appId + " version " + appVersion); + Log.d(TAG, action + " app instance " + appInstanceHandle + " with id 0x" + + Long.toHexString(appId) + " version 0x" + Integer.toHexString(appVersion)); return 0; } diff --git a/com/android/server/location/ContextHubServiceTransaction.java b/com/android/server/location/ContextHubServiceTransaction.java new file mode 100644 index 00000000..66145bbb --- /dev/null +++ b/com/android/server/location/ContextHubServiceTransaction.java @@ -0,0 +1,161 @@ +/* + * 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 com.android.server.location; + +import android.hardware.location.ContextHubTransaction; +import android.hardware.location.NanoAppState; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * An abstract class representing transactions requested to the Context Hub Service. + * + * @hide + */ +/* package */ abstract class ContextHubServiceTransaction { + private final int mTransactionId; + @ContextHubTransaction.Type + private final int mTransactionType; + + /* + * true if the transaction has already completed, false otherwise + */ + private boolean mIsComplete = false; + + /* package */ ContextHubServiceTransaction(int id, int type) { + mTransactionId = id; + mTransactionType = type; + } + + /** + * Starts this transaction with a Context Hub. + * + * All instances of this class must implement this method by making an asynchronous request to + * a hub. + * + * @return the synchronous error code of the transaction start + */ + /* package */ + abstract int onTransact(); + + /** + * A function to invoke when a transaction times out. + * + * All instances of this class must implement this method by reporting the timeout to the + * client. + */ + /* package */ + abstract void onTimeout(); + + /** + * A function to invoke when the transaction completes. + * + * Only relevant for load, unload, enable, or disable transactions. + * + * @param result the result of the transaction + */ + /* package */ void onTransactionComplete(int result) { + } + + /** + * A function to invoke when a query transaction completes. + * + * Only relevant for query transactions. + * + * @param result the result of the query + * @param nanoAppStateList the list of nanoapps given by the query response + */ + /* package */ void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) { + } + + /** + * @return the ID of this transaction + */ + /* package */ int getTransactionId() { + return mTransactionId; + } + + /** + * @return the type of this transaction + * @see ContextHubTransaction.Type + */ + @ContextHubTransaction.Type + /* package */ int getTransactionType() { + return mTransactionType; + } + + /** + * Gets the timeout period as defined in IContexthub.hal + * + * @return the timeout of this transaction in the specified time unit + */ + /* package */ long getTimeout(TimeUnit unit) { + switch (mTransactionType) { + case ContextHubTransaction.TYPE_LOAD_NANOAPP: + return unit.convert(30L, TimeUnit.SECONDS); + case ContextHubTransaction.TYPE_UNLOAD_NANOAPP: + case ContextHubTransaction.TYPE_ENABLE_NANOAPP: + case ContextHubTransaction.TYPE_DISABLE_NANOAPP: + case ContextHubTransaction.TYPE_QUERY_NANOAPPS: + // Note: query timeout is not specified at the HAL + default: /* fall through */ + return unit.convert(5L, TimeUnit.SECONDS); + } + } + + /** + * Marks the transaction as complete. + * + * Should only be called as a result of a response from a Context Hub callback + */ + /* package */ void setComplete() { + mIsComplete = true; + } + + /** + * @return true if the transaction has already completed, false otherwise + */ + /* package */ boolean isComplete() { + return mIsComplete; + } + + /** + * @return the human-readable string of this transaction's type + */ + private String getTransactionTypeString() { + switch (mTransactionType) { + case ContextHubTransaction.TYPE_LOAD_NANOAPP: + return "Load"; + case ContextHubTransaction.TYPE_UNLOAD_NANOAPP: + return "Unload"; + case ContextHubTransaction.TYPE_ENABLE_NANOAPP: + return "Enable"; + case ContextHubTransaction.TYPE_DISABLE_NANOAPP: + return "Disable"; + case ContextHubTransaction.TYPE_QUERY_NANOAPPS: + return "Query"; + default: + return "Unknown"; + } + } + + @Override + public String toString() { + return getTransactionTypeString() + " transaction (ID = " + mTransactionId + ")"; + } +} diff --git a/com/android/server/location/ContextHubServiceUtil.java b/com/android/server/location/ContextHubServiceUtil.java new file mode 100644 index 00000000..6faeb72e --- /dev/null +++ b/com/android/server/location/ContextHubServiceUtil.java @@ -0,0 +1,211 @@ +/* + * 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 com.android.server.location; + +import android.Manifest; +import android.content.Context; +import android.hardware.contexthub.V1_0.ContextHub; +import android.hardware.contexthub.V1_0.ContextHubMsg; +import android.hardware.contexthub.V1_0.HostEndPoint; +import android.hardware.contexthub.V1_0.HubAppInfo; +import android.hardware.contexthub.V1_0.Result; +import android.hardware.location.ContextHubInfo; +import android.hardware.location.ContextHubTransaction; +import android.hardware.location.NanoAppBinary; +import android.hardware.location.NanoAppMessage; +import android.hardware.location.NanoAppState; +import android.util.Log; + +import java.util.List; +import java.util.ArrayList; + +/** + * A class encapsulating helper functions used by the ContextHubService class + */ +/* package */ class ContextHubServiceUtil { + private static final String TAG = "ContextHubServiceUtil"; + private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE; + private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '" + + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware"; + + /** + * Creates a ContextHubInfo array from an ArrayList of HIDL ContextHub objects. + * + * @param hubList the ContextHub ArrayList + * @return the ContextHubInfo array + */ + /* package */ + static ContextHubInfo[] createContextHubInfoArray(List<ContextHub> hubList) { + ContextHubInfo[] contextHubInfoList = new ContextHubInfo[hubList.size()]; + for (int i = 0; i < hubList.size(); i++) { + contextHubInfoList[i] = new ContextHubInfo(hubList.get(i)); + } + + return contextHubInfoList; + } + + /** + * Copies a primitive byte array to a ArrayList<Byte>. + * + * @param inputArray the primitive byte array + * @param outputArray the ArrayList<Byte> array to append + */ + /* package */ + static void copyToByteArrayList(byte[] inputArray, ArrayList<Byte> outputArray) { + outputArray.clear(); + outputArray.ensureCapacity(inputArray.length); + for (byte element : inputArray) { + outputArray.add(element); + } + } + + /** + * Creates a byte array given a ArrayList<Byte> and copies its contents. + * + * @param array the ArrayList<Byte> object + * @return the byte array + */ + /* package */ + static byte[] createPrimitiveByteArray(ArrayList<Byte> array) { + byte[] primitiveArray = new byte[array.size()]; + for (int i = 0; i < array.size(); i++) { + primitiveArray[i] = array.get(i); + } + + return primitiveArray; + } + + /** + * Generates the Context Hub HAL's NanoAppBinary object from the client-facing + * android.hardware.location.NanoAppBinary object. + * + * @param nanoAppBinary the client-facing NanoAppBinary object + * @return the Context Hub HAL's NanoAppBinary object + */ + /* package */ + static android.hardware.contexthub.V1_0.NanoAppBinary createHidlNanoAppBinary( + NanoAppBinary nanoAppBinary) { + android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary = + new android.hardware.contexthub.V1_0.NanoAppBinary(); + + hidlNanoAppBinary.appId = nanoAppBinary.getNanoAppId(); + hidlNanoAppBinary.appVersion = nanoAppBinary.getNanoAppVersion(); + hidlNanoAppBinary.flags = nanoAppBinary.getFlags(); + hidlNanoAppBinary.targetChreApiMajorVersion = nanoAppBinary.getTargetChreApiMajorVersion(); + hidlNanoAppBinary.targetChreApiMinorVersion = nanoAppBinary.getTargetChreApiMinorVersion(); + + // Log exceptions while processing the binary, but continue to pass down the binary + // since the error checking is deferred to the Context Hub. + try { + copyToByteArrayList(nanoAppBinary.getBinaryNoHeader(), hidlNanoAppBinary.customBinary); + } catch (IndexOutOfBoundsException e) { + Log.w(TAG, e.getMessage()); + } catch (NullPointerException e) { + Log.w(TAG, "NanoApp binary was null"); + } + + return hidlNanoAppBinary; + } + + /** + * Generates a client-facing NanoAppState array from a HAL HubAppInfo array. + * + * @param nanoAppInfoList the array of HubAppInfo objects + * @return the corresponding array of NanoAppState objects + */ + /* package */ + static List<NanoAppState> createNanoAppStateList( + List<HubAppInfo> nanoAppInfoList) { + ArrayList<NanoAppState> nanoAppStateList = new ArrayList<>(); + for (HubAppInfo appInfo : nanoAppInfoList) { + nanoAppStateList.add( + new NanoAppState(appInfo.appId, appInfo.version, appInfo.enabled)); + } + + return nanoAppStateList; + } + + /** + * Creates a HIDL ContextHubMsg object to send to a nanoapp. + * + * @param hostEndPoint the ID of the client sending the message + * @param message the client-facing NanoAppMessage object describing the message + * @return the HIDL ContextHubMsg object + */ + /* package */ + static ContextHubMsg createHidlContextHubMessage(short hostEndPoint, NanoAppMessage message) { + ContextHubMsg hidlMessage = new ContextHubMsg(); + + hidlMessage.appName = message.getNanoAppId(); + hidlMessage.hostEndPoint = hostEndPoint; + hidlMessage.msgType = message.getMessageType(); + copyToByteArrayList(message.getMessageBody(), hidlMessage.msg); + + return hidlMessage; + } + + /** + * Creates a client-facing NanoAppMessage object to send to a client. + * + * @param message the HIDL ContextHubMsg object from a nanoapp + * @return the NanoAppMessage object + */ + /* package */ + static NanoAppMessage createNanoAppMessage(ContextHubMsg message) { + byte[] messageArray = createPrimitiveByteArray(message.msg); + + return NanoAppMessage.createMessageFromNanoApp( + message.appName, message.msgType, messageArray, + message.hostEndPoint == HostEndPoint.BROADCAST); + } + + /** + * Checks for location hardware permissions. + * + * @param context the context of the service + */ + /* package */ + static void checkPermissions(Context context) { + context.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE); + } + + /** + * Helper function to convert from the HAL Result enum error code to the + * ContextHubTransaction.Result type. + * + * @param halResult the Result enum error code + * @return the ContextHubTransaction.Result equivalent + */ + @ContextHubTransaction.Result + /* package */ + static int toTransactionResult(int halResult) { + switch (halResult) { + case Result.OK: + return ContextHubTransaction.TRANSACTION_SUCCESS; + case Result.BAD_PARAMS: + return ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS; + case Result.NOT_INIT: + return ContextHubTransaction.TRANSACTION_FAILED_UNINITIALIZED; + case Result.TRANSACTION_PENDING: + return ContextHubTransaction.TRANSACTION_FAILED_PENDING; + case Result.TRANSACTION_FAILED: + case Result.UNKNOWN_FAILURE: + default: /* fall through */ + return ContextHubTransaction.TRANSACTION_FAILED_UNKNOWN; + } + } +} diff --git a/com/android/server/location/ContextHubTransactionManager.java b/com/android/server/location/ContextHubTransactionManager.java new file mode 100644 index 00000000..47d9d568 --- /dev/null +++ b/com/android/server/location/ContextHubTransactionManager.java @@ -0,0 +1,351 @@ +/* + * 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 com.android.server.location; + +import android.hardware.contexthub.V1_0.IContexthub; +import android.hardware.contexthub.V1_0.Result; +import android.hardware.contexthub.V1_0.TransactionResult; +import android.hardware.location.ContextHubTransaction; +import android.hardware.location.IContextHubTransactionCallback; +import android.hardware.location.NanoAppBinary; +import android.hardware.location.NanoAppState; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Manages transactions at the Context Hub Service. + * + * This class maintains a queue of transaction requests made to the ContextHubService by clients, + * and executes them through the Context Hub. At any point in time, either the transaction queue is + * empty, or there is a pending transaction that is waiting for an asynchronous response from the + * hub. This class also handles synchronous errors and timeouts of each transaction. + * + * @hide + */ +/* package */ class ContextHubTransactionManager { + private static final String TAG = "ContextHubTransactionManager"; + + /* + * Maximum number of transaction requests that can be pending at a time + */ + private static final int MAX_PENDING_REQUESTS = 10; + + /* + * The proxy to talk to the Context Hub + */ + private final IContexthub mContextHubProxy; + + /* + * The manager for all clients for the service. + */ + private final ContextHubClientManager mClientManager; + + /* + * A queue containing the current transactions + */ + private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>(); + + /* + * The next available transaction ID + */ + private final AtomicInteger mNextAvailableId = new AtomicInteger(); + + /* + * An executor and the future object for scheduling timeout timers + */ + private final ScheduledThreadPoolExecutor mTimeoutExecutor = new ScheduledThreadPoolExecutor(1); + private ScheduledFuture<?> mTimeoutFuture = null; + + /* package */ ContextHubTransactionManager( + IContexthub contextHubProxy, ContextHubClientManager clientManager) { + mContextHubProxy = contextHubProxy; + mClientManager = clientManager; + } + + /** + * Creates a transaction for loading a nanoapp. + * + * @param contextHubId the ID of the hub to load the nanoapp to + * @param nanoAppBinary the binary of the nanoapp to load + * @param onCompleteCallback the client on complete callback + * @return the generated transaction + */ + /* package */ ContextHubServiceTransaction createLoadTransaction( + int contextHubId, NanoAppBinary nanoAppBinary, + IContextHubTransactionCallback onCompleteCallback) { + return new ContextHubServiceTransaction( + mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_LOAD_NANOAPP) { + @Override + /* package */ int onTransact() { + android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary = + ContextHubServiceUtil.createHidlNanoAppBinary(nanoAppBinary); + try { + return mContextHubProxy.loadNanoApp( + contextHubId, hidlNanoAppBinary, this.getTransactionId()); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while trying to load nanoapp with ID 0x" + + Long.toHexString(nanoAppBinary.getNanoAppId())); + return Result.UNKNOWN_FAILURE; + } + } + + @Override + /* package */ void onTimeout() { + onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT); + } + + @Override + /* package */ void onTransactionComplete(int result) { + try { + onCompleteCallback.onTransactionComplete(result); + if (result == Result.OK) { + mClientManager.onNanoAppLoaded(contextHubId, nanoAppBinary.getNanoAppId()); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while calling client onTransactionComplete"); + } + } + }; + } + + /** + * Creates a transaction for unloading a nanoapp. + * + * @param contextHubId the ID of the hub to load the nanoapp to + * @param nanoAppId the ID of the nanoapp to unload + * @param onCompleteCallback the client on complete callback + * @return the generated transaction + */ + /* package */ ContextHubServiceTransaction createUnloadTransaction( + int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback) { + return new ContextHubServiceTransaction( + mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_UNLOAD_NANOAPP) { + @Override + /* package */ int onTransact() { + try { + return mContextHubProxy.unloadNanoApp( + contextHubId, nanoAppId, this.getTransactionId()); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while trying to unload nanoapp with ID 0x" + + Long.toHexString(nanoAppId)); + return Result.UNKNOWN_FAILURE; + } + } + + @Override + /* package */ void onTimeout() { + onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT); + } + + @Override + /* package */ void onTransactionComplete(int result) { + try { + onCompleteCallback.onTransactionComplete(result); + if (result == Result.OK) { + mClientManager.onNanoAppUnloaded(contextHubId, nanoAppId); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while calling client onTransactionComplete"); + } + } + }; + } + + /** + * Creates a transaction for querying for a list of nanoapps. + * + * @param contextHubId the ID of the hub to query + * @param onCompleteCallback the client on complete callback + * @return the generated transaction + */ + /* package */ ContextHubServiceTransaction createQueryTransaction( + int contextHubId, IContextHubTransactionCallback onCompleteCallback) { + return new ContextHubServiceTransaction( + mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_QUERY_NANOAPPS) { + @Override + /* package */ int onTransact() { + try { + return mContextHubProxy.queryApps(contextHubId); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while trying to query for nanoapps"); + return Result.UNKNOWN_FAILURE; + } + } + + @Override + /* package */ void onTimeout() { + onQueryResponse(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT, + Collections.emptyList()); + } + + @Override + /* package */ void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) { + try { + onCompleteCallback.onQueryResponse(result, nanoAppStateList); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while calling client onQueryComplete"); + } + } + }; + } + + /** + * Adds a new transaction to the queue. + * + * If there was no pending transaction at the time, the transaction that was added will be + * started in this method. + * + * @param transaction the transaction to add + * @throws IllegalStateException if the queue is full + */ + /* package */ + synchronized void addTransaction( + ContextHubServiceTransaction transaction) throws IllegalStateException { + if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) { + throw new IllegalStateException("Transaction transaction queue is full (capacity = " + + MAX_PENDING_REQUESTS + ")"); + } + mTransactionQueue.add(transaction); + + if (mTransactionQueue.size() == 1) { + startNextTransaction(); + } + } + + /** + * Handles a transaction response from a Context Hub. + * + * @param transactionId the transaction ID of the response + * @param result the result of the transaction + */ + /* package */ + synchronized void onTransactionResponse(int transactionId, int result) { + ContextHubServiceTransaction transaction = mTransactionQueue.peek(); + if (transaction == null) { + Log.w(TAG, "Received unexpected transaction response (no transaction pending)"); + return; + } + if (transaction.getTransactionId() != transactionId) { + Log.w(TAG, "Received unexpected transaction response (expected ID = " + + transaction.getTransactionId() + ", received ID = " + transactionId + ")"); + return; + } + + transaction.onTransactionComplete(result); + removeTransactionAndStartNext(); + } + + /** + * Handles a query response from a Context Hub. + * + * @param nanoAppStateList the list of nanoapps included in the response + */ + /* package */ + synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) { + ContextHubServiceTransaction transaction = mTransactionQueue.peek(); + if (transaction == null) { + Log.w(TAG, "Received unexpected query response (no transaction pending)"); + return; + } + if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) { + Log.w(TAG, "Received unexpected query response (expected " + transaction + ")"); + return; + } + + transaction.onQueryResponse(TransactionResult.SUCCESS, nanoAppStateList); + removeTransactionAndStartNext(); + } + + /** + * Handles a hub reset event by stopping a pending transaction and starting the next. + */ + /* package */ + synchronized void onHubReset() { + ContextHubServiceTransaction transaction = mTransactionQueue.peek(); + if (transaction == null) { + return; + } + + removeTransactionAndStartNext(); + } + + /** + * Pops the front transaction from the queue and starts the next pending transaction request. + * + * Removing elements from the transaction queue must only be done through this method. When a + * pending transaction is removed, the timeout timer is cancelled and the transaction is marked + * complete. + * + * It is assumed that the transaction queue is non-empty when this method is invoked, and that + * the caller has obtained a lock on this ContextHubTransactionManager object. + */ + private void removeTransactionAndStartNext() { + mTimeoutFuture.cancel(false /* mayInterruptIfRunning */); + + ContextHubServiceTransaction transaction = mTransactionQueue.remove(); + transaction.setComplete(); + + if (!mTransactionQueue.isEmpty()) { + startNextTransaction(); + } + } + + /** + * Starts the next pending transaction request. + * + * Starting new transactions must only be done through this method. This method continues to + * process the transaction queue as long as there are pending requests, and no transaction is + * pending. + * + * It is assumed that the caller has obtained a lock on this ContextHubTransactionManager + * object. + */ + private void startNextTransaction() { + int result = Result.UNKNOWN_FAILURE; + while (result != Result.OK && !mTransactionQueue.isEmpty()) { + ContextHubServiceTransaction transaction = mTransactionQueue.peek(); + result = transaction.onTransact(); + + if (result == Result.OK) { + Runnable onTimeoutFunc = () -> { + synchronized (this) { + if (!transaction.isComplete()) { + Log.d(TAG, transaction + " timed out"); + transaction.onTimeout(); + + removeTransactionAndStartNext(); + } + } + }; + + long timeoutSeconds = transaction.getTimeout(TimeUnit.SECONDS); + mTimeoutFuture = mTimeoutExecutor.schedule(onTimeoutFunc, timeoutSeconds, + TimeUnit.SECONDS); + } else { + mTransactionQueue.remove(); + } + } + } +} diff --git a/com/android/server/locksettings/LockSettingsService.java b/com/android/server/locksettings/LockSettingsService.java index a1a01061..60f451ab 100644 --- a/com/android/server/locksettings/LockSettingsService.java +++ b/com/android/server/locksettings/LockSettingsService.java @@ -603,160 +603,156 @@ public class LockSettingsService extends ILockSettings.Stub { } private void migrateOldData() { - try { - // These Settings moved before multi-user was enabled, so we only have to do it for the - // root user. - if (getString("migrated", null, 0) == null) { - final ContentResolver cr = mContext.getContentResolver(); - for (String validSetting : VALID_SETTINGS) { - String value = Settings.Secure.getString(cr, validSetting); - if (value != null) { - setString(validSetting, value, 0); - } + // These Settings moved before multi-user was enabled, so we only have to do it for the + // root user. + if (getString("migrated", null, 0) == null) { + final ContentResolver cr = mContext.getContentResolver(); + for (String validSetting : VALID_SETTINGS) { + String value = Settings.Secure.getString(cr, validSetting); + if (value != null) { + setString(validSetting, value, 0); } - // No need to move the password / pattern files. They're already in the right place. - setString("migrated", "true", 0); - Slog.i(TAG, "Migrated lock settings to new location"); - } - - // These Settings changed after multi-user was enabled, hence need to be moved per user. - if (getString("migrated_user_specific", null, 0) == null) { - final ContentResolver cr = mContext.getContentResolver(); - List<UserInfo> users = mUserManager.getUsers(); - for (int user = 0; user < users.size(); user++) { - // Migrate owner info - final int userId = users.get(user).id; - final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO; - String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId); - if (!TextUtils.isEmpty(ownerInfo)) { - setString(OWNER_INFO, ownerInfo, userId); - Settings.Secure.putStringForUser(cr, OWNER_INFO, "", userId); - } + } + // No need to move the password / pattern files. They're already in the right place. + setString("migrated", "true", 0); + Slog.i(TAG, "Migrated lock settings to new location"); + } - // Migrate owner info enabled. Note there was a bug where older platforms only - // stored this value if the checkbox was toggled at least once. The code detects - // this case by handling the exception. - final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED; - boolean enabled; - try { - int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId); - enabled = ivalue != 0; - setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId); - } catch (SettingNotFoundException e) { - // Setting was never stored. Store it if the string is not empty. - if (!TextUtils.isEmpty(ownerInfo)) { - setLong(OWNER_INFO_ENABLED, 1, userId); - } - } - Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId); + // These Settings changed after multi-user was enabled, hence need to be moved per user. + if (getString("migrated_user_specific", null, 0) == null) { + final ContentResolver cr = mContext.getContentResolver(); + List<UserInfo> users = mUserManager.getUsers(); + for (int user = 0; user < users.size(); user++) { + // Migrate owner info + final int userId = users.get(user).id; + final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO; + String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId); + if (!TextUtils.isEmpty(ownerInfo)) { + setString(OWNER_INFO, ownerInfo, userId); + Settings.Secure.putStringForUser(cr, OWNER_INFO, "", userId); } - // No need to move the password / pattern files. They're already in the right place. - setString("migrated_user_specific", "true", 0); - Slog.i(TAG, "Migrated per-user lock settings to new location"); - } - - // Migrates biometric weak such that the fallback mechanism becomes the primary. - if (getString("migrated_biometric_weak", null, 0) == null) { - List<UserInfo> users = mUserManager.getUsers(); - for (int i = 0; i < users.size(); i++) { - int userId = users.get(i).id; - long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY, - DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, - userId); - long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY, - DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, - userId); - if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) { - setLong(LockPatternUtils.PASSWORD_TYPE_KEY, - alternateType, - userId); + + // Migrate owner info enabled. Note there was a bug where older platforms only + // stored this value if the checkbox was toggled at least once. The code detects + // this case by handling the exception. + final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED; + boolean enabled; + try { + int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId); + enabled = ivalue != 0; + setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId); + } catch (SettingNotFoundException e) { + // Setting was never stored. Store it if the string is not empty. + if (!TextUtils.isEmpty(ownerInfo)) { + setLong(OWNER_INFO_ENABLED, 1, userId); } - setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY, - DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, + } + Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId); + } + // No need to move the password / pattern files. They're already in the right place. + setString("migrated_user_specific", "true", 0); + Slog.i(TAG, "Migrated per-user lock settings to new location"); + } + + // Migrates biometric weak such that the fallback mechanism becomes the primary. + if (getString("migrated_biometric_weak", null, 0) == null) { + List<UserInfo> users = mUserManager.getUsers(); + for (int i = 0; i < users.size(); i++) { + int userId = users.get(i).id; + long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY, + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, + userId); + long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY, + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, + userId); + if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) { + setLong(LockPatternUtils.PASSWORD_TYPE_KEY, + alternateType, userId); } - setString("migrated_biometric_weak", "true", 0); - Slog.i(TAG, "Migrated biometric weak to use the fallback instead"); + setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY, + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, + userId); } + setString("migrated_biometric_weak", "true", 0); + Slog.i(TAG, "Migrated biometric weak to use the fallback instead"); + } - // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one - // user was present on the system, so if we're upgrading to M and there is more than one - // user we disable the flag to remain consistent. - if (getString("migrated_lockscreen_disabled", null, 0) == null) { - final List<UserInfo> users = mUserManager.getUsers(); - final int userCount = users.size(); - int switchableUsers = 0; - for (int i = 0; i < userCount; i++) { - if (users.get(i).supportsSwitchTo()) { - switchableUsers++; - } + // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one + // user was present on the system, so if we're upgrading to M and there is more than one + // user we disable the flag to remain consistent. + if (getString("migrated_lockscreen_disabled", null, 0) == null) { + final List<UserInfo> users = mUserManager.getUsers(); + final int userCount = users.size(); + int switchableUsers = 0; + for (int i = 0; i < userCount; i++) { + if (users.get(i).supportsSwitchTo()) { + switchableUsers++; } + } - if (switchableUsers > 1) { - for (int i = 0; i < userCount; i++) { - int id = users.get(i).id; + if (switchableUsers > 1) { + for (int i = 0; i < userCount; i++) { + int id = users.get(i).id; - if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) { - setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id); - } + if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) { + setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id); } } - - setString("migrated_lockscreen_disabled", "true", 0); - Slog.i(TAG, "Migrated lockscreen disabled flag"); } - final List<UserInfo> users = mUserManager.getUsers(); - for (int i = 0; i < users.size(); i++) { - final UserInfo userInfo = users.get(i); - if (userInfo.isManagedProfile() && mStorage.hasChildProfileLock(userInfo.id)) { - // When managed profile has a unified lock, the password quality stored has 2 - // possibilities only. - // 1). PASSWORD_QUALITY_UNSPECIFIED, which is upgraded from dp2, and we are - // going to set it back to PASSWORD_QUALITY_ALPHANUMERIC. - // 2). PASSWORD_QUALITY_ALPHANUMERIC, which is the actual password quality for - // unified lock. - final long quality = getLong(LockPatternUtils.PASSWORD_TYPE_KEY, - DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id); - if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { - // Only possible when it's upgraded from nyc dp3 - Slog.i(TAG, "Migrated tied profile lock type"); - setLong(LockPatternUtils.PASSWORD_TYPE_KEY, - DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userInfo.id); - } else if (quality != DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) { - // It should not happen - Slog.e(TAG, "Invalid tied profile lock type: " + quality); - } + setString("migrated_lockscreen_disabled", "true", 0); + Slog.i(TAG, "Migrated lockscreen disabled flag"); + } + + final List<UserInfo> users = mUserManager.getUsers(); + for (int i = 0; i < users.size(); i++) { + final UserInfo userInfo = users.get(i); + if (userInfo.isManagedProfile() && mStorage.hasChildProfileLock(userInfo.id)) { + // When managed profile has a unified lock, the password quality stored has 2 + // possibilities only. + // 1). PASSWORD_QUALITY_UNSPECIFIED, which is upgraded from dp2, and we are + // going to set it back to PASSWORD_QUALITY_ALPHANUMERIC. + // 2). PASSWORD_QUALITY_ALPHANUMERIC, which is the actual password quality for + // unified lock. + final long quality = getLong(LockPatternUtils.PASSWORD_TYPE_KEY, + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id); + if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { + // Only possible when it's upgraded from nyc dp3 + Slog.i(TAG, "Migrated tied profile lock type"); + setLong(LockPatternUtils.PASSWORD_TYPE_KEY, + DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userInfo.id); + } else if (quality != DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) { + // It should not happen + Slog.e(TAG, "Invalid tied profile lock type: " + quality); } - try { - final String alias = LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userInfo.id; - java.security.KeyStore keyStore = - java.security.KeyStore.getInstance("AndroidKeyStore"); - keyStore.load(null); - if (keyStore.containsAlias(alias)) { - keyStore.deleteEntry(alias); - } - } catch (KeyStoreException | NoSuchAlgorithmException | - CertificateException | IOException e) { - Slog.e(TAG, "Unable to remove tied profile key", e); + } + try { + final String alias = LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userInfo.id; + java.security.KeyStore keyStore = + java.security.KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + if (keyStore.containsAlias(alias)) { + keyStore.deleteEntry(alias); } + } catch (KeyStoreException | NoSuchAlgorithmException | + CertificateException | IOException e) { + Slog.e(TAG, "Unable to remove tied profile key", e); } + } - boolean isWatch = mContext.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_WATCH); - // Wear used to set DISABLE_LOCKSCREEN to 'true', but because Wear now allows accounts - // and device management the lockscreen must be re-enabled now for users that upgrade. - if (isWatch && getString("migrated_wear_lockscreen_disabled", null, 0) == null) { - final int userCount = users.size(); - for (int i = 0; i < userCount; i++) { - int id = users.get(i).id; - setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id); - } - setString("migrated_wear_lockscreen_disabled", "true", 0); - Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices"); + boolean isWatch = mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_WATCH); + // Wear used to set DISABLE_LOCKSCREEN to 'true', but because Wear now allows accounts + // and device management the lockscreen must be re-enabled now for users that upgrade. + if (isWatch && getString("migrated_wear_lockscreen_disabled", null, 0) == null) { + final int userCount = users.size(); + for (int i = 0; i < userCount; i++) { + int id = users.get(i).id; + setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id); } - } catch (RemoteException re) { - Slog.e(TAG, "Unable to migrate old data", re); + setString("migrated_wear_lockscreen_disabled", "true", 0); + Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices"); } } @@ -868,7 +864,7 @@ public class LockSettingsService extends ILockSettings.Stub { } @Override - public boolean getSeparateProfileChallengeEnabled(int userId) throws RemoteException { + public boolean getSeparateProfileChallengeEnabled(int userId) { checkReadPermission(SEPARATE_PROFILE_CHALLENGE_KEY, userId); synchronized (mSeparateChallengeLock) { return getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId); @@ -877,7 +873,7 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public void setSeparateProfileChallengeEnabled(int userId, boolean enabled, - String managedUserPassword) throws RemoteException { + String managedUserPassword) { checkWritePermission(userId); synchronized (mSeparateChallengeLock) { setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId); @@ -891,19 +887,19 @@ public class LockSettingsService extends ILockSettings.Stub { } @Override - public void setBoolean(String key, boolean value, int userId) throws RemoteException { + public void setBoolean(String key, boolean value, int userId) { checkWritePermission(userId); setStringUnchecked(key, userId, value ? "1" : "0"); } @Override - public void setLong(String key, long value, int userId) throws RemoteException { + public void setLong(String key, long value, int userId) { checkWritePermission(userId); setStringUnchecked(key, userId, Long.toString(value)); } @Override - public void setString(String key, String value, int userId) throws RemoteException { + public void setString(String key, String value, int userId) { checkWritePermission(userId); setStringUnchecked(key, userId, value); } @@ -2103,7 +2099,7 @@ public class LockSettingsService extends ILockSettings.Stub { long handle = getSyntheticPasswordHandleLocked(userId); authResult = mSpManager.unwrapPasswordBasedSyntheticPassword( - getGateKeeperService(), handle, userCredential, userId); + getGateKeeperService(), handle, userCredential, userId, progressCallback); if (authResult.credentialType != credentialType) { Slog.e(TAG, "Credential type mismatch."); @@ -2126,9 +2122,6 @@ public class LockSettingsService extends ILockSettings.Stub { } if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { - if (progressCallback != null) { - progressCallback.onCredentialVerified(); - } notifyActivePasswordMetricsAvailable(userCredential, userId); unlockKeystore(authResult.authToken.deriveKeyStorePassword(), userId); @@ -2227,7 +2220,7 @@ public class LockSettingsService extends ILockSettings.Stub { } long handle = getSyntheticPasswordHandleLocked(userId); AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword( - getGateKeeperService(), handle, savedCredential, userId); + getGateKeeperService(), handle, savedCredential, userId, null); VerifyCredentialResponse response = authResult.gkResponse; AuthenticationToken auth = authResult.authToken; @@ -2281,7 +2274,7 @@ public class LockSettingsService extends ILockSettings.Stub { } else /* isSyntheticPasswordBasedCredentialLocked(userId) */ { long pwdHandle = getSyntheticPasswordHandleLocked(userId); auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(), - pwdHandle, null, userId).authToken; + pwdHandle, null, userId, null).authToken; } } if (isSyntheticPasswordBasedCredentialLocked(userId)) { diff --git a/com/android/server/locksettings/SyntheticPasswordManager.java b/com/android/server/locksettings/SyntheticPasswordManager.java index 1a1aa569..7a3a746e 100644 --- a/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/com/android/server/locksettings/SyntheticPasswordManager.java @@ -781,7 +781,8 @@ public class SyntheticPasswordManager { * unknown. Caller might choose to validate it by examining AuthenticationResult.credentialType */ public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper, - long handle, String credential, int userId) throws RemoteException { + long handle, String credential, int userId, + ICheckCredentialProgressCallback progressCallback) throws RemoteException { if (credential == null) { credential = DEFAULT_PASSWORD; } @@ -841,7 +842,11 @@ public class SyntheticPasswordManager { applicationId = transformUnderSecdiscardable(pwdToken, loadSecdiscardable(handle, userId)); } - + // Supplied credential passes first stage weaver/gatekeeper check so it should be correct. + // Notify the callback so the keyguard UI can proceed immediately. + if (progressCallback != null) { + progressCallback.onCredentialVerified(); + } result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED, applicationId, sid, userId); diff --git a/com/android/server/media/AudioPlayerStateMonitor.java b/com/android/server/media/AudioPlayerStateMonitor.java index be223f1e..7881a952 100644 --- a/com/android/server/media/AudioPlayerStateMonitor.java +++ b/com/android/server/media/AudioPlayerStateMonitor.java @@ -16,7 +16,7 @@ package com.android.server.media; -import android.annotation.Nullable; +import android.annotation.NonNull; import android.content.Context; import android.media.AudioPlaybackConfiguration; import android.media.IAudioService; @@ -27,14 +27,14 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.IntArray; import android.util.Log; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; -import java.util.HashSet; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -49,47 +49,57 @@ class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub { private static AudioPlayerStateMonitor sInstance = new AudioPlayerStateMonitor(); /** - * Called when the state of audio player is changed. + * Listener for handling the active state changes of audio players. */ - interface OnAudioPlayerStateChangedListener { - void onAudioPlayerStateChanged( - int uid, int prevState, @Nullable AudioPlaybackConfiguration config); + interface OnAudioPlayerActiveStateChangedListener { + /** + * Called when the active state of audio player is changed. + * + * @param config The audio playback configuration for the audio player for which active + * state was changed. If {@param isRemoved} is {@code true}, this holds + * outdated information. + * @param isRemoved {@code true} if the audio player is removed. + */ + void onAudioPlayerActiveStateChanged( + @NonNull AudioPlaybackConfiguration config, boolean isRemoved); } private final static class MessageHandler extends Handler { - private static final int MSG_AUDIO_PLAYER_STATE_CHANGED = 1; + private static final int MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED = 1; - private final OnAudioPlayerStateChangedListener mListsner; + private final OnAudioPlayerActiveStateChangedListener mListener; - public MessageHandler(Looper looper, OnAudioPlayerStateChangedListener listener) { + MessageHandler(Looper looper, OnAudioPlayerActiveStateChangedListener listener) { super(looper); - mListsner = listener; + mListener = listener; } @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_AUDIO_PLAYER_STATE_CHANGED: - mListsner.onAudioPlayerStateChanged( - msg.arg1, msg.arg2, (AudioPlaybackConfiguration) msg.obj); + case MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED: + mListener.onAudioPlayerActiveStateChanged((AudioPlaybackConfiguration) msg.obj, + msg.arg1 != 0); break; } } - public void sendAudioPlayerStateChangedMessage(int uid, int prevState, - AudioPlaybackConfiguration config) { - obtainMessage(MSG_AUDIO_PLAYER_STATE_CHANGED, uid, prevState, config).sendToTarget(); + void sendAudioPlayerActiveStateChangedMessage( + final AudioPlaybackConfiguration config, final boolean isRemoved) { + obtainMessage(MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED, + isRemoved ? 1 : 0, 0 /* unused */, config).sendToTarget(); } } private final Object mLock = new Object(); @GuardedBy("mLock") - private final Map<OnAudioPlayerStateChangedListener, MessageHandler> mListenerMap = - new HashMap<>(); + private final Map<OnAudioPlayerActiveStateChangedListener, MessageHandler> mListenerMap = + new ArrayMap<>(); @GuardedBy("mLock") - private final Map<Integer, Integer> mAudioPlayerStates = new HashMap<>(); + private final Set<Integer> mActiveAudioUids = new ArraySet<>(); @GuardedBy("mLock") - private final Map<Integer, HashSet<Integer>> mAudioPlayersForUid = new HashMap<>(); + private ArrayMap<Integer, AudioPlaybackConfiguration> mPrevActiveAudioPlaybackConfigs = + new ArrayMap<>(); // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video) // The UID whose audio playback becomes active at the last comes first. // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID. @@ -122,32 +132,24 @@ class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub { } final long token = Binder.clearCallingIdentity(); try { - final Map<Integer, Integer> prevAudioPlayerStates = new HashMap<>(mAudioPlayerStates); - final Map<Integer, HashSet<Integer>> prevAudioPlayersForUid = - new HashMap<>(mAudioPlayersForUid); synchronized (mLock) { - mAudioPlayerStates.clear(); - mAudioPlayersForUid.clear(); + // Update mActiveAudioUids + mActiveAudioUids.clear(); + ArrayMap<Integer, AudioPlaybackConfiguration> activeAudioPlaybackConfigs = + new ArrayMap<>(); for (AudioPlaybackConfiguration config : configs) { - int pii = config.getPlayerInterfaceId(); - int uid = config.getClientUid(); - mAudioPlayerStates.put(pii, config.getPlayerState()); - HashSet<Integer> players = mAudioPlayersForUid.get(uid); - if (players == null) { - players = new HashSet<Integer>(); - players.add(pii); - mAudioPlayersForUid.put(uid, players); - } else { - players.add(pii); + if (config.isActive()) { + mActiveAudioUids.add(config.getClientUid()); + activeAudioPlaybackConfigs.put(config.getPlayerInterfaceId(), config); } } - for (AudioPlaybackConfiguration config : configs) { - if (!config.isActive()) { - continue; - } - int uid = config.getClientUid(); - if (!isActiveState(prevAudioPlayerStates.get(config.getPlayerInterfaceId()))) { + // Update mSortedAuioPlaybackClientUids. + for (int i = 0; i < activeAudioPlaybackConfigs.size(); ++i) { + AudioPlaybackConfiguration config = activeAudioPlaybackConfigs.valueAt(i); + final int uid = config.getClientUid(); + if (!mPrevActiveAudioPlaybackConfigs.containsKey( + config.getPlayerInterfaceId())) { if (DEBUG) { Log.d(TAG, "Found a new active media playback. " + AudioPlaybackConfiguration.toLogFriendlyString(config)); @@ -163,40 +165,21 @@ class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub { mSortedAudioPlaybackClientUids.add(0, uid); } } - // Notify the change of audio player states. + // Notify the active state change of audio players. for (AudioPlaybackConfiguration config : configs) { - final Integer prevState = prevAudioPlayerStates.get(config.getPlayerInterfaceId()); - final int prevStateInt = - (prevState == null) ? AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN : - prevState.intValue(); - if (prevStateInt != config.getPlayerState()) { - sendAudioPlayerStateChangedMessageLocked( - config.getClientUid(), prevStateInt, config); + final int pii = config.getPlayerInterfaceId(); + boolean wasActive = mPrevActiveAudioPlaybackConfigs.remove(pii) != null; + if (wasActive != config.isActive()) { + sendAudioPlayerActiveStateChangedMessageLocked( + config, /* isRemoved */ false); } } - for (Integer prevUid : prevAudioPlayersForUid.keySet()) { - // If all players for prevUid is removed, notify the prev state was - // PLAYER_STATE_STARTED only when there were a player whose state was - // PLAYER_STATE_STARTED, otherwise any inactive state is okay to notify. - if (!mAudioPlayersForUid.containsKey(prevUid)) { - Set<Integer> prevPlayers = prevAudioPlayersForUid.get(prevUid); - int prevState = AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN; - for (int pii : prevPlayers) { - Integer state = prevAudioPlayerStates.get(pii); - if (state == null) { - continue; - } - if (state == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) { - prevState = state; - break; - } else if (prevState - == AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN) { - prevState = state; - } - } - sendAudioPlayerStateChangedMessageLocked(prevUid, prevState, null); - } + for (AudioPlaybackConfiguration config : mPrevActiveAudioPlaybackConfigs.values()) { + sendAudioPlayerActiveStateChangedMessageLocked(config, /* isRemoved */ true); } + + // Update mPrevActiveAudioPlaybackConfigs + mPrevActiveAudioPlaybackConfigs = activeAudioPlaybackConfigs; } } finally { Binder.restoreCallingIdentity(token); @@ -204,9 +187,10 @@ class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub { } /** - * Registers OnAudioPlayerStateChangedListener. + * Registers OnAudioPlayerActiveStateChangedListener. */ - public void registerListener(OnAudioPlayerStateChangedListener listener, Handler handler) { + public void registerListener( + OnAudioPlayerActiveStateChangedListener listener, Handler handler) { synchronized (mLock) { mListenerMap.put(listener, new MessageHandler((handler == null) ? Looper.myLooper() : handler.getLooper(), listener)); @@ -214,9 +198,9 @@ class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub { } /** - * Unregisters OnAudioPlayerStateChangedListener. + * Unregisters OnAudioPlayerActiveStateChangedListener. */ - public void unregisterListener(OnAudioPlayerStateChangedListener listener) { + public void unregisterListener(OnAudioPlayerActiveStateChangedListener listener) { synchronized (mLock) { mListenerMap.remove(listener); } @@ -239,16 +223,7 @@ class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub { */ public boolean isPlaybackActive(int uid) { synchronized (mLock) { - Set<Integer> players = mAudioPlayersForUid.get(uid); - if (players == null) { - return false; - } - for (Integer pii : players) { - if (isActiveState(mAudioPlayerStates.get(pii))) { - return true; - } - } - return false; + return mActiveAudioUids.contains(uid); } } @@ -314,14 +289,10 @@ class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub { } } - private void sendAudioPlayerStateChangedMessageLocked( - final int uid, final int prevState, final AudioPlaybackConfiguration config) { + private void sendAudioPlayerActiveStateChangedMessageLocked( + final AudioPlaybackConfiguration config, final boolean isRemoved) { for (MessageHandler messageHandler : mListenerMap.values()) { - messageHandler.sendAudioPlayerStateChangedMessage(uid, prevState, config); + messageHandler.sendAudioPlayerActiveStateChangedMessage(config, isRemoved); } } - - private static boolean isActiveState(Integer state) { - return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED); - } } diff --git a/com/android/server/media/MediaRouterService.java b/com/android/server/media/MediaRouterService.java index 3c9e1d45..0b089fbc 100644 --- a/com/android/server/media/MediaRouterService.java +++ b/com/android/server/media/MediaRouterService.java @@ -19,7 +19,7 @@ package com.android.server.media; import com.android.internal.util.DumpUtils; import com.android.server.Watchdog; -import android.annotation.Nullable; +import android.annotation.NonNull; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -100,7 +100,9 @@ public final class MediaRouterService extends IMediaRouterService.Stub private final IAudioService mAudioService; private final AudioPlayerStateMonitor mAudioPlayerStateMonitor; private final Handler mHandler = new Handler(); - private final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo(); + private final AudioRoutesInfo mAudioRoutesInfo = new AudioRoutesInfo(); + private final IntArray mActivePlayerMinPriorityQueue = new IntArray(); + private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray(); public MediaRouterService(Context context) { mContext = context; @@ -111,7 +113,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance(); mAudioPlayerStateMonitor.registerListener( - new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() { + new AudioPlayerStateMonitor.OnAudioPlayerActiveStateChangedListener() { static final long WAIT_MS = 500; final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() { @Override @@ -121,39 +123,41 @@ public final class MediaRouterService extends IMediaRouterService.Stub }; @Override - public void onAudioPlayerStateChanged( - int uid, int prevState, @Nullable AudioPlaybackConfiguration config) { + public void onAudioPlayerActiveStateChanged( + @NonNull AudioPlaybackConfiguration config, boolean isRemoved) { + final boolean active = !isRemoved && config.isActive(); + final int pii = config.getPlayerInterfaceId(); + final int uid = config.getClientUid(); + + final int idx = mActivePlayerMinPriorityQueue.indexOf(pii); + // Keep the latest active player and its uid at the end of the queue. + if (idx >= 0) { + mActivePlayerMinPriorityQueue.remove(idx); + mActivePlayerUidMinPriorityQueue.remove(idx); + } + int restoreUid = -1; - boolean active = config == null ? false : config.isActive(); if (active) { + mActivePlayerMinPriorityQueue.add(config.getPlayerInterfaceId()); + mActivePlayerUidMinPriorityQueue.add(uid); restoreUid = uid; - } else if (prevState != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) { - // Noting to do if the prev state is not an active state. - return; - } else { - IntArray sortedAudioPlaybackClientUids = - mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids(); - for (int i = 0; i < sortedAudioPlaybackClientUids.size(); ++i) { - if (mAudioPlayerStateMonitor.isPlaybackActive( - sortedAudioPlaybackClientUids.get(i))) { - restoreUid = sortedAudioPlaybackClientUids.get(i); - break; - } - } + } else if (mActivePlayerUidMinPriorityQueue.size() > 0) { + restoreUid = mActivePlayerUidMinPriorityQueue.get( + mActivePlayerUidMinPriorityQueue.size() - 1); } mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable); if (restoreUid >= 0) { restoreRoute(restoreUid); if (DEBUG) { - Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid - + " active " + active + " restoring " + restoreUid); + Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid + + ", active=" + active + ", restoreUid=" + restoreUid); } } else { mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS); if (DEBUG) { - Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid - + " active " + active + " delaying"); + Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid + + ", active=" + active + ", delaying"); } } } @@ -166,7 +170,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub @Override public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) { synchronized (mLock) { - if (newRoutes.mainType != mCurAudioRoutesInfo.mainType) { + if (newRoutes.mainType != mAudioRoutesInfo.mainType) { if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET | AudioRoutesInfo.MAIN_HEADPHONES | AudioRoutesInfo.MAIN_USB)) == 0) { @@ -176,10 +180,10 @@ public final class MediaRouterService extends IMediaRouterService.Stub // headset was plugged in. mGlobalBluetoothA2dpOn = false; } - mCurAudioRoutesInfo.mainType = newRoutes.mainType; + mAudioRoutesInfo.mainType = newRoutes.mainType; } if (!TextUtils.equals( - newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) { + newRoutes.bluetoothName, mAudioRoutesInfo.bluetoothName)) { if (newRoutes.bluetoothName == null) { // BT was disconnected. mGlobalBluetoothA2dpOn = false; @@ -187,8 +191,14 @@ public final class MediaRouterService extends IMediaRouterService.Stub // BT was connected or changed. mGlobalBluetoothA2dpOn = true; } - mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName; + mAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName; } + // Although a Bluetooth device is connected before a new audio playback is + // started, dispatchAudioRoutChanged() can be called after + // onAudioPlayerActiveStateChanged(). That causes restoreBluetoothA2dp() + // is called before mGlobalBluetoothA2dpOn is updated. + // Calling restoreBluetoothA2dp() here could prevent that. + restoreBluetoothA2dp(); } } }); @@ -405,12 +415,17 @@ public final class MediaRouterService extends IMediaRouterService.Stub void restoreBluetoothA2dp() { try { + boolean btConnected = false; boolean a2dpOn = false; synchronized (mLock) { + btConnected = mAudioRoutesInfo.bluetoothName != null; a2dpOn = mGlobalBluetoothA2dpOn; } - Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")"); - mAudioService.setBluetoothA2dpOn(a2dpOn); + // We don't need to change a2dp status when bluetooth is not connected. + if (btConnected) { + Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")"); + mAudioService.setBluetoothA2dpOn(a2dpOn); + } } catch (RemoteException e) { Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn."); } diff --git a/com/android/server/media/MediaSessionService.java b/com/android/server/media/MediaSessionService.java index f6a81d07..06f4f5e8 100644 --- a/com/android/server/media/MediaSessionService.java +++ b/com/android/server/media/MediaSessionService.java @@ -16,7 +16,6 @@ package com.android.server.media; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.INotificationManager; import android.app.KeyguardManager; @@ -138,23 +137,19 @@ public class MediaSessionService extends SystemService implements Monitor { mAudioService = getAudioService(); mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance(); mAudioPlayerStateMonitor.registerListener( - new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() { - @Override - public void onAudioPlayerStateChanged( - int uid, int prevState, @Nullable AudioPlaybackConfiguration config) { - if (config == null || !config.isActive() || config.getPlayerType() - == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) { - return; - } - synchronized (mLock) { - FullUserRecord user = - getFullUserRecordLocked(UserHandle.getUserId(uid)); - if (user != null) { - user.mPriorityStack.updateMediaButtonSessionIfNeeded(); + (config, isRemoved) -> { + if (isRemoved || !config.isActive() || config.getPlayerType() + == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) { + return; } - } - } - }, null /* handler */); + synchronized (mLock) { + FullUserRecord user = getFullUserRecordLocked( + UserHandle.getUserId(config.getClientUid())); + if (user != null) { + user.mPriorityStack.updateMediaButtonSessionIfNeeded(); + } + } + }, null /* handler */); mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService); mContentResolver = getContext().getContentResolver(); mSettingsObserver = new SettingsObserver(); diff --git a/com/android/server/net/NetworkPolicyLogger.java b/com/android/server/net/NetworkPolicyLogger.java new file mode 100644 index 00000000..2bd9cab3 --- /dev/null +++ b/com/android/server/net/NetworkPolicyLogger.java @@ -0,0 +1,514 @@ +/* + * 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 com.android.server.net; + +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY; +import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW; +import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; +import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY; + +import android.app.ActivityManager; +import android.net.NetworkPolicyManager; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.RingBuffer; +import com.android.server.am.ProcessList; + +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; + +public class NetworkPolicyLogger { + static final String TAG = "NetworkPolicy"; + + static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); + static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE); + + private static final int MAX_LOG_SIZE = + ActivityManager.isLowRamDeviceStatic() ? 20 : 50; + private static final int MAX_NETWORK_BLOCKED_LOG_SIZE = + ActivityManager.isLowRamDeviceStatic() ? 50 : 100; + + private static final int EVENT_TYPE_GENERIC = 0; + private static final int EVENT_NETWORK_BLOCKED = 1; + private static final int EVENT_UID_STATE_CHANGED = 2; + private static final int EVENT_POLICIES_CHANGED = 3; + private static final int EVENT_METEREDNESS_CHANGED = 4; + private static final int EVENT_USER_STATE_REMOVED = 5; + private static final int EVENT_RESTRICT_BG_CHANGED = 6; + private static final int EVENT_DEVICE_IDLE_MODE_ENABLED = 7; + private static final int EVENT_APP_IDLE_STATE_CHANGED = 8; + private static final int EVENT_PAROLE_STATE_CHANGED = 9; + private static final int EVENT_TEMP_POWER_SAVE_WL_CHANGED = 10; + private static final int EVENT_UID_FIREWALL_RULE_CHANGED = 11; + private static final int EVENT_FIREWALL_CHAIN_ENABLED = 12; + + static final int NTWK_BLOCKED_POWER = 0; + static final int NTWK_ALLOWED_NON_METERED = 1; + static final int NTWK_BLOCKED_BLACKLIST = 2; + static final int NTWK_ALLOWED_WHITELIST = 3; + static final int NTWK_ALLOWED_TMP_WHITELIST = 4; + static final int NTWK_BLOCKED_BG_RESTRICT = 5; + static final int NTWK_ALLOWED_DEFAULT = 6; + + private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE); + private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE); + private final LogBuffer mEventsBuffer = new LogBuffer(MAX_LOG_SIZE); + + private final Object mLock = new Object(); + + void networkBlocked(int uid, int reason) { + synchronized (mLock) { + if (LOGD) Slog.d(TAG, uid + " is " + getBlockedReason(reason)); + mNetworkBlockedBuffer.networkBlocked(uid, reason); + } + } + + void uidStateChanged(int uid, int procState, long procStateSeq) { + synchronized (mLock) { + if (LOGV) Slog.v(TAG, + uid + " state changed to " + procState + " with seq=" + procStateSeq); + mUidStateChangeBuffer.uidStateChanged(uid, procState, procStateSeq); + } + } + + void event(String msg) { + synchronized (mLock) { + if (LOGV) Slog.v(TAG, msg); + mEventsBuffer.event(msg); + } + } + + void uidPolicyChanged(int uid, int oldPolicy, int newPolicy) { + synchronized (mLock) { + if (LOGV) Slog.v(TAG, getPolicyChangedLog(uid, oldPolicy, newPolicy)); + mEventsBuffer.uidPolicyChanged(uid, oldPolicy, newPolicy); + } + } + + void meterednessChanged(int netId, boolean newMetered) { + synchronized (mLock) { + if (LOGD) Slog.d(TAG, getMeterednessChangedLog(netId, newMetered)); + mEventsBuffer.meterednessChanged(netId, newMetered); + } + } + + void removingUserState(int userId) { + synchronized (mLock) { + if (LOGD) Slog.d(TAG, getUserRemovedLog(userId)); + mEventsBuffer.userRemoved(userId); + } + } + + void restrictBackgroundChanged(boolean oldValue, boolean newValue) { + synchronized (mLock) { + if (LOGD) Slog.d(TAG, + getRestrictBackgroundChangedLog(oldValue, newValue)); + mEventsBuffer.restrictBackgroundChanged(oldValue, newValue); + } + } + + void deviceIdleModeEnabled(boolean enabled) { + synchronized (mLock) { + if (LOGD) Slog.d(TAG, getDeviceIdleModeEnabled(enabled)); + mEventsBuffer.deviceIdleModeEnabled(enabled); + } + } + + void appIdleStateChanged(int uid, boolean idle) { + synchronized (mLock) { + if (LOGD) Slog.d(TAG, getAppIdleChangedLog(uid, idle)); + mEventsBuffer.appIdleStateChanged(uid, idle); + } + } + + void paroleStateChanged(boolean paroleOn) { + synchronized (mLock) { + if (LOGD) Slog.d(TAG, getParoleStateChanged(paroleOn)); + mEventsBuffer.paroleStateChanged(paroleOn); + } + } + + void tempPowerSaveWlChanged(int appId, boolean added) { + synchronized (mLock) { + if (LOGV) Slog.v(TAG, getTempPowerSaveWlChangedLog(appId, added)); + mEventsBuffer.tempPowerSaveWlChanged(appId, added); + } + } + + void uidFirewallRuleChanged(int chain, int uid, int rule) { + synchronized (mLock) { + if (LOGV) Slog.v(TAG, getUidFirewallRuleChangedLog(chain, uid, rule)); + mEventsBuffer.uidFirewallRuleChanged(chain, uid, rule); + } + } + + void firewallChainEnabled(int chain, boolean enabled) { + synchronized (mLock) { + if (LOGD) Slog.d(TAG, getFirewallChainEnabledLog(chain, enabled)); + mEventsBuffer.firewallChainEnabled(chain, enabled); + } + } + + void firewallRulesChanged(int chain, int[] uids, int[] rules) { + synchronized (mLock) { + final String log = "Firewall rules changed for " + getFirewallChainName(chain) + + "; uids=" + Arrays.toString(uids) + "; rules=" + Arrays.toString(rules); + if (LOGD) Slog.d(TAG, log); + mEventsBuffer.event(log); + } + } + + void dumpLogs(IndentingPrintWriter pw) { + synchronized (mLock) { + pw.println(); + pw.println("mEventLogs (most recent first):"); + pw.increaseIndent(); + mEventsBuffer.reverseDump(pw); + pw.decreaseIndent(); + + pw.println(); + pw.println("mNetworkBlockedLogs (most recent first):"); + pw.increaseIndent(); + mNetworkBlockedBuffer.reverseDump(pw); + pw.decreaseIndent(); + + pw.println(); + pw.println("mUidStateChangeLogs (most recent first):"); + pw.increaseIndent(); + mUidStateChangeBuffer.reverseDump(pw); + pw.decreaseIndent(); + } + } + + private static String getBlockedReason(int reason) { + switch (reason) { + case NTWK_BLOCKED_POWER: + return "blocked by power restrictions"; + case NTWK_ALLOWED_NON_METERED: + return "allowed on unmetered network"; + case NTWK_BLOCKED_BLACKLIST: + return "blacklisted on metered network"; + case NTWK_ALLOWED_WHITELIST: + return "whitelisted on metered network"; + case NTWK_ALLOWED_TMP_WHITELIST: + return "temporary whitelisted on metered network"; + case NTWK_BLOCKED_BG_RESTRICT: + return "blocked when background is restricted"; + case NTWK_ALLOWED_DEFAULT: + return "allowed by default"; + default: + return String.valueOf(reason); + } + } + + private static String getPolicyChangedLog(int uid, int oldPolicy, int newPolicy) { + return "Policy for " + uid + " changed from " + + NetworkPolicyManager.uidPoliciesToString(oldPolicy) + " to " + + NetworkPolicyManager.uidPoliciesToString(newPolicy); + } + + private static String getMeterednessChangedLog(int netId, boolean newMetered) { + return "Meteredness of netId=" + netId + " changed to " + newMetered; + } + + private static String getUserRemovedLog(int userId) { + return "Remove state for u" + userId; + } + + private static String getRestrictBackgroundChangedLog(boolean oldValue, boolean newValue) { + return "Changed restrictBackground: " + oldValue + "->" + newValue; + } + + private static String getDeviceIdleModeEnabled(boolean enabled) { + return "DeviceIdleMode enabled: " + enabled; + } + + private static String getAppIdleChangedLog(int uid, boolean idle) { + return "App idle state of uid " + uid + ": " + idle; + } + + private static String getParoleStateChanged(boolean paroleOn) { + return "Parole state: " + paroleOn; + } + + private static String getTempPowerSaveWlChangedLog(int appId, boolean added) { + return "temp-power-save whitelist for " + appId + " changed to: " + added; + } + + private static String getUidFirewallRuleChangedLog(int chain, int uid, int rule) { + return String.format("Firewall rule changed: %d-%s-%s", + uid, getFirewallChainName(chain), getFirewallRuleName(rule)); + } + + private static String getFirewallChainEnabledLog(int chain, boolean enabled) { + return "Firewall chain " + getFirewallChainName(chain) + " state: " + enabled; + } + + private static String getFirewallChainName(int chain) { + switch (chain) { + case FIREWALL_CHAIN_DOZABLE: + return FIREWALL_CHAIN_NAME_DOZABLE; + case FIREWALL_CHAIN_STANDBY: + return FIREWALL_CHAIN_NAME_STANDBY; + case FIREWALL_CHAIN_POWERSAVE: + return FIREWALL_CHAIN_NAME_POWERSAVE; + default: + return String.valueOf(chain); + } + } + + private static String getFirewallRuleName(int rule) { + switch (rule) { + case FIREWALL_RULE_DEFAULT: + return "default"; + case FIREWALL_RULE_ALLOW: + return "allow"; + case FIREWALL_RULE_DENY: + return "deny"; + default: + return String.valueOf(rule); + } + } + + private final static class LogBuffer extends RingBuffer<Data> { + private static final SimpleDateFormat sFormatter + = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSS"); + private static final Date sDate = new Date(); + + public LogBuffer(int capacity) { + super(Data.class, capacity); + } + + public void uidStateChanged(int uid, int procState, long procStateSeq) { + final Data data = getNextSlot(); + if (data == null) return; + + data.reset(); + data.type = EVENT_UID_STATE_CHANGED; + data.ifield1 = uid; + data.ifield2 = procState; + data.lfield1 = procStateSeq; + data.timeStamp = System.currentTimeMillis(); + } + + public void event(String msg) { + final Data data = getNextSlot(); + if (data == null) return; + + data.reset(); + data.type = EVENT_TYPE_GENERIC; + data.sfield1 = msg; + data.timeStamp = System.currentTimeMillis(); + } + + public void networkBlocked(int uid, int reason) { + final Data data = getNextSlot(); + if (data == null) return; + + data.reset(); + data.type = EVENT_NETWORK_BLOCKED; + data.ifield1 = uid; + data.ifield2 = reason; + data.timeStamp = System.currentTimeMillis(); + } + + public void uidPolicyChanged(int uid, int oldPolicy, int newPolicy) { + final Data data = getNextSlot(); + if (data == null) return; + + data.reset(); + data.type = EVENT_POLICIES_CHANGED; + data.ifield1 = uid; + data.ifield2 = oldPolicy; + data.ifield3 = newPolicy; + data.timeStamp = System.currentTimeMillis(); + } + + public void meterednessChanged(int netId, boolean newMetered) { + final Data data = getNextSlot(); + if (data == null) return; + + data.reset(); + data.type = EVENT_METEREDNESS_CHANGED; + data.ifield1 = netId; + data.bfield1 = newMetered; + data.timeStamp = System.currentTimeMillis(); + } + + public void userRemoved(int userId) { + final Data data = getNextSlot(); + if (data == null) return; + + data.reset(); + data.type = EVENT_USER_STATE_REMOVED; + data.ifield1 = userId; + data.timeStamp = System.currentTimeMillis(); + } + + public void restrictBackgroundChanged(boolean oldValue, boolean newValue) { + final Data data = getNextSlot(); + if (data == null) return; + + data.reset(); + data.type = EVENT_RESTRICT_BG_CHANGED; + data.bfield1 = oldValue; + data.bfield2 = newValue; + data.timeStamp = System.currentTimeMillis(); + } + + public void deviceIdleModeEnabled(boolean enabled) { + final Data data = getNextSlot(); + if (data == null) return; + + data.reset(); + data.type = EVENT_DEVICE_IDLE_MODE_ENABLED; + data.bfield1 = enabled; + data.timeStamp = System.currentTimeMillis(); + } + + public void appIdleStateChanged(int uid, boolean idle) { + final Data data = getNextSlot(); + if (data == null) return; + + data.reset(); + data.type = EVENT_APP_IDLE_STATE_CHANGED; + data.ifield1 = uid; + data.bfield1 = idle; + data.timeStamp = System.currentTimeMillis(); + } + + public void paroleStateChanged(boolean paroleOn) { + final Data data = getNextSlot(); + if (data == null) return; + + data.reset(); + data.type = EVENT_PAROLE_STATE_CHANGED; + data.bfield1 = paroleOn; + data.timeStamp = System.currentTimeMillis(); + } + + public void tempPowerSaveWlChanged(int appId, boolean added) { + final Data data = getNextSlot(); + if (data == null) return; + + data.reset(); + data.type = EVENT_TEMP_POWER_SAVE_WL_CHANGED; + data.ifield1 = appId; + data.bfield1 = added; + data.timeStamp = System.currentTimeMillis(); + } + + public void uidFirewallRuleChanged(int chain, int uid, int rule) { + final Data data = getNextSlot(); + if (data == null) return; + + data.reset(); + data.type = EVENT_UID_FIREWALL_RULE_CHANGED; + data.ifield1 = chain; + data.ifield2 = uid; + data.ifield3 = rule; + data.timeStamp = System.currentTimeMillis(); + } + + public void firewallChainEnabled(int chain, boolean enabled) { + final Data data = getNextSlot(); + if (data == null) return; + + data.reset(); + data.type = EVENT_FIREWALL_CHAIN_ENABLED; + data.ifield1 = chain; + data.bfield1 = enabled; + data.timeStamp = System.currentTimeMillis(); + } + + public void reverseDump(IndentingPrintWriter pw) { + final Data[] allData = toArray(); + for (int i = allData.length - 1; i >= 0; --i) { + if (allData[i] == null) { + pw.println("NULL"); + continue; + } + pw.print(formatDate(allData[i].timeStamp)); + pw.print(" - "); + pw.println(getContent(allData[i])); + } + } + + public String getContent(Data data) { + switch (data.type) { + case EVENT_TYPE_GENERIC: + return data.sfield1; + case EVENT_NETWORK_BLOCKED: + return data.ifield1 + "-" + getBlockedReason(data.ifield2); + case EVENT_UID_STATE_CHANGED: + return data.ifield1 + "-" + ProcessList.makeProcStateString(data.ifield2) + + "-" + data.lfield1; + case EVENT_POLICIES_CHANGED: + return getPolicyChangedLog(data.ifield1, data.ifield2, data.ifield3); + case EVENT_METEREDNESS_CHANGED: + return getMeterednessChangedLog(data.ifield1, data.bfield1); + case EVENT_USER_STATE_REMOVED: + return getUserRemovedLog(data.ifield1); + case EVENT_RESTRICT_BG_CHANGED: + return getRestrictBackgroundChangedLog(data.bfield1, data.bfield2); + case EVENT_DEVICE_IDLE_MODE_ENABLED: + return getDeviceIdleModeEnabled(data.bfield1); + case EVENT_APP_IDLE_STATE_CHANGED: + return getAppIdleChangedLog(data.ifield1, data.bfield1); + case EVENT_PAROLE_STATE_CHANGED: + return getParoleStateChanged(data.bfield1); + case EVENT_TEMP_POWER_SAVE_WL_CHANGED: + return getTempPowerSaveWlChangedLog(data.ifield1, data.bfield1); + case EVENT_UID_FIREWALL_RULE_CHANGED: + return getUidFirewallRuleChangedLog(data.ifield1, data.ifield2, data.ifield3); + case EVENT_FIREWALL_CHAIN_ENABLED: + return getFirewallChainEnabledLog(data.ifield1, data.bfield1); + default: + return String.valueOf(data.type); + } + } + + private String formatDate(long millis) { + sDate.setTime(millis); + return sFormatter.format(sDate); + } + } + + public final static class Data { + int type; + long timeStamp; + + int ifield1; + int ifield2; + int ifield3; + long lfield1; + boolean bfield1; + boolean bfield2; + String sfield1; + + public void reset(){ + sfield1 = null; + } + } +} diff --git a/com/android/server/net/NetworkPolicyManagerService.java b/com/android/server/net/NetworkPolicyManagerService.java index 3fa3cd49..fdfe2418 100644 --- a/com/android/server/net/NetworkPolicyManagerService.java +++ b/com/android/server/net/NetworkPolicyManagerService.java @@ -82,6 +82,13 @@ import static com.android.internal.util.XmlUtils.writeIntAttribute; import static com.android.internal.util.XmlUtils.writeLongAttribute; import static com.android.internal.util.XmlUtils.writeStringAttribute; import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT; +import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_DEFAULT; +import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_NON_METERED; +import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_WHITELIST; +import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_WHITELIST; +import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BG_RESTRICT; +import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BLACKLIST; +import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_POWER; import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; @@ -248,9 +255,9 @@ import java.util.concurrent.TimeUnit; * </ul> */ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { - static final String TAG = "NetworkPolicy"; - private static final boolean LOGD = false; - private static final boolean LOGV = false; + static final String TAG = NetworkPolicyLogger.TAG; + private static final boolean LOGD = NetworkPolicyLogger.LOGD; + private static final boolean LOGV = NetworkPolicyLogger.LOGV; private static final int VERSION_INIT = 1; private static final int VERSION_ADDED_SNOOZE = 2; @@ -265,13 +272,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int VERSION_ADDED_CYCLE = 11; private static final int VERSION_LATEST = VERSION_ADDED_CYCLE; - /** - * Max items written to {@link #ProcStateSeqHistory}. - */ - @VisibleForTesting - public static final int MAX_PROC_STATE_SEQ_HISTORY = - ActivityManager.isLowRamDeviceStatic() ? 50 : 200; - @VisibleForTesting public static final int TYPE_WARNING = SystemMessage.NOTE_NET_WARNING; @VisibleForTesting @@ -471,13 +471,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private ActivityManagerInternal mActivityManagerInternal; - /** - * This is used for debugging purposes. Whenever the IUidObserver.onUidStateChanged is called, - * the uid and procStateSeq will be written to this and will be printed as part of dump. - */ - @VisibleForTesting - public ProcStateSeqHistory mObservedHistory - = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY); + private final NetworkPolicyLogger mLogger = new NetworkPolicyLogger(); // TODO: keep whitelist of system-critical services that should never have // rules enforced, such as system, phone, and radio UIDs. @@ -966,6 +960,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); if ((oldMetered != newMetered) || mNetworkMetered.indexOfKey(network.netId) < 0) { + mLogger.meterednessChanged(network.netId, newMetered); mNetworkMetered.put(network.netId, newMetered); updateNetworkRulesNL(); } @@ -2148,6 +2143,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final int oldPolicy = mUidPolicy.get(uid, POLICY_NONE); if (oldPolicy != policy) { setUidPolicyUncheckedUL(uid, oldPolicy, policy, true); + mLogger.uidPolicyChanged(uid, oldPolicy, policy); } } finally { Binder.restoreCallingIdentity(token); @@ -2168,6 +2164,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { policy |= oldPolicy; if (oldPolicy != policy) { setUidPolicyUncheckedUL(uid, oldPolicy, policy, true); + mLogger.uidPolicyChanged(uid, oldPolicy, policy); } } } @@ -2185,6 +2182,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { policy = oldPolicy & ~policy; if (oldPolicy != policy) { setUidPolicyUncheckedUL(uid, oldPolicy, policy, true); + mLogger.uidPolicyChanged(uid, oldPolicy, policy); } } } @@ -2264,7 +2262,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { */ boolean removeUserStateUL(int userId, boolean writePolicy) { - if (LOGV) Slog.v(TAG, "removeUserStateUL()"); + mLogger.removingUserState(userId); boolean changed = false; // Remove entries from revoked default restricted background UID whitelist @@ -2429,7 +2427,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @Override public void onTetheringChanged(String iface, boolean tethering) { // No need to enforce permission because setRestrictBackground() will do it. - if (LOGD) Log.d(TAG, "onTetherStateChanged(" + iface + ", " + tethering + ")"); synchronized (mUidRulesFirstLock) { if (mRestrictBackground && tethering) { Log.d(TAG, "Tethering on (" + iface +"); disable Data Saver"); @@ -2486,6 +2483,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } sendRestrictBackgroundChangedMsg(); + mLogger.restrictBackgroundChanged(oldRestrictBackground, mRestrictBackground); if (mRestrictBackgroundPowerState.globalBatterySaverEnabled) { mRestrictBackgroundChangedInBsm = true; @@ -2551,6 +2549,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return; } mDeviceIdleMode = enabled; + mLogger.deviceIdleModeEnabled(enabled); if (mSystemReady) { // Device idle change means we need to rebuild rules for all // known apps, so do a global refresh. @@ -2964,10 +2963,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } fout.decreaseIndent(); - fout.println("Observed uid state changes:"); - fout.increaseIndent(); - mObservedHistory.dumpUL(fout); - fout.decreaseIndent(); + mLogger.dumpLogs(fout); } } } @@ -3750,8 +3746,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { try { final int uid = mContext.getPackageManager().getPackageUidAsUser(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); - if (LOGV) Log.v(TAG, "onAppIdleStateChanged(): uid=" + uid + ", idle=" + idle); synchronized (mUidRulesFirstLock) { + mLogger.appIdleStateChanged(uid, idle); updateRuleForAppIdleUL(uid); updateRulesForPowerRestrictionsUL(uid); } @@ -3762,6 +3758,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @Override public void onParoleStateChanged(boolean isParoleOn) { synchronized (mUidRulesFirstLock) { + mLogger.paroleStateChanged(isParoleOn); updateRulesForAppIdleParoleUL(); } } @@ -3947,7 +3944,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { synchronized (mUidRulesFirstLock) { // We received a uid state change callback, add it to the history so that it // will be useful for debugging. - mObservedHistory.addProcStateSeqUL(uid, procStateSeq); + mLogger.uidStateChanged(uid, procState, procStateSeq); // Now update the network policy rules as per the updated uid state. updateUidStateUL(uid, procState); // Updating the network rules is done, so notify AMS about this. @@ -4081,6 +4078,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { rules[index] = uidRules.valueAt(index); } mNetworkManager.setFirewallUidRules(chain, uids, rules); + mLogger.firewallRulesChanged(chain, uids, rules); } catch (IllegalStateException e) { Log.wtf(TAG, "problem setting firewall uid rules", e); } catch (RemoteException e) { @@ -4107,6 +4105,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { try { mNetworkManager.setFirewallUidRule(chain, uid, rule); + mLogger.uidFirewallRuleChanged(chain, uid, rule); } catch (IllegalStateException e) { Log.wtf(TAG, "problem setting firewall uid rules", e); } catch (RemoteException e) { @@ -4129,6 +4128,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mFirewallChainStates.put(chain, enable); try { mNetworkManager.setFirewallChainEnabled(chain, enable); + mLogger.firewallChainEnabled(chain, enable); } catch (IllegalStateException e) { Log.wtf(TAG, "problem enable firewall chain", e); } catch (RemoteException e) { @@ -4305,30 +4305,30 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { isBackgroundRestricted = mRestrictBackground; } if (hasRule(uidRules, RULE_REJECT_ALL)) { - if (LOGV) logUidStatus(uid, "blocked by power restrictions"); + mLogger.networkBlocked(uid, NTWK_BLOCKED_POWER); return true; } if (!isNetworkMetered) { - if (LOGV) logUidStatus(uid, "allowed on unmetered network"); + mLogger.networkBlocked(uid, NTWK_ALLOWED_NON_METERED); return false; } if (hasRule(uidRules, RULE_REJECT_METERED)) { - if (LOGV) logUidStatus(uid, "blacklisted on metered network"); + mLogger.networkBlocked(uid, NTWK_BLOCKED_BLACKLIST); return true; } if (hasRule(uidRules, RULE_ALLOW_METERED)) { - if (LOGV) logUidStatus(uid, "whitelisted on metered network"); + mLogger.networkBlocked(uid, NTWK_ALLOWED_WHITELIST); return false; } if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) { - if (LOGV) logUidStatus(uid, "temporary whitelisted on metered network"); + mLogger.networkBlocked(uid, NTWK_ALLOWED_TMP_WHITELIST); return false; } if (isBackgroundRestricted) { - if (LOGV) logUidStatus(uid, "blocked when background is restricted"); + mLogger.networkBlocked(uid, NTWK_BLOCKED_BG_RESTRICT); return true; } - if (LOGV) logUidStatus(uid, "allowed by default"); + mLogger.networkBlocked(uid, NTWK_ALLOWED_DEFAULT); return false; } @@ -4379,6 +4379,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @Override public void onTempPowerSaveWhitelistChange(int appId, boolean added) { synchronized (mUidRulesFirstLock) { + mLogger.tempPowerSaveWlChanged(appId, added); if (added) { mPowerSaveTempWhitelistAppIds.put(appId, true); } else { @@ -4393,80 +4394,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return (uidRules & rule) != 0; } - private static void logUidStatus(int uid, String descr) { - Slog.d(TAG, String.format("uid %d is %s", uid, descr)); - } - - /** - * This class is used for storing and dumping the last {@link #MAX_PROC_STATE_SEQ_HISTORY} - * (uid, procStateSeq) pairs. - */ - @VisibleForTesting - public static final class ProcStateSeqHistory { - private static final int INVALID_UID = -1; - - /** - * Denotes maximum number of items this history can hold. - */ - private final int mMaxCapacity; - /** - * Used for storing the uid information. - */ - private final int[] mUids; - /** - * Used for storing the sequence numbers associated with {@link #mUids}. - */ - private final long[] mProcStateSeqs; - /** - * Points to the next available slot for writing (uid, procStateSeq) pair. - */ - private int mHistoryNext; - - public ProcStateSeqHistory(int maxCapacity) { - mMaxCapacity = maxCapacity; - mUids = new int[mMaxCapacity]; - Arrays.fill(mUids, INVALID_UID); - mProcStateSeqs = new long[mMaxCapacity]; - } - - @GuardedBy("mUidRulesFirstLock") - public void addProcStateSeqUL(int uid, long procStateSeq) { - mUids[mHistoryNext] = uid; - mProcStateSeqs[mHistoryNext] = procStateSeq; - mHistoryNext = increaseNext(mHistoryNext, 1); - } - - @GuardedBy("mUidRulesFirstLock") - public void dumpUL(IndentingPrintWriter fout) { - if (mUids[0] == INVALID_UID) { - fout.println("NONE"); - return; - } - int index = mHistoryNext; - do { - index = increaseNext(index, -1); - if (mUids[index] == INVALID_UID) { - break; - } - fout.println(getString(mUids[index], mProcStateSeqs[index])); - } while (index != mHistoryNext); - } - - public static String getString(int uid, long procStateSeq) { - return "UID=" + uid + " Seq=" + procStateSeq; - } - - private int increaseNext(int next, int increment) { - next += increment; - if (next >= mMaxCapacity) { - next = 0; - } else if (next < 0) { - next = mMaxCapacity - 1; - } - return next; - } - } - private class NotificationId { private final String mTag; private final int mId; diff --git a/com/android/server/notification/NotificationManagerService.java b/com/android/server/notification/NotificationManagerService.java index 557ba427..bec6fc2c 100644 --- a/com/android/server/notification/NotificationManagerService.java +++ b/com/android/server/notification/NotificationManagerService.java @@ -16,15 +16,21 @@ package com.android.server.notification; +import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED; +import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.IMPORTANCE_NONE; -import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.content.pm.PackageManager.FEATURE_LEANBACK; import static android.content.pm.PackageManager.FEATURE_TELEVISION; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.UserHandle.USER_NULL; import static android.service.notification.NotificationListenerService + .HINT_HOST_DISABLE_CALL_EFFECTS; +import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; +import static android.service.notification.NotificationListenerService + .HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; +import static android.service.notification.NotificationListenerService .NOTIFICATION_CHANNEL_OR_GROUP_ADDED; import static android.service.notification.NotificationListenerService .NOTIFICATION_CHANNEL_OR_GROUP_DELETED; @@ -32,12 +38,13 @@ import static android.service.notification.NotificationListenerService .NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; -import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; +import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED; import static android.service.notification.NotificationListenerService.REASON_CLICK; import static android.service.notification.NotificationListenerService.REASON_ERROR; -import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; +import static android.service.notification.NotificationListenerService + .REASON_GROUP_SUMMARY_CANCELED; import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL; import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED; @@ -48,14 +55,10 @@ import static android.service.notification.NotificationListenerService.REASON_SN import static android.service.notification.NotificationListenerService.REASON_TIMEOUT; import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED; import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED; -import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; -import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; -import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS; import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF; import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON; import static android.service.notification.NotificationListenerService.TRIM_FULL; import static android.service.notification.NotificationListenerService.TRIM_LIGHT; - import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; @@ -68,17 +71,17 @@ import android.app.AlarmManager; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AutomaticZenRule; -import android.app.NotificationChannelGroup; -import android.app.backup.BackupManager; import android.app.IActivityManager; import android.app.INotificationManager; import android.app.ITransientNotification; import android.app.Notification; import android.app.NotificationChannel; -import android.app.NotificationManager.Policy; +import android.app.NotificationChannelGroup; import android.app.NotificationManager; +import android.app.NotificationManager.Policy; import android.app.PendingIntent; import android.app.StatusBarManager; +import android.app.backup.BackupManager; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; @@ -119,8 +122,8 @@ import android.os.ShellCommand; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; -import android.os.Vibrator; import android.os.VibrationEffect; +import android.os.Vibrator; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.Condition; @@ -149,7 +152,6 @@ import android.util.Slog; import android.util.SparseArray; import android.util.Xml; import android.util.proto.ProtoOutputStream; -import android.view.WindowManagerInternal; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; @@ -174,9 +176,10 @@ import com.android.server.SystemService; import com.android.server.lights.Light; import com.android.server.lights.LightsManager; import com.android.server.notification.ManagedServices.ManagedServiceInfo; +import com.android.server.notification.ManagedServices.UserProfiles; import com.android.server.policy.PhoneWindowManager; import com.android.server.statusbar.StatusBarManagerInternal; -import com.android.server.notification.ManagedServices.UserProfiles; +import com.android.server.wm.WindowManagerInternal; import libcore.io.IoUtils; @@ -196,7 +199,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.ArrayList; @@ -736,6 +738,11 @@ public class NotificationManagerService extends SystemService { for (NotificationVisibility nv : newlyVisibleKeys) { NotificationRecord r = mNotificationsByKey.get(nv.key); if (r == null) continue; + if (!r.isSeen()) { + // Report to usage stats that notification was made visible + if (DBG) Slog.d(TAG, "Marking notification as visible " + nv.key); + reportSeen(r); + } r.setVisibility(true, nv.rank); nv.recycle(); } @@ -766,7 +773,7 @@ public class NotificationManagerService extends SystemService { .setType(expanded ? MetricsEvent.TYPE_DETAIL : MetricsEvent.TYPE_COLLAPSE)); } - if (expanded) { + if (expanded && userAction) { r.recordExpanded(); } EventLogTags.writeNotificationExpansion(key, @@ -1515,7 +1522,11 @@ public class NotificationManagerService extends SystemService { } } } + final NotificationChannel preUpdate = + mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), true); + mRankingHelper.updateNotificationChannel(pkg, uid, channel, true); + maybeNotifyChannelOwner(pkg, uid, preUpdate, channel); if (!fromListener) { final NotificationChannel modifiedChannel = @@ -1528,12 +1539,40 @@ public class NotificationManagerService extends SystemService { savePolicyFile(); } + private void maybeNotifyChannelOwner(String pkg, int uid, NotificationChannel preUpdate, + NotificationChannel update) { + try { + if ((preUpdate.getImportance() == IMPORTANCE_NONE + && update.getImportance() != IMPORTANCE_NONE) + || (preUpdate.getImportance() != IMPORTANCE_NONE + && update.getImportance() == IMPORTANCE_NONE)) { + getContext().sendBroadcastAsUser( + new Intent(ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED) + .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID, + update.getId()) + .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, + update.getImportance() == IMPORTANCE_NONE) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) + .setPackage(pkg), + UserHandle.of(UserHandle.getUserId(uid)), null); + } + } catch (SecurityException e) { + Slog.w(TAG, "Can't notify app about channel change", e); + } + } + private void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, boolean fromApp, boolean fromListener) { Preconditions.checkNotNull(group); Preconditions.checkNotNull(pkg); + + final NotificationChannelGroup preUpdate = + mRankingHelper.getNotificationChannelGroup(group.getId(), pkg, uid); mRankingHelper.createNotificationChannelGroup(pkg, uid, group, fromApp); + if (!fromApp) { + maybeNotifyChannelGroupOwner(pkg, uid, preUpdate, group); + } if (!fromListener) { mListeners.notifyNotificationChannelGroupChanged(pkg, UserHandle.of(UserHandle.getCallingUserId()), group, @@ -1541,6 +1580,25 @@ public class NotificationManagerService extends SystemService { } } + private void maybeNotifyChannelGroupOwner(String pkg, int uid, + NotificationChannelGroup preUpdate, NotificationChannelGroup update) { + try { + if (preUpdate.isBlocked() != update.isBlocked()) { + getContext().sendBroadcastAsUser( + new Intent(ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED) + .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID, + update.getId()) + .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, + update.isBlocked()) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) + .setPackage(pkg), + UserHandle.of(UserHandle.getUserId(uid)), null); + } + } catch (SecurityException e) { + Slog.w(TAG, "Can't notify app about group change", e); + } + } + private ArrayList<ComponentName> getSuppressors() { ArrayList<ComponentName> names = new ArrayList<ComponentName>(); for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) { @@ -1643,6 +1701,14 @@ public class NotificationManagerService extends SystemService { return INotificationManager.Stub.asInterface(mService); } + protected void reportSeen(NotificationRecord r) { + final int userId = r.sbn.getUserId(); + mAppUsageStats.reportEvent(r.sbn.getPackageName(), + userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM + : userId, + UsageEvents.Event.NOTIFICATION_SEEN); + } + @VisibleForTesting NotificationManagerInternal getInternalService() { return mInternalService; @@ -1911,11 +1977,18 @@ public class NotificationManagerService extends SystemService { } @Override + public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) { + checkCallerIsSystemOrSameApp(pkg); + return mRankingHelper.getNotificationChannelGroupWithChannels( + pkg, Binder.getCallingUid(), groupId, false); + } + + @Override public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups( String pkg) { checkCallerIsSystemOrSameApp(pkg); - return new ParceledListSlice<>(new ArrayList( - mRankingHelper.getNotificationChannelGroups(pkg, Binder.getCallingUid()))); + return mRankingHelper.getNotificationChannelGroups( + pkg, Binder.getCallingUid(), false, false); } @Override @@ -1985,7 +2058,7 @@ public class NotificationManagerService extends SystemService { public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage( String pkg, int uid, boolean includeDeleted) { checkCallerIsSystem(); - return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted); + return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted, true); } @Override @@ -2269,10 +2342,7 @@ public class NotificationManagerService extends SystemService { } if (!r.isSeen()) { if (DBG) Slog.d(TAG, "Marking notification as seen " + keys[i]); - mAppUsageStats.reportEvent(r.sbn.getPackageName(), - userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM - : userId, - UsageEvents.Event.USER_INTERACTION); + reportSeen(r); r.setSeen(); } } @@ -4673,7 +4743,8 @@ public class NotificationManagerService extends SystemService { } void sendAccessibilityEvent(Notification notification, CharSequence packageName) { - if (!mAccessibilityManager.isEnabled()) { + if (!mAccessibilityManager.isObservedEventType( + AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)) { return; } diff --git a/com/android/server/notification/RankingConfig.java b/com/android/server/notification/RankingConfig.java index b9c0d907..b1b0bf26 100644 --- a/com/android/server/notification/RankingConfig.java +++ b/com/android/server/notification/RankingConfig.java @@ -36,7 +36,7 @@ public interface RankingConfig { void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, boolean fromTargetApp); ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, - int uid, boolean includeDeleted); + int uid, boolean includeDeleted, boolean includeNonGrouped); void createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp); void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser); diff --git a/com/android/server/notification/RankingHelper.java b/com/android/server/notification/RankingHelper.java index d566a450..c0dccb53 100644 --- a/com/android/server/notification/RankingHelper.java +++ b/com/android/server/notification/RankingHelper.java @@ -750,12 +750,15 @@ public class RankingHelper implements RankingConfig { int uid) { Preconditions.checkNotNull(pkg); Record r = getRecord(pkg, uid); + if (r == null) { + return null; + } return r.groups.get(groupId); } @Override public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, - int uid, boolean includeDeleted) { + int uid, boolean includeDeleted, boolean includeNonGrouped) { Preconditions.checkNotNull(pkg); Map<String, NotificationChannelGroup> groups = new ArrayMap<>(); Record r = getRecord(pkg, uid); @@ -783,7 +786,7 @@ public class RankingHelper implements RankingConfig { } } } - if (nonGrouped.getChannels().size() > 0) { + if (includeNonGrouped && nonGrouped.getChannels().size() > 0) { groups.put(null, nonGrouped); } return new ParceledListSlice<>(new ArrayList<>(groups.values())); diff --git a/com/android/server/pm/BackgroundDexOptService.java b/com/android/server/pm/BackgroundDexOptService.java index 679250cb..8591304e 100644 --- a/com/android/server/pm/BackgroundDexOptService.java +++ b/com/android/server/pm/BackgroundDexOptService.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; +import android.annotation.Nullable; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; @@ -40,6 +41,7 @@ import com.android.server.PinnerService; import com.android.server.pm.dex.DexoptOptions; import java.io.File; +import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.TimeUnit; @@ -402,14 +404,22 @@ public class BackgroundDexOptService extends JobService { } /** - * Execute the idle optimizations immediately. + * Execute idle optimizations immediately on packages in packageNames. If packageNames is null, + * then execute on all packages. */ - public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context) { + public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context, + @Nullable List<String> packageNames) { // Create a new object to make sure we don't interfere with the scheduled jobs. // Note that this may still run at the same time with the job scheduled by the // JobScheduler but the scheduler will not be able to cancel it. BackgroundDexOptService bdos = new BackgroundDexOptService(); - int result = bdos.idleOptimization(pm, pm.getOptimizablePackages(), context); + ArraySet<String> packagesToOptimize; + if (packageNames == null) { + packagesToOptimize = pm.getOptimizablePackages(); + } else { + packagesToOptimize = new ArraySet<>(packageNames); + } + int result = bdos.idleOptimization(pm, packagesToOptimize, context); return result == OPTIMIZE_PROCESSED; } diff --git a/com/android/server/pm/Installer.java b/com/android/server/pm/Installer.java index 210eb138..6a06be2f 100644 --- a/com/android/server/pm/Installer.java +++ b/com/android/server/pm/Installer.java @@ -486,6 +486,16 @@ public class Installer extends SystemService { } } + public byte[] hashSecondaryDexFile(String dexPath, String packageName, int uid, + @Nullable String volumeUuid, int flags) throws InstallerException { + if (!checkBeforeRemote()) return new byte[0]; + try { + return mInstalld.hashSecondaryDexFile(dexPath, packageName, uid, volumeUuid, flags); + } catch (Exception e) { + throw InstallerException.from(e); + } + } + public void invalidateMounts() throws InstallerException { if (!checkBeforeRemote()) return; try { diff --git a/com/android/server/pm/PackageDexOptimizer.java b/com/android/server/pm/PackageDexOptimizer.java index 29f48ee3..00cfa310 100644 --- a/com/android/server/pm/PackageDexOptimizer.java +++ b/com/android/server/pm/PackageDexOptimizer.java @@ -154,7 +154,13 @@ public class PackageDexOptimizer { targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo); final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); final List<String> paths = pkg.getAllCodePaths(); - final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); + + int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); + if (sharedGid == -1) { + Slog.wtf(TAG, "Well this is awkward; package " + pkg.applicationInfo.name + " had UID " + + pkg.applicationInfo.uid, new Throwable()); + sharedGid = android.os.Process.NOBODY_UID; + } // Get the class loader context dependencies. // For each code path in the package, this array contains the class loader context that diff --git a/com/android/server/pm/PackageManagerService.java b/com/android/server/pm/PackageManagerService.java index 83cffe57..2d5f7c71 100644 --- a/com/android/server/pm/PackageManagerService.java +++ b/com/android/server/pm/PackageManagerService.java @@ -81,15 +81,12 @@ import static android.content.pm.PackageManager.MOVE_FAILED_OPERATION_PENDING; import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.content.pm.PackageParser.PARSE_IS_OEM; -import static android.content.pm.PackageParser.PARSE_IS_PRIVILEGED; import static android.content.pm.PackageParser.isApkFile; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.os.storage.StorageManager.FLAG_STORAGE_CE; import static android.os.storage.StorageManager.FLAG_STORAGE_DE; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDWR; - import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE; import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT; import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME; @@ -169,11 +166,13 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManager.LegacyPackageDeleteObserver; +import android.content.pm.PackageManager.PackageInfoFlags; import android.content.pm.PackageParser; import android.content.pm.PackageParser.ActivityIntentInfo; import android.content.pm.PackageParser.Package; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; +import android.content.pm.PackageParser.ParseFlags; import android.content.pm.PackageStats; import android.content.pm.PackageUserState; import android.content.pm.ParceledListSlice; @@ -293,6 +292,7 @@ import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.Settings.DatabaseVersion; import com.android.server.pm.Settings.VersionInfo; +import com.android.server.pm.dex.DexLogger; import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.DexoptOptions; import com.android.server.pm.dex.PackageDexUsage; @@ -447,27 +447,48 @@ public class PackageManagerService extends IPackageManager.Stub // package apks to install directory. private static final String INSTALL_PACKAGE_SUFFIX = "-"; - static final int SCAN_NO_DEX = 1<<1; - static final int SCAN_FORCE_DEX = 1<<2; - static final int SCAN_UPDATE_SIGNATURE = 1<<3; - static final int SCAN_NEW_INSTALL = 1<<4; - static final int SCAN_UPDATE_TIME = 1<<5; - static final int SCAN_BOOTING = 1<<6; - static final int SCAN_TRUSTED_OVERLAY = 1<<7; - static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<8; - static final int SCAN_REPLACING = 1<<9; - static final int SCAN_REQUIRE_KNOWN = 1<<10; - static final int SCAN_MOVE = 1<<11; - static final int SCAN_INITIAL = 1<<12; - static final int SCAN_CHECK_ONLY = 1<<13; - static final int SCAN_DONT_KILL_APP = 1<<14; - static final int SCAN_IGNORE_FROZEN = 1<<15; - static final int SCAN_FIRST_BOOT_OR_UPGRADE = 1<<16; - static final int SCAN_AS_INSTANT_APP = 1<<17; - static final int SCAN_AS_FULL_APP = 1<<18; - static final int SCAN_AS_VIRTUAL_PRELOAD = 1<<19; - /** Should not be with the scan flags */ - static final int FLAGS_REMOVE_CHATTY = 1<<31; + static final int SCAN_NO_DEX = 1<<0; + static final int SCAN_UPDATE_SIGNATURE = 1<<1; + static final int SCAN_NEW_INSTALL = 1<<2; + static final int SCAN_UPDATE_TIME = 1<<3; + static final int SCAN_BOOTING = 1<<4; + static final int SCAN_TRUSTED_OVERLAY = 1<<5; + static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<6; + static final int SCAN_REQUIRE_KNOWN = 1<<7; + static final int SCAN_MOVE = 1<<8; + static final int SCAN_INITIAL = 1<<9; + static final int SCAN_CHECK_ONLY = 1<<10; + static final int SCAN_DONT_KILL_APP = 1<<11; + static final int SCAN_IGNORE_FROZEN = 1<<12; + static final int SCAN_FIRST_BOOT_OR_UPGRADE = 1<<13; + static final int SCAN_AS_INSTANT_APP = 1<<14; + static final int SCAN_AS_FULL_APP = 1<<15; + static final int SCAN_AS_VIRTUAL_PRELOAD = 1<<16; + static final int SCAN_AS_SYSTEM = 1<<17; + static final int SCAN_AS_PRIVILEGED = 1<<18; + static final int SCAN_AS_OEM = 1<<19; + + @IntDef(flag = true, prefix = { "SCAN_" }, value = { + SCAN_NO_DEX, + SCAN_UPDATE_SIGNATURE, + SCAN_NEW_INSTALL, + SCAN_UPDATE_TIME, + SCAN_BOOTING, + SCAN_TRUSTED_OVERLAY, + SCAN_DELETE_DATA_ON_FAILURES, + SCAN_REQUIRE_KNOWN, + SCAN_MOVE, + SCAN_INITIAL, + SCAN_CHECK_ONLY, + SCAN_DONT_KILL_APP, + SCAN_IGNORE_FROZEN, + SCAN_FIRST_BOOT_OR_UPGRADE, + SCAN_AS_INSTANT_APP, + SCAN_AS_FULL_APP, + SCAN_AS_VIRTUAL_PRELOAD, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ScanFlags {} private static final String STATIC_SHARED_LIB_DELIMITER = "_"; /** Extension of the compressed packages */ @@ -2380,7 +2401,10 @@ public class PackageManagerService extends IPackageManager.Stub mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context, "*dexopt*"); - mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock); + DexManager.Listener dexManagerListener = DexLogger.getListener(this, + installer, mInstallLock); + mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock, + dexManagerListener); mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper()); mOnPermissionChangeListeners = new OnPermissionChangeListeners( @@ -2511,32 +2535,44 @@ public class PackageManagerService extends IPackageManager.Stub // Collect vendor overlay packages. (Do this before scanning any apps.) // For security and version matching reason, only consider // overlay packages if they reside in the right directory. - scanDirTracedLI(new File(VENDOR_OVERLAY_DIR), mDefParseFlags - | PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR - | PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0); + scanDirTracedLI(new File(VENDOR_OVERLAY_DIR), + mDefParseFlags + | PackageParser.PARSE_IS_SYSTEM_DIR, + scanFlags + | SCAN_AS_SYSTEM + | SCAN_TRUSTED_OVERLAY, + 0); mParallelPackageParserCallback.findStaticOverlayPackages(); // Find base frameworks (resource packages without code). - scanDirTracedLI(frameworkDir, mDefParseFlags - | PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR - | PackageParser.PARSE_IS_PRIVILEGED, - scanFlags | SCAN_NO_DEX, 0); + scanDirTracedLI(frameworkDir, + mDefParseFlags + | PackageParser.PARSE_IS_SYSTEM_DIR, + scanFlags + | SCAN_NO_DEX + | SCAN_AS_SYSTEM + | SCAN_AS_PRIVILEGED, + 0); // Collected privileged system packages. final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app"); - scanDirTracedLI(privilegedAppDir, mDefParseFlags - | PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR - | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0); + scanDirTracedLI(privilegedAppDir, + mDefParseFlags + | PackageParser.PARSE_IS_SYSTEM_DIR, + scanFlags + | SCAN_AS_SYSTEM + | SCAN_AS_PRIVILEGED, + 0); // Collect ordinary system packages. final File systemAppDir = new File(Environment.getRootDirectory(), "app"); - scanDirTracedLI(systemAppDir, mDefParseFlags - | PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0); + scanDirTracedLI(systemAppDir, + mDefParseFlags + | PackageParser.PARSE_IS_SYSTEM_DIR, + scanFlags + | SCAN_AS_SYSTEM, + 0); // Collect all vendor packages. File vendorAppDir = new File("/vendor/app"); @@ -2545,16 +2581,22 @@ public class PackageManagerService extends IPackageManager.Stub } catch (IOException e) { // failed to look up canonical path, continue with original one } - scanDirTracedLI(vendorAppDir, mDefParseFlags - | PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0); + scanDirTracedLI(vendorAppDir, + mDefParseFlags + | PackageParser.PARSE_IS_SYSTEM_DIR, + scanFlags + | SCAN_AS_SYSTEM, + 0); // Collect all OEM packages. final File oemAppDir = new File(Environment.getOemDirectory(), "app"); - scanDirTracedLI(oemAppDir, mDefParseFlags - | PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR - | PackageParser.PARSE_IS_OEM, scanFlags, 0); + scanDirTracedLI(oemAppDir, + mDefParseFlags + | PackageParser.PARSE_IS_SYSTEM_DIR, + scanFlags + | SCAN_AS_SYSTEM + | SCAN_AS_OEM, + 0); // Prune any system packages that no longer exist. final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>(); @@ -2711,21 +2753,38 @@ public class PackageManagerService extends IPackageManager.Stub logCriticalInfo(Log.WARN, "Expected better " + packageName + " but never showed up; reverting to system"); - int reparseFlags = mDefParseFlags; + final @ParseFlags int reparseFlags; + final @ScanFlags int rescanFlags; if (FileUtils.contains(privilegedAppDir, scanFile)) { - reparseFlags = PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR - | PackageParser.PARSE_IS_PRIVILEGED; + reparseFlags = + mDefParseFlags | + PackageParser.PARSE_IS_SYSTEM_DIR; + rescanFlags = + scanFlags + | SCAN_AS_SYSTEM + | SCAN_AS_PRIVILEGED; } else if (FileUtils.contains(systemAppDir, scanFile)) { - reparseFlags = PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR; + reparseFlags = + mDefParseFlags | + PackageParser.PARSE_IS_SYSTEM_DIR; + rescanFlags = + scanFlags + | SCAN_AS_SYSTEM; } else if (FileUtils.contains(vendorAppDir, scanFile)) { - reparseFlags = PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR; + reparseFlags = + mDefParseFlags | + PackageParser.PARSE_IS_SYSTEM_DIR; + rescanFlags = + scanFlags + | SCAN_AS_SYSTEM; } else if (FileUtils.contains(oemAppDir, scanFile)) { - reparseFlags = PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR - | PackageParser.PARSE_IS_OEM; + reparseFlags = + mDefParseFlags | + PackageParser.PARSE_IS_SYSTEM_DIR; + rescanFlags = + scanFlags + | SCAN_AS_SYSTEM + | SCAN_AS_OEM; } else { Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile); continue; @@ -2734,7 +2793,7 @@ public class PackageManagerService extends IPackageManager.Stub mSettings.enableSystemPackageLPw(packageName); try { - scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, null); + scanPackageTracedLI(scanFile, reparseFlags, rescanFlags, 0, null); } catch (PackageManagerException e) { Slog.e(TAG, "Failed to parse original system package: " + e.getMessage()); @@ -3581,7 +3640,7 @@ public class PackageManagerService extends IPackageManager.Stub final int N = list.size(); for (int i = 0; i < N; i++) { ResolveInfo info = list.get(i); - if (packageName.equals(info.activityInfo.packageName)) { + if (info.priority >= 0 && packageName.equals(info.activityInfo.packageName)) { return true; } } @@ -7986,96 +8045,95 @@ public class PackageManagerService extends IPackageManager.Stub return finalList; } - private void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + dir.getAbsolutePath() + "]"); + private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime) { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]"); try { - scanDirLI(dir, parseFlags, scanFlags, currentTime); + scanDirLI(scanDir, parseFlags, scanFlags, currentTime); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } - private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) { - final File[] files = dir.listFiles(); + private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) { + final File[] files = scanDir.listFiles(); if (ArrayUtils.isEmpty(files)) { - Log.d(TAG, "No files in app dir " + dir); + Log.d(TAG, "No files in app dir " + scanDir); return; } if (DEBUG_PACKAGE_SCANNING) { - Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags + Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags + " flags=0x" + Integer.toHexString(parseFlags)); } - ParallelPackageParser parallelPackageParser = new ParallelPackageParser( + try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser( mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir, - mParallelPackageParserCallback); - - // Submit files for parsing in parallel - int fileCount = 0; - for (File file : files) { - final boolean isPackage = (isApkFile(file) || file.isDirectory()) - && !PackageInstallerService.isStageName(file.getName()); - if (!isPackage) { - // Ignore entries which are not packages - continue; + mParallelPackageParserCallback)) { + // Submit files for parsing in parallel + int fileCount = 0; + for (File file : files) { + final boolean isPackage = (isApkFile(file) || file.isDirectory()) + && !PackageInstallerService.isStageName(file.getName()); + if (!isPackage) { + // Ignore entries which are not packages + continue; + } + parallelPackageParser.submit(file, parseFlags); + fileCount++; } - parallelPackageParser.submit(file, parseFlags); - fileCount++; - } - // Process results one by one - for (; fileCount > 0; fileCount--) { - ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take(); - Throwable throwable = parseResult.throwable; - int errorCode = PackageManager.INSTALL_SUCCEEDED; + // Process results one by one + for (; fileCount > 0; fileCount--) { + ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take(); + Throwable throwable = parseResult.throwable; + int errorCode = PackageManager.INSTALL_SUCCEEDED; - if (throwable == null) { - // Static shared libraries have synthetic package names - if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) { - renameStaticSharedLibraryPackage(parseResult.pkg); - } - try { - if (errorCode == PackageManager.INSTALL_SUCCEEDED) { - scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags, - currentTime, null); + if (throwable == null) { + // Static shared libraries have synthetic package names + if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) { + renameStaticSharedLibraryPackage(parseResult.pkg); } - } catch (PackageManagerException e) { + try { + if (errorCode == PackageManager.INSTALL_SUCCEEDED) { + scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags, + currentTime, null); + } + } catch (PackageManagerException e) { + errorCode = e.error; + Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage()); + } + } else if (throwable instanceof PackageParser.PackageParserException) { + PackageParser.PackageParserException e = (PackageParser.PackageParserException) + throwable; errorCode = e.error; - Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage()); + Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage()); + } else { + throw new IllegalStateException("Unexpected exception occurred while parsing " + + parseResult.scanFile, throwable); } - } else if (throwable instanceof PackageParser.PackageParserException) { - PackageParser.PackageParserException e = (PackageParser.PackageParserException) - throwable; - errorCode = e.error; - Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage()); - } else { - throw new IllegalStateException("Unexpected exception occurred while parsing " - + parseResult.scanFile, throwable); - } - // Delete invalid userdata apps - if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 && - errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) { - logCriticalInfo(Log.WARN, - "Deleting invalid package at " + parseResult.scanFile); - removeCodePathLI(parseResult.scanFile); + // Delete invalid userdata apps + if ((scanFlags & SCAN_AS_SYSTEM) == 0 && + errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) { + logCriticalInfo(Log.WARN, + "Deleting invalid package at " + parseResult.scanFile); + removeCodePathLI(parseResult.scanFile); + } } } - parallelPackageParser.close(); } public static void reportSettingsProblem(int priority, String msg) { logCriticalInfo(priority, msg); } - private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, File srcFile, - final int policyFlags) throws PackageManagerException { + private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, + final @ParseFlags int parseFlags) throws PackageManagerException { // When upgrading from pre-N MR1, verify the package time stamp using the package // directory and not the APK file. final long lastModifiedTime = mIsPreNMR1Upgrade - ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg, srcFile); + ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg); if (ps != null - && ps.codePath.equals(srcFile) + && ps.codePathString.equals(pkg.codePath) && ps.timeStamp == lastModifiedTime && !isCompatSignatureUpdateNeeded(pkg) && !isRecoverSignatureUpdateNeeded(pkg)) { @@ -8098,12 +8156,12 @@ public class PackageManagerService extends IPackageManager.Stub Slog.w(TAG, "PackageSetting for " + ps.name + " is missing signatures. Collecting certs again to recover them."); } else { - Slog.i(TAG, srcFile.toString() + " changed; collecting certs"); + Slog.i(TAG, toString() + " changed; collecting certs"); } try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); - PackageParser.collectCertificates(pkg, policyFlags); + PackageParser.collectCertificates(pkg, parseFlags); } catch (PackageParserException e) { throw PackageManagerException.from(e); } finally { @@ -8138,10 +8196,6 @@ public class PackageManagerService extends IPackageManager.Stub pp.setDisplayMetrics(mMetrics); pp.setCallback(mPackageParserCallback); - if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) { - parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY; - } - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage"); final PackageParser.Package pkg; try { @@ -8157,16 +8211,17 @@ public class PackageManagerService extends IPackageManager.Stub renameStaticSharedLibraryPackage(pkg); } - return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user); + return scanPackageChildLI(pkg, parseFlags, scanFlags, currentTime, user); } /** * Scans a package and returns the newly parsed package. * @throws PackageManagerException on a parse error. */ - private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile, - final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user) - throws PackageManagerException { + private PackageParser.Package scanPackageChildLI(PackageParser.Package pkg, + final @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime, + @Nullable UserHandle user) + throws PackageManagerException { // If the package has children and this is the first dive in the function // we scan the package with the SCAN_CHECK_ONLY flag set to see whether all // packages (parent and children) would be successfully scanned before the @@ -8181,20 +8236,20 @@ public class PackageManagerService extends IPackageManager.Stub } // Scan the parent - PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, scanFile, policyFlags, + PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, parseFlags, scanFlags, currentTime, user); // Scan the children final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; for (int i = 0; i < childCount; i++) { PackageParser.Package childPackage = pkg.childPackages.get(i); - scanPackageInternalLI(childPackage, scanFile, policyFlags, scanFlags, + scanPackageInternalLI(childPackage, parseFlags, scanFlags, currentTime, user); } if ((scanFlags & SCAN_CHECK_ONLY) != 0) { - return scanPackageLI(pkg, scanFile, policyFlags, scanFlags, currentTime, user); + return scanPackageChildLI(pkg, parseFlags, scanFlags, currentTime, user); } return scannedPkg; @@ -8204,11 +8259,12 @@ public class PackageManagerService extends IPackageManager.Stub * Scans a package and returns the newly parsed package. * @throws PackageManagerException on a parse error. */ - private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg, File scanFile, - int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user) - throws PackageManagerException { + private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg, + @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime, + @Nullable UserHandle user) + throws PackageManagerException { PackageSetting ps = null; - PackageSetting updatedPkg; + PackageSetting updatedPs; // reader synchronized (mPackages) { // Look to see if we already know about this package. @@ -8225,14 +8281,14 @@ public class PackageManagerService extends IPackageManager.Stub // Check to see if this package could be hiding/updating a system // package. Must look for it either under the original or real // package name depending on our state. - updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName); - if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg); + updatedPs = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName); + if (DEBUG_INSTALL && updatedPs != null) Slog.d(TAG, "updatedPkg = " + updatedPs); // If this is a package we don't know about on the system partition, we // may need to remove disabled child packages on the system partition // or may need to not add child packages if the parent apk is updated // on the data partition and no longer defines this child package. - if ((policyFlags & PackageParser.PARSE_IS_SYSTEM) != 0) { + if ((scanFlags & SCAN_AS_SYSTEM) != 0) { // If this is a parent package for an updated system app and this system // app got an OTA update which no longer defines some of the child packages // we have to prune them from the disabled system packages. @@ -8260,28 +8316,27 @@ public class PackageManagerService extends IPackageManager.Stub } } - final boolean isUpdatedPkg = updatedPkg != null; - final boolean isUpdatedSystemPkg = isUpdatedPkg - && (policyFlags & PackageParser.PARSE_IS_SYSTEM) != 0; + final boolean isUpdatedPkg = updatedPs != null; + final boolean isUpdatedSystemPkg = isUpdatedPkg && (scanFlags & SCAN_AS_SYSTEM) != 0; boolean isUpdatedPkgBetter = false; // First check if this is a system package that may involve an update if (isUpdatedSystemPkg) { // If new package is not located in "/system/priv-app" (e.g. due to an OTA), // it needs to drop FLAG_PRIVILEGED. - if (locationIsPrivileged(scanFile)) { - updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; + if (locationIsPrivileged(pkg.codePath)) { + updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; } else { - updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; + updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; } // If new package is not located in "/oem" (e.g. due to an OTA), // it needs to drop FLAG_OEM. - if (locationIsOem(scanFile)) { - updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM; + if (locationIsOem(pkg.codePath)) { + updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM; } else { - updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_OEM; + updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_OEM; } - if (ps != null && !ps.codePath.equals(scanFile)) { + if (ps != null && !ps.codePathString.equals(pkg.codePath)) { // The path has changed from what was last scanned... check the // version of the new path against what we have stored to determine // what to do. @@ -8289,26 +8344,27 @@ public class PackageManagerService extends IPackageManager.Stub if (pkg.mVersionCode <= ps.versionCode) { // The system package has been updated and the code path does not match // Ignore entry. Skip it. - if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + scanFile + if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + pkg.codePath + " ignored: updated version " + ps.versionCode + " better than this " + pkg.mVersionCode); - if (!updatedPkg.codePath.equals(scanFile)) { + if (!updatedPs.codePathString.equals(pkg.codePath)) { Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg " - + ps.name + " changing from " + updatedPkg.codePathString - + " to " + scanFile); - updatedPkg.codePath = scanFile; - updatedPkg.codePathString = scanFile.toString(); - updatedPkg.resourcePath = scanFile; - updatedPkg.resourcePathString = scanFile.toString(); + + ps.name + " changing from " + updatedPs.codePathString + + " to " + pkg.codePath); + final File codePath = new File(pkg.codePath); + updatedPs.codePath = codePath; + updatedPs.codePathString = pkg.codePath; + updatedPs.resourcePath = codePath; + updatedPs.resourcePathString = pkg.codePath; } - updatedPkg.pkg = pkg; - updatedPkg.versionCode = pkg.mVersionCode; + updatedPs.pkg = pkg; + updatedPs.versionCode = pkg.mVersionCode; // Update the disabled system child packages to point to the package too. - final int childCount = updatedPkg.childPackageNames != null - ? updatedPkg.childPackageNames.size() : 0; + final int childCount = updatedPs.childPackageNames != null + ? updatedPs.childPackageNames.size() : 0; for (int i = 0; i < childCount; i++) { - String childPackageName = updatedPkg.childPackageNames.get(i); + String childPackageName = updatedPs.childPackageNames.get(i); PackageSetting updatedChildPkg = mSettings.getDisabledSystemPkgLPr( childPackageName); if (updatedChildPkg != null) { @@ -8329,7 +8385,7 @@ public class PackageManagerService extends IPackageManager.Stub mPackages.remove(ps.name); } - logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile + logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath + " reverting from " + ps.codePathString + ": new version " + pkg.mVersionCode + " better than installed " + ps.versionCode); @@ -8349,7 +8405,7 @@ public class PackageManagerService extends IPackageManager.Stub String resourcePath = null; String baseResourcePath = null; - if ((policyFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !isUpdatedPkgBetter) { + if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !isUpdatedPkgBetter) { if (ps != null && ps.resourcePathString != null) { resourcePath = ps.resourcePathString; baseResourcePath = ps.resourcePathString; @@ -8376,39 +8432,38 @@ public class PackageManagerService extends IPackageManager.Stub if (isUpdatedSystemPkg && !isUpdatedPkgBetter) { // Set CPU Abis to application info. if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) { - final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, updatedPkg); - derivePackageAbi(pkg, scanFile, cpuAbiOverride, false, mAppLib32InstallDir); + final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, updatedPs); + derivePackageAbi(pkg, cpuAbiOverride, false, mAppLib32InstallDir); } else { - pkg.applicationInfo.primaryCpuAbi = updatedPkg.primaryCpuAbiString; - pkg.applicationInfo.secondaryCpuAbi = updatedPkg.secondaryCpuAbiString; + pkg.applicationInfo.primaryCpuAbi = updatedPs.primaryCpuAbiString; + pkg.applicationInfo.secondaryCpuAbi = updatedPs.secondaryCpuAbiString; } - pkg.mExtras = updatedPkg; + pkg.mExtras = updatedPs; throw new PackageManagerException(Log.WARN, "Package " + ps.name + " at " - + scanFile + " ignored: updated version " + ps.versionCode + + pkg.codePath + " ignored: updated version " + ps.versionCode + " better than this " + pkg.mVersionCode); } if (isUpdatedPkg) { - // An updated system app will not have the PARSE_IS_SYSTEM flag set - // initially - policyFlags |= PackageParser.PARSE_IS_SYSTEM; + // updated system applications don't initially have the SCAN_AS_SYSTEM flag set + scanFlags |= SCAN_AS_SYSTEM; - // An updated privileged app will not have the PARSE_IS_PRIVILEGED + // An updated privileged application will not have the PARSE_IS_PRIVILEGED // flag set initially - if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) { - policyFlags |= PackageParser.PARSE_IS_PRIVILEGED; + if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) { + scanFlags |= SCAN_AS_PRIVILEGED; } // An updated OEM app will not have the PARSE_IS_OEM // flag set initially - if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) { - policyFlags |= PackageParser.PARSE_IS_OEM; + if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) { + scanFlags |= SCAN_AS_OEM; } } // Verify certificates against what was last scanned - collectCertificatesLI(ps, pkg, scanFile, policyFlags); + collectCertificatesLI(ps, pkg, parseFlags); /* * A new system app appeared, but we already had a non-system one of the @@ -8416,7 +8471,7 @@ public class PackageManagerService extends IPackageManager.Stub */ boolean shouldHideSystemApp = false; if (!isUpdatedPkg && ps != null - && (policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) { + && (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) { /* * Check to make sure the signatures match first. If they don't, * wipe the installed application and its data. @@ -8438,7 +8493,7 @@ public class PackageManagerService extends IPackageManager.Stub */ if (pkg.mVersionCode <= ps.versionCode) { shouldHideSystemApp = true; - logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + scanFile + logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + pkg.codePath + " but new version " + pkg.mVersionCode + " better than installed " + ps.versionCode + "; hiding system"); } else { @@ -8448,7 +8503,7 @@ public class PackageManagerService extends IPackageManager.Stub * already-installed application and replace it with our own * while keeping the application data. */ - logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile + logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath + " reverting from " + ps.codePathString + ": new version " + pkg.mVersionCode + " better than installed " + ps.versionCode); InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps), @@ -8464,9 +8519,9 @@ public class PackageManagerService extends IPackageManager.Stub // are kept in different files. (except for app in either system or // vendor path). // TODO grab this value from PackageSettings - if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { + if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { if (ps != null && !ps.codePath.equals(ps.resourcePath)) { - policyFlags |= PackageParser.PARSE_FORWARD_LOCK; + parseFlags |= PackageParser.PARSE_FORWARD_LOCK; } } @@ -8479,7 +8534,7 @@ public class PackageManagerService extends IPackageManager.Stub } // Note that we invoke the following method only if we are about to unpack an application - PackageParser.Package scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags + PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user); /* @@ -8985,11 +9040,11 @@ public class PackageManagerService extends IPackageManager.Stub * Execute the background dexopt job immediately. */ @Override - public boolean runBackgroundDexoptJob() { + public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames) { if (getInstantAppPackageName(Binder.getCallingUid()) != null) { return false; } - return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext); + return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext, packageNames); } List<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) { @@ -9481,8 +9536,8 @@ public class PackageManagerService extends IPackageManager.Stub } private PackageParser.Package scanPackageTracedLI(PackageParser.Package pkg, - final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user) - throws PackageManagerException { + final @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime, + @Nullable UserHandle user) throws PackageManagerException { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage"); // If the package has children and this is the first dive in the function // we recursively scan the package with the SCAN_CHECK_ONLY flag set to see @@ -9500,12 +9555,12 @@ public class PackageManagerService extends IPackageManager.Stub final PackageParser.Package scannedPkg; try { // Scan the parent - scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags, currentTime, user); + scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags, currentTime, user); // Scan the children final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; for (int i = 0; i < childCount; i++) { PackageParser.Package childPkg = pkg.childPackages.get(i); - scanPackageLI(childPkg, policyFlags, + scanPackageLI(childPkg, parseFlags, scanFlags, currentTime, user); } } finally { @@ -9513,18 +9568,18 @@ public class PackageManagerService extends IPackageManager.Stub } if ((scanFlags & SCAN_CHECK_ONLY) != 0) { - return scanPackageTracedLI(pkg, policyFlags, scanFlags, currentTime, user); + return scanPackageTracedLI(pkg, parseFlags, scanFlags, currentTime, user); } return scannedPkg; } - private PackageParser.Package scanPackageLI(PackageParser.Package pkg, final int policyFlags, - int scanFlags, long currentTime, @Nullable UserHandle user) - throws PackageManagerException { + private PackageParser.Package scanPackageLI(PackageParser.Package pkg, + final @ParseFlags int parseFlags, final @ScanFlags int scanFlags, long currentTime, + @Nullable UserHandle user) throws PackageManagerException { boolean success = false; try { - final PackageParser.Package res = scanPackageDirtyLI(pkg, policyFlags, scanFlags, + final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags, currentTime, user); success = true; return res; @@ -9587,16 +9642,17 @@ public class PackageManagerService extends IPackageManager.Stub } private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, - final int policyFlags, final int scanFlags, long currentTime, @Nullable UserHandle user) + final @ParseFlags int parseFlags, final @ScanFlags int scanFlags, long currentTime, + @Nullable UserHandle user) throws PackageManagerException { if (DEBUG_PACKAGE_SCANNING) { - if ((policyFlags & PackageParser.PARSE_CHATTY) != 0) + if ((parseFlags & PackageParser.PARSE_CHATTY) != 0) Log.d(TAG, "Scanning package " + pkg.packageName); } - applyPolicy(pkg, policyFlags); + applyPolicy(pkg, parseFlags, scanFlags); - assertPackageIsValid(pkg, policyFlags, scanFlags); + assertPackageIsValid(pkg, parseFlags, scanFlags); if (Build.IS_DEBUGGABLE && pkg.isPrivileged() && @@ -9629,7 +9685,7 @@ public class PackageManagerService extends IPackageManager.Stub suid = mSettings.getSharedUserLPw( pkg.mSharedUserId, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/); if (DEBUG_PACKAGE_SCANNING) { - if ((policyFlags & PackageParser.PARSE_CHATTY) != 0) + if ((parseFlags & PackageParser.PARSE_CHATTY) != 0) Log.d(TAG, "Shared UserID " + pkg.mSharedUserId + " (uid=" + suid.userId + "): packages=" + suid.packages); } @@ -9797,7 +9853,7 @@ public class PackageManagerService extends IPackageManager.Stub } if ((scanFlags & SCAN_BOOTING) == 0 - && (policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { + && (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { // Check all shared libraries and map to their actual file path. // We only do this here for apps not on a system dir, because those // are the only ones that can fail an install due to this. We @@ -9834,7 +9890,7 @@ public class PackageManagerService extends IPackageManager.Stub // over the latest parsed certs. pkgSetting.signatures.mSignatures = pkg.mSignatures; } else { - if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { + if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package " + pkg.packageName + " upgrade keys do not match the " + "previously installed version"); @@ -9861,7 +9917,7 @@ public class PackageManagerService extends IPackageManager.Stub // over the latest parsed certs. pkgSetting.signatures.mSignatures = pkg.mSignatures; } catch (PackageManagerException e) { - if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { + if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { throw e; } // The signature has changed, but this package is in the system @@ -9921,8 +9977,7 @@ public class PackageManagerService extends IPackageManager.Stub if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi"); final boolean extractNativeLibs = !pkg.isLibrary(); - derivePackageAbi(pkg, scanFile, cpuAbiOverride, extractNativeLibs, - mAppLib32InstallDir); + derivePackageAbi(pkg, cpuAbiOverride, extractNativeLibs, mAppLib32InstallDir); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); // Some system apps still use directory structure for native libraries @@ -10029,7 +10084,7 @@ public class PackageManagerService extends IPackageManager.Stub } // Take care of first install / last update times. - final long scanFileTime = getLastModifiedTime(pkg, scanFile); + final long scanFileTime = getLastModifiedTime(pkg); if (currentTime != 0) { if (pkgSetting.firstInstallTime == 0) { pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = currentTime; @@ -10039,7 +10094,7 @@ public class PackageManagerService extends IPackageManager.Stub } else if (pkgSetting.firstInstallTime == 0) { // We need *something*. Take time time stamp of the file. pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = scanFileTime; - } else if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) { + } else if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) { if (scanFileTime != pkgSetting.timeStamp) { // A package on the system image has changed; consider this // to be an update. @@ -10058,7 +10113,7 @@ public class PackageManagerService extends IPackageManager.Stub final int userId = user == null ? 0 : user.getIdentifier(); // Modify state for the given package setting commitPackageSettings(pkg, pkgSetting, user, scanFlags, - (policyFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/); + (parseFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/); if (pkgSetting.getInstantApp(userId)) { mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId); } @@ -10073,8 +10128,9 @@ public class PackageManagerService extends IPackageManager.Stub * Implementation detail: This method must NOT have any side effect. It would * ideally be static, but, it requires locks to read system state. */ - private void applyPolicy(PackageParser.Package pkg, int policyFlags) { - if ((policyFlags&PackageParser.PARSE_IS_SYSTEM) != 0) { + private void applyPolicy(PackageParser.Package pkg, final @ParseFlags int parseFlags, + final @ScanFlags int scanFlags) { + if ((scanFlags & SCAN_AS_SYSTEM) != 0) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; if (pkg.applicationInfo.isDirectBootAware()) { // we're direct boot aware; set for all components @@ -10095,21 +10151,60 @@ public class PackageManagerService extends IPackageManager.Stub pkg.isStub = true; } } else { - // Only allow system apps to be flagged as core apps. + // non system apps can't be flagged as core pkg.coreApp = false; // clear flags not applicable to regular apps + pkg.applicationInfo.flags &= + ~ApplicationInfo.FLAG_PERSISTENT; pkg.applicationInfo.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE; pkg.applicationInfo.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE; + // clear protected broadcasts + pkg.protectedBroadcasts = null; + // cap permission priorities + if (pkg.permissionGroups != null && pkg.permissionGroups.size() > 0) { + for (int i = pkg.permissionGroups.size() - 1; i >= 0; --i) { + pkg.permissionGroups.get(i).info.priority = 0; + } + } + } + if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) { + // ignore export request for single user receivers + if (pkg.receivers != null) { + for (int i = pkg.receivers.size() - 1; i >= 0; --i) { + final PackageParser.Activity receiver = pkg.receivers.get(i); + if ((receiver.info.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) { + receiver.info.exported = false; + } + } + } + // ignore export request for single user services + if (pkg.services != null) { + for (int i = pkg.services.size() - 1; i >= 0; --i) { + final PackageParser.Service service = pkg.services.get(i); + if ((service.info.flags & ServiceInfo.FLAG_SINGLE_USER) != 0) { + service.info.exported = false; + } + } + } + // ignore export request for single user providers + if (pkg.providers != null) { + for (int i = pkg.providers.size() - 1; i >= 0; --i) { + final PackageParser.Provider provider = pkg.providers.get(i); + if ((provider.info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0) { + provider.info.exported = false; + } + } + } } - pkg.mTrustedOverlay = (policyFlags&PackageParser.PARSE_TRUSTED_OVERLAY) != 0; + pkg.mTrustedOverlay = (scanFlags & SCAN_TRUSTED_OVERLAY) != 0; - if ((policyFlags&PackageParser.PARSE_IS_PRIVILEGED) != 0) { + if ((scanFlags & SCAN_AS_PRIVILEGED) != 0) { pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; } - if ((policyFlags&PackageParser.PARSE_IS_OEM) != 0) { + if ((scanFlags & SCAN_AS_OEM) != 0) { pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM; } @@ -10130,9 +10225,10 @@ public class PackageManagerService extends IPackageManager.Stub * * @throws PackageManagerException If the package fails any of the validation checks */ - private void assertPackageIsValid(PackageParser.Package pkg, int policyFlags, int scanFlags) - throws PackageManagerException { - if ((policyFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) { + private void assertPackageIsValid(PackageParser.Package pkg, final @ParseFlags int parseFlags, + final @ScanFlags int scanFlags) + throws PackageManagerException { + if ((parseFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) { assertCodePolicy(pkg); } @@ -10290,7 +10386,7 @@ public class PackageManagerService extends IPackageManager.Stub // Only privileged apps and updated privileged apps can add child packages. if (pkg.childPackages != null && !pkg.childPackages.isEmpty()) { - if ((policyFlags & PARSE_IS_PRIVILEGED) == 0) { + if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) { throw new PackageManagerException("Only privileged apps can add child " + "packages. Ignoring package " + pkg.packageName); } @@ -10416,7 +10512,8 @@ public class PackageManagerService extends IPackageManager.Stub * be available for query, resolution, etc... */ private void commitPackageSettings(PackageParser.Package pkg, PackageSetting pkgSetting, - UserHandle user, int scanFlags, boolean chatty) throws PackageManagerException { + UserHandle user, final @ScanFlags int scanFlags, boolean chatty) + throws PackageManagerException { final String pkgName = pkg.packageName; if (mCustomResolverComponentName != null && mCustomResolverComponentName.getPackageName().equals(pkg.packageName)) { @@ -10767,10 +10864,9 @@ public class PackageManagerService extends IPackageManager.Stub * * If {@code extractLibs} is true, native libraries are extracted from the app if required. */ - private static void derivePackageAbi(PackageParser.Package pkg, File scanFile, - String cpuAbiOverride, boolean extractLibs, - File appLib32InstallDir) - throws PackageManagerException { + private static void derivePackageAbi(PackageParser.Package pkg, String cpuAbiOverride, + boolean extractLibs, File appLib32InstallDir) + throws PackageManagerException { // Give ourselves some initial paths; we'll come back for another // pass once we've determined ABI below. setNativeLibraryPaths(pkg, appLib32InstallDir); @@ -14903,7 +14999,12 @@ public class PackageManagerService extends IPackageManager.Stub resourceFile = afterCodeFile; // Reflect the rename in scanned details - pkg.setCodePath(afterCodeFile.getAbsolutePath()); + try { + pkg.setCodePath(afterCodeFile.getCanonicalPath()); + } catch (IOException e) { + Slog.e(TAG, "Failed to get path: " + afterCodeFile, e); + return false; + } pkg.setBaseCodePath(FileUtils.rewriteAfterRename(beforeCodeFile, afterCodeFile, pkg.baseCodePath)); pkg.setSplitCodePaths(FileUtils.rewriteAfterRename(beforeCodeFile, @@ -15248,9 +15349,9 @@ public class PackageManagerService extends IPackageManager.Stub /* * Install a non-existing package. */ - private void installNewPackageLIF(PackageParser.Package pkg, final int policyFlags, - int scanFlags, UserHandle user, String installerPackageName, String volumeUuid, - PackageInstalledInfo res, int installReason) { + private void installNewPackageLIF(PackageParser.Package pkg, final @ParseFlags int parseFlags, + final @ScanFlags int scanFlags, UserHandle user, String installerPackageName, + String volumeUuid, PackageInstalledInfo res, int installReason) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installNewPackage"); // Remember this for later, in case we need to rollback this install @@ -15279,7 +15380,7 @@ public class PackageManagerService extends IPackageManager.Stub } try { - PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags, + PackageParser.Package newPackage = scanPackageTracedLI(pkg, parseFlags, scanFlags, System.currentTimeMillis(), user); updateSettingsLI(newPackage, installerPackageName, null, res, user, installReason); @@ -15307,9 +15408,9 @@ public class PackageManagerService extends IPackageManager.Stub } } - private void replacePackageLIF(PackageParser.Package pkg, final int policyFlags, int scanFlags, - UserHandle user, String installerPackageName, PackageInstalledInfo res, - int installReason) { + private void replacePackageLIF(PackageParser.Package pkg, final @ParseFlags int parseFlags, + final @ScanFlags int scanFlags, UserHandle user, String installerPackageName, + PackageInstalledInfo res, int installReason) { final boolean isInstantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0; final PackageParser.Package oldPackage; @@ -15329,7 +15430,7 @@ public class PackageManagerService extends IPackageManager.Stub == android.os.Build.VERSION_CODES.CUR_DEVELOPMENT; if (oldTargetsPreRelease && !newTargetsPreRelease - && ((policyFlags & PackageParser.PARSE_FORCE_SDK) == 0)) { + && ((parseFlags & PackageParser.PARSE_FORCE_SDK) == 0)) { Slog.w(TAG, "Can't install package targeting released sdk"); res.setReturnCode(PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE); return; @@ -15480,15 +15581,16 @@ public class PackageManagerService extends IPackageManager.Stub final boolean oem = (oldPackage.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0; - final int systemPolicyFlags = policyFlags - | PackageParser.PARSE_IS_SYSTEM - | (privileged ? PARSE_IS_PRIVILEGED : 0) - | (oem ? PARSE_IS_OEM : 0); + final @ParseFlags int systemParseFlags = parseFlags; + final @ScanFlags int systemScanFlags = scanFlags + | SCAN_AS_SYSTEM + | (privileged ? SCAN_AS_PRIVILEGED : 0) + | (oem ? SCAN_AS_OEM : 0); - replaceSystemPackageLIF(oldPackage, pkg, systemPolicyFlags, scanFlags, + replaceSystemPackageLIF(oldPackage, pkg, systemParseFlags, systemScanFlags, user, allUsers, installerPackageName, res, installReason); } else { - replaceNonSystemPackageLIF(oldPackage, pkg, policyFlags, scanFlags, + replaceNonSystemPackageLIF(oldPackage, pkg, parseFlags, scanFlags, user, allUsers, installerPackageName, res, installReason); } } @@ -15510,9 +15612,9 @@ public class PackageManagerService extends IPackageManager.Stub } private void replaceNonSystemPackageLIF(PackageParser.Package deletedPackage, - PackageParser.Package pkg, final int policyFlags, int scanFlags, UserHandle user, - int[] allUsers, String installerPackageName, PackageInstalledInfo res, - int installReason) { + PackageParser.Package pkg, final @ParseFlags int parseFlags, + final @ScanFlags int scanFlags, UserHandle user, int[] allUsers, + String installerPackageName, PackageInstalledInfo res, int installReason) { if (DEBUG_INSTALL) Slog.d(TAG, "replaceNonSystemPackageLI: new=" + pkg + ", old=" + deletedPackage); @@ -15553,7 +15655,7 @@ public class PackageManagerService extends IPackageManager.Stub clearAppProfilesLIF(deletedPackage, UserHandle.USER_ALL); try { - final PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags, + final PackageParser.Package newPackage = scanPackageTracedLI(pkg, parseFlags, scanFlags | SCAN_UPDATE_TIME, System.currentTimeMillis(), user); updateSettingsLI(newPackage, installerPackageName, allUsers, res, user, installReason); @@ -15659,7 +15761,8 @@ public class PackageManagerService extends IPackageManager.Stub } private void replaceSystemPackageLIF(PackageParser.Package deletedPackage, - PackageParser.Package pkg, final int policyFlags, int scanFlags, UserHandle user, + PackageParser.Package pkg, final @ParseFlags int parseFlags, + final @ScanFlags int scanFlags, UserHandle user, int[] allUsers, String installerPackageName, PackageInstalledInfo res, int installReason) { if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg @@ -15697,7 +15800,7 @@ public class PackageManagerService extends IPackageManager.Stub PackageParser.Package newPackage = null; try { // Add the package to the internal data structures - newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags, 0, user); + newPackage = scanPackageTracedLI(pkg, parseFlags, scanFlags, 0, user); // Set the update and install times PackageSetting deletedPkgSetting = (PackageSetting) deletedPackage.mExtras; @@ -15754,7 +15857,7 @@ public class PackageManagerService extends IPackageManager.Stub } // Add back the old system package try { - scanPackageTracedLI(deletedPackage, policyFlags, SCAN_UPDATE_SIGNATURE, 0, user); + scanPackageTracedLI(deletedPackage, parseFlags, SCAN_UPDATE_SIGNATURE, 0, user); } catch (PackageManagerException e) { Slog.e(TAG, "Failed to restore original package: " + e.getMessage()); } @@ -16008,7 +16111,7 @@ public class PackageManagerService extends IPackageManager.Stub final boolean virtualPreload = ((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0); boolean replace = false; - int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE; + @ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE; if (args.move != null) { // moving a complete application; perform an initial scan on the new install location scanFlags |= SCAN_INITIAL; @@ -16041,7 +16144,7 @@ public class PackageManagerService extends IPackageManager.Stub } // Retrieve PackageSettings and parse package - final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY + @ParseFlags final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY | PackageParser.PARSE_ENFORCE_CODE | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0) @@ -16383,8 +16486,7 @@ public class PackageManagerService extends IPackageManager.Stub String abiOverride = (TextUtils.isEmpty(pkg.cpuAbiOverride) ? args.abiOverride : pkg.cpuAbiOverride); final boolean extractNativeLibs = !pkg.isLibrary(); - derivePackageAbi(pkg, new File(pkg.codePath), abiOverride, - extractNativeLibs, mAppLib32InstallDir); + derivePackageAbi(pkg, abiOverride, extractNativeLibs, mAppLib32InstallDir); } catch (PackageManagerException pme) { Slog.e(TAG, "Error deriving application ABI", pme); res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Error deriving application ABI"); @@ -16473,7 +16575,7 @@ public class PackageManagerService extends IPackageManager.Stub return; } } - replacePackageLIF(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user, + replacePackageLIF(pkg, parseFlags, scanFlags, args.user, installerPackageName, res, args.installReason); } else { installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES, @@ -17099,7 +17201,7 @@ public class PackageManagerService extends IPackageManager.Stub try (PackageFreezer freezer = freezePackageForDelete(packageName, freezeUser, deleteFlags, "deletePackageX")) { res = deletePackageLIF(packageName, UserHandle.of(removeUser), true, allUsers, - deleteFlags | FLAGS_REMOVE_CHATTY, info, true, null); + deleteFlags | PackageManager.DELETE_CHATTY, info, true, null); } synchronized (mPackages) { if (res) { @@ -17291,7 +17393,7 @@ public class PackageManagerService extends IPackageManager.Stub } } - removePackageLI(ps, (flags & FLAGS_REMOVE_CHATTY) != 0); + removePackageLI(ps, (flags & PackageManager.DELETE_CHATTY) != 0); if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) { final PackageParser.Package resolvedPkg; @@ -17388,21 +17490,19 @@ public class PackageManagerService extends IPackageManager.Stub } } - static boolean locationIsPrivileged(File path) { + static boolean locationIsPrivileged(String path) { try { - final String privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app") - .getCanonicalPath(); - return path.getCanonicalPath().startsWith(privilegedAppDir); + final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app"); + return path.startsWith(privilegedAppDir.getCanonicalPath()); } catch (IOException e) { Slog.e(TAG, "Unable to access code path " + path); } return false; } - static boolean locationIsOem(File path) { + static boolean locationIsOem(String path) { try { - return path.getCanonicalPath().startsWith( - Environment.getOemDirectory().getCanonicalPath()); + return path.startsWith(Environment.getOemDirectory().getCanonicalPath()); } catch (IOException e) { Slog.e(TAG, "Unable to access code path " + path); } @@ -17498,7 +17598,7 @@ public class PackageManagerService extends IPackageManager.Stub // Install the system package if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs); try { - installPackageFromSystemLIF(disabledPs.codePath, false /*isPrivileged*/, allUserHandles, + installPackageFromSystemLIF(disabledPs.codePathString, false, allUserHandles, outInfo.origUsers, deletedPs.getPermissionsState(), writeSettings); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to restore system package:" + deletedPkg.packageName + ": " @@ -17515,23 +17615,25 @@ public class PackageManagerService extends IPackageManager.Stub /** * Installs a package that's already on the system partition. */ - private PackageParser.Package installPackageFromSystemLIF(@NonNull File codePath, + private PackageParser.Package installPackageFromSystemLIF(@NonNull String codePathString, boolean isPrivileged, @Nullable int[] allUserHandles, @Nullable int[] origUserHandles, @Nullable PermissionsState origPermissionState, boolean writeSettings) throws PackageManagerException { - int parseFlags = mDefParseFlags + @ParseFlags int parseFlags = + mDefParseFlags | PackageParser.PARSE_MUST_BE_APK - | PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR; - if (isPrivileged || locationIsPrivileged(codePath)) { - parseFlags |= PackageParser.PARSE_IS_PRIVILEGED; + @ScanFlags int scanFlags = SCAN_AS_SYSTEM; + if (isPrivileged || locationIsPrivileged(codePathString)) { + scanFlags |= SCAN_AS_PRIVILEGED; } - if (locationIsOem(codePath)) { - parseFlags |= PackageParser.PARSE_IS_OEM; + if (locationIsOem(codePathString)) { + scanFlags |= SCAN_AS_OEM; } + final File codePath = new File(codePathString); final PackageParser.Package pkg = - scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/, 0 /*currentTime*/, null); + scanPackageTracedLI(codePath, parseFlags, scanFlags, 0 /*currentTime*/, null); try { // update shared libraries for the newly re-installed system package @@ -19587,9 +19689,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); pp.setCallback(mPackageParserCallback); final PackageParser.Package tmpPkg; try { - final int parseFlags = mDefParseFlags + final @ParseFlags int parseFlags = mDefParseFlags | PackageParser.PARSE_MUST_BE_APK - | PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR; tmpPkg = pp.parsePackage(codePath, parseFlags); } catch (PackageParserException e) { @@ -19642,7 +19743,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); // until we can disable the package later. enableSystemPackageLPw(deletedPkg); } - installPackageFromSystemLIF(new File(deletedPkg.codePath), + installPackageFromSystemLIF(deletedPkg.codePath, false /*isPrivileged*/, null /*allUserHandles*/, null /*origUserHandles*/, null /*origPermissionsState*/, true /*writeSettings*/); @@ -22607,6 +22708,12 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } @Override + public int getPackageUid(String packageName, int flags, int userId) { + return PackageManagerService.this + .getPackageUid(packageName, flags, userId); + } + + @Override public ApplicationInfo getApplicationInfo( String packageName, int flags, int filterCallingUid, int userId) { return PackageManagerService.this diff --git a/com/android/server/pm/PackageManagerServiceUtils.java b/com/android/server/pm/PackageManagerServiceUtils.java index 758abd76..20ec9b5e 100644 --- a/com/android/server/pm/PackageManagerServiceUtils.java +++ b/com/android/server/pm/PackageManagerServiceUtils.java @@ -295,19 +295,20 @@ public class PackageManagerServiceUtils { return false; } - public static long getLastModifiedTime(PackageParser.Package pkg, File srcFile) { - if (srcFile.isDirectory()) { - final File baseFile = new File(pkg.baseCodePath); - long maxModifiedTime = baseFile.lastModified(); - if (pkg.splitCodePaths != null) { - for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) { - final File splitFile = new File(pkg.splitCodePaths[i]); - maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified()); - } + public static long getLastModifiedTime(PackageParser.Package pkg) { + final File srcFile = new File(pkg.codePath); + if (!srcFile.isDirectory()) { + return srcFile.lastModified(); + } + final File baseFile = new File(pkg.baseCodePath); + long maxModifiedTime = baseFile.lastModified(); + if (pkg.splitCodePaths != null) { + for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) { + final File splitFile = new File(pkg.splitCodePaths[i]); + maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified()); } - return maxModifiedTime; } - return srcFile.lastModified(); + return maxModifiedTime; } /** @@ -570,7 +571,7 @@ public class PackageManagerServiceUtils { if (!match) { throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package " + packageName + - " signatures don't match previously installed version; ignoring!"); + " signatures do not match previously installed version; ignoring!"); } } // Check for shared user signatures @@ -578,16 +579,16 @@ public class PackageManagerServiceUtils { // Already existing package. Make sure signatures match boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures) == PackageManager.SIGNATURE_MATCH; - if (!match) { + if (!match && compareCompat) { match = matchSignaturesCompat( packageName, pkgSetting.sharedUser.signatures, parsedSignatures); } - if (!match && compareCompat) { + if (!match && compareRecover) { match = matchSignaturesRecover( packageName, pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures); compatMatch |= match; } - if (!match && compareRecover) { + if (!match) { throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE, "Package " + packageName + " has no signatures that match those in shared user " diff --git a/com/android/server/pm/PackageManagerShellCommand.java b/com/android/server/pm/PackageManagerShellCommand.java index 807eb1a8..44f36d17 100644 --- a/com/android/server/pm/PackageManagerShellCommand.java +++ b/com/android/server/pm/PackageManagerShellCommand.java @@ -264,7 +264,7 @@ class PackageManagerShellCommand extends ShellCommand { PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null, null, null); params.sessionParams.setSize(PackageHelper.calculateInstalledSize( - pkgLite, params.sessionParams.abiOverride)); + pkgLite, params.sessionParams.abiOverride, fd.getFileDescriptor())); } catch (PackageParserException | IOException e) { getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath); throw new IllegalArgumentException( @@ -1169,11 +1169,17 @@ class PackageManagerShellCommand extends ShellCommand { } List<String> failedPackages = new ArrayList<>(); + int index = 0; for (String packageName : packageNames) { if (clearProfileData) { mInterface.clearApplicationProfileData(packageName); } + if (allPackages) { + pw.println(++index + "/" + packageNames.size() + ": " + packageName); + pw.flush(); + } + boolean result = secondaryDex ? mInterface.performDexOptSecondary(packageName, targetCompilerFilter, forceCompilation) @@ -1219,7 +1225,13 @@ class PackageManagerShellCommand extends ShellCommand { } private int runDexoptJob() throws RemoteException { - boolean result = mInterface.runBackgroundDexoptJob(); + String arg; + List<String> packageNames = new ArrayList<>(); + while ((arg = getNextArg()) != null) { + packageNames.add(arg); + } + boolean result = mInterface.runBackgroundDexoptJob(packageNames.isEmpty() ? null : + packageNames); return result ? 0 : -1; } diff --git a/com/android/server/pm/Settings.java b/com/android/server/pm/Settings.java index 7077fd16..ddad6774 100644 --- a/com/android/server/pm/Settings.java +++ b/com/android/server/pm/Settings.java @@ -3572,11 +3572,10 @@ public final class Settings { int pkgFlags = 0; int pkgPrivateFlags = 0; pkgFlags |= ApplicationInfo.FLAG_SYSTEM; - final File codePathFile = new File(codePathStr); - if (PackageManagerService.locationIsPrivileged(codePathFile)) { + if (PackageManagerService.locationIsPrivileged(codePathStr)) { pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; } - PackageSetting ps = new PackageSetting(name, realName, codePathFile, + PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiStr, secondaryCpuAbiStr, cpuAbiOverrideStr, versionCode, pkgFlags, pkgPrivateFlags, parentPackageName, null /*childPackageNames*/, 0 /*sharedUserId*/, null, null); diff --git a/com/android/server/pm/UserManagerService.java b/com/android/server/pm/UserManagerService.java index 11523101..dbf413f0 100644 --- a/com/android/server/pm/UserManagerService.java +++ b/com/android/server/pm/UserManagerService.java @@ -717,6 +717,19 @@ public class UserManagerService extends IUserManager.Stub { } } + @Override + public int getProfileParentId(int userHandle) { + checkManageUsersPermission("get the profile parent"); + synchronized (mUsersLock) { + UserInfo profileParent = getProfileParentLU(userHandle); + if (profileParent == null) { + return userHandle; + } + + return profileParent.id; + } + } + private UserInfo getProfileParentLU(int userHandle) { UserInfo profile = getUserInfoLU(userHandle); if (profile == null) { diff --git a/com/android/server/pm/dex/DexLogger.java b/com/android/server/pm/dex/DexLogger.java new file mode 100644 index 00000000..88d9e52c --- /dev/null +++ b/com/android/server/pm/dex/DexLogger.java @@ -0,0 +1,123 @@ +/* + * 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 com.android.server.pm.dex; + +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.os.RemoteException; + +import android.util.ArraySet; +import android.util.ByteStringUtils; +import android.util.EventLog; +import android.util.PackageUtils; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.pm.Installer; +import com.android.server.pm.Installer.InstallerException; + +import java.io.File; +import java.util.Set; + +import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; + +/** + * This class is responsible for logging data about secondary dex files. + * The data logged includes hashes of the name and content of each file. + */ +public class DexLogger implements DexManager.Listener { + private static final String TAG = "DexLogger"; + + // Event log tag & subtag used for SafetyNet logging of dynamic + // code loading (DCL) - see b/63927552. + private static final int SNET_TAG = 0x534e4554; + private static final String DCL_SUBTAG = "dcl"; + + private final IPackageManager mPackageManager; + private final Object mInstallLock; + @GuardedBy("mInstallLock") + private final Installer mInstaller; + + public static DexManager.Listener getListener(IPackageManager pms, + Installer installer, Object installLock) { + return new DexLogger(pms, installer, installLock); + } + + @VisibleForTesting + /*package*/ DexLogger(IPackageManager pms, Installer installer, Object installLock) { + mPackageManager = pms; + mInstaller = installer; + mInstallLock = installLock; + } + + /** + * Compute and log hashes of the name and content of a secondary dex file. + */ + @Override + public void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo, + String dexPath, int storageFlags) { + int ownerUid = appInfo.uid; + + byte[] hash = null; + synchronized(mInstallLock) { + try { + hash = mInstaller.hashSecondaryDexFile(dexPath, appInfo.packageName, + ownerUid, appInfo.volumeUuid, storageFlags); + } catch (InstallerException e) { + Slog.e(TAG, "Got InstallerException when hashing dex " + dexPath + + " : " + e.getMessage()); + } + } + if (hash == null) { + return; + } + + String dexFileName = new File(dexPath).getName(); + String message = PackageUtils.computeSha256Digest(dexFileName.getBytes()); + // Valid SHA256 will be 256 bits, 32 bytes. + if (hash.length == 32) { + message = message + ' ' + ByteStringUtils.toHexString(hash); + } + + writeDclEvent(ownerUid, message); + + if (dexUseInfo.isUsedByOtherApps()) { + Set<String> otherPackages = dexUseInfo.getLoadingPackages(); + Set<Integer> otherUids = new ArraySet<>(otherPackages.size()); + for (String otherPackageName : otherPackages) { + try { + int otherUid = mPackageManager.getPackageUid( + otherPackageName, /*flags*/0, dexUseInfo.getOwnerUserId()); + if (otherUid != -1 && otherUid != ownerUid) { + otherUids.add(otherUid); + } + } catch (RemoteException ignore) { + // Can't happen, we're local. + } + } + for (int otherUid : otherUids) { + writeDclEvent(otherUid, message); + } + } + } + + @VisibleForTesting + /*package*/ void writeDclEvent(int uid, String message) { + EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, uid, message); + } +} diff --git a/com/android/server/pm/dex/DexManager.java b/com/android/server/pm/dex/DexManager.java index 62747547..0e2730cb 100644 --- a/com/android/server/pm/dex/DexManager.java +++ b/com/android/server/pm/dex/DexManager.java @@ -76,6 +76,7 @@ public class DexManager { private final Object mInstallLock; @GuardedBy("mInstallLock") private final Installer mInstaller; + private final Listener mListener; // Possible outcomes of a dex search. private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found @@ -96,14 +97,24 @@ public class DexManager { */ private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo(); + public interface Listener { + /** + * Invoked just before the secondary dex file {@code dexPath} for the specified application + * is reconciled. + */ + void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo, + String dexPath, int storageFlags); + } + public DexManager(IPackageManager pms, PackageDexOptimizer pdo, - Installer installer, Object installLock) { + Installer installer, Object installLock, Listener listener) { mPackageCodeLocationsCache = new HashMap<>(); mPackageDexUsage = new PackageDexUsage(); mPackageManager = pms; mPackageDexOptimizer = pdo; mInstaller = installer; mInstallLock = installLock; + mListener = listener; } /** @@ -389,7 +400,7 @@ public class DexManager { : mPackageDexOptimizer; String packageName = options.getPackageName(); PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); - if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { + if (useInfo.getDexUseInfoMap().isEmpty()) { if (DEBUG) { Slog.d(TAG, "No secondary dex use for package:" + packageName); } @@ -433,7 +444,7 @@ public class DexManager { */ public void reconcileSecondaryDexFiles(String packageName) { PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); - if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { + if (useInfo.getDexUseInfoMap().isEmpty()) { if (DEBUG) { Slog.d(TAG, "No secondary dex use for package:" + packageName); } @@ -481,12 +492,16 @@ public class DexManager { continue; } + if (mListener != null) { + mListener.onReconcileSecondaryDexFile(info, dexUseInfo, dexPath, flags); + } + boolean dexStillExists = true; synchronized(mInstallLock) { try { String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]); dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName, - pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags); + info.uid, isas, info.volumeUuid, flags); } catch (InstallerException e) { Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath + " : " + e.getMessage()); diff --git a/com/android/server/policy/BarController.java b/com/android/server/policy/BarController.java index b1792358..10d9565c 100644 --- a/com/android/server/policy/BarController.java +++ b/com/android/server/policy/BarController.java @@ -24,9 +24,9 @@ import android.util.Slog; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; -import android.view.WindowManagerPolicy.WindowState; import com.android.server.LocalServices; +import com.android.server.policy.WindowManagerPolicy.WindowState; import com.android.server.statusbar.StatusBarManagerInternal; import java.io.PrintWriter; diff --git a/com/android/server/policy/GlobalActions.java b/com/android/server/policy/GlobalActions.java index 3707a5ed..108b6b25 100644 --- a/com/android/server/policy/GlobalActions.java +++ b/com/android/server/policy/GlobalActions.java @@ -14,14 +14,14 @@ package com.android.server.policy; -import com.android.server.LocalServices; -import com.android.server.statusbar.StatusBarManagerInternal; -import com.android.server.statusbar.StatusBarManagerInternal.GlobalActionsListener; - import android.content.Context; import android.os.Handler; import android.util.Slog; -import android.view.WindowManagerPolicy.WindowManagerFuncs; + +import com.android.server.LocalServices; +import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs; +import com.android.server.statusbar.StatusBarManagerInternal; +import com.android.server.statusbar.StatusBarManagerInternal.GlobalActionsListener; class GlobalActions implements GlobalActionsListener { diff --git a/com/android/server/policy/LegacyGlobalActions.java b/com/android/server/policy/LegacyGlobalActions.java index 8eb6d065..96d062df 100644 --- a/com/android/server/policy/LegacyGlobalActions.java +++ b/com/android/server/policy/LegacyGlobalActions.java @@ -25,6 +25,7 @@ import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.TelephonyProperties; import com.android.internal.R; import com.android.internal.widget.LockPatternUtils; +import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs; import android.app.ActivityManager; import android.app.Dialog; @@ -64,7 +65,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.view.WindowManagerPolicy.WindowManagerFuncs; import android.view.accessibility.AccessibilityEvent; import android.widget.AdapterView; import android.widget.BaseAdapter; @@ -919,7 +919,7 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn /** * @param enabledIconResId The icon for when this action is on. * @param disabledIconResid The icon for when this action is off. - * @param essage The general information message, e.g 'Silent Mode' + * @param message The general information message, e.g 'Silent Mode' * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' */ diff --git a/com/android/server/policy/PhoneWindowManager.java b/com/android/server/policy/PhoneWindowManager.java index 9162a97a..7415ec38 100644 --- a/com/android/server/policy/PhoneWindowManager.java +++ b/com/android/server/policy/PhoneWindowManager.java @@ -119,12 +119,13 @@ import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION; import static android.view.WindowManagerGlobal.ADD_OKAY; import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED; -import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED; -import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT; -import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED; -import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; -import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED; -import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN; + +import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED; +import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT; +import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED; +import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; +import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED; +import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN; import android.annotation.Nullable; import android.app.ActivityManager; @@ -210,7 +211,6 @@ import android.util.Slog; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.Display; -import android.view.DisplayFrames; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.IApplicationToken; @@ -230,9 +230,6 @@ import android.view.ViewConfiguration; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.WindowManagerGlobal; -import android.view.WindowManagerInternal; -import android.view.WindowManagerInternal.AppTransitionListener; -import android.view.WindowManagerPolicy; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.animation.Animation; @@ -243,6 +240,7 @@ import android.view.inputmethod.InputMethodManagerInternal; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; @@ -259,6 +257,9 @@ import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.vr.VrManagerInternal; import com.android.server.wm.AppTransition; +import com.android.server.wm.DisplayFrames; +import com.android.server.wm.WindowManagerInternal; +import com.android.server.wm.WindowManagerInternal.AppTransitionListener; import java.io.File; import java.io.FileReader; @@ -304,6 +305,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1; static final int LONG_PRESS_POWER_SHUT_OFF = 2; static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3; + static final int LONG_PRESS_POWER_GO_TO_VOICE_ASSIST = 4; + + static final int VERY_LONG_PRESS_POWER_NOTHING = 0; + static final int VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS = 1; static final int MULTI_PRESS_POWER_NOTHING = 0; static final int MULTI_PRESS_POWER_THEATER_MODE = 1; @@ -569,6 +574,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mLidControlsSleep; int mShortPressOnPowerBehavior; int mLongPressOnPowerBehavior; + int mVeryLongPressOnPowerBehavior; int mDoublePressOnPowerBehavior; int mTriplePressOnPowerBehavior; int mLongPressOnBackBehavior; @@ -586,6 +592,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mHasSoftInput = false; boolean mTranslucentDecorEnabled = true; boolean mUseTvRouting; + int mVeryLongPressTimeout; private boolean mHandleVolumeKeysInWM; @@ -796,6 +803,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_HANDLE_ALL_APPS = 26; private static final int MSG_LAUNCH_ASSIST = 27; private static final int MSG_LAUNCH_ASSIST_LONG_PRESS = 28; + private static final int MSG_POWER_VERY_LONG_PRESS = 29; private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0; private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1; @@ -855,6 +863,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { case MSG_POWER_LONG_PRESS: powerLongPress(); break; + case MSG_POWER_VERY_LONG_PRESS: + powerVeryLongPress(); + break; case MSG_UPDATE_DREAMING_SLEEP_TOKEN: updateDreamingSleepToken(msg.arg1 != 0); break; @@ -1043,7 +1054,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { private ImmersiveModeConfirmation mImmersiveModeConfirmation; - private SystemGesturesPointerEventListener mSystemGestures; + @VisibleForTesting + SystemGesturesPointerEventListener mSystemGestures; IStatusBarService getStatusBarService() { synchronized (mServiceAquireLock) { @@ -1299,6 +1311,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); + + if (hasVeryLongPressOnPowerBehavior()) { + Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS); + longMsg.setAsynchronous(true); + mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout); + } } } else { wakeUpFromPowerKey(event.getDownTime()); @@ -1308,6 +1326,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); + + if (hasVeryLongPressOnPowerBehavior()) { + Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS); + longMsg.setAsynchronous(true); + mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout); + } + mBeganFromNonInteractive = true; } else { final int maxCount = getMaxMultiPressPowerCount(); @@ -1369,6 +1394,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerKeyHandled = true; mHandler.removeMessages(MSG_POWER_LONG_PRESS); } + if (hasVeryLongPressOnPowerBehavior()) { + mHandler.removeMessages(MSG_POWER_VERY_LONG_PRESS); + } } private void cancelPendingBackKeyAction() { @@ -1516,6 +1544,29 @@ public class PhoneWindowManager implements WindowManagerPolicy { sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF); break; + case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST: + mPowerKeyHandled = true; + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + final boolean keyguardActive = mKeyguardDelegate == null + ? false + : mKeyguardDelegate.isShowing(); + if (!keyguardActive) { + Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); + startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); + } + break; + } + } + + private void powerVeryLongPress() { + switch (mVeryLongPressOnPowerBehavior) { + case VERY_LONG_PRESS_POWER_NOTHING: + break; + case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS: + mPowerKeyHandled = true; + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + showGlobalActionsInternal(); + break; } } @@ -1574,6 +1625,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING; } + private boolean hasVeryLongPressOnPowerBehavior() { + return mVeryLongPressOnPowerBehavior != VERY_LONG_PRESS_POWER_NOTHING; + } + private boolean hasLongPressOnBackBehavior() { return mLongPressOnBackBehavior != LONG_PRESS_BACK_NOTHING; } @@ -1979,12 +2034,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { com.android.internal.R.integer.config_shortPressOnPowerBehavior); mLongPressOnPowerBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_longPressOnPowerBehavior); + mVeryLongPressOnPowerBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_veryLongPressOnPowerBehavior); mDoublePressOnPowerBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_doublePressOnPowerBehavior); mTriplePressOnPowerBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_triplePressOnPowerBehavior); mShortPressOnSleepBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_shortPressOnSleepBehavior); + mVeryLongPressTimeout = mContext.getResources().getInteger( + com.android.internal.R.integer.config_veryLongPressTimeout); mUseTvRouting = AudioSystem.getPlatformType(mContext) == AudioSystem.PLATFORM_TELEVISION; @@ -2607,17 +2666,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { // The status bar is the only window allowed to exhibit keyguard behavior. attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; } + } + private int getImpliedSysUiFlagsForLayout(LayoutParams attrs) { + int impliedFlags = 0; if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) { - attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + impliedFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; } final boolean forceWindowDrawsStatusBarBackground = (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0; if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 || forceWindowDrawsStatusBarBackground && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) { - attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + impliedFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; } + return impliedFlags; } void readLidState() { @@ -2667,7 +2730,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public void onConfigurationChanged() { // TODO(multi-display): Define policy for secondary displays. - Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext(); + Context uiContext = getSystemUiContext(); final Resources res = uiContext.getResources(); mStatusBarHeight = @@ -2708,6 +2771,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + @VisibleForTesting + Context getSystemUiContext() { + return ActivityThread.currentActivityThread().getSystemUiContext(); + } + @Override public int getMaxWallpaperLayer() { return getWindowLayerFromTypeLw(TYPE_STATUS_BAR); @@ -4768,7 +4836,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { final int fl = PolicyControl.getWindowFlags(win, attrs); final int pfl = attrs.privateFlags; final int sim = attrs.softInputMode; - final int sysUiFl = PolicyControl.getSystemUiVisibility(win, null); + final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(win, null); + final int sysUiFl = requestedSysUiFl | getImpliedSysUiFlagsForLayout(attrs); final Rect pf = mTmpParentFrame; final Rect df = mTmpDisplayFrame; @@ -8062,19 +8131,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - public boolean canMagnifyWindow(int windowType) { - switch (windowType) { - case WindowManager.LayoutParams.TYPE_INPUT_METHOD: - case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG: - case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR: - case WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY: { - return false; - } - } - return true; - } - - @Override public boolean isTopLevelWindow(int windowType) { if (windowType >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && windowType <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { @@ -8206,6 +8262,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { pw.print("mLongPressOnPowerBehavior="); pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior)); pw.print(prefix); + pw.print("mVeryLongPressOnPowerBehavior="); + pw.println(veryLongPressOnPowerBehaviorToString(mVeryLongPressOnPowerBehavior)); + pw.print(prefix); pw.print("mDoublePressOnPowerBehavior="); pw.println(multiPressOnPowerBehaviorToString(mDoublePressOnPowerBehavior)); pw.print(prefix); @@ -8458,6 +8517,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { return Integer.toString(behavior); } } + + private static String veryLongPressOnPowerBehaviorToString(int behavior) { + switch (behavior) { + case VERY_LONG_PRESS_POWER_NOTHING: + return "VERY_LONG_PRESS_POWER_NOTHING"; + case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS: + return "VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS"; + default: + return Integer.toString(behavior); + } + } + private static String multiPressOnPowerBehaviorToString(int behavior) { switch (behavior) { case MULTI_PRESS_POWER_NOTHING: diff --git a/com/android/server/policy/PolicyControl.java b/com/android/server/policy/PolicyControl.java index dbafc424..3f26d867 100644 --- a/com/android/server/policy/PolicyControl.java +++ b/com/android/server/policy/PolicyControl.java @@ -25,7 +25,8 @@ import android.util.Slog; import android.view.View; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; -import android.view.WindowManagerPolicy.WindowState; + +import com.android.server.policy.WindowManagerPolicy.WindowState; import java.io.PrintWriter; import java.io.StringWriter; @@ -36,7 +37,7 @@ import java.io.StringWriter; * This includes forcing immersive mode behavior for one or both system bars (based on a package * list) and permanently disabling immersive mode confirmations for specific packages. * - * Control by setting {@link Settings.Global.POLICY_CONTROL} to one or more name-value pairs. + * Control by setting {@link Settings.Global#POLICY_CONTROL} to one or more name-value pairs. * e.g. * to force immersive mode everywhere: * "immersive.full=*" diff --git a/com/android/server/policy/SplashScreenSurface.java b/com/android/server/policy/SplashScreenSurface.java index 37d6c0b5..b9202c33 100644 --- a/com/android/server/policy/SplashScreenSurface.java +++ b/com/android/server/policy/SplashScreenSurface.java @@ -23,11 +23,10 @@ import android.os.IBinder; import android.util.Slog; import android.view.View; import android.view.WindowManager; -import android.view.WindowManagerPolicy; -import android.view.WindowManagerPolicy.StartingSurface; import com.android.internal.policy.DecorView; import com.android.internal.policy.PhoneWindow; +import com.android.server.policy.WindowManagerPolicy.StartingSurface; /** * Holds the contents of a splash screen starting window, i.e. the {@link DecorView} of a diff --git a/com/android/server/policy/StatusBarController.java b/com/android/server/policy/StatusBarController.java index ecc88b50..af7e91c5 100644 --- a/com/android/server/policy/StatusBarController.java +++ b/com/android/server/policy/StatusBarController.java @@ -18,7 +18,7 @@ package com.android.server.policy; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.MATCH_PARENT; -import static android.view.WindowManagerInternal.AppTransitionListener; +import static com.android.server.wm.WindowManagerInternal.AppTransitionListener; import android.app.StatusBarManager; import android.os.IBinder; diff --git a/com/android/server/policy/SystemGesturesPointerEventListener.java b/com/android/server/policy/SystemGesturesPointerEventListener.java index 598c58e5..d3cc8eff 100644 --- a/com/android/server/policy/SystemGesturesPointerEventListener.java +++ b/com/android/server/policy/SystemGesturesPointerEventListener.java @@ -24,7 +24,7 @@ import android.util.Slog; import android.view.GestureDetector; import android.view.InputDevice; import android.view.MotionEvent; -import android.view.WindowManagerPolicy.PointerEventListener; +import android.view.WindowManagerPolicyConstants.PointerEventListener; import android.widget.OverScroller; /* diff --git a/android/view/WindowManagerPolicy.java b/com/android/server/policy/WindowManagerPolicy.java index 534335bf..5f067d49 100644 --- a/android/view/WindowManagerPolicy.java +++ b/com/android/server/policy/WindowManagerPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006 The Android Open Source Project + * 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. @@ -14,10 +14,8 @@ * limitations under the License. */ -package android.view; +package com.android.server.policy; -import static android.Manifest.permission; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; @@ -63,9 +61,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType; +import android.Manifest; import android.annotation.IntDef; import android.annotation.Nullable; -import android.annotation.SystemApi; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.CompatibilityInfo; @@ -78,10 +76,20 @@ import android.os.Looper; import android.os.RemoteException; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import android.view.Display; +import android.view.IApplicationToken; +import android.view.IWindowManager; +import android.view.InputEventReceiver; +import android.view.KeyEvent; +import android.view.Surface; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.WindowManagerPolicyConstants; import android.view.animation.Animation; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; +import com.android.server.wm.DisplayFrames; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -125,62 +133,26 @@ import java.lang.annotation.RetentionPolicy; * acquired by the window manager while it holds the window lock, so this is * even more restrictive than <var>Lw</var>. * </dl> - * - * @hide */ -public interface WindowManagerPolicy { - // Policy flags. These flags are also defined in frameworks/base/include/ui/Input.h. - public final static int FLAG_WAKE = 0x00000001; - public final static int FLAG_VIRTUAL = 0x00000002; - - public final static int FLAG_INJECTED = 0x01000000; - public final static int FLAG_TRUSTED = 0x02000000; - public final static int FLAG_FILTERED = 0x04000000; - public final static int FLAG_DISABLE_KEY_REPEAT = 0x08000000; - - public final static int FLAG_INTERACTIVE = 0x20000000; - public final static int FLAG_PASS_TO_USER = 0x40000000; - - // Flags for IActivityManager.keyguardGoingAway() - public final static int KEYGUARD_GOING_AWAY_FLAG_TO_SHADE = 1 << 0; - public final static int KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS = 1 << 1; - public final static int KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER = 1 << 2; - - // Flags used for indicating whether the internal and/or external input devices - // of some type are available. - public final static int PRESENCE_INTERNAL = 1 << 0; - public final static int PRESENCE_EXTERNAL = 1 << 1; - +public interface WindowManagerPolicy extends WindowManagerPolicyConstants { // Navigation bar position values int NAV_BAR_LEFT = 1 << 0; int NAV_BAR_RIGHT = 1 << 1; int NAV_BAR_BOTTOM = 1 << 2; - public final static boolean WATCH_POINTER = false; - - /** - * Sticky broadcast of the current HDMI plugged state. - */ - public final static String ACTION_HDMI_PLUGGED = "android.intent.action.HDMI_PLUGGED"; - - /** - * Extra in {@link #ACTION_HDMI_PLUGGED} indicating the state: true if - * plugged in to HDMI, false if not. - */ - public final static String EXTRA_HDMI_PLUGGED_STATE = "state"; - - /** - * Set to {@code true} when intent was invoked from pressing the home key. - * @hide - */ - @SystemApi - public static final String EXTRA_FROM_HOME_KEY = "android.intent.extra.FROM_HOME_KEY"; - /** * Pass this event to the user / app. To be returned from * {@link #interceptKeyBeforeQueueing}. */ - public final static int ACTION_PASS_TO_USER = 0x00000001; + int ACTION_PASS_TO_USER = 0x00000001; + /** Layout state may have changed (so another layout will be performed) */ + int FINISH_LAYOUT_REDO_LAYOUT = 0x0001; + /** Configuration state may have changed */ + int FINISH_LAYOUT_REDO_CONFIG = 0x0002; + /** Wallpaper may need to move */ + int FINISH_LAYOUT_REDO_WALLPAPER = 0x0004; + /** Need to recompute animations */ + int FINISH_LAYOUT_REDO_ANIM = 0x0008; /** * Register shortcuts for window manager to dispatch. @@ -241,7 +213,7 @@ public interface WindowManagerPolicy { */ public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overlayFrame, Rect contentFrame, Rect visibleFrame, Rect decorFrame, - Rect stableFrame, Rect outsetFrame); + Rect stableFrame, @Nullable Rect outsetFrame); /** * Retrieve the current frame of the window that has been assigned by @@ -483,7 +455,7 @@ public interface WindowManagerPolicy { /** * Returns true if the window owner can add internal system windows. - * That is, they have {@link permission#INTERNAL_SYSTEM_WINDOW}. + * That is, they have {@link Manifest.permission#INTERNAL_SYSTEM_WINDOW}. */ default boolean canAddInternalSystemWindow() { return false; @@ -491,7 +463,7 @@ public interface WindowManagerPolicy { /** * Returns true if the window owner has the permission to acquire a sleep token when it's - * visible. That is, they have the permission {@link permission#DEVICE_POWER}. + * visible. That is, they have the permission {@link Manifest.permission#DEVICE_POWER}. */ boolean canAcquireSleepToken(); } @@ -648,24 +620,6 @@ public interface WindowManagerPolicy { } } - public interface PointerEventListener { - /** - * 1. onPointerEvent will be called on the service.UiThread. - * 2. motionEvent will be recycled after onPointerEvent returns so if it is needed later a - * copy() must be made and the copy must be recycled. - **/ - void onPointerEvent(MotionEvent motionEvent); - - /** - * @see #onPointerEvent(MotionEvent) - **/ - default void onPointerEvent(MotionEvent motionEvent, int displayId) { - if (displayId == DEFAULT_DISPLAY) { - onPointerEvent(motionEvent); - } - } - } - /** Window has been added to the screen. */ public static final int TRANSIT_ENTER = 1; /** Window has been removed from the screen. */ @@ -682,13 +636,6 @@ public interface WindowManagerPolicy { // NOTE: screen off reasons are in order of significance, with more // important ones lower than less important ones. - /** Screen turned off because of a device admin */ - public final int OFF_BECAUSE_OF_ADMIN = 1; - /** Screen turned off because of power button */ - public final int OFF_BECAUSE_OF_USER = 2; - /** Screen turned off because of timeout */ - public final int OFF_BECAUSE_OF_TIMEOUT = 3; - /** @hide */ @IntDef({USER_ROTATION_FREE, USER_ROTATION_LOCKED}) @Retention(RetentionPolicy.SOURCE) @@ -806,7 +753,7 @@ public interface WindowManagerPolicy { * @param type The type of window being assigned. * @param canAddInternalSystemWindow If the owner window associated with the type we are * evaluating can add internal system windows. I.e they have - * {@link permission#INTERNAL_SYSTEM_WINDOW}. If true, alert window + * {@link Manifest.permission#INTERNAL_SYSTEM_WINDOW}. If true, alert window * types {@link android.view.WindowManager.LayoutParams#isSystemAlertWindowType(int)} * can be assigned layers greater than the layer for * {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} Else, their @@ -861,11 +808,11 @@ public interface WindowManagerPolicy { case TYPE_INPUT_METHOD_DIALOG: // on-screen keyboards and other such input method user interfaces go here. return 15; - case TYPE_STATUS_BAR_SUB_PANEL: - return 17; case TYPE_STATUS_BAR: - return 18; + return 17; case TYPE_STATUS_BAR_PANEL: + return 18; + case TYPE_STATUS_BAR_SUB_PANEL: return 19; case TYPE_KEYGUARD_DIALOG: return 20; @@ -916,13 +863,6 @@ public interface WindowManagerPolicy { } } - int APPLICATION_LAYER = 2; - int APPLICATION_MEDIA_SUBLAYER = -2; - int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1; - int APPLICATION_PANEL_SUBLAYER = 1; - int APPLICATION_SUB_PANEL_SUBLAYER = 2; - int APPLICATION_ABOVE_SUB_PANEL_SUBLAYER = 3; - /** * Return how to Z-order sub-windows in relation to the window they are attached to. * Return positive to have them ordered in front, negative for behind. @@ -975,7 +915,7 @@ public interface WindowManagerPolicy { /** * Return the available screen width that we should report for the * configuration. This must be no larger than - * {@link #getNonDecorDisplayWidth(int, int, int)}; it may be smaller than + * {@link #getNonDecorDisplayWidth(int, int, int, int int, int)}; it may be smaller than * that to account for more transient decoration like a status bar. */ public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation, @@ -984,7 +924,7 @@ public interface WindowManagerPolicy { /** * Return the available screen height that we should report for the * configuration. This must be no larger than - * {@link #getNonDecorDisplayHeight(int, int, int)}; it may be smaller than + * {@link #getNonDecorDisplayHeight(int, int, int, int, int)}; it may be smaller than * that to account for more transient decoration like a status bar. */ public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation, @@ -1213,15 +1153,6 @@ public interface WindowManagerPolicy { return false; } - /** Layout state may have changed (so another layout will be performed) */ - static final int FINISH_LAYOUT_REDO_LAYOUT = 0x0001; - /** Configuration state may have changed */ - static final int FINISH_LAYOUT_REDO_CONFIG = 0x0002; - /** Wallpaper may need to move */ - static final int FINISH_LAYOUT_REDO_WALLPAPER = 0x0004; - /** Need to recompute animations */ - static final int FINISH_LAYOUT_REDO_ANIM = 0x0008; - /** * Called following layout of all windows before each window has policy applied. * @@ -1374,7 +1305,7 @@ public interface WindowManagerPolicy { public void enableKeyguard(boolean enabled); /** - * Callback used by {@link WindowManagerPolicy#exitKeyguardSecurely} + * Callback used by {@link #exitKeyguardSecurely} */ interface OnKeyguardExitResult { void onKeyguardExitResult(boolean success); @@ -1552,8 +1483,8 @@ public interface WindowManagerPolicy { * * @return The rotation mode. * - * @see WindowManagerPolicy#USER_ROTATION_LOCKED - * @see WindowManagerPolicy#USER_ROTATION_FREE + * @see #USER_ROTATION_LOCKED + * @see #USER_ROTATION_FREE */ @UserRotationMode public int getUserRotationMode(); @@ -1561,8 +1492,7 @@ public interface WindowManagerPolicy { /** * Inform the policy that the user has chosen a preferred orientation ("rotation lock"). * - * @param mode One of {@link WindowManagerPolicy#USER_ROTATION_LOCKED} or - * {@link WindowManagerPolicy#USER_ROTATION_FREE}. + * @param mode One of {@link #USER_ROTATION_LOCKED} or {@link #USER_ROTATION_FREE}. * @param rotation One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90}, * {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}. */ @@ -1663,14 +1593,6 @@ public interface WindowManagerPolicy { void writeToProto(ProtoOutputStream proto, long fieldId); /** - * Returns whether a given window type can be magnified. - * - * @param windowType The window type. - * @return True if the window can be magnified. - */ - public boolean canMagnifyWindow(int windowType); - - /** * Returns whether a given window type is considered a top level one. * A top level window does not have a container, i.e. attached window, * or if it has a container it is laid out as a top-level window, not @@ -1765,20 +1687,4 @@ public interface WindowManagerPolicy { return Integer.toString(mode); } } - - /** - * Convert the off reason to a human readable format. - */ - static String offReasonToString(int why) { - switch (why) { - case OFF_BECAUSE_OF_ADMIN: - return "OFF_BECAUSE_OF_ADMIN"; - case OFF_BECAUSE_OF_USER: - return "OFF_BECAUSE_OF_USER"; - case OFF_BECAUSE_OF_TIMEOUT: - return "OFF_BECAUSE_OF_TIMEOUT"; - default: - return Integer.toString(why); - } - } } diff --git a/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index 70cd54ff..58002bc8 100644 --- a/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -15,14 +15,14 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; import android.util.Slog; -import android.view.WindowManagerPolicy; -import android.view.WindowManagerPolicy.OnKeyguardExitResult; +import android.view.WindowManagerPolicyConstants; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardService; import com.android.server.UiThread; +import com.android.server.policy.WindowManagerPolicy.OnKeyguardExitResult; import java.io.PrintWriter; @@ -419,7 +419,7 @@ public class KeyguardServiceDelegate { pw.println(prefix + "deviceHasKeyguard=" + mKeyguardState.deviceHasKeyguard); pw.println(prefix + "enabled=" + mKeyguardState.enabled); pw.println(prefix + "offReason=" + - WindowManagerPolicy.offReasonToString(mKeyguardState.offReason)); + WindowManagerPolicyConstants.offReasonToString(mKeyguardState.offReason)); pw.println(prefix + "currentUser=" + mKeyguardState.currentUser); pw.println(prefix + "bootCompleted=" + mKeyguardState.bootCompleted); pw.println(prefix + "screenState=" + screenStateToString(mKeyguardState.screenState)); diff --git a/com/android/server/power/BatterySaverPolicy.java b/com/android/server/power/BatterySaverPolicy.java index 15121b80..7c234f96 100644 --- a/com/android/server/power/BatterySaverPolicy.java +++ b/com/android/server/power/BatterySaverPolicy.java @@ -29,21 +29,26 @@ import android.util.ArrayMap; import android.util.KeyValueListParser; import android.util.Slog; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.R; +import com.android.server.power.batterysaver.CpuFrequencies; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; /** * Class to decide whether to turn on battery saver mode for specific service * - * Test: atest BatterySaverPolicyTest + * Test: + atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java */ public class BatterySaverPolicy extends ContentObserver { private static final String TAG = "BatterySaverPolicy"; + public static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE. + // Value of batterySaverGpsMode such that GPS isn't affected by battery saver mode. public static final int GPS_MODE_NO_CHANGE = 0; // Value of batterySaverGpsMode such that GPS is disabled when battery saver mode @@ -59,19 +64,27 @@ public class BatterySaverPolicy extends ContentObserver { private static final String KEY_FIREWALL_DISABLED = "firewall_disabled"; private static final String KEY_ADJUST_BRIGHTNESS_DISABLED = "adjust_brightness_disabled"; private static final String KEY_DATASAVER_DISABLED = "datasaver_disabled"; + private static final String KEY_LAUNCH_BOOST_DISABLED = "launch_boost_disabled"; private static final String KEY_ADJUST_BRIGHTNESS_FACTOR = "adjust_brightness_factor"; private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred"; private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred"; - private static final String KEY_FORCE_ALL_APPS_STANDBY_JOBS = "force_all_apps_standby_jobs"; - private static final String KEY_FORCE_ALL_APPS_STANDBY_ALARMS = "force_all_apps_standby_alarms"; + private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby"; + private static final String KEY_FORCE_BACKGROUND_CHECK = "force_background_check"; private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled"; - private static final String KEY_SCREEN_ON_FILE_PREFIX = "file-on:"; - private static final String KEY_SCREEN_OFF_FILE_PREFIX = "file-off:"; + private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i"; + private static final String KEY_CPU_FREQ_NONINTERACTIVE = "cpufreq-n"; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private String mSettings; + + @GuardedBy("mLock") + private String mDeviceSpecificSettings; - private static String mSettings; - private static String mDeviceSpecificSettings; - private static String mDeviceSpecificSettingsSource; // For dump() only. + @GuardedBy("mLock") + private String mDeviceSpecificSettingsSource; // For dump() only. /** * {@code true} if vibration is disabled in battery saver mode. @@ -79,6 +92,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_VIBRATION_DISABLED */ + @GuardedBy("mLock") private boolean mVibrationDisabled; /** @@ -87,6 +101,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_ANIMATION_DISABLED */ + @GuardedBy("mLock") private boolean mAnimationDisabled; /** @@ -96,6 +111,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_SOUNDTRIGGER_DISABLED */ + @GuardedBy("mLock") private boolean mSoundTriggerDisabled; /** @@ -104,6 +120,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_FULLBACKUP_DEFERRED */ + @GuardedBy("mLock") private boolean mFullBackupDeferred; /** @@ -112,6 +129,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_KEYVALUE_DEFERRED */ + @GuardedBy("mLock") private boolean mKeyValueBackupDeferred; /** @@ -120,6 +138,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_FIREWALL_DISABLED */ + @GuardedBy("mLock") private boolean mFireWallDisabled; /** @@ -128,6 +147,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_ADJUST_BRIGHTNESS_DISABLED */ + @GuardedBy("mLock") private boolean mAdjustBrightnessDisabled; /** @@ -136,14 +156,22 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_DATASAVER_DISABLED */ + @GuardedBy("mLock") private boolean mDataSaverDisabled; /** + * {@code true} if launch boost should be disabled on battery saver. + */ + @GuardedBy("mLock") + private boolean mLaunchBoostDisabled; + + /** * This is the flag to decide the gps mode in battery saver mode. * * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_GPS_MODE */ + @GuardedBy("mLock") private int mGpsMode; /** @@ -153,25 +181,27 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_ADJUST_BRIGHTNESS_FACTOR */ + @GuardedBy("mLock") private float mAdjustBrightnessFactor; /** - * Whether to put all apps in the stand-by mode or not for job scheduler. + * Whether to put all apps in the stand-by mode. */ - private boolean mForceAllAppsStandbyJobs; + @GuardedBy("mLock") + private boolean mForceAllAppsStandby; /** - * Whether to put all apps in the stand-by mode or not for alarms. + * Whether to put all apps in the stand-by mode. */ - private boolean mForceAllAppsStandbyAlarms; + @GuardedBy("mLock") + private boolean mForceBackgroundCheck; /** * Weather to show non-essential sensors (e.g. edge sensors) or not. */ + @GuardedBy("mLock") private boolean mOptionalSensorsDisabled; - private final Object mLock = new Object(); - @GuardedBy("mLock") private Context mContext; @@ -179,25 +209,25 @@ public class BatterySaverPolicy extends ContentObserver { private ContentResolver mContentResolver; @GuardedBy("mLock") - private final ArrayList<BatterySaverPolicyListener> mListeners = new ArrayList<>(); + private final List<BatterySaverPolicyListener> mListeners = new ArrayList<>(); /** * List of [Filename -> content] that should be written when battery saver is activated - * and the screen is on. + * and the device is interactive. * * We use this to change the max CPU frequencies. */ @GuardedBy("mLock") - private ArrayMap<String, String> mScreenOnFiles; + private ArrayMap<String, String> mFilesForInteractive; /** * List of [Filename -> content] that should be written when battery saver is activated - * and the screen is off. + * and the device is non-interactive. * * We use this to change the max CPU frequencies. */ @GuardedBy("mLock") - private ArrayMap<String, String> mScreenOffFiles; + private ArrayMap<String, String> mFilesForNoninteractive; public interface BatterySaverPolicyListener { void onBatterySaverPolicyChanged(BatterySaverPolicy policy); @@ -228,7 +258,11 @@ public class BatterySaverPolicy extends ContentObserver { @VisibleForTesting String getGlobalSetting(String key) { - return Settings.Global.getString(mContentResolver, key); + final ContentResolver cr; + synchronized (mLock) { + cr = mContentResolver; + } + return Settings.Global.getString(cr, key); } @VisibleForTesting @@ -236,11 +270,6 @@ public class BatterySaverPolicy extends ContentObserver { return R.string.config_batterySaverDeviceSpecificConfig; } - @VisibleForTesting - void onChangeForTest() { - onChange(true, null); - } - @Override public void onChange(boolean selfChange, Uri uri) { final BatterySaverPolicyListener[] listeners; @@ -279,6 +308,11 @@ public class BatterySaverPolicy extends ContentObserver { mSettings = setting; mDeviceSpecificSettings = deviceSpecificSetting; + if (DEBUG) { + Slog.i(TAG, "mSettings=" + mSettings); + Slog.i(TAG, "mDeviceSpecificSettings=" + mDeviceSpecificSettings); + } + final KeyValueListParser parser = new KeyValueListParser(','); // Non-device-specific parameters. @@ -297,9 +331,9 @@ public class BatterySaverPolicy extends ContentObserver { mAdjustBrightnessDisabled = parser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false); mAdjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f); mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true); - mForceAllAppsStandbyJobs = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_JOBS, true); - mForceAllAppsStandbyAlarms = - parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_ALARMS, true); + mLaunchBoostDisabled = parser.getBoolean(KEY_LAUNCH_BOOST_DISABLED, true); + mForceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, true); + mForceBackgroundCheck = parser.getBoolean(KEY_FORCE_BACKGROUND_CHECK, true); mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true); // Get default value from Settings.Secure @@ -315,29 +349,11 @@ public class BatterySaverPolicy extends ContentObserver { + deviceSpecificSetting); } - mScreenOnFiles = collectParams(parser, KEY_SCREEN_ON_FILE_PREFIX); - mScreenOffFiles = collectParams(parser, KEY_SCREEN_OFF_FILE_PREFIX); - } - - private static ArrayMap<String, String> collectParams( - KeyValueListParser parser, String prefix) { - final ArrayMap<String, String> ret = new ArrayMap<>(); - - for (int i = parser.size() - 1; i >= 0; i--) { - final String key = parser.keyAt(i); - if (!key.startsWith(prefix)) { - continue; - } - final String path = key.substring(prefix.length()); - - if (!(path.startsWith("/sys/") || path.startsWith("/proc"))) { - Slog.wtf(TAG, "Invalid path: " + path); - continue; - } + mFilesForInteractive = (new CpuFrequencies()).parseString( + parser.getString(KEY_CPU_FREQ_INTERACTIVE, "")).toSysFileMap(); - ret.put(path, parser.getString(key, "")); - } - return ret; + mFilesForNoninteractive = (new CpuFrequencies()).parseString( + parser.getString(KEY_CPU_FREQ_NONINTERACTIVE, "")).toSysFileMap(); } /** @@ -387,11 +403,11 @@ public class BatterySaverPolicy extends ContentObserver { case ServiceType.VIBRATION: return builder.setBatterySaverEnabled(mVibrationDisabled) .build(); - case ServiceType.FORCE_ALL_APPS_STANDBY_JOBS: - return builder.setBatterySaverEnabled(mForceAllAppsStandbyJobs) + case ServiceType.FORCE_ALL_APPS_STANDBY: + return builder.setBatterySaverEnabled(mForceAllAppsStandby) .build(); - case ServiceType.FORCE_ALL_APPS_STANDBY_ALARMS: - return builder.setBatterySaverEnabled(mForceAllAppsStandbyAlarms) + case ServiceType.FORCE_BACKGROUND_CHECK: + return builder.setBatterySaverEnabled(mForceBackgroundCheck) .build(); case ServiceType.OPTIONAL_SENSORS: return builder.setBatterySaverEnabled(mOptionalSensorsDisabled) @@ -403,9 +419,15 @@ public class BatterySaverPolicy extends ContentObserver { } } - public ArrayMap<String, String> getFileValues(boolean screenOn) { + public ArrayMap<String, String> getFileValues(boolean interactive) { + synchronized (mLock) { + return interactive ? mFilesForInteractive : mFilesForNoninteractive; + } + } + + public boolean isLaunchBoostDisabled() { synchronized (mLock) { - return screenOn ? mScreenOnFiles : mScreenOffFiles; + return mLaunchBoostDisabled; } } @@ -413,10 +435,10 @@ public class BatterySaverPolicy extends ContentObserver { synchronized (mLock) { pw.println(); pw.println("Battery saver policy"); - pw.println(" Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS); - pw.println(" value: " + mSettings); - pw.println(" Settings " + mDeviceSpecificSettingsSource); - pw.println(" value: " + mDeviceSpecificSettings); + pw.println(" Settings: " + Settings.Global.BATTERY_SAVER_CONSTANTS); + pw.println(" value: " + mSettings); + pw.println(" Settings: " + mDeviceSpecificSettingsSource); + pw.println(" value: " + mDeviceSpecificSettings); pw.println(); pw.println(" " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled); @@ -425,20 +447,21 @@ public class BatterySaverPolicy extends ContentObserver { pw.println(" " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred); pw.println(" " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled); pw.println(" " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled); + pw.println(" " + KEY_LAUNCH_BOOST_DISABLED + "=" + mLaunchBoostDisabled); pw.println(" " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled); pw.println(" " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor); pw.println(" " + KEY_GPS_MODE + "=" + mGpsMode); - pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY_JOBS + "=" + mForceAllAppsStandbyJobs); - pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY_ALARMS + "=" + mForceAllAppsStandbyAlarms); + pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY + "=" + mForceAllAppsStandby); + pw.println(" " + KEY_FORCE_BACKGROUND_CHECK + "=" + mForceBackgroundCheck); pw.println(" " + KEY_OPTIONAL_SENSORS_DISABLED + "=" + mOptionalSensorsDisabled); pw.println(); - pw.print(" Screen On Files:\n"); - dumpMap(pw, " ", mScreenOnFiles); + pw.print(" Interactive File values:\n"); + dumpMap(pw, " ", mFilesForInteractive); pw.println(); - pw.print(" Screen Off Files:\n"); - dumpMap(pw, " ", mScreenOffFiles); + pw.print(" Noninteractive File values:\n"); + dumpMap(pw, " ", mFilesForNoninteractive); pw.println(); } } diff --git a/com/android/server/power/Notifier.java b/com/android/server/power/Notifier.java index 0ecf0e1e..8ee26f29 100644 --- a/com/android/server/power/Notifier.java +++ b/com/android/server/power/Notifier.java @@ -25,6 +25,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.EventLogTags; import com.android.server.LocalServices; +import com.android.server.policy.WindowManagerPolicy; import android.content.BroadcastReceiver; import android.content.Context; @@ -49,7 +50,6 @@ import android.os.WorkSource; import android.provider.Settings; import android.util.EventLog; import android.util.Slog; -import android.view.WindowManagerPolicy; import android.view.inputmethod.InputMethodManagerInternal; /** diff --git a/com/android/server/power/PowerManagerService.java b/com/android/server/power/PowerManagerService.java index a47b8095..7f1a534c 100644 --- a/com/android/server/power/PowerManagerService.java +++ b/com/android/server/power/PowerManagerService.java @@ -69,7 +69,6 @@ import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.view.Display; -import android.view.WindowManagerPolicy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IAppOpsService; @@ -90,6 +89,7 @@ import com.android.server.Watchdog; import com.android.server.am.BatteryStatsService; import com.android.server.lights.Light; import com.android.server.lights.LightsManager; +import com.android.server.policy.WindowManagerPolicy; import com.android.server.power.batterysaver.BatterySaverController; import libcore.util.Objects; @@ -1457,6 +1457,10 @@ public final class PowerManagerService extends SystemService case PowerManager.GO_TO_SLEEP_REASON_HDMI: Slog.i(TAG, "Going to sleep due to HDMI standby (uid " + uid +")..."); break; + case PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY: + Slog.i(TAG, "Going to sleep by an accessibility service request (uid " + + uid +")..."); + break; default: Slog.i(TAG, "Going to sleep by application request (uid " + uid +")..."); reason = PowerManager.GO_TO_SLEEP_REASON_APPLICATION; @@ -3105,7 +3109,16 @@ public final class PowerManagerService extends SystemService mIsVrModeEnabled = enabled; } - public static void powerHintInternal(int hintId, int data) { + private void powerHintInternal(int hintId, int data) { + // Maybe filter the event. + switch (hintId) { + case PowerHint.LAUNCH: // 1: activate launch boost 0: deactivate. + if (data == 1 && mBatterySaverController.isLaunchBoostDisabled()) { + return; + } + break; + } + nativeSendPowerHint(hintId, data); } diff --git a/com/android/server/power/ShutdownThread.java b/com/android/server/power/ShutdownThread.java index 6bf725e1..6fb345bc 100644 --- a/com/android/server/power/ShutdownThread.java +++ b/com/android/server/power/ShutdownThread.java @@ -20,10 +20,7 @@ package com.android.server.power; import android.app.AlertDialog; import android.app.Dialog; import android.app.IActivityManager; -import android.app.KeyguardManager; import android.app.ProgressDialog; -import android.app.WallpaperColors; -import android.app.WallpaperManager; import android.bluetooth.BluetoothAdapter; import android.bluetooth.IBluetoothManager; import android.content.BroadcastReceiver; @@ -31,8 +28,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; import android.media.AudioAttributes; import android.os.FileUtils; import android.os.Handler; @@ -47,8 +42,6 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.Vibrator; -import android.os.storage.IStorageManager; -import android.os.storage.IStorageShutdownObserver; import android.util.ArrayMap; import android.util.Log; import android.util.TimingsTraceLog; @@ -123,7 +116,6 @@ public final class ShutdownThread extends Thread { private static String METRIC_RADIOS = "shutdown_radios"; private static String METRIC_BT = "shutdown_bt"; private static String METRIC_RADIO = "shutdown_radio"; - private static String METRIC_SM = "shutdown_storage_manager"; private final Object mActionDoneSync = new Object(); private boolean mActionDone; @@ -526,54 +518,6 @@ public final class ShutdownThread extends Thread { shutdownTimingLog.traceEnd(); // ShutdownRadios metricEnded(METRIC_RADIOS); - // Shutdown StorageManagerService to ensure media is in a safe state - IStorageShutdownObserver observer = new IStorageShutdownObserver.Stub() { - public void onShutDownComplete(int statusCode) throws RemoteException { - Log.w(TAG, "Result code " + statusCode + " from StorageManagerService.shutdown"); - actionDone(); - } - }; - - Log.i(TAG, "Shutting down StorageManagerService"); - shutdownTimingLog.traceBegin("ShutdownStorageManager"); - metricStarted(METRIC_SM); - - // Set initial variables and time out time. - mActionDone = false; - final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME; - synchronized (mActionDoneSync) { - try { - final IStorageManager storageManager = IStorageManager.Stub.asInterface( - ServiceManager.checkService("mount")); - if (storageManager != null) { - storageManager.shutdown(observer); - } else { - Log.w(TAG, "StorageManagerService unavailable for shutdown"); - } - } catch (Exception e) { - Log.e(TAG, "Exception during StorageManagerService shutdown", e); - } - while (!mActionDone) { - long delay = endShutTime - SystemClock.elapsedRealtime(); - if (delay <= 0) { - Log.w(TAG, "StorageManager shutdown wait timed out"); - break; - } else if (mRebootHasProgressBar) { - int status = (int)((MAX_SHUTDOWN_WAIT_TIME - delay) * 1.0 * - (MOUNT_SERVICE_STOP_PERCENT - RADIO_STOP_PERCENT) / - MAX_SHUTDOWN_WAIT_TIME); - status += RADIO_STOP_PERCENT; - sInstance.setRebootProgress(status, null); - } - try { - mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS)); - } catch (InterruptedException e) { - } - } - } - shutdownTimingLog.traceEnd(); // ShutdownStorageManager - metricEnded(METRIC_SM); - if (mRebootHasProgressBar) { sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null); @@ -585,6 +529,7 @@ public final class ShutdownThread extends Thread { shutdownTimingLog.traceEnd(); // SystemServerShutdown metricEnded(METRIC_SYSTEM_SERVER); saveMetrics(mReboot); + // Remaining work will be done by init, including vold shutdown rebootOrShutdown(mContext, mReboot, mReason); } diff --git a/com/android/server/power/batterysaver/BatterySaverController.java b/com/android/server/power/batterysaver/BatterySaverController.java index b3e85383..80bc9359 100644 --- a/com/android/server/power/batterysaver/BatterySaverController.java +++ b/com/android/server/power/batterysaver/BatterySaverController.java @@ -16,6 +16,7 @@ package com.android.server.power.batterysaver; import android.Manifest; +import android.app.ActivityManagerInternal; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -25,6 +26,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PowerManager; +import android.os.PowerManagerInternal; import android.os.PowerManagerInternal.LowPowerModeListener; import android.os.PowerSaveState; import android.os.UserHandle; @@ -33,7 +35,9 @@ import android.util.Slog; import android.widget.Toast; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; +import com.android.server.LocalServices; import com.android.server.power.BatterySaverPolicy; import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener; import com.android.server.power.PowerManagerService; @@ -46,7 +50,7 @@ import java.util.ArrayList; public class BatterySaverController implements BatterySaverPolicyListener { static final String TAG = "BatterySaverController"; - static final boolean DEBUG = false; // DO NOT MERGE WITH TRUE + static final boolean DEBUG = BatterySaverPolicy.DEBUG; private final Object mLock = new Object(); private final Context mContext; @@ -63,20 +67,17 @@ public class BatterySaverController implements BatterySaverPolicyListener { @GuardedBy("mLock") private boolean mEnabled; - /** - * Keep track of the previous enabled state, which we use to decide when to send broadcasts, - * which we don't want to send only when the screen state changes. - */ - @GuardedBy("mLock") - private boolean mWasEnabled; - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case Intent.ACTION_SCREEN_ON: case Intent.ACTION_SCREEN_OFF: - mHandler.postStateChanged(); + if (!isEnabled()) { + return; // No need to send it if not enabled. + } + // Don't send the broadcast, because we never did so in this case. + mHandler.postStateChanged(/*sendBroadcast=*/ false); break; } } @@ -109,6 +110,9 @@ public class BatterySaverController implements BatterySaverPolicyListener { final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); mContext.registerReceiver(mReceiver, filter); + + mFileUpdater.systemReady(LocalServices.getService(ActivityManagerInternal.class) + .isRuntimeRestarted()); } private PowerManager getPowerManager() { @@ -121,25 +125,32 @@ public class BatterySaverController implements BatterySaverPolicyListener { @Override public void onBatterySaverPolicyChanged(BatterySaverPolicy policy) { - mHandler.postStateChanged(); + if (!isEnabled()) { + return; // No need to send it if not enabled. + } + mHandler.postStateChanged(/*sendBroadcast=*/ true); } private class MyHandler extends Handler { - private final int MSG_STATE_CHANGED = 1; + private static final int MSG_STATE_CHANGED = 1; + + private static final int ARG_DONT_SEND_BROADCAST = 0; + private static final int ARG_SEND_BROADCAST = 1; public MyHandler(Looper looper) { super(looper); } - public void postStateChanged() { - obtainMessage(MSG_STATE_CHANGED).sendToTarget(); + public void postStateChanged(boolean sendBroadcast) { + obtainMessage(MSG_STATE_CHANGED, sendBroadcast ? + ARG_SEND_BROADCAST : ARG_DONT_SEND_BROADCAST, 0).sendToTarget(); } @Override public void dispatchMessage(Message msg) { switch (msg.what) { case MSG_STATE_CHANGED: - handleBatterySaverStateChanged(); + handleBatterySaverStateChanged(msg.arg1 == ARG_SEND_BROADCAST); break; } } @@ -155,53 +166,75 @@ public class BatterySaverController implements BatterySaverPolicyListener { } mEnabled = enable; - mHandler.postStateChanged(); + mHandler.postStateChanged(/*sendBroadcast=*/ true); + } + } + + /** @return whether battery saver is enabled or not. */ + boolean isEnabled() { + synchronized (mLock) { + return mEnabled; } } /** + * @return true if launch boost should currently be disabled. + */ + public boolean isLaunchBoostDisabled() { + return isEnabled() && mBatterySaverPolicy.isLaunchBoostDisabled(); + } + + /** * Dispatch power save events to the listeners. * - * This is always called on the handler thread. + * This method is always called on the handler thread. + * + * This method is called only in the following cases: + * - When battery saver becomes activated. + * - When battery saver becomes deactivated. + * - When battery saver is on the interactive state changes. + * - When battery saver is on the battery saver policy changes. */ - void handleBatterySaverStateChanged() { + void handleBatterySaverStateChanged(boolean sendBroadcast) { final LowPowerModeListener[] listeners; - final boolean wasEnabled; final boolean enabled; - final boolean isScreenOn = getPowerManager().isInteractive(); + final boolean isInteractive = getPowerManager().isInteractive(); final ArrayMap<String, String> fileValues; synchronized (mLock) { - Slog.i(TAG, "Battery saver enabled: screen on=" + isScreenOn); + Slog.i(TAG, "Battery saver " + (mEnabled ? "enabled" : "disabled") + + ": isInteractive=" + isInteractive); listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]); - wasEnabled = mWasEnabled; enabled = mEnabled; if (enabled) { - fileValues = mBatterySaverPolicy.getFileValues(isScreenOn); + fileValues = mBatterySaverPolicy.getFileValues(isInteractive); } else { fileValues = null; } } - PowerManagerService.powerHintInternal(PowerHint.LOW_POWER, enabled ? 1 : 0); - - if (enabled) { - // STOPSHIP Remove the toast. - Toast.makeText(mContext, - com.android.internal.R.string.battery_saver_warning, - Toast.LENGTH_LONG).show(); + final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); + if (pmi != null) { + pmi.powerHint(PowerHint.LOW_POWER, enabled ? 1 : 0); } - if (fileValues == null || fileValues.size() == 0) { + if (ArrayUtils.isEmpty(fileValues)) { mFileUpdater.restoreDefault(); } else { mFileUpdater.writeFiles(fileValues); } - if (enabled != wasEnabled) { + if (sendBroadcast) { + if (enabled) { + // STOPSHIP Remove the toast. + Toast.makeText(mContext, + com.android.internal.R.string.battery_saver_warning, + Toast.LENGTH_LONG).show(); + } + if (DEBUG) { Slog.i(TAG, "Sending broadcasts for mode: " + enabled); } @@ -231,9 +264,5 @@ public class BatterySaverController implements BatterySaverPolicyListener { listener.onLowPowerModeChanged(result); } } - - synchronized (mLock) { - mWasEnabled = enabled; - } } } diff --git a/com/android/server/power/batterysaver/CpuFrequencies.java b/com/android/server/power/batterysaver/CpuFrequencies.java new file mode 100644 index 00000000..1629486b --- /dev/null +++ b/com/android/server/power/batterysaver/CpuFrequencies.java @@ -0,0 +1,101 @@ +/* + * 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 com.android.server.power.batterysaver; + +import android.util.ArrayMap; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; + +import java.util.Map; + + +/** + * Helper to parse a list of "core-number:frequency" pairs concatenated with / as a separator, + * and convert them into a map of "filename -> value" that should be written to + * /sys/.../scaling_max_freq. + * + * Example input: "0:1900800/4:2500000", which will be converted into: + * "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq" "1900800" + * "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq" "2500000" + * + * Test: + atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java + */ +public class CpuFrequencies { + private static final String TAG = "CpuFrequencies"; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final ArrayMap<Integer, Long> mCoreAndFrequencies = new ArrayMap<>(); + + public CpuFrequencies() { + } + + /** + * Parse a string. + */ + public CpuFrequencies parseString(String cpuNumberAndFrequencies) { + synchronized (mLock) { + mCoreAndFrequencies.clear(); + try { + for (String pair : cpuNumberAndFrequencies.split("/")) { + final String[] coreAndFreq = pair.split(":", 2); + + if (coreAndFreq.length != 2) { + throw new IllegalArgumentException("Wrong format"); + } + final int core = Integer.parseInt(coreAndFreq[0]); + final long freq = Long.parseLong(coreAndFreq[1]); + + mCoreAndFrequencies.put(core, freq); + } + } catch (IllegalArgumentException e) { + Slog.wtf(TAG, "Invalid configuration: " + cpuNumberAndFrequencies, e); + } + } + return this; + } + + /** + * Return a new map containing the filename-value pairs. + */ + public ArrayMap<String, String> toSysFileMap() { + final ArrayMap<String, String> map = new ArrayMap<>(); + addToSysFileMap(map); + return map; + } + + /** + * Add the filename-value pairs to an existing map. + */ + public void addToSysFileMap(Map<String, String> map) { + synchronized (mLock) { + final int size = mCoreAndFrequencies.size(); + + for (int i = 0; i < size; i++) { + final int core = mCoreAndFrequencies.keyAt(i); + final long freq = mCoreAndFrequencies.valueAt(i); + + final String file = "/sys/devices/system/cpu/cpu" + Integer.toString(core) + + "/cpufreq/scaling_max_freq"; + + map.put(file, Long.toString(freq)); + } + } + } +} diff --git a/com/android/server/power/batterysaver/FileUpdater.java b/com/android/server/power/batterysaver/FileUpdater.java index cfe8fc49..e0ab9e93 100644 --- a/com/android/server/power/batterysaver/FileUpdater.java +++ b/com/android/server/power/batterysaver/FileUpdater.java @@ -16,40 +16,389 @@ package com.android.server.power.batterysaver; import android.content.Context; +import android.os.Environment; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemProperties; import android.util.ArrayMap; +import android.util.AtomicFile; import android.util.Slog; +import android.util.Xml; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; +import com.android.server.IoThread; + +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Map; /** * Used by {@link BatterySaverController} to write values to /sys/ (and possibly /proc/ too) files - * with retry and to restore the original values. + * with retries. It also support restoring to the file original values. * - * TODO Implement it + * Retries are needed because writing to "/sys/.../scaling_max_freq" returns EIO when the current + * frequency happens to be above the new max frequency. + * + * Test: + atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java */ public class FileUpdater { private static final String TAG = BatterySaverController.TAG; private static final boolean DEBUG = BatterySaverController.DEBUG; + /** + * If this system property is set to 1, it'll skip all file writes. This can be used when + * one needs to change max CPU frequency for benchmarking, for example. + */ + private static final String PROP_SKIP_WRITE = "debug.batterysaver.no_write_files"; + + private static final String TAG_DEFAULT_ROOT = "defaults"; + + // Don't do disk access with this lock held. private final Object mLock = new Object(); + private final Context mContext; + private final Handler mHandler; + + /** + * Filename -> value map that holds pending writes. + */ + @GuardedBy("mLock") + private final ArrayMap<String, String> mPendingWrites = new ArrayMap<>(); + + /** + * Filename -> value that holds the original value of each file. + */ + @GuardedBy("mLock") + private final ArrayMap<String, String> mDefaultValues = new ArrayMap<>(); + + /** Number of retries. We give up on writing after {@link #MAX_RETRIES} retries. */ + @GuardedBy("mLock") + private int mRetries = 0; + + private final int MAX_RETRIES; + + private final long RETRY_INTERVAL_MS; + + /** + * "Official" constructor. Don't use the other constructor in the production code. + */ public FileUpdater(Context context) { + this(context, IoThread.get().getLooper(), 10, 5000); + } + + /** + * Constructor for test. + */ + @VisibleForTesting + FileUpdater(Context context, Looper looper, int maxRetries, int retryIntervalMs) { mContext = context; + mHandler = new Handler(looper); + + MAX_RETRIES = maxRetries; + RETRY_INTERVAL_MS = retryIntervalMs; + } + + public void systemReady(boolean runtimeRestarted) { + synchronized (mLock) { + if (runtimeRestarted) { + // If it runtime restarted, read the original values from the disk and apply. + if (loadDefaultValuesLocked()) { + Slog.d(TAG, "Default values loaded after runtime restart; writing them..."); + restoreDefault(); + } + } else { + // Delete it, without checking the result. (file-not-exist is not an exception.) + injectDefaultValuesFilename().delete(); + } + } } + /** + * Write values to files. (Note the actual writes happen ASAP but asynchronously.) + */ public void writeFiles(ArrayMap<String, String> fileValues) { - if (DEBUG) { - final int size = fileValues.size(); - for (int i = 0; i < size; i++) { - Slog.d(TAG, "Writing '" + fileValues.valueAt(i) - + "' to '" + fileValues.keyAt(i) + "'"); + synchronized (mLock) { + for (int i = fileValues.size() - 1; i >= 0; i--) { + final String file = fileValues.keyAt(i); + final String value = fileValues.valueAt(i); + + if (DEBUG) { + Slog.d(TAG, "Scheduling write: '" + value + "' to '" + file + "'"); + } + + mPendingWrites.put(file, value); + } + mRetries = 0; + + mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable); + mHandler.post(mHandleWriteOnHandlerRunnable); } } + /** + * Restore the default values. + */ public void restoreDefault() { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "Resetting file default values."); + } + mPendingWrites.clear(); + + writeFiles(mDefaultValues); + } + } + + private Runnable mHandleWriteOnHandlerRunnable = () -> handleWriteOnHandler(); + + /** Convert map keys into a single string for debug messages. */ + private String getKeysString(Map<String, String> source) { + return new ArrayList<>(source.keySet()).toString(); + } + + /** Clone an ArrayMap. */ + private ArrayMap<String, String> cloneMap(ArrayMap<String, String> source) { + return new ArrayMap<>(source); + } + + /** + * Called on the handler and writes {@link #mPendingWrites} to the disk. + * + * When it about to write to each file for the first time, it'll read the file and store + * the original value in {@link #mDefaultValues}. + */ + private void handleWriteOnHandler() { + // We don't want to access the disk with the lock held, so copy the pending writes to + // a local map. + final ArrayMap<String, String> writes; + synchronized (mLock) { + if (mPendingWrites.size() == 0) { + return; + } + + if (DEBUG) { + Slog.d(TAG, "Writing files: (# retries=" + mRetries + ") " + + getKeysString(mPendingWrites)); + } + + writes = cloneMap(mPendingWrites); + } + + // Then write. + + boolean needRetry = false; + + final int size = writes.size(); + for (int i = 0; i < size; i++) { + final String file = writes.keyAt(i); + final String value = writes.valueAt(i); + + // Make sure the default value is loaded. + if (!ensureDefaultLoaded(file)) { + continue; + } + + // Write to the file. When succeeded, remove it from the pending list. + // Otherwise, schedule a retry. + try { + injectWriteToFile(file, value); + + removePendingWrite(file); + } catch (IOException e) { + needRetry = true; + } + } + if (needRetry) { + scheduleRetry(); + } + } + + private void removePendingWrite(String file) { + synchronized (mLock) { + mPendingWrites.remove(file); + } + } + + private void scheduleRetry() { + synchronized (mLock) { + if (mPendingWrites.size() == 0) { + return; // Shouldn't happen but just in case. + } + + mRetries++; + if (mRetries > MAX_RETRIES) { + doWtf("Gave up writing files: " + getKeysString(mPendingWrites)); + return; + } + + mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable); + mHandler.postDelayed(mHandleWriteOnHandlerRunnable, RETRY_INTERVAL_MS); + } + } + + /** + * Make sure {@link #mDefaultValues} has the default value loaded for {@code file}. + * + * @return true if the default value is loaded. false if the file cannot be read. + */ + private boolean ensureDefaultLoaded(String file) { + // Has the default already? + synchronized (mLock) { + if (mDefaultValues.containsKey(file)) { + return true; + } + } + final String originalValue; + try { + originalValue = injectReadFromFileTrimmed(file); + } catch (IOException e) { + // If the file is not readable, assume can't write too. + injectWtf("Unable to read from file", e); + + removePendingWrite(file); + return false; + } + synchronized (mLock) { + mDefaultValues.put(file, originalValue); + saveDefaultValuesLocked(); + } + return true; + } + + @VisibleForTesting + String injectReadFromFileTrimmed(String file) throws IOException { + return IoUtils.readFileAsString(file).trim(); + } + + @VisibleForTesting + void injectWriteToFile(String file, String value) throws IOException { + if (injectShouldSkipWrite()) { + Slog.i(TAG, "Skipped writing to '" + file + "'"); + return; + } + if (DEBUG) { + Slog.d(TAG, "Writing: '" + value + "' to '" + file + "'"); + } + try (FileWriter out = new FileWriter(file)) { + out.write(value); + } catch (IOException | RuntimeException e) { + Slog.w(TAG, "Failed writing '" + value + "' to '" + file + "': " + e.getMessage()); + throw e; + } + } + + private void saveDefaultValuesLocked() { + final AtomicFile file = new AtomicFile(injectDefaultValuesFilename()); + + FileOutputStream outs = null; + try { + file.getBaseFile().getParentFile().mkdirs(); + outs = file.startWrite(); + + // Write to XML + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(outs, StandardCharsets.UTF_8.name()); + out.startDocument(null, true); + out.startTag(null, TAG_DEFAULT_ROOT); + + XmlUtils.writeMapXml(mDefaultValues, out, null); + + // Epilogue. + out.endTag(null, TAG_DEFAULT_ROOT); + out.endDocument(); + + // Close. + file.finishWrite(outs); + } catch (IOException | XmlPullParserException | RuntimeException e) { + Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); + file.failWrite(outs); + } + } + + @VisibleForTesting + boolean loadDefaultValuesLocked() { + final AtomicFile file = new AtomicFile(injectDefaultValuesFilename()); if (DEBUG) { - Slog.d(TAG, "Resetting file default values"); + Slog.d(TAG, "Loading from " + file.getBaseFile()); } + Map<String, String> read = null; + try (FileInputStream in = file.openRead()) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, StandardCharsets.UTF_8.name()); + + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + // Check the root tag + final String tag = parser.getName(); + if (depth == 1) { + if (!TAG_DEFAULT_ROOT.equals(tag)) { + Slog.e(TAG, "Invalid root tag: " + tag); + return false; + } + continue; + } + final String[] tagName = new String[1]; + read = (ArrayMap<String, String>) XmlUtils.readThisArrayMapXml(parser, + TAG_DEFAULT_ROOT, tagName, null); + } + } catch (FileNotFoundException e) { + read = null; + } catch (IOException | XmlPullParserException | RuntimeException e) { + Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); + } + if (read != null) { + mDefaultValues.clear(); + mDefaultValues.putAll(read); + return true; + } + return false; + } + + private void doWtf(String message) { + injectWtf(message, null); + } + + @VisibleForTesting + void injectWtf(String message, Throwable e) { + Slog.wtf(TAG, message, e); + } + + File injectDefaultValuesFilename() { + final File dir = new File(Environment.getDataSystemDirectory(), "battery-saver"); + dir.mkdirs(); + return new File(dir, "default-values.xml"); + } + + @VisibleForTesting + boolean injectShouldSkipWrite() { + return SystemProperties.getBoolean(PROP_SKIP_WRITE, false); + } + + @VisibleForTesting + ArrayMap<String, String> getDefaultValuesForTest() { + return mDefaultValues; } } diff --git a/com/android/server/stats/StatsCompanionService.java b/com/android/server/stats/StatsCompanionService.java index dafab770..e240ec5e 100644 --- a/com/android/server/stats/StatsCompanionService.java +++ b/com/android/server/stats/StatsCompanionService.java @@ -75,6 +75,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private final PendingIntent mPullingAlarmIntent; private final BroadcastReceiver mAppUpdateReceiver; private final BroadcastReceiver mUserUpdateReceiver; + private final ShutdownEventReceiver mShutdownEventReceiver; private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader(); private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats(); private final KernelCpuSpeedReader[] mKernelCpuSpeedReaders; @@ -109,6 +110,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } }; + mShutdownEventReceiver = new ShutdownEventReceiver(); Slog.w(TAG, "Registered receiver for ACTION_PACKAGE_REPLACE AND ADDED."); PowerProfile powerProfile = new PowerProfile(context); final int numClusters = powerProfile.getNumCpuClusters(); @@ -239,20 +241,45 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { Slog.d(TAG, "Time to poll something."); synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing"); + Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing."); return; } try { // Two-way call to statsd to retain AlarmManager wakelock sStatsd.informPollAlarmFired(); } catch (RemoteException e) { - Slog.w(TAG, "Failed to inform statsd of pulling alarm firing", e); + Slog.w(TAG, "Failed to inform statsd of pulling alarm firing.", e); } } // AlarmManager releases its own wakelock here. } } + public final static class ShutdownEventReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + /** + * Skip immediately if intent is not relevant to device shutdown. + */ + if (!intent.getAction().equals(Intent.ACTION_REBOOT) + && !intent.getAction().equals(Intent.ACTION_SHUTDOWN)) { + return; + } + Slog.i(TAG, "StatsCompanionService noticed a shutdown."); + synchronized (sStatsdLock) { + if (sStatsd == null) { + Slog.w(TAG, "Could not access statsd to inform it of a shutdown event."); + return; + } + try { + sStatsd.writeDataToDisk(); + } catch (Exception e) { + Slog.w(TAG, "Failed to inform statsd of a shutdown event.", e); + } + } + } + } + @Override // Binder call public void setAnomalyAlarm(long timestampMs) { enforceCallingPermission(); @@ -496,6 +523,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { sayHiToStatsd(); // tell statsd that we're ready too and link to it } + @Override + public void triggerUidSnapshot() { + enforceCallingPermission(); + synchronized (sStatsdLock) { + try { + informAllUidsLocked(mContext); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to trigger uid snapshot.", e); + } + } + } + private void enforceCallingPermission() { if (Binder.getCallingPid() == Process.myPid()) { return; @@ -572,7 +611,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e); forgetEverything(); } - // Setup broadcast receiver for updates + // Setup broadcast receiver for updates. IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REPLACED); filter.addAction(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); @@ -587,6 +626,12 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { mContext.registerReceiverAsUser(mUserUpdateReceiver, UserHandle.ALL, filter, null, null); + // Setup receiver for device reboots or shutdowns. + filter = new IntentFilter(Intent.ACTION_REBOOT); + filter.addAction(Intent.ACTION_SHUTDOWN); + mContext.registerReceiverAsUser( + mShutdownEventReceiver, UserHandle.ALL, filter, null, null); + // Pull the latest state of UID->app name, version mapping when statsd starts. informAllUidsLocked(mContext); } catch (RemoteException e) { @@ -609,6 +654,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { sStatsd = null; mContext.unregisterReceiver(mAppUpdateReceiver); mContext.unregisterReceiver(mUserUpdateReceiver); + mContext.unregisterReceiver(mShutdownEventReceiver); cancelAnomalyAlarm(); cancelPullingAlarms(); } diff --git a/com/android/server/statusbar/StatusBarManagerInternal.java b/com/android/server/statusbar/StatusBarManagerInternal.java index b07fe98d..3792bc67 100644 --- a/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/com/android/server/statusbar/StatusBarManagerInternal.java @@ -90,6 +90,13 @@ public interface StatusBarManagerInternal { boolean showShutdownUi(boolean isReboot, String requestString); + /** + * Show a rotation suggestion that a user may approve to rotate the screen. + * + * @param rotation rotation suggestion + */ + void onProposedRotationChanged(int rotation); + public interface GlobalActionsListener { /** * Called when sysui starts and connects its status bar, or when the status bar binder diff --git a/com/android/server/statusbar/StatusBarManagerService.java b/com/android/server/statusbar/StatusBarManagerService.java index c78a3406..c7c03b48 100644 --- a/com/android/server/statusbar/StatusBarManagerService.java +++ b/com/android/server/statusbar/StatusBarManagerService.java @@ -406,6 +406,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } return false; } + + @Override + public void onProposedRotationChanged(int rotation) { + if (mBar != null){ + try { + mBar.onProposedRotationChanged(rotation); + } catch (RemoteException ex) {} + } + } }; // ================================================================================ diff --git a/com/android/server/usage/AppIdleHistory.java b/com/android/server/usage/AppIdleHistory.java index c5ca3304..ee112415 100644 --- a/com/android/server/usage/AppIdleHistory.java +++ b/com/android/server/usage/AppIdleHistory.java @@ -91,8 +91,6 @@ public class AppIdleHistory { private long mScreenOnSnapshot; // Elapsed time snapshot when last write of mScreenOnDuration private long mScreenOnDuration; // Total screen on duration since device was "born" - private long mElapsedTimeThreshold; - private long mScreenOnTimeThreshold; private final File mStorageDir; private boolean mScreenOn; @@ -113,11 +111,6 @@ public class AppIdleHistory { readScreenOnTime(); } - public void setThresholds(long elapsedTimeThreshold, long screenOnTimeThreshold) { - mElapsedTimeThreshold = elapsedTimeThreshold; - mScreenOnTimeThreshold = screenOnTimeThreshold; - } - public void updateDisplay(boolean screenOn, long elapsedRealtime) { if (screenOn == mScreenOn) return; @@ -186,7 +179,7 @@ public class AppIdleHistory { writeScreenOnTime(); } - public void reportUsage(String packageName, int userId, long elapsedRealtime) { + public int reportUsage(String packageName, int userId, long elapsedRealtime) { ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, elapsedRealtime, true); @@ -197,12 +190,33 @@ public class AppIdleHistory { + (elapsedRealtime - mElapsedSnapshot); appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE; - appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_ACTIVE; + if (appUsageHistory.currentBucket > STANDBY_BUCKET_ACTIVE) { + appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE; + if (DEBUG) { + Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket + + ", reason=" + appUsageHistory.bucketingReason); + } + } appUsageHistory.bucketingReason = AppStandby.REASON_USAGE; - if (DEBUG) { - Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket - + ", reason=" + appUsageHistory.bucketingReason); + + return appUsageHistory.currentBucket; + } + + public int reportMildUsage(String packageName, int userId, long elapsedRealtime) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, + elapsedRealtime, true); + if (appUsageHistory.currentBucket > STANDBY_BUCKET_WORKING_SET) { + appUsageHistory.currentBucket = STANDBY_BUCKET_WORKING_SET; + if (DEBUG) { + Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket + + ", reason=" + appUsageHistory.bucketingReason); + } } + // TODO: Should this be a different reason for partial usage? + appUsageHistory.bucketingReason = AppStandby.REASON_USAGE; + + return appUsageHistory.currentBucket; } public void setIdle(String packageName, int userId, long elapsedRealtime) { @@ -313,7 +327,8 @@ public class AppIdleHistory { return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration); } - public void setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) { + /* Returns the new standby bucket the app is assigned to */ + public int setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) { ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, elapsedRealtime, true); @@ -325,6 +340,7 @@ public class AppIdleHistory { // This is to pretend that the app was just used, don't freeze the state anymore. appUsageHistory.bucketingReason = REASON_USAGE; } + return appUsageHistory.currentBucket; } public void clearUsage(String packageName, int userId) { diff --git a/com/android/server/usage/AppStandbyController.java b/com/android/server/usage/AppStandbyController.java index 5623a68f..3c099c24 100644 --- a/com/android/server/usage/AppStandbyController.java +++ b/com/android/server/usage/AppStandbyController.java @@ -91,14 +91,14 @@ public class AppStandbyController { 0, 0, COMPRESS_TIME ? 120 * 1000 : 1 * ONE_HOUR, - COMPRESS_TIME ? 240 * 1000 : 8 * ONE_HOUR + COMPRESS_TIME ? 240 * 1000 : 2 * ONE_HOUR }; static final long[] ELAPSED_TIME_THRESHOLDS = { 0, COMPRESS_TIME ? 1 * ONE_MINUTE : 12 * ONE_HOUR, - COMPRESS_TIME ? 4 * ONE_MINUTE : 2 * ONE_DAY, - COMPRESS_TIME ? 16 * ONE_MINUTE : 8 * ONE_DAY + COMPRESS_TIME ? 4 * ONE_MINUTE : 24 * ONE_HOUR, + COMPRESS_TIME ? 16 * ONE_MINUTE : 48 * ONE_HOUR }; static final int[] THRESHOLD_BUCKETS = { @@ -140,9 +140,7 @@ public class AppStandbyController { static final int MSG_PAROLE_STATE_CHANGED = 9; static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10; - long mAppIdleScreenThresholdMillis; long mCheckIdleIntervalMillis; - long mAppIdleWallclockThresholdMillis; long mAppIdleParoleIntervalMillis; long mAppIdleParoleDurationMillis; long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS; @@ -156,7 +154,7 @@ public class AppStandbyController { private volatile boolean mPendingOneTimeCheckIdleStates; - private final Handler mHandler; + private final AppStandbyHandler mHandler; private final Context mContext; // TODO: Provide a mechanism to set an external bucketing service @@ -229,6 +227,7 @@ public class AppStandbyController { // Get sync adapters for the authority String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser( authority, userId); + final long elapsedRealtime = SystemClock.elapsedRealtime(); for (String packageName: packages) { // Only force the sync adapters to active if the provider is not in the same package and // the sync adapter is a system package. @@ -239,7 +238,12 @@ public class AppStandbyController { continue; } if (!packageName.equals(providerPkgName)) { - setAppIdleAsync(packageName, false, userId); + synchronized (mAppIdleLock) { + int newBucket = mAppIdleHistory.reportMildUsage(packageName, userId, + elapsedRealtime); + maybeInformListeners(packageName, userId, elapsedRealtime, + newBucket); + } } } catch (PackageManager.NameNotFoundException e) { // Shouldn't happen @@ -408,6 +412,7 @@ public class AppStandbyController { private void maybeInformListeners(String packageName, int userId, long elapsedRealtime, int bucket) { synchronized (mAppIdleLock) { + // TODO: fold these into one call + lookup for efficiency if needed if (mAppIdleHistory.shouldInformListeners(packageName, userId, elapsedRealtime, bucket)) { mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, @@ -493,11 +498,21 @@ public class AppStandbyController { if ((event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND || event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND || event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION - || event.mEventType == UsageEvents.Event.USER_INTERACTION)) { - mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime); + || event.mEventType == UsageEvents.Event.USER_INTERACTION + || event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN)) { + + final int newBucket; + if (event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN) { + newBucket = mAppIdleHistory.reportMildUsage(event.mPackage, userId, + elapsedRealtime); + } else { + newBucket = mAppIdleHistory.reportUsage(event.mPackage, userId, + elapsedRealtime); + } + + maybeInformListeners(event.mPackage, userId, elapsedRealtime, + newBucket); if (previouslyIdle) { - maybeInformListeners(event.mPackage, userId, elapsedRealtime, - AppStandby.STANDBY_BUCKET_ACTIVE); notifyBatteryStats(event.mPackage, userId, false); } } @@ -519,15 +534,15 @@ public class AppStandbyController { final boolean previouslyIdle = isAppIdleFiltered(packageName, appId, userId, elapsedRealtime); + final int standbyBucket; synchronized (mAppIdleLock) { - mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime); + standbyBucket = mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime); } final boolean stillIdle = isAppIdleFiltered(packageName, appId, userId, elapsedRealtime); // Inform listeners if necessary if (previouslyIdle != stillIdle) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId, - /* idle = */ stillIdle ? 1 : 0, packageName)); + maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket); if (!stillIdle) { notifyBatteryStats(packageName, userId, idle); } @@ -723,7 +738,7 @@ public class AppStandbyController { .sendToTarget(); } - @StandbyBuckets int getAppStandbyBucket(String packageName, int userId, + @StandbyBuckets public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime, boolean shouldObfuscateInstantApps) { if (shouldObfuscateInstantApps && mInjector.isPackageEphemeral(userId, packageName)) { @@ -737,6 +752,8 @@ public class AppStandbyController { String reason, long elapsedRealtime) { mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason); + maybeInformListeners(packageName, userId, elapsedRealtime, + newBucket); } private boolean isActiveDeviceAdmin(String packageName, int userId) { @@ -896,14 +913,6 @@ public class AppStandbyController { pw.println(); pw.println("Settings:"); - pw.print(" mAppIdleDurationMillis="); - TimeUtils.formatDuration(mAppIdleScreenThresholdMillis, pw); - pw.println(); - - pw.print(" mAppIdleWallclockThresholdMillis="); - TimeUtils.formatDuration(mAppIdleWallclockThresholdMillis, pw); - pw.println(); - pw.print(" mCheckIdleIntervalMillis="); TimeUtils.formatDuration(mCheckIdleIntervalMillis, pw); pw.println(); @@ -1033,6 +1042,11 @@ public class AppStandbyController { int userId) { return appWidgetManager.isBoundWidgetPackage(packageName, userId); } + + String getAppIdleSettings() { + return Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.APP_IDLE_CONSTANTS); + } } class AppStandbyHandler extends Handler { @@ -1165,31 +1179,18 @@ public class AppStandbyController { // Look at global settings for this. // TODO: Maybe apply different thresholds for different users. try { - mParser.setString(Settings.Global.getString(mContext.getContentResolver(), - Settings.Global.APP_IDLE_CONSTANTS)); + mParser.setString(mInjector.getAppIdleSettings()); } catch (IllegalArgumentException e) { Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage()); // fallthrough, mParser is empty and all defaults will be returned. } - // Default: 12 hours of screen-on time sans dream-time - mAppIdleScreenThresholdMillis = mParser.getLong(KEY_IDLE_DURATION, - COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE); - - mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD, - COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days - - mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4, - COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours - // Default: 24 hours between paroles mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL, COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE); mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION, COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes - mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis, - mAppIdleScreenThresholdMillis); String screenThresholdsValue = mParser.getString(KEY_SCREEN_TIME_THRESHOLDS, null); mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue, @@ -1199,6 +1200,9 @@ public class AppStandbyController { null); mAppStandbyElapsedThresholds = parseLongArray(elapsedThresholdsValue, ELAPSED_TIME_THRESHOLDS); + mCheckIdleIntervalMillis = Math.min(mAppStandbyElapsedThresholds[1] / 4, + COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours + } } diff --git a/com/android/server/usage/UsageStatsService.java b/com/android/server/usage/UsageStatsService.java index 44e6a6cb..65c1cefa 100644 --- a/com/android/server/usage/UsageStatsService.java +++ b/com/android/server/usage/UsageStatsService.java @@ -24,6 +24,7 @@ import android.app.usage.AppStandby; import android.app.usage.ConfigurationStats; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; +import android.app.usage.AppStandby.StandbyBuckets; import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStats; import android.app.usage.UsageStatsManagerInternal; @@ -867,6 +868,12 @@ public class UsageStatsService extends SystemService implements } @Override + @StandbyBuckets public int getAppStandbyBucket(String packageName, int userId, + long nowElapsed) { + return mAppStandby.getAppStandbyBucket(packageName, userId, nowElapsed, false); + } + + @Override public int[] getIdleUidsForUser(int userId) { return mAppStandby.getIdleUidsForUser(userId); } diff --git a/com/android/server/usage/UserUsageStatsService.java b/com/android/server/usage/UserUsageStatsService.java index 0b105904..f02221cb 100644 --- a/com/android/server/usage/UserUsageStatsService.java +++ b/com/android/server/usage/UserUsageStatsService.java @@ -643,6 +643,8 @@ class UserUsageStatsService { return "SHORTCUT_INVOCATION"; case UsageEvents.Event.CHOOSER_ACTION: return "CHOOSER_ACTION"; + case UsageEvents.Event.NOTIFICATION_SEEN: + return "NOTIFICATION_SEEN"; default: return "UNKNOWN"; } diff --git a/com/android/server/usb/UsbAlsaManager.java b/com/android/server/usb/UsbAlsaManager.java index d359b704..7bea8a11 100644 --- a/com/android/server/usb/UsbAlsaManager.java +++ b/com/android/server/usb/UsbAlsaManager.java @@ -255,6 +255,7 @@ public final class UsbAlsaManager { } private void alsaFileAdded(String name) { + Slog.i(TAG, "alsaFileAdded(" + name + ")"); int type = AlsaDevice.TYPE_UNKNOWN; int card = -1, device = -1; diff --git a/com/android/server/usb/UsbHostManager.java b/com/android/server/usb/UsbHostManager.java index 095fdc63..dd2e1929 100644 --- a/com/android/server/usb/UsbHostManager.java +++ b/com/android/server/usb/UsbHostManager.java @@ -19,13 +19,8 @@ package com.android.server.usb; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; -import android.hardware.usb.UsbConfiguration; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; -import android.hardware.usb.UsbManager; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.text.TextUtils; @@ -37,7 +32,6 @@ import com.android.server.usb.descriptors.UsbDescriptorParser; import com.android.server.usb.descriptors.report.TextReportCanvas; import com.android.server.usb.descriptors.tree.UsbDescriptorsTree; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -50,28 +44,23 @@ public class UsbHostManager { private final Context mContext; - // contains all connected USB devices - private final HashMap<String, UsbDevice> mDevices = new HashMap<>(); - // USB busses to exclude from USB host support private final String[] mHostBlacklist; - private final Object mLock = new Object(); - - private UsbDevice mNewDevice; - private UsbConfiguration mNewConfiguration; - private UsbInterface mNewInterface; - private ArrayList<UsbConfiguration> mNewConfigurations; - private ArrayList<UsbInterface> mNewInterfaces; - private ArrayList<UsbEndpoint> mNewEndpoints; - private final UsbAlsaManager mUsbAlsaManager; private final UsbSettingsManager mSettingsManager; + private final Object mLock = new Object(); @GuardedBy("mLock") + // contains all connected USB devices + private final HashMap<String, UsbDevice> mDevices = new HashMap<>(); + + private Object mSettingsLock = new Object(); + @GuardedBy("mSettingsLock") private UsbProfileGroupSettingsManager mCurrentSettings; - @GuardedBy("mLock") + private Object mHandlerLock = new Object(); + @GuardedBy("mHandlerLock") private ComponentName mUsbDeviceConnectionHandler; public UsbHostManager(Context context, UsbAlsaManager alsaManager, @@ -91,33 +80,33 @@ public class UsbHostManager { } public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) { - synchronized (mLock) { + synchronized (mSettingsLock) { mCurrentSettings = settings; } } private UsbProfileGroupSettingsManager getCurrentUserSettings() { - synchronized (mLock) { + synchronized (mSettingsLock) { return mCurrentSettings; } } public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) { - synchronized (mLock) { + synchronized (mHandlerLock) { mUsbDeviceConnectionHandler = usbDeviceConnectionHandler; } } private @Nullable ComponentName getUsbDeviceConnectionHandler() { - synchronized (mLock) { + synchronized (mHandlerLock) { return mUsbDeviceConnectionHandler; } } - private boolean isBlackListed(String deviceName) { + private boolean isBlackListed(String deviceAddress) { int count = mHostBlacklist.length; for (int i = 0; i < count; i++) { - if (deviceName.startsWith(mHostBlacklist[i])) { + if (deviceAddress.startsWith(mHostBlacklist[i])) { return true; } } @@ -136,166 +125,73 @@ public class UsbHostManager { } /* Called from JNI in monitorUsbHostBus() to report new USB devices - Returns true if successful, in which case the JNI code will continue adding configurations, - interfaces and endpoints, and finally call endUsbDeviceAdded after all descriptors - have been processed + Returns true if successful, i.e. the USB Audio device descriptors are + correctly parsed and the unique device is added to the audio device list. */ @SuppressWarnings("unused") - private boolean beginUsbDeviceAdded(String deviceName, int vendorID, int productID, - int deviceClass, int deviceSubclass, int deviceProtocol, - String manufacturerName, String productName, int version, String serialNumber) { - + private boolean usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass, + byte[] descriptors) { if (DEBUG) { - Slog.d(TAG, "usb:UsbHostManager.beginUsbDeviceAdded(" + deviceName + ")"); - // Audio Class Codes: - // Audio: 0x01 - // Audio Subclass Codes: - // undefined: 0x00 - // audio control: 0x01 - // audio streaming: 0x02 - // midi streaming: 0x03 - - // some useful debugging info - Slog.d(TAG, "usb: nm:" + deviceName + " vnd:" + vendorID + " prd:" + productID + " cls:" - + deviceClass + " sub:" + deviceSubclass + " proto:" + deviceProtocol); + Slog.d(TAG, "usbDeviceAdded(" + deviceAddress + ") - start"); } - // OK this is non-obvious, but true. One can't tell if the device being attached is even - // potentially an audio device without parsing the interface descriptors, so punt on any - // such test until endUsbDeviceAdded() when we have that info. - - if (isBlackListed(deviceName) || - isBlackListed(deviceClass, deviceSubclass)) { + // check class/subclass first as it is more likely to be blacklisted + if (isBlackListed(deviceClass, deviceSubclass) || isBlackListed(deviceAddress)) { + if (DEBUG) { + Slog.d(TAG, "device is black listed"); + } return false; } synchronized (mLock) { - if (mDevices.get(deviceName) != null) { - Slog.w(TAG, "device already on mDevices list: " + deviceName); + if (mDevices.get(deviceAddress) != null) { + Slog.w(TAG, "device already on mDevices list: " + deviceAddress); + //TODO If this is the same peripheral as is being connected, replace + // it with the new connection. return false; } - if (mNewDevice != null) { - Slog.e(TAG, "mNewDevice is not null in endUsbDeviceAdded"); - return false; - } - - // Create version string in "%.%" format - String versionString = Integer.toString(version >> 8) + "." + (version & 0xFF); + UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress); + if (parser.parseDescriptors(descriptors)) { - mNewDevice = new UsbDevice(deviceName, vendorID, productID, - deviceClass, deviceSubclass, deviceProtocol, - manufacturerName, productName, versionString, serialNumber); - - mNewConfigurations = new ArrayList<>(); - mNewInterfaces = new ArrayList<>(); - mNewEndpoints = new ArrayList<>(); - } - - return true; - } - - /* Called from JNI in monitorUsbHostBus() to report new USB configuration for the device - currently being added. Returns true if successful, false in case of error. - */ - @SuppressWarnings("unused") - private void addUsbConfiguration(int id, String name, int attributes, int maxPower) { - if (mNewConfiguration != null) { - mNewConfiguration.setInterfaces( - mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()])); - mNewInterfaces.clear(); - } - - mNewConfiguration = new UsbConfiguration(id, name, attributes, maxPower); - mNewConfigurations.add(mNewConfiguration); - } - - /* Called from JNI in monitorUsbHostBus() to report new USB interface for the device - currently being added. Returns true if successful, false in case of error. - */ - @SuppressWarnings("unused") - private void addUsbInterface(int id, String name, int altSetting, - int Class, int subClass, int protocol) { - if (mNewInterface != null) { - mNewInterface.setEndpoints( - mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()])); - mNewEndpoints.clear(); - } - - mNewInterface = new UsbInterface(id, altSetting, name, Class, subClass, protocol); - mNewInterfaces.add(mNewInterface); - } - - /* Called from JNI in monitorUsbHostBus() to report new USB endpoint for the device - currently being added. Returns true if successful, false in case of error. - */ - @SuppressWarnings("unused") - private void addUsbEndpoint(int address, int attributes, int maxPacketSize, int interval) { - mNewEndpoints.add(new UsbEndpoint(address, attributes, maxPacketSize, interval)); - } - - /* Called from JNI in monitorUsbHostBus() to finish adding a new device */ - @SuppressWarnings("unused") - private void endUsbDeviceAdded() { - if (DEBUG) { - Slog.d(TAG, "usb:UsbHostManager.endUsbDeviceAdded()"); - } - if (mNewInterface != null) { - mNewInterface.setEndpoints( - mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()])); - } - if (mNewConfiguration != null) { - mNewConfiguration.setInterfaces( - mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()])); - } - - - synchronized (mLock) { - if (mNewDevice != null) { - mNewDevice.setConfigurations( - mNewConfigurations.toArray( - new UsbConfiguration[mNewConfigurations.size()])); - mDevices.put(mNewDevice.getDeviceName(), mNewDevice); - Slog.d(TAG, "Added device " + mNewDevice); + UsbDevice newDevice = parser.toAndroidUsbDevice(); + mDevices.put(deviceAddress, newDevice); // It is fine to call this only for the current user as all broadcasts are sent to // all profiles of the user and the dialogs should only show once. ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler(); if (usbDeviceConnectionHandler == null) { - getCurrentUserSettings().deviceAttached(mNewDevice); + getCurrentUserSettings().deviceAttached(newDevice); } else { - getCurrentUserSettings().deviceAttachedForFixedHandler(mNewDevice, + getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice, usbDeviceConnectionHandler); } - // deviceName is something like: "/dev/bus/usb/001/001" - UsbDescriptorParser parser = new UsbDescriptorParser(); - boolean isInputHeadset = false; - boolean isOutputHeadset = false; - if (parser.parseDevice(mNewDevice.getDeviceName())) { - isInputHeadset = parser.isInputHeadset(); - isOutputHeadset = parser.isOutputHeadset(); - Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset - + " , out: " + isOutputHeadset + "]"); - } - mUsbAlsaManager.usbDeviceAdded(mNewDevice, - isInputHeadset, isOutputHeadset); + + // Headset? + boolean isInputHeadset = parser.isInputHeadset(); + boolean isOutputHeadset = parser.isOutputHeadset(); + Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset + + " , out: " + isOutputHeadset + "]"); + + mUsbAlsaManager.usbDeviceAdded(newDevice, isInputHeadset, isOutputHeadset); } else { - Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded"); + Slog.e(TAG, "Error parsing USB device descriptors for " + deviceAddress); + return false; } - mNewDevice = null; - mNewConfigurations = null; - mNewInterfaces = null; - mNewEndpoints = null; - mNewConfiguration = null; - mNewInterface = null; } + + if (DEBUG) { + Slog.d(TAG, "beginUsbDeviceAdded(" + deviceAddress + ") end"); + } + + return true; } /* Called from JNI in monitorUsbHostBus to report USB device removal */ @SuppressWarnings("unused") - private void usbDeviceRemoved(String deviceName) { + private void usbDeviceRemoved(String deviceAddress) { synchronized (mLock) { - UsbDevice device = mDevices.remove(deviceName); + UsbDevice device = mDevices.remove(deviceAddress); if (device != null) { mUsbAlsaManager.usbDeviceRemoved(device); mSettingsManager.usbDeviceRemoved(device); @@ -323,31 +219,35 @@ public class UsbHostManager { } /* Opens the specified USB device */ - public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings) { + public ParcelFileDescriptor openDevice(String deviceAddress, UsbUserSettingsManager settings, + String packageName, int uid) { synchronized (mLock) { - if (isBlackListed(deviceName)) { + if (isBlackListed(deviceAddress)) { throw new SecurityException("USB device is on a restricted bus"); } - UsbDevice device = mDevices.get(deviceName); + UsbDevice device = mDevices.get(deviceAddress); if (device == null) { // if it is not in mDevices, it either does not exist or is blacklisted throw new IllegalArgumentException( - "device " + deviceName + " does not exist or is restricted"); + "device " + deviceAddress + " does not exist or is restricted"); } - settings.checkPermission(device); - return nativeOpenDevice(deviceName); + + settings.checkPermission(device, packageName, uid); + return nativeOpenDevice(deviceAddress); } } public void dump(IndentingPrintWriter pw) { + pw.println("USB Host State:"); + synchronized (mHandlerLock) { + if (mUsbDeviceConnectionHandler != null) { + pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler); + } + } synchronized (mLock) { - pw.println("USB Host State:"); for (String name : mDevices.keySet()) { pw.println(" " + name + ": " + mDevices.get(name)); } - if (mUsbDeviceConnectionHandler != null) { - pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler); - } Collection<UsbDevice> devices = mDevices.values(); if (devices.size() != 0) { @@ -355,17 +255,12 @@ public class UsbHostManager { for (UsbDevice device : devices) { StringBuilder stringBuilder = new StringBuilder(); - UsbDescriptorParser parser = new UsbDescriptorParser(); - if (parser.parseDevice(device.getDeviceName())) { + UsbDescriptorParser parser = new UsbDescriptorParser(device.getDeviceName()); + if (parser.parseDevice()) { UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree(); descriptorTree.parse(parser); - UsbManager usbManager = - (UsbManager) mContext.getSystemService(Context.USB_SERVICE); - UsbDeviceConnection connection = usbManager.openDevice(device); - - descriptorTree.report(new TextReportCanvas(connection, stringBuilder)); - connection.close(); + descriptorTree.report(new TextReportCanvas(parser, stringBuilder)); stringBuilder.append("isHeadset[in: " + parser.isInputHeadset() + " , out: " + parser.isOutputHeadset() + "]"); @@ -381,5 +276,5 @@ public class UsbHostManager { } private native void monitorUsbHostBus(); - private native ParcelFileDescriptor nativeOpenDevice(String deviceName); + private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress); } diff --git a/com/android/server/usb/UsbService.java b/com/android/server/usb/UsbService.java index e4fcea77..17de83f0 100644 --- a/com/android/server/usb/UsbService.java +++ b/com/android/server/usb/UsbService.java @@ -232,7 +232,7 @@ public class UsbService extends IUsbManager.Stub { /* Opens the specified USB device (host mode) */ @Override - public ParcelFileDescriptor openDevice(String deviceName) { + public ParcelFileDescriptor openDevice(String deviceName, String packageName) { ParcelFileDescriptor fd = null; if (mHostManager != null) { @@ -242,7 +242,8 @@ public class UsbService extends IUsbManager.Stub { boolean isCurrentUser = isCallerInCurrentUserProfileGroupLocked(); if (isCurrentUser) { - fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt)); + fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt), + packageName, Binder.getCallingUid()); } else { Slog.w(TAG, "Cannot open " + deviceName + " for user " + userIdInt + " as user is not active."); @@ -308,9 +309,10 @@ public class UsbService extends IUsbManager.Stub { } @Override - public boolean hasDevicePermission(UsbDevice device) { + public boolean hasDevicePermission(UsbDevice device, String packageName) { final int userId = UserHandle.getCallingUserId(); - return getSettingsForUser(userId).hasPermission(device); + return getSettingsForUser(userId).hasPermission(device, packageName, + Binder.getCallingUid()); } @Override @@ -322,7 +324,8 @@ public class UsbService extends IUsbManager.Stub { @Override public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) { final int userId = UserHandle.getCallingUserId(); - getSettingsForUser(userId).requestPermission(device, packageName, pi); + getSettingsForUser(userId).requestPermission(device, packageName, pi, + Binder.getCallingUid()); } @Override diff --git a/com/android/server/usb/UsbUserSettingsManager.java b/com/android/server/usb/UsbUserSettingsManager.java index 96c5211c..11e43e30 100644 --- a/com/android/server/usb/UsbUserSettingsManager.java +++ b/com/android/server/usb/UsbUserSettingsManager.java @@ -26,6 +26,8 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbManager; import android.os.Binder; import android.os.Process; @@ -95,10 +97,70 @@ class UsbUserSettingsManager { } } + /** + * Check whether a particular device or any of its interfaces + * is of class VIDEO. + * + * @param device The device that needs to get scanned + * @return True in case a VIDEO device or interface is present, + * False otherwise. + */ + private boolean isCameraDevicePresent(UsbDevice device) { + if (device.getDeviceClass() == UsbConstants.USB_CLASS_VIDEO) { + return true; + } + + for (int i = 0; i < device.getInterfaceCount(); i++) { + UsbInterface iface = device.getInterface(i); + if (iface.getInterfaceClass() == UsbConstants.USB_CLASS_VIDEO) { + return true; + } + } + + return false; + } + + /** + * Check for camera permission of the calling process. + * + * @param packageName Package name of the caller. + * @param uid Linux uid of the calling process. + * + * @return True in case camera permission is available, False otherwise. + */ + private boolean isCameraPermissionGranted(String packageName, int uid) { + int targetSdkVersion = android.os.Build.VERSION_CODES.P; + try { + ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0); + // compare uid with packageName to foil apps pretending to be someone else + if (aInfo.uid != uid) { + Slog.i(TAG, "Package " + packageName + " does not match caller's uid " + uid); + return false; + } + targetSdkVersion = aInfo.targetSdkVersion; + } catch (PackageManager.NameNotFoundException e) { + Slog.i(TAG, "Package not found, likely due to invalid package name!"); + return false; + } + + if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) { + int allowed = mUserContext.checkCallingPermission(android.Manifest.permission.CAMERA); + if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) { + Slog.i(TAG, "Camera permission required for USB video class devices"); + return false; + } + } - public boolean hasPermission(UsbDevice device) { + return true; + } + + public boolean hasPermission(UsbDevice device, String packageName, int uid) { synchronized (mLock) { - int uid = Binder.getCallingUid(); + if (isCameraDevicePresent(device)) { + if (!isCameraPermissionGranted(packageName, uid)) { + return false; + } + } if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) { return true; } @@ -124,8 +186,8 @@ class UsbUserSettingsManager { } } - public void checkPermission(UsbDevice device) { - if (!hasPermission(device)) { + public void checkPermission(UsbDevice device, String packageName, int uid) { + if (!hasPermission(device, packageName, uid)) { throw new SecurityException("User has not given permission to device " + device); } } @@ -166,11 +228,11 @@ class UsbUserSettingsManager { } } - public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) { + public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int uid) { Intent intent = new Intent(); // respond immediately if permission has already been granted - if (hasPermission(device)) { + if (hasPermission(device, packageName, uid)) { intent.putExtra(UsbManager.EXTRA_DEVICE, device); intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); try { @@ -180,6 +242,18 @@ class UsbUserSettingsManager { } return; } + if (isCameraDevicePresent(device)) { + if (!isCameraPermissionGranted(packageName, uid)) { + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); + try { + pi.send(mUserContext, 0, intent); + } catch (PendingIntent.CanceledException e) { + if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); + } + return; + } + } // start UsbPermissionActivity so user can choose an activity intent.putExtra(UsbManager.EXTRA_DEVICE, device); diff --git a/com/android/server/usb/descriptors/Usb10ACHeader.java b/com/android/server/usb/descriptors/Usb10ACHeader.java index a35b4631..7763150a 100644 --- a/com/android/server/usb/descriptors/Usb10ACHeader.java +++ b/com/android/server/usb/descriptors/Usb10ACHeader.java @@ -32,7 +32,7 @@ public final class Usb10ACHeader extends UsbACHeaderInterface { // numbers associate with this endpoint private byte mControls; // Vers 2.0 thing - public Usb10ACHeader(int length, byte type, byte subtype, byte subclass, int spec) { + public Usb10ACHeader(int length, byte type, byte subtype, int subclass, int spec) { super(length, type, subtype, subclass, spec); } diff --git a/com/android/server/usb/descriptors/Usb10ACInputTerminal.java b/com/android/server/usb/descriptors/Usb10ACInputTerminal.java index 2363c4dd..75531d17 100644 --- a/com/android/server/usb/descriptors/Usb10ACInputTerminal.java +++ b/com/android/server/usb/descriptors/Usb10ACInputTerminal.java @@ -32,7 +32,7 @@ public final class Usb10ACInputTerminal extends UsbACTerminal { private byte mChannelNames; // 10:1 Unused (0x00) private byte mTerminal; // 11:1 Unused (0x00) - public Usb10ACInputTerminal(int length, byte type, byte subtype, byte subclass) { + public Usb10ACInputTerminal(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/com/android/server/usb/descriptors/Usb10ACMixerUnit.java b/com/android/server/usb/descriptors/Usb10ACMixerUnit.java index d3486643..c7634ba7 100644 --- a/com/android/server/usb/descriptors/Usb10ACMixerUnit.java +++ b/com/android/server/usb/descriptors/Usb10ACMixerUnit.java @@ -30,7 +30,7 @@ public final class Usb10ACMixerUnit extends UsbACMixerUnit { private byte[] mControls; // bitmasks of which controls are present for each channel private byte mNameID; // string descriptor ID of mixer name - public Usb10ACMixerUnit(int length, byte type, byte subtype, byte subClass) { + public Usb10ACMixerUnit(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } diff --git a/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java b/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java index 9f2f09ec..468ae571 100644 --- a/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java +++ b/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java @@ -28,7 +28,7 @@ public final class Usb10ACOutputTerminal extends UsbACTerminal { private byte mSourceID; // 7:1 From Input Terminal. (0x01) private byte mTerminal; // 8:1 Unused. - public Usb10ACOutputTerminal(int length, byte type, byte subtype, byte subClass) { + public Usb10ACOutputTerminal(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } diff --git a/com/android/server/usb/descriptors/Usb10ASFormatI.java b/com/android/server/usb/descriptors/Usb10ASFormatI.java index 1523bb52..1d8498ae 100644 --- a/com/android/server/usb/descriptors/Usb10ASFormatI.java +++ b/com/android/server/usb/descriptors/Usb10ASFormatI.java @@ -33,7 +33,7 @@ public final class Usb10ASFormatI extends UsbASFormat { // min & max rates otherwise mSamFreqType rates. // All 3-byte values. All rates in Hz - public Usb10ASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) { + public Usb10ASFormatI(int length, byte type, byte subtype, byte formatType, int subclass) { super(length, type, subtype, formatType, subclass); } diff --git a/com/android/server/usb/descriptors/Usb10ASFormatII.java b/com/android/server/usb/descriptors/Usb10ASFormatII.java index b1e7680e..3c45790a 100644 --- a/com/android/server/usb/descriptors/Usb10ASFormatII.java +++ b/com/android/server/usb/descriptors/Usb10ASFormatII.java @@ -38,7 +38,7 @@ public final class Usb10ASFormatII extends UsbASFormat { // the min & max rates. otherwise mSamFreqType rates. // All 3-byte values. All rates in Hz - public Usb10ASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) { + public Usb10ASFormatII(int length, byte type, byte subtype, byte formatType, int subclass) { super(length, type, subtype, formatType, subclass); } diff --git a/com/android/server/usb/descriptors/Usb10ASGeneral.java b/com/android/server/usb/descriptors/Usb10ASGeneral.java index 2d4f604e..4fbbb213 100644 --- a/com/android/server/usb/descriptors/Usb10ASGeneral.java +++ b/com/android/server/usb/descriptors/Usb10ASGeneral.java @@ -34,7 +34,7 @@ public final class Usb10ASGeneral extends UsbACInterface { private int mFormatTag; // 5:2 The Audio Data Format that has to be used to communicate // with this interface. - public Usb10ASGeneral(int length, byte type, byte subtype, byte subclass) { + public Usb10ASGeneral(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/com/android/server/usb/descriptors/Usb20ACHeader.java b/com/android/server/usb/descriptors/Usb20ACHeader.java index eefae3d5..fe1b5023 100644 --- a/com/android/server/usb/descriptors/Usb20ACHeader.java +++ b/com/android/server/usb/descriptors/Usb20ACHeader.java @@ -29,7 +29,7 @@ public final class Usb20ACHeader extends UsbACHeaderInterface { // See audio20.pdf Appendix A.7, “Audio Function Category Codes.” private byte mControls; // 8:1 See audio20.pdf Table 4-5. - public Usb20ACHeader(int length, byte type, byte subtype, byte subclass, int spec) { + public Usb20ACHeader(int length, byte type, byte subtype, int subclass, int spec) { super(length, type, subtype, subclass, spec); } diff --git a/com/android/server/usb/descriptors/Usb20ACInputTerminal.java b/com/android/server/usb/descriptors/Usb20ACInputTerminal.java index 3e2ac39c..ee1b32c1 100644 --- a/com/android/server/usb/descriptors/Usb20ACInputTerminal.java +++ b/com/android/server/usb/descriptors/Usb20ACInputTerminal.java @@ -39,7 +39,7 @@ public final class Usb20ACInputTerminal extends UsbACTerminal { private byte mTerminalName; // 16:1 - Index of a string descriptor, describing the // Input Terminal. - public Usb20ACInputTerminal(int length, byte type, byte subtype, byte subclass) { + public Usb20ACInputTerminal(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/com/android/server/usb/descriptors/Usb20ACMixerUnit.java b/com/android/server/usb/descriptors/Usb20ACMixerUnit.java index 1b267a67..ab965856 100644 --- a/com/android/server/usb/descriptors/Usb20ACMixerUnit.java +++ b/com/android/server/usb/descriptors/Usb20ACMixerUnit.java @@ -33,7 +33,7 @@ public final class Usb20ACMixerUnit extends UsbACMixerUnit { private byte mNameID; // 12+p+N:1 Index of a string descriptor, describing the // Mixer Unit. - public Usb20ACMixerUnit(int length, byte type, byte subtype, byte subClass) { + public Usb20ACMixerUnit(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } diff --git a/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java b/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java index 67478aad..20a97af4 100644 --- a/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java +++ b/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java @@ -34,7 +34,7 @@ public final class Usb20ACOutputTerminal extends UsbACTerminal { private int mControls; // 9:2 - see Audio20.pdf Table 4-10 private byte mTerminalID; // 11:1 - Index of a string descriptor, describing the - public Usb20ACOutputTerminal(int length, byte type, byte subtype, byte subClass) { + public Usb20ACOutputTerminal(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } diff --git a/com/android/server/usb/descriptors/Usb20ASFormatI.java b/com/android/server/usb/descriptors/Usb20ASFormatI.java index c0319961..7310a3e2 100644 --- a/com/android/server/usb/descriptors/Usb20ASFormatI.java +++ b/com/android/server/usb/descriptors/Usb20ASFormatI.java @@ -31,7 +31,7 @@ public final class Usb20ASFormatI extends UsbASFormat { private byte mBitResolution; // 5:1 The number of effectively used bits from // the available bits in an audio subslot. - public Usb20ASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) { + public Usb20ASFormatI(int length, byte type, byte subtype, byte formatType, int subclass) { super(length, type, subtype, formatType, subclass); } diff --git a/com/android/server/usb/descriptors/Usb20ASFormatII.java b/com/android/server/usb/descriptors/Usb20ASFormatII.java index dc44ff06..fe887432 100644 --- a/com/android/server/usb/descriptors/Usb20ASFormatII.java +++ b/com/android/server/usb/descriptors/Usb20ASFormatII.java @@ -34,7 +34,7 @@ public final class Usb20ASFormatII extends UsbASFormat { /** * TBD */ - public Usb20ASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) { + public Usb20ASFormatII(int length, byte type, byte subtype, byte formatType, int subclass) { super(length, type, subtype, formatType, subclass); } diff --git a/com/android/server/usb/descriptors/Usb20ASFormatIII.java b/com/android/server/usb/descriptors/Usb20ASFormatIII.java index b44a2167..b0ba02ff 100644 --- a/com/android/server/usb/descriptors/Usb20ASFormatIII.java +++ b/com/android/server/usb/descriptors/Usb20ASFormatIII.java @@ -31,7 +31,7 @@ public final class Usb20ASFormatIII extends UsbASFormat { private byte mBitResolution; // 5:1 The number of effectively used bits from // the available bits in an audio subframe. - public Usb20ASFormatIII(int length, byte type, byte subtype, byte formatType, byte subclass) { + public Usb20ASFormatIII(int length, byte type, byte subtype, byte formatType, int subclass) { super(length, type, subtype, formatType, subclass); } diff --git a/com/android/server/usb/descriptors/Usb20ASGeneral.java b/com/android/server/usb/descriptors/Usb20ASGeneral.java index 18d48a00..de207388 100644 --- a/com/android/server/usb/descriptors/Usb20ASGeneral.java +++ b/com/android/server/usb/descriptors/Usb20ASGeneral.java @@ -41,7 +41,7 @@ public final class Usb20ASGeneral extends UsbACInterface { private byte mChannelNames; // 15:1 Index of a string descriptor, describing the // name of the first physical channel. - public Usb20ASGeneral(int length, byte type, byte subtype, byte subclass) { + public Usb20ASGeneral(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java b/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java index 6e1ce075..409e605c 100644 --- a/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java +++ b/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java @@ -38,7 +38,7 @@ public class UsbACAudioControlEndpoint extends UsbACEndpoint { static final byte ATTRIBSMASK_SYNC = 0x0C; static final byte ATTRIBMASK_TRANS = 0x03; - public UsbACAudioControlEndpoint(int length, byte type, byte subclass) { + public UsbACAudioControlEndpoint(int length, byte type, int subclass) { super(length, type, subclass); } diff --git a/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java b/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java index d3519029..e63bb74a 100644 --- a/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java +++ b/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java @@ -24,7 +24,7 @@ public class UsbACAudioStreamEndpoint extends UsbACEndpoint { private static final String TAG = "UsbACAudioStreamEndpoint"; //TODO data fields... - public UsbACAudioStreamEndpoint(int length, byte type, byte subclass) { + public UsbACAudioStreamEndpoint(int length, byte type, int subclass) { super(length, type, subclass); } diff --git a/com/android/server/usb/descriptors/UsbACEndpoint.java b/com/android/server/usb/descriptors/UsbACEndpoint.java index 4a6839d9..7ebccf39 100644 --- a/com/android/server/usb/descriptors/UsbACEndpoint.java +++ b/com/android/server/usb/descriptors/UsbACEndpoint.java @@ -25,16 +25,16 @@ import android.util.Log; abstract class UsbACEndpoint extends UsbDescriptor { private static final String TAG = "UsbACEndpoint"; - protected final byte mSubclass; // from the mSubclass member of the "enclosing" - // Interface Descriptor, not the stream. - protected byte mSubtype; // 2:1 HEADER descriptor subtype + protected final int mSubclass; // from the mSubclass member of the "enclosing" + // Interface Descriptor, not the stream. + protected byte mSubtype; // 2:1 HEADER descriptor subtype - UsbACEndpoint(int length, byte type, byte subclass) { + UsbACEndpoint(int length, byte type, int subclass) { super(length, type); mSubclass = subclass; } - public byte getSubclass() { + public int getSubclass() { return mSubclass; } @@ -52,7 +52,7 @@ abstract class UsbACEndpoint extends UsbDescriptor { public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser, int length, byte type) { UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface(); - byte subClass = interfaceDesc.getUsbSubclass(); + int subClass = interfaceDesc.getUsbSubclass(); switch (subClass) { case AUDIO_AUDIOCONTROL: return new UsbACAudioControlEndpoint(length, type, subClass); diff --git a/com/android/server/usb/descriptors/UsbACFeatureUnit.java b/com/android/server/usb/descriptors/UsbACFeatureUnit.java index ab3903b4..2c7ef798 100644 --- a/com/android/server/usb/descriptors/UsbACFeatureUnit.java +++ b/com/android/server/usb/descriptors/UsbACFeatureUnit.java @@ -46,7 +46,7 @@ public final class UsbACFeatureUnit extends UsbACInterface { // logical channel private byte mUnitName; // ?:1 Index of a string descriptor, describing this Feature Unit. - public UsbACFeatureUnit(int length, byte type, byte subtype, byte subClass) { + public UsbACFeatureUnit(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } diff --git a/com/android/server/usb/descriptors/UsbACHeaderInterface.java b/com/android/server/usb/descriptors/UsbACHeaderInterface.java index 01a355e2..88d026eb 100644 --- a/com/android/server/usb/descriptors/UsbACHeaderInterface.java +++ b/com/android/server/usb/descriptors/UsbACHeaderInterface.java @@ -31,7 +31,7 @@ public abstract class UsbACHeaderInterface extends UsbACInterface { // of this descriptor header and all Unit and Terminal descriptors. public UsbACHeaderInterface( - int length, byte type, byte subtype, byte subclass, int adcRelease) { + int length, byte type, byte subtype, int subclass, int adcRelease) { super(length, type, subtype, subclass); mADCRelease = adcRelease; } diff --git a/com/android/server/usb/descriptors/UsbACInterface.java b/com/android/server/usb/descriptors/UsbACInterface.java index df6c53fa..38c12a1f 100644 --- a/com/android/server/usb/descriptors/UsbACInterface.java +++ b/com/android/server/usb/descriptors/UsbACInterface.java @@ -78,10 +78,10 @@ public abstract class UsbACInterface extends UsbDescriptor { public static final int FORMAT_III_IEC1937_MPEG2_Layer1LS = 0x2005; protected final byte mSubtype; // 2:1 HEADER descriptor subtype - protected final byte mSubclass; // from the mSubclass member of the + protected final int mSubclass; // from the mSubclass member of the // "enclosing" Interface Descriptor - public UsbACInterface(int length, byte type, byte subtype, byte subclass) { + public UsbACInterface(int length, byte type, byte subtype, int subclass) { super(length, type); mSubtype = subtype; mSubclass = subclass; @@ -91,12 +91,12 @@ public abstract class UsbACInterface extends UsbDescriptor { return mSubtype; } - public byte getSubclass() { + public int getSubclass() { return mSubclass; } private static UsbDescriptor allocAudioControlDescriptor(UsbDescriptorParser parser, - ByteStream stream, int length, byte type, byte subtype, byte subClass) { + ByteStream stream, int length, byte type, byte subtype, int subClass) { switch (subtype) { case ACI_HEADER: { @@ -157,7 +157,7 @@ public abstract class UsbACInterface extends UsbDescriptor { } private static UsbDescriptor allocAudioStreamingDescriptor(UsbDescriptorParser parser, - ByteStream stream, int length, byte type, byte subtype, byte subClass) { + ByteStream stream, int length, byte type, byte subtype, int subClass) { //int spec = parser.getUsbSpec(); int acInterfaceSpec = parser.getACInterfaceSpec(); switch (subtype) { @@ -182,7 +182,7 @@ public abstract class UsbACInterface extends UsbDescriptor { } private static UsbDescriptor allocMidiStreamingDescriptor(int length, byte type, - byte subtype, byte subClass) { + byte subtype, int subClass) { switch (subtype) { case MSI_HEADER: return new UsbMSMidiHeader(length, type, subtype, subClass); @@ -212,7 +212,7 @@ public abstract class UsbACInterface extends UsbDescriptor { int length, byte type) { byte subtype = stream.getByte(); UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface(); - byte subClass = interfaceDesc.getUsbSubclass(); + int subClass = interfaceDesc.getUsbSubclass(); switch (subClass) { case AUDIO_AUDIOCONTROL: return allocAudioControlDescriptor( @@ -236,7 +236,7 @@ public abstract class UsbACInterface extends UsbDescriptor { public void report(ReportCanvas canvas) { super.report(canvas); - byte subClass = getSubclass(); + int subClass = getSubclass(); String subClassName = UsbStrings.getACInterfaceSubclassName(subClass); byte subtype = getSubtype(); diff --git a/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java b/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java index 9e00a797..bd027aeb 100644 --- a/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java +++ b/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java @@ -22,7 +22,7 @@ package com.android.server.usb.descriptors; public final class UsbACInterfaceUnparsed extends UsbACInterface { private static final String TAG = "UsbACInterfaceUnparsed"; - public UsbACInterfaceUnparsed(int length, byte type, byte subtype, byte subClass) { + public UsbACInterfaceUnparsed(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } } diff --git a/com/android/server/usb/descriptors/UsbACMidiEndpoint.java b/com/android/server/usb/descriptors/UsbACMidiEndpoint.java index 9c314575..42ee8892 100644 --- a/com/android/server/usb/descriptors/UsbACMidiEndpoint.java +++ b/com/android/server/usb/descriptors/UsbACMidiEndpoint.java @@ -28,7 +28,7 @@ public final class UsbACMidiEndpoint extends UsbACEndpoint { private byte mNumJacks; private byte[] mJackIds; - public UsbACMidiEndpoint(int length, byte type, byte subclass) { + public UsbACMidiEndpoint(int length, byte type, int subclass) { super(length, type, subclass); } diff --git a/com/android/server/usb/descriptors/UsbACMixerUnit.java b/com/android/server/usb/descriptors/UsbACMixerUnit.java index 88faed96..606fa2b4 100644 --- a/com/android/server/usb/descriptors/UsbACMixerUnit.java +++ b/com/android/server/usb/descriptors/UsbACMixerUnit.java @@ -24,7 +24,7 @@ public class UsbACMixerUnit extends UsbACInterface { // are connected. protected byte mNumOutputs; // The number of output channels - public UsbACMixerUnit(int length, byte type, byte subtype, byte subClass) { + public UsbACMixerUnit(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } diff --git a/com/android/server/usb/descriptors/UsbACSelectorUnit.java b/com/android/server/usb/descriptors/UsbACSelectorUnit.java index b16bc575..4644fe14 100644 --- a/com/android/server/usb/descriptors/UsbACSelectorUnit.java +++ b/com/android/server/usb/descriptors/UsbACSelectorUnit.java @@ -32,7 +32,7 @@ public final class UsbACSelectorUnit extends UsbACInterface { // Input Pin of this Selector Unit is connected. private byte mNameIndex; // Index of a string descriptor, describing the Selector Unit. - public UsbACSelectorUnit(int length, byte type, byte subtype, byte subClass) { + public UsbACSelectorUnit(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } diff --git a/com/android/server/usb/descriptors/UsbACTerminal.java b/com/android/server/usb/descriptors/UsbACTerminal.java index 28365085..36139d6c 100644 --- a/com/android/server/usb/descriptors/UsbACTerminal.java +++ b/com/android/server/usb/descriptors/UsbACTerminal.java @@ -32,7 +32,7 @@ public abstract class UsbACTerminal extends UsbACInterface { protected int mTerminalType; // 4:2 USB Streaming. (0x0101) protected byte mAssocTerminal; // 6:1 Unused (0x00) - public UsbACTerminal(int length, byte type, byte subtype, byte subclass) { + public UsbACTerminal(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/com/android/server/usb/descriptors/UsbASFormat.java b/com/android/server/usb/descriptors/UsbASFormat.java index 7a92f9e1..5e515a14 100644 --- a/com/android/server/usb/descriptors/UsbASFormat.java +++ b/com/android/server/usb/descriptors/UsbASFormat.java @@ -40,7 +40,7 @@ public class UsbASFormat extends UsbACInterface { public static final byte EXT_FORMAT_TYPE_II = (byte) 0x82; public static final byte EXT_FORMAT_TYPE_III = (byte) 0x83; - public UsbASFormat(int length, byte type, byte subtype, byte formatType, byte mSubclass) { + public UsbASFormat(int length, byte type, byte subtype, byte formatType, int mSubclass) { super(length, type, subtype, mSubclass); mFormatType = formatType; } @@ -66,8 +66,8 @@ public class UsbASFormat extends UsbACInterface { * stream. */ public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser, - ByteStream stream, int length, byte type, - byte subtype, byte subclass) { + ByteStream stream, int length, byte type, + byte subtype, int subclass) { byte formatType = stream.getByte(); int acInterfaceSpec = parser.getACInterfaceSpec(); diff --git a/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/com/android/server/usb/descriptors/UsbConfigDescriptor.java index 75279c61..993778fa 100644 --- a/com/android/server/usb/descriptors/UsbConfigDescriptor.java +++ b/com/android/server/usb/descriptors/UsbConfigDescriptor.java @@ -15,8 +15,13 @@ */ package com.android.server.usb.descriptors; +import android.hardware.usb.UsbConfiguration; +import android.hardware.usb.UsbInterface; + import com.android.server.usb.descriptors.report.ReportCanvas; +import java.util.ArrayList; + /** * @hide * An USB Config Descriptor. @@ -25,15 +30,18 @@ import com.android.server.usb.descriptors.report.ReportCanvas; public final class UsbConfigDescriptor extends UsbDescriptor { private static final String TAG = "UsbConfigDescriptor"; - private int mTotalLength; // 2:2 Total length in bytes of data returned + private int mTotalLength; // 2:2 Total length in bytes of data returned private byte mNumInterfaces; // 4:1 Number of Interfaces - private byte mConfigValue; // 5:1 Value to use as an argument to select this configuration - private byte mConfigIndex; // 6:1 Index of String Descriptor describing this configuration - private byte mAttribs; // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered) - // D6 Self Powered - // D5 Remote Wakeup - // D4..0 Reserved, set to 0. - private byte mMaxPower; // 8:1 Maximum Power Consumption in 2mA units + private int mConfigValue; // 5:1 Value to use as an argument to select this configuration + private byte mConfigIndex; // 6:1 Index of String Descriptor describing this configuration + private int mAttribs; // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered) + // D6 Self Powered + // D5 Remote Wakeup + // D4..0 Reserved, set to 0. + private int mMaxPower; // 8:1 Maximum Power Consumption in 2mA units + + private ArrayList<UsbInterfaceDescriptor> mInterfaceDescriptors = + new ArrayList<UsbInterfaceDescriptor>(); UsbConfigDescriptor(int length, byte type) { super(length, type); @@ -48,7 +56,7 @@ public final class UsbConfigDescriptor extends UsbDescriptor { return mNumInterfaces; } - public byte getConfigValue() { + public int getConfigValue() { return mConfigValue; } @@ -56,22 +64,38 @@ public final class UsbConfigDescriptor extends UsbDescriptor { return mConfigIndex; } - public byte getAttribs() { + public int getAttribs() { return mAttribs; } - public byte getMaxPower() { + public int getMaxPower() { return mMaxPower; } + void addInterfaceDescriptor(UsbInterfaceDescriptor interfaceDesc) { + mInterfaceDescriptors.add(interfaceDesc); + } + + UsbConfiguration toAndroid(UsbDescriptorParser parser) { + String name = parser.getDescriptorString(mConfigIndex); + UsbConfiguration config = new + UsbConfiguration(mConfigValue, name, mAttribs, mMaxPower); + UsbInterface[] interfaces = new UsbInterface[mInterfaceDescriptors.size()]; + for (int index = 0; index < mInterfaceDescriptors.size(); index++) { + interfaces[index] = mInterfaceDescriptors.get(index).toAndroid(parser); + } + config.setInterfaces(interfaces); + return config; + } + @Override public int parseRawDescriptors(ByteStream stream) { mTotalLength = stream.unpackUsbShort(); mNumInterfaces = stream.getByte(); - mConfigValue = stream.getByte(); + mConfigValue = stream.getUnsignedByte(); mConfigIndex = stream.getByte(); - mAttribs = stream.getByte(); - mMaxPower = stream.getByte(); + mAttribs = stream.getUnsignedByte(); + mMaxPower = stream.getUnsignedByte(); return mLength; } diff --git a/com/android/server/usb/descriptors/UsbDescriptor.java b/com/android/server/usb/descriptors/UsbDescriptor.java index 8c7565b7..3fc5fe32 100644 --- a/com/android/server/usb/descriptors/UsbDescriptor.java +++ b/com/android/server/usb/descriptors/UsbDescriptor.java @@ -85,36 +85,36 @@ public abstract class UsbDescriptor implements Reporting { public static final byte DESCRIPTORTYPE_ENDPOINT_COMPANION = 0x30; // 48 // Class IDs - public static final byte CLASSID_DEVICE = 0x00; - public static final byte CLASSID_AUDIO = 0x01; - public static final byte CLASSID_COM = 0x02; - public static final byte CLASSID_HID = 0x03; - // public static final byte CLASSID_??? = 0x04; - public static final byte CLASSID_PHYSICAL = 0x05; - public static final byte CLASSID_IMAGE = 0x06; - public static final byte CLASSID_PRINTER = 0x07; - public static final byte CLASSID_STORAGE = 0x08; - public static final byte CLASSID_HUB = 0x09; - public static final byte CLASSID_CDC_CONTROL = 0x0A; - public static final byte CLASSID_SMART_CARD = 0x0B; - //public static final byte CLASSID_??? = 0x0C; - public static final byte CLASSID_SECURITY = 0x0D; - public static final byte CLASSID_VIDEO = 0x0E; - public static final byte CLASSID_HEALTHCARE = 0x0F; - public static final byte CLASSID_AUDIOVIDEO = 0x10; - public static final byte CLASSID_BILLBOARD = 0x11; - public static final byte CLASSID_TYPECBRIDGE = 0x12; - public static final byte CLASSID_DIAGNOSTIC = (byte) 0xDC; - public static final byte CLASSID_WIRELESS = (byte) 0xE0; - public static final byte CLASSID_MISC = (byte) 0xEF; - public static final byte CLASSID_APPSPECIFIC = (byte) 0xFE; - public static final byte CLASSID_VENDSPECIFIC = (byte) 0xFF; + public static final int CLASSID_DEVICE = 0x00; + public static final int CLASSID_AUDIO = 0x01; + public static final int CLASSID_COM = 0x02; + public static final int CLASSID_HID = 0x03; + // public static final int CLASSID_??? = 0x04; + public static final int CLASSID_PHYSICAL = 0x05; + public static final int CLASSID_IMAGE = 0x06; + public static final int CLASSID_PRINTER = 0x07; + public static final int CLASSID_STORAGE = 0x08; + public static final int CLASSID_HUB = 0x09; + public static final int CLASSID_CDC_CONTROL = 0x0A; + public static final int CLASSID_SMART_CARD = 0x0B; + //public static final int CLASSID_??? = 0x0C; + public static final int CLASSID_SECURITY = 0x0D; + public static final int CLASSID_VIDEO = 0x0E; + public static final int CLASSID_HEALTHCARE = 0x0F; + public static final int CLASSID_AUDIOVIDEO = 0x10; + public static final int CLASSID_BILLBOARD = 0x11; + public static final int CLASSID_TYPECBRIDGE = 0x12; + public static final int CLASSID_DIAGNOSTIC = 0xDC; + public static final int CLASSID_WIRELESS = 0xE0; + public static final int CLASSID_MISC = 0xEF; + public static final int CLASSID_APPSPECIFIC = 0xFE; + public static final int CLASSID_VENDSPECIFIC = 0xFF; // Audio Subclass codes - public static final byte AUDIO_SUBCLASS_UNDEFINED = 0x00; - public static final byte AUDIO_AUDIOCONTROL = 0x01; - public static final byte AUDIO_AUDIOSTREAMING = 0x02; - public static final byte AUDIO_MIDISTREAMING = 0x03; + public static final int AUDIO_SUBCLASS_UNDEFINED = 0x00; + public static final int AUDIO_AUDIOCONTROL = 0x01; + public static final int AUDIO_AUDIOSTREAMING = 0x02; + public static final int AUDIO_MIDISTREAMING = 0x03; // Request IDs public static final int REQUEST_GET_STATUS = 0x00; diff --git a/com/android/server/usb/descriptors/UsbDescriptorParser.java b/com/android/server/usb/descriptors/UsbDescriptorParser.java index ad7bde5c..6c6bd013 100644 --- a/com/android/server/usb/descriptors/UsbDescriptorParser.java +++ b/com/android/server/usb/descriptors/UsbDescriptorParser.java @@ -15,6 +15,7 @@ */ package com.android.server.usb.descriptors; +import android.hardware.usb.UsbDevice; import android.util.Log; import java.util.ArrayList; @@ -25,11 +26,16 @@ import java.util.ArrayList; */ public final class UsbDescriptorParser { private static final String TAG = "UsbDescriptorParser"; + private static final boolean DEBUG = false; + + private final String mDeviceAddr; // Descriptor Objects + private static final int DESCRIPTORS_ALLOC_SIZE = 128; private ArrayList<UsbDescriptor> mDescriptors = new ArrayList<UsbDescriptor>(); private UsbDeviceDescriptor mDeviceDescriptor; + private UsbConfigDescriptor mCurConfigDescriptor; private UsbInterfaceDescriptor mCurInterfaceDescriptor; // The AudioClass spec implemented by the AudioClass Interfaces @@ -37,7 +43,13 @@ public final class UsbDescriptorParser { // Obtained from the first AudioClass Header descriptor. private int mACInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0; - public UsbDescriptorParser() {} + public UsbDescriptorParser(String deviceAddr) { + mDeviceAddr = deviceAddr; + } + + public String getDeviceAddr() { + return mDeviceAddr; + } /** * @return the USB Spec value associated with the Device descriptor for the @@ -60,6 +72,18 @@ public final class UsbDescriptorParser { public int getACInterfaceSpec() { return mACInterfacesSpec; } + + private class UsbDescriptorsStreamFormatException extends Exception { + String mMessage; + UsbDescriptorsStreamFormatException(String message) { + mMessage = message; + } + + public String toString() { + return "Descriptor Stream Format Exception: " + mMessage; + } + } + /** * The probability (as returned by getHeadsetProbability() at which we conclude * the peripheral is a headset. @@ -67,7 +91,8 @@ public final class UsbDescriptorParser { private static final float IN_HEADSET_TRIGGER = 0.75f; private static final float OUT_HEADSET_TRIGGER = 0.75f; - private UsbDescriptor allocDescriptor(ByteStream stream) { + private UsbDescriptor allocDescriptor(ByteStream stream) + throws UsbDescriptorsStreamFormatException { stream.resetReadCount(); int length = stream.getUnsignedByte(); @@ -83,15 +108,38 @@ public final class UsbDescriptorParser { break; case UsbDescriptor.DESCRIPTORTYPE_CONFIG: - descriptor = new UsbConfigDescriptor(length, type); + descriptor = mCurConfigDescriptor = new UsbConfigDescriptor(length, type); + if (mDeviceDescriptor != null) { + mDeviceDescriptor.addConfigDescriptor(mCurConfigDescriptor); + } else { + Log.e(TAG, "Config Descriptor found with no associated Device Descriptor!"); + throw new UsbDescriptorsStreamFormatException( + "Config Descriptor found with no associated Device Descriptor!"); + } break; case UsbDescriptor.DESCRIPTORTYPE_INTERFACE: descriptor = mCurInterfaceDescriptor = new UsbInterfaceDescriptor(length, type); + if (mCurConfigDescriptor != null) { + mCurConfigDescriptor.addInterfaceDescriptor(mCurInterfaceDescriptor); + } else { + Log.e(TAG, "Interface Descriptor found with no associated Config Descriptor!"); + throw new UsbDescriptorsStreamFormatException( + "Interface Descriptor found with no associated Config Descriptor!"); + } break; case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT: descriptor = new UsbEndpointDescriptor(length, type); + if (mCurInterfaceDescriptor != null) { + mCurInterfaceDescriptor.addEndpointDescriptor( + (UsbEndpointDescriptor) descriptor); + } else { + Log.e(TAG, + "Endpoint Descriptor found with no associated Interface Descriptor!"); + throw new UsbDescriptorsStreamFormatException( + "Endpoint Descriptor found with no associated Interface Descriptor!"); + } break; /* @@ -144,8 +192,12 @@ public final class UsbDescriptorParser { /** * @hide */ - public void parseDescriptors(byte[] descriptors) { - mDescriptors.clear(); + public boolean parseDescriptors(byte[] descriptors) { + if (DEBUG) { + Log.d(TAG, "parseDescriptors() - start"); + } + // This will allow us to (probably) alloc mDescriptors just once. + mDescriptors = new ArrayList<UsbDescriptor>(DESCRIPTORS_ALLOC_SIZE); ByteStream stream = new ByteStream(descriptors); while (stream.available() > 0) { @@ -173,21 +225,36 @@ public final class UsbDescriptorParser { } } } + if (DEBUG) { + Log.d(TAG, "parseDescriptors() - end " + mDescriptors.size() + " descriptors."); + } + return true; } /** * @hide */ - public boolean parseDevice(String deviceAddr) { - byte[] rawDescriptors = getRawDescriptors(deviceAddr); - if (rawDescriptors != null) { - parseDescriptors(rawDescriptors); - return true; - } - return false; + public boolean parseDevice() { + byte[] rawDescriptors = getRawDescriptors(); + + return rawDescriptors != null + ? parseDescriptors(rawDescriptors) : false; + } + + private byte[] getRawDescriptors() { + return getRawDescriptors_native(mDeviceAddr); + } + + private native byte[] getRawDescriptors_native(String deviceAddr); + + /** + * @hide + */ + public String getDescriptorString(int stringId) { + return getDescriptorString_native(mDeviceAddr, stringId); } - private native byte[] getRawDescriptors(String deviceAddr); + private native String getDescriptorString_native(String deviceAddr, int stringId); public int getParsingSpec() { return mDeviceDescriptor != null ? mDeviceDescriptor.getSpec() : 0; @@ -200,6 +267,17 @@ public final class UsbDescriptorParser { /** * @hide */ + public UsbDevice toAndroidUsbDevice() { + if (mDeviceDescriptor == null) { + return null; + } + + return mDeviceDescriptor.toAndroid(this); + } + + /** + * @hide + */ public ArrayList<UsbDescriptor> getDescriptors(byte type) { ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); for (UsbDescriptor descriptor : mDescriptors) { @@ -213,7 +291,7 @@ public final class UsbDescriptorParser { /** * @hide */ - public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(byte usbClass) { + public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(int usbClass) { ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); for (UsbDescriptor descriptor : mDescriptors) { // ensure that this isn't an unrecognized DESCRIPTORTYPE_INTERFACE @@ -235,7 +313,7 @@ public final class UsbDescriptorParser { /** * @hide */ - public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, byte subclass) { + public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, int subclass) { ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); for (UsbDescriptor descriptor : mDescriptors) { if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE) { @@ -355,8 +433,6 @@ public final class UsbDescriptorParser { * to count on the peripheral being a headset. */ public boolean isInputHeadset() { - // TEMP - Log.i(TAG, "---- isInputHeadset() prob:" + (getInputHeadsetProbability() * 100f) + "%"); return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER; } @@ -410,8 +486,6 @@ public final class UsbDescriptorParser { * to count on the peripheral being a headset. */ public boolean isOutputHeadset() { - // TEMP - Log.i(TAG, "---- isOutputHeadset() prob:" + (getOutputHeadsetProbability() * 100f) + "%"); return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER; } diff --git a/com/android/server/usb/descriptors/UsbDeviceDescriptor.java b/com/android/server/usb/descriptors/UsbDeviceDescriptor.java index d5cb89ea..8e7f0fde 100644 --- a/com/android/server/usb/descriptors/UsbDeviceDescriptor.java +++ b/com/android/server/usb/descriptors/UsbDeviceDescriptor.java @@ -15,9 +15,14 @@ */ package com.android.server.usb.descriptors; +import android.hardware.usb.UsbConfiguration; +import android.hardware.usb.UsbDevice; + import com.android.server.usb.descriptors.report.ReportCanvas; import com.android.server.usb.descriptors.report.UsbStrings; +import java.util.ArrayList; + /** * @hide * A USB Device Descriptor. @@ -31,9 +36,9 @@ public final class UsbDeviceDescriptor extends UsbDescriptor { public static final int USBSPEC_2_0 = 0x0200; private int mSpec; // 2:2 bcdUSB 2 BCD USB Specification Number - BCD - private byte mDevClass; // 4:1 class code - private byte mDevSubClass; // 5:1 subclass code - private byte mProtocol; // 6:1 protocol + private int mDevClass; // 4:1 class code + private int mDevSubClass; // 5:1 subclass code + private int mProtocol; // 6:1 protocol private byte mPacketSize; // 7:1 Maximum Packet Size for Zero Endpoint. // Valid Sizes are 8, 16, 32, 64 private int mVendorID; // 8:2 vendor ID @@ -44,6 +49,9 @@ public final class UsbDeviceDescriptor extends UsbDescriptor { private byte mSerialNum; // 16:1 Index of Serial Number String Descriptor private byte mNumConfigs; // 17:1 Number of Possible Configurations + private ArrayList<UsbConfigDescriptor> mConfigDescriptors = + new ArrayList<UsbConfigDescriptor>(); + UsbDeviceDescriptor(int length, byte type) { super(length, type); mHierarchyLevel = 1; @@ -53,15 +61,15 @@ public final class UsbDeviceDescriptor extends UsbDescriptor { return mSpec; } - public byte getDevClass() { + public int getDevClass() { return mDevClass; } - public byte getDevSubClass() { + public int getDevSubClass() { return mDevSubClass; } - public byte getProtocol() { + public int getProtocol() { return mProtocol; } @@ -97,12 +105,41 @@ public final class UsbDeviceDescriptor extends UsbDescriptor { return mNumConfigs; } + void addConfigDescriptor(UsbConfigDescriptor config) { + mConfigDescriptors.add(config); + } + + /** + * @hide + */ + public UsbDevice toAndroid(UsbDescriptorParser parser) { + String mfgName = parser.getDescriptorString(mMfgIndex); + String prodName = parser.getDescriptorString(mProductIndex); + + // Create version string in "%.%" format + String versionString = + Integer.toString(mDeviceRelease >> 8) + "." + (mDeviceRelease & 0xFF); + String serialStr = parser.getDescriptorString(mSerialNum); + + UsbDevice device = new UsbDevice(parser.getDeviceAddr(), mVendorID, mProductID, + mDevClass, mDevSubClass, + mProtocol, mfgName, prodName, + versionString, serialStr); + UsbConfiguration[] configs = new UsbConfiguration[mConfigDescriptors.size()]; + for (int index = 0; index < mConfigDescriptors.size(); index++) { + configs[index] = mConfigDescriptors.get(index).toAndroid(parser); + } + device.setConfigurations(configs); + + return device; + } + @Override public int parseRawDescriptors(ByteStream stream) { mSpec = stream.unpackUsbShort(); - mDevClass = stream.getByte(); - mDevSubClass = stream.getByte(); - mProtocol = stream.getByte(); + mDevClass = stream.getUnsignedByte(); + mDevSubClass = stream.getUnsignedByte(); + mProtocol = stream.getUnsignedByte(); mPacketSize = stream.getByte(); mVendorID = stream.unpackUsbShort(); mProductID = stream.unpackUsbShort(); @@ -124,9 +161,9 @@ public final class UsbDeviceDescriptor extends UsbDescriptor { int spec = getSpec(); canvas.writeListItem("Spec: " + ReportCanvas.getBCDString(spec)); - byte devClass = getDevClass(); + int devClass = getDevClass(); String classStr = UsbStrings.getClassName(devClass); - byte devSubClass = getDevSubClass(); + int devSubClass = getDevSubClass(); String subClasStr = UsbStrings.getClassName(devSubClass); canvas.writeListItem("Class " + devClass + ": " + classStr + " Subclass" + devSubClass + ": " + subClasStr); @@ -134,12 +171,11 @@ public final class UsbDeviceDescriptor extends UsbDescriptor { + " Product ID: " + ReportCanvas.getHexString(getProductID()) + " Product Release: " + ReportCanvas.getBCDString(getDeviceRelease())); + UsbDescriptorParser parser = canvas.getParser(); byte mfgIndex = getMfgIndex(); - String manufacturer = - UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), mfgIndex); + String manufacturer = parser.getDescriptorString(mfgIndex); byte productIndex = getProductIndex(); - String product = - UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), productIndex); + String product = parser.getDescriptorString(productIndex); canvas.writeListItem("Manufacturer " + mfgIndex + ": " + manufacturer + " Product " + productIndex + ": " + product); diff --git a/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/com/android/server/usb/descriptors/UsbEndpointDescriptor.java index 6322fbe8..11302380 100644 --- a/com/android/server/usb/descriptors/UsbEndpointDescriptor.java +++ b/com/android/server/usb/descriptors/UsbEndpointDescriptor.java @@ -15,6 +15,8 @@ */ package com.android.server.usb.descriptors; +import android.hardware.usb.UsbEndpoint; + import com.android.server.usb.descriptors.report.ReportCanvas; /** @@ -25,16 +27,16 @@ import com.android.server.usb.descriptors.report.ReportCanvas; public class UsbEndpointDescriptor extends UsbDescriptor { private static final String TAG = "UsbEndpointDescriptor"; - public static final byte MASK_ENDPOINT_ADDRESS = 0b0001111; - public static final byte MASK_ENDPOINT_DIRECTION = (byte) 0b10000000; - public static final byte DIRECTION_OUTPUT = 0x00; - public static final byte DIRECTION_INPUT = (byte) 0x80; + public static final int MASK_ENDPOINT_ADDRESS = 0b000000000001111; + public static final int MASK_ENDPOINT_DIRECTION = (byte) 0b0000000010000000; + public static final int DIRECTION_OUTPUT = 0x0000; + public static final int DIRECTION_INPUT = (byte) 0x0080; - public static final byte MASK_ATTRIBS_TRANSTYPE = 0b00000011; - public static final byte TRANSTYPE_CONTROL = 0x00; - public static final byte TRANSTYPE_ISO = 0x01; - public static final byte TRANSTYPE_BULK = 0x02; - public static final byte TRANSTYPE_INTERRUPT = 0x03; + public static final int MASK_ATTRIBS_TRANSTYPE = 0b00000011; + public static final int TRANSTYPE_CONTROL = 0x00; + public static final int TRANSTYPE_ISO = 0x01; + public static final int TRANSTYPE_BULK = 0x02; + public static final int TRANSTYPE_INTERRUPT = 0x03; public static final byte MASK_ATTRIBS_SYNCTYPE = 0b00001100; public static final byte SYNCTYPE_NONE = 0b00000000; @@ -42,18 +44,18 @@ public class UsbEndpointDescriptor extends UsbDescriptor { public static final byte SYNCTYPE_ADAPTSYNC = 0b00001000; public static final byte SYNCTYPE_RESERVED = 0b00001100; - public static final byte MASK_ATTRIBS_USEAGE = 0b00110000; - public static final byte USEAGE_DATA = 0b00000000; - public static final byte USEAGE_FEEDBACK = 0b00010000; - public static final byte USEAGE_EXPLICIT = 0b00100000; - public static final byte USEAGE_RESERVED = 0b00110000; + public static final int MASK_ATTRIBS_USEAGE = 0b00110000; + public static final int USEAGE_DATA = 0b00000000; + public static final int USEAGE_FEEDBACK = 0b00010000; + public static final int USEAGE_EXPLICIT = 0b00100000; + public static final int USEAGE_RESERVED = 0b00110000; - private byte mEndpointAddress; // 2:1 Endpoint Address + private int mEndpointAddress; // 2:1 Endpoint Address // Bits 0..3b Endpoint Number. // Bits 4..6b Reserved. Set to Zero // Bits 7 Direction 0 = Out, 1 = In // (Ignored for Control Endpoints) - private byte mAttributes; // 3:1 Various flags + private int mAttributes; // 3:1 Various flags // Bits 0..1 Transfer Type: // 00 = Control, 01 = Isochronous, 10 = Bulk, 11 = Interrupt // Bits 2..7 are reserved. If Isochronous endpoint, @@ -69,7 +71,7 @@ public class UsbEndpointDescriptor extends UsbDescriptor { // 11: Reserved private int mPacketSize; // 4:2 Maximum Packet Size this endpoint is capable of // sending or receiving - private byte mInterval; // 6:1 Interval for polling endpoint data transfers. Value in + private int mInterval; // 6:1 Interval for polling endpoint data transfers. Value in // frame counts. // Ignored for Bulk & Control Endpoints. Isochronous must equal // 1 and field may range from 1 to 255 for interrupt endpoints. @@ -81,11 +83,11 @@ public class UsbEndpointDescriptor extends UsbDescriptor { mHierarchyLevel = 4; } - public byte getEndpointAddress() { + public int getEndpointAddress() { return mEndpointAddress; } - public byte getAttributes() { + public int getAttributes() { return mAttributes; } @@ -93,7 +95,7 @@ public class UsbEndpointDescriptor extends UsbDescriptor { return mPacketSize; } - public byte getInterval() { + public int getInterval() { return mInterval; } @@ -105,12 +107,16 @@ public class UsbEndpointDescriptor extends UsbDescriptor { return mSyncAddress; } + /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) { + return new UsbEndpoint(mEndpointAddress, mAttributes, mPacketSize, mInterval); + } + @Override public int parseRawDescriptors(ByteStream stream) { - mEndpointAddress = stream.getByte(); - mAttributes = stream.getByte(); + mEndpointAddress = stream.getUnsignedByte(); + mAttributes = stream.getUnsignedByte(); mPacketSize = stream.unpackUsbShort(); - mInterval = stream.getByte(); + mInterval = stream.getUnsignedByte(); if (mLength == 9) { mRefresh = stream.getByte(); mSyncAddress = stream.getByte(); @@ -124,13 +130,13 @@ public class UsbEndpointDescriptor extends UsbDescriptor { canvas.openList(); - byte address = getEndpointAddress(); + int address = getEndpointAddress(); canvas.writeListItem("Address: " + ReportCanvas.getHexString(address & UsbEndpointDescriptor.MASK_ENDPOINT_ADDRESS) + ((address & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION) == UsbEndpointDescriptor.DIRECTION_OUTPUT ? " [out]" : " [in]")); - byte attributes = getAttributes(); + int attributes = getAttributes(); canvas.openListItem(); canvas.write("Attributes: " + ReportCanvas.getHexString(attributes) + " "); switch (attributes & UsbEndpointDescriptor.MASK_ATTRIBS_TRANSTYPE) { diff --git a/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java index 4eef6caf..d87b1afb 100644 --- a/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java +++ b/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java @@ -15,9 +15,14 @@ */ package com.android.server.usb.descriptors; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; + import com.android.server.usb.descriptors.report.ReportCanvas; import com.android.server.usb.descriptors.report.UsbStrings; +import java.util.ArrayList; + /** * @hide * A common super-class for all USB Interface Descritor subtypes. @@ -26,14 +31,17 @@ import com.android.server.usb.descriptors.report.UsbStrings; public class UsbInterfaceDescriptor extends UsbDescriptor { private static final String TAG = "UsbInterfaceDescriptor"; - protected byte mInterfaceNumber; // 2:1 Number of Interface + protected int mInterfaceNumber; // 2:1 Number of Interface protected byte mAlternateSetting; // 3:1 Value used to select alternative setting protected byte mNumEndpoints; // 4:1 Number of Endpoints used for this interface - protected byte mUsbClass; // 5:1 Class Code - protected byte mUsbSubclass; // 6:1 Subclass Code - protected byte mProtocol; // 7:1 Protocol Code + protected int mUsbClass; // 5:1 Class Code + protected int mUsbSubclass; // 6:1 Subclass Code + protected int mProtocol; // 7:1 Protocol Code protected byte mDescrIndex; // 8:1 Index of String Descriptor Describing this interface + private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors = + new ArrayList<UsbEndpointDescriptor>(); + UsbInterfaceDescriptor(int length, byte type) { super(length, type); mHierarchyLevel = 3; @@ -41,18 +49,18 @@ public class UsbInterfaceDescriptor extends UsbDescriptor { @Override public int parseRawDescriptors(ByteStream stream) { - mInterfaceNumber = stream.getByte(); + mInterfaceNumber = stream.getUnsignedByte(); mAlternateSetting = stream.getByte(); mNumEndpoints = stream.getByte(); - mUsbClass = stream.getByte(); - mUsbSubclass = stream.getByte(); - mProtocol = stream.getByte(); + mUsbClass = stream.getUnsignedByte(); + mUsbSubclass = stream.getUnsignedByte(); + mProtocol = stream.getUnsignedByte(); mDescrIndex = stream.getByte(); return mLength; } - public byte getInterfaceNumber() { + public int getInterfaceNumber() { return mInterfaceNumber; } @@ -64,15 +72,15 @@ public class UsbInterfaceDescriptor extends UsbDescriptor { return mNumEndpoints; } - public byte getUsbClass() { + public int getUsbClass() { return mUsbClass; } - public byte getUsbSubclass() { + public int getUsbSubclass() { return mUsbSubclass; } - public byte getProtocol() { + public int getProtocol() { return mProtocol; } @@ -80,13 +88,29 @@ public class UsbInterfaceDescriptor extends UsbDescriptor { return mDescrIndex; } + void addEndpointDescriptor(UsbEndpointDescriptor endpoint) { + mEndpointDescriptors.add(endpoint); + } + + UsbInterface toAndroid(UsbDescriptorParser parser) { + String name = parser.getDescriptorString(mDescrIndex); + UsbInterface ntrface = new UsbInterface( + mInterfaceNumber, mAlternateSetting, name, mUsbClass, mUsbSubclass, mProtocol); + UsbEndpoint[] endpoints = new UsbEndpoint[mEndpointDescriptors.size()]; + for (int index = 0; index < mEndpointDescriptors.size(); index++) { + endpoints[index] = mEndpointDescriptors.get(index).toAndroid(parser); + } + ntrface.setEndpoints(endpoints); + return ntrface; + } + @Override public void report(ReportCanvas canvas) { super.report(canvas); - byte usbClass = getUsbClass(); - byte usbSubclass = getUsbSubclass(); - byte protocol = getProtocol(); + int usbClass = getUsbClass(); + int usbSubclass = getUsbSubclass(); + int protocol = getProtocol(); String className = UsbStrings.getClassName(usbClass); String subclassName = ""; if (usbClass == UsbDescriptor.CLASSID_AUDIO) { diff --git a/com/android/server/usb/descriptors/UsbMSMidiHeader.java b/com/android/server/usb/descriptors/UsbMSMidiHeader.java index 85a3e680..d0ca6db8 100644 --- a/com/android/server/usb/descriptors/UsbMSMidiHeader.java +++ b/com/android/server/usb/descriptors/UsbMSMidiHeader.java @@ -25,7 +25,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas; public final class UsbMSMidiHeader extends UsbACInterface { private static final String TAG = "UsbMSMidiHeader"; - public UsbMSMidiHeader(int length, byte type, byte subtype, byte subclass) { + public UsbMSMidiHeader(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/com/android/server/usb/descriptors/UsbMSMidiInputJack.java b/com/android/server/usb/descriptors/UsbMSMidiInputJack.java index 1d5cbf2b..7df7cfc6 100644 --- a/com/android/server/usb/descriptors/UsbMSMidiInputJack.java +++ b/com/android/server/usb/descriptors/UsbMSMidiInputJack.java @@ -25,7 +25,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas; public final class UsbMSMidiInputJack extends UsbACInterface { private static final String TAG = "UsbMSMidiInputJack"; - UsbMSMidiInputJack(int length, byte type, byte subtype, byte subclass) { + UsbMSMidiInputJack(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java b/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java index 9f50240a..1879ac09 100644 --- a/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java +++ b/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java @@ -25,7 +25,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas; public final class UsbMSMidiOutputJack extends UsbACInterface { private static final String TAG = "UsbMSMidiOutputJack"; - public UsbMSMidiOutputJack(int length, byte type, byte subtype, byte subclass) { + public UsbMSMidiOutputJack(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/com/android/server/usb/descriptors/report/HTMLReportCanvas.java b/com/android/server/usb/descriptors/report/HTMLReportCanvas.java index 99ebccad..adfc5143 100644 --- a/com/android/server/usb/descriptors/report/HTMLReportCanvas.java +++ b/com/android/server/usb/descriptors/report/HTMLReportCanvas.java @@ -15,7 +15,7 @@ */ package com.android.server.usb.descriptors.report; -import android.hardware.usb.UsbDeviceConnection; +import com.android.server.usb.descriptors.UsbDescriptorParser; /** * @hide @@ -32,8 +32,8 @@ public final class HTMLReportCanvas extends ReportCanvas { * from the USB device. * @param stringBuilder Generated output gets written into this object. */ - public HTMLReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) { - super(connection); + public HTMLReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) { + super(parser); mStringBuilder = stringBuilder; } diff --git a/com/android/server/usb/descriptors/report/ReportCanvas.java b/com/android/server/usb/descriptors/report/ReportCanvas.java index 9e0adf55..c34dc988 100644 --- a/com/android/server/usb/descriptors/report/ReportCanvas.java +++ b/com/android/server/usb/descriptors/report/ReportCanvas.java @@ -15,7 +15,7 @@ */ package com.android.server.usb.descriptors.report; -import android.hardware.usb.UsbDeviceConnection; +import com.android.server.usb.descriptors.UsbDescriptorParser; /** * @hide @@ -24,22 +24,19 @@ import android.hardware.usb.UsbDeviceConnection; public abstract class ReportCanvas { private static final String TAG = "ReportCanvas"; - private final UsbDeviceConnection mConnection; + private final UsbDescriptorParser mParser; /** * Constructor. * @param connection The USB connection object used to retrieve strings * from the USB device. */ - public ReportCanvas(UsbDeviceConnection connection) { - mConnection = connection; + public ReportCanvas(UsbDescriptorParser parser) { + mParser = parser; } - /** - * @returns the UsbDeviceConnection member (mConnection). - */ - public UsbDeviceConnection getConnection() { - return mConnection; + public UsbDescriptorParser getParser() { + return mParser; } /** diff --git a/com/android/server/usb/descriptors/report/TextReportCanvas.java b/com/android/server/usb/descriptors/report/TextReportCanvas.java index a43569d4..1e19ea14 100644 --- a/com/android/server/usb/descriptors/report/TextReportCanvas.java +++ b/com/android/server/usb/descriptors/report/TextReportCanvas.java @@ -15,7 +15,7 @@ */ package com.android.server.usb.descriptors.report; -import android.hardware.usb.UsbDeviceConnection; +import com.android.server.usb.descriptors.UsbDescriptorParser; /** * @hide @@ -34,8 +34,8 @@ public final class TextReportCanvas extends ReportCanvas { * from the USB device. * @param stringBuilder Generated output gets written into this object. */ - public TextReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) { - super(connection); + public TextReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) { + super(parser); mStringBuilder = stringBuilder; } diff --git a/com/android/server/usb/descriptors/report/UsbStrings.java b/com/android/server/usb/descriptors/report/UsbStrings.java index 64ecebc2..fb4576a6 100644 --- a/com/android/server/usb/descriptors/report/UsbStrings.java +++ b/com/android/server/usb/descriptors/report/UsbStrings.java @@ -32,8 +32,8 @@ public final class UsbStrings { private static HashMap<Byte, String> sDescriptorNames; private static HashMap<Byte, String> sACControlInterfaceNames; private static HashMap<Byte, String> sACStreamingInterfaceNames; - private static HashMap<Byte, String> sClassNames; - private static HashMap<Byte, String> sAudioSubclassNames; + private static HashMap<Integer, String> sClassNames; + private static HashMap<Integer, String> sAudioSubclassNames; private static HashMap<Integer, String> sAudioEncodingNames; private static HashMap<Integer, String> sTerminalNames; private static HashMap<Integer, String> sFormatNames; @@ -92,7 +92,7 @@ public final class UsbStrings { } private static void initClassNames() { - sClassNames = new HashMap<Byte, String>(); + sClassNames = new HashMap<Integer, String>(); sClassNames.put(UsbDescriptor.CLASSID_DEVICE, "Device"); sClassNames.put(UsbDescriptor.CLASSID_AUDIO, "Audio"); sClassNames.put(UsbDescriptor.CLASSID_COM, "Communications"); @@ -118,7 +118,7 @@ public final class UsbStrings { } private static void initAudioSubclassNames() { - sAudioSubclassNames = new HashMap<Byte, String>(); + sAudioSubclassNames = new HashMap<Integer, String>(); sAudioSubclassNames.put(UsbDescriptor.AUDIO_SUBCLASS_UNDEFINED, "Undefinded"); sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOCONTROL, "Audio Control"); sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOSTREAMING, "Audio Streaming"); @@ -300,7 +300,7 @@ public final class UsbStrings { /** * Retrieves the name for the specified USB class ID. */ - public static String getClassName(byte classID) { + public static String getClassName(int classID) { String name = sClassNames.get(classID); int iClassID = classID & 0xFF; return name != null @@ -312,7 +312,7 @@ public final class UsbStrings { /** * Retrieves the name for the specified USB audio subclass ID. */ - public static String getAudioSubclassName(byte subClassID) { + public static String getAudioSubclassName(int subClassID) { String name = sAudioSubclassNames.get(subClassID); int iSubclassID = subClassID & 0xFF; return name != null @@ -335,7 +335,7 @@ public final class UsbStrings { /** * Retrieves the name for the specified USB audio interface subclass ID. */ - public static String getACInterfaceSubclassName(byte subClassID) { + public static String getACInterfaceSubclassName(int subClassID) { return subClassID == UsbDescriptor.AUDIO_AUDIOCONTROL ? "AC Control" : "AC Streaming"; } } diff --git a/com/android/server/utils/AppInstallerUtil.java b/com/android/server/utils/AppInstallerUtil.java new file mode 100644 index 00000000..af7ff41f --- /dev/null +++ b/com/android/server/utils/AppInstallerUtil.java @@ -0,0 +1,71 @@ +/* + * 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 com.android.server.utils; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.util.Log; + +public class AppInstallerUtil { + private static final String LOG_TAG = "AppInstallerUtil"; + + private static Intent resolveIntent(Context context, Intent i) { + ResolveInfo result = context.getPackageManager().resolveActivity(i, 0); + return result != null ? new Intent(i.getAction()) + .setClassName(result.activityInfo.packageName, result.activityInfo.name) : null; + } + + /** + * Returns the package name of the app which installed a given packageName, if available. + */ + public static String getInstallerPackageName(Context context, String packageName) { + String installerPackageName = null; + try { + installerPackageName = + context.getPackageManager().getInstallerPackageName(packageName); + } catch (IllegalArgumentException e) { + Log.e(LOG_TAG, "Exception while retrieving the package installer of " + packageName, e); + } + if (installerPackageName == null) { + return null; + } + return installerPackageName; + } + + /** + * Returns an intent to launcher the installer for a given package name. + */ + public static Intent createIntent(Context context, String installerPackageName, + String packageName) { + Intent intent = new Intent(Intent.ACTION_SHOW_APP_INFO).setPackage(installerPackageName); + final Intent result = resolveIntent(context, intent); + if (result != null) { + result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); + return result; + } + return null; + } + + /** + * Convenience method that looks up the installerPackageName. + */ + public static Intent createIntent(Context context, String packageName) { + String installerPackageName = getInstallerPackageName(context, packageName); + return createIntent(context, installerPackageName, packageName); + } +} diff --git a/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 44e5314f..2d93da9f 100644 --- a/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -427,8 +427,11 @@ public class VoiceInteractionManagerService extends SystemService { if (hasComponent) { mShortcutServiceInternal.setShortcutHostPackage(TAG, serviceComponent.getPackageName(), mCurUser); + mAmInternal.setAllowAppSwitches(TAG, + serviceInfo.applicationInfo.uid, mCurUser); } else { mShortcutServiceInternal.setShortcutHostPackage(TAG, null, mCurUser); + mAmInternal.setAllowAppSwitches(TAG, -1, mCurUser); } } diff --git a/com/android/server/vr/Vr2dDisplay.java b/com/android/server/vr/Vr2dDisplay.java index 95d03d4b..7866e7d4 100644 --- a/com/android/server/vr/Vr2dDisplay.java +++ b/com/android/server/vr/Vr2dDisplay.java @@ -24,9 +24,8 @@ import android.service.vr.IPersistentVrStateCallbacks; import android.service.vr.IVrManager; import android.util.Log; import android.view.Surface; -import android.view.WindowManagerInternal; -import com.android.server.vr.VrManagerService; +import com.android.server.wm.WindowManagerInternal; /** * Creates a 2D Virtual Display while VR Mode is enabled. This display will be used to run and diff --git a/com/android/server/vr/VrManagerService.java b/com/android/server/vr/VrManagerService.java index 5493207d..1f4e64e8 100644 --- a/com/android/server/vr/VrManagerService.java +++ b/com/android/server/vr/VrManagerService.java @@ -58,7 +58,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; -import android.view.WindowManagerInternal; +import com.android.server.wm.WindowManagerInternal; import com.android.internal.R; import com.android.internal.util.DumpUtils; diff --git a/com/android/server/wifi/HalDeviceManager.java b/com/android/server/wifi/HalDeviceManager.java index 0cc735af..ffc7113f 100644 --- a/com/android/server/wifi/HalDeviceManager.java +++ b/com/android/server/wifi/HalDeviceManager.java @@ -16,6 +16,8 @@ package com.android.server.wifi; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.hardware.wifi.V1_0.IWifi; import android.hardware.wifi.V1_0.IWifiApIface; import android.hardware.wifi.V1_0.IWifiChip; @@ -34,7 +36,6 @@ import android.hidl.manager.V1_0.IServiceManager; import android.hidl.manager.V1_0.IServiceNotification; import android.os.Handler; import android.os.HwRemoteBinder; -import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.util.LongSparseArray; @@ -102,13 +103,14 @@ public class HalDeviceManager { * single copy kept. * * @param listener ManagerStatusListener listener object. - * @param handler Handler on which to dispatch listener. Null implies a new Handler based on - * the current looper. + * @param handler Handler on which to dispatch listener. Null implies the listener will be + * invoked synchronously from the context of the client which triggered the + * state change. */ - public void registerStatusListener(ManagerStatusListener listener, Handler handler) { + public void registerStatusListener(@NonNull ManagerStatusListener listener, + @Nullable Handler handler) { synchronized (mLock) { - if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener, - handler == null ? new Handler(Looper.myLooper()) : handler))) { + if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener, handler))) { Log.w(TAG, "registerStatusListener: duplicate registration ignored"); } } @@ -197,36 +199,37 @@ public class HalDeviceManager { * @param destroyedListener Optional (nullable) listener to call when the allocated interface * is removed. Will only be registered and used if an interface is * created successfully. - * @param handler The Handler on which to dispatch the listener. A null implies a new Handler - * based on the current looper. + * @param handler Handler on which to dispatch listener. Null implies the listener will be + * invoked synchronously from the context of the client which triggered the + * iface destruction. * @return A newly created interface - or null if the interface could not be created. */ - public IWifiStaIface createStaIface(InterfaceDestroyedListener destroyedListener, - Handler handler) { + public IWifiStaIface createStaIface(@Nullable InterfaceDestroyedListener destroyedListener, + @Nullable Handler handler) { return (IWifiStaIface) createIface(IfaceType.STA, destroyedListener, handler); } /** * Create AP interface if possible (see createStaIface doc). */ - public IWifiApIface createApIface(InterfaceDestroyedListener destroyedListener, - Handler handler) { + public IWifiApIface createApIface(@Nullable InterfaceDestroyedListener destroyedListener, + @Nullable Handler handler) { return (IWifiApIface) createIface(IfaceType.AP, destroyedListener, handler); } /** * Create P2P interface if possible (see createStaIface doc). */ - public IWifiP2pIface createP2pIface(InterfaceDestroyedListener destroyedListener, - Handler handler) { + public IWifiP2pIface createP2pIface(@Nullable InterfaceDestroyedListener destroyedListener, + @Nullable Handler handler) { return (IWifiP2pIface) createIface(IfaceType.P2P, destroyedListener, handler); } /** * Create NAN interface if possible (see createStaIface doc). */ - public IWifiNanIface createNanIface(InterfaceDestroyedListener destroyedListener, - Handler handler) { + public IWifiNanIface createNanIface(@Nullable InterfaceDestroyedListener destroyedListener, + @Nullable Handler handler) { return (IWifiNanIface) createIface(IfaceType.NAN, destroyedListener, handler); } @@ -268,11 +271,16 @@ public class HalDeviceManager { * and false on failure. This listener is in addition to the one registered when the interface * was created - allowing non-creators to monitor interface status. * - * Listener called-back on the specified handler - or on the current looper if a null is passed. + * @param destroyedListener Listener to call when the allocated interface is removed. + * Will only be registered and used if an interface is created + * successfully. + * @param handler Handler on which to dispatch listener. Null implies the listener will be + * invoked synchronously from the context of the client which triggered the + * iface destruction. */ public boolean registerDestroyedListener(IWifiIface iface, - InterfaceDestroyedListener destroyedListener, - Handler handler) { + @NonNull InterfaceDestroyedListener destroyedListener, + @Nullable Handler handler) { String name = getName(iface); if (DBG) Log.d(TAG, "registerDestroyedListener: iface(name)=" + name); @@ -284,7 +292,7 @@ public class HalDeviceManager { } return cacheEntry.destroyedListeners.add( - new InterfaceDestroyedListenerProxy(destroyedListener, handler)); + new InterfaceDestroyedListenerProxy(name, destroyedListener, handler)); } } @@ -303,11 +311,12 @@ public class HalDeviceManager { * @param ifaceType The interface type (IfaceType) to be monitored. * @param listener Listener to call when an interface of the requested * type could be created - * @param handler The Handler on which to dispatch the listener. A null implies a new Handler - * on the current looper. + * @param handler Handler on which to dispatch listener. Null implies the listener will be + * invoked synchronously from the context of the client which triggered the + * mode change. */ public void registerInterfaceAvailableForRequestListener(int ifaceType, - InterfaceAvailableForRequestListener listener, Handler handler) { + @NonNull InterfaceAvailableForRequestListener listener, @Nullable Handler handler) { if (DBG) Log.d(TAG, "registerInterfaceAvailableForRequestListener: ifaceType=" + ifaceType); synchronized (mLock) { @@ -382,8 +391,10 @@ public class HalDeviceManager { * * Can be registered when the interface is requested with createXxxIface() - will * only be valid if the interface creation was successful - i.e. a non-null was returned. + * + * @param ifaceName Name of the interface that was destroyed. */ - void onDestroyed(); + void onDestroyed(@NonNull String ifaceName); } /** @@ -1212,9 +1223,8 @@ public class HalDeviceManager { private class ManagerStatusListenerProxy extends ListenerProxy<ManagerStatusListener> { - ManagerStatusListenerProxy(ManagerStatusListener statusListener, - Handler handler) { - super(statusListener, handler, true, "ManagerStatusListenerProxy"); + ManagerStatusListenerProxy(ManagerStatusListener statusListener, Handler handler) { + super(statusListener, handler, "ManagerStatusListenerProxy"); } @Override @@ -1346,7 +1356,8 @@ public class HalDeviceManager { cacheEntry.type = ifaceType; if (destroyedListener != null) { cacheEntry.destroyedListeners.add( - new InterfaceDestroyedListenerProxy(destroyedListener, handler)); + new InterfaceDestroyedListenerProxy( + cacheEntry.name, destroyedListener, handler)); } cacheEntry.creationTime = mClock.getUptimeSinceBootMillis(); @@ -1898,11 +1909,8 @@ public class HalDeviceManager { } private abstract class ListenerProxy<LISTENER> { - private static final int LISTENER_TRIGGERED = 0; - protected LISTENER mListener; private Handler mHandler; - private boolean mFrontOfQueue; // override equals & hash to make sure that the container HashSet is unique with respect to // the contained listener @@ -1917,37 +1925,36 @@ public class HalDeviceManager { } void trigger() { - if (mFrontOfQueue) { - mHandler.postAtFrontOfQueue(() -> { - action(); - }); - } else { + if (mHandler != null) { mHandler.post(() -> { action(); }); + } else { + action(); } } protected abstract void action(); - ListenerProxy(LISTENER listener, Handler handler, boolean frontOfQueue, String tag) { + ListenerProxy(LISTENER listener, Handler handler, String tag) { mListener = listener; mHandler = handler; - mFrontOfQueue = frontOfQueue; } } private class InterfaceDestroyedListenerProxy extends ListenerProxy<InterfaceDestroyedListener> { - InterfaceDestroyedListenerProxy(InterfaceDestroyedListener destroyedListener, - Handler handler) { - super(destroyedListener, handler == null ? new Handler(Looper.myLooper()) : handler, - true, "InterfaceDestroyedListenerProxy"); + private final String mIfaceName; + InterfaceDestroyedListenerProxy(@NonNull String ifaceName, + InterfaceDestroyedListener destroyedListener, + Handler handler) { + super(destroyedListener, handler, "InterfaceDestroyedListenerProxy"); + mIfaceName = ifaceName; } @Override protected void action() { - mListener.onDestroyed(); + mListener.onDestroyed(mIfaceName); } } @@ -1955,8 +1962,7 @@ public class HalDeviceManager { ListenerProxy<InterfaceAvailableForRequestListener> { InterfaceAvailableForRequestListenerProxy( InterfaceAvailableForRequestListener destroyedListener, Handler handler) { - super(destroyedListener, handler == null ? new Handler(Looper.myLooper()) : handler, - false, "InterfaceAvailableForRequestListenerProxy"); + super(destroyedListener, handler, "InterfaceAvailableForRequestListenerProxy"); } @Override diff --git a/com/android/server/wifi/SoftApManager.java b/com/android/server/wifi/SoftApManager.java index e045c396..cec9887c 100644 --- a/com/android/server/wifi/SoftApManager.java +++ b/com/android/server/wifi/SoftApManager.java @@ -25,9 +25,7 @@ import android.content.Context; import android.content.Intent; import android.net.InterfaceConfiguration; import android.net.wifi.IApInterface; -import android.net.wifi.IApInterfaceEventCallback; import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiConfiguration.KeyMgmt; import android.net.wifi.WifiManager; import android.os.INetworkManagementService; import android.os.Looper; @@ -41,9 +39,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.server.net.BaseNetworkObserver; +import com.android.server.wifi.WifiNative.SoftApListener; import com.android.server.wifi.util.ApConfigUtil; -import java.nio.charset.StandardCharsets; import java.util.Locale; /** @@ -76,16 +74,14 @@ public class SoftApManager implements ActiveModeManager { private int mNumAssociatedStations = 0; - /** - * Listener for AP Interface events. - */ - public class ApInterfaceListener extends IApInterfaceEventCallback.Stub { + private final SoftApListener mSoftApListener = new SoftApListener() { @Override public void onNumAssociatedStationsChanged(int numStations) { mStateMachine.sendMessage( SoftApStateMachine.CMD_NUM_ASSOCIATED_STATIONS_CHANGED, numStations); } - } + }; + /** * Listener for soft AP state changes. @@ -227,71 +223,24 @@ public class SoftApManager implements ActiveModeManager { return ERROR_GENERIC; } } - - int encryptionType = getIApInterfaceEncryptionType(localConfig); - if (localConfig.hiddenSSID) { Log.d(TAG, "SoftAP is a hidden network"); } - - try { - // Note that localConfig.SSID is intended to be either a hex string or "double quoted". - // However, it seems that whatever is handing us these configurations does not obey - // this convention. - boolean success = mApInterface.writeHostapdConfig( - localConfig.SSID.getBytes(StandardCharsets.UTF_8), localConfig.hiddenSSID, - localConfig.apChannel, encryptionType, - (localConfig.preSharedKey != null) - ? localConfig.preSharedKey.getBytes(StandardCharsets.UTF_8) - : new byte[0]); - if (!success) { - Log.e(TAG, "Failed to write hostapd configuration"); - return ERROR_GENERIC; - } - - success = mApInterface.startHostapd(new ApInterfaceListener()); - if (!success) { - Log.e(TAG, "Failed to start hostapd."); - return ERROR_GENERIC; - } - } catch (RemoteException e) { - Log.e(TAG, "Exception in starting soft AP: " + e); + if (!mWifiNative.startSoftAp(localConfig, mSoftApListener)) { + Log.e(TAG, "Soft AP start failed"); + return ERROR_GENERIC; } - Log.d(TAG, "Soft AP is started"); return SUCCESS; } - private static int getIApInterfaceEncryptionType(WifiConfiguration localConfig) { - int encryptionType; - switch (localConfig.getAuthType()) { - case KeyMgmt.NONE: - encryptionType = IApInterface.ENCRYPTION_TYPE_NONE; - break; - case KeyMgmt.WPA_PSK: - encryptionType = IApInterface.ENCRYPTION_TYPE_WPA; - break; - case KeyMgmt.WPA2_PSK: - encryptionType = IApInterface.ENCRYPTION_TYPE_WPA2; - break; - default: - // We really shouldn't default to None, but this was how NetworkManagementService - // used to do this. - encryptionType = IApInterface.ENCRYPTION_TYPE_NONE; - break; - } - return encryptionType; - } - /** * Teardown soft AP. */ private void stopSoftAp() { - try { - mApInterface.stopHostapd(); - } catch (RemoteException e) { - Log.e(TAG, "Exception in stopping soft AP: " + e); + if (!mWifiNative.stopSoftAp()) { + Log.d(TAG, "Soft AP stop failed"); return; } Log.d(TAG, "Soft AP is stopped"); diff --git a/com/android/server/wifi/WakeupController.java b/com/android/server/wifi/WakeupController.java new file mode 100644 index 00000000..a3c095ac --- /dev/null +++ b/com/android/server/wifi/WakeupController.java @@ -0,0 +1,74 @@ +/* + * 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 com.android.server.wifi; + +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * WakeupController is responsible managing Auto Wifi. + * + * <p>It determines if and when to re-enable wifi after it has been turned off by the user. + */ +public class WakeupController { + + // TODO(b/69624403) propagate this to Settings + private static final boolean USE_PLATFORM_WIFI_WAKE = false; + + private final Context mContext; + private final Handler mHandler; + private final FrameworkFacade mFrameworkFacade; + private final ContentObserver mContentObserver; + + /** Whether this feature is enabled in Settings. */ + private boolean mWifiWakeupEnabled; + + public WakeupController( + Context context, + Looper looper, + FrameworkFacade frameworkFacade) { + mContext = context; + mHandler = new Handler(looper); + mFrameworkFacade = frameworkFacade; + mContentObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + mWifiWakeupEnabled = mFrameworkFacade.getIntegerSetting( + mContext, Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1; + } + }; + mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor( + Settings.Global.WIFI_WAKEUP_ENABLED), true, mContentObserver); + mContentObserver.onChange(false /* selfChange */); + } + + /** + * Whether the feature is enabled in settings. + * + * <p>Note: This method is only used to determine whether or not to actually enable wifi. All + * other aspects of the WakeupController lifecycle operate normally irrespective of this. + */ + @VisibleForTesting + boolean isEnabled() { + return mWifiWakeupEnabled; + } +} diff --git a/com/android/server/wifi/WifiInjector.java b/com/android/server/wifi/WifiInjector.java index cc559341..f14a57f6 100644 --- a/com/android/server/wifi/WifiInjector.java +++ b/com/android/server/wifi/WifiInjector.java @@ -122,6 +122,7 @@ public class WifiInjector { private final WifiStateTracker mWifiStateTracker; private final Runtime mJavaRuntime; private final SelfRecovery mSelfRecovery; + private final WakeupController mWakeupController; private final boolean mUseRealLogger; @@ -230,6 +231,8 @@ public class WifiInjector { mWifiConfigManager, mWifiConfigStore, mWifiStateMachine, new OpenNetworkRecommender(), new ConnectToNetworkNotificationBuilder(mContext, mFrameworkFacade)); + mWakeupController = new WakeupController(mContext, + mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade); mLockManager = new WifiLockManager(mContext, BatteryStatsService.getService()); mWifiController = new WifiController(mContext, mWifiStateMachine, mSettingsStore, mLockManager, mWifiServiceHandlerThread.getLooper(), mFrameworkFacade); @@ -349,6 +352,10 @@ public class WifiInjector { return mPasspointManager; } + public WakeupController getWakeupController() { + return mWakeupController; + } + public TelephonyManager makeTelephonyManager() { // may not be available when WiFi starts return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); diff --git a/com/android/server/wifi/WifiNative.java b/com/android/server/wifi/WifiNative.java index cda1cf6f..cf80d223 100644 --- a/com/android/server/wifi/WifiNative.java +++ b/com/android/server/wifi/WifiNative.java @@ -273,6 +273,36 @@ public class WifiNative { return mWificondControl.stopPnoScan(); } + /** + * Callbacks for SoftAp interface. + */ + public interface SoftApListener { + /** + * Invoked when the number of associated stations changes. + */ + void onNumAssociatedStationsChanged(int numStations); + } + + /** + * Start Soft AP operation using the provided configuration. + * + * @param config Configuration to use for the soft ap created. + * @param listener Callback for AP events. + * @return true on success, false otherwise. + */ + public boolean startSoftAp(WifiConfiguration config, SoftApListener listener) { + return mWificondControl.startSoftAp(config, listener); + } + + /** + * Stop the ongoing Soft AP operation. + * + * @return true on success, false otherwise. + */ + public boolean stopSoftAp() { + return mWificondControl.stopSoftAp(); + } + /******************************************************** * Supplicant operations ********************************************************/ diff --git a/com/android/server/wifi/WifiServiceImpl.java b/com/android/server/wifi/WifiServiceImpl.java index e86bd541..8db180f1 100644 --- a/com/android/server/wifi/WifiServiceImpl.java +++ b/com/android/server/wifi/WifiServiceImpl.java @@ -16,7 +16,6 @@ package com.android.server.wifi; -import static android.app.AppOpsManager.MODE_IGNORED; import static android.net.wifi.WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_FAILURE_REASON; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; @@ -43,7 +42,6 @@ import static com.android.server.wifi.WifiController.CMD_USER_PRESENT; import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED; import android.Manifest; -import android.annotation.CheckResult; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.app.AppOpsManager; @@ -75,6 +73,7 @@ import android.net.wifi.WifiLinkLayerStats; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.LocalOnlyHotspotCallback; import android.net.wifi.WifiScanner; +import android.net.wifi.hotspot2.IProvisioningCallback; import android.net.wifi.hotspot2.OsuProvider; import android.net.wifi.hotspot2.PasspointConfiguration; import android.os.AsyncTask; @@ -143,11 +142,6 @@ public class WifiServiceImpl extends IWifiManager.Stub { private static final boolean DBG = true; private static final boolean VDBG = false; - // Dumpsys argument to enable/disable disconnect on IP reachability failures. - private static final String DUMP_ARG_SET_IPREACH_DISCONNECT = "set-ipreach-disconnect"; - private static final String DUMP_ARG_SET_IPREACH_DISCONNECT_ENABLED = "enabled"; - private static final String DUMP_ARG_SET_IPREACH_DISCONNECT_DISABLED = "disabled"; - // Default scan background throttling interval if not overriden in settings private static final long DEFAULT_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 60 * 1000; @@ -591,9 +585,7 @@ public class WifiServiceImpl extends IWifiManager.Stub { */ @Override public void startScan(ScanSettings settings, WorkSource workSource, String packageName) { - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return; - } + enforceChangePermission(); mLog.info("startScan uid=%").c(Binder.getCallingUid()).flush(); // Check and throttle background apps for wifi scan. @@ -737,21 +729,9 @@ public class WifiServiceImpl extends IWifiManager.Stub { "WifiService"); } - /** - * Checks whether the caller can change the wifi state. - * Possible results: - * 1. Operation is allowed. No exception thrown, and AppOpsManager.MODE_ALLOWED returned. - * 2. Operation is not allowed, and caller must be told about this. SecurityException is thrown. - * 3. Operation is not allowed, and caller must not be told about this (i.e. must silently - * ignore the operation). No exception is thrown, and AppOpsManager.MODE_IGNORED returned. - */ - @CheckResult - private int enforceChangePermission(String callingPackage) { + private void enforceChangePermission() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, "WifiService"); - - return mAppOps.noteOp(AppOpsManager.OP_CHANGE_WIFI_STATE, Binder.getCallingUid(), - callingPackage); } private void enforceLocationHardwarePermission() { @@ -795,10 +775,7 @@ public class WifiServiceImpl extends IWifiManager.Stub { @Override public synchronized boolean setWifiEnabled(String packageName, boolean enable) throws RemoteException { - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return false; - } - + enforceChangePermission(); Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + ", package=" + packageName); mLog.info("setWifiEnabled package=% uid=% enable=%").c(packageName) @@ -1192,9 +1169,7 @@ public class WifiServiceImpl extends IWifiManager.Stub { final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return LocalOnlyHotspotCallback.ERROR_GENERIC; - } + enforceChangePermission(); enforceLocationPermission(packageName, uid); // also need to verify that Locations services are enabled. if (mSettingsStore.getLocationModeSetting(mContext) == Settings.Secure.LOCATION_MODE_OFF) { @@ -1266,12 +1241,9 @@ public class WifiServiceImpl extends IWifiManager.Stub { * Hotspot. */ @Override - public void stopLocalOnlyHotspot(String packageName) { + public void stopLocalOnlyHotspot() { // first check if the caller has permission to stop a local only hotspot - if (enforceChangePermission(packageName) == MODE_IGNORED) { - // As this step is about cleaning up previously allocated resources, we'll allow the - // app to do this cleanup even if the op is configured to be ignored. - } + enforceChangePermission(); final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); @@ -1373,10 +1345,8 @@ public class WifiServiceImpl extends IWifiManager.Stub { * @throws SecurityException if the caller does not have permission to write the sotap config */ @Override - public void setWifiApConfiguration(WifiConfiguration wifiConfig, String packageName) { - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return; - } + public void setWifiApConfiguration(WifiConfiguration wifiConfig) { + enforceChangePermission(); int uid = Binder.getCallingUid(); // only allow Settings UI to write the stored SoftApConfig if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid)) { @@ -1408,10 +1378,8 @@ public class WifiServiceImpl extends IWifiManager.Stub { * see {@link android.net.wifi.WifiManager#disconnect()} */ @Override - public void disconnect(String packageName) { - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return; - } + public void disconnect() { + enforceChangePermission(); mLog.info("disconnect uid=%").c(Binder.getCallingUid()).flush(); mWifiStateMachine.disconnectCommand(); } @@ -1420,10 +1388,8 @@ public class WifiServiceImpl extends IWifiManager.Stub { * see {@link android.net.wifi.WifiManager#reconnect()} */ @Override - public void reconnect(String packageName) { - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return; - } + public void reconnect() { + enforceChangePermission(); mLog.info("reconnect uid=%").c(Binder.getCallingUid()).flush(); mWifiStateMachine.reconnectCommand(new WorkSource(Binder.getCallingUid())); } @@ -1432,10 +1398,8 @@ public class WifiServiceImpl extends IWifiManager.Stub { * see {@link android.net.wifi.WifiManager#reassociate()} */ @Override - public void reassociate(String packageName) { - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return; - } + public void reassociate() { + enforceChangePermission(); mLog.info("reassociate uid=%").c(Binder.getCallingUid()).flush(); mWifiStateMachine.reassociateCommand(); } @@ -1636,10 +1600,8 @@ public class WifiServiceImpl extends IWifiManager.Stub { * network if the operation succeeds, or {@code -1} if it fails */ @Override - public int addOrUpdateNetwork(WifiConfiguration config, String packageName) { - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return -1; - } + public int addOrUpdateNetwork(WifiConfiguration config) { + enforceChangePermission(); mLog.info("addOrUpdateNetwork uid=%").c(Binder.getCallingUid()).flush(); // Previously, this API is overloaded for installing Passpoint profiles. Now @@ -1658,7 +1620,7 @@ public class WifiServiceImpl extends IWifiManager.Stub { config.enterpriseConfig.getClientCertificateChain()); passpointConfig.getCredential().setClientPrivateKey( config.enterpriseConfig.getClientPrivateKey()); - if (!addOrUpdatePasspointConfiguration(passpointConfig, packageName)) { + if (!addOrUpdatePasspointConfiguration(passpointConfig)) { Slog.e(TAG, "Failed to add Passpoint profile"); return -1; } @@ -1709,10 +1671,8 @@ public class WifiServiceImpl extends IWifiManager.Stub { * @return {@code true} if the operation succeeded */ @Override - public boolean removeNetwork(int netId, String packageName) { - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return false; - } + public boolean removeNetwork(int netId) { + enforceChangePermission(); mLog.info("removeNetwork uid=%").c(Binder.getCallingUid()).flush(); // TODO Add private logging for netId b/33807876 if (mWifiStateMachineChannel != null) { @@ -1731,10 +1691,8 @@ public class WifiServiceImpl extends IWifiManager.Stub { * @return {@code true} if the operation succeeded */ @Override - public boolean enableNetwork(int netId, boolean disableOthers, String packageName) { - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return false; - } + public boolean enableNetwork(int netId, boolean disableOthers) { + enforceChangePermission(); // TODO b/33807876 Log netId mLog.info("enableNetwork uid=% disableOthers=%") .c(Binder.getCallingUid()) @@ -1756,10 +1714,8 @@ public class WifiServiceImpl extends IWifiManager.Stub { * @return {@code true} if the operation succeeded */ @Override - public boolean disableNetwork(int netId, String packageName) { - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return false; - } + public boolean disableNetwork(int netId) { + enforceChangePermission(); // TODO b/33807876 Log netId mLog.info("disableNetwork uid=%").c(Binder.getCallingUid()).flush(); @@ -1817,11 +1773,8 @@ public class WifiServiceImpl extends IWifiManager.Stub { * @return true on success or false on failure */ @Override - public boolean addOrUpdatePasspointConfiguration( - PasspointConfiguration config, String packageName) { - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return false; - } + public boolean addOrUpdatePasspointConfiguration(PasspointConfiguration config) { + enforceChangePermission(); mLog.info("addorUpdatePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush(); if (!mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_WIFI_PASSPOINT)) { @@ -1838,10 +1791,8 @@ public class WifiServiceImpl extends IWifiManager.Stub { * @return true on success or false on failure */ @Override - public boolean removePasspointConfiguration(String fqdn, String packageName) { - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return false; - } + public boolean removePasspointConfiguration(String fqdn) { + enforceChangePermission(); mLog.info("removePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush(); if (!mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_WIFI_PASSPOINT)) { @@ -1913,10 +1864,8 @@ public class WifiServiceImpl extends IWifiManager.Stub { * TODO: deprecate this */ @Override - public boolean saveConfiguration(String packageName) { - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return false; - } + public boolean saveConfiguration() { + enforceChangePermission(); mLog.info("saveConfiguration uid=%").c(Binder.getCallingUid()).flush(); if (mWifiStateMachineChannel != null) { return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel); @@ -2112,13 +2061,9 @@ public class WifiServiceImpl extends IWifiManager.Stub { * an AsyncChannel communication with WifiService */ @Override - public Messenger getWifiServiceMessenger(String packageName) throws RemoteException { + public Messenger getWifiServiceMessenger() { enforceAccessPermission(); - if (enforceChangePermission(packageName) == MODE_IGNORED) { - // We don't have a good way of creating a fake Messenger, and returning null would - // immediately break callers. - throw new RemoteException("Could not create wifi service messenger"); - } + enforceChangePermission(); mLog.info("getWifiServiceMessenger uid=%").c(Binder.getCallingUid()).flush(); return new Messenger(mClientHandler); } @@ -2127,11 +2072,9 @@ public class WifiServiceImpl extends IWifiManager.Stub { * Disable an ephemeral network, i.e. network that is created thru a WiFi Scorer */ @Override - public void disableEphemeralNetwork(String SSID, String packageName) { + public void disableEphemeralNetwork(String SSID) { enforceAccessPermission(); - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return; - } + enforceChangePermission(); mLog.info("disableEphemeralNetwork uid=%").c(Binder.getCallingUid()).flush(); mWifiStateMachine.disableEphemeralNetwork(SSID); } @@ -2485,10 +2428,8 @@ public class WifiServiceImpl extends IWifiManager.Stub { } @Override - public boolean setEnableAutoJoinWhenAssociated(boolean enabled, String packageName) { - if (enforceChangePermission(packageName) == MODE_IGNORED) { - return false; - } + public boolean setEnableAutoJoinWhenAssociated(boolean enabled) { + enforceChangePermission(); mLog.info("setEnableAutoJoinWhenAssociated uid=% enabled=%") .c(Binder.getCallingUid()) .c(enabled).flush(); @@ -2517,7 +2458,7 @@ public class WifiServiceImpl extends IWifiManager.Stub { } @Override - public void factoryReset(String packageName) { + public void factoryReset() { enforceConnectivityInternalPermission(); mLog.info("factoryReset uid=%").c(Binder.getCallingUid()).flush(); if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) { @@ -2543,9 +2484,9 @@ public class WifiServiceImpl extends IWifiManager.Stub { Binder.getCallingUid(), mWifiStateMachineChannel); if (networks != null) { for (WifiConfiguration config : networks) { - removeNetwork(config.networkId, packageName); + removeNetwork(config.networkId); } - saveConfiguration(packageName); + saveConfiguration(); } } } @@ -2719,4 +2660,33 @@ public class WifiServiceImpl extends IWifiManager.Stub { restoreNetworks(wifiConfigurations); Slog.d(TAG, "Restored supplicant backup data"); } + + /** + * Starts subscription provisioning with a provider + * + * @param provider {@link OsuProvider} the provider to provision with + * @param callback {@link IProvisoningCallback} the callback object to inform status + */ + @Override + public void startSubscriptionProvisioning(OsuProvider provider, + IProvisioningCallback callback) { + if (provider == null) { + throw new IllegalArgumentException("Provider must not be null"); + } + if (callback == null) { + throw new IllegalArgumentException("Callback must not be null"); + } + enforceNetworkSettingsPermission(); + if (!mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_WIFI_PASSPOINT)) { + throw new UnsupportedOperationException("Passpoint not enabled"); + } + final int uid = Binder.getCallingUid(); + mLog.trace("startSubscriptionProvisioning uid=%").c(uid).flush(); + if (mWifiStateMachine.syncStartSubscriptionProvisioning(uid, provider, + callback, mWifiStateMachineChannel)) { + mLog.trace("Subscription provisioning started with %") + .c(provider.toString()).flush(); + } + } } diff --git a/com/android/server/wifi/WifiStateMachine.java b/com/android/server/wifi/WifiStateMachine.java index 2c8c0b76..b005923d 100644 --- a/com/android/server/wifi/WifiStateMachine.java +++ b/com/android/server/wifi/WifiStateMachine.java @@ -78,6 +78,7 @@ import android.net.wifi.WifiSsid; import android.net.wifi.WpsInfo; import android.net.wifi.WpsResult; import android.net.wifi.WpsResult.Status; +import android.net.wifi.hotspot2.IProvisioningCallback; import android.net.wifi.hotspot2.OsuProvider; import android.net.wifi.hotspot2.PasspointConfiguration; import android.net.wifi.p2p.IWifiP2pManager; @@ -181,6 +182,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss private static final String EXTRA_OSU_ICON_QUERY_BSSID = "BSSID"; private static final String EXTRA_OSU_ICON_QUERY_FILENAME = "FILENAME"; + private static final String EXTRA_OSU_PROVIDER = "OsuProvider"; private boolean mVerboseLoggingEnabled = false; @@ -740,6 +742,9 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss /* Used to set the tx power limit for SAR during the start of a phone call. */ private static final int CMD_SELECT_TX_POWER_SCENARIO = BASE + 253; + // Start subscription provisioning with a given provider + private static final int CMD_START_SUBSCRIPTION_PROVISIONING = BASE + 254; + // For message logging. private static final Class[] sMessageClasses = { AsyncChannel.class, WifiStateMachine.class, DhcpClient.class }; @@ -1246,6 +1251,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss mWifiNative.enableVerboseLogging(verbose); mWifiConfigManager.enableVerboseLogging(verbose); mSupplicantStateTracker.enableVerboseLogging(verbose); + mPasspointManager.enableVerboseLogging(verbose); } private static final String SYSTEM_PROPERTY_LOG_CONTROL_WIFIHAL = "log.tag.WifiHAL"; @@ -2011,6 +2017,25 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss } /** + * Start subscription provisioning synchronously + * + * @param provider {@link OsuProvider} the provider to provision with + * @param callback {@link IProvisioningCallback} callback for provisioning status + * @return boolean true indicates provisioning was started, false otherwise + */ + public boolean syncStartSubscriptionProvisioning(int callingUid, OsuProvider provider, + IProvisioningCallback callback, AsyncChannel channel) { + Message msg = Message.obtain(); + msg.what = CMD_START_SUBSCRIPTION_PROVISIONING; + msg.arg1 = callingUid; + msg.obj = callback; + msg.getData().putParcelable(EXTRA_OSU_PROVIDER, provider); + Message resultMsg = channel.sendMessageSynchronously(msg); + boolean result = resultMsg.arg1 != 0; + resultMsg.recycle(); + return result; + } + /** * Get connection statistics synchronously * * @param channel @@ -3896,6 +3921,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss break; case CMD_INITIALIZE: ok = mWifiNative.initializeVendorHal(mVendorHalDeathRecipient); + mPasspointManager.initializeProvisioner( + mWifiInjector.getWifiServiceHandlerThread().getLooper()); replyToMessage(message, message.what, ok ? SUCCESS : FAILURE); break; case CMD_BOOT_COMPLETED: @@ -4028,6 +4055,9 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss case CMD_GET_MATCHING_OSU_PROVIDERS: replyToMessage(message, message.what, new ArrayList<OsuProvider>()); break; + case CMD_START_SUBSCRIPTION_PROVISIONING: + replyToMessage(message, message.what, 0); + break; case CMD_IP_CONFIGURATION_SUCCESSFUL: case CMD_IP_CONFIGURATION_LOST: case CMD_IP_REACHABILITY_LOST: @@ -5156,6 +5186,14 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss replyToMessage(message, message.what, mPasspointManager.getMatchingOsuProviders((ScanResult) message.obj)); break; + case CMD_START_SUBSCRIPTION_PROVISIONING: + IProvisioningCallback callback = (IProvisioningCallback) message.obj; + OsuProvider provider = + (OsuProvider) message.getData().getParcelable(EXTRA_OSU_PROVIDER); + int res = mPasspointManager.startSubscriptionProvisioning( + message.arg1, provider, callback) ? 1 : 0; + replyToMessage(message, message.what, res); + break; case CMD_RECONNECT: WorkSource workSource = (WorkSource) message.obj; mWifiConnectivityManager.forceConnectivityScan(workSource); diff --git a/com/android/server/wifi/WificondControl.java b/com/android/server/wifi/WificondControl.java index aa723d65..3dd63114 100644 --- a/com/android/server/wifi/WificondControl.java +++ b/com/android/server/wifi/WificondControl.java @@ -18,18 +18,21 @@ package com.android.server.wifi; import android.annotation.NonNull; import android.net.wifi.IApInterface; +import android.net.wifi.IApInterfaceEventCallback; import android.net.wifi.IClientInterface; import android.net.wifi.IPnoScanEvent; import android.net.wifi.IScanEvent; import android.net.wifi.IWifiScannerImpl; import android.net.wifi.IWificond; import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiScanner; import android.net.wifi.WifiSsid; import android.os.Binder; import android.os.RemoteException; import android.util.Log; +import com.android.server.wifi.WifiNative.SoftApListener; import com.android.server.wifi.hotspot2.NetworkDetail; import com.android.server.wifi.util.InformationElementUtil; import com.android.server.wifi.util.NativeUtil; @@ -41,6 +44,7 @@ import com.android.server.wifi.wificond.PnoNetwork; import com.android.server.wifi.wificond.PnoSettings; import com.android.server.wifi.wificond.SingleScanSettings; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Set; @@ -70,6 +74,7 @@ public class WificondControl { private IWifiScannerImpl mWificondScanner; private IScanEvent mScanEventHandler; private IPnoScanEvent mPnoScanEventHandler; + private IApInterfaceEventCallback mApInterfaceListener; private String mClientInterfaceName; @@ -122,6 +127,22 @@ public class WificondControl { } } + /** + * Listener for AP Interface events. + */ + private class ApInterfaceEventCallback extends IApInterfaceEventCallback.Stub { + private SoftApListener mSoftApListener; + + ApInterfaceEventCallback(SoftApListener listener) { + mSoftApListener = listener; + } + + @Override + public void onNumAssociatedStationsChanged(int numStations) { + mSoftApListener.onNumAssociatedStationsChanged(numStations); + } + } + /** Enable or disable verbose logging of WificondControl. * @param enable True to enable verbose logging. False to disable verbose logging. */ @@ -251,12 +272,12 @@ public class WificondControl { * @return Returns true on success. */ public boolean disableSupplicant() { - if (mClientInterface == null) { - Log.e(TAG, "No valid wificond client interface handler"); + if (mWificond == null) { + Log.e(TAG, "No valid handler"); return false; } try { - return mClientInterface.disableSupplicant(); + return mWificond.disableSupplicant(); } catch (RemoteException e) { Log.e(TAG, "Failed to disable supplicant due to remote exception"); } @@ -268,13 +289,13 @@ public class WificondControl { * @return Returns true on success. */ public boolean enableSupplicant() { - if (mClientInterface == null) { - Log.e(TAG, "No valid wificond client interface handler"); + if (mWificond == null) { + Log.e(TAG, "No valid wificond handler"); return false; } try { - return mClientInterface.enableSupplicant(); + return mWificond.enableSupplicant(); } catch (RemoteException e) { Log.e(TAG, "Failed to enable supplicant due to remote exception"); } @@ -526,7 +547,6 @@ public class WificondControl { } } - /** * Query the list of valid frequencies for the provided band. * The result depends on the on the country code that has been set. @@ -560,4 +580,90 @@ public class WificondControl { } return null; } + + /** + * Start Soft AP operation using the provided configuration. + * + * @param config Configuration to use for the soft ap created. + * @param listener Callback for AP events. + * @return true on success, false otherwise. + */ + public boolean startSoftAp(WifiConfiguration config, SoftApListener listener) { + if (mApInterface == null) { + Log.e(TAG, "No valid ap interface handler"); + return false; + } + int encryptionType = getIApInterfaceEncryptionType(config); + try { + // TODO(b/67745880) Note that config.SSID is intended to be either a + // hex string or "double quoted". + // However, it seems that whatever is handing us these configurations does not obey + // this convention. + boolean success = mApInterface.writeHostapdConfig( + config.SSID.getBytes(StandardCharsets.UTF_8), config.hiddenSSID, + config.apChannel, encryptionType, + (config.preSharedKey != null) + ? config.preSharedKey.getBytes(StandardCharsets.UTF_8) + : new byte[0]); + if (!success) { + Log.e(TAG, "Failed to write hostapd configuration"); + return false; + } + mApInterfaceListener = new ApInterfaceEventCallback(listener); + success = mApInterface.startHostapd(mApInterfaceListener); + if (!success) { + Log.e(TAG, "Failed to start hostapd."); + return false; + } + } catch (RemoteException e) { + Log.e(TAG, "Exception in starting soft AP: " + e); + return false; + } + return true; + } + + /** + * Stop the ongoing Soft AP operation. + * + * @return true on success, false otherwise. + */ + public boolean stopSoftAp() { + if (mApInterface == null) { + Log.e(TAG, "No valid ap interface handler"); + return false; + } + try { + boolean success = mApInterface.stopHostapd(); + if (!success) { + Log.e(TAG, "Failed to stop hostapd."); + return false; + } + } catch (RemoteException e) { + Log.e(TAG, "Exception in stopping soft AP: " + e); + return false; + } + mApInterfaceListener = null; + return true; + } + + private static int getIApInterfaceEncryptionType(WifiConfiguration localConfig) { + int encryptionType; + switch (localConfig.getAuthType()) { + case WifiConfiguration.KeyMgmt.NONE: + encryptionType = IApInterface.ENCRYPTION_TYPE_NONE; + break; + case WifiConfiguration.KeyMgmt.WPA_PSK: + encryptionType = IApInterface.ENCRYPTION_TYPE_WPA; + break; + case WifiConfiguration.KeyMgmt.WPA2_PSK: + encryptionType = IApInterface.ENCRYPTION_TYPE_WPA2; + break; + default: + // We really shouldn't default to None, but this was how NetworkManagementService + // used to do this. + encryptionType = IApInterface.ENCRYPTION_TYPE_NONE; + break; + } + return encryptionType; + } } diff --git a/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java b/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java index 86f4e37c..3358a4a2 100644 --- a/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java +++ b/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java @@ -244,13 +244,20 @@ public class WifiAwareDiscoverySessionState { * (usually not used in the match decisions). * @param matchFilter The filter from the discovery advertisement (which was * used in the match decision). + * @param rangingIndication Bit mask indicating the type of ranging event triggered. + * @param rangeMm The range to the peer in mm (valid if rangingIndication specifies ingress + * or egress events - i.e. non-zero). */ public void onMatch(int requestorInstanceId, byte[] peerMac, byte[] serviceSpecificInfo, - byte[] matchFilter) { + byte[] matchFilter, int rangingIndication, int rangeMm) { int peerId = getPeerIdOrAddIfNew(requestorInstanceId, peerMac); try { - mCallback.onMatch(peerId, serviceSpecificInfo, matchFilter); + if (rangingIndication == 0) { + mCallback.onMatch(peerId, serviceSpecificInfo, matchFilter); + } else { + mCallback.onMatchWithDistance(peerId, serviceSpecificInfo, matchFilter, rangeMm); + } } catch (RemoteException e) { Log.w(TAG, "onMatch: RemoteException (FYI): " + e); } diff --git a/com/android/server/wifi/aware/WifiAwareNativeApi.java b/com/android/server/wifi/aware/WifiAwareNativeApi.java index a6e724f7..c8b5fced 100644 --- a/com/android/server/wifi/aware/WifiAwareNativeApi.java +++ b/com/android/server/wifi/aware/WifiAwareNativeApi.java @@ -26,6 +26,7 @@ import android.hardware.wifi.V1_0.NanEnableRequest; import android.hardware.wifi.V1_0.NanInitiateDataPathRequest; import android.hardware.wifi.V1_0.NanMatchAlg; import android.hardware.wifi.V1_0.NanPublishRequest; +import android.hardware.wifi.V1_0.NanRangingIndication; import android.hardware.wifi.V1_0.NanRespondToDataPathIndicationRequest; import android.hardware.wifi.V1_0.NanSubscribeRequest; import android.hardware.wifi.V1_0.NanTransmitFollowupRequest; @@ -430,11 +431,13 @@ public class WifiAwareNativeApi implements WifiAwareShellCommand.DelegatedShellC req.baseConfigs.disableMatchExpirationIndication = true; req.baseConfigs.disableFollowupReceivedIndication = false; - // TODO: configure ranging and security - req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN; - req.baseConfigs.rangingRequired = false; req.autoAcceptDataPathRequests = false; + req.baseConfigs.rangingRequired = publishConfig.mEnableRanging; + + // TODO: configure security + req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN; + req.publishType = publishConfig.mPublishType; req.txType = NanTxType.BROADCAST; @@ -493,9 +496,23 @@ public class WifiAwareNativeApi implements WifiAwareShellCommand.DelegatedShellC req.baseConfigs.disableMatchExpirationIndication = true; req.baseConfigs.disableFollowupReceivedIndication = false; - // TODO: configure ranging and security + req.baseConfigs.rangingRequired = + subscribeConfig.mMinDistanceMmSet || subscribeConfig.mMaxDistanceMmSet; + req.baseConfigs.configRangingIndications = 0; + // TODO: b/69428593 remove correction factors once HAL converted from CM to MM + if (subscribeConfig.mMinDistanceMmSet) { + req.baseConfigs.distanceIngressCm = (short) Math.min( + subscribeConfig.mMinDistanceMm / 10, Short.MAX_VALUE); + req.baseConfigs.configRangingIndications |= NanRangingIndication.INGRESS_MET_MASK; + } + if (subscribeConfig.mMaxDistanceMmSet) { + req.baseConfigs.distanceEgressCm = (short) Math.min(subscribeConfig.mMaxDistanceMm / 10, + Short.MAX_VALUE); + req.baseConfigs.configRangingIndications |= NanRangingIndication.EGRESS_MET_MASK; + } + + // TODO: configure security req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN; - req.baseConfigs.rangingRequired = false; req.subscribeType = subscribeConfig.mSubscribeType; diff --git a/com/android/server/wifi/aware/WifiAwareNativeCallback.java b/com/android/server/wifi/aware/WifiAwareNativeCallback.java index 2121160b..b45978b2 100644 --- a/com/android/server/wifi/aware/WifiAwareNativeCallback.java +++ b/com/android/server/wifi/aware/WifiAwareNativeCallback.java @@ -405,13 +405,17 @@ public class WifiAwareNativeCallback extends IWifiNanIfaceEventCallback.Stub imp + (event.serviceSpecificInfo == null ? 0 : event.serviceSpecificInfo.size()) + ", matchFilter=" + Arrays.toString( convertArrayListToNativeByteArray(event.matchFilter)) + ", mf.size()=" + ( - event.matchFilter == null ? 0 : event.matchFilter.size())); + event.matchFilter == null ? 0 : event.matchFilter.size()) + + ", rangingIndicationType=" + event.rangingIndicationType + + ", rangingMeasurementInCm=" + event.rangingMeasurementInCm); } incrementCbCount(CB_EV_MATCH); + // TODO: b/69428593 get rid of conversion once HAL moves from CM to MM mWifiAwareStateManager.onMatchNotification(event.discoverySessionId, event.peerId, event.addr, convertArrayListToNativeByteArray(event.serviceSpecificInfo), - convertArrayListToNativeByteArray(event.matchFilter)); + convertArrayListToNativeByteArray(event.matchFilter), event.rangingIndicationType, + event.rangingMeasurementInCm * 10); } @Override diff --git a/com/android/server/wifi/aware/WifiAwareNativeManager.java b/com/android/server/wifi/aware/WifiAwareNativeManager.java index 8659a775..d6bec5f1 100644 --- a/com/android/server/wifi/aware/WifiAwareNativeManager.java +++ b/com/android/server/wifi/aware/WifiAwareNativeManager.java @@ -16,6 +16,7 @@ package com.android.server.wifi.aware; +import android.annotation.NonNull; import android.hardware.wifi.V1_0.IWifiNanIface; import android.hardware.wifi.V1_0.IfaceType; import android.hardware.wifi.V1_0.WifiStatus; @@ -150,7 +151,7 @@ public class WifiAwareNativeManager { private class InterfaceDestroyedListener implements HalDeviceManager.InterfaceDestroyedListener { @Override - public void onDestroyed() { + public void onDestroyed(@NonNull String ifaceName) { if (DBG) Log.d(TAG, "Interface was destroyed"); awareIsDown(); } diff --git a/com/android/server/wifi/aware/WifiAwareStateManager.java b/com/android/server/wifi/aware/WifiAwareStateManager.java index 0efe7361..89d9a904 100644 --- a/com/android/server/wifi/aware/WifiAwareStateManager.java +++ b/com/android/server/wifi/aware/WifiAwareStateManager.java @@ -183,6 +183,8 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe private static final String MESSAGE_BUNDLE_KEY_PMK = "pmk"; private static final String MESSAGE_BUNDLE_KEY_PASSPHRASE = "passphrase"; private static final String MESSAGE_BUNDLE_KEY_OOB = "out_of_band"; + private static final String MESSAGE_RANGING_INDICATION = "ranging_indication"; + private static final String MESSAGE_RANGE_MM = "range_mm"; private WifiAwareNativeApi mWifiAwareNativeApi; private WifiAwareNativeManager mWifiAwareNativeManager; @@ -944,7 +946,7 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe * matching service (to the one we were looking for). */ public void onMatchNotification(int pubSubId, int requestorInstanceId, byte[] peerMac, - byte[] serviceSpecificInfo, byte[] matchFilter) { + byte[] serviceSpecificInfo, byte[] matchFilter, int rangingIndication, int rangeMm) { Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION); msg.arg1 = NOTIFICATION_TYPE_MATCH; msg.arg2 = pubSubId; @@ -952,6 +954,8 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, peerMac); msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA, serviceSpecificInfo); msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA, matchFilter); + msg.getData().putInt(MESSAGE_RANGING_INDICATION, rangingIndication); + msg.getData().putInt(MESSAGE_RANGE_MM, rangeMm); mSm.sendMessage(msg); } @@ -1255,9 +1259,11 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe byte[] serviceSpecificInfo = msg.getData() .getByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA); byte[] matchFilter = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA); + int rangingIndication = msg.getData().getInt(MESSAGE_RANGING_INDICATION); + int rangeMm = msg.getData().getInt(MESSAGE_RANGE_MM); onMatchLocal(pubSubId, requestorInstanceId, peerMac, serviceSpecificInfo, - matchFilter); + matchFilter, rangingIndication, rangeMm); break; } case NOTIFICATION_TYPE_SESSION_TERMINATED: { @@ -2788,13 +2794,14 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe } private void onMatchLocal(int pubSubId, int requestorInstanceId, byte[] peerMac, - byte[] serviceSpecificInfo, byte[] matchFilter) { + byte[] serviceSpecificInfo, byte[] matchFilter, int rangingIndication, int rangeMm) { if (VDBG) { Log.v(TAG, "onMatch: pubSubId=" + pubSubId + ", requestorInstanceId=" + requestorInstanceId + ", peerDiscoveryMac=" + String.valueOf(HexEncoding.encode(peerMac)) + ", serviceSpecificInfo=" + Arrays.toString(serviceSpecificInfo) - + ", matchFilter=" + Arrays.toString(matchFilter)); + + ", matchFilter=" + Arrays.toString(matchFilter) + + ", rangingIndication=" + rangingIndication + ", rangeMm=" + rangeMm); } Pair<WifiAwareClientState, WifiAwareDiscoverySessionState> data = @@ -2804,7 +2811,8 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe return; } - data.second.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo, matchFilter); + data.second.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo, matchFilter, + rangingIndication, rangeMm); } private void onSessionTerminatedLocal(int pubSubId, boolean isPublish, int reason) { diff --git a/com/android/server/wifi/hotspot2/OsuNetworkConnection.java b/com/android/server/wifi/hotspot2/OsuNetworkConnection.java new file mode 100644 index 00000000..f1bf1175 --- /dev/null +++ b/com/android/server/wifi/hotspot2/OsuNetworkConnection.java @@ -0,0 +1,270 @@ +/* + * 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 com.android.server.wifi.hotspot2; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Network; +import android.net.NetworkInfo; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiSsid; +import android.os.Handler; +import android.text.TextUtils; +import android.util.Log; + +/** + * Responsible for setup/monitor on Wi-Fi state and connection to the OSU AP. + */ +public class OsuNetworkConnection { + private static final String TAG = "OsuNetworkConnection"; + + private final Context mContext; + + private boolean mVerboseLoggingEnabled = false; + private WifiManager mWifiManager; + private Callbacks mCallbacks; + private boolean mConnected = false; + private int mNetworkId = -1; + private boolean mWifiEnabled = false; + + /** + * Callbacks on Wi-Fi connection state changes. + */ + public interface Callbacks { + /** + * Invoked when network connection is established with IP connectivity. + * + * @param network {@link Network} associated with the connected network. + */ + void onConnected(Network network); + + /** + * Invoked when the targeted network is disconnected. + */ + void onDisconnected(); + + /** + * Invoked when a timer tracking connection request is not reset by successfull connection. + */ + void onTimeOut(); + + /** + * Invoked when Wifi is enabled. + */ + void onWifiEnabled(); + + /** + * Invoked when Wifi is disabled. + */ + void onWifiDisabled(); + } + + /** + * Create an instance of {@link NetworkConnection} for the specified Wi-Fi network. + * @param context The application context + */ + public OsuNetworkConnection(Context context) { + mContext = context; + } + + /** + * Called to initialize tracking of wifi state and network events by registering for the + * corresponding intents. + */ + public void init(Handler handler) { + IntentFilter filter = new IntentFilter(); + filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + handleNetworkStateChanged( + intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO), + intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)); + } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { + int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, + WifiManager.WIFI_STATE_UNKNOWN); + handleWifiStateChanged(state); + } + } + }; + mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + mContext.registerReceiver(receiver, filter, null, handler); + mWifiEnabled = mWifiManager.isWifiEnabled(); + } + + /** + * Disconnect, if required in the two cases + * - still connected to the OSU AP + * - connection to OSU AP was requested and in progress + */ + public void disconnectIfNeeded() { + if (mNetworkId < 0) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "No connection to tear down"); + } + return; + } + mWifiManager.removeNetwork(mNetworkId); + mNetworkId = -1; + mConnected = false; + if (mCallbacks != null) { + mCallbacks.onDisconnected(); + } + } + + /** + * Register for network and Wifi state events + * @param callbacks The callbacks to be invoked on network change events + */ + public void setEventCallback(Callbacks callbacks) { + mCallbacks = callbacks; + } + + /** + * Connect to a OSU Wi-Fi network specified by the given SSID. The security type of the Wi-Fi + * network is either open or OSEN (OSU Server-only authenticated layer 2 Encryption Network). + * When network access identifier is provided, OSEN is used. + * + * @param ssid The SSID to connect to + * @param nai Network access identifier of the network + * + * @return boolean true if connection was successfully initiated + */ + public boolean connect(WifiSsid ssid, String nai) { + if (mConnected) { + if (mVerboseLoggingEnabled) { + // Already connected + Log.v(TAG, "Connect called twice"); + } + return true; + } + if (!mWifiManager.isWifiEnabled()) { + Log.w(TAG, "Wifi is not enabled"); + return false; + } + WifiConfiguration config = new WifiConfiguration(); + config.SSID = "\"" + ssid.toString() + "\""; + if (TextUtils.isEmpty(nai)) { + config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + } else { + // TODO(sohanirao): Handle OSEN. + Log.w(TAG, "OSEN not supported"); + return false; + } + mNetworkId = mWifiManager.addNetwork(config); + if (mNetworkId < 0) { + Log.e(TAG, "Unable to add network"); + return false; + } + if (!mWifiManager.enableNetwork(mNetworkId, true)) { + Log.e(TAG, "Unable to enable network " + mNetworkId); + disconnectIfNeeded(); + return false; + } + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Current network ID " + mNetworkId); + } + // TODO(sohanirao): setup alarm to time out the connection attempt. + return true; + } + + /** + * Method to update logging level in this class + * @param verbose more than 0 enables verbose logging + */ + public void enableVerboseLogging(int verbose) { + mVerboseLoggingEnabled = verbose > 0 ? true : false; + } + + /** + * Handle network state changed events. + * + * @param networkInfo {@link NetworkInfo} indicating the current network state + * @param wifiInfo {@link WifiInfo} associated with the current network when connected + */ + private void handleNetworkStateChanged(NetworkInfo networkInfo, WifiInfo wifiInfo) { + if (networkInfo == null) { + Log.w(TAG, "NetworkInfo not provided for network state changed event"); + return; + } + switch (networkInfo.getDetailedState()) { + case CONNECTED: + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Connected event received"); + } + if (wifiInfo == null) { + Log.w(TAG, "WifiInfo not provided for network state changed event"); + return; + } + handleConnectedEvent(wifiInfo); + break; + case DISCONNECTED: + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Disconnected event received"); + } + disconnectIfNeeded(); + break; + default: + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Ignore uninterested state: " + networkInfo.getDetailedState()); + } + break; + } + } + + /** + * Handle network connected event. + * + * @param wifiInfo {@link WifiInfo} associated with the current connection + */ + private void handleConnectedEvent(WifiInfo wifiInfo) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "handleConnectedEvent " + wifiInfo.getNetworkId()); + } + if (wifiInfo.getNetworkId() != mNetworkId) { + disconnectIfNeeded(); + return; + } + if (!mConnected) { + mConnected = true; + if (mCallbacks != null) { + mCallbacks.onConnected(mWifiManager.getCurrentNetwork()); + } + } + } + + /** + * Handle Wifi state change event + */ + private void handleWifiStateChanged(int state) { + if (state == WifiManager.WIFI_STATE_DISABLED && mWifiEnabled) { + mWifiEnabled = false; + if (mCallbacks != null) mCallbacks.onWifiDisabled(); + } + if (state == WifiManager.WIFI_STATE_ENABLED && !mWifiEnabled) { + mWifiEnabled = true; + if (mCallbacks != null) mCallbacks.onWifiEnabled(); + } + } +} diff --git a/com/android/server/wifi/hotspot2/PasspointManager.java b/com/android/server/wifi/hotspot2/PasspointManager.java index 3580c839..fec3dd81 100644 --- a/com/android/server/wifi/hotspot2/PasspointManager.java +++ b/com/android/server/wifi/hotspot2/PasspointManager.java @@ -33,8 +33,10 @@ import android.graphics.drawable.Icon; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiEnterpriseConfig; +import android.net.wifi.hotspot2.IProvisioningCallback; import android.net.wifi.hotspot2.OsuProvider; import android.net.wifi.hotspot2.PasspointConfiguration; +import android.os.Looper; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; @@ -99,6 +101,7 @@ public class PasspointManager { private final WifiConfigManager mWifiConfigManager; private final CertificateVerifier mCertVerifier; private final WifiMetrics mWifiMetrics; + private final PasspointProvisioner mPasspointProvisioner; // Counter used for assigning unique identifier to each provider. private long mProviderIndex; @@ -212,10 +215,27 @@ public class PasspointManager { mProviderIndex = 0; wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigStoreData( mKeyStore, mSimAccessor, new DataSourceHandler())); + mPasspointProvisioner = objectFactory.makePasspointProvisioner(context, + objectFactory.makeOsuNetworkConnection(context)); sPasspointManager = this; } /** + * Initializes the provisioning flow with a looper + */ + public void initializeProvisioner(Looper looper) { + mPasspointProvisioner.init(looper); + } + + /** + * Enable verbose logging + * @param verbose more than 0 enables verbose logging + */ + public void enableVerboseLogging(int verbose) { + mPasspointProvisioner.enableVerboseLogging(verbose); + } + + /** * Add or update a Passpoint provider with the given configuration. * * Each provider is uniquely identified by its FQDN (Fully Qualified Domain Name). @@ -704,4 +724,16 @@ public class PasspointManager { mProviders.put(passpointConfig.getHomeSp().getFqdn(), provider); return true; } + + /** + * Start the subscription provisioning flow with a provider. + * @param callingUid integer indicating the uid of the caller + * @param provider {@link OsuProvider} the provider to subscribe to + * @param callback {@link IProvisioningCallback} callback to update status to the caller + * @return boolean return value from the provisioning method + */ + public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider, + IProvisioningCallback callback) { + return mPasspointProvisioner.startSubscriptionProvisioning(callingUid, provider, callback); + } } diff --git a/com/android/server/wifi/hotspot2/PasspointObjectFactory.java b/com/android/server/wifi/hotspot2/PasspointObjectFactory.java index c41c49ac..71fc47a2 100644 --- a/com/android/server/wifi/hotspot2/PasspointObjectFactory.java +++ b/com/android/server/wifi/hotspot2/PasspointObjectFactory.java @@ -16,6 +16,7 @@ package com.android.server.wifi.hotspot2; +import android.content.Context; import android.net.wifi.hotspot2.PasspointConfiguration; import com.android.server.wifi.Clock; @@ -95,4 +96,25 @@ public class PasspointObjectFactory{ public CertificateVerifier makeCertificateVerifier() { return new CertificateVerifier(); } + + /** + * Create an instance of {@link PasspointProvisioner}. + * + * @param context + * @return {@link PasspointProvisioner} + */ + public PasspointProvisioner makePasspointProvisioner(Context context, + OsuNetworkConnection osuNetworkConnection) { + return new PasspointProvisioner(context, osuNetworkConnection); + } + + /** + * Create an instance of {@link OsuNetworkConnection}. + * + * @param context + * @return {@link OsuNetworkConnection} + */ + public OsuNetworkConnection makeOsuNetworkConnection(Context context) { + return new OsuNetworkConnection(context); + } } diff --git a/com/android/server/wifi/hotspot2/PasspointProvisioner.java b/com/android/server/wifi/hotspot2/PasspointProvisioner.java new file mode 100644 index 00000000..d3bb7731 --- /dev/null +++ b/com/android/server/wifi/hotspot2/PasspointProvisioner.java @@ -0,0 +1,309 @@ +/* + * 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 com.android.server.wifi.hotspot2; + +import android.content.Context; +import android.net.Network; +import android.net.wifi.hotspot2.IProvisioningCallback; +import android.net.wifi.hotspot2.OsuProvider; +import android.net.wifi.hotspot2.ProvisioningCallback; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; + +/** + * Provides methods to carry out provisioning flow + */ +public class PasspointProvisioner { + private static final String TAG = "PasspointProvisioner"; + + private static final int PROVISIONING_STATUS = 0; + private static final int PROVISIONING_FAILURE = 1; + + private final Context mContext; + private final ProvisioningStateMachine mProvisioningStateMachine; + private final OsuNetworkCallbacks mOsuNetworkCallbacks; + private final OsuNetworkConnection mOsuNetworkConnection; + + private int mCallingUid; + private boolean mVerboseLoggingEnabled = false; + + PasspointProvisioner(Context context, OsuNetworkConnection osuNetworkConnection) { + mContext = context; + mOsuNetworkConnection = osuNetworkConnection; + mProvisioningStateMachine = new ProvisioningStateMachine(); + mOsuNetworkCallbacks = new OsuNetworkCallbacks(); + } + + /** + * Sets up for provisioning + * @param looper Looper on which the Provisioning state machine will run + */ + public void init(Looper looper) { + mProvisioningStateMachine.start(new Handler(looper)); + mOsuNetworkConnection.init(mProvisioningStateMachine.getHandler()); + } + + /** + * Enable verbose logging to help debug failures + * @param level integer indicating verbose logging enabled if > 0 + */ + public void enableVerboseLogging(int level) { + mVerboseLoggingEnabled = (level > 0) ? true : false; + mOsuNetworkConnection.enableVerboseLogging(level); + } + + /** + * Start provisioning flow with a given provider. + * @param callingUid calling uid. + * @param provider {@link OsuProvider} to provision with. + * @param callback {@link IProvisioningCallback} to provide provisioning status. + * @return boolean value, true if provisioning was started, false otherwise. + * + * Implements HS2.0 provisioning flow with a given HS2.0 provider. + */ + public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider, + IProvisioningCallback callback) { + mCallingUid = callingUid; + + Log.v(TAG, "Provisioning started with " + provider.toString()); + + mProvisioningStateMachine.getHandler().post(() -> { + mProvisioningStateMachine.startProvisioning(provider, callback); + }); + + return true; + } + + /** + * Handles the provisioning flow state transitions + */ + class ProvisioningStateMachine { + private static final String TAG = "ProvisioningStateMachine"; + + private static final int DEFAULT_STATE = 0; + private static final int INITIAL_STATE = 1; + private static final int WAITING_TO_CONNECT = 2; + private static final int OSU_AP_CONNECTED = 3; + private static final int FAILED_STATE = 4; + + private OsuProvider mOsuProvider; + private IProvisioningCallback mProvisioningCallback; + private Handler mHandler; + private int mState; + + ProvisioningStateMachine() { + mState = DEFAULT_STATE; + } + + /** + * Initializes and starts the state machine with a handler to handle incoming events + */ + public void start(Handler handler) { + mHandler = handler; + changeState(INITIAL_STATE); + } + + /** + * Returns the handler on which a runnable can be posted + * @return Handler State Machine's handler + */ + public Handler getHandler() { + return mHandler; + } + + /** + * Start Provisioning with the Osuprovider and invoke callbacks + * @param provider OsuProvider to provision with + * @param callback IProvisioningCallback to invoke callbacks on + */ + public void startProvisioning(OsuProvider provider, IProvisioningCallback callback) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "startProvisioning received in state=" + mState); + } + if (mState != INITIAL_STATE) { + Log.v(TAG, "State Machine needs to be reset before starting provisioning"); + resetStateMachine(); + } + mProvisioningCallback = callback; + mOsuProvider = provider; + + // Register for network and wifi state events during provisioning flow + mOsuNetworkConnection.setEventCallback(mOsuNetworkCallbacks); + + if (mOsuNetworkConnection.connect(mOsuProvider.getOsuSsid(), + mOsuProvider.getNetworkAccessIdentifier())) { + invokeProvisioningCallback(PROVISIONING_STATUS, + ProvisioningCallback.OSU_STATUS_AP_CONNECTING); + changeState(WAITING_TO_CONNECT); + } else { + invokeProvisioningCallback(PROVISIONING_FAILURE, + ProvisioningCallback.OSU_FAILURE_AP_CONNECTION); + enterFailedState(); + } + } + + /** + * Handle Wifi Disable event + */ + public void handleWifiDisabled() { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Wifi Disabled in state=" + mState); + } + if (mState == INITIAL_STATE || mState == FAILED_STATE) { + Log.w(TAG, "Wifi Disable unhandled in state=" + mState); + return; + } + invokeProvisioningCallback(PROVISIONING_FAILURE, + ProvisioningCallback.OSU_FAILURE_AP_CONNECTION); + enterFailedState(); + } + + private void resetStateMachine() { + // Set to null so that no callbacks are invoked during reset + mProvisioningCallback = null; + if (mState != INITIAL_STATE || mState != FAILED_STATE) { + // Transition through Failed state to clean up + enterFailedState(); + } + changeState(INITIAL_STATE); + } + + /** + * Connected event received + * @param network Network object for this connection + */ + public void handleConnectedEvent(Network network) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Connected event received in state=" + mState); + } + if (mState != WAITING_TO_CONNECT) { + // Not waiting for a connection + Log.w(TAG, "Connection event unhandled in state=" + mState); + return; + } + invokeProvisioningCallback(PROVISIONING_STATUS, + ProvisioningCallback.OSU_STATUS_AP_CONNECTED); + changeState(OSU_AP_CONNECTED); + } + + /** + * Disconnect event received + */ + public void handleDisconnect() { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Connection failed in state=" + mState); + } + if (mState == INITIAL_STATE || mState == FAILED_STATE) { + Log.w(TAG, "Disconnect event unhandled in state=" + mState); + return; + } + invokeProvisioningCallback(PROVISIONING_FAILURE, + ProvisioningCallback.OSU_FAILURE_AP_CONNECTION); + enterFailedState(); + } + + private void changeState(int nextState) { + if (nextState != mState) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Changing state from " + mState + " -> " + nextState); + } + mState = nextState; + } + } + + private void invokeProvisioningCallback(int callbackType, int status) { + if (mProvisioningCallback == null) { + Log.e(TAG, "Provisioning callback " + callbackType + " with status " + status + + " not invoked, callback is null"); + return; + } + try { + if (callbackType == PROVISIONING_STATUS) { + mProvisioningCallback.onProvisioningStatus(status); + } else { + mProvisioningCallback.onProvisioningFailure(status); + } + } catch (RemoteException e) { + if (callbackType == PROVISIONING_STATUS) { + Log.e(TAG, "Remote Exception while posting Provisioning status " + status); + } else { + Log.e(TAG, "Remote Exception while posting Provisioning failure " + status); + } + } + } + + private void enterFailedState() { + changeState(FAILED_STATE); + mOsuNetworkConnection.setEventCallback(null); + mOsuNetworkConnection.disconnectIfNeeded(); + } + } + + /** + * Callbacks for network and wifi events + */ + class OsuNetworkCallbacks implements OsuNetworkConnection.Callbacks { + + OsuNetworkCallbacks() { + } + + @Override + public void onConnected(Network network) { + Log.v(TAG, "onConnected to " + network); + if (network == null) { + mProvisioningStateMachine.getHandler().post(() -> { + mProvisioningStateMachine.handleDisconnect(); + }); + } else { + mProvisioningStateMachine.getHandler().post(() -> { + mProvisioningStateMachine.handleConnectedEvent(network); + }); + } + } + + @Override + public void onDisconnected() { + Log.v(TAG, "onDisconnected"); + mProvisioningStateMachine.getHandler().post(() -> { + mProvisioningStateMachine.handleDisconnect(); + }); + } + + @Override + public void onTimeOut() { + Log.v(TAG, "Timed out waiting for connection to OSU AP"); + mProvisioningStateMachine.getHandler().post(() -> { + mProvisioningStateMachine.handleDisconnect(); + }); + } + + @Override + public void onWifiEnabled() { + Log.v(TAG, "onWifiEnabled"); + } + + @Override + public void onWifiDisabled() { + Log.v(TAG, "onWifiDisabled"); + mProvisioningStateMachine.getHandler().post(() -> { + mProvisioningStateMachine.handleWifiDisabled(); + }); + } + } +} diff --git a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java index da3da7c2..751737f4 100644 --- a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java +++ b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java @@ -16,6 +16,7 @@ package com.android.server.wifi.p2p; +import android.annotation.NonNull; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; @@ -524,7 +525,7 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { } mHalDeviceManager = mWifiInjector.getHalDeviceManager(); } - mIWifiP2pIface = mHalDeviceManager.createP2pIface(() -> { + mIWifiP2pIface = mHalDeviceManager.createP2pIface((@NonNull String ifaceName) -> { if (DBG) Log.d(TAG, "IWifiP2pIface destroyedListener"); synchronized (mLock) { mIWifiP2pIface = null; diff --git a/com/android/server/wifi/rtt/RttNative.java b/com/android/server/wifi/rtt/RttNative.java index fe1829f8..dd54c248 100644 --- a/com/android/server/wifi/rtt/RttNative.java +++ b/com/android/server/wifi/rtt/RttNative.java @@ -46,7 +46,7 @@ import java.util.List; */ public class RttNative extends IWifiRttControllerEventCallback.Stub { private static final String TAG = "RttNative"; - private static final boolean VDBG = true; // STOPSHIP if true + private static final boolean VDBG = false; // STOPSHIP if true private final RttServiceImpl mRttService; private final HalDeviceManager mHalDeviceManager; diff --git a/com/android/server/wifi/rtt/RttService.java b/com/android/server/wifi/rtt/RttService.java index 0790deed..5c2cec15 100644 --- a/com/android/server/wifi/rtt/RttService.java +++ b/com/android/server/wifi/rtt/RttService.java @@ -66,7 +66,8 @@ public class RttService extends SystemService { Context.WIFI_AWARE_SERVICE); RttNative rttNative = new RttNative(mImpl, halDeviceManager); - mImpl.start(handlerThread.getLooper(), awareBinder, rttNative, wifiPermissionsUtil); + mImpl.start(handlerThread.getLooper(), wifiInjector.getClock(), awareBinder, rttNative, + wifiPermissionsUtil); } } } diff --git a/com/android/server/wifi/rtt/RttServiceImpl.java b/com/android/server/wifi/rtt/RttServiceImpl.java index 36caab74..401daabb 100644 --- a/com/android/server/wifi/rtt/RttServiceImpl.java +++ b/com/android/server/wifi/rtt/RttServiceImpl.java @@ -16,6 +16,7 @@ package com.android.server.wifi.rtt; +import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -39,13 +40,13 @@ import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; -import android.os.SystemClock; import android.os.UserHandle; import android.os.WorkSource; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.WakeupMessage; +import com.android.server.wifi.Clock; import com.android.server.wifi.util.NativeUtil; import com.android.server.wifi.util.WifiPermissionsUtil; @@ -66,12 +67,14 @@ import java.util.Map; */ public class RttServiceImpl extends IWifiRttManager.Stub { private static final String TAG = "RttServiceImpl"; - private static final boolean VDBG = true; // STOPSHIP if true + private static final boolean VDBG = false; // STOPSHIP if true private final Context mContext; + private Clock mClock; private IWifiAwareManager mAwareBinder; private RttNative mRttNative; private WifiPermissionsUtil mWifiPermissionsUtil; + private ActivityManager mActivityManager; private PowerManager mPowerManager; private RttServiceSynchronized mRttServiceSynchronized; @@ -79,7 +82,10 @@ public class RttServiceImpl extends IWifiRttManager.Stub { @VisibleForTesting public static final String HAL_RANGING_TIMEOUT_TAG = TAG + " HAL Ranging Timeout"; - private static final long HAL_RANGING_TIMEOUT_MS = 5_000; + private static final long HAL_RANGING_TIMEOUT_MS = 5_000; // 5 sec + + // TODO: b/69323456 convert to a settable value + /* package */ static final long BACKGROUND_PROCESS_EXEC_GAP_MS = 1_800_000; // 30 min public RttServiceImpl(Context context) { mContext = context; @@ -93,17 +99,21 @@ public class RttServiceImpl extends IWifiRttManager.Stub { * Initializes the RTT service (usually with objects from an injector). * * @param looper The looper on which to synchronize operations. + * @param clock A mockable clock. * @param awareBinder The Wi-Fi Aware service (binder) if supported on the system. * @param rttNative The Native interface to the HAL. * @param wifiPermissionsUtil Utility for permission checks. */ - public void start(Looper looper, IWifiAwareManager awareBinder, RttNative rttNative, + public void start(Looper looper, Clock clock, IWifiAwareManager awareBinder, + RttNative rttNative, WifiPermissionsUtil wifiPermissionsUtil) { + mClock = clock; mAwareBinder = awareBinder; mRttNative = rttNative; mWifiPermissionsUtil = wifiPermissionsUtil; mRttServiceSynchronized = new RttServiceSynchronized(looper, rttNative); + mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); mPowerManager = mContext.getSystemService(PowerManager.class); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); @@ -323,6 +333,7 @@ public class RttServiceImpl extends IWifiRttManager.Stub { private RttNative mRttNative; private int mNextCommandId = 1000; + private Map<Integer, RttRequesterInfo> mRttRequesterInfo = new HashMap<>(); private List<RttRequestInfo> mRttRequestQueue = new LinkedList<>(); private WakeupMessage mRangingTimeoutMessage = null; @@ -540,10 +551,23 @@ public class RttServiceImpl extends IWifiRttManager.Stub { return; } + if (!preExecThrottleCheck(nextRequest.workSource)) { + Log.w(TAG, "RttServiceSynchronized.startRanging: execution throttled - nextRequest=" + + nextRequest + ", mRttRequesterInfo=" + mRttRequesterInfo); + try { + nextRequest.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL); + } catch (RemoteException e) { + Log.e(TAG, "RttServiceSynchronized.startRanging: throttled, callback failed -- " + + e); + } + executeNextRangingRequestIfPossible(true); + return; + } + nextRequest.cmdId = mNextCommandId++; if (mRttNative.rangeRequest(nextRequest.cmdId, nextRequest.request)) { mRangingTimeoutMessage.schedule( - SystemClock.elapsedRealtime() + HAL_RANGING_TIMEOUT_MS); + mClock.getElapsedSinceBootMillis() + HAL_RANGING_TIMEOUT_MS); } else { Log.w(TAG, "RttServiceSynchronized.startRanging: native rangeRequest call failed"); try { @@ -558,6 +582,64 @@ public class RttServiceImpl extends IWifiRttManager.Stub { } /** + * Perform pre-execution throttling checks: + * - If all uids in ws are in background then check last execution and block if request is + * more frequent than permitted + * - If executing (i.e. permitted) then update execution time + * + * Returns true to permit execution, false to abort it. + */ + private boolean preExecThrottleCheck(WorkSource ws) { + if (VDBG) Log.v(TAG, "preExecThrottleCheck: ws=" + ws); + + // are all UIDs running in the background or is at least 1 in the foreground? + boolean allUidsInBackground = true; + for (int i = 0; i < ws.size(); ++i) { + int uidImportance = mActivityManager.getUidImportance(ws.get(i)); + if (VDBG) { + Log.v(TAG, "preExecThrottleCheck: uid=" + ws.get(i) + " -> importance=" + + uidImportance); + } + if (uidImportance + <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) { + allUidsInBackground = false; + break; + } + } + + // if all UIDs are in background then check timestamp since last execution and see if + // any is permitted (infrequent enough) + boolean allowExecution = false; + long mostRecentExecutionPermitted = + mClock.getElapsedSinceBootMillis() - BACKGROUND_PROCESS_EXEC_GAP_MS; + if (allUidsInBackground) { + for (int i = 0; i < ws.size(); ++i) { + RttRequesterInfo info = mRttRequesterInfo.get(ws.get(i)); + if (info == null || info.lastRangingExecuted < mostRecentExecutionPermitted) { + allowExecution = true; + break; + } + } + } else { + allowExecution = true; + } + + // update exec time + if (allowExecution) { + for (int i = 0; i < ws.size(); ++i) { + RttRequesterInfo info = mRttRequesterInfo.get(ws.get(i)); + if (info == null) { + info = new RttRequesterInfo(); + mRttRequesterInfo.put(ws.get(i), info); + } + info.lastRangingExecuted = mClock.getElapsedSinceBootMillis(); + } + } + + return allowExecution; + } + + /** * Check request for any PeerHandle Aware requests. If there are any: issue requests to * translate the peer ID to a MAC address and abort current execution of the range request. * The request will be re-attempted when response is received. @@ -754,6 +836,7 @@ public class RttServiceImpl extends IWifiRttManager.Stub { // dump call (asynchronous most likely) protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(" mNextCommandId: " + mNextCommandId); + pw.println(" mRttRequesterInfo: " + mRttRequesterInfo); pw.println(" mRttRequestQueue: " + mRttRequestQueue); pw.println(" mRangingTimeoutMessage: " + mRangingTimeoutMessage); mRttNative.dump(fd, pw, args); @@ -783,4 +866,14 @@ public class RttServiceImpl extends IWifiRttManager.Stub { ", peerHandlesTranslated=").append(peerHandlesTranslated).toString(); } } + + private static class RttRequesterInfo { + public long lastRangingExecuted; + + @Override + public String toString() { + return new StringBuilder("RttRequesterInfo: lastRangingExecuted=").append( + lastRangingExecuted).toString(); + } + } } diff --git a/com/android/server/wm/AccessibilityController.java b/com/android/server/wm/AccessibilityController.java index de4fd7cd..95b139ad 100644 --- a/com/android/server/wm/AccessibilityController.java +++ b/com/android/server/wm/AccessibilityController.java @@ -55,14 +55,14 @@ import android.view.SurfaceControl; import android.view.ViewConfiguration; import android.view.WindowInfo; import android.view.WindowManager; -import android.view.WindowManagerInternal.MagnificationCallbacks; -import android.view.WindowManagerInternal.WindowsForAccessibilityCallback; -import android.view.WindowManagerPolicy; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import com.android.internal.R; import com.android.internal.os.SomeArgs; +import com.android.server.policy.WindowManagerPolicy; +import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks; +import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback; import java.util.ArrayList; import java.util.HashSet; @@ -292,6 +292,8 @@ final class AccessibilityController { public void setMagnificationSpecLocked(MagnificationSpec spec) { mMagnifedViewport.updateMagnificationSpecLocked(spec); mMagnifedViewport.recomputeBoundsLocked(); + + mService.applyMagnificationSpec(spec); mService.scheduleAnimationLocked(); } @@ -421,7 +423,7 @@ final class AccessibilityController { public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) { MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked(); if (spec != null && !spec.isNop()) { - if (!mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) { + if (!windowState.shouldMagnify()) { return null; } } @@ -476,6 +478,7 @@ final class AccessibilityController { private final ViewportWindow mWindow; private boolean mFullRedrawNeeded; + private int mTempLayer = 0; public MagnifiedViewport() { mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE); @@ -565,7 +568,7 @@ final class AccessibilityController { portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION); windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE); - if (mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) { + if (windowState.shouldMagnify()) { mMagnificationRegion.op(windowBounds, Region.Op.UNION); mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT); } else { @@ -676,10 +679,12 @@ final class AccessibilityController { private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) { final DisplayContent dc = mService.getDefaultDisplayContentLocked(); + mTempLayer = 0; dc.forAllWindows((w) -> { if (w.isOnScreen() && w.isVisibleLw() && !w.mWinAnimator.mEnterAnimationPending) { - outWindows.put(w.mLayer, w); + mTempLayer++; + outWindows.put(mTempLayer, w); } }, false /* traverseTopToBottom */ ); } @@ -705,7 +710,7 @@ final class AccessibilityController { SurfaceControl surfaceControl = null; try { mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); - surfaceControl = new SurfaceControl.Builder(mService.mFxSession) + surfaceControl = mService.getDefaultDisplayContentLocked().makeOverlay() .setName(SURFACE_TITLE) .setSize(mTempPoint.x, mTempPoint.y) // not a typo .setFormat(PixelFormat.TRANSLUCENT) @@ -714,8 +719,6 @@ final class AccessibilityController { /* ignore */ } mSurfaceControl = surfaceControl; - mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay() - .getLayerStack()); mSurfaceControl.setLayer(mService.mPolicy.getWindowLayerFromTypeLw( TYPE_MAGNIFICATION_OVERLAY) * WindowManagerService.TYPE_LAYER_MULTIPLIER); @@ -1005,6 +1008,8 @@ final class AccessibilityController { private final long mRecurringAccessibilityEventsIntervalMillis; + private int mTempLayer = 0; + public WindowsForAccessibilityObserver(WindowManagerService windowManagerService, WindowsForAccessibilityCallback callback) { mContext = windowManagerService.mContext; @@ -1090,6 +1095,7 @@ final class AccessibilityController { if (isReportedWindowType(windowState.mAttrs.type)) { // Add the window to the ones to be reported. WindowInfo window = obtainPopulatedWindowInfo(windowState, boundsInScreen); + window.layer = addedWindows.size(); addedWindows.add(window.token); windows.add(window); if (windowState.isFocused()) { @@ -1323,9 +1329,10 @@ final class AccessibilityController { private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) { final DisplayContent dc = mService.getDefaultDisplayContentLocked(); + mTempLayer = 0; dc.forAllWindows((w) -> { if (w.isVisibleLw()) { - outWindows.put(w.mLayer, w); + outWindows.put(mTempLayer++, w); } }, false /* traverseTopToBottom */ ); } diff --git a/com/android/server/wm/AppTransition.java b/com/android/server/wm/AppTransition.java index c19ede0c..c2cbced2 100644 --- a/com/android/server/wm/AppTransition.java +++ b/com/android/server/wm/AppTransition.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import static android.view.WindowManagerInternal.AppTransitionListener; import static com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation; import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation; import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation; @@ -44,6 +43,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static com.android.server.wm.WindowManagerInternal.AppTransitionListener; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; diff --git a/com/android/server/wm/AppWindowAnimator.java b/com/android/server/wm/AppWindowAnimator.java index 2ef7f253..5c1d5b25 100644 --- a/com/android/server/wm/AppWindowAnimator.java +++ b/com/android/server/wm/AppWindowAnimator.java @@ -16,7 +16,7 @@ package com.android.server.wm; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.wm.AppTransition.TRANSIT_UNSET; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS; @@ -253,7 +253,6 @@ public class AppWindowAnimator { private void updateLayers() { mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */); - updateThumbnailLayer(); } private void stepThumbnailAnimation(long currentTime) { @@ -283,27 +282,12 @@ public class AppWindowAnimator { + "][" + tmpFloats[Matrix.MSKEW_X] + "," + tmpFloats[Matrix.MSCALE_Y] + "]"); thumbnail.setAlpha(thumbnailTransformation.getAlpha()); - updateThumbnailLayer(); thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y], tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]); thumbnail.setWindowCrop(thumbnailTransformation.getClipRect()); } /** - * Updates the thumbnail layer z order to just above the highest animation layer if changed - */ - void updateThumbnailLayer() { - if (thumbnail != null) { - final int layer = mAppToken.getHighestAnimLayer(); - if (DEBUG_LAYERS) Slog.v(TAG, - "Setting thumbnail layer " + mAppToken + ": layer=" + layer); - thumbnail.setLayer(layer + WindowManagerService.WINDOW_LAYER_MULTIPLIER - - WindowManagerService.LAYER_OFFSET_THUMBNAIL); - mThumbnailLayer = layer; - } - } - - /** * Sometimes we need to synchronize the first frame of animation with some external event, e.g. * Recents hiding some of its content. To achieve this, we prolong the start of the animaiton * and keep producing the first frame of the animation. diff --git a/com/android/server/wm/AppWindowContainerController.java b/com/android/server/wm/AppWindowContainerController.java index 58418402..00a0d3d1 100644 --- a/com/android/server/wm/AppWindowContainerController.java +++ b/com/android/server/wm/AppWindowContainerController.java @@ -45,10 +45,11 @@ import android.os.Trace; import android.util.Slog; import android.view.DisplayInfo; import android.view.IApplicationToken; -import android.view.WindowManagerPolicy.StartingSurface; import com.android.internal.annotations.VisibleForTesting; import com.android.server.AttributeCache; +import com.android.server.policy.WindowManagerPolicy.StartingSurface; + /** * Controller for the app window token container. This is created by activity manager to link * activity records to the app window token container they use in window manager. @@ -180,13 +181,12 @@ public class AppWindowContainerController IApplicationToken token, AppWindowContainerListener listener, int index, int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges, boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable, - int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos, - Rect bounds) { + int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos) { this(taskController, token, listener, index, requestedOrientation, fullscreen, showForAllUsers, configChanges, voiceInteraction, launchTaskBehind, alwaysFocusable, targetSdkVersion, rotationAnimationHint, inputDispatchingTimeoutNanos, - WindowManagerService.getInstance(), bounds); + WindowManagerService.getInstance()); } public AppWindowContainerController(TaskWindowContainerController taskController, @@ -194,7 +194,7 @@ public class AppWindowContainerController int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges, boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable, int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos, - WindowManagerService service, Rect bounds) { + WindowManagerService service) { super(listener, service); mHandler = new H(service.mH.getLooper()); mToken = token; @@ -215,7 +215,7 @@ public class AppWindowContainerController atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(), inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion, requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind, - alwaysFocusable, this, bounds); + alwaysFocusable, this); if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken + " controller=" + taskController + " at " + index); task.addChild(atoken, index); @@ -227,11 +227,11 @@ public class AppWindowContainerController boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint, int configChanges, boolean launchTaskBehind, - boolean alwaysFocusable, AppWindowContainerController controller, Rect bounds) { + boolean alwaysFocusable, AppWindowContainerController controller) { return new AppWindowToken(service, token, voiceInteraction, dc, inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation, rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable, - controller, bounds); + controller); } public void removeContainer(int displayId) { @@ -298,17 +298,6 @@ public class AppWindowContainerController } } - // TODO(b/36505427): Maybe move to WindowContainerController so other sub-classes can use it as - // a generic way to set override config. Need to untangle current ways the override config is - // currently set for tasks and displays before we are doing that though. - public void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) { - synchronized(mWindowMap) { - if (mContainer != null) { - mContainer.onOverrideConfigurationChanged(overrideConfiguration, bounds); - } - } - } - public void setDisablePreviewScreenshots(boolean disable) { synchronized (mWindowMap) { if (mContainer == null) { diff --git a/com/android/server/wm/AppWindowToken.java b/com/android/server/wm/AppWindowToken.java index 98db80ef..c39ce982 100644 --- a/com/android/server/wm/AppWindowToken.java +++ b/com/android/server/wm/AppWindowToken.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; @@ -28,8 +27,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.AppTransition.TRANSIT_UNSET; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; @@ -67,10 +66,10 @@ import android.view.IApplicationToken; import android.view.SurfaceControl; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; -import android.view.WindowManagerPolicy.StartingSurface; import com.android.internal.util.ToBooleanFunction; import com.android.server.input.InputApplicationHandle; +import com.android.server.policy.WindowManagerPolicy.StartingSurface; import com.android.server.wm.WindowManagerService.H; import java.io.PrintWriter; @@ -176,11 +175,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree private boolean mLastContainsShowWhenLockedWindow; private boolean mLastContainsDismissKeyguardWindow; - // The bounds of this activity. Mainly used for aspect-ratio compatibility. - // TODO(b/36505427): Every level on WindowContainer now has bounds information, which directly - // affects the configuration. We should probably move this into that class. - private final Rect mBounds = new Rect(); - ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>(); ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>(); @@ -197,8 +191,8 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint, int configChanges, boolean launchTaskBehind, boolean alwaysFocusable, - AppWindowContainerController controller, Rect bounds) { - this(service, token, voiceInteraction, dc, fullscreen, bounds); + AppWindowContainerController controller) { + this(service, token, voiceInteraction, dc, fullscreen); setController(controller); mInputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos; mShowForAllUsers = showForAllUsers; @@ -215,7 +209,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction, - DisplayContent dc, boolean fillsParent, Rect bounds) { + DisplayContent dc, boolean fillsParent) { super(service, token != null ? token.asBinder() : null, TYPE_APPLICATION, true, dc, false /* ownerCanManageAppTokens */); appToken = token; @@ -223,27 +217,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree mFillsParent = fillsParent; mInputApplicationHandle = new InputApplicationHandle(this); mAppAnimator = new AppWindowAnimator(this, service); - if (bounds != null) { - mBounds.set(bounds); - } - } - - void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) { - onOverrideConfigurationChanged(overrideConfiguration); - if (mBounds.equals(bounds)) { - return; - } - // TODO(b/36505427): If bounds is in WC, then we can automatically call onResize() when set. - mBounds.set(bounds); - onResize(); - } - - void getBounds(Rect outBounds) { - outBounds.set(mBounds); - } - - boolean hasBounds() { - return !mBounds.isEmpty(); } void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) { diff --git a/com/android/server/wm/BlackFrame.java b/com/android/server/wm/BlackFrame.java index 9729e501..f19cd0ff 100644 --- a/com/android/server/wm/BlackFrame.java +++ b/com/android/server/wm/BlackFrame.java @@ -30,7 +30,6 @@ import android.graphics.Rect; import android.util.Slog; import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl; -import android.view.SurfaceSession; /** * Four black surfaces put together to make a black frame. @@ -42,22 +41,22 @@ public class BlackFrame { final int layer; final SurfaceControl surface; - BlackSurface(SurfaceSession session, int layer, int l, int t, int r, int b, int layerStack) - throws OutOfResourcesException { + BlackSurface(int layer, + int l, int t, int r, int b, DisplayContent dc) throws OutOfResourcesException { left = l; top = t; this.layer = layer; int w = r-l; int h = b-t; - surface = new SurfaceControl.Builder(session) + surface = dc.makeOverlay() .setName("BlackSurface") .setSize(w, h) .setColorLayer(true) + .setParent(null) // TODO: Work-around for b/69259549 .build(); surface.setAlpha(1); - surface.setLayerStack(layerStack); surface.setLayer(layer); surface.show(); if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG_WM, @@ -114,30 +113,32 @@ public class BlackFrame { } } - public BlackFrame(SurfaceSession session, Rect outer, Rect inner, int layer, int layerStack, + public BlackFrame(Rect outer, Rect inner, int layer, DisplayContent dc, boolean forceDefaultOrientation) throws OutOfResourcesException { boolean success = false; mForceDefaultOrientation = forceDefaultOrientation; + // TODO: Why do we use 4 surfaces instead of just one big one behind the screenshot? + // b/68253229 mOuterRect = new Rect(outer); mInnerRect = new Rect(inner); try { if (outer.top < inner.top) { - mBlackSurfaces[0] = new BlackSurface(session, layer, - outer.left, outer.top, inner.right, inner.top, layerStack); + mBlackSurfaces[0] = new BlackSurface(layer, + outer.left, outer.top, inner.right, inner.top, dc); } if (outer.left < inner.left) { - mBlackSurfaces[1] = new BlackSurface(session, layer, - outer.left, inner.top, inner.left, outer.bottom, layerStack); + mBlackSurfaces[1] = new BlackSurface(layer, + outer.left, inner.top, inner.left, outer.bottom, dc); } if (outer.bottom > inner.bottom) { - mBlackSurfaces[2] = new BlackSurface(session, layer, - inner.left, inner.bottom, outer.right, outer.bottom, layerStack); + mBlackSurfaces[2] = new BlackSurface(layer, + inner.left, inner.bottom, outer.right, outer.bottom, dc); } if (outer.right > inner.right) { - mBlackSurfaces[3] = new BlackSurface(session, layer, - inner.right, outer.top, outer.right, inner.bottom, layerStack); + mBlackSurfaces[3] = new BlackSurface(layer, + inner.right, outer.top, outer.right, inner.bottom, dc); } success = true; } finally { diff --git a/com/android/server/wm/BoundsAnimationController.java b/com/android/server/wm/BoundsAnimationController.java index 7953ee43..ba67ff6a 100644 --- a/com/android/server/wm/BoundsAnimationController.java +++ b/com/android/server/wm/BoundsAnimationController.java @@ -33,7 +33,6 @@ import android.util.ArrayMap; import android.util.Slog; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; -import android.view.WindowManagerInternal; import com.android.internal.annotations.VisibleForTesting; diff --git a/com/android/server/wm/CircularDisplayMask.java b/com/android/server/wm/CircularDisplayMask.java index 2d5d1b2f..2a216abb 100644 --- a/com/android/server/wm/CircularDisplayMask.java +++ b/com/android/server/wm/CircularDisplayMask.java @@ -33,7 +33,6 @@ import android.view.Display; import android.view.Surface; import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl; -import android.view.SurfaceSession; class CircularDisplayMask { private static final String TAG = TAG_WITH_CLASS_NAME ? "CircularDisplayMask" : TAG_WM; @@ -54,8 +53,10 @@ class CircularDisplayMask { private boolean mDimensionsUnequal = false; private int mMaskThickness; - public CircularDisplayMask(Display display, SurfaceSession session, int zOrder, + public CircularDisplayMask(DisplayContent dc, int zOrder, int screenOffset, int maskThickness) { + final Display display = dc.getDisplay(); + mScreenSize = new Point(); display.getSize(mScreenSize); if (mScreenSize.x != mScreenSize.y + screenOffset) { @@ -66,7 +67,7 @@ class CircularDisplayMask { SurfaceControl ctrl = null; try { - ctrl = new SurfaceControl.Builder(session) + ctrl = dc.makeOverlay() .setName("CircularDisplayMask") .setSize(mScreenSize.x, mScreenSize.y) // not a typo .setFormat(PixelFormat.TRANSLUCENT) diff --git a/com/android/server/wm/ConfigurationContainer.java b/com/android/server/wm/ConfigurationContainer.java index cc948070..d340923b 100644 --- a/com/android/server/wm/ConfigurationContainer.java +++ b/com/android/server/wm/ConfigurationContainer.java @@ -36,6 +36,7 @@ import static com.android.server.wm.proto.ConfigurationContainerProto.OVERRIDE_C import android.annotation.CallSuper; import android.app.WindowConfiguration; import android.content.res.Configuration; +import android.graphics.Rect; import android.util.proto.ProtoOutputStream; import java.io.PrintWriter; @@ -46,6 +47,11 @@ import java.util.ArrayList; * hierarchy. */ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { + /** + * {@link #Rect} returned from {@link #getOverrideBounds()} to prevent original value from being + * set directly. + */ + private Rect mReturnBounds = new Rect(); /** Contains override configuration settings applied to this configuration container. */ private Configuration mOverrideConfiguration = new Configuration(); @@ -71,6 +77,16 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { // TODO: Can't have ag/2592611 soon enough! private final Configuration mTmpConfig = new Configuration(); + // Used for setting bounds + private final Rect mTmpRect = new Rect(); + + static final int BOUNDS_CHANGE_NONE = 0; + // Return value from {@link setBounds} indicating the position of the override bounds changed. + static final int BOUNDS_CHANGE_POSITION = 1; + // Return value from {@link setBounds} indicating the size of the override bounds changed. + static final int BOUNDS_CHANGE_SIZE = 1 << 1; + + /** * Returns full configuration applied to this configuration container. * This method should be used for getting settings applied in each particular level of the @@ -148,6 +164,118 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } } + /** + * Indicates whether this container has not specified any bounds different from its parent. In + * this case, it will inherit the bounds of the first ancestor which specifies a bounds. + * @return {@code true} if no explicit bounds have been set at this container level. + * {@code false} otherwise. + */ + public boolean matchParentBounds() { + return getOverrideBounds().isEmpty(); + } + + /** + * Returns whether the bounds specified is considered the same as the existing override bounds. + * This is either when the two bounds are equal or the override bounds is empty and the + * specified bounds is null. + * + * @return {@code true} if the bounds are equivalent, {@code false} otherwise + */ + public boolean equivalentOverrideBounds(Rect bounds) { + return equivalentBounds(getOverrideBounds(), bounds); + } + + /** + * Returns whether the two bounds are equal to each other or are a combination of null or empty. + */ + public static boolean equivalentBounds(Rect bounds, Rect other) { + return bounds == other + || (bounds != null && (bounds.equals(other) || (bounds.isEmpty() && other == null))) + || (other != null && other.isEmpty() && bounds == null); + } + + /** + * Returns the effective bounds of this container, inheriting the first non-empty bounds set in + * its ancestral hierarchy, including itself. + * @return + */ + public Rect getBounds() { + mReturnBounds.set(getConfiguration().windowConfiguration.getBounds()); + return mReturnBounds; + } + + public void getBounds(Rect outBounds) { + outBounds.set(getBounds()); + } + + /** + * Returns the current bounds explicitly set on this container. The {@link Rect} handed back is + * shared for all calls to this method and should not be modified. + */ + public Rect getOverrideBounds() { + mReturnBounds.set(getOverrideConfiguration().windowConfiguration.getBounds()); + + return mReturnBounds; + } + + /** + * Sets the passed in {@link Rect} to the current bounds. + * @see {@link #getOverrideBounds()}. + */ + public void getOverrideBounds(Rect outBounds) { + outBounds.set(getOverrideBounds()); + } + + /** + * Sets the bounds at the current hierarchy level, overriding any bounds set on an ancestor. + * This value will be reported when {@link #getBounds()} and {@link #getOverrideBounds()}. If + * an empty {@link Rect} or null is specified, this container will be considered to match its + * parent bounds {@see #matchParentBounds} and will inherit bounds from its parent. + * @param bounds The bounds defining the container size. + * @return a bitmask representing the types of changes made to the bounds. + */ + public int setBounds(Rect bounds) { + int boundsChange = diffOverrideBounds(bounds); + + if (boundsChange == BOUNDS_CHANGE_NONE) { + return boundsChange; + } + + + mTmpConfig.setTo(getOverrideConfiguration()); + mTmpConfig.windowConfiguration.setBounds(bounds); + onOverrideConfigurationChanged(mTmpConfig); + + return boundsChange; + } + + public int setBounds(int left, int top, int right, int bottom) { + mTmpRect.set(left, top, right, bottom); + return setBounds(mTmpRect); + } + + int diffOverrideBounds(Rect bounds) { + if (equivalentOverrideBounds(bounds)) { + return BOUNDS_CHANGE_NONE; + } + + int boundsChange = BOUNDS_CHANGE_NONE; + + final Rect existingBounds = getOverrideBounds(); + + if (bounds == null || existingBounds.left != bounds.left + || existingBounds.top != bounds.top) { + boundsChange |= BOUNDS_CHANGE_POSITION; + } + + if (bounds == null || existingBounds.width() != bounds.width() + || existingBounds.height() != bounds.height()) { + boundsChange |= BOUNDS_CHANGE_SIZE; + } + + return boundsChange; + } + public WindowConfiguration getWindowConfiguration() { return mFullConfiguration.windowConfiguration; } @@ -375,6 +503,10 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return toString(); } + boolean isAlwaysOnTop() { + return mFullConfiguration.windowConfiguration.isAlwaysOnTop(); + } + abstract protected int getChildCount(); abstract protected E getChildAt(int index); diff --git a/com/android/server/wm/DimLayer.java b/com/android/server/wm/DimLayer.java deleted file mode 100644 index 8fb2be8c..00000000 --- a/com/android/server/wm/DimLayer.java +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER; -import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC; -import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; - -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.os.SystemClock; -import android.util.Slog; -import android.view.DisplayInfo; -import android.view.SurfaceControl; - -import java.io.PrintWriter; - -public class DimLayer { - private static final String TAG = TAG_WITH_CLASS_NAME ? "DimLayer" : TAG_WM; - private final WindowManagerService mService; - - /** Actual surface that dims */ - private SurfaceControl mDimSurface; - - /** Last value passed to mDimSurface.setAlpha() */ - private float mAlpha = 0; - - /** Last value passed to mDimSurface.setLayer() */ - private int mLayer = -1; - - /** Next values to pass to mDimSurface.setPosition() and mDimSurface.setSize() */ - private final Rect mBounds = new Rect(); - - /** Last values passed to mDimSurface.setPosition() and mDimSurface.setSize() */ - private final Rect mLastBounds = new Rect(); - - /** True after mDimSurface.show() has been called, false after mDimSurface.hide(). */ - private boolean mShowing = false; - - /** Value of mAlpha when beginning transition to mTargetAlpha */ - private float mStartAlpha = 0; - - /** Final value of mAlpha following transition */ - private float mTargetAlpha = 0; - - /** Time in units of SystemClock.uptimeMillis() at which the current transition started */ - private long mStartTime; - - /** Time in milliseconds to take to transition from mStartAlpha to mTargetAlpha */ - private long mDuration; - - private boolean mDestroyed = false; - - private final int mDisplayId; - - - /** Interface implemented by users of the dim layer */ - interface DimLayerUser { - /** Returns true if the dim should be fullscreen. */ - boolean dimFullscreen(); - /** Returns the display info. of the dim layer user. */ - DisplayInfo getDisplayInfo(); - /** Returns true if the dim layer user is currently attached to a display */ - boolean isAttachedToDisplay(); - /** Gets the bounds of the dim layer user. */ - void getDimBounds(Rect outBounds); - /** Returns the layer to place a dim layer. */ - default int getLayerForDim(WindowStateAnimator animator, int layerOffset, - int defaultLayer) { - return defaultLayer; - } - - String toShortString(); - } - /** The user of this dim layer. */ - private final DimLayerUser mUser; - - private final String mName; - - DimLayer(WindowManagerService service, DimLayerUser user, int displayId, String name) { - mUser = user; - mDisplayId = displayId; - mService = service; - mName = name; - if (DEBUG_DIM_LAYER) Slog.v(TAG, "Ctor: displayId=" + displayId); - } - - private void constructSurface(WindowManagerService service) { - service.openSurfaceTransaction(); - try { - mDimSurface = new SurfaceControl.Builder(service.mFxSession) - .setName(mName) - .setSize(16, 16) - .setColorLayer(true) - .build(); - - if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG, - " DIM " + mDimSurface + ": CREATE"); - mDimSurface.setLayerStack(mDisplayId); - adjustBounds(); - adjustAlpha(mAlpha); - adjustLayer(mLayer); - } catch (Exception e) { - Slog.e(TAG_WM, "Exception creating Dim surface", e); - } finally { - service.closeSurfaceTransaction("DimLayer.constructSurface"); - } - } - - /** Return true if dim layer is showing */ - boolean isDimming() { - return mTargetAlpha != 0; - } - - /** Return true if in a transition period */ - boolean isAnimating() { - return mTargetAlpha != mAlpha; - } - - float getTargetAlpha() { - return mTargetAlpha; - } - - void setLayer(int layer) { - if (mLayer == layer) { - return; - } - mLayer = layer; - adjustLayer(layer); - } - - private void adjustLayer(int layer) { - if (mDimSurface != null) { - mDimSurface.setLayer(layer); - } - } - - int getLayer() { - return mLayer; - } - - private void setAlpha(float alpha) { - if (mAlpha == alpha) { - return; - } - mAlpha = alpha; - adjustAlpha(alpha); - } - - private void adjustAlpha(float alpha) { - if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha alpha=" + alpha); - try { - if (mDimSurface != null) { - mDimSurface.setAlpha(alpha); - } - if (alpha == 0 && mShowing) { - if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha hiding"); - if (mDimSurface != null) { - mDimSurface.hide(); - mShowing = false; - } - } else if (alpha > 0 && !mShowing) { - if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha showing"); - if (mDimSurface != null) { - mDimSurface.show(); - mShowing = true; - } - } - } catch (RuntimeException e) { - Slog.w(TAG, "Failure setting alpha immediately", e); - } - } - - /** - * NOTE: Must be called with Surface transaction open. - */ - private void adjustBounds() { - if (mUser.dimFullscreen()) { - getBoundsForFullscreen(mBounds); - } - - if (mDimSurface != null) { - mDimSurface.setPosition(mBounds.left, mBounds.top); - mDimSurface.setSize(mBounds.width(), mBounds.height()); - if (DEBUG_DIM_LAYER) Slog.v(TAG, - "adjustBounds user=" + mUser.toShortString() + " mBounds=" + mBounds); - } - - mLastBounds.set(mBounds); - } - - private void getBoundsForFullscreen(Rect outBounds) { - final int dw, dh; - final float xPos, yPos; - // Set surface size to screen size. - final DisplayInfo info = mUser.getDisplayInfo(); - // Multiply by 1.5 so that rotating a frozen surface that includes this does not expose - // a corner. - dw = (int) (info.logicalWidth * 1.5); - dh = (int) (info.logicalHeight * 1.5); - // back off position so 1/4 of Surface is before and 1/4 is after. - xPos = -1 * dw / 6; - yPos = -1 * dh / 6; - outBounds.set((int) xPos, (int) yPos, (int) xPos + dw, (int) yPos + dh); - } - - void setBoundsForFullscreen() { - getBoundsForFullscreen(mBounds); - setBounds(mBounds); - } - - /** @param bounds The new bounds to set */ - void setBounds(Rect bounds) { - mBounds.set(bounds); - if (isDimming() && !mLastBounds.equals(bounds)) { - try { - mService.openSurfaceTransaction(); - adjustBounds(); - } catch (RuntimeException e) { - Slog.w(TAG, "Failure setting size", e); - } finally { - mService.closeSurfaceTransaction("DimLayer.setBounds"); - } - } - } - - /** - * @param duration The time to test. - * @return True if the duration would lead to an earlier end to the current animation. - */ - private boolean durationEndsEarlier(long duration) { - return SystemClock.uptimeMillis() + duration < mStartTime + mDuration; - } - - /** Jump to the end of the animation. - * NOTE: Must be called with Surface transaction open. */ - void show() { - if (isAnimating()) { - if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: immediate"); - show(mLayer, mTargetAlpha, 0); - } - } - - /** - * Begin an animation to a new dim value. - * NOTE: Must be called with Surface transaction open. - * - * @param layer The layer to set the surface to. - * @param alpha The dim value to end at. - * @param duration How long to take to get there in milliseconds. - */ - void show(int layer, float alpha, long duration) { - if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: layer=" + layer + " alpha=" + alpha - + " duration=" + duration + ", mDestroyed=" + mDestroyed); - if (mDestroyed) { - Slog.e(TAG, "show: no Surface"); - // Make sure isAnimating() returns false. - mTargetAlpha = mAlpha = 0; - return; - } - - if (mDimSurface == null) { - constructSurface(mService); - } - - if (!mLastBounds.equals(mBounds)) { - adjustBounds(); - } - setLayer(layer); - - long curTime = SystemClock.uptimeMillis(); - final boolean animating = isAnimating(); - if ((animating && (mTargetAlpha != alpha || durationEndsEarlier(duration))) - || (!animating && mAlpha != alpha)) { - if (duration <= 0) { - // No animation required, just set values. - setAlpha(alpha); - } else { - // Start or continue animation with new parameters. - mStartAlpha = mAlpha; - mStartTime = curTime; - mDuration = duration; - } - } - mTargetAlpha = alpha; - if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: mStartAlpha=" + mStartAlpha + " mStartTime=" - + mStartTime + " mTargetAlpha=" + mTargetAlpha); - } - - /** Immediate hide. - * NOTE: Must be called with Surface transaction open. */ - void hide() { - if (mShowing) { - if (DEBUG_DIM_LAYER) Slog.v(TAG, "hide: immediate"); - hide(0); - } - } - - /** - * Gradually fade to transparent. - * NOTE: Must be called with Surface transaction open. - * - * @param duration Time to fade in milliseconds. - */ - void hide(long duration) { - if (mShowing && (mTargetAlpha != 0 || durationEndsEarlier(duration))) { - if (DEBUG_DIM_LAYER) Slog.v(TAG, "hide: duration=" + duration); - show(mLayer, 0, duration); - } - } - - /** - * Advance the dimming per the last #show(int, float, long) call. - * NOTE: Must be called with Surface transaction open. - * - * @return True if animation is still required after this step. - */ - boolean stepAnimation() { - if (mDestroyed) { - Slog.e(TAG, "stepAnimation: surface destroyed"); - // Ensure that isAnimating() returns false; - mTargetAlpha = mAlpha = 0; - return false; - } - if (isAnimating()) { - final long curTime = SystemClock.uptimeMillis(); - final float alphaDelta = mTargetAlpha - mStartAlpha; - float alpha = mStartAlpha + alphaDelta * (curTime - mStartTime) / mDuration; - if (alphaDelta > 0 && alpha > mTargetAlpha || - alphaDelta < 0 && alpha < mTargetAlpha) { - // Don't exceed limits. - alpha = mTargetAlpha; - } - if (DEBUG_DIM_LAYER) Slog.v(TAG, "stepAnimation: curTime=" + curTime + " alpha=" + alpha); - setAlpha(alpha); - } - - return isAnimating(); - } - - /** Cleanup */ - void destroySurface() { - if (DEBUG_DIM_LAYER) Slog.v(TAG, "destroySurface."); - if (mDimSurface != null) { - mDimSurface.destroy(); - mDimSurface = null; - } - mDestroyed = true; - } - - public void printTo(String prefix, PrintWriter pw) { - pw.print(prefix); pw.print("mDimSurface="); pw.print(mDimSurface); - pw.print(" mLayer="); pw.print(mLayer); - pw.print(" mAlpha="); pw.println(mAlpha); - pw.print(prefix); pw.print("mLastBounds="); pw.print(mLastBounds.toShortString()); - pw.print(" mBounds="); pw.println(mBounds.toShortString()); - pw.print(prefix); pw.print("Last animation: "); - pw.print(" mDuration="); pw.print(mDuration); - pw.print(" mStartTime="); pw.print(mStartTime); - pw.print(" curTime="); pw.println(SystemClock.uptimeMillis()); - pw.print(prefix); pw.print(" mStartAlpha="); pw.print(mStartAlpha); - pw.print(" mTargetAlpha="); pw.println(mTargetAlpha); - } -} diff --git a/com/android/server/wm/DimLayerController.java b/com/android/server/wm/DimLayerController.java deleted file mode 100644 index 6f9e45a6..00000000 --- a/com/android/server/wm/DimLayerController.java +++ /dev/null @@ -1,403 +0,0 @@ -package com.android.server.wm; - -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM; - -import android.graphics.Rect; -import android.util.ArrayMap; -import android.util.Slog; -import android.util.TypedValue; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.wm.DimLayer.DimLayerUser; - -import java.io.PrintWriter; - -/** - * Centralizes the control of dim layers used for - * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND} - * as well as other use cases (such as dimming above a dead window). - */ -class DimLayerController { - private static final String TAG_LOCAL = "DimLayerController"; - private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM; - - /** Amount of time in milliseconds to animate the dim surface from one value to another, - * when no window animation is driving it. */ - private static final int DEFAULT_DIM_DURATION = 200; - - /** - * The default amount of dim applied over a dead window - */ - private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f; - - // Shared dim layer for fullscreen users. {@link DimLayerState#dimLayer} will point to this - // instead of creating a new object per fullscreen task on a display. - private DimLayer mSharedFullScreenDimLayer; - - private ArrayMap<DimLayer.DimLayerUser, DimLayerState> mState = new ArrayMap<>(); - - private DisplayContent mDisplayContent; - - private Rect mTmpBounds = new Rect(); - - DimLayerController(DisplayContent displayContent) { - mDisplayContent = displayContent; - } - - /** Updates the dim layer bounds, recreating it if needed. */ - void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) { - final DimLayerState state = getOrCreateDimLayerState(dimLayerUser); - final boolean previousFullscreen = state.dimLayer != null - && state.dimLayer == mSharedFullScreenDimLayer; - DimLayer newDimLayer; - final int displayId = mDisplayContent.getDisplayId(); - if (dimLayerUser.dimFullscreen()) { - if (previousFullscreen && mSharedFullScreenDimLayer != null) { - // Update the bounds for fullscreen in case of rotation. - mSharedFullScreenDimLayer.setBoundsForFullscreen(); - return; - } - // Use shared fullscreen dim layer - newDimLayer = mSharedFullScreenDimLayer; - if (newDimLayer == null) { - if (state.dimLayer != null) { - // Re-purpose the previous dim layer. - newDimLayer = state.dimLayer; - } else { - // Create new full screen dim layer. - newDimLayer = new DimLayer(mDisplayContent.mService, dimLayerUser, displayId, - getDimLayerTag(dimLayerUser)); - } - dimLayerUser.getDimBounds(mTmpBounds); - newDimLayer.setBounds(mTmpBounds); - mSharedFullScreenDimLayer = newDimLayer; - } else if (state.dimLayer != null) { - state.dimLayer.destroySurface(); - } - } else { - newDimLayer = (state.dimLayer == null || previousFullscreen) - ? new DimLayer(mDisplayContent.mService, dimLayerUser, displayId, - getDimLayerTag(dimLayerUser)) - : state.dimLayer; - dimLayerUser.getDimBounds(mTmpBounds); - newDimLayer.setBounds(mTmpBounds); - } - state.dimLayer = newDimLayer; - } - - private static String getDimLayerTag(DimLayerUser dimLayerUser) { - return TAG_LOCAL + "/" + dimLayerUser.toShortString(); - } - - private DimLayerState getOrCreateDimLayerState(DimLayer.DimLayerUser dimLayerUser) { - if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser=" - + dimLayerUser.toShortString()); - DimLayerState state = mState.get(dimLayerUser); - if (state == null) { - state = new DimLayerState(); - mState.put(dimLayerUser, state); - } - return state; - } - - private void setContinueDimming(DimLayer.DimLayerUser dimLayerUser) { - DimLayerState state = mState.get(dimLayerUser); - if (state == null) { - if (DEBUG_DIM_LAYER) Slog.w(TAG, "setContinueDimming, no state for: " - + dimLayerUser.toShortString()); - return; - } - state.continueDimming = true; - } - - boolean isDimming() { - for (int i = mState.size() - 1; i >= 0; i--) { - DimLayerState state = mState.valueAt(i); - if (state.dimLayer != null && state.dimLayer.isDimming()) { - return true; - } - } - return false; - } - - void resetDimming() { - for (int i = mState.size() - 1; i >= 0; i--) { - mState.valueAt(i).continueDimming = false; - } - } - - private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) { - DimLayerState state = mState.get(dimLayerUser); - return state != null && state.continueDimming; - } - - void startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser, - WindowStateAnimator newWinAnimator, boolean aboveApp) { - // Only set dim params on the highest dimmed layer. - // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer. - DimLayerState state = getOrCreateDimLayerState(dimLayerUser); - state.dimAbove = aboveApp; - if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded," - + " dimLayerUser=" + dimLayerUser.toShortString() - + " newWinAnimator=" + newWinAnimator - + " state.animator=" + state.animator); - if (newWinAnimator.getShown() && (state.animator == null - || !state.animator.getShown() - || state.animator.mAnimLayer <= newWinAnimator.mAnimLayer)) { - state.animator = newWinAnimator; - if (state.animator.mWin.mAppToken == null && !dimLayerUser.dimFullscreen()) { - // Dim should cover the entire screen for system windows. - mDisplayContent.getLogicalDisplayRect(mTmpBounds); - } else { - dimLayerUser.getDimBounds(mTmpBounds); - } - state.dimLayer.setBounds(mTmpBounds); - } - } - - void stopDimmingIfNeeded() { - if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded, mState.size()=" + mState.size()); - for (int i = mState.size() - 1; i >= 0; i--) { - DimLayer.DimLayerUser dimLayerUser = mState.keyAt(i); - stopDimmingIfNeeded(dimLayerUser); - } - } - - private void stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser) { - // No need to check if state is null, we know the key has a value. - DimLayerState state = mState.get(dimLayerUser); - if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded," - + " dimLayerUser=" + dimLayerUser.toShortString() - + " state.continueDimming=" + state.continueDimming - + " state.dimLayer.isDimming=" + state.dimLayer.isDimming()); - if (state.animator != null && state.animator.mWin.mWillReplaceWindow) { - return; - } - - if (!state.continueDimming && state.dimLayer.isDimming()) { - state.animator = null; - dimLayerUser.getDimBounds(mTmpBounds); - state.dimLayer.setBounds(mTmpBounds); - } - } - - boolean animateDimLayers() { - int fullScreen = -1; - int fullScreenAndDimming = -1; - int topFullScreenUserLayer = 0; - boolean result = false; - - for (int i = mState.size() - 1; i >= 0; i--) { - final DimLayer.DimLayerUser user = mState.keyAt(i); - final DimLayerState state = mState.valueAt(i); - - if (!user.isAttachedToDisplay()) { - // Leaked dim user that is no longer attached to the display. Go ahead and clean it - // clean-up and log what happened. - // TODO: This is a work around for b/34395537 as the dim user should have cleaned-up - // it self when it was detached from the display. Need to investigate how the dim - // user is leaking... - //Slog.wtfStack(TAG_WM, "Leaked dim user=" + user.toShortString() - // + " state=" + state); - Slog.w(TAG_WM, "Leaked dim user=" + user.toShortString() + " state=" + state); - removeDimLayerUser(user); - continue; - } - - // We have to check that we are actually the shared fullscreen layer - // for this path. If we began as non fullscreen and became fullscreen - // (e.g. Docked stack closing), then we may not be the shared layer - // and we have to make sure we always animate the layer. - if (user.dimFullscreen() && state.dimLayer == mSharedFullScreenDimLayer) { - fullScreen = i; - if (!state.continueDimming) { - continue; - } - - // When choosing which user to assign the shared fullscreen layer to - // we need to look at Z-order. - if (topFullScreenUserLayer == 0 || - (state.animator != null && state.animator.mAnimLayer > topFullScreenUserLayer)) { - fullScreenAndDimming = i; - if (state.animator != null) { - topFullScreenUserLayer = state.animator.mAnimLayer; - } - } - } else { - // We always want to animate the non fullscreen windows, they don't share their - // dim layers. - result |= animateDimLayers(user); - } - } - // For the shared, full screen dim layer, we prefer the animation that is causing it to - // appear. - if (fullScreenAndDimming != -1) { - result |= animateDimLayers(mState.keyAt(fullScreenAndDimming)); - } else if (fullScreen != -1) { - // If there is no animation for the full screen dim layer to appear, we can use any of - // the animators that will cause it to disappear. - result |= animateDimLayers(mState.keyAt(fullScreen)); - } - return result; - } - - private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) { - DimLayerState state = mState.get(dimLayerUser); - if (DEBUG_DIM_LAYER) Slog.v(TAG, "animateDimLayers," - + " dimLayerUser=" + dimLayerUser.toShortString() - + " state.animator=" + state.animator - + " state.continueDimming=" + state.continueDimming); - final int dimLayer; - final float dimAmount; - if (state.animator == null) { - dimLayer = state.dimLayer.getLayer(); - dimAmount = 0; - } else { - if (state.dimAbove) { - dimLayer = state.animator.mAnimLayer + LAYER_OFFSET_DIM; - dimAmount = DEFAULT_DIM_AMOUNT_DEAD_WINDOW; - } else { - dimLayer = dimLayerUser.getLayerForDim(state.animator, LAYER_OFFSET_DIM, - state.animator.mAnimLayer - LAYER_OFFSET_DIM); - dimAmount = state.animator.mWin.mAttrs.dimAmount; - } - } - final float targetAlpha = state.dimLayer.getTargetAlpha(); - if (targetAlpha != dimAmount) { - if (state.animator == null) { - state.dimLayer.hide(DEFAULT_DIM_DURATION); - } else { - long duration = (state.animator.mAnimating && state.animator.mAnimation != null) - ? state.animator.mAnimation.computeDurationHint() - : DEFAULT_DIM_DURATION; - if (targetAlpha > dimAmount) { - duration = getDimLayerFadeDuration(duration); - } - state.dimLayer.show(dimLayer, dimAmount, duration); - - // If we showed a dim layer, make sure to redo the layout because some things depend - // on whether a dim layer is showing or not. - if (targetAlpha == 0) { - mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT; - mDisplayContent.setLayoutNeeded(); - } - } - } else if (state.dimLayer.getLayer() != dimLayer) { - state.dimLayer.setLayer(dimLayer); - } - if (state.dimLayer.isAnimating()) { - if (!mDisplayContent.okToAnimate()) { - // Jump to the end of the animation. - state.dimLayer.show(); - } else { - return state.dimLayer.stepAnimation(); - } - } - return false; - } - - boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) { - DimLayerState state = mState.get(dimLayerUser); - return state != null && state.animator == winAnimator && state.dimLayer.isDimming(); - } - - private long getDimLayerFadeDuration(long duration) { - TypedValue tv = new TypedValue(); - mDisplayContent.mService.mContext.getResources().getValue( - com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true); - if (tv.type == TypedValue.TYPE_FRACTION) { - duration = (long) tv.getFraction(duration, duration); - } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) { - duration = tv.data; - } - return duration; - } - - void close() { - for (int i = mState.size() - 1; i >= 0; i--) { - DimLayerState state = mState.valueAt(i); - state.dimLayer.destroySurface(); - } - mState.clear(); - mSharedFullScreenDimLayer = null; - } - - void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) { - DimLayerState state = mState.get(dimLayerUser); - if (state != null) { - // Destroy the surface, unless it's the shared fullscreen dim. - if (state.dimLayer != mSharedFullScreenDimLayer) { - state.dimLayer.destroySurface(); - } - mState.remove(dimLayerUser); - } - if (mState.isEmpty()) { - mSharedFullScreenDimLayer = null; - } - } - - @VisibleForTesting - boolean hasDimLayerUser(DimLayer.DimLayerUser dimLayerUser) { - return mState.containsKey(dimLayerUser); - } - - @VisibleForTesting - boolean hasSharedFullScreenDimLayer() { - return mSharedFullScreenDimLayer != null; - } - - void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) { - applyDim(dimLayerUser, animator, false /* aboveApp */); - } - - void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) { - applyDim(dimLayerUser, animator, true /* aboveApp */); - } - - void applyDim( - DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp) { - if (dimLayerUser == null) { - Slog.e(TAG, "Trying to apply dim layer for: " + this - + ", but no dim layer user found."); - return; - } - if (!getContinueDimming(dimLayerUser)) { - setContinueDimming(dimLayerUser); - if (!isDimming(dimLayerUser, animator)) { - if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming."); - startDimmingIfNeeded(dimLayerUser, animator, aboveApp); - } - } - } - - private static class DimLayerState { - // The particular window requesting a dim layer. If null, hide dimLayer. - WindowStateAnimator animator; - // Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the - // end then stop any dimming. - boolean continueDimming; - DimLayer dimLayer; - boolean dimAbove; - } - - void dump(String prefix, PrintWriter pw) { - pw.println(prefix + "DimLayerController"); - final String doubleSpace = " "; - final String prefixPlusDoubleSpace = prefix + doubleSpace; - - for (int i = 0, n = mState.size(); i < n; i++) { - pw.println(prefixPlusDoubleSpace + mState.keyAt(i).toShortString()); - DimLayerState state = mState.valueAt(i); - pw.println(prefixPlusDoubleSpace + doubleSpace + "dimLayer=" - + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" : state.dimLayer) - + ", animator=" + state.animator + ", continueDimming=" + state.continueDimming); - if (state.dimLayer != null) { - state.dimLayer.printTo(prefixPlusDoubleSpace + doubleSpace, pw); - } - } - } -} diff --git a/com/android/server/wm/Dimmer.java b/com/android/server/wm/Dimmer.java new file mode 100644 index 00000000..9fe16ae8 --- /dev/null +++ b/com/android/server/wm/Dimmer.java @@ -0,0 +1,195 @@ +/* + * 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 com.android.server.wm; + +import android.util.ArrayMap; +import android.util.Slog; +import android.view.SurfaceControl; +import android.graphics.Rect; + +/** + * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is + * black layers of varying opacity at various Z-levels which create the effect of a Dim. + */ +class Dimmer { + private static final String TAG = "WindowManager"; + + private class DimState { + SurfaceControl mSurfaceControl; + boolean mDimming; + + /** + * Used for Dims not assosciated with a WindowContainer. See {@link Dimmer#dimAbove} for + * details on Dim lifecycle. + */ + boolean mDontReset; + + DimState(SurfaceControl ctl) { + mSurfaceControl = ctl; + mDimming = true; + } + }; + + private ArrayMap<WindowContainer, DimState> mDimLayerUsers = new ArrayMap<>(); + + /** + * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the + * host, some controller of it, or one of the hosts children. + */ + private WindowContainer mHost; + + Dimmer(WindowContainer host) { + mHost = host; + } + + SurfaceControl makeDimLayer() { + final SurfaceControl control = mHost.makeChildSurface(null) + .setParent(mHost.getSurfaceControl()) + .setColorLayer(true) + .setName("Dim Layer for - " + mHost.getName()) + .build(); + return control; + } + + /** + * Retreive the DimState for a given child of the host. + */ + DimState getDimState(WindowContainer container) { + DimState state = mDimLayerUsers.get(container); + if (state == null) { + final SurfaceControl ctl = makeDimLayer(); + state = new DimState(ctl); + /** + * See documentation on {@link #dimAbove} to understand lifecycle management of Dim's + * via state resetting for Dim's with containers. + */ + if (container == null) { + state.mDontReset = true; + } + mDimLayerUsers.put(container, state); + } + return state; + } + + private void dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer, + float alpha) { + final DimState d = getDimState(container); + t.show(d.mSurfaceControl); + if (container != null) { + t.setRelativeLayer(d.mSurfaceControl, + container.getSurfaceControl(), relativeLayer); + } else { + t.setLayer(d.mSurfaceControl, Integer.MAX_VALUE); + } + t.setAlpha(d.mSurfaceControl, alpha); + + d.mDimming = true; + } + + /** + * Finish a dim started by dimAbove in the case there was no call to dimAbove. + * + * @param t A Transaction in which to finish the dim. + */ + void stopDim(SurfaceControl.Transaction t) { + DimState d = getDimState(null); + t.hide(d.mSurfaceControl); + d.mDontReset = false; + } + /** + * Place a Dim above the entire host container. The caller is responsible for calling stopDim to + * remove this effect. If the Dim can be assosciated with a particular child of the host + * consider using the other variant of dimAbove which ties the Dim lifetime to the child + * lifetime more explicitly. + * + * @param t A transaction in which to apply the Dim. + * @param alpha The alpha at which to Dim. + */ + void dimAbove(SurfaceControl.Transaction t, float alpha) { + dim(t, null, 1, alpha); + } + + /** + * Place a dim above the given container, which should be a child of the host container. + * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset + * and the child should call dimAbove again to request the Dim to continue. + * + * @param t A transaction in which to apply the Dim. + * @param container The container which to dim above. Should be a child of our host. + * @param alpha The alpha at which to Dim. + */ + void dimAbove(SurfaceControl.Transaction t, WindowContainer container, float alpha) { + dim(t, container, 1, alpha); + } + + /** + * Like {@link #dimAbove} but places the dim below the given container. + * + * @param t A transaction in which to apply the Dim. + * @param container The container which to dim below. Should be a child of our host. + * @param alpha The alpha at which to Dim. + */ + + void dimBelow(SurfaceControl.Transaction t, WindowContainer container, float alpha) { + dim(t, container, -1, alpha); + } + + /** + * Mark all dims as pending completion on the next call to {@link #updateDims} + * + * This is intended for us by the host container, to be called at the beginning of + * {@link WindowContainer#prepareSurfaces}. After calling this, the container should + * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them + * a chance to request dims to continue. + */ + void resetDimStates() { + for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) { + final DimState state = mDimLayerUsers.valueAt(i); + if (state.mDontReset == false) { + state.mDimming = false; + } + } + } + + /** + * Call after invoking {@link WindowContainer#prepareSurfaces} on children as + * described in {@link #resetDimStates}. + * + * @param t A transaction in which to update the dims. + * @param bounds The bounds at which to dim. + * @return true if any Dims were updated. + */ + boolean updateDims(SurfaceControl.Transaction t, Rect bounds) { + boolean didSomething = false; + for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) { + DimState state = mDimLayerUsers.valueAt(i); + // TODO: We want to animate the addition and removal of Dim's instead of immediately + // acting. When we do this we need to take care to account for the "Replacing Windows" + // case (and seamless dim transfer). + if (state.mDimming == false) { + mDimLayerUsers.removeAt(i); + state.mSurfaceControl.destroy(); + } else { + didSomething = true; + // TODO: Once we use geometry from hierarchy this falls away. + t.setSize(state.mSurfaceControl, bounds.width(), bounds.height()); + t.setPosition(state.mSurfaceControl, bounds.left, bounds.top); + } + } + return didSomething; + } +} diff --git a/com/android/server/wm/DisplayContent.java b/com/android/server/wm/DisplayContent.java index 4d839d00..41348ba4 100644 --- a/com/android/server/wm/DisplayContent.java +++ b/com/android/server/wm/DisplayContent.java @@ -58,10 +58,10 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT; @@ -142,14 +142,15 @@ import android.util.proto.ProtoOutputStream; import android.view.Display; import android.view.DisplayInfo; import android.view.InputDevice; +import android.view.MagnificationSpec; import android.view.Surface; import android.view.SurfaceControl; -import android.view.WindowManagerPolicy; +import android.view.SurfaceSession; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ToBooleanFunction; import com.android.internal.view.IInputMethodClient; -import android.view.DisplayFrames; +import com.android.server.policy.WindowManagerPolicy; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -180,8 +181,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final TaskStackContainers mTaskStackContainers = new TaskStackContainers(); // Contains all non-app window containers that should be displayed above the app containers // (e.g. Status bar) - private final NonAppWindowContainers mAboveAppWindowsContainers = - new NonAppWindowContainers("mAboveAppWindowsContainers"); + private final AboveAppWindowContainers mAboveAppWindowsContainers = + new AboveAppWindowContainers("mAboveAppWindowsContainers"); // Contains all non-app window containers that should be displayed below the app containers // (e.g. Wallpaper). private final NonAppWindowContainers mBelowAppWindowsContainers = @@ -313,6 +314,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final Matrix mTmpMatrix = new Matrix(); private final Region mTmpRegion = new Region(); + /** Used for handing back size of display */ + private final Rect mTmpBounds = new Rect(); + WindowManagerService mService; /** Remove this display when animation on it has completed. */ @@ -321,8 +325,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final DockedStackDividerController mDividerControllerLocked; final PinnedStackController mPinnedStackControllerLocked; - DimLayerController mDimLayerController; - final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>(); private boolean mHaveBootMsg = false; @@ -346,10 +348,38 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // {@code false} if this display is in the processing of being created. private boolean mDisplayReady = false; - private final WindowLayersController mLayersController; WallpaperController mWallpaperController; int mInputMethodAnimLayerAdjustment; + private final SurfaceSession mSession = new SurfaceSession(); + + /** + * We organize all top-level Surfaces in to the following layers. + * mOverlayLayer contains a few Surfaces which are always on top of others + * and omitted from Screen-Magnification, for example the strict mode flash or + * the magnification overlay itself. + * {@link #mWindowingLayer} contains everything else. + */ + private SurfaceControl mOverlayLayer; + + /** + * See {@link #mOverlayLayer} + */ + private SurfaceControl mWindowingLayer; + + /** + * Specifies the size of the surfaces in {@link #mOverlayLayer} and {@link #mWindowingLayer}. + * <p> + * For these surfaces currently we use a surface based on the larger of width or height so we + * don't have to resize when rotating the display. + */ + private int mSurfaceSize; + + /** + * A list of surfaces to be destroyed after {@link #mPendingTransaction} is applied. + */ + private final ArrayList<SurfaceControl> mPendingDestroyingSurfaces = new ArrayList<>(); + private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> { WindowStateAnimator winAnimator = w.mWinAnimator; if (winAnimator.hasSurface()) { @@ -503,9 +533,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return true; }; - private final Consumer<WindowState> mPrepareWindowSurfaces = - w -> w.mWinAnimator.prepareSurfaceLocked(true); - private final Consumer<WindowState> mPerformLayout = w -> { // Don't do layout of a window if it is not visible, or soon won't be visible, to avoid // wasting time and funky changes while a window is animating away. @@ -558,12 +585,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo w.updateLastInsetValues(); } - // Window frames may have changed. Update dim layer with the new bounds. - final Task task = w.getTask(); - if (task != null) { - mDimLayerController.updateDimLayer(task); - } - if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.mFrame + " mContainingFrame=" + w.mContainingFrame + " mDisplayFrame=" + w.mDisplayFrame); @@ -657,8 +678,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } - w.applyDimLayerIfNeeded(); - if (isDefaultDisplay && obscuredChanged && w.isVisibleLw() && mWallpaperController.isWallpaperTarget(w)) { // This is the wallpaper target and its obscured state changed... make sure the @@ -741,13 +760,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * initialize direct children. * @param display May not be null. * @param service You know. - * @param layersController window layer controller used to assign layer to the windows on this - * display. * @param wallpaperController wallpaper windows controller used to adjust the positioning of the * wallpaper windows in the window list. */ DisplayContent(Display display, WindowManagerService service, - WindowLayersController layersController, WallpaperController wallpaperController) { + WallpaperController wallpaperController) { if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) { throw new IllegalArgumentException("Display with ID=" + display.getDisplayId() + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId()) @@ -756,7 +773,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mDisplay = display; mDisplayId = display.getDisplayId(); - mLayersController = layersController; mWallpaperController = wallpaperController; display.getDisplayInfo(mDisplayInfo); display.getMetrics(mDisplayMetrics); @@ -766,7 +782,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo initializeDisplayBaseInfo(); mDividerControllerLocked = new DockedStackDividerController(service, this); mPinnedStackControllerLocked = new PinnedStackController(service, this); - mDimLayerController = new DimLayerController(this); + + mSurfaceSize = Math.max(mBaseDisplayHeight, mBaseDisplayWidth); + + final SurfaceControl.Builder b = mService.makeSurfaceBuilder(mSession) + .setSize(mSurfaceSize, mSurfaceSize) + .setOpaque(true); + mWindowingLayer = b.setName("Display Root").build(); + mOverlayLayer = b.setName("Display Overlays").build(); + + getPendingTransaction().setLayer(mWindowingLayer, 0) + .setLayerStack(mWindowingLayer, mDisplayId) + .show(mWindowingLayer) + .setLayer(mOverlayLayer, 1) + .setLayerStack(mOverlayLayer, mDisplayId) + .show(mOverlayLayer); + getPendingTransaction().apply(); // These are the only direct children we should ever have and they are permanent. super.addChild(mBelowAppWindowsContainers, null); @@ -1030,11 +1061,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo setLayoutNeeded(); final int[] anim = new int[2]; - if (isDimming()) { - anim[0] = anim[1] = 0; - } else { - mService.mPolicy.selectRotationAnimationLw(anim); - } + mService.mPolicy.selectRotationAnimationLw(anim); if (!rotateSeamlessly) { mService.startFreezingDisplayLocked(inTransaction, anim[0], anim[1], this); @@ -1071,8 +1098,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // it doesn't support hardware OpenGL emulation yet. if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) { - if (screenRotationAnimation.setRotationInTransaction( - rotation, mService.mFxSession, + if (screenRotationAnimation.setRotationInTransaction(rotation, MAX_ANIMATION_DURATION, mService.getTransitionAnimationScaleLocked(), mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight)) { mService.scheduleAnimationLocked(); @@ -1201,6 +1227,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics, mCompatDisplayMetrics); } + + updateBounds(); return mDisplayInfo; } @@ -1519,8 +1547,17 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // See {@link PhoneWindowManager#setInitialDisplaySize}...sigh... mService.reconfigureDisplayLocked(this); - getDockedDividerController().onConfigurationChanged(); - getPinnedStackController().onConfigurationChanged(); + final DockedStackDividerController dividerController = getDockedDividerController(); + + if (dividerController != null) { + getDockedDividerController().onConfigurationChanged(); + } + + final PinnedStackController pinnedStackController = getPinnedStackController(); + + if (pinnedStackController != null) { + getPinnedStackController().onConfigurationChanged(); + } } /** @@ -1659,33 +1696,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi; } - void getLogicalDisplayRect(Rect out) { - // Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked. - final int orientation = mDisplayInfo.rotation; - boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270); - final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth; - final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight; - int width = mDisplayInfo.logicalWidth; - int left = (physWidth - width) / 2; - int height = mDisplayInfo.logicalHeight; - int top = (physHeight - height) / 2; - out.set(left, top, left + width, top + height); - } - - private void getLogicalDisplayRect(Rect out, int orientation) { - getLogicalDisplayRect(out); - - // Rotate the Rect if needed. - final int currentRotation = mDisplayInfo.rotation; - final int rotationDelta = deltaRotation(currentRotation, orientation); - if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) { - createRotationMatrix(rotationDelta, mBaseDisplayWidth, mBaseDisplayHeight, mTmpMatrix); - mTmpRectF.set(out); - mTmpMatrix.mapRect(mTmpRectF); - mTmpRectF.round(out); - } - } - /** * If display metrics changed, overrides are not set and it's not just a rotation - update base * values. @@ -1753,6 +1763,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight); + + updateBounds(); } void getContentRect(Rect out) { @@ -1907,22 +1919,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } - boolean animateDimLayers() { - return mDimLayerController.animateDimLayers(); - } - - private void resetDimming() { - mDimLayerController.resetDimming(); - } - - boolean isDimming() { - return mDimLayerController.isDimming(); - } - - private void stopDimmingIfNeeded() { - mDimLayerController.stopDimmingIfNeeded(); - } - @Override void removeIfPossible() { if (isAnimating()) { @@ -1938,7 +1934,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo try { super.removeImmediately(); if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this); - mDimLayerController.close(); if (mService.canDispatchPointerEvents()) { if (mTapDetector != null) { mService.unregisterPointerEventListener(mTapDetector); @@ -1947,6 +1942,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mService.unregisterPointerEventListener(mService.mMousePositionTracker); } } + // The pending transaction won't be applied so we should + // just clean up any surfaces pending destruction. + onPendingTransactionApplied(); } finally { mRemovingDisplay = false; } @@ -2096,7 +2094,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } void rotateBounds(int oldRotation, int newRotation, Rect bounds) { - getLogicalDisplayRect(mTmpRect, newRotation); + getBounds(mTmpRect, newRotation); // Compute a transform matrix to undo the coordinate space transformation, // and present the window at the same physical position it previously occupied. @@ -2228,8 +2226,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo token.dump(pw, " "); } } - pw.println(); - mDimLayerController.dump(prefix, pw); + pw.println(); // Dump stack references @@ -2342,10 +2339,16 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo /** Updates the layer assignment of windows on this display. */ void assignWindowLayers(boolean setLayoutNeeded) { - mLayersController.assignWindowLayers(this); + assignChildLayers(getPendingTransaction()); if (setLayoutNeeded) { setLayoutNeeded(); } + + // We accumlate the layer changes in-to "getPendingTransaction()" but we defer + // the application of this transaction until the animation pass triggers + // prepareSurfaces. This allows us to synchronize Z-ordering changes with + // the hiding and showing of surfaces. + scheduleAnimation(); } // TODO: This should probably be called any time a visual change is made to the hierarchy like @@ -2701,10 +2704,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } - void prepareWindowSurfaces() { - forAllWindows(mPrepareWindowSurfaces, false /* traverseTopToBottom */); - } - boolean inputMethodClientHasFocus(IInputMethodClient client) { final WindowState imFocus = computeImeTarget(false /* updateImeTarget */); if (imFocus == null) { @@ -2846,7 +2845,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } while (pendingLayoutChanges != 0); mTmpApplySurfaceChangesTransactionState.reset(); - resetDimming(); mTmpRecoveringMemory = recoveringMemory; forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */); @@ -2857,8 +2855,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mTmpApplySurfaceChangesTransactionState.preferredModeId, true /* inTraversal, must call performTraversalInTrans... below */); - stopDimmingIfNeeded(); - final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible(); if (wallpaperVisible != mLastWallpaperVisible) { mLastWallpaperVisible = wallpaperVisible; @@ -2875,6 +2871,44 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mTmpApplySurfaceChangesTransactionState.focusDisplayed; } + private void updateBounds() { + calculateBounds(mTmpBounds); + setBounds(mTmpBounds); + } + + // Determines the current display bounds based on the current state + private void calculateBounds(Rect out) { + // Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked. + final int orientation = mDisplayInfo.rotation; + boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270); + final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth; + final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight; + int width = mDisplayInfo.logicalWidth; + int left = (physWidth - width) / 2; + int height = mDisplayInfo.logicalHeight; + int top = (physHeight - height) / 2; + out.set(left, top, left + width, top + height); + } + + @Override + public void getBounds(Rect out) { + calculateBounds(out); + } + + private void getBounds(Rect out, int orientation) { + getBounds(out); + + // Rotate the Rect if needed. + final int currentRotation = mDisplayInfo.rotation; + final int rotationDelta = deltaRotation(currentRotation, orientation); + if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) { + createRotationMatrix(rotationDelta, mBaseDisplayWidth, mBaseDisplayHeight, mTmpMatrix); + mTmpRectF.set(out); + mTmpMatrix.mapRect(mTmpRectF); + mTmpRectF.round(out); + } + } + void performLayout(boolean initial, boolean updateInputWindows) { if (!isLayoutNeeded()) { return; @@ -2935,241 +2969,30 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * Takes a snapshot of the display. In landscape mode this grabs the whole screen. * In portrait mode, it grabs the full screenshot. * - * @param width the width of the target bitmap - * @param height the height of the target bitmap - * @param includeFullDisplay true if the screen should not be cropped before capture - * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1 * @param config of the output bitmap * @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot - * @param includeDecor whether to include window decors, like the status or navigation bar - * background of the window */ - Bitmap screenshotApplications(IBinder appToken, int width, int height, - boolean includeFullDisplay, float frameScale, Bitmap.Config config, - boolean wallpaperOnly, boolean includeDecor) { - Bitmap bitmap = screenshotApplications(appToken, width, height, includeFullDisplay, - frameScale, wallpaperOnly, includeDecor, SurfaceControl::screenshot); - if (bitmap == null) { - return null; - } - - if (DEBUG_SCREENSHOT) { - // TEST IF IT's ALL BLACK - int[] buffer = new int[bitmap.getWidth() * bitmap.getHeight()]; - bitmap.getPixels(buffer, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), - bitmap.getHeight()); - boolean allBlack = true; - final int firstColor = buffer[0]; - for (int i = 0; i < buffer.length; i++) { - if (buffer[i] != firstColor) { - allBlack = false; - break; - } - } - if (allBlack) { - final WindowState appWin = mScreenshotApplicationState.appWin; - final int maxLayer = mScreenshotApplicationState.maxLayer; - final int minLayer = mScreenshotApplicationState.minLayer; - Slog.i(TAG_WM, "Screenshot " + appWin + " was monochrome(" + - Integer.toHexString(firstColor) + ")! mSurfaceLayer=" + - (appWin != null ? - appWin.mWinAnimator.mSurfaceController.getLayer() : "null") + - " minLayer=" + minLayer + " maxLayer=" + maxLayer); - } - } - - // Create a copy of the screenshot that is immutable and backed in ashmem. - // This greatly reduces the overhead of passing the bitmap between processes. - Bitmap ret = bitmap.createAshmemBitmap(config); - bitmap.recycle(); - return ret; - } - - GraphicBuffer screenshotApplicationsToBuffer(IBinder appToken, int width, int height, - boolean includeFullDisplay, float frameScale, boolean wallpaperOnly, - boolean includeDecor) { - return screenshotApplications(appToken, width, height, includeFullDisplay, frameScale, - wallpaperOnly, includeDecor, SurfaceControl::screenshotToBuffer); - } - - private <E> E screenshotApplications(IBinder appToken, int width, int height, - boolean includeFullDisplay, float frameScale, boolean wallpaperOnly, - boolean includeDecor, Screenshoter<E> screenshoter) { - int dw = mDisplayInfo.logicalWidth; - int dh = mDisplayInfo.logicalHeight; - if (dw == 0 || dh == 0) { - if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken - + ": returning null. logical widthxheight=" + dw + "x" + dh); - return null; - } - - E bitmap; - - mScreenshotApplicationState.reset(appToken == null && !wallpaperOnly); - final Rect frame = new Rect(); - final Rect stackBounds = new Rect(); - - final int aboveAppLayer = (mService.mPolicy.getWindowLayerFromTypeLw(TYPE_APPLICATION) + 1) - * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET; - final MutableBoolean mutableIncludeFullDisplay = new MutableBoolean(includeFullDisplay); - synchronized(mService.mWindowMap) { + Bitmap screenshotDisplay(Bitmap.Config config, boolean wallpaperOnly) { + synchronized (mService.mWindowMap) { if (!mService.mPolicy.isScreenOn()) { - if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Attempted to take screenshot while display" - + " was off."); - return null; - } - // Figure out the part of the screen that is actually the app. - mScreenshotApplicationState.appWin = null; - forAllWindows(w -> { - if (!w.mHasSurface) { - return false; - } - if (w.mLayer >= aboveAppLayer) { - return false; - } - if (wallpaperOnly && !w.mIsWallpaper) { - return false; - } - if (w.mIsImWindow) { - return false; - } else if (w.mIsWallpaper) { - // If this is the wallpaper layer and we're only looking for the wallpaper layer - // then the target window state is this one. - if (wallpaperOnly) { - mScreenshotApplicationState.appWin = w; - } - - if (mScreenshotApplicationState.appWin == null) { - // We have not ran across the target window yet, so it is probably behind - // the wallpaper. This can happen when the keyguard is up and all windows - // are moved behind the wallpaper. We don't want to include the wallpaper - // layer in the screenshot as it will cover-up the layer of the target - // window. - return false; - } - // Fall through. The target window is in front of the wallpaper. For this - // case we want to include the wallpaper layer in the screenshot because - // the target window might have some transparent areas. - } else if (appToken != null) { - if (w.mAppToken == null || w.mAppToken.token != appToken) { - // This app window is of no interest if it is not associated with the - // screenshot app. - return false; - } - mScreenshotApplicationState.appWin = w; - } - - // Include this window. - - final WindowStateAnimator winAnim = w.mWinAnimator; - int layer = winAnim.mSurfaceController.getLayer(); - if (mScreenshotApplicationState.maxLayer < layer) { - mScreenshotApplicationState.maxLayer = layer; - } - if (mScreenshotApplicationState.minLayer > layer) { - mScreenshotApplicationState.minLayer = layer; + if (DEBUG_SCREENSHOT) { + Slog.i(TAG_WM, "Attempted to take screenshot while display was off."); } - - // Don't include wallpaper in bounds calculation - if (!w.mIsWallpaper && !mutableIncludeFullDisplay.value) { - if (includeDecor) { - final Task task = w.getTask(); - if (task != null) { - task.getBounds(frame); - } else { - - // No task bounds? Too bad! Ain't no screenshot then. - return true; - } - } else { - final Rect wf = w.mFrame; - final Rect cr = w.mContentInsets; - int left = wf.left + cr.left; - int top = wf.top + cr.top; - int right = wf.right - cr.right; - int bottom = wf.bottom - cr.bottom; - frame.union(left, top, right, bottom); - w.getVisibleBounds(stackBounds); - if (!Rect.intersects(frame, stackBounds)) { - // Set frame empty if there's no intersection. - frame.setEmpty(); - } - } - } - - final boolean foundTargetWs = - (w.mAppToken != null && w.mAppToken.token == appToken) - || (mScreenshotApplicationState.appWin != null && wallpaperOnly); - if (foundTargetWs && winAnim.getShown() && winAnim.mLastAlpha > 0f) { - mScreenshotApplicationState.screenshotReady = true; - } - - if (w.isObscuringDisplay()){ - return true; - } - return false; - }, true /* traverseTopToBottom */); - - final WindowState appWin = mScreenshotApplicationState.appWin; - final boolean screenshotReady = mScreenshotApplicationState.screenshotReady; - final int maxLayer = mScreenshotApplicationState.maxLayer; - final int minLayer = mScreenshotApplicationState.minLayer; - - if (appToken != null && appWin == null) { - // Can't find a window to snapshot. - if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, - "Screenshot: Couldn't find a surface matching " + appToken); return null; } - if (!screenshotReady) { - Slog.i(TAG_WM, "Failed to capture screenshot of " + appToken + - " appWin=" + (appWin == null ? "null" : (appWin + " drawState=" + - appWin.mWinAnimator.mDrawState))); + if (wallpaperOnly && !shouldScreenshotWallpaper()) { return null; } - // Screenshot is ready to be taken. Everything from here below will continue - // through the bottom of the loop and return a value. We only stay in the loop - // because we don't want to release the mWindowMap lock until the screenshot is - // taken. - - if (maxLayer == 0) { - if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken - + ": returning null maxLayer=" + maxLayer); - return null; - } + int dw = mDisplayInfo.logicalWidth; + int dh = mDisplayInfo.logicalHeight; - if (!mutableIncludeFullDisplay.value) { - // Constrain frame to the screen size. - if (!frame.intersect(0, 0, dw, dh)) { - frame.setEmpty(); - } - } else { - // Caller just wants entire display. - frame.set(0, 0, dw, dh); - } - if (frame.isEmpty()) { + if (dw <= 0 || dh <= 0) { return null; } - if (width < 0) { - width = (int) (frame.width() * frameScale); - } - if (height < 0) { - height = (int) (frame.height() * frameScale); - } - - // Tell surface flinger what part of the image to crop. Take the top - // right part of the application, and crop the larger dimension to fit. - Rect crop = new Rect(frame); - if (width / (float) frame.width() < height / (float) frame.height()) { - int cropWidth = (int)((float)width / (float)height * frame.height()); - crop.right = crop.left + cropWidth; - } else { - int cropHeight = (int)((float)height / (float)width * frame.width()); - crop.bottom = crop.top + cropHeight; - } + final Rect frame = new Rect(0, 0, dw, dh); // The screenshot API does not apply the current screen rotation. int rot = mDisplay.getRotation(); @@ -3178,43 +3001,52 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo rot = (rot == ROTATION_90) ? ROTATION_270 : ROTATION_90; } - // Surfaceflinger is not aware of orientation, so convert our logical - // crop to surfaceflinger's portrait orientation. - convertCropForSurfaceFlinger(crop, rot, dw, dh); - - if (DEBUG_SCREENSHOT) { - Slog.i(TAG_WM, "Screenshot: " + dw + "x" + dh + " from " + minLayer + " to " - + maxLayer + " appToken=" + appToken); - forAllWindows(w -> { - final WindowSurfaceController controller = w.mWinAnimator.mSurfaceController; - Slog.i(TAG_WM, w + ": " + w.mLayer - + " animLayer=" + w.mWinAnimator.mAnimLayer - + " surfaceLayer=" + ((controller == null) - ? "null" : controller.getLayer())); - }, false /* traverseTopToBottom */); - } + // SurfaceFlinger is not aware of orientation, so convert our logical + // crop to SurfaceFlinger's portrait orientation. + convertCropForSurfaceFlinger(frame, rot, dw, dh); final ScreenRotationAnimation screenRotationAnimation = mService.mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY); final boolean inRotation = screenRotationAnimation != null && screenRotationAnimation.isAnimating(); - if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, - "Taking screenshot while rotating"); - - // We force pending transactions to flush before taking - // the screenshot by pushing an empty synchronous transaction. - SurfaceControl.openTransaction(); - SurfaceControl.closeTransactionSync(); + if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, "Taking screenshot while rotating"); - bitmap = screenshoter.screenshot(crop, width, height, minLayer, maxLayer, - inRotation, rot); + // TODO(b/68392460): We should screenshot Task controls directly + // but it's difficult at the moment as the Task doesn't have the + // correct size set. + final Bitmap bitmap = SurfaceControl.screenshot(frame, dw, dh, 0, 1, inRotation, rot); if (bitmap == null) { - Slog.w(TAG_WM, "Screenshot failure taking screenshot for (" + dw + "x" + dh - + ") to layer " + maxLayer); + Slog.w(TAG_WM, "Failed to take screenshot"); return null; } + + // Create a copy of the screenshot that is immutable and backed in ashmem. + // This greatly reduces the overhead of passing the bitmap between processes. + final Bitmap ret = bitmap.createAshmemBitmap(config); + bitmap.recycle(); + return ret; } - return bitmap; + } + + private boolean shouldScreenshotWallpaper() { + MutableBoolean screenshotReady = new MutableBoolean(false); + + forAllWindows(w -> { + if (!w.mIsWallpaper) { + return false; + } + + // Found the wallpaper window + final WindowStateAnimator winAnim = w.mWinAnimator; + + if (winAnim.getShown() && winAnim.mLastAlpha > 0f) { + screenshotReady.value = true; + } + + return true; + }, true /* traverseTopToBottom */); + + return screenshotReady.value; } // TODO: Can this use createRotationMatrix()? @@ -3366,6 +3198,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * I.e Activities. */ private final class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> { + /** + * A control placed at the appropriate level for transitions to occur. + */ + SurfaceControl mAnimationLayer = null; // Cached reference to some special stacks we tend to get a lot so we don't need to loop // through the list to find them. @@ -3677,13 +3513,85 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // to prevent freezing/unfreezing the display too early. return mLastOrientation; } + + @Override + void assignChildLayers(SurfaceControl.Transaction t) { + final int NORMAL_STACK_STATE = 0; + final int BOOSTED_STATE = 1; + final int ALWAYS_ON_TOP_STATE = 2; + + // We allow stacks to change visual order from the AM specified order due to + // Z-boosting during animations. However we must take care to ensure TaskStacks + // which are marked as alwaysOnTop remain that way. + int layer = 0; + for (int state = 0; state <= ALWAYS_ON_TOP_STATE; state++) { + for (int i = 0; i < mChildren.size(); i++) { + final TaskStack s = mChildren.get(i); + layer++; + if (state == NORMAL_STACK_STATE) { + s.assignLayer(t, layer); + } else if (state == BOOSTED_STATE && s.needsZBoost()) { + s.assignLayer(t, layer); + } else if (state == ALWAYS_ON_TOP_STATE && + s.isAlwaysOnTop()) { + s.assignLayer(t, layer); + } + s.assignChildLayers(t); + } + // The appropriate place for App-Transitions to occur is right + // above all other animations but still below things in the Picture-and-Picture + // windowing mode. + if (state == BOOSTED_STATE && mAnimationLayer != null) { + t.setLayer(mAnimationLayer, layer + 1); + } + } + } + + @Override + void onParentSet() { + super.onParentSet(); + if (getParent() != null) { + mAnimationLayer = makeSurface().build(); + } else { + mAnimationLayer.destroy(); + mAnimationLayer = null; + } + } + } + + private final class AboveAppWindowContainers extends NonAppWindowContainers { + AboveAppWindowContainers(String name) { + super(name); + } + + void assignChildLayers(SurfaceControl.Transaction t, WindowContainer imeContainer) { + boolean needAssignIme = imeContainer != null + && imeContainer.getSurfaceControl() != null; + for (int j = 0; j < mChildren.size(); ++j) { + final WindowToken wt = mChildren.get(j); + wt.assignLayer(t, j); + wt.assignChildLayers(t); + + int layer = mService.mPolicy.getWindowLayerFromTypeLw( + wt.windowType, wt.mOwnerCanManageAppTokens); + if (needAssignIme && layer >= TYPE_INPUT_METHOD_DIALOG) { + t.setRelativeLayer(imeContainer.getSurfaceControl(), + wt.getSurfaceControl(), -1); + needAssignIme = false; + } + } + if (needAssignIme) { + t.setRelativeLayer(imeContainer.getSurfaceControl(), + getSurfaceControl(), Integer.MIN_VALUE); + } + } } /** * Window container class that contains all containers on this display that are not related to * Apps. E.g. status bar. */ - private final class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> { + private class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> { /** * Compares two child window tokens returns -1 if the first is lesser than the second in * terms of z-order and 1 otherwise. @@ -3752,12 +3660,124 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } + SurfaceControl.Builder makeSurface(SurfaceSession s) { + return mService.makeSurfaceBuilder(s) + .setParent(mWindowingLayer); + } + + @Override + SurfaceSession getSession() { + return mSession; + } + + @Override + SurfaceControl.Builder makeChildSurface(WindowContainer child) { + SurfaceSession s = child != null ? child.getSession() : getSession(); + final SurfaceControl.Builder b = mService.makeSurfaceBuilder(s); + b.setSize(mSurfaceSize, mSurfaceSize); + + if (child == null) { + return b; + } + + return b.setName(child.getName()) + .setParent(mWindowingLayer); + } + + /** + * The makeSurface variants are for use by the window-container + * hierarchy. makeOverlay here is a function for various non windowing + * overlays like the ScreenRotation screenshot, the Strict Mode Flash + * and other potpourii. + */ + SurfaceControl.Builder makeOverlay() { + return mService.makeSurfaceBuilder(mSession) + .setParent(mOverlayLayer); + } + + void applyMagnificationSpec(MagnificationSpec spec) { + applyMagnificationSpec(getPendingTransaction(), spec); + getPendingTransaction().apply(); + } + + @Override + void onParentSet() { + // Since we are the top of the SurfaceControl hierarchy here + // we create the root surfaces explicitly rather than chaining + // up as the default implementation in onParentSet does. So we + // explicitly do NOT call super here. + } + + @Override + void assignChildLayers(SurfaceControl.Transaction t) { + t.setLayer(mOverlayLayer, 1) + .setLayer(mWindowingLayer, 0); + + // These are layers as children of "mWindowingLayer" + mBelowAppWindowsContainers.assignLayer(t, 0); + mTaskStackContainers.assignLayer(t, 1); + mAboveAppWindowsContainers.assignLayer(t, 2); + + WindowState imeTarget = mService.mInputMethodTarget; + boolean needAssignIme = true; + + // In the case where we have an IME target that is not in split-screen + // mode IME assignment is easy. We just need the IME to go directly above + // the target. This way children of the target will naturally go above the IME + // and everyone is happy. + // + // In the case of split-screen windowing mode, we need to elevate the IME above the + // docked divider while keeping the app itself below the docked divider, so instead + // we use relative layering of the IME targets child windows, and place the + // IME in the non-app layer (see {@link AboveAppWindowContainers#assignChildLayers}). + // + // In the case where we have no IME target we assign it where it's base layer would + // place it in the AboveAppWindowContainers. + if (imeTarget != null && !imeTarget.inSplitScreenWindowingMode() + && (imeTarget.getSurfaceControl() != null)) { + t.setRelativeLayer(mImeWindowsContainers.getSurfaceControl(), + imeTarget.getSurfaceControl(), + // TODO: We need to use an extra level on the app surface to ensure + // this is always above SurfaceView but always below attached window. + 1); + needAssignIme = false; + } + + // Above we have assigned layers to our children, now we ask them to assign + // layers to their children. + mBelowAppWindowsContainers.assignChildLayers(t); + mTaskStackContainers.assignChildLayers(t); + mAboveAppWindowsContainers.assignChildLayers(t, + needAssignIme == true ? mImeWindowsContainers : null); + mImeWindowsContainers.assignChildLayers(t); + } + + /** + * Here we satisfy an unfortunate special case of the IME in split-screen mode. Imagine + * that the IME target is one of the docked applications. We'd like the docked divider to be + * above both of the applications, and we'd like the IME to be above the docked divider. + * However we need child windows of the applications to be above the IME (Text drag handles). + * This is a non-strictly hierarcical layering and we need to break out of the Z ordering + * somehow. We do this by relatively ordering children of the target to the IME in cooperation + * with {@link #WindowState#assignLayer} + */ + void assignRelativeLayerForImeTargetChild(SurfaceControl.Transaction t, WindowContainer child) { + t.setRelativeLayer(child.getSurfaceControl(), mImeWindowsContainers.getSurfaceControl(), 1); + } + + @Override + void destroyAfterPendingTransaction(SurfaceControl surface) { + mPendingDestroyingSurfaces.add(surface); + } + /** - * Interface to screenshot into various types, i.e. {@link Bitmap} and {@link GraphicBuffer}. + * Destroys any surfaces that have been put into the pending list with + * {@link #destroyAfterTransaction}. */ - @FunctionalInterface - private interface Screenshoter<E> { - E screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer, - boolean useIdentityTransform, int rotation); + void onPendingTransactionApplied() { + for (int i = mPendingDestroyingSurfaces.size() - 1; i >= 0; i--) { + mPendingDestroyingSurfaces.get(i).destroy(); + } + mPendingDestroyingSurfaces.clear(); } } diff --git a/android/view/DisplayFrames.java b/com/android/server/wm/DisplayFrames.java index e6861d83..0249713e 100644 --- a/android/view/DisplayFrames.java +++ b/com/android/server/wm/DisplayFrames.java @@ -11,10 +11,10 @@ * 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 + * limitations under the License. */ -package android.view; +package com.android.server.wm; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; @@ -23,6 +23,7 @@ import static com.android.server.wm.proto.DisplayFramesProto.STABLE_BOUNDS; import android.graphics.Rect; import android.util.proto.ProtoOutputStream; +import android.view.DisplayInfo; import java.io.PrintWriter; diff --git a/com/android/server/wm/DockedStackDividerController.java b/com/android/server/wm/DockedStackDividerController.java index d79ba897..a37598e7 100644 --- a/com/android/server/wm/DockedStackDividerController.java +++ b/com/android/server/wm/DockedStackDividerController.java @@ -54,7 +54,6 @@ import android.view.inputmethod.InputMethodManagerInternal; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.internal.policy.DockedDividerUtils; import com.android.server.LocalServices; -import com.android.server.wm.DimLayer.DimLayerUser; import com.android.server.wm.WindowManagerService.H; import java.io.PrintWriter; @@ -62,7 +61,7 @@ import java.io.PrintWriter; /** * Keeps information about the docked stack divider. */ -public class DockedStackDividerController implements DimLayerUser { +public class DockedStackDividerController { private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM; @@ -114,7 +113,6 @@ public class DockedStackDividerController implements DimLayerUser { private boolean mLastVisibility = false; private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners = new RemoteCallbackList<>(); - private final DimLayer mDimLayer; private boolean mMinimizedDock; private int mOriginalDockedSide = DOCKED_INVALID; @@ -141,13 +139,12 @@ public class DockedStackDividerController implements DimLayerUser { private boolean mImeHideRequested; private final Rect mLastDimLayerRect = new Rect(); private float mLastDimLayerAlpha; + private TaskStack mDimmedStack; DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) { mService = service; mDisplayContent = displayContent; final Context context = service.mContext; - mDimLayer = new DimLayer(displayContent.mService, this, displayContent.getDisplayId(), - "DockedStackDim"); mMinimizedDockInterpolator = AnimationUtils.loadInterpolator( context, android.R.interpolator.fast_out_slow_in); loadDimens(); @@ -463,6 +460,11 @@ public class DockedStackDividerController implements DimLayerUser { } mOriginalDockedSide = DOCKED_INVALID; setMinimizedDockedStack(false /* minimizedDock */, false /* animate */); + + if (mDimmedStack != null) { + mDimmedStack.stopDimming(); + mDimmedStack = null; + } } /** @@ -564,34 +566,12 @@ public class DockedStackDividerController implements DimLayerUser { final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStack(); boolean visibleAndValid = visible && stack != null && dockedStack != null; if (visibleAndValid) { - stack.getDimBounds(mTmpRect); - if (mTmpRect.height() > 0 && mTmpRect.width() > 0) { - if (!mLastDimLayerRect.equals(mTmpRect) || mLastDimLayerAlpha != alpha) { - try { - // TODO: This should use the regular animation transaction - here and below - mService.openSurfaceTransaction(); - mDimLayer.setBounds(mTmpRect); - mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */); - } finally { - mService.closeSurfaceTransaction("setResizeDimLayer"); - } - } - mLastDimLayerRect.set(mTmpRect); - mLastDimLayerAlpha = alpha; - } else { - visibleAndValid = false; - } + mDimmedStack = stack; + stack.dim(alpha); } - if (!visibleAndValid) { - if (mLastDimLayerAlpha != 0f) { - try { - mService.openSurfaceTransaction(); - mDimLayer.hide(); - } finally { - mService.closeSurfaceTransaction("setResizeDimLayer"); - } - } - mLastDimLayerAlpha = 0f; + if (!visibleAndValid && stack != null) { + mDimmedStack = null; + stack.stopDimming(); } } @@ -675,8 +655,8 @@ public class DockedStackDividerController implements DimLayerUser { } private boolean isWithinDisplay(Task task) { - task.mStack.getBounds(mTmpRect); - mDisplayContent.getLogicalDisplayRect(mTmpRect2); + task.getBounds(mTmpRect); + mDisplayContent.getBounds(mTmpRect2); return mTmpRect.intersect(mTmpRect2); } @@ -829,12 +809,8 @@ public class DockedStackDividerController implements DimLayerUser { return animateForMinimizedDockedStack(now); } else if (mAnimatingForIme) { return animateForIme(now); - } else { - if (mDimLayer != null && mDimLayer.isDimming()) { - mDimLayer.setLayer(getResizeDimLayer()); - } - return false; } + return false; } private boolean animateForIme(long now) { @@ -942,27 +918,6 @@ public class DockedStackDividerController implements DimLayerUser { + (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST); } - @Override - public boolean dimFullscreen() { - return false; - } - - @Override - public DisplayInfo getDisplayInfo() { - return mDisplayContent.getDisplayInfo(); - } - - @Override - public boolean isAttachedToDisplay() { - return mDisplayContent != null; - } - - @Override - public void getDimBounds(Rect outBounds) { - // This dim layer user doesn't need this. - } - - @Override public String toShortString() { return TAG; } @@ -977,10 +932,6 @@ public class DockedStackDividerController implements DimLayerUser { pw.println(prefix + " mMinimizedDock=" + mMinimizedDock); pw.println(prefix + " mAdjustedForIme=" + mAdjustedForIme); pw.println(prefix + " mAdjustedForDivider=" + mAdjustedForDivider); - if (mDimLayer.isDimming()) { - pw.println(prefix + " Dim layer is dimming: "); - mDimLayer.printTo(prefix + " ", pw); - } } void writeToProto(ProtoOutputStream proto, long fieldId) { diff --git a/com/android/server/wm/DragDropController.java b/com/android/server/wm/DragDropController.java index 4567e101..65951dc6 100644 --- a/com/android/server/wm/DragDropController.java +++ b/com/android/server/wm/DragDropController.java @@ -36,9 +36,10 @@ import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.View; -import android.view.WindowManagerInternal.IDragDropCallback; + import com.android.internal.util.Preconditions; import com.android.server.input.InputWindowHandle; +import com.android.server.wm.WindowManagerInternal.IDragDropCallback; /** * Managing drag and drop operations initiated by View#startDragAndDrop. diff --git a/com/android/server/wm/DragState.java b/com/android/server/wm/DragState.java index e81d366b..112e62f2 100644 --- a/com/android/server/wm/DragState.java +++ b/com/android/server/wm/DragState.java @@ -645,15 +645,15 @@ class DragState { try (final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { transaction.setPosition( mSurfaceControl, - (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_X), - (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_Y)); + (float) animation.getAnimatedValue(ANIMATED_PROPERTY_X), + (float) animation.getAnimatedValue(ANIMATED_PROPERTY_Y)); transaction.setAlpha( mSurfaceControl, - (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_ALPHA)); + (float) animation.getAnimatedValue(ANIMATED_PROPERTY_ALPHA)); transaction.setMatrix( mSurfaceControl, - (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0, - 0, (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE)); + (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0, + 0, (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE)); transaction.apply(); } } diff --git a/com/android/server/wm/EmulatorDisplayOverlay.java b/com/android/server/wm/EmulatorDisplayOverlay.java index 8bec8d75..fddf6ca2 100644 --- a/com/android/server/wm/EmulatorDisplayOverlay.java +++ b/com/android/server/wm/EmulatorDisplayOverlay.java @@ -49,19 +49,19 @@ class EmulatorDisplayOverlay { private int mRotation; private boolean mVisible; - public EmulatorDisplayOverlay(Context context, Display display, SurfaceSession session, + public EmulatorDisplayOverlay(Context context, DisplayContent dc, int zOrder) { + final Display display = dc.getDisplay(); mScreenSize = new Point(); display.getSize(mScreenSize); SurfaceControl ctrl = null; try { - ctrl = new SurfaceControl.Builder(session) + ctrl = dc.makeOverlay() .setName("EmulatorDisplayOverlay") .setSize(mScreenSize.x, mScreenSize.y) .setFormat(PixelFormat.TRANSLUCENT) .build(); - ctrl.setLayerStack(display.getLayerStack()); ctrl.setLayer(zOrder); ctrl.setPosition(0, 0); ctrl.show(); diff --git a/com/android/server/wm/InputMonitor.java b/com/android/server/wm/InputMonitor.java index a766097c..7e29a3aa 100644 --- a/com/android/server/wm/InputMonitor.java +++ b/com/android/server/wm/InputMonitor.java @@ -47,11 +47,11 @@ import android.view.InputChannel; import android.view.InputEventReceiver; import android.view.KeyEvent; import android.view.WindowManager; -import android.view.WindowManagerPolicy; import com.android.server.input.InputApplicationHandle; import com.android.server.input.InputManagerService; import com.android.server.input.InputWindowHandle; +import com.android.server.policy.WindowManagerPolicy; import java.io.PrintWriter; import java.util.Arrays; @@ -661,8 +661,8 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { if (w.inPinnedWindowingMode()) { if (mAddPipInputConsumerHandle && (inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer)) { - // Update the bounds of the Pip input consumer to match the Pinned stack - w.getStack().getBounds(pipTouchableBounds); + // Update the bounds of the Pip input consumer to match the window bounds. + w.getBounds(pipTouchableBounds); pipInputConsumer.mWindowHandle.touchableRegion.set(pipTouchableBounds); addInputWindowHandle(pipInputConsumer.mWindowHandle); mAddPipInputConsumerHandle = false; diff --git a/com/android/server/wm/KeyguardDisableHandler.java b/com/android/server/wm/KeyguardDisableHandler.java index 2eb186b5..4a20f1a0 100644 --- a/com/android/server/wm/KeyguardDisableHandler.java +++ b/com/android/server/wm/KeyguardDisableHandler.java @@ -29,7 +29,8 @@ import android.os.RemoteException; import android.os.TokenWatcher; import android.util.Log; import android.util.Pair; -import android.view.WindowManagerPolicy; + +import com.android.server.policy.WindowManagerPolicy; public class KeyguardDisableHandler extends Handler { private static final String TAG = TAG_WITH_CLASS_NAME ? "KeyguardDisableHandler" : TAG_WM; diff --git a/com/android/server/wm/PinnedStackWindowController.java b/com/android/server/wm/PinnedStackWindowController.java index 41f076d7..b021a722 100644 --- a/com/android/server/wm/PinnedStackWindowController.java +++ b/com/android/server/wm/PinnedStackWindowController.java @@ -106,7 +106,7 @@ public class PinnedStackWindowController extends StackWindowController { } else { // Otherwise, use the display bounds toBounds = new Rect(); - mContainer.getDisplayContent().getLogicalDisplayRect(toBounds); + mContainer.getDisplayContent().getBounds(toBounds); } } else if (fromFullscreen) { schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END; diff --git a/com/android/server/wm/PointerEventDispatcher.java b/com/android/server/wm/PointerEventDispatcher.java index 484987ec..ab8b8d47 100644 --- a/com/android/server/wm/PointerEventDispatcher.java +++ b/com/android/server/wm/PointerEventDispatcher.java @@ -21,7 +21,7 @@ import android.view.InputDevice; import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.MotionEvent; -import android.view.WindowManagerPolicy.PointerEventListener; +import android.view.WindowManagerPolicyConstants.PointerEventListener; import com.android.server.UiThread; diff --git a/com/android/server/wm/RootWindowContainer.java b/com/android/server/wm/RootWindowContainer.java index f541926e..4008811b 100644 --- a/com/android/server/wm/RootWindowContainer.java +++ b/com/android/server/wm/RootWindowContainer.java @@ -59,10 +59,10 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE; import static android.view.WindowManager.LayoutParams.TYPE_DREAM; import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEEP_SCREEN_ON; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; @@ -138,7 +138,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { ParcelFileDescriptor mSurfaceTraceFd; RemoteEventTrace mRemoteEventTrace; - private final WindowLayersController mLayersController; final WallpaperController mWallpaperController; private final Handler mHandler; @@ -163,7 +162,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { RootWindowContainer(WindowManagerService service) { mService = service; mHandler = new MyHandler(service.mH.getLooper()); - mLayersController = new WindowLayersController(mService); mWallpaperController = new WallpaperController(mService); } @@ -231,7 +229,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { } private DisplayContent createDisplayContent(final Display display) { - final DisplayContent dc = new DisplayContent(display, mService, mLayersController, + final DisplayContent dc = new DisplayContent(display, mService, mWallpaperController); final int displayId = display.getDisplayId(); @@ -1103,4 +1101,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { String getName() { return "ROOT"; } + + @Override + void scheduleAnimation() { + mService.scheduleAnimationLocked(); + } } diff --git a/com/android/server/wm/ScreenRotationAnimation.java b/com/android/server/wm/ScreenRotationAnimation.java index 3350feae..5a39de5c 100644 --- a/com/android/server/wm/ScreenRotationAnimation.java +++ b/com/android/server/wm/ScreenRotationAnimation.java @@ -28,7 +28,6 @@ import static com.android.server.wm.proto.ScreenRotationAnimationProto.STARTED; import android.content.Context; import android.graphics.Matrix; -import android.graphics.PixelFormat; import android.graphics.Rect; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -225,12 +224,12 @@ class ScreenRotationAnimation { } public ScreenRotationAnimation(Context context, DisplayContent displayContent, - SurfaceSession session, boolean inTransaction, boolean forceDefaultOrientation, + boolean inTransaction, boolean forceDefaultOrientation, boolean isSecure, WindowManagerService service) { mService = service; mContext = context; mDisplayContent = displayContent; - displayContent.getLogicalDisplayRect(mOriginalDisplayRect); + displayContent.getBounds(mOriginalDisplayRect); // Screenshot does NOT include rotation! final Display display = displayContent.getDisplay(); @@ -269,7 +268,7 @@ class ScreenRotationAnimation { try { try { - mSurfaceControl = new SurfaceControl.Builder(session) + mSurfaceControl = displayContent.makeOverlay() .setName("ScreenshotSurface") .setSize(mWidth, mHeight) .setSecure(isSecure) @@ -281,7 +280,6 @@ class ScreenRotationAnimation { // TODO(multidisplay): we should use the proper display SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay( SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur); - mSurfaceControl.setLayerStack(display.getLayerStack()); mSurfaceControl.setLayer(SCREEN_FREEZE_LAYER_SCREENSHOT); mSurfaceControl.setAlpha(0); mSurfaceControl.show(); @@ -313,7 +311,7 @@ class ScreenRotationAnimation { float x = mTmpFloats[Matrix.MTRANS_X]; float y = mTmpFloats[Matrix.MTRANS_Y]; if (mForceDefaultOrientation) { - mDisplayContent.getLogicalDisplayRect(mCurrentDisplayRect); + mDisplayContent.getBounds(mCurrentDisplayRect); x -= mCurrentDisplayRect.left; y -= mCurrentDisplayRect.top; } @@ -370,11 +368,11 @@ class ScreenRotationAnimation { } // Must be called while in a transaction. - public boolean setRotationInTransaction(int rotation, SurfaceSession session, + public boolean setRotationInTransaction(int rotation, long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight) { setRotationInTransaction(rotation); if (TWO_PHASE_ANIMATION) { - return startAnimation(session, maxAnimationDuration, animationScale, + return startAnimation(maxAnimationDuration, animationScale, finalWidth, finalHeight, false, 0, 0); } @@ -385,7 +383,7 @@ class ScreenRotationAnimation { /** * Returns true if animating. */ - private boolean startAnimation(SurfaceSession session, long maxAnimationDuration, + private boolean startAnimation(long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight, boolean dismissing, int exitAnim, int enterAnim) { if (mSurfaceControl == null) { @@ -561,8 +559,8 @@ class ScreenRotationAnimation { Rect outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1, mOriginalWidth*2, mOriginalHeight*2); Rect inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight); - mCustomBlackFrame = new BlackFrame(session, outer, inner, - SCREEN_FREEZE_LAYER_CUSTOM, layerStack, false); + mCustomBlackFrame = new BlackFrame(outer, inner, + SCREEN_FREEZE_LAYER_CUSTOM, mDisplayContent, false); mCustomBlackFrame.setMatrix(mFrameInitialMatrix); } catch (OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate black surface", e); @@ -601,8 +599,8 @@ class ScreenRotationAnimation { mOriginalWidth*2, mOriginalHeight*2); inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight); } - mExitingBlackFrame = new BlackFrame(session, outer, inner, - SCREEN_FREEZE_LAYER_EXIT, layerStack, mForceDefaultOrientation); + mExitingBlackFrame = new BlackFrame(outer, inner, + SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation); mExitingBlackFrame.setMatrix(mFrameInitialMatrix); } catch (OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate black surface", e); @@ -624,8 +622,8 @@ class ScreenRotationAnimation { Rect outer = new Rect(-finalWidth*1, -finalHeight*1, finalWidth*2, finalHeight*2); Rect inner = new Rect(0, 0, finalWidth, finalHeight); - mEnteringBlackFrame = new BlackFrame(session, outer, inner, - SCREEN_FREEZE_LAYER_ENTER, layerStack, false); + mEnteringBlackFrame = new BlackFrame(outer, inner, + SCREEN_FREEZE_LAYER_ENTER, mDisplayContent, false); } catch (OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate black surface", e); } finally { @@ -642,7 +640,7 @@ class ScreenRotationAnimation { /** * Returns true if animating. */ - public boolean dismiss(SurfaceSession session, long maxAnimationDuration, + public boolean dismiss(long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) { if (DEBUG_STATE) Slog.v(TAG, "Dismiss!"); if (mSurfaceControl == null) { @@ -650,7 +648,7 @@ class ScreenRotationAnimation { return false; } if (!mStarted) { - startAnimation(session, maxAnimationDuration, animationScale, finalWidth, finalHeight, + startAnimation(maxAnimationDuration, animationScale, finalWidth, finalHeight, true, exitAnim, enterAnim); } if (!mStarted) { diff --git a/com/android/server/wm/SnapshotStartingData.java b/com/android/server/wm/SnapshotStartingData.java index 35f35db5..c9e43c52 100644 --- a/com/android/server/wm/SnapshotStartingData.java +++ b/com/android/server/wm/SnapshotStartingData.java @@ -17,8 +17,8 @@ package com.android.server.wm; import android.app.ActivityManager.TaskSnapshot; -import android.graphics.GraphicBuffer; -import android.view.WindowManagerPolicy.StartingSurface; + +import com.android.server.policy.WindowManagerPolicy.StartingSurface; /** * Represents starting data for snapshot starting windows. diff --git a/com/android/server/wm/SplashScreenStartingData.java b/com/android/server/wm/SplashScreenStartingData.java index 4b14f867..f52ce389 100644 --- a/com/android/server/wm/SplashScreenStartingData.java +++ b/com/android/server/wm/SplashScreenStartingData.java @@ -18,7 +18,8 @@ package com.android.server.wm; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; -import android.view.WindowManagerPolicy.StartingSurface; + +import com.android.server.policy.WindowManagerPolicy.StartingSurface; /** * Represents starting data for splash screens, i.e. "traditional" starting windows. diff --git a/com/android/server/wm/StackWindowController.java b/com/android/server/wm/StackWindowController.java index 95c1d536..e7547bf0 100644 --- a/com/android/server/wm/StackWindowController.java +++ b/com/android/server/wm/StackWindowController.java @@ -87,12 +87,6 @@ public class StackWindowController } } - public boolean isVisible() { - synchronized (mWindowMap) { - return mContainer != null && mContainer.isVisible(); - } - } - public void reparent(int displayId, Rect outStackBounds, boolean onTop) { synchronized (mWindowMap) { if (mContainer == null) { @@ -111,8 +105,7 @@ public class StackWindowController } } - public void positionChildAt(TaskWindowContainerController child, int position, Rect bounds, - Configuration overrideConfig) { + public void positionChildAt(TaskWindowContainerController child, int position) { synchronized (mWindowMap) { if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child + " at " + position); @@ -126,7 +119,7 @@ public class StackWindowController "positionChildAt: could not find stack for task=" + mContainer); return; } - child.mContainer.positionAt(position, bounds, overrideConfig); + child.mContainer.positionAt(position); mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); } } @@ -178,24 +171,22 @@ public class StackWindowController * Re-sizes a stack and its containing tasks. * * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen. - * @param configs Configurations for tasks in the resized stack, keyed by task id. * @param taskBounds Bounds for tasks in the resized stack, keyed by task id. - * @return True if the stack is now fullscreen. + * @param taskTempInsetBounds Inset bounds for individual tasks, keyed by task id. */ - public boolean resize(Rect bounds, SparseArray<Configuration> configs, - SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) { + public void resize(Rect bounds, SparseArray<Rect> taskBounds, + SparseArray<Rect> taskTempInsetBounds) { synchronized (mWindowMap) { if (mContainer == null) { throw new IllegalArgumentException("resizeStack: stack " + this + " not found."); } // We might trigger a configuration change. Save the current task bounds for freezing. mContainer.prepareFreezingTaskBounds(); - if (mContainer.setBounds(bounds, configs, taskBounds, taskTempInsetBounds) + if (mContainer.setBounds(bounds, taskBounds, taskTempInsetBounds) && mContainer.isVisible()) { mContainer.getDisplayContent().setLayoutNeeded(); mService.mWindowPlacerLocked.performSurfacePlacement(); } - return mContainer.getRawFullscreen(); } } @@ -227,7 +218,7 @@ public class StackWindowController public void getRawBounds(Rect outBounds) { synchronized (mWindowMap) { - if (mContainer.getRawFullscreen()) { + if (mContainer.matchParentBounds()) { outBounds.setEmpty(); } else { mContainer.getRawBounds(outBounds); @@ -275,6 +266,7 @@ public class StackWindowController final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds(); + config.windowConfiguration.setBounds(bounds); config.windowConfiguration.setAppBounds(!bounds.isEmpty() ? bounds : null); boolean intersectParentBounds = false; diff --git a/com/android/server/wm/StartingData.java b/com/android/server/wm/StartingData.java index 8c564bb5..eb5011f7 100644 --- a/com/android/server/wm/StartingData.java +++ b/com/android/server/wm/StartingData.java @@ -16,7 +16,7 @@ package com.android.server.wm; -import android.view.WindowManagerPolicy.StartingSurface; +import com.android.server.policy.WindowManagerPolicy.StartingSurface; /** * Represents the model about how a starting window should be constructed. diff --git a/com/android/server/wm/StrictModeFlash.java b/com/android/server/wm/StrictModeFlash.java index eb8ee696..f51a6a92 100644 --- a/com/android/server/wm/StrictModeFlash.java +++ b/com/android/server/wm/StrictModeFlash.java @@ -41,15 +41,14 @@ class StrictModeFlash { private boolean mDrawNeeded; private final int mThickness = 20; - public StrictModeFlash(Display display, SurfaceSession session) { + public StrictModeFlash(DisplayContent dc) { SurfaceControl ctrl = null; try { - ctrl = new SurfaceControl.Builder(session) + ctrl = dc.makeOverlay() .setName("StrictModeFlash") .setSize(1, 1) .setFormat(PixelFormat.TRANSLUCENT) .build(); - ctrl.setLayerStack(display.getLayerStack()); ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); // one more than Watermark? arbitrary. ctrl.setPosition(0, 0); ctrl.show(); diff --git a/com/android/server/wm/SurfaceBuilderFactory.java b/com/android/server/wm/SurfaceBuilderFactory.java new file mode 100644 index 00000000..5390e5a1 --- /dev/null +++ b/com/android/server/wm/SurfaceBuilderFactory.java @@ -0,0 +1,25 @@ +/* + * 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 com.android.server.wm; + +import android.view.SurfaceSession; +import android.view.SurfaceControl; + +interface SurfaceBuilderFactory { + SurfaceControl.Builder make(SurfaceSession s); +}; + diff --git a/com/android/server/wm/SurfaceControlWithBackground.java b/com/android/server/wm/SurfaceControlWithBackground.java index a5080d57..7c5bd43a 100644 --- a/com/android/server/wm/SurfaceControlWithBackground.java +++ b/com/android/server/wm/SurfaceControlWithBackground.java @@ -16,7 +16,13 @@ package com.android.server.wm; -import android.graphics.PixelFormat; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; + +import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM; +import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT; +import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT; + import android.graphics.Rect; import android.graphics.Region; import android.os.IBinder; @@ -24,13 +30,6 @@ import android.os.Parcel; import android.view.Surface; import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl; -import android.view.SurfaceSession; - -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManagerPolicy.NAV_BAR_BOTTOM; -import static android.view.WindowManagerPolicy.NAV_BAR_LEFT; -import static android.view.WindowManagerPolicy.NAV_BAR_RIGHT; /** * SurfaceControl extension that has black background behind navigation bar area for fullscreen diff --git a/com/android/server/wm/Task.java b/com/android/server/wm/Task.java index 13435d76..8aa129a4 100644 --- a/com/android/server/wm/Task.java +++ b/com/android/server/wm/Task.java @@ -51,14 +51,8 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; import java.util.function.Consumer; -class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerUser { +class Task extends WindowContainer<AppWindowToken> { static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM; - // Return value from {@link setBounds} indicating no change was made to the Task bounds. - private static final int BOUNDS_CHANGE_NONE = 0; - // Return value from {@link setBounds} indicating the position of the Task bounds changed. - private static final int BOUNDS_CHANGE_POSITION = 1; - // Return value from {@link setBounds} indicating the size of the Task bounds changed. - private static final int BOUNDS_CHANGE_SIZE = 1 << 1; // TODO: Track parent marks like this in WindowContainer. TaskStack mStack; @@ -67,8 +61,6 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU private boolean mDeferRemoval = false; final WindowManagerService mService; - // Content limits relative to the DisplayContent this sits in. - private Rect mBounds = new Rect(); final Rect mPreparedFrozenBounds = new Rect(); final Configuration mPreparedFrozenMergedConfig = new Configuration(); @@ -78,13 +70,12 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU // Device rotation as of the last time {@link #mBounds} was set. private int mRotation; - // Whether mBounds is fullscreen - private boolean mFillsParent = true; - // For comparison with DisplayContent bounds. private Rect mTmpRect = new Rect(); // For handling display rotations. private Rect mTmpRect2 = new Rect(); + // For retrieving dim bounds + private Rect mTmpRect3 = new Rect(); // Resize mode of the task. See {@link ActivityInfo#resizeMode} private int mResizeMode; @@ -105,8 +96,11 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU // stack moves and we in fact do so when moving from full screen to pinned. private boolean mPreserveNonFloatingState = false; - Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds, - int resizeMode, boolean supportsPictureInPicture, TaskDescription taskDescription, + private Dimmer mDimmer = new Dimmer(this); + private final Rect mTmpDimBoundsRect = new Rect(); + + Task(int taskId, TaskStack stack, int userId, WindowManagerService service, int resizeMode, + boolean supportsPictureInPicture, TaskDescription taskDescription, TaskWindowContainerController controller) { mTaskId = taskId; mStack = stack; @@ -115,7 +109,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU mResizeMode = resizeMode; mSupportsPictureInPicture = supportsPictureInPicture; setController(controller); - setBounds(bounds, getOverrideConfiguration()); + setBounds(getOverrideBounds()); mTaskDescription = taskDescription; // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED). @@ -188,12 +182,6 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "removeTask"); mDeferRemoval = false; - // Make sure to remove dim layer user first before removing task its from parent. - DisplayContent content = getDisplayContent(); - if (content != null) { - content.mDimLayerController.removeDimLayerUser(this); - } - super.removeImmediately(); } @@ -230,13 +218,14 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU } /** @see com.android.server.am.ActivityManagerService#positionTaskInStack(int, int, int). */ - void positionAt(int position, Rect bounds, Configuration overrideConfig) { + void positionAt(int position) { mStack.positionChildAt(position, this, false /* includingParents */); - resizeLocked(bounds, overrideConfig, false /* force */); } @Override void onParentSet() { + super.onParentSet(); + // Update task bounds if needed. updateDisplayInfo(getDisplayContent()); @@ -272,50 +261,37 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU } } - /** Set the task bounds. Passing in null sets the bounds to fullscreen. */ - // TODO: There is probably not a need to pass in overrideConfig anymore since any change to it - // will be automatically propagated from the AM. Also, mBound is going to be in - // WindowConfiguration long term. - private int setBounds(Rect bounds, Configuration overrideConfig) { - if (overrideConfig == null) { - overrideConfig = EMPTY; + public int setBounds(Rect bounds, boolean forceResize) { + final int boundsChanged = setBounds(bounds); + + if (forceResize && (boundsChanged & BOUNDS_CHANGE_SIZE) != BOUNDS_CHANGE_SIZE) { + onResize(); + return BOUNDS_CHANGE_SIZE | boundsChanged; } - boolean oldFullscreen = mFillsParent; + return boundsChanged; + } + + /** Set the task bounds. Passing in null sets the bounds to fullscreen. */ + @Override + public int setBounds(Rect bounds) { int rotation = Surface.ROTATION_0; final DisplayContent displayContent = mStack.getDisplayContent(); if (displayContent != null) { - displayContent.getLogicalDisplayRect(mTmpRect); rotation = displayContent.getDisplayInfo().rotation; - mFillsParent = bounds == null; - if (mFillsParent) { - bounds = mTmpRect; - } - } - - if (bounds == null) { + } else if (bounds == null) { // Can't set to fullscreen if we don't have a display to get bounds from... return BOUNDS_CHANGE_NONE; } - if (mBounds.equals(bounds) && oldFullscreen == mFillsParent && mRotation == rotation) { - return BOUNDS_CHANGE_NONE; - } - int boundsChange = BOUNDS_CHANGE_NONE; - if (mBounds.left != bounds.left || mBounds.top != bounds.top) { - boundsChange |= BOUNDS_CHANGE_POSITION; - } - if (mBounds.width() != bounds.width() || mBounds.height() != bounds.height()) { - boundsChange |= BOUNDS_CHANGE_SIZE; + if (equivalentOverrideBounds(bounds)) { + return BOUNDS_CHANGE_NONE; } - mBounds.set(bounds); + final int boundsChange = super.setBounds(bounds); mRotation = rotation; - if (displayContent != null) { - displayContent.mDimLayerController.updateDimLayer(this); - } - onOverrideConfigurationChanged(overrideConfig); + return boundsChange; } @@ -363,28 +339,12 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU return isResizeable(); } - boolean resizeLocked(Rect bounds, Configuration overrideConfig, boolean forced) { - int boundsChanged = setBounds(bounds, overrideConfig); - if (forced) { - boundsChanged |= BOUNDS_CHANGE_SIZE; - } - if (boundsChanged == BOUNDS_CHANGE_NONE) { - return false; - } - if ((boundsChanged & BOUNDS_CHANGE_SIZE) == BOUNDS_CHANGE_SIZE) { - onResize(); - } else { - onMovedByResize(); - } - return true; - } - /** * Prepares the task bounds to be frozen with the current size. See * {@link AppWindowToken#freezeBounds}. */ void prepareFreezingBounds() { - mPreparedFrozenBounds.set(mBounds); + mPreparedFrozenBounds.set(getBounds()); mPreparedFrozenMergedConfig.setTo(getConfiguration()); } @@ -410,30 +370,30 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU mTmpRect2.offsetTo(adjustedBounds.left, adjustedBounds.top); } setTempInsetBounds(tempInsetBounds); - resizeLocked(mTmpRect2, getOverrideConfiguration(), false /* forced */); + setBounds(mTmpRect2, false /* forced */); } /** Return true if the current bound can get outputted to the rest of the system as-is. */ private boolean useCurrentBounds() { final DisplayContent displayContent = getDisplayContent(); - return mFillsParent + return matchParentBounds() || !inSplitScreenSecondaryWindowingMode() || displayContent == null || displayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null; } - /** Original bounds of the task if applicable, otherwise fullscreen rect. */ - void getBounds(Rect out) { + @Override + public void getBounds(Rect out) { if (useCurrentBounds()) { // No need to adjust the output bounds if fullscreen or the docked stack is visible // since it is already what we want to represent to the rest of the system. - out.set(mBounds); + super.getBounds(out); return; } // The bounds has been adjusted to accommodate for a docked stack, but the docked stack is // not currently visible. Go ahead a represent it as fullscreen to the rest of the system. - mStack.getDisplayContent().getLogicalDisplayRect(out); + mStack.getDisplayContent().getBounds(out); } /** @@ -482,7 +442,6 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU } /** Bounds of the task to be used for dimming, as well as touch related tests. */ - @Override public void getDimBounds(Rect out) { final DisplayContent displayContent = mStack.getDisplayContent(); // It doesn't matter if we in particular are part of the resize, since we couldn't have @@ -494,7 +453,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU return; } - if (!mFillsParent) { + if (!matchParentBounds()) { // When minimizing the docked stack when going home, we don't adjust the task bounds // so we need to intersect the task bounds with the stack bounds here. // @@ -505,11 +464,11 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU mStack.getBounds(out); } else { mStack.getBounds(mTmpRect); - mTmpRect.intersect(mBounds); + mTmpRect.intersect(getBounds()); } out.set(mTmpRect); } else { - out.set(mBounds); + out.set(getBounds()); } return; } @@ -517,7 +476,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU // The bounds has been adjusted to accommodate for a docked stack, but the docked stack is // not currently visible. Go ahead a represent it as fullscreen to the rest of the system. if (displayContent != null) { - displayContent.getLogicalDisplayRect(out); + displayContent.getBounds(out); } } @@ -545,10 +504,10 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU if (displayContent == null) { return; } - if (mFillsParent) { + if (matchParentBounds()) { // TODO: Yeah...not sure if this works with WindowConfiguration, but shouldn't be a // problem once we move mBounds into WindowConfiguration. - setBounds(null, getOverrideConfiguration()); + setBounds(null); return; } final int newRotation = displayContent.getDisplayInfo().rotation; @@ -561,18 +520,18 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU // task bounds so it stays in the same place. // - Rotate the bounds and notify activity manager if the task can be resized independently // from its stack. The stack will take care of task rotation for the other case. - mTmpRect2.set(mBounds); + mTmpRect2.set(getBounds()); if (!getWindowConfiguration().canResizeTask()) { - setBounds(mTmpRect2, getOverrideConfiguration()); + setBounds(mTmpRect2); return; } displayContent.rotateBounds(mRotation, newRotation, mTmpRect2); - if (setBounds(mTmpRect2, getOverrideConfiguration()) != BOUNDS_CHANGE_NONE) { + if (setBounds(mTmpRect2) != BOUNDS_CHANGE_NONE) { final TaskWindowContainerController controller = getController(); if (controller != null) { - controller.requestResize(mBounds, RESIZE_MODE_SYSTEM_SCREEN_ROTATION); + controller.requestResize(getBounds(), RESIZE_MODE_SYSTEM_SCREEN_ROTATION); } } } @@ -634,26 +593,9 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU return null; } - @Override - public boolean dimFullscreen() { - return isFullscreen(); - } - - @Override - public int getLayerForDim(WindowStateAnimator animator, int layerOffset, int defaultLayer) { - // If the dim layer is for a starting window, move the dim layer back in the z-order behind - // the lowest activity window to ensure it does not occlude the main window if it is - // translucent - final AppWindowToken appToken = animator.mWin.mAppToken; - if (animator.mAttrType == TYPE_APPLICATION_STARTING && hasChild(appToken) ) { - return Math.min(defaultLayer, appToken.getLowestAnimLayer() - layerOffset); - } - return defaultLayer; - } - boolean isFullscreen() { if (useCurrentBounds()) { - return mFillsParent; + return matchParentBounds(); } // The bounds has been adjusted to accommodate for a docked stack, but the docked stack // is not currently visible. Go ahead a represent it as fullscreen to the rest of the @@ -661,16 +603,6 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU return true; } - @Override - public DisplayInfo getDisplayInfo() { - return getDisplayContent().getDisplayInfo(); - } - - @Override - public boolean isAttachedToDisplay() { - return getDisplayContent() != null; - } - void forceWindowsScaleable(boolean force) { mService.openSurfaceTransaction(); try { @@ -692,7 +624,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU @Override boolean fillsParent() { - return mFillsParent || !getWindowConfiguration().canResizeTask(); + return matchParentBounds() || !getWindowConfiguration().canResizeTask(); } @Override @@ -718,9 +650,18 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU mPreserveNonFloatingState = false; } + Dimmer getDimmer() { + return mDimmer; + } + @Override - public String toShortString() { - return "Task=" + mTaskId; + void prepareSurfaces() { + mDimmer.resetDimStates(); + super.prepareSurfaces(); + getDimBounds(mTmpDimBoundsRect); + if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) { + scheduleAnimation(); + } } @CallSuper @@ -733,8 +674,8 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU final AppWindowToken appWindowToken = mChildren.get(i); appWindowToken.writeToProto(proto, APP_WINDOW_TOKENS, trim); } - proto.write(FILLS_PARENT, mFillsParent); - mBounds.writeToProto(proto, BOUNDS); + proto.write(FILLS_PARENT, matchParentBounds()); + getBounds().writeToProto(proto, BOUNDS); mTempInsetBounds.writeToProto(proto, TEMP_INSET_BOUNDS); proto.end(token); } @@ -743,8 +684,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU final String doublePrefix = prefix + " "; pw.println(prefix + "taskId=" + mTaskId); - pw.println(doublePrefix + "mFillsParent=" + mFillsParent); - pw.println(doublePrefix + "mBounds=" + mBounds.toShortString()); + pw.println(doublePrefix + "mBounds=" + getBounds().toShortString()); pw.println(doublePrefix + "mdr=" + mDeferRemoval); pw.println(doublePrefix + "appTokens=" + mChildren); pw.println(doublePrefix + "mTempInsetBounds=" + mTempInsetBounds.toShortString()); @@ -757,4 +697,8 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU wtoken.dump(pw, triplePrefix); } } + + String toShortString() { + return "Task=" + mTaskId; + } } diff --git a/com/android/server/wm/TaskPositioner.java b/com/android/server/wm/TaskPositioner.java index 12f6b5a2..87d0a401 100644 --- a/com/android/server/wm/TaskPositioner.java +++ b/com/android/server/wm/TaskPositioner.java @@ -59,7 +59,7 @@ import com.android.server.wm.WindowManagerService.H; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -class TaskPositioner implements DimLayer.DimLayerUser { +class TaskPositioner { private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false; private static final String TAG_LOCAL = "TaskPositioner"; private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM; @@ -99,9 +99,6 @@ class TaskPositioner implements DimLayer.DimLayerUser { private WindowPositionerEventReceiver mInputEventReceiver; private Display mDisplay; private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); - private DimLayer mDimLayer; - @CtrlType - private int mCurrentDimSide; private Rect mTmpRect = new Rect(); private int mSideMargin; private int mMinVisibleWidth; @@ -207,15 +204,6 @@ class TaskPositioner implements DimLayer.DimLayerUser { mService.mActivityManager.resizeTask( mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED); } - - if (mCurrentDimSide != CTRL_NONE) { - final int createMode = mCurrentDimSide == CTRL_LEFT - ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT - : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT; - mService.mActivityManager.setTaskWindowingModeSplitScreenPrimary( - mTask.mTaskId, createMode, true /*toTop*/, true /* animate */, - null /* initialBounds */); - } } catch(RemoteException e) {} // Post back to WM to handle clean-ups. We still need the input @@ -243,7 +231,9 @@ class TaskPositioner implements DimLayer.DimLayerUser { /** * @param display The Display that the window being dragged is on. */ - void register(Display display) { + void register(DisplayContent displayContent) { + final Display display = displayContent.getDisplay(); + if (DEBUG_TASK_POSITIONING) { Slog.d(TAG, "Registering task positioner"); } @@ -305,7 +295,6 @@ class TaskPositioner implements DimLayer.DimLayerUser { } mService.pauseRotationLocked(); - mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId(), TAG_LOCAL); mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics); mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics); mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics); @@ -336,12 +325,6 @@ class TaskPositioner implements DimLayer.DimLayerUser { mDragWindowHandle = null; mDragApplicationHandle = null; mDisplay = null; - - if (mDimLayer != null) { - mDimLayer.destroySurface(); - mDimLayer = null; - } - mCurrentDimSide = CTRL_NONE; mDragEnded = true; // Resume rotations after a drag. @@ -399,6 +382,27 @@ class TaskPositioner implements DimLayer.DimLayerUser { mStartOrientationWasLandscape = startBounds.width() >= startBounds.height(); mWindowOriginalBounds.set(startBounds); + // Notify the app that resizing has started, even though we haven't received any new + // bounds yet. This will guarantee that the app starts the backdrop renderer before + // configuration changes which could cause an activity restart. + if (mResizing) { + synchronized (mService.mWindowMap) { + notifyMoveLocked(startX, startY); + } + + // Perform the resize on the WMS handler thread when we don't have the WMS lock held + // to ensure that we don't deadlock WMS and AMS. Note that WindowPositionerEventReceiver + // callbacks are delivered on the same handler so this initial resize is always + // guaranteed to happen before subsequent drag resizes. + mService.mH.post(() -> { + try { + mService.mActivityManager.resizeTask( + mTask.mTaskId, startBounds, RESIZE_MODE_USER_FORCED); + } catch (RemoteException e) { + } + }); + } + // Make sure we always have valid drag bounds even if the drag ends before any move events // have been handled. mWindowDragBounds.set(startBounds); @@ -434,7 +438,6 @@ class TaskPositioner implements DimLayer.DimLayerUser { } updateWindowDragBounds(nX, nY, mTmpRect); - updateDimLayerVisibility(nX); return false; } @@ -621,88 +624,6 @@ class TaskPositioner implements DimLayer.DimLayerUser { "updateWindowDragBounds: " + mWindowDragBounds); } - private void updateDimLayerVisibility(int x) { - @CtrlType - int dimSide = getDimSide(x); - if (dimSide == mCurrentDimSide) { - return; - } - - mCurrentDimSide = dimSide; - - if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION updateDimLayerVisibility"); - mService.openSurfaceTransaction(); - if (mCurrentDimSide == CTRL_NONE) { - mDimLayer.hide(); - } else { - showDimLayer(); - } - mService.closeSurfaceTransaction("updateDimLayerVisibility"); - } - - /** - * Returns the side of the screen the dim layer should be shown. - * @param x horizontal coordinate used to determine if the dim layer should be shown - * @return Returns {@link #CTRL_LEFT} if the dim layer should be shown on the left half of the - * screen, {@link #CTRL_RIGHT} if on the right side, or {@link #CTRL_NONE} if the dim layer - * shouldn't be shown. - */ - private int getDimSide(int x) { - if (!mTask.mStack.inFreeformWindowingMode() - || !mTask.mStack.fillsParent() - || mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) { - return CTRL_NONE; - } - - mTask.mStack.getDimBounds(mTmpRect); - if (x - mSideMargin <= mTmpRect.left) { - return CTRL_LEFT; - } - if (x + mSideMargin >= mTmpRect.right) { - return CTRL_RIGHT; - } - - return CTRL_NONE; - } - - private void showDimLayer() { - mTask.mStack.getDimBounds(mTmpRect); - if (mCurrentDimSide == CTRL_LEFT) { - mTmpRect.right = mTmpRect.centerX(); - } else if (mCurrentDimSide == CTRL_RIGHT) { - mTmpRect.left = mTmpRect.centerX(); - } - - mDimLayer.setBounds(mTmpRect); - mDimLayer.show(mService.getDragLayerLocked(), RESIZING_HINT_ALPHA, - RESIZING_HINT_DURATION_MS); - } - - @Override /** {@link DimLayer.DimLayerUser} */ - public boolean dimFullscreen() { - return isFullscreen(); - } - - boolean isFullscreen() { - return false; - } - - @Override /** {@link DimLayer.DimLayerUser} */ - public DisplayInfo getDisplayInfo() { - return mTask.mStack.getDisplayInfo(); - } - - @Override - public boolean isAttachedToDisplay() { - return mTask != null && mTask.getDisplayContent() != null; - } - - @Override - public void getDimBounds(Rect out) { - // This dim layer user doesn't need this. - } - - @Override public String toShortString() { return TAG; } diff --git a/com/android/server/wm/TaskSnapshotController.java b/com/android/server/wm/TaskSnapshotController.java index 54ef0651..84e475a2 100644 --- a/com/android/server/wm/TaskSnapshotController.java +++ b/com/android/server/wm/TaskSnapshotController.java @@ -18,12 +18,12 @@ package com.android.server.wm; import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS; import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.Nullable; import android.app.ActivityManager; -import android.app.ActivityManager.StackId; import android.app.ActivityManager.TaskSnapshot; import android.content.pm.PackageManager; import android.graphics.Bitmap; @@ -35,16 +35,17 @@ import android.util.ArraySet; import android.util.Slog; import android.view.DisplayListCanvas; import android.view.RenderNode; +import android.view.SurfaceControl; import android.view.ThreadedRenderer; import android.view.WindowManager.LayoutParams; -import android.view.WindowManagerPolicy.ScreenOffListener; -import android.view.WindowManagerPolicy.StartingSurface; - -import com.google.android.collect.Sets; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.policy.WindowManagerPolicy.ScreenOffListener; +import com.android.server.policy.WindowManagerPolicy.StartingSurface; import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter; +import com.google.android.collect.Sets; + import java.io.PrintWriter; /** @@ -210,11 +211,28 @@ class TaskSnapshotController { if (mainWindow == null) { return null; } + if (!mService.mPolicy.isScreenOn()) { + if (DEBUG_SCREENSHOT) { + Slog.i(TAG_WM, "Attempted to take screenshot while display was off."); + } + return null; + } + if (task.getSurfaceControl() == null) { + return null; + } + final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic(); final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f; - final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token, - -1, -1, false, scaleFraction, false, true); + final Rect taskFrame = new Rect(); + task.getBounds(taskFrame); + + final GraphicBuffer buffer = SurfaceControl.captureLayersToBuffer( + task.getSurfaceControl().getHandle(), taskFrame, scaleFraction); + if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { + if (DEBUG_SCREENSHOT) { + Slog.w(TAG_WM, "Failed to take screenshot"); + } return null; } return new TaskSnapshot(buffer, top.getConfiguration().orientation, diff --git a/com/android/server/wm/TaskSnapshotSurface.java b/com/android/server/wm/TaskSnapshotSurface.java index 3ce090ac..41915a32 100644 --- a/com/android/server/wm/TaskSnapshotSurface.java +++ b/com/android/server/wm/TaskSnapshotSurface.java @@ -18,9 +18,7 @@ package com.android.server.wm; import static android.graphics.Color.WHITE; import static android.graphics.Color.alpha; -import static android.view.SurfaceControl.HIDDEN; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; -import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE; @@ -67,12 +65,12 @@ import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.view.WindowManagerPolicy.StartingSurface; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.DecorView; import com.android.internal.view.BaseIWindow; +import com.android.server.policy.WindowManagerPolicy.StartingSurface; /** * This class represents a starting window that shows a snapshot. diff --git a/com/android/server/wm/TaskStack.java b/com/android/server/wm/TaskStack.java index 053fb470..4a3a3fc9 100644 --- a/com/android/server/wm/TaskStack.java +++ b/com/android/server/wm/TaskStack.java @@ -31,12 +31,11 @@ import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; import static android.view.WindowManager.DOCKED_TOP; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM; import static com.android.server.wm.proto.StackProto.ANIMATION_BACKGROUND_SURFACE_IS_DIMMING; import static com.android.server.wm.proto.StackProto.BOUNDS; import static com.android.server.wm.proto.StackProto.FILLS_PARENT; @@ -55,6 +54,7 @@ import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; import android.view.Surface; +import android.view.SurfaceControl; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; @@ -63,7 +63,7 @@ import com.android.server.EventLogTags; import java.io.PrintWriter; -public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLayerUser, +public class TaskStack extends WindowContainer<Task> implements BoundsAnimationTarget { /** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to * restrict IME adjustment so that a min portion of top stack remains visible.*/ @@ -87,9 +87,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye private Rect mTmpRect2 = new Rect(); private Rect mTmpRect3 = new Rect(); - /** Content limits relative to the DisplayContent this sits in. */ - private Rect mBounds = new Rect(); - /** Stack bounds adjusted to screen content area (taking into account IM windows, etc.) */ private final Rect mAdjustedBounds = new Rect(); @@ -99,17 +96,14 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye */ private final Rect mFullyAdjustedImeBounds = new Rect(); - /** Whether mBounds is fullscreen */ - private boolean mFillsParent = true; - // Device rotation as of the last time {@link #mBounds} was set. private int mRotation; /** Density as of last time {@link #mBounds} was set. */ private int mDensity; - /** Support for non-zero {@link android.view.animation.Animation#getBackgroundColor()} */ - private DimLayer mAnimationBackgroundSurface; + private SurfaceControl mAnimationBackgroundSurface; + private boolean mAnimationBackgroundSurfaceIsShown = false; /** The particular window with an Animation with non-zero background color. */ private WindowStateAnimator mAnimationBackgroundAnimator; @@ -149,6 +143,13 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye Rect mPreAnimationBounds = new Rect(); + private Dimmer mDimmer = new Dimmer(this); + + /** + * For {@link #prepareSurfaces}. + */ + final Rect mTmpDimBoundsRect = new Rect(); + TaskStack(WindowManagerService service, int stackId, StackWindowController controller) { mService = service; mStackId = stackId; @@ -172,27 +173,20 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye /** * Set the bounds of the stack and its containing tasks. * @param stackBounds New stack bounds. Passing in null sets the bounds to fullscreen. - * @param configs Configuration for individual tasks, keyed by task id. * @param taskBounds Bounds for individual tasks, keyed by task id. + * @param taskTempInsetBounds Inset bounds for individual tasks, keyed by task id. * @return True if the stack bounds was changed. * */ boolean setBounds( - Rect stackBounds, SparseArray<Configuration> configs, SparseArray<Rect> taskBounds, - SparseArray<Rect> taskTempInsetBounds) { + Rect stackBounds, SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) { setBounds(stackBounds); // Update bounds of containing tasks. for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) { final Task task = mChildren.get(taskNdx); - Configuration config = configs.get(task.mTaskId); - if (config != null) { - Rect bounds = taskBounds.get(task.mTaskId); - task.resizeLocked(bounds, config, false /* forced */); - task.setTempInsetBounds(taskTempInsetBounds != null ? - taskTempInsetBounds.get(task.mTaskId) : null); - } else { - Slog.wtf(TAG_WM, "No config for task: " + task + ", is there a mismatch with AM?"); - } + task.setBounds(taskBounds.get(task.mTaskId), false /* forced */); + task.setTempInsetBounds(taskTempInsetBounds != null ? + taskTempInsetBounds.get(task.mTaskId) : null); } return true; } @@ -219,20 +213,20 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye final boolean adjusted = !mAdjustedBounds.isEmpty(); Rect insetBounds = null; if (adjusted && isAdjustedForMinimizedDockedStack()) { - insetBounds = mBounds; + insetBounds = getRawBounds(); } else if (adjusted && mAdjustedForIme) { if (mImeGoingAway) { - insetBounds = mBounds; + insetBounds = getRawBounds(); } else { insetBounds = mFullyAdjustedImeBounds; } } - alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : mBounds, insetBounds); + alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : getRawBounds(), insetBounds); mDisplayContent.setLayoutNeeded(); } private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) { - if (mFillsParent) { + if (matchParentBounds()) { return; } @@ -245,53 +239,85 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } } - private boolean setBounds(Rect bounds) { - boolean oldFullscreen = mFillsParent; + private void updateAnimationBackgroundBounds() { + if (mAnimationBackgroundSurface == null) { + return; + } + getRawBounds(mTmpRect); + // TODO: Should be in relative coordinates. + getPendingTransaction().setSize(mAnimationBackgroundSurface, mTmpRect.width(), + mTmpRect.height()).setPosition(mAnimationBackgroundSurface, mTmpRect.left, + mTmpRect.top); + scheduleAnimation(); + } + + private void hideAnimationSurface() { + if (mAnimationBackgroundSurface == null) { + return; + } + getPendingTransaction().hide(mAnimationBackgroundSurface); + mAnimationBackgroundSurfaceIsShown = false; + scheduleAnimation(); + } + + private void showAnimationSurface(float alpha) { + if (mAnimationBackgroundSurface == null) { + return; + } + getPendingTransaction().setLayer(mAnimationBackgroundSurface, Integer.MIN_VALUE) + .setAlpha(mAnimationBackgroundSurface, alpha) + .show(mAnimationBackgroundSurface); + mAnimationBackgroundSurfaceIsShown = true; + scheduleAnimation(); + } + + @Override + public int setBounds(Rect bounds) { + return setBounds(getOverrideBounds(), bounds); + } + + private int setBounds(Rect existing, Rect bounds) { int rotation = Surface.ROTATION_0; int density = DENSITY_DPI_UNDEFINED; if (mDisplayContent != null) { - mDisplayContent.getLogicalDisplayRect(mTmpRect); + mDisplayContent.getBounds(mTmpRect); rotation = mDisplayContent.getDisplayInfo().rotation; density = mDisplayContent.getDisplayInfo().logicalDensityDpi; - mFillsParent = bounds == null; - if (mFillsParent) { - bounds = mTmpRect; - } } - if (bounds == null) { - // Can't set to fullscreen if we don't have a display to get bounds from... - return false; - } - if (mBounds.equals(bounds) && oldFullscreen == mFillsParent && mRotation == rotation) { - return false; + if (equivalentBounds(existing, bounds) && mRotation == rotation) { + return BOUNDS_CHANGE_NONE; } + final int result = super.setBounds(bounds); + if (mDisplayContent != null) { - mDisplayContent.mDimLayerController.updateDimLayer(this); - mAnimationBackgroundSurface.setBounds(bounds); + updateAnimationBackgroundBounds(); } - mBounds.set(bounds); mRotation = rotation; mDensity = density; updateAdjustedBounds(); - return true; + return result; } /** Bounds of the stack without adjusting for other factors in the system like visibility * of docked stack. - * Most callers should be using {@link #getBounds} as it take into consideration other system - * factors. */ + * Most callers should be using {@link ConfigurationContainer#getOverrideBounds} as it take into + * consideration other system factors. */ void getRawBounds(Rect out) { - out.set(mBounds); + out.set(getRawBounds()); + } + + Rect getRawBounds() { + return super.getBounds(); } /** Return true if the current bound can get outputted to the rest of the system as-is. */ private boolean useCurrentBounds() { - if (mFillsParent + if (matchParentBounds() || !inSplitScreenSecondaryWindowingMode() || mDisplayContent == null || mDisplayContent.getSplitScreenPrimaryStack() != null) { @@ -300,24 +326,29 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye return false; } - public void getBounds(Rect out) { + @Override + public void getBounds(Rect bounds) { + bounds.set(getBounds()); + } + + @Override + public Rect getBounds() { if (useCurrentBounds()) { // If we're currently adjusting for IME or minimized docked stack, we use the adjusted // bounds; otherwise, no need to adjust the output bounds if fullscreen or the docked // stack is visible since it is already what we want to represent to the rest of the // system. if (!mAdjustedBounds.isEmpty()) { - out.set(mAdjustedBounds); + return mAdjustedBounds; } else { - out.set(mBounds); + return super.getBounds(); } - return; } // The bounds has been adjusted to accommodate for a docked stack, but the docked stack // is not currently visible. Go ahead a represent it as fullscreen to the rest of the // system. - mDisplayContent.getLogicalDisplayRect(out); + return mDisplayContent.getBounds(); } /** @@ -338,7 +369,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye mBoundsAnimationSourceHintBounds.setEmpty(); } - mPreAnimationBounds.set(mBounds); + mPreAnimationBounds.set(getRawBounds()); } /** @@ -368,7 +399,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } /** Bounds of the stack with other system factors taken into consideration. */ - @Override public void getDimBounds(Rect out) { getBounds(out); } @@ -384,12 +414,12 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye if (bounds != null) { setBounds(bounds); return; - } else if (mFillsParent) { + } else if (matchParentBounds()) { setBounds(null); return; } - mTmpRect2.set(mBounds); + mTmpRect2.set(getRawBounds()); final int newRotation = mDisplayContent.getDisplayInfo().rotation; final int newDensity = mDisplayContent.getDisplayInfo().logicalDensityDpi; if (mRotation == newRotation && mDensity == newDensity) { @@ -432,14 +462,14 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye return false; } - if (mFillsParent) { + if (matchParentBounds()) { // Update stack bounds again since rotation changed since updateDisplayInfo(). setBounds(null); // Return false since we don't need the client to resize. return false; } - mTmpRect2.set(mBounds); + mTmpRect2.set(getRawBounds()); mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2); if (inSplitScreenPrimaryWindowingMode()) { repositionPrimarySplitScreenStackAfterRotation(mTmpRect2); @@ -476,7 +506,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye if (mDisplayContent.getDockedDividerController().canPrimaryStackDockTo(dockSide)) { return; } - mDisplayContent.getLogicalDisplayRect(mTmpRect); + mDisplayContent.getBounds(mTmpRect); dockSide = DockedDividerUtils.invertDockSide(dockSide); switch (dockSide) { case DOCKED_LEFT: @@ -700,9 +730,12 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } mDisplayContent = dc; - mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId(), - "animation background stackId=" + mStackId); + updateBoundsForWindowModeChange(); + mAnimationBackgroundSurface = makeChildSurface(null).setColorLayer(true) + .setName("animation background stackId=" + mStackId) + .build(); + super.onDisplayChanged(dc); } @@ -718,7 +751,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye // not fullscreen. If it's fullscreen, it means that we are in the transition of // dismissing it, so we must not resize this stack. bounds = new Rect(); - mDisplayContent.getLogicalDisplayRect(mTmpRect); + mDisplayContent.getBounds(mTmpRect); mTmpRect2.setEmpty(); if (splitScreenStack != null) { splitScreenStack.getRawBounds(mTmpRect2); @@ -781,7 +814,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } if (!inSplitScreenWindowingMode() || mDisplayContent == null) { - outStackBounds.set(mBounds); + outStackBounds.set(getRawBounds()); return; } @@ -796,7 +829,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye // The docked stack is being dismissed, but we caught before it finished being // dismissed. In that case we want to treat it as if it is not occupying any space and // let others occupy the whole display. - mDisplayContent.getLogicalDisplayRect(outStackBounds); + mDisplayContent.getBounds(outStackBounds); return; } @@ -804,11 +837,11 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye if (dockedSide == DOCKED_INVALID) { // Not sure how you got here...Only thing we can do is return current bounds. Slog.e(TAG_WM, "Failed to get valid docked side for docked stack=" + dockedStack); - outStackBounds.set(mBounds); + outStackBounds.set(getRawBounds()); return; } - mDisplayContent.getLogicalDisplayRect(mTmpRect); + mDisplayContent.getBounds(mTmpRect); dockedStack.getRawBounds(mTmpRect2); final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT; getStackDockedModeBounds(mTmpRect, outStackBounds, mTmpRect2, @@ -914,16 +947,16 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye @Override void onParentSet() { + super.onParentSet(); + if (getParent() != null || mDisplayContent == null) { return; } - // Looks like the stack was removed from the display. Go ahead and clean things up. - mDisplayContent.mDimLayerController.removeDimLayerUser(this); EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId); if (mAnimationBackgroundSurface != null) { - mAnimationBackgroundSurface.destroySurface(); + mAnimationBackgroundSurface.destroy(); mAnimationBackgroundSurface = null; } @@ -933,9 +966,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye void resetAnimationBackgroundAnimator() { mAnimationBackgroundAnimator = null; - if (mAnimationBackgroundSurface != null) { - mAnimationBackgroundSurface.hide(); - } + hideAnimationSurface(); } void setAnimationBackground(WindowStateAnimator winAnimator, int color) { @@ -944,8 +975,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye || animLayer < mAnimationBackgroundAnimator.mAnimLayer) { mAnimationBackgroundAnimator = winAnimator; animLayer = mDisplayContent.getLayerForAnimationBackground(winAnimator); - mAnimationBackgroundSurface.show(animLayer - LAYER_OFFSET_DIM, - ((color >> 24) & 0xff) / 255f, 0); + showAnimationSurface(((color >> 24) & 0xff) / 255f); } } @@ -1112,14 +1142,14 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye // occluded by IME. We shift its bottom up by the height of the IME, but // leaves at least 30% of the top stack visible. final int minTopStackBottom = - getMinTopStackBottom(displayContentRect, mBounds.bottom); + getMinTopStackBottom(displayContentRect, getRawBounds().bottom); final int bottom = Math.max( - mBounds.bottom - yOffset + dividerWidth - dividerWidthInactive, + getRawBounds().bottom - yOffset + dividerWidth - dividerWidthInactive, minTopStackBottom); - mTmpAdjustedBounds.set(mBounds); - mTmpAdjustedBounds.bottom = - (int) (mAdjustImeAmount * bottom + (1 - mAdjustImeAmount) * mBounds.bottom); - mFullyAdjustedImeBounds.set(mBounds); + mTmpAdjustedBounds.set(getRawBounds()); + mTmpAdjustedBounds.bottom = (int) (mAdjustImeAmount * bottom + (1 - mAdjustImeAmount) + * getRawBounds().bottom); + mFullyAdjustedImeBounds.set(getRawBounds()); } else { // When the stack is on bottom and has no focus, it's only adjusted for divider width. final int dividerWidthDelta = dividerWidthInactive - dividerWidth; @@ -1129,22 +1159,24 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye // We try to move it up by the height of the IME window, but only to the extent // that leaves at least 30% of the top stack visible. // 'top' is where the top of bottom stack will move to in this case. - final int topBeforeImeAdjust = mBounds.top - dividerWidth + dividerWidthInactive; + final int topBeforeImeAdjust = + getRawBounds().top - dividerWidth + dividerWidthInactive; final int minTopStackBottom = - getMinTopStackBottom(displayContentRect, mBounds.top - dividerWidth); + getMinTopStackBottom(displayContentRect, + getRawBounds().top - dividerWidth); final int top = Math.max( - mBounds.top - yOffset, minTopStackBottom + dividerWidthInactive); + getRawBounds().top - yOffset, minTopStackBottom + dividerWidthInactive); - mTmpAdjustedBounds.set(mBounds); + mTmpAdjustedBounds.set(getRawBounds()); // Account for the adjustment for IME and divider width separately. // (top - topBeforeImeAdjust) is the amount of movement due to IME only, // and dividerWidthDelta is due to divider width change only. - mTmpAdjustedBounds.top = mBounds.top + + mTmpAdjustedBounds.top = getRawBounds().top + (int) (mAdjustImeAmount * (top - topBeforeImeAdjust) + mAdjustDividerAmount * dividerWidthDelta); - mFullyAdjustedImeBounds.set(mBounds); + mFullyAdjustedImeBounds.set(getRawBounds()); mFullyAdjustedImeBounds.top = top; - mFullyAdjustedImeBounds.bottom = top + mBounds.height(); + mFullyAdjustedImeBounds.bottom = top + getRawBounds().height(); } return true; } @@ -1158,21 +1190,21 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye if (dockSide == DOCKED_TOP) { mService.getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect); int topInset = mTmpRect.top; - mTmpAdjustedBounds.set(mBounds); - mTmpAdjustedBounds.bottom = - (int) (minimizeAmount * topInset + (1 - minimizeAmount) * mBounds.bottom); + mTmpAdjustedBounds.set(getRawBounds()); + mTmpAdjustedBounds.bottom = (int) (minimizeAmount * topInset + (1 - minimizeAmount) + * getRawBounds().bottom); } else if (dockSide == DOCKED_LEFT) { - mTmpAdjustedBounds.set(mBounds); - final int width = mBounds.width(); + mTmpAdjustedBounds.set(getRawBounds()); + final int width = getRawBounds().width(); mTmpAdjustedBounds.right = (int) (minimizeAmount * mDockedStackMinimizeThickness - + (1 - minimizeAmount) * mBounds.right); + + (1 - minimizeAmount) * getRawBounds().right); mTmpAdjustedBounds.left = mTmpAdjustedBounds.right - width; } else if (dockSide == DOCKED_RIGHT) { - mTmpAdjustedBounds.set(mBounds); - mTmpAdjustedBounds.left = - (int) (minimizeAmount * (mBounds.right - mDockedStackMinimizeThickness) - + (1 - minimizeAmount) * mBounds.left); + mTmpAdjustedBounds.set(getRawBounds()); + mTmpAdjustedBounds.left = (int) (minimizeAmount * + (getRawBounds().right - mDockedStackMinimizeThickness) + + (1 - minimizeAmount) * getRawBounds().left); } return true; } @@ -1194,9 +1226,9 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye if (dockSide == DOCKED_TOP) { mService.getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect); int topInset = mTmpRect.top; - return mBounds.bottom - topInset; + return getRawBounds().bottom - topInset; } else if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) { - return mBounds.width() - mDockedStackMinimizeThickness; + return getRawBounds().width() - mDockedStackMinimizeThickness; } else { return 0; } @@ -1230,11 +1262,12 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye return; } - final Rect insetBounds = mImeGoingAway ? mBounds : mFullyAdjustedImeBounds; + final Rect insetBounds = mImeGoingAway ? getRawBounds() : mFullyAdjustedImeBounds; task.alignToAdjustedBounds(mAdjustedBounds, insetBounds, getDockSide() == DOCKED_TOP); mDisplayContent.setLayoutNeeded(); } + boolean isAdjustedForMinimizedDockedStack() { return mMinimizeAmount != 0f; } @@ -1248,17 +1281,16 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) { mChildren.get(taskNdx).writeToProto(proto, TASKS, trim); } - proto.write(FILLS_PARENT, mFillsParent); - mBounds.writeToProto(proto, BOUNDS); - proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurface.isDimming()); + proto.write(FILLS_PARENT, matchParentBounds()); + getRawBounds().writeToProto(proto, BOUNDS); + proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurfaceIsShown); proto.end(token); } public void dump(String prefix, PrintWriter pw) { pw.println(prefix + "mStackId=" + mStackId); pw.println(prefix + "mDeferRemoval=" + mDeferRemoval); - pw.println(prefix + "mFillsParent=" + mFillsParent); - pw.println(prefix + "mBounds=" + mBounds.toShortString()); + pw.println(prefix + "mBounds=" + getRawBounds().toShortString()); if (mMinimizeAmount != 0f) { pw.println(prefix + "mMinimizeAmount=" + mMinimizeAmount); } @@ -1273,9 +1305,8 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) { mChildren.get(taskNdx).dump(prefix + " ", pw); } - if (mAnimationBackgroundSurface.isDimming()) { - pw.println(prefix + "mWindowAnimationBackgroundSurface:"); - mAnimationBackgroundSurface.printTo(prefix + " ", pw); + if (mAnimationBackgroundSurfaceIsShown) { + pw.println(prefix + "mWindowAnimationBackgroundSurface is shown"); } if (!mExitingAppTokens.isEmpty()) { pw.println(); @@ -1290,23 +1321,10 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } } - /** Fullscreen status of the stack without adjusting for other factors in the system like - * visibility of docked stack. - * Most callers should be using {@link #fillsParent} as it take into consideration other - * system factors. */ - boolean getRawFullscreen() { - return mFillsParent; - } - - @Override - public boolean dimFullscreen() { - return !isActivityTypeStandard() || fillsParent(); - } - @Override boolean fillsParent() { if (useCurrentBounds()) { - return mFillsParent; + return matchParentBounds(); } // The bounds has been adjusted to accommodate for a docked stack, but the docked stack // is not currently visible. Go ahead a represent it as fullscreen to the rest of the @@ -1315,16 +1333,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } @Override - public DisplayInfo getDisplayInfo() { - return mDisplayContent.getDisplayInfo(); - } - - @Override - public boolean isAttachedToDisplay() { - return mDisplayContent != null; - } - - @Override public String toString() { return "{stackId=" + mStackId + " tasks=" + mChildren + "}"; } @@ -1333,7 +1341,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye return toShortString(); } - @Override public String toShortString() { return "Stack=" + mStackId; } @@ -1343,7 +1350,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye * information which side of the screen was the dock anchored. */ int getDockSide() { - return getDockSide(mBounds); + return getDockSide(getRawBounds()); } private int getDockSide(Rect bounds) { @@ -1353,7 +1360,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye if (mDisplayContent == null) { return DOCKED_INVALID; } - mDisplayContent.getLogicalDisplayRect(mTmpRect); + mDisplayContent.getBounds(mTmpRect); final int orientation = mDisplayContent.getConfiguration().orientation; return getDockSideUnchecked(bounds, mTmpRect, orientation); } @@ -1473,7 +1480,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye */ if (task.isActivityTypeHome() && isMinimizedDockAndHomeStackResizable()) { - mDisplayContent.getLogicalDisplayRect(mTmpRect); + mDisplayContent.getBounds(mTmpRect); } else { task.getDimBounds(mTmpRect); } @@ -1691,4 +1698,32 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye || activityType == ACTIVITY_TYPE_RECENTS || activityType == ACTIVITY_TYPE_ASSISTANT; } + + Dimmer getDimmer() { + return mDimmer; + } + + @Override + void prepareSurfaces() { + mDimmer.resetDimStates(); + super.prepareSurfaces(); + getDimBounds(mTmpDimBoundsRect); + if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) { + scheduleAnimation(); + } + } + + public DisplayInfo getDisplayInfo() { + return mDisplayContent.getDisplayInfo(); + } + + void dim(float alpha) { + mDimmer.dimAbove(getPendingTransaction(), alpha); + scheduleAnimation(); + } + + void stopDimming() { + mDimmer.stopDim(getPendingTransaction()); + scheduleAnimation(); + } } diff --git a/com/android/server/wm/TaskTapPointerEventListener.java b/com/android/server/wm/TaskTapPointerEventListener.java index 42a2d9df..84ad5764 100644 --- a/com/android/server/wm/TaskTapPointerEventListener.java +++ b/com/android/server/wm/TaskTapPointerEventListener.java @@ -20,7 +20,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; import android.view.MotionEvent; -import android.view.WindowManagerPolicy.PointerEventListener; +import android.view.WindowManagerPolicyConstants.PointerEventListener; import com.android.server.wm.WindowManagerService.H; diff --git a/com/android/server/wm/TaskWindowContainerController.java b/com/android/server/wm/TaskWindowContainerController.java index b3bb0b7e..5caae32d 100644 --- a/com/android/server/wm/TaskWindowContainerController.java +++ b/com/android/server/wm/TaskWindowContainerController.java @@ -18,7 +18,6 @@ package com.android.server.wm; import android.app.ActivityManager.TaskDescription; import android.app.ActivityManager.TaskSnapshot; -import android.content.res.Configuration; import android.graphics.Rect; import android.os.Handler; import android.os.Looper; @@ -30,6 +29,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.lang.ref.WeakReference; import static com.android.server.EventLogTags.WM_TASK_CREATED; +import static com.android.server.wm.ConfigurationContainer.BOUNDS_CHANGE_NONE; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -75,7 +75,7 @@ public class TaskWindowContainerController + stackController); } EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId); - final Task task = createTask(taskId, stack, userId, bounds, resizeMode, + final Task task = createTask(taskId, stack, userId, resizeMode, supportsPictureInPicture, taskDescription); final int position = toTop ? POSITION_TOP : POSITION_BOTTOM; // We only want to move the parents to the parents if we are creating this task at the @@ -85,10 +85,10 @@ public class TaskWindowContainerController } @VisibleForTesting - Task createTask(int taskId, TaskStack stack, int userId, Rect bounds, int resizeMode, + Task createTask(int taskId, TaskStack stack, int userId, int resizeMode, boolean supportsPictureInPicture, TaskDescription taskDescription) { - return new Task(taskId, stack, userId, mService, bounds, resizeMode, - supportsPictureInPicture, taskDescription, this); + return new Task(taskId, stack, userId, mService, resizeMode, supportsPictureInPicture, + taskDescription, this); } @Override @@ -151,14 +151,14 @@ public class TaskWindowContainerController } } - public void resize(Rect bounds, Configuration overrideConfig, boolean relayout, - boolean forced) { + public void resize(boolean relayout, boolean forced) { synchronized (mWindowMap) { if (mContainer == null) { throw new IllegalArgumentException("resizeTask: taskId " + mTaskId + " not found."); } - if (mContainer.resizeLocked(bounds, overrideConfig, forced) && relayout) { + if (mContainer.setBounds(mContainer.getOverrideBounds(), forced) != BOUNDS_CHANGE_NONE + && relayout) { mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); } } diff --git a/com/android/server/wm/WallpaperController.java b/com/android/server/wm/WallpaperController.java index 629cc868..3ae45497 100644 --- a/com/android/server/wm/WallpaperController.java +++ b/com/android/server/wm/WallpaperController.java @@ -23,8 +23,8 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER; diff --git a/com/android/server/wm/Watermark.java b/com/android/server/wm/Watermark.java index d97aaac4..9216b66e 100644 --- a/com/android/server/wm/Watermark.java +++ b/com/android/server/wm/Watermark.java @@ -53,7 +53,7 @@ class Watermark { private int mLastDH; private boolean mDrawNeeded; - Watermark(Display display, DisplayMetrics dm, SurfaceSession session, String[] tokens) { + Watermark(DisplayContent dc, DisplayMetrics dm, String[] tokens) { if (false) { Log.i(TAG_WM, "*********************** WATERMARK"); for (int i=0; i<tokens.length; i++) { @@ -61,7 +61,7 @@ class Watermark { } } - mDisplay = display; + mDisplay = dc.getDisplay(); mTokens = tokens; StringBuilder builder = new StringBuilder(32); @@ -114,7 +114,7 @@ class Watermark { SurfaceControl ctrl = null; try { - ctrl = new SurfaceControl.Builder(session) + ctrl = dc.makeOverlay() .setName("WatermarkSurface") .setSize(1, 1) .setFormat(PixelFormat.TRANSLUCENT) diff --git a/com/android/server/wm/WindowAnimator.java b/com/android/server/wm/WindowAnimator.java index 1912095c..7c56f00b 100644 --- a/com/android/server/wm/WindowAnimator.java +++ b/com/android/server/wm/WindowAnimator.java @@ -30,10 +30,9 @@ import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; import android.view.Choreographer; -import android.view.SurfaceControl; -import android.view.WindowManagerPolicy; import com.android.server.AnimationThread; +import com.android.server.policy.WindowManagerPolicy; import java.io.PrintWriter; @@ -200,7 +199,7 @@ public class WindowAnimator { ++mAnimTransactionSequence; dc.updateWindowsForAnimator(this); dc.updateWallpaperForAnimator(this); - dc.prepareWindowSurfaces(); + dc.prepareSurfaces(); } for (int i = 0; i < numDisplays; i++) { @@ -214,8 +213,6 @@ public class WindowAnimator { if (screenRotationAnimation != null) { screenRotationAnimation.updateSurfacesInTransaction(); } - - orAnimating(dc.animateDimLayers()); orAnimating(dc.getDockedDividerController().animate(mCurrentTime)); //TODO (multidisplay): Magnification is supported only for the default display. if (accessibilityController != null && dc.isDefaultDisplay) { @@ -237,6 +234,13 @@ public class WindowAnimator { if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate"); } + final int numDisplays = mDisplayContentsAnimators.size(); + for (int i = 0; i < numDisplays; i++) { + final int displayId = mDisplayContentsAnimators.keyAt(i); + final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId); + dc.onPendingTransactionApplied(); + } + boolean hasPendingLayoutChanges = mService.mRoot.hasPendingLayoutChanges(this); boolean doRequest = false; if (mBulkUpdateParams != 0) { @@ -271,6 +275,7 @@ public class WindowAnimator { mService.destroyPreservedSurfaceLocked(); mService.mWindowPlacerLocked.destroyPendingSurfaces(); + if (DEBUG_WINDOW_TRACE) { Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams) diff --git a/com/android/server/wm/WindowContainer.java b/com/android/server/wm/WindowContainer.java index 8f4b897c..64675825 100644 --- a/com/android/server/wm/WindowContainer.java +++ b/com/android/server/wm/WindowContainer.java @@ -21,9 +21,13 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static com.android.server.wm.proto.WindowContainerProto.CONFIGURATION_CONTAINER; import static com.android.server.wm.proto.WindowContainerProto.ORIENTATION; +import static android.view.SurfaceControl.Transaction; import android.annotation.CallSuper; import android.content.res.Configuration; +import android.view.MagnificationSpec; +import android.view.SurfaceControl; +import android.view.SurfaceSession; import android.util.Pools; import android.util.proto.ProtoOutputStream; @@ -64,7 +68,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< new Pools.SynchronizedPool<>(3); // The owner/creator for this container. No controller if null. - private WindowContainerController mController; + WindowContainerController mController; + + protected SurfaceControl mSurfaceControl; + + /** + * Applied as part of the animation pass in "prepareSurfaces". + */ + private Transaction mPendingTransaction = new Transaction(); @Override final protected WindowContainer getParent() { @@ -101,7 +112,22 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * Supposed to be overridden and contain actions that should be executed after parent was set. */ void onParentSet() { - // Do nothing by default. + if (mParent == null) { + return; + } + if (mSurfaceControl == null) { + // If we don't yet have a surface, but we now have a parent, we should + // build a surface. + mSurfaceControl = makeSurface().build(); + getPendingTransaction().show(mSurfaceControl); + } else { + // If we have a surface but a new parent, we just need to perform a reparent. + getPendingTransaction().reparent(mSurfaceControl, mParent.mSurfaceControl.getHandle()); + } + + // Either way we need to ask the parent to assign us a Z-order. + mParent.assignChildLayers(); + scheduleAnimation(); } // Temp. holders for a chain of containers we are currently processing. @@ -188,6 +214,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mChildren.remove(child); } + if (mSurfaceControl != null) { + destroyAfterPendingTransaction(mSurfaceControl); + mSurfaceControl = null; + } + if (mParent != null) { mParent.removeChild(this); } @@ -195,6 +226,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< if (mController != null) { setController(null); } + } /** @@ -286,11 +318,24 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @see #mFullConfiguration */ @Override - final public void onOverrideConfigurationChanged(Configuration overrideConfiguration) { + public void onOverrideConfigurationChanged(Configuration overrideConfiguration) { + // We must diff before the configuration is applied so that we can capture the change + // against the existing bounds. + final int diff = diffOverrideBounds(overrideConfiguration.windowConfiguration.getBounds()); super.onOverrideConfigurationChanged(overrideConfiguration); if (mParent != null) { mParent.onDescendantOverrideConfigurationChanged(); } + + if (diff == BOUNDS_CHANGE_NONE) { + return; + } + + if ((diff & BOUNDS_CHANGE_SIZE) == BOUNDS_CHANGE_SIZE) { + onResize(); + } else { + onMovedByResize(); + } } /** @@ -407,7 +452,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** -a * Returns whether this child is on top of the window hierarchy. + * @return Whether this child is on top of the window hierarchy. */ boolean isOnTop() { return getParent().getTopChild() == this && getParent().isOnTop(); @@ -673,6 +718,82 @@ a * Returns whether this child is on top of the window hierarchy. mController = controller; } + SurfaceControl.Builder makeSurface() { + final WindowContainer p = getParent(); + return p.makeChildSurface(this); + } + + /** + * @param child The WindowContainer this child surface is for, or null if the Surface + * is not assosciated with a WindowContainer (e.g. a surface used for Dimming). + */ + SurfaceControl.Builder makeChildSurface(WindowContainer child) { + final WindowContainer p = getParent(); + // Give the parent a chance to set properties. In hierarchy v1 we rely + // on this to set full-screen dimensions on all our Surface-less Layers. + return p.makeChildSurface(child) + .setParent(mSurfaceControl); + } + + /** + * @return Whether this WindowContainer should be magnified by the accessibility magnifier. + */ + boolean shouldMagnify() { + for (int i = 0; i < mChildren.size(); i++) { + if (!mChildren.get(i).shouldMagnify()) { + return false; + } + } + return true; + } + + SurfaceSession getSession() { + if (getParent() != null) { + return getParent().getSession(); + } + return null; + } + + void assignLayer(Transaction t, int layer) { + if (mSurfaceControl != null) { + t.setLayer(mSurfaceControl, layer); + } + } + + void assignChildLayers(Transaction t) { + int layer = 0; + boolean boosting = false; + + // We use two passes as a way to promote children which + // need Z-boosting to the end of the list. + for (int i = 0; i < 2; i++ ) { + for (int j = 0; j < mChildren.size(); ++j) { + final WindowContainer wc = mChildren.get(j); + if (wc.needsZBoost() && !boosting) { + continue; + } + wc.assignLayer(t, layer); + wc.assignChildLayers(t); + + layer++; + } + boosting = true; + } + } + + void assignChildLayers() { + assignChildLayers(getPendingTransaction()); + } + + boolean needsZBoost() { + for (int i = 0; i < mChildren.size(); i++) { + if (mChildren.get(i).needsZBoost()) { + return true; + } + } + return false; + } + /** * Write to a protocol buffer output stream. Protocol buffer message definition is at * {@link com.android.server.wm.proto.WindowContainerProto}. @@ -719,4 +840,59 @@ a * Returns whether this child is on top of the window hierarchy. mConsumerWrapperPool.release(this); } } + + // TODO(b/68336570): Should this really be on WindowContainer since it + // can only be used on the top-level nodes that aren't animated? + // (otherwise we would be fighting other callers of setMatrix). + void applyMagnificationSpec(Transaction t, MagnificationSpec spec) { + if (shouldMagnify()) { + t.setMatrix(mSurfaceControl, spec.scale, 0, 0, spec.scale) + .setPosition(mSurfaceControl, spec.offsetX, spec.offsetY); + } else { + for (int i = 0; i < mChildren.size(); i++) { + mChildren.get(i).applyMagnificationSpec(t, spec); + } + } + } + + /** + * TODO: Once we totally eliminate global transaction we will pass transaction in here + * rather than merging to global. + */ + void prepareSurfaces() { + SurfaceControl.mergeToGlobalTransaction(getPendingTransaction()); + for (int i = 0; i < mChildren.size(); i++) { + mChildren.get(i).prepareSurfaces(); + } + } + + /** + * Trigger a call to prepareSurfaces from the animation thread, such that + * mPendingTransaction will be applied. + */ + void scheduleAnimation() { + if (mParent != null) { + mParent.scheduleAnimation(); + } + } + + SurfaceControl getSurfaceControl() { + return mSurfaceControl; + } + + /** + * Destroy a given surface after executing mPendingTransaction. This is + * largely a workaround for destroy not being part of transactions + * rather than an intentional design, so please take care when + * expanding use. + */ + void destroyAfterPendingTransaction(SurfaceControl surface) { + if (mParent != null) { + mParent.destroyAfterPendingTransaction(surface); + } + } + + Transaction getPendingTransaction() { + return mPendingTransaction; + } } diff --git a/com/android/server/wm/WindowLayersController.java b/com/android/server/wm/WindowLayersController.java deleted file mode 100644 index 7caf2fe9..00000000 --- a/com/android/server/wm/WindowLayersController.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import android.util.Slog; - -import java.util.ArrayDeque; -import java.util.function.Consumer; - -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET; -import static com.android.server.wm.WindowManagerService.WINDOW_LAYER_MULTIPLIER; - -/** - * Controller for assigning layers to windows on the display. - * - * This class encapsulates general algorithm for assigning layers and special rules that we need to - * apply on top. The general algorithm goes through windows from bottom to the top and the higher - * the window is, the higher layer is assigned. The final layer is equal to base layer + - * adjustment from the order. This means that the window list is assumed to be ordered roughly by - * the base layer (there are exceptions, e.g. due to keyguard and wallpaper and they need to be - * handled with care, because they break the algorithm). - * - * On top of the general algorithm we add special rules, that govern such amazing things as: - * <li>IME (which has higher base layer, but will be positioned above application windows)</li> - * <li>docked/pinned windows (that need to be lifted above other application windows, including - * animations) - * <li>dock divider (which needs to live above applications, but below IME)</li> - * <li>replaced windows, which need to live above their normal level, because they anticipate - * an animation</li>. - */ -class WindowLayersController { - private final WindowManagerService mService; - - WindowLayersController(WindowManagerService service) { - mService = service; - } - - private ArrayDeque<WindowState> mPinnedWindows = new ArrayDeque<>(); - private ArrayDeque<WindowState> mDockedWindows = new ArrayDeque<>(); - private ArrayDeque<WindowState> mAssistantWindows = new ArrayDeque<>(); - private ArrayDeque<WindowState> mInputMethodWindows = new ArrayDeque<>(); - private WindowState mDockDivider = null; - private ArrayDeque<WindowState> mReplacingWindows = new ArrayDeque<>(); - private int mCurBaseLayer; - private int mCurLayer; - private boolean mAnyLayerChanged; - private int mHighestApplicationLayer; - private int mHighestDockedAffectedLayer; - private int mHighestLayerInImeTargetBaseLayer; - private WindowState mImeTarget; - private boolean mAboveImeTarget; - private ArrayDeque<WindowState> mAboveImeTargetAppWindows = new ArrayDeque(); - - private final Consumer<WindowState> mAssignWindowLayersConsumer = w -> { - boolean layerChanged = false; - - int oldLayer = w.mLayer; - if (w.mBaseLayer == mCurBaseLayer) { - mCurLayer += WINDOW_LAYER_MULTIPLIER; - } else { - mCurBaseLayer = mCurLayer = w.mBaseLayer; - } - assignAnimLayer(w, mCurLayer); - - // TODO: Preserved old behavior of code here but not sure comparing oldLayer to - // mAnimLayer and mLayer makes sense...though the worst case would be unintentional - // layer reassignment. - if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) { - layerChanged = true; - mAnyLayerChanged = true; - } - - if (w.mAppToken != null) { - mHighestApplicationLayer = Math.max(mHighestApplicationLayer, - w.mWinAnimator.mAnimLayer); - } - if (mImeTarget != null && w.mBaseLayer == mImeTarget.mBaseLayer) { - mHighestLayerInImeTargetBaseLayer = Math.max(mHighestLayerInImeTargetBaseLayer, - w.mWinAnimator.mAnimLayer); - } - if (w.getAppToken() != null && w.inSplitScreenSecondaryWindowingMode()) { - mHighestDockedAffectedLayer = Math.max(mHighestDockedAffectedLayer, - w.mWinAnimator.mAnimLayer); - } - - collectSpecialWindows(w); - - if (layerChanged) { - w.scheduleAnimationIfDimming(); - } - }; - - final void assignWindowLayers(DisplayContent dc) { - if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based", - new RuntimeException("here").fillInStackTrace()); - - reset(); - dc.forAllWindows(mAssignWindowLayersConsumer, false /* traverseTopToBottom */); - - adjustSpecialWindows(); - - //TODO (multidisplay): Magnification is supported only for the default display. - if (mService.mAccessibilityController != null && mAnyLayerChanged - && dc.getDisplayId() == DEFAULT_DISPLAY) { - mService.mAccessibilityController.onWindowLayersChangedLocked(); - } - - if (DEBUG_LAYERS) logDebugLayers(dc); - } - - private void logDebugLayers(DisplayContent dc) { - dc.forAllWindows((w) -> { - final WindowStateAnimator winAnimator = w.mWinAnimator; - Slog.v(TAG_WM, "Assign layer " + w + ": " + "mBase=" + w.mBaseLayer - + " mLayer=" + w.mLayer + (w.mAppToken == null - ? "" : " mAppLayer=" + w.mAppToken.getAnimLayerAdjustment()) - + " =mAnimLayer=" + winAnimator.mAnimLayer); - }, false /* traverseTopToBottom */); - } - - private void reset() { - mPinnedWindows.clear(); - mInputMethodWindows.clear(); - mDockedWindows.clear(); - mAssistantWindows.clear(); - mReplacingWindows.clear(); - mDockDivider = null; - - mCurBaseLayer = 0; - mCurLayer = 0; - mAnyLayerChanged = false; - - mHighestApplicationLayer = 0; - mHighestDockedAffectedLayer = 0; - mHighestLayerInImeTargetBaseLayer = (mImeTarget != null) ? mImeTarget.mBaseLayer : 0; - mImeTarget = mService.mInputMethodTarget; - mAboveImeTarget = false; - mAboveImeTargetAppWindows.clear(); - } - - private void collectSpecialWindows(WindowState w) { - if (w.mAttrs.type == TYPE_DOCK_DIVIDER) { - mDockDivider = w; - return; - } - if (w.mWillReplaceWindow) { - mReplacingWindows.add(w); - } - if (w.mIsImWindow) { - mInputMethodWindows.add(w); - return; - } - if (mImeTarget != null) { - if (w.getParentWindow() == mImeTarget && w.mSubLayer > 0) { - // Child windows of the ime target with a positive sub-layer should be placed above - // the IME. - mAboveImeTargetAppWindows.add(w); - } else if (mAboveImeTarget && w.mAppToken != null) { - // windows of apps above the IME target should be placed above the IME. - mAboveImeTargetAppWindows.add(w); - } - if (w == mImeTarget) { - mAboveImeTarget = true; - } - } - - final int windowingMode = w.getWindowingMode(); - if (windowingMode == WINDOWING_MODE_PINNED) { - mPinnedWindows.add(w); - } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - mDockedWindows.add(w); - } - if (w.isActivityTypeAssistant()) { - mAssistantWindows.add(w); - } - } - - private void adjustSpecialWindows() { - // The following adjustments are beyond the highest docked-affected layer - int layer = mHighestDockedAffectedLayer + TYPE_LAYER_OFFSET; - - // Adjust the docked stack windows and dock divider above only the windows that are affected - // by the docked stack. When this happens, also boost the assistant window layers, otherwise - // the docked stack windows & divider would be promoted above the assistant. - if (!mDockedWindows.isEmpty() && mHighestDockedAffectedLayer > 0) { - while (!mDockedWindows.isEmpty()) { - final WindowState window = mDockedWindows.remove(); - layer = assignAndIncreaseLayerIfNeeded(window, layer); - } - - layer = assignAndIncreaseLayerIfNeeded(mDockDivider, layer); - - while (!mAssistantWindows.isEmpty()) { - final WindowState window = mAssistantWindows.remove(); - if (window.mLayer > mHighestDockedAffectedLayer) { - layer = assignAndIncreaseLayerIfNeeded(window, layer); - } - } - } - - // The following adjustments are beyond the highest app layer or boosted layer - layer = Math.max(layer, mHighestApplicationLayer + WINDOW_LAYER_MULTIPLIER); - - // We know that we will be animating a relaunching window in the near future, which will - // receive a z-order increase. We want the replaced window to immediately receive the same - // treatment, e.g. to be above the dock divider. - while (!mReplacingWindows.isEmpty()) { - layer = assignAndIncreaseLayerIfNeeded(mReplacingWindows.remove(), layer); - } - - while (!mPinnedWindows.isEmpty()) { - layer = assignAndIncreaseLayerIfNeeded(mPinnedWindows.remove(), layer); - } - - // Make sure IME is the highest window in the base layer of it's target. - if (mImeTarget != null) { - if (mImeTarget.mAppToken == null) { - // For non-app ime targets adjust the layer we start from to match what we found - // when assigning layers. Otherwise, just use the highest app layer we have some far. - layer = mHighestLayerInImeTargetBaseLayer + WINDOW_LAYER_MULTIPLIER; - } - - while (!mInputMethodWindows.isEmpty()) { - layer = assignAndIncreaseLayerIfNeeded(mInputMethodWindows.remove(), layer); - } - - // Adjust app windows the should be displayed above the IME since they are above the IME - // target. - while (!mAboveImeTargetAppWindows.isEmpty()) { - layer = assignAndIncreaseLayerIfNeeded(mAboveImeTargetAppWindows.remove(), layer); - } - } - - } - - private int assignAndIncreaseLayerIfNeeded(WindowState win, int layer) { - if (win != null) { - assignAnimLayer(win, layer); - // Make sure we leave space in-between normal windows for dims and such. - layer += WINDOW_LAYER_MULTIPLIER; - } - return layer; - } - - private void assignAnimLayer(WindowState w, int layer) { - w.mLayer = layer; - w.mWinAnimator.mAnimLayer = w.getAnimLayerAdjustment() - + w.getSpecialWindowAnimLayerAdjustment(); - if (w.mAppToken != null) { - w.mAppToken.mAppAnimator.updateThumbnailLayer(); - } - } -} diff --git a/android/view/WindowManagerInternal.java b/com/android/server/wm/WindowManagerInternal.java index cd1b1908..036f7b0a 100644 --- a/android/view/WindowManagerInternal.java +++ b/com/android/server/wm/WindowManagerInternal.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.view; +package com.android.server.wm; import android.annotation.NonNull; import android.annotation.Nullable; @@ -23,8 +23,14 @@ import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManagerInternal; import android.os.IBinder; +import android.view.IInputFilter; +import android.view.IWindow; +import android.view.MagnificationSpec; +import android.view.WindowInfo; import android.view.animation.Animation; +import com.android.server.policy.WindowManagerPolicy; + import java.util.List; /** @@ -381,4 +387,9 @@ public abstract class WindowManagerInternal { * Sets callback to DragDropController. */ public abstract void registerDragDropControllerCallback(IDragDropCallback callback); + + /** + * @see android.view.IWindowManager#lockNow + */ + public abstract void lockNow(); } diff --git a/com/android/server/wm/WindowManagerService.java b/com/android/server/wm/WindowManagerService.java index ce343061..82f842c0 100644 --- a/com/android/server/wm/WindowManagerService.java +++ b/com/android/server/wm/WindowManagerService.java @@ -65,10 +65,10 @@ import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.LockGuard.INDEX_WINDOW; import static com.android.server.LockGuard.installLock; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.AppTransition.TRANSIT_UNSET; import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END; import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START; @@ -126,7 +126,6 @@ import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IAssistDataReceiver; import android.content.BroadcastReceiver; -import android.content.ClipData; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -219,10 +218,7 @@ import android.view.WindowContentFrameStats; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.WindowManagerGlobal; -import android.view.WindowManagerInternal; -import android.view.WindowManagerPolicy; -import android.view.WindowManagerPolicy.PointerEventListener; -import android.view.WindowManagerPolicy.ScreenOffListener; +import android.view.WindowManagerPolicyConstants.PointerEventListener; import android.view.animation.Animation; import android.view.inputmethod.InputMethodManagerInternal; @@ -245,7 +241,8 @@ import com.android.server.LocalServices; import com.android.server.UiThread; import com.android.server.Watchdog; import com.android.server.input.InputManagerService; -import android.view.DisplayFrames; +import com.android.server.policy.WindowManagerPolicy; +import com.android.server.policy.WindowManagerPolicy.ScreenOffListener; import com.android.server.power.ShutdownThread; import com.android.server.utils.PriorityDump; @@ -529,7 +526,6 @@ public class WindowManagerService extends IWindowManager.Stub AccessibilityController mAccessibilityController; - final SurfaceSession mFxSession; Watermark mWatermark; StrictModeFlash mStrictModeFlash; CircularDisplayMask mCircularDisplayMask; @@ -804,6 +800,13 @@ public class WindowManagerService extends IWindowManager.Stub static WindowManagerThreadPriorityBooster sThreadPriorityBooster = new WindowManagerThreadPriorityBooster(); + class DefaultSurfaceBuilderFactory implements SurfaceBuilderFactory { + public SurfaceControl.Builder make(SurfaceSession s) { + return new SurfaceControl.Builder(s); + } + }; + SurfaceBuilderFactory mSurfaceBuilderFactory = new DefaultSurfaceBuilderFactory(); + static void boostPriorityForLockedSection() { sThreadPriorityBooster.boost(); } @@ -992,7 +995,6 @@ public class WindowManagerService extends IWindowManager.Stub mPointerEventDispatcher = null; } - mFxSession = new SurfaceSession(); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); mDisplays = mDisplayManager.getDisplays(); for (Display display : mDisplays) { @@ -2478,11 +2480,8 @@ public class WindowManagerService extends IWindowManager.Stub mWaitingForConfig = true; displayContent.setLayoutNeeded(); int anim[] = new int[2]; - if (displayContent.isDimming()) { - anim[0] = anim[1] = 0; - } else { - mPolicy.selectRotationAnimationLw(anim); - } + mPolicy.selectRotationAnimationLw(anim); + startFreezingDisplayLocked(false, anim[0], anim[1], displayContent); config = new Configuration(mTempConfiguration); } @@ -3592,8 +3591,7 @@ public class WindowManagerService extends IWindowManager.Stub com.android.internal.R.dimen.circular_display_mask_thickness); mCircularDisplayMask = new CircularDisplayMask( - getDefaultDisplayContentLocked().getDisplay(), - mFxSession, + getDefaultDisplayContentLocked(), mPolicy.getWindowLayerFromTypeLw( WindowManager.LayoutParams.TYPE_POINTER) * TYPE_LAYER_MULTIPLIER + 10, screenOffset, maskThickness); @@ -3621,8 +3619,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mEmulatorDisplayOverlay == null) { mEmulatorDisplayOverlay = new EmulatorDisplayOverlay( mContext, - getDefaultDisplayContentLocked().getDisplay(), - mFxSession, + getDefaultDisplayContentLocked(), mPolicy.getWindowLayerFromTypeLw( WindowManager.LayoutParams.TYPE_POINTER) * TYPE_LAYER_MULTIPLIER + 10); @@ -3670,7 +3667,7 @@ public class WindowManagerService extends IWindowManager.Stub // TODO(multi-display): support multiple displays if (mStrictModeFlash == null) { mStrictModeFlash = new StrictModeFlash( - getDefaultDisplayContentLocked().getDisplay(), mFxSession); + getDefaultDisplayContentLocked()); } mStrictModeFlash.setVisibility(on); } finally { @@ -3694,9 +3691,8 @@ public class WindowManagerService extends IWindowManager.Stub } try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper"); - return screenshotApplications(null /* appToken */, DEFAULT_DISPLAY, -1 /* width */, - -1 /* height */, true /* includeFullDisplay */, 1f /* frameScale */, - Bitmap.Config.ARGB_8888, true /* wallpaperOnly */, false /* includeDecor */); + return screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888, + true /* wallpaperOnly */); } finally { Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @@ -3715,10 +3711,8 @@ public class WindowManagerService extends IWindowManager.Stub } FgThread.getHandler().post(() -> { - Bitmap bm = screenshotApplications(null /* appToken */, DEFAULT_DISPLAY, - -1 /* width */, -1 /* height */, true /* includeFullDisplay */, - 1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */, - false /* includeDecor */); + Bitmap bm = screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888, + false /* wallpaperOnly */); try { receiver.onHandleAssistScreenshot(bm); } catch (RemoteException e) { @@ -3752,29 +3746,21 @@ public class WindowManagerService extends IWindowManager.Stub * In portrait mode, it grabs the full screenshot. * * @param displayId the Display to take a screenshot of. - * @param width the width of the target bitmap - * @param height the height of the target bitmap - * @param includeFullDisplay true if the screen should not be cropped before capture - * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1 * @param config of the output bitmap * @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot - * @param includeDecor whether to include window decors, like the status or navigation bar - * background of the window */ - private Bitmap screenshotApplications(IBinder appToken, int displayId, int width, - int height, boolean includeFullDisplay, float frameScale, Bitmap.Config config, - boolean wallpaperOnly, boolean includeDecor) { + private Bitmap screenshotApplications(int displayId, Bitmap.Config config, + boolean wallpaperOnly) { final DisplayContent displayContent; synchronized(mWindowMap) { displayContent = mRoot.getDisplayContentOrCreate(displayId); if (displayContent == null) { - if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken - + ": returning null. No Display for displayId=" + displayId); + if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot returning null. No Display for " + + "displayId=" + displayId); return null; } } - return displayContent.screenshotApplications(appToken, width, height, - includeFullDisplay, frameScale, config, wallpaperOnly, includeDecor); + return displayContent.screenshotDisplay(config, wallpaperOnly); } /** @@ -4522,7 +4508,7 @@ public class WindowManagerService extends IWindowManager.Stub Display display = displayContent.getDisplay(); mTaskPositioner = new TaskPositioner(this); - mTaskPositioner.register(display); + mTaskPositioner.register(displayContent); mInputMonitor.updateInputWindowsLw(true /*force*/); // We need to grab the touch focus so that the touch events during the @@ -5519,7 +5505,7 @@ public class WindowManagerService extends IWindowManager.Stub } void reconfigureDisplayLocked(@NonNull DisplayContent displayContent) { - if (!mDisplayReady) { + if (!displayContent.isReady()) { return; } displayContent.configureDisplayPolicy(); @@ -5892,7 +5878,7 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.updateDisplayInfo(); screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent, - mFxSession, inTransaction, mPolicy.isDefaultOrientationForced(), isSecure, + inTransaction, mPolicy.isDefaultOrientationForced(), isSecure, this); mAnimator.setScreenRotationAnimationLocked(mFrozenDisplayId, screenRotationAnimation); @@ -5952,11 +5938,10 @@ public class WindowManagerService extends IWindowManager.Stub // TODO(multidisplay): rotation on main screen only. DisplayInfo displayInfo = displayContent.getDisplayInfo(); // Get rotation animation again, with new top window - boolean isDimming = displayContent.isDimming(); - if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, isDimming)) { + if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, false)) { mExitAnimId = mEnterAnimId = 0; } - if (screenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION, + if (screenRotationAnimation.dismiss(MAX_ANIMATION_DURATION, getTransitionAnimationScaleLocked(), displayInfo.logicalWidth, displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) { scheduleAnimationLocked(); @@ -6040,8 +6025,8 @@ public class WindowManagerService extends IWindowManager.Stub if (toks != null && toks.length > 0) { // TODO(multi-display): Show watermarks on secondary displays. final DisplayContent displayContent = getDefaultDisplayContentLocked(); - mWatermark = new Watermark(displayContent.getDisplay(), - displayContent.mRealDisplayMetrics, mFxSession, toks); + mWatermark = new Watermark(displayContent, displayContent.mRealDisplayMetrics, + toks); } } } catch (FileNotFoundException e) { @@ -7456,6 +7441,11 @@ public class WindowManagerService extends IWindowManager.Stub public void registerDragDropControllerCallback(IDragDropCallback callback) { mDragDropController.registerCallback(callback); } + + @Override + public void lockNow() { + WindowManagerService.this.lockNow(null); + } } void registerAppFreezeListener(AppFreezeListener listener) { @@ -7557,4 +7547,13 @@ public class WindowManagerService extends IWindowManager.Stub w.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows); }, false /* traverseTopToBottom */); } + + public void applyMagnificationSpec(MagnificationSpec spec) { + getDefaultDisplayContentLocked().applyMagnificationSpec(spec); + } + + SurfaceControl.Builder makeSurfaceBuilder(SurfaceSession s) { + return mSurfaceBuilderFactory.make(s); + } } + diff --git a/com/android/server/wm/WindowManagerShellCommand.java b/com/android/server/wm/WindowManagerShellCommand.java index 4b98d9d9..23586955 100644 --- a/com/android/server/wm/WindowManagerShellCommand.java +++ b/com/android/server/wm/WindowManagerShellCommand.java @@ -16,6 +16,8 @@ package com.android.server.wm; +import static android.os.Build.IS_USER; + import android.os.ShellCommand; import java.io.PrintWriter; @@ -50,8 +52,10 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" help"); pw.println(" Print this help text."); pw.println(); - pw.println(" tracing (start | stop)"); - pw.println(" start or stop window tracing"); - pw.println(); + if (!IS_USER){ + pw.println(" tracing (start | stop)"); + pw.println(" start or stop window tracing"); + pw.println(); + } } } diff --git a/com/android/server/wm/WindowState.java b/com/android/server/wm/WindowState.java index 6b1932d7..c2ac9052 100644 --- a/com/android/server/wm/WindowState.java +++ b/com/android/server/wm/WindowState.java @@ -23,6 +23,7 @@ import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_ import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE; +import static android.view.SurfaceControl.Transaction; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON; @@ -41,6 +42,7 @@ import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH; @@ -54,6 +56,10 @@ import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType; @@ -61,10 +67,10 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM; import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; -import static android.view.WindowManagerPolicy.TRANSIT_ENTER; -import static android.view.WindowManagerPolicy.TRANSIT_EXIT; -import static android.view.WindowManagerPolicy.TRANSIT_PREVIEW_DONE; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER; +import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT; +import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; @@ -147,14 +153,16 @@ import android.view.IWindowId; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventReceiver; +import android.view.SurfaceControl; +import android.view.SurfaceSession; import android.view.View; import android.view.ViewTreeObserver; import android.view.WindowInfo; import android.view.WindowManager; -import android.view.WindowManagerPolicy; import com.android.internal.util.ToBooleanFunction; import com.android.server.input.InputWindowHandle; +import com.android.server.policy.WindowManagerPolicy; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -602,6 +610,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP }; }; + /** + * Indicates whether we have requested a Dim (in the sense of {@link Dimmer}) from our host + * container. + */ + private boolean mIsDimming = false; + + private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f; + WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a, int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) { @@ -799,7 +815,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP layoutXDiff = 0; layoutYDiff = 0; } else { - getContainerBounds(mContainingFrame); + getBounds(mContainingFrame); if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) { // If the bounds are frozen, we still want to translate the window freely and only @@ -957,7 +973,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mContentInsets.setEmpty(); mVisibleInsets.setEmpty(); } else { - getDisplayContent().getLogicalDisplayRect(mTmpRect); + getDisplayContent().getBounds(mTmpRect); // Override right and/or bottom insets in case if the frame doesn't fit the screen in // non-fullscreen mode. boolean overrideRightInset = !windowsAreFloating && !inFullscreenContainer @@ -1029,6 +1045,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + " of=" + mOutsets.toShortString()); } + // TODO: Look into whether this override is still necessary. + @Override + public Rect getBounds() { + if (isInMultiWindowMode()) { + return getTask().getBounds(); + } else if (mAppToken != null){ + return mAppToken.getBounds(); + } else { + return super.getBounds(); + } + } + @Override public Rect getFrameLw() { return mFrame; @@ -2034,23 +2062,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return isVisibleOrAdding(); } - void scheduleAnimationIfDimming() { - final DisplayContent dc = getDisplayContent(); - if (dc == null) { - return; - } - - // If layout is currently deferred, we want to hold of with updating the layers. - if (mService.mWindowPlacerLocked.isLayoutDeferred()) { - return; - } - final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser(); - if (dimLayerUser != null && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator)) { - // Force an animation pass just to update the mDimLayer layer. - mService.scheduleAnimationLocked(); - } - } - private final class DeadWindowEventReceiver extends InputEventReceiver { DeadWindowEventReceiver(InputChannel inputChannel) { super(inputChannel, mService.mH.getLooper()); @@ -2106,31 +2117,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mInputWindowHandle.inputChannel = null; } - void applyDimLayerIfNeeded() { - // When the app is terminated (eg. from Recents), the task might have already been - // removed with the window pending removal. Don't apply dim in such cases, as there - // will be no more updateDimLayer() calls, which leaves the dimlayer invalid. - final AppWindowToken token = mAppToken; - if (token != null && token.removed) { - return; - } - - final DisplayContent dc = getDisplayContent(); - if (!mAnimatingExit && mAppDied) { - // If app died visible, apply a dim over the window to indicate that it's inactive - dc.mDimLayerController.applyDimAbove(getDimLayerUser(), mWinAnimator); - } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0 - && dc != null && !mAnimatingExit && isVisible()) { - dc.mDimLayerController.applyDimBehind(getDimLayerUser(), mWinAnimator); - } - } - - private DimLayer.DimLayerUser getDimLayerUser() { + private Dimmer getDimmer() { Task task = getTask(); if (task != null) { - return task; + return task.getDimmer(); + } + TaskStack taskStack = getStack(); + if (taskStack != null) { + return taskStack.getDimmer(); } - return getStack(); + return null; } /** Returns true if the replacement window was removed. */ @@ -2152,9 +2148,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private void removeReplacedWindow() { if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + this); - if (isDimming()) { - transferDimToReplacement(); - } mWillReplaceWindow = false; mAnimateReplacingWindow = false; mReplacingRemoveRequested = false; @@ -2217,11 +2210,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // need to intercept touches outside of that window. The dim layer user // associated with the window (task or stack) will give us the good bounds, as // they would be used to display the dim layer. - final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser(); - if (dimLayerUser != null) { - dimLayerUser.getDimBounds(mTmpRect); + final Task task = getTask(); + if (task != null) { + task.getDimBounds(mTmpRect); } else { - getVisibleBounds(mTmpRect); + getStack().getDimBounds(mTmpRect); } if (inFreeformWindowingMode()) { // For freeform windows we the touch region to include the whole surface for the @@ -2465,7 +2458,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mPolicyVisibility = true; mPolicyVisibilityAfterAnim = true; if (doAnimation) { - mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_ENTER, true); + mWinAnimator.applyAnimationLocked(TRANSIT_ENTER, true); } if (requestAnim) { mService.scheduleAnimationLocked(); @@ -2493,7 +2486,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return false; } if (doAnimation) { - mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT, false); + mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false); if (mWinAnimator.mAnimation == null) { doAnimation = false; } @@ -2729,14 +2722,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return displayContent.isDefaultDisplay; } - @Override - public boolean isDimming() { - final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser(); - final DisplayContent dc = getDisplayContent(); - return dimLayerUser != null && dc != null - && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator); - } - void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) { mShowToOwnerOnly = showToOwnerOnly; } @@ -2979,33 +2964,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP /** Is this window in a container that takes up the entire screen space? */ private boolean inFullscreenContainer() { - if (mAppToken == null) { - return true; - } - if (mAppToken.hasBounds()) { - return false; - } - return !isInMultiWindowMode(); + return mAppToken == null || (mAppToken.matchParentBounds() && !isInMultiWindowMode()); } /** @return true when the window is in fullscreen task, but has non-fullscreen bounds set. */ boolean isLetterboxedAppWindow() { - final Task task = getTask(); - final boolean taskIsFullscreen = task != null && task.isFullscreen(); - final boolean appWindowIsFullscreen = mAppToken != null && !mAppToken.hasBounds(); - - return taskIsFullscreen && !appWindowIsFullscreen; - } - - /** Returns the appropriate bounds to use for computing frames. */ - private void getContainerBounds(Rect outBounds) { - if (isInMultiWindowMode()) { - getTask().getBounds(outBounds); - } else if (mAppToken != null){ - mAppToken.getBounds(outBounds); - } else { - outBounds.setEmpty(); - } + return !isInMultiWindowMode() && mAppToken != null && !mAppToken.matchParentBounds(); } boolean isDragResizeChanged() { @@ -3071,7 +3035,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (task == null) { return false; } - if (!inSplitScreenWindowingMode()) { + if (!inSplitScreenWindowingMode() && !inFreeformWindowingMode()) { return false; } if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) { @@ -3591,15 +3555,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return winY; } - private void transferDimToReplacement() { - final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser(); - final DisplayContent dc = getDisplayContent(); - if (dimLayerUser != null && dc != null) { - dc.mDimLayerController.applyDim(dimLayerUser, - mReplacementWindow.mWinAnimator, (mAttrs.flags & FLAG_DIM_BEHIND) != 0); - } - } - // During activity relaunch due to resize, we sometimes use window replacement // for only child windows (as the main window is handled by window preservation) // and the big surface. @@ -4156,9 +4111,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP policyCrop.intersect(-mCompatFrame.left, -mCompatFrame.top, displayInfo.logicalWidth - mCompatFrame.left, displayInfo.logicalHeight - mCompatFrame.top); - } else if (mLayer >= mService.mSystemDecorLayer) { - // Above the decor layer is easy, just use the entire window - policyCrop.set(0, 0, mCompatFrame.width(), mCompatFrame.height()); } else if (mDecorFrame.isEmpty()) { // Windows without policy decor aren't cropped. policyCrop.set(0, 0, mCompatFrame.width(), mCompatFrame.height()); @@ -4388,4 +4340,78 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return false; } } + + @Override + boolean shouldMagnify() { + if (mAttrs.type == TYPE_INPUT_METHOD || + mAttrs.type == TYPE_INPUT_METHOD_DIALOG || + mAttrs.type == TYPE_MAGNIFICATION_OVERLAY || + mAttrs.type == TYPE_NAVIGATION_BAR || + // It's tempting to wonder: Have we forgotten the rounded corners overlay? + // worry not: it's a fake TYPE_NAVIGATION_BAR_PANEL + mAttrs.type == TYPE_NAVIGATION_BAR_PANEL || + mAttrs.type == TYPE_STATUS_BAR) { + return false; + } + return true; + } + + @Override + SurfaceSession getSession() { + if (mSession.mSurfaceSession != null) { + return mSession.mSurfaceSession; + } else { + return getParent().getSession(); + } + } + + @Override + boolean needsZBoost() { + return getAnimLayerAdjustment() > 0 || mWillReplaceWindow; + } + + @Override + SurfaceControl.Builder makeSurface() { + return getParent().makeChildSurface(this); + } + + private void applyDims(Dimmer dimmer) { + if (!mAnimatingExit && mAppDied) { + mIsDimming = true; + getDimmer().dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW); + } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0 + && !mAnimatingExit && isVisible()) { + mIsDimming = true; + getDimmer().dimBelow(getPendingTransaction(), this, mAttrs.dimAmount); + } + } + + @Override + void prepareSurfaces() { + final Dimmer dimmer = getDimmer(); + mIsDimming = false; + if (dimmer != null) { + applyDims(dimmer); + } + + mWinAnimator.prepareSurfaceLocked(true); + super.prepareSurfaces(); + } + + @Override + void assignLayer(Transaction t, int layer) { + // See comment in assignRelativeLayerForImeTargetChild + if (!isChildWindow() + || (mService.mInputMethodTarget != getParentWindow()) + || !inSplitScreenWindowingMode()) { + super.assignLayer(t, layer); + return; + } + getDisplayContent().assignRelativeLayerForImeTargetChild(t, this); + } + + @Override + public boolean isDimming() { + return mIsDimming; + } } diff --git a/com/android/server/wm/WindowStateAnimator.java b/com/android/server/wm/WindowStateAnimator.java index 86397aea..4b5661f5 100644 --- a/com/android/server/wm/WindowStateAnimator.java +++ b/com/android/server/wm/WindowStateAnimator.java @@ -60,17 +60,17 @@ import android.os.Trace; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; -import android.view.MagnificationSpec; import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; -import android.view.WindowManagerPolicy; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; +import com.android.server.policy.WindowManagerPolicy; + import java.io.PrintWriter; import java.io.FileDescriptor; @@ -559,7 +559,10 @@ class WindowStateAnimator { } if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin, "SET FREEZE LAYER", false); if (mSurfaceController != null) { - mSurfaceController.setLayer(mAnimLayer + 1); + // Our SurfaceControl is always at layer 0 within the parent Surface managed by + // window-state. We want this old Surface to stay on top of the new one + // until we do the swap, so we place it at layer 1. + mSurfaceController.mSurfaceControl.setLayer(1); } mDestroyPreservedSurfaceUponRedraw = true; mSurfaceDestroyDeferred = true; @@ -730,7 +733,6 @@ class WindowStateAnimator { try { mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top, false); mSurfaceController.setLayerStackInTransaction(getLayerStack()); - mSurfaceController.setLayer(mAnimLayer); } finally { mService.closeSurfaceTransaction("createSurfaceLocked"); } @@ -867,29 +869,11 @@ class WindowStateAnimator { mPendingDestroySurface = null; } - void applyMagnificationSpec(MagnificationSpec spec, Matrix transform) { - final int surfaceInsetLeft = mWin.mAttrs.surfaceInsets.left; - final int surfaceInsetTop = mWin.mAttrs.surfaceInsets.top; - - if (spec != null && !spec.isNop()) { - float scale = spec.scale; - transform.postScale(scale, scale); - transform.postTranslate(spec.offsetX, spec.offsetY); - - // As we are scaling the whole surface, to keep the content - // in the same position we will also have to scale the surfaceInsets. - transform.postTranslate(-(surfaceInsetLeft*scale - surfaceInsetLeft), - -(surfaceInsetTop*scale - surfaceInsetTop)); - } - } - void computeShownFrameLocked() { final boolean selfTransformation = mHasLocalTransformation; - Transformation attachedTransformation = - (mParentWinAnimator != null && mParentWinAnimator.mHasLocalTransformation) - ? mParentWinAnimator.mTransformation : null; Transformation appTransformation = (mAppAnimator != null && mAppAnimator.hasTransformation) ? mAppAnimator.transformation : null; + Transformation wallpaperTargetTransformation = null; // Wallpapers are animated based on the "real" window they // are currently targeting. @@ -899,9 +883,9 @@ class WindowStateAnimator { if (wallpaperAnimator.mHasLocalTransformation && wallpaperAnimator.mAnimation != null && !wallpaperAnimator.mAnimation.getDetachWallpaper()) { - attachedTransformation = wallpaperAnimator.mTransformation; - if (DEBUG_WALLPAPER && attachedTransformation != null) { - Slog.v(TAG, "WP target attached xform: " + attachedTransformation); + wallpaperTargetTransformation = wallpaperAnimator.mTransformation; + if (DEBUG_WALLPAPER && wallpaperTargetTransformation != null) { + Slog.v(TAG, "WP target attached xform: " + wallpaperTargetTransformation); } } final AppWindowAnimator wpAppAnimator = wallpaperTarget.mAppToken == null ? @@ -923,7 +907,7 @@ class WindowStateAnimator { screenRotationAnimation != null && screenRotationAnimation.isAnimating(); mHasClipRect = false; - if (selfTransformation || attachedTransformation != null + if (selfTransformation || wallpaperTargetTransformation != null || appTransformation != null || screenAnimation) { // cache often used attributes locally final Rect frame = mWin.mFrame; @@ -953,27 +937,31 @@ class WindowStateAnimator { if (selfTransformation) { tmpMatrix.postConcat(mTransformation.getMatrix()); } - if (attachedTransformation != null) { - tmpMatrix.postConcat(attachedTransformation.getMatrix()); + + if (wallpaperTargetTransformation != null) { + tmpMatrix.postConcat(wallpaperTargetTransformation.getMatrix()); } if (appTransformation != null) { tmpMatrix.postConcat(appTransformation.getMatrix()); } + int left = frame.left; + int top = frame.top; + if (mWin.isChildWindow()) { + WindowState parent = mWin.getParentWindow(); + left -= parent.mFrame.left; + top -= parent.mFrame.top; + } + // The translation that applies the position of the window needs to be applied at the // end in case that other translations include scaling. Otherwise the scaling will // affect this translation. But it needs to be set before the screen rotation animation // so the pivot point is at the center of the screen for all windows. - tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset); + tmpMatrix.postTranslate(left + mWin.mXOffset, top + mWin.mYOffset); if (screenAnimation) { tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix()); } - MagnificationSpec spec = getMagnificationSpec(); - if (spec != null) { - applyMagnificationSpec(spec, tmpMatrix); - } - // "convert" it into SurfaceFlinger's format // (a 2x2 matrix + an offset) // Here we must not transform the position of the surface @@ -1004,8 +992,8 @@ class WindowStateAnimator { if (selfTransformation) { mShownAlpha *= mTransformation.getAlpha(); } - if (attachedTransformation != null) { - mShownAlpha *= attachedTransformation.getAlpha(); + if (wallpaperTargetTransformation != null) { + mShownAlpha *= wallpaperTargetTransformation.getAlpha(); } if (appTransformation != null) { mShownAlpha *= appTransformation.getAlpha(); @@ -1036,8 +1024,8 @@ class WindowStateAnimator { && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v( TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha + " self=" + (selfTransformation ? mTransformation.getAlpha() : "null") - + " attached=" + (attachedTransformation == null ? - "null" : attachedTransformation.getAlpha()) + + " attached=" + (wallpaperTargetTransformation == null ? + "null" : wallpaperTargetTransformation.getAlpha()) + " app=" + (appTransformation == null ? "null" : appTransformation.getAlpha()) + " screen=" + (screenAnimation ? screenRotationAnimation.getEnterTransformation().getAlpha() : "null")); @@ -1057,49 +1045,16 @@ class WindowStateAnimator { TAG, "computeShownFrameLocked: " + this + " not attached, mAlpha=" + mAlpha); - MagnificationSpec spec = getMagnificationSpec(); - if (spec != null) { - final Rect frame = mWin.mFrame; - final float tmpFloats[] = mService.mTmpFloats; - final Matrix tmpMatrix = mWin.mTmpMatrix; - - tmpMatrix.setScale(mWin.mGlobalScale, mWin.mGlobalScale); - tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset); - - applyMagnificationSpec(spec, tmpMatrix); - - tmpMatrix.getValues(tmpFloats); - - mHaveMatrix = true; - mDsDx = tmpFloats[Matrix.MSCALE_X]; - mDtDx = tmpFloats[Matrix.MSKEW_Y]; - mDtDy = tmpFloats[Matrix.MSKEW_X]; - mDsDy = tmpFloats[Matrix.MSCALE_Y]; - float x = tmpFloats[Matrix.MTRANS_X]; - float y = tmpFloats[Matrix.MTRANS_Y]; - mWin.mShownPosition.set(Math.round(x), Math.round(y)); - - mShownAlpha = mAlpha; - } else { - mWin.mShownPosition.set(mWin.mFrame.left, mWin.mFrame.top); - if (mWin.mXOffset != 0 || mWin.mYOffset != 0) { - mWin.mShownPosition.offset(mWin.mXOffset, mWin.mYOffset); - } - mShownAlpha = mAlpha; - mHaveMatrix = false; - mDsDx = mWin.mGlobalScale; - mDtDx = 0; - mDtDy = 0; - mDsDy = mWin.mGlobalScale; + mWin.mShownPosition.set(mWin.mFrame.left, mWin.mFrame.top); + if (mWin.mXOffset != 0 || mWin.mYOffset != 0) { + mWin.mShownPosition.offset(mWin.mXOffset, mWin.mYOffset); } - } - - private MagnificationSpec getMagnificationSpec() { - //TODO (multidisplay): Magnification is supported only for the default display. - if (mService.mAccessibilityController != null && mWin.getDisplayId() == DEFAULT_DISPLAY) { - return mService.mAccessibilityController.getMagnificationSpecForWindowLocked(mWin); - } - return null; + mShownAlpha = mAlpha; + mHaveMatrix = false; + mDsDx = mWin.mGlobalScale; + mDtDx = 0; + mDtDy = 0; + mDsDy = mWin.mGlobalScale; } /** @@ -1140,26 +1095,6 @@ class WindowStateAnimator { w.expandForSurfaceInsets(finalClipRect); } - // We may be applying a magnification spec to all windows, - // simulating a transformation in screen space, in which case - // we need to transform all other screen space values...including - // the final crop. This is kind of messed up and we should look - // in to actually transforming screen-space via a parent-layer. - // b/38322835 - MagnificationSpec spec = getMagnificationSpec(); - if (spec != null && !spec.isNop()) { - Matrix transform = mWin.mTmpMatrix; - RectF finalCrop = mService.mTmpRectF; - transform.reset(); - transform.postScale(spec.scale, spec.scale); - transform.postTranslate(-spec.offsetX, -spec.offsetY); - transform.mapRect(finalCrop); - finalClipRect.top = (int) finalCrop.top; - finalClipRect.left = (int) finalCrop.left; - finalClipRect.right = (int) finalCrop.right; - finalClipRect.bottom = (int) finalCrop.bottom; - } - return true; } @@ -1517,7 +1452,6 @@ class WindowStateAnimator { mReportSurfaceResized = true; mAnimator.setPendingLayoutChanges(w.getDisplayId(), WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); - w.applyDimLayerIfNeeded(); } } @@ -1615,7 +1549,6 @@ class WindowStateAnimator { mDtDy * w.mHScale * mExtraHScale, mDsDy * w.mVScale * mExtraVScale, recoveringMemory); - mSurfaceController.setLayer(mAnimLayer); if (prepared && mDrawState == HAS_DRAWN) { if (mLastHidden) { @@ -2045,7 +1978,7 @@ class WindowStateAnimator { final float width = w.mFrame.width(); final float height = w.mFrame.height(); - mService.getDefaultDisplayContentLocked().getLogicalDisplayRect(displayRect); + mService.getDefaultDisplayContentLocked().getBounds(displayRect); final float displayWidth = displayRect.width(); final float displayHeight = displayRect.height(); diff --git a/com/android/server/wm/WindowSurfaceController.java b/com/android/server/wm/WindowSurfaceController.java index a2145230..6746754b 100644 --- a/com/android/server/wm/WindowSurfaceController.java +++ b/com/android/server/wm/WindowSurfaceController.java @@ -53,7 +53,7 @@ class WindowSurfaceController { final WindowStateAnimator mAnimator; - private SurfaceControlWithBackground mSurfaceControl; + SurfaceControlWithBackground mSurfaceControl; // Should only be set from within setShown(). private boolean mSurfaceShown = false; @@ -101,7 +101,8 @@ class WindowSurfaceController { mWindowSession = win.mSession; Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl"); - final SurfaceControl.Builder b = new SurfaceControl.Builder(s) + final SurfaceControl.Builder b = win.makeSurface() + .setParent(win.getSurfaceControl()) .setName(name) .setSize(w, h) .setFormat(format) @@ -245,25 +246,6 @@ class WindowSurfaceController { } } - void setLayer(int layer) { - if (mSurfaceControl != null) { - mService.openSurfaceTransaction(); - try { - if (mAnimator.mWin.usesRelativeZOrdering()) { - mSurfaceControl.setRelativeLayer( - mAnimator.mWin.getParentWindow() - .mWinAnimator.mSurfaceController.mSurfaceControl, - -1); - } else { - mSurfaceLayer = layer; - mSurfaceControl.setLayer(layer); - } - } finally { - mService.closeSurfaceTransaction("setLayer"); - } - } - } - void setLayerStackInTransaction(int layerStack) { if (mSurfaceControl != null) { mSurfaceControl.setLayerStack(layerStack); diff --git a/com/android/server/wm/WindowSurfacePlacer.java b/com/android/server/wm/WindowSurfacePlacer.java index cd5e4750..169d0a3a 100644 --- a/com/android/server/wm/WindowSurfacePlacer.java +++ b/com/android/server/wm/WindowSurfacePlacer.java @@ -20,8 +20,8 @@ import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.ActivityManagerInternal.APP_TRANSITION_SNAPSHOT; import static android.app.ActivityManagerInternal.APP_TRANSITION_SPLASH_SCREEN; import static android.app.ActivityManagerInternal.APP_TRANSITION_WINDOWS_DRAWN; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE; import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN; import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; @@ -704,7 +704,7 @@ class WindowSurfacePlacer { // Create a new surface for the thumbnail WindowState window = appToken.findMainWindow(); - final SurfaceControl surfaceControl = new SurfaceControl.Builder(mService.mFxSession) + final SurfaceControl surfaceControl = appToken.makeSurface() .setName("thumbnail anim") .setSize(dirty.width(), dirty.height()) .setFormat(PixelFormat.TRANSLUCENT) @@ -712,7 +712,6 @@ class WindowSurfacePlacer { window != null ? window.mOwnerUid : Binder.getCallingUid()) .build(); - surfaceControl.setLayerStack(display.getLayerStack()); if (SHOW_TRANSACTIONS) { Slog.i(TAG, " THUMBNAIL " + surfaceControl + ": CREATE"); } @@ -750,10 +749,13 @@ class WindowSurfacePlacer { anim.restrictDuration(MAX_ANIMATION_DURATION); anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked()); - openingAppAnimator.updateThumbnailLayer(); openingAppAnimator.thumbnail = surfaceControl; openingAppAnimator.thumbnailAnimation = anim; mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect); + + // We parent the thumbnail to the app token, and just place it + // on top of anything else in the app token. + surfaceControl.setLayer(Integer.MAX_VALUE); } catch (Surface.OutOfResourcesException e) { Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w=" + dirty.width() + " h=" + dirty.height(), e); diff --git a/com/android/server/wm/WindowTracing.java b/com/android/server/wm/WindowTracing.java index 5657f6c4..c858b198 100644 --- a/com/android/server/wm/WindowTracing.java +++ b/com/android/server/wm/WindowTracing.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.os.Build.IS_USER; import static com.android.server.wm.proto.WindowManagerTraceFileProto.ENTRY; import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER; import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER_H; @@ -62,11 +63,16 @@ class WindowTracing { } void startTrace(PrintWriter pw) throws IOException { + if (IS_USER){ + logAndPrintln(pw, "Error: Tracing is not supported on user builds."); + return; + } synchronized (mLock) { logAndPrintln(pw, "Start tracing to " + mTraceFile + "."); mWriteQueue.clear(); mTraceFile.delete(); try (OutputStream os = new FileOutputStream(mTraceFile)) { + mTraceFile.setReadable(true, false); ProtoOutputStream proto = new ProtoOutputStream(os); proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE); proto.flush(); @@ -82,6 +88,10 @@ class WindowTracing { } void stopTrace(PrintWriter pw) { + if (IS_USER){ + logAndPrintln(pw, "Error: Tracing is not supported on user builds."); + return; + } synchronized (mLock) { logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush."); mEnabled = mEnabledLockFree = false; @@ -147,9 +157,11 @@ class WindowTracing { } static WindowTracing createDefaultAndStartLooper(Context context) { - File file = new File("/data/system/window_trace.proto"); + File file = new File("/data/misc/wmtrace/wm_trace.pb"); WindowTracing windowTracing = new WindowTracing(file); - new Thread(windowTracing::loop, "window_tracing").start(); + if (!IS_USER){ + new Thread(windowTracing::loop, "window_tracing").start(); + } return windowTracing; } diff --git a/com/android/settingslib/Utils.java b/com/android/settingslib/Utils.java index 21861692..eb338427 100644 --- a/com/android/settingslib/Utils.java +++ b/com/android/settingslib/Utils.java @@ -105,7 +105,8 @@ public class Utils { } } return new UserIconDrawable(iconSize).setIconDrawable( - UserIcons.getDefaultUserIcon(user.id, /* light= */ false)).bake(); + UserIcons.getDefaultUserIcon(context.getResources(), user.id, /* light= */ false)) + .bake(); } /** Formats a double from 0.0..100.0 with an option to round **/ diff --git a/com/android/settingslib/drawer/CategoryManager.java b/com/android/settingslib/drawer/CategoryManager.java index ee7885d2..07033304 100644 --- a/com/android/settingslib/drawer/CategoryManager.java +++ b/com/android/settingslib/drawer/CategoryManager.java @@ -18,7 +18,6 @@ package com.android.settingslib.drawer; import android.content.ComponentName; import android.content.Context; import android.support.annotation.VisibleForTesting; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -27,7 +26,6 @@ import android.util.Pair; import com.android.settingslib.applications.InterestingConfigChanges; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -104,10 +102,10 @@ public class CategoryManager { } for (int i = 0; i < mCategories.size(); i++) { DashboardCategory category = mCategories.get(i); - for (int j = 0; j < category.tiles.size(); j++) { - Tile tile = category.tiles.get(j); + for (int j = 0; j < category.getTilesCount(); j++) { + Tile tile = category.getTile(j); if (tileBlacklist.contains(tile.intent.getComponent())) { - category.tiles.remove(j--); + category.removeTile(j--); } } } @@ -181,7 +179,7 @@ public class CategoryManager { newCategory = new DashboardCategory(); categoryByKeyMap.put(newCategoryKey, newCategory); } - newCategory.tiles.add(tile); + newCategory.addTile(tile); } } } @@ -198,7 +196,7 @@ public class CategoryManager { synchronized void sortCategories(Context context, Map<String, DashboardCategory> categoryByKeyMap) { for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) { - sortCategoriesForExternalTiles(context, categoryEntry.getValue()); + categoryEntry.getValue().sortTiles(context.getPackageName()); } } @@ -210,16 +208,16 @@ public class CategoryManager { synchronized void filterDuplicateTiles(Map<String, DashboardCategory> categoryByKeyMap) { for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) { final DashboardCategory category = categoryEntry.getValue(); - final int count = category.tiles.size(); + final int count = category.getTilesCount(); final Set<ComponentName> components = new ArraySet<>(); for (int i = count - 1; i >= 0; i--) { - final Tile tile = category.tiles.get(i); + final Tile tile = category.getTile(i); if (tile.intent == null) { continue; } final ComponentName tileComponent = tile.intent.getComponent(); if (components.contains(tileComponent)) { - category.tiles.remove(i); + category.removeTile(i); } else { components.add(tileComponent); } @@ -234,28 +232,7 @@ public class CategoryManager { */ private synchronized void sortCategoriesForExternalTiles(Context context, DashboardCategory dashboardCategory) { - final String skipPackageName = context.getPackageName(); + dashboardCategory.sortTiles(context.getPackageName()); - // Sort tiles based on [priority, package within priority] - Collections.sort(dashboardCategory.tiles, (tile1, tile2) -> { - final String package1 = tile1.intent.getComponent().getPackageName(); - final String package2 = tile2.intent.getComponent().getPackageName(); - final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2); - // First sort by priority - final int priorityCompare = tile2.priority - tile1.priority; - if (priorityCompare != 0) { - return priorityCompare; - } - // Then sort by package name, skip package take precedence - if (packageCompare != 0) { - if (TextUtils.equals(package1, skipPackageName)) { - return -1; - } - if (TextUtils.equals(package2, skipPackageName)) { - return 1; - } - } - return packageCompare; - }); } } diff --git a/com/android/settingslib/drawer/DashboardCategory.java b/com/android/settingslib/drawer/DashboardCategory.java index f6f81682..a966e824 100644 --- a/com/android/settingslib/drawer/DashboardCategory.java +++ b/com/android/settingslib/drawer/DashboardCategory.java @@ -16,6 +16,8 @@ package com.android.settingslib.drawer; +import static java.lang.String.CASE_INSENSITIVE_ORDER; + import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; @@ -23,6 +25,8 @@ import android.text.TextUtils; import android.util.Log; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; public class DashboardCategory implements Parcelable { @@ -48,39 +52,59 @@ public class DashboardCategory implements Parcelable { /** * List of the category's children */ - public List<Tile> tiles = new ArrayList<>(); - + private List<Tile> mTiles = new ArrayList<>(); + + DashboardCategory(DashboardCategory in) { + if (in != null) { + title = in.title; + key = in.key; + priority = in.priority; + for (Tile tile : in.mTiles) { + mTiles.add(tile); + } + } + } public DashboardCategory() { // Empty } + /** + * Get a copy of the list of the category's children. + * + * Note: the returned list serves as a read-only list. If tiles needs to be added or removed + * from the actual tiles list, it should be done through {@link #addTile}, {@link #removeTile}. + */ + public List<Tile> getTiles() { + return Collections.unmodifiableList(mTiles); + } + public void addTile(Tile tile) { - tiles.add(tile); + mTiles.add(tile); } public void addTile(int n, Tile tile) { - tiles.add(n, tile); + mTiles.add(n, tile); } public void removeTile(Tile tile) { - tiles.remove(tile); + mTiles.remove(tile); } public void removeTile(int n) { - tiles.remove(n); + mTiles.remove(n); } public int getTilesCount() { - return tiles.size(); + return mTiles.size(); } public Tile getTile(int n) { - return tiles.get(n); + return mTiles.get(n); } public boolean containsComponent(ComponentName component) { - for (Tile tile : tiles) { + for (Tile tile : mTiles) { if (TextUtils.equals(tile.intent.getComponent().getClassName(), component.getClassName())) { if (DEBUG) { @@ -95,6 +119,40 @@ public class DashboardCategory implements Parcelable { return false; } + /** + * Sort priority value for tiles in this category. + */ + public void sortTiles() { + Collections.sort(mTiles, TILE_COMPARATOR); + } + + /** + * Sort priority value and package name for tiles in this category. + */ + public void sortTiles(String skipPackageName) { + // Sort mTiles based on [priority, package within priority] + Collections.sort(mTiles, (tile1, tile2) -> { + final String package1 = tile1.intent.getComponent().getPackageName(); + final String package2 = tile2.intent.getComponent().getPackageName(); + final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2); + // First sort by priority + final int priorityCompare = tile2.priority - tile1.priority; + if (priorityCompare != 0) { + return priorityCompare; + } + // Then sort by package name, skip package take precedence + if (packageCompare != 0) { + if (TextUtils.equals(package1, skipPackageName)) { + return -1; + } + if (TextUtils.equals(package2, skipPackageName)) { + return 1; + } + } + return packageCompare; + }); + } + @Override public int describeContents() { return 0; @@ -106,11 +164,11 @@ public class DashboardCategory implements Parcelable { dest.writeString(key); dest.writeInt(priority); - final int count = tiles.size(); + final int count = mTiles.size(); dest.writeInt(count); for (int n = 0; n < count; n++) { - Tile tile = tiles.get(n); + Tile tile = mTiles.get(n); tile.writeToParcel(dest, flags); } } @@ -124,7 +182,7 @@ public class DashboardCategory implements Parcelable { for (int n = 0; n < count; n++) { Tile tile = Tile.CREATOR.createFromParcel(in); - tiles.add(tile); + mTiles.add(tile); } } @@ -141,4 +199,13 @@ public class DashboardCategory implements Parcelable { return new DashboardCategory[size]; } }; + + public static final Comparator<Tile> TILE_COMPARATOR = + new Comparator<Tile>() { + @Override + public int compare(Tile lhs, Tile rhs) { + return rhs.priority - lhs.priority; + } + }; + } diff --git a/com/android/settingslib/drawer/TileUtils.java b/com/android/settingslib/drawer/TileUtils.java index 038dcf84..e986e0f7 100644 --- a/com/android/settingslib/drawer/TileUtils.java +++ b/com/android/settingslib/drawer/TileUtils.java @@ -253,7 +253,7 @@ public class TileUtils { } ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values()); for (DashboardCategory category : categories) { - Collections.sort(category.tiles, TILE_COMPARATOR); + category.sortTiles(); } Collections.sort(categories, CATEGORY_COMPARATOR); if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took " @@ -595,14 +595,6 @@ public class TileUtils { return pathSegments.get(0); } - public static final Comparator<Tile> TILE_COMPARATOR = - new Comparator<Tile>() { - @Override - public int compare(Tile lhs, Tile rhs) { - return rhs.priority - lhs.priority; - } - }; - private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR = new Comparator<DashboardCategory>() { @Override diff --git a/com/android/settingslib/drawer/UserAdapter.java b/com/android/settingslib/drawer/UserAdapter.java index b27d8235..8a09df28 100644 --- a/com/android/settingslib/drawer/UserAdapter.java +++ b/com/android/settingslib/drawer/UserAdapter.java @@ -64,7 +64,8 @@ public class UserAdapter implements SpinnerAdapter, ListAdapter { if (um.getUserIcon(userId) != null) { icon = new BitmapDrawable(context.getResources(), um.getUserIcon(userId)); } else { - icon = UserIcons.getDefaultUserIcon(userId, /* light= */ false); + icon = UserIcons.getDefaultUserIcon( + context.getResources(), userId, /* light= */ false); } } this.mIcon = encircle(context, icon); diff --git a/com/android/setupwizardlib/GlifPatternDrawable.java b/com/android/setupwizardlib/GlifPatternDrawable.java index 51c1a490..c1d968ae 100644 --- a/com/android/setupwizardlib/GlifPatternDrawable.java +++ b/com/android/setupwizardlib/GlifPatternDrawable.java @@ -23,7 +23,6 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; -import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; @@ -96,7 +95,6 @@ public class GlifPatternDrawable extends Drawable { private int mColor; private Paint mTempPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private ColorFilter mColorFilter; public GlifPatternDrawable(int color) { setColor(color); @@ -140,17 +138,10 @@ public class GlifPatternDrawable extends Drawable { canvas.clipRect(bounds); scaleCanvasToBounds(canvas, bitmap, bounds); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB - && canvas.isHardwareAccelerated()) { - mTempPaint.setColorFilter(mColorFilter); - canvas.drawBitmap(bitmap, 0, 0, mTempPaint); - } else { - // Software renderer doesn't work properly with ColorMatrix filter on ALPHA_8 bitmaps. - canvas.drawColor(Color.BLACK); - mTempPaint.setColor(Color.WHITE); - canvas.drawBitmap(bitmap, 0, 0, mTempPaint); - canvas.drawColor(mColor); - } + canvas.drawColor(Color.BLACK); + mTempPaint.setColor(Color.WHITE); + canvas.drawBitmap(bitmap, 0, 0, mTempPaint); + canvas.drawColor(mColor); canvas.restore(); } @@ -299,12 +290,6 @@ public class GlifPatternDrawable extends Drawable { final int g = Color.green(color); final int b = Color.blue(color); mColor = Color.argb(COLOR_ALPHA_INT, r, g, b); - mColorFilter = new ColorMatrixColorFilter(new float[] { - 0, 0, 0, 1 - COLOR_ALPHA, r * COLOR_ALPHA, - 0, 0, 0, 1 - COLOR_ALPHA, g * COLOR_ALPHA, - 0, 0, 0, 1 - COLOR_ALPHA, b * COLOR_ALPHA, - 0, 0, 0, 0, 255 - }); invalidateSelf(); } diff --git a/com/android/systemui/OverviewProxyService.java b/com/android/systemui/OverviewProxyService.java index 2e4a5a41..22922e7b 100644 --- a/com/android/systemui/OverviewProxyService.java +++ b/com/android/systemui/OverviewProxyService.java @@ -22,11 +22,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; -import android.graphics.Bitmap; import android.graphics.Rect; -import android.net.Uri; import android.os.Binder; -import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -36,12 +33,14 @@ import android.os.UserHandle; import android.util.Log; import android.view.SurfaceControl; +import com.android.systemui.OverviewProxyService.OverviewProxyListener; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; -import com.android.systemui.OverviewProxyService.OverviewProxyListener; +import com.android.systemui.shared.system.GraphicBufferCompat; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -67,12 +66,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private int mConnectionBackoffAttempts; private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { - public Bitmap screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer, - boolean useIdentityTransform, int rotation) { + public GraphicBufferCompat screenshot(Rect sourceCrop, int width, int height, int minLayer, + int maxLayer, boolean useIdentityTransform, int rotation) { long token = Binder.clearCallingIdentity(); try { - return SurfaceControl.screenshot(sourceCrop, width, height, minLayer, maxLayer, - useIdentityTransform, rotation); + return new GraphicBufferCompat(SurfaceControl.screenshotToBuffer(sourceCrop, width, + height, minLayer, maxLayer, useIdentityTransform, rotation)); } finally { Binder.restoreCallingIdentity(token); } diff --git a/com/android/systemui/SystemUIFactory.java b/com/android/systemui/SystemUIFactory.java index 0c067ff3..526a8f46 100644 --- a/com/android/systemui/SystemUIFactory.java +++ b/com/android/systemui/SystemUIFactory.java @@ -28,6 +28,7 @@ import com.android.systemui.Dependency.DependencyProvider; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.ScrimView; +import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LockIcon; @@ -86,10 +87,10 @@ public class SystemUIFactory { public ScrimController createScrimController(LightBarController lightBarController, ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim, - LockscreenWallpaper lockscreenWallpaper, - Consumer<Boolean> scrimVisibleListener) { + LockscreenWallpaper lockscreenWallpaper, Consumer<Boolean> scrimVisibleListener, + DozeParameters dozeParameters) { return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim, - scrimVisibleListener); + scrimVisibleListener, dozeParameters); } public NotificationIconAreaController createNotificationIconAreaController(Context context, diff --git a/com/android/systemui/doze/DozeHost.java b/com/android/systemui/doze/DozeHost.java index 7db118d7..2f607eee 100644 --- a/com/android/systemui/doze/DozeHost.java +++ b/com/android/systemui/doze/DozeHost.java @@ -35,7 +35,6 @@ public interface DozeHost { boolean isBlockingDoze(); void startPendingIntentDismissingKeyguard(PendingIntent intent); - void abortPulsing(); void extendPulse(); void setAnimateWakeup(boolean animateWakeup); diff --git a/com/android/systemui/keyguard/KeyguardSliceProvider.java b/com/android/systemui/keyguard/KeyguardSliceProvider.java index 03018f7d..6ddc76b5 100644 --- a/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -84,7 +84,7 @@ public class KeyguardSliceProvider extends SliceProvider { @Override public Slice onBindSlice(Uri sliceUri) { - return new Slice.Builder(sliceUri).addText(mLastText, Slice.HINT_TITLE).build(); + return new Slice.Builder(sliceUri).addText(mLastText, null, Slice.HINT_TITLE).build(); } @Override diff --git a/com/android/systemui/keyguard/KeyguardViewMediator.java b/com/android/systemui/keyguard/KeyguardViewMediator.java index a35ba9fa..c92acd06 100644 --- a/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -60,7 +60,7 @@ import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.view.ViewGroup; -import android.view.WindowManagerPolicy; +import android.view.WindowManagerPolicyConstants; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -129,7 +129,7 @@ import java.util.ArrayList; * false, this will override all other conditions for turning on the keyguard. * * Threading and synchronization: - * This class is created by the initialization routine of the {@link android.view.WindowManagerPolicy}, + * This class is created by the initialization routine of the {@link WindowManagerPolicyConstants}, * and runs on its thread. The keyguard UI is created from that thread in the * constructor of this class. The apis may be called from other threads, including the * {@link com.android.server.input.InputManagerService}'s and {@link android.view.WindowManager}'s. @@ -766,8 +766,8 @@ public class KeyguardViewMediator extends SystemUI { /** * Called to let us know the screen was turned off. - * @param why either {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_USER} or - * {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}. + * @param why either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER} or + * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}. */ public void onStartedGoingToSleep(int why) { if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + why + ")"); @@ -797,8 +797,8 @@ public class KeyguardViewMediator extends SystemUI { } } else if (mShowing) { mPendingReset = true; - } else if ((why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT && timeout > 0) - || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) { + } else if ((why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_TIMEOUT && timeout > 0) + || (why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER && !lockImmediately)) { doKeyguardLaterLocked(timeout); mLockLater = true; } else if (!mLockPatternUtils.isLockScreenDisabled(currentUser)) { @@ -1031,7 +1031,7 @@ public class KeyguardViewMediator extends SystemUI { } /** - * Same semantics as {@link android.view.WindowManagerPolicy#enableKeyguard}; provide + * Same semantics as {@link WindowManagerPolicyConstants#enableKeyguard}; provide * a way for external stuff to override normal keyguard behavior. For instance * the phone app disables the keyguard when it receives incoming calls. */ @@ -1780,13 +1780,13 @@ public class KeyguardViewMediator extends SystemUI { int flags = 0; if (mStatusBarKeyguardViewManager.shouldDisableWindowAnimationsForUnlock() || mWakeAndUnlocking) { - flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; + flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; } if (mStatusBarKeyguardViewManager.isGoingToNotificationShade()) { - flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; + flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; } if (mStatusBarKeyguardViewManager.isUnlockWithWallpaper()) { - flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; + flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; } mUpdateMonitor.setKeyguardGoingAway(true /* goingAway */); @@ -2028,12 +2028,9 @@ public class KeyguardViewMediator extends SystemUI { } public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar, - ViewGroup container, - ScrimController scrimController, - FingerprintUnlockController fingerprintUnlockController) { + ViewGroup container, FingerprintUnlockController fingerprintUnlockController) { mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container, - scrimController, fingerprintUnlockController, - mDismissCallbackRegistry); + fingerprintUnlockController, mDismissCallbackRegistry); return mStatusBarKeyguardViewManager; } diff --git a/com/android/systemui/pip/phone/PipTouchHandler.java b/com/android/systemui/pip/phone/PipTouchHandler.java index 2b48e0fb..51175d1d 100644 --- a/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/com/android/systemui/pip/phone/PipTouchHandler.java @@ -387,7 +387,9 @@ public class PipTouchHandler { } case MotionEvent.ACTION_HOVER_ENTER: case MotionEvent.ACTION_HOVER_MOVE: { - if (mAccessibilityManager.isEnabled() && !mSendingHoverAccessibilityEvents) { + if (mAccessibilityManager.isObservedEventType( + AccessibilityEvent.TYPE_VIEW_HOVER_ENTER) + && !mSendingHoverAccessibilityEvents) { AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); event.setImportantForAccessibility(true); @@ -400,7 +402,9 @@ public class PipTouchHandler { break; } case MotionEvent.ACTION_HOVER_EXIT: { - if (mAccessibilityManager.isEnabled() && mSendingHoverAccessibilityEvents) { + if (mAccessibilityManager.isObservedEventType( + AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) + && mSendingHoverAccessibilityEvents) { AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); event.setImportantForAccessibility(true); diff --git a/com/android/systemui/pip/tv/PipManager.java b/com/android/systemui/pip/tv/PipManager.java index eef43d29..a9846801 100644 --- a/com/android/systemui/pip/tv/PipManager.java +++ b/com/android/systemui/pip/tv/PipManager.java @@ -625,9 +625,7 @@ public class PipManager implements BasePipManager { @Override public void onTaskStackChanged() { if (DEBUG) Log.d(TAG, "onTaskStackChanged()"); - if (!checkCurrentUserId(mContext, DEBUG)) { - return; - } + if (getState() != STATE_NO_PIP) { boolean hasPip = false; @@ -662,9 +660,7 @@ public class PipManager implements BasePipManager { @Override public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { if (DEBUG) Log.d(TAG, "onActivityPinned()"); - if (!checkCurrentUserId(mContext, DEBUG)) { - return; - } + StackInfo stackInfo = getPinnedStackInfo(); if (stackInfo == null) { Log.w(TAG, "Cannot find pinned stack"); @@ -690,9 +686,7 @@ public class PipManager implements BasePipManager { @Override public void onPinnedActivityRestartAttempt(boolean clearedTask) { if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()"); - if (!checkCurrentUserId(mContext, DEBUG)) { - return; - } + // If PIPed activity is launched again by Launcher or intent, make it fullscreen. movePipToFullscreen(); } @@ -700,9 +694,7 @@ public class PipManager implements BasePipManager { @Override public void onPinnedStackAnimationEnded() { if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()"); - if (!checkCurrentUserId(mContext, DEBUG)) { - return; - } + switch (getState()) { case STATE_PIP_MENU: showPipMenu(); diff --git a/com/android/systemui/recents/RecentsImpl.java b/com/android/systemui/recents/RecentsImpl.java index 3b1b2f90..663f2067 100644 --- a/com/android/systemui/recents/RecentsImpl.java +++ b/com/android/systemui/recents/RecentsImpl.java @@ -23,14 +23,12 @@ import static android.view.View.MeasureSpec; import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS; import android.app.ActivityManager; -import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityOptions; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.GraphicBuffer; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; @@ -89,7 +87,6 @@ import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFutur import com.android.systemui.shared.recents.view.RecentsTransition; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.stackdivider.DividerView; -import com.android.systemui.statusbar.phone.NavigationBarGestureHelper; import com.android.systemui.statusbar.phone.StatusBar; import java.util.ArrayList; @@ -156,7 +153,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // Launched from app is always the worst case (in terms of how many // thumbnails/tasks visible) launchState.launchedFromApp = true; - mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState); + mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState, + -1 /* lastScrollPPresent */); VisibilityReport visibilityReport = mBackgroundLayoutAlgorithm.computeStackVisibilityReport( stack.getTasks()); @@ -656,13 +654,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // the resize mode already. if (ssp.setTaskWindowingModeSplitScreenPrimary(taskId, stackCreateMode, initialBounds)) { EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds)); - showRecents( - false /* triggeredFromAltTab */, - dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS, - false /* animate */, - true /* launchedWhileDockingTask*/, - false /* fromHome */, - DividerView.INVALID_RECENTS_GROW_TARGET); } } diff --git a/com/android/systemui/recents/misc/SystemServicesProxy.java b/com/android/systemui/recents/misc/SystemServicesProxy.java index d89bab75..2d3080b1 100644 --- a/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -21,12 +21,10 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.StackInfo; import android.app.ActivityOptions; @@ -49,9 +47,6 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.IRemoteCallback; -import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; @@ -63,7 +58,6 @@ import android.service.dreams.IDreamManager; import android.util.Log; import android.util.MutableBoolean; import android.view.Display; -import android.view.IAppTransitionAnimationSpecsFuture; import android.view.IDockedStackListener; import android.view.IWindowManager; import android.view.WindowManager; @@ -74,16 +68,12 @@ import android.view.accessibility.AccessibilityManager; import com.android.internal.app.AssistUtils; import com.android.internal.os.BackgroundThread; import com.android.systemui.Dependency; -import com.android.systemui.R; import com.android.systemui.UiOffloadThread; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsImpl; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.statusbar.policy.UserInfoController; import java.util.List; -import java.util.function.Consumer; /** * Acts as a shim around the real system services that we need to access data from, and provides @@ -268,22 +258,6 @@ public class SystemServicesProxy { return mIsSafeMode; } - /** Docks a task to the side of the screen and starts it. */ - public boolean startTaskInDockedMode(int taskId, int createMode) { - if (mIam == null) return false; - - try { - final ActivityOptions options = ActivityOptions.makeBasic(); - options.setSplitScreenCreateMode(createMode); - options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - mIam.startActivityFromRecents(taskId, options.toBundle()); - return true; - } catch (Exception e) { - Log.e(TAG, "Failed to dock task: " + taskId + " with createMode: " + createMode, e); - } - return false; - } - /** Moves an already resumed task to the side of the screen to initiate split screen. */ public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, Rect initialBounds) { @@ -397,7 +371,7 @@ public class SystemServicesProxy { if (mIam == null) return false; try { - return mIam.isInLockTaskMode(); + return mIam.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED; } catch (RemoteException e) { return false; } @@ -540,16 +514,6 @@ public class SystemServicesProxy { } } - public void overridePendingAppTransitionMultiThumbFuture( - IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener, - boolean scaleUp) { - try { - mIwm.overridePendingAppTransitionMultiThumbFuture(future, animStartedListener, scaleUp); - } catch (RemoteException e) { - Log.w(TAG, "Failed to override transition: " + e); - } - } - /** * Updates the visibility of recents. */ diff --git a/com/android/systemui/recents/views/RecentsView.java b/com/android/systemui/recents/views/RecentsView.java index 1440fc16..e3ed1aaa 100644 --- a/com/android/systemui/recents/views/RecentsView.java +++ b/com/android/systemui/recents/views/RecentsView.java @@ -16,13 +16,14 @@ package com.android.systemui.recents.views; +import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; + import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.Nullable; import android.app.ActivityOptions; -import android.app.ActivityOptions.OnAnimationStartedListener; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Canvas; @@ -33,11 +34,11 @@ import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.os.IRemoteCallback; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; import android.util.MathUtils; -import android.view.AppTransitionAnimationSpec; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -86,13 +87,15 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat; import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; import com.android.systemui.shared.recents.view.RecentsTransition; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.ActivityOptionsCompat; +import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.systemui.stackdivider.WindowManagerProxy; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.phone.ScrimController; @@ -608,16 +611,17 @@ public class RecentsView extends FrameLayout { // rect to its final layout-space rect Utilities.setViewFrameFromTranslation(event.taskView); - // Dock the task and launch it - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode)) { + final ActivityOptions options = ActivityOptionsCompat.makeSplitScreenOptions( + dockState.createMode == SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT); + if (ActivityManagerWrapper.getInstance().startActivityFromRecents(event.task.key.id, + options)) { final Runnable animStartedListener = () -> { EventBus.getDefault().send(new DockedFirstAnimationFrameEvent()); - // Remove the task and don't bother relaying out, as all the tasks will be - // relaid out when the stack changes on the multiwindow change event + // Remove the task and don't bother relaying out, as all the tasks + // will be relaid out when the stack changes on the multiwindow + // change event getStack().removeTask(event.task, null, true /* fromDockGesture */); }; - final Rect taskRect = getTaskRect(event.taskView); AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture( getHandler()) { @@ -626,10 +630,8 @@ public class RecentsView extends FrameLayout { return mTransitionHelper.composeDockAnimationSpec(event.taskView, taskRect); } }; - ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(), - RecentsTransition.wrapStartedListener(getHandler(), animStartedListener), - true /* scaleUp */); - + WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture( + future, animStartedListener, getHandler(), true /* scaleUp */); MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP, event.task.getTopComponent().flattenToShortString()); } else { @@ -1032,11 +1034,9 @@ public class RecentsView extends FrameLayout { if (taskIndex > -1) { taskIndexFromFront = stack.getTaskCount() - taskIndex - 1; } - EventBus.getDefault().send(new LaunchTaskSucceededEvent( - taskIndexFromFront)); + EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront)); } else { - Log.e(TAG, mContext.getString(R.string.recents_launch_error_message, - task.title)); + Log.e(TAG, mContext.getString(R.string.recents_launch_error_message, task.title)); // Dismiss the task if we fail to launch it if (taskView != null) { diff --git a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index 600da041..d9f79bb6 100644 --- a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -431,7 +431,7 @@ public class TaskStackLayoutAlgorithm { * in the stack. */ public void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet, - RecentsActivityLaunchState launchState) { + RecentsActivityLaunchState launchState, float lastScrollPPercent) { SystemServicesProxy ssp = Recents.getSystemServices(); // Clear the progress map @@ -506,6 +506,8 @@ public class TaskStackLayoutAlgorithm { if (launchState.launchedWithAltTab) { mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP); + } else if (0 <= lastScrollPPercent && lastScrollPPercent <= 1) { + mInitialScrollP = Utilities.mapRange(lastScrollPPercent, mMinScrollP, mMaxScrollP); } else if (Recents.getConfiguration().isLowRamDevice) { mInitialScrollP = mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks, scrollToFront); diff --git a/com/android/systemui/recents/views/TaskStackView.java b/com/android/systemui/recents/views/TaskStackView.java index 11975012..36c9095f 100644 --- a/com/android/systemui/recents/views/TaskStackView.java +++ b/com/android/systemui/recents/views/TaskStackView.java @@ -209,6 +209,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal private int mLastHeight; private boolean mStackActionButtonVisible; + // Percentage of last ScrollP from the min to max scrollP that lives after configuration changes + private float mLastScrollPPercent; + // We keep track of the task view focused by user interaction and draw a frame around it in the // grid layout. private TaskViewFocusFrame mTaskViewFocusFrame; @@ -327,6 +330,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mStackScroller.reset(); mStableLayoutAlgorithm.reset(); mLayoutAlgorithm.reset(); + mLastScrollPPercent = -1; } // Since we always animate to the same place in (the initial state), always reset the stack @@ -822,7 +826,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax, RecentsActivityLaunchState launchState) { // Compute the min and max scroll values - mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState); + mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState, mLastScrollPPercent); if (boundScrollToNewMinMax) { mStackScroller.boundScroll(); @@ -1150,6 +1154,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (mTaskViewsClipDirty) { clipTaskViews(); } + mLastScrollPPercent = Utilities.clamp(Utilities.unmapRange(mStackScroller.getStackScroll(), + mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP), 0, 1); } /** diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java index 8e2a25c1..4834bb18 100644 --- a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java +++ b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java @@ -45,6 +45,11 @@ import java.util.List; */ public class RecentsTaskLoadPlan { + /** The set of conditions to preload tasks. */ + public static class PreloadOptions { + public boolean loadTitles = true; + } + /** The set of conditions to load tasks. */ public static class Options { public int runningTaskId = -1; @@ -80,7 +85,8 @@ public class RecentsTaskLoadPlan { * Note: Do not lock, since this can be calling back to the loader, which separately also drives * this call (callers should synchronize on the loader before making this call). */ - public void preloadPlan(RecentsTaskLoader loader, int runningTaskId, int currentUserId) { + public void preloadPlan(PreloadOptions opts, RecentsTaskLoader loader, int runningTaskId, + int currentUserId) { Resources res = mContext.getResources(); ArrayList<Task> allTasks = new ArrayList<>(); if (mRawTasks == null) { @@ -110,9 +116,12 @@ public class RecentsTaskLoadPlan { } // Load the title, icon, and color - String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription); - String titleDescription = loader.getAndUpdateContentDescription(taskKey, - t.taskDescription); + String title = opts.loadTitles + ? loader.getAndUpdateActivityTitle(taskKey, t.taskDescription) + : ""; + String titleDescription = opts.loadTitles + ? loader.getAndUpdateContentDescription(taskKey, t.taskDescription) + : ""; Drawable icon = isStackTask ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false) : null; diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java index 9a991cfa..0f68026c 100644 --- a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java +++ b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java @@ -32,6 +32,7 @@ import android.util.LruCache; import com.android.internal.annotations.GuardedBy; import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.Options; +import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions; import com.android.systemui.shared.recents.model.Task.TaskKey; import com.android.systemui.shared.recents.model.TaskKeyLruCache.EvictionCallback; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -155,7 +156,7 @@ public class RecentsTaskLoader { int currentUserId) { try { Trace.beginSection("preloadPlan"); - plan.preloadPlan(this, runningTaskId, currentUserId); + plan.preloadPlan(new PreloadOptions(), this, runningTaskId, currentUserId); } finally { Trace.endSection(); } diff --git a/com/android/systemui/shared/recents/utilities/AppTrace.java b/com/android/systemui/shared/recents/utilities/AppTrace.java new file mode 100644 index 00000000..0241c593 --- /dev/null +++ b/com/android/systemui/shared/recents/utilities/AppTrace.java @@ -0,0 +1,73 @@ +/* + * 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 com.android.systemui.shared.recents.utilities; + +import static android.os.Trace.TRACE_TAG_APP; + +/** + * Helper class for internal trace functions. + */ +public class AppTrace { + + /** + * Begins a new async trace section with the given {@param key} and {@param cookie}. + */ + public static void start(String key, int cookie) { + android.os.Trace.asyncTraceBegin(TRACE_TAG_APP, key, cookie); + } + + /** + * Begins a new async trace section with the given {@param key}. + */ + public static void start(String key) { + android.os.Trace.asyncTraceBegin(TRACE_TAG_APP, key, 0); + } + + /** + * Ends an existing async trace section with the given {@param key}. + */ + public static void end(String key) { + android.os.Trace.asyncTraceEnd(TRACE_TAG_APP, key, 0); + } + + /** + * Ends an existing async trace section with the given {@param key} and {@param cookie}. + */ + public static void end(String key, int cookie) { + android.os.Trace.asyncTraceEnd(TRACE_TAG_APP, key, cookie); + } + + /** + * Begins a new trace section with the given {@param key}. Can be nested. + */ + public static void beginSection(String key) { + android.os.Trace.beginSection(key); + } + + /** + * Ends an existing trace section started in the last {@link #beginSection(String)}. + */ + public static void endSection() { + android.os.Trace.endSection(); + } + + /** + * Traces a counter value. + */ + public static void count(String name, int count) { + android.os.Trace.traceCounter(TRACE_TAG_APP, name, count); + } +} diff --git a/com/android/systemui/shared/system/ActivityManagerWrapper.java b/com/android/systemui/shared/system/ActivityManagerWrapper.java index f6fab86c..eb2d12ed 100644 --- a/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -319,32 +319,39 @@ public class ActivityManagerWrapper { mBackgroundExecutor.submit(new Runnable() { @Override public void run() { + boolean result = false; try { - ActivityManager.getService().startActivityFromRecents(taskKey.id, - finalOptions == null ? null : finalOptions.toBundle()); - if (resultCallback != null) { - resultCallbackHandler.post(new Runnable() { - @Override - public void run() { - resultCallback.accept(true); - } - }); - } + result = startActivityFromRecents(taskKey.id, finalOptions); } catch (Exception e) { - if (resultCallback != null) { - resultCallbackHandler.post(new Runnable() { - @Override - public void run() { - resultCallback.accept(false); - } - }); - } + // Fall through + } + final boolean finalResult = result; + if (resultCallback != null) { + resultCallbackHandler.post(new Runnable() { + @Override + public void run() { + resultCallback.accept(finalResult); + } + }); } } }); } /** + * Starts a task from Recents synchronously. + */ + public boolean startActivityFromRecents(int taskId, ActivityOptions options) { + try { + Bundle optsBundle = options == null ? null : options.toBundle(); + ActivityManager.getService().startActivityFromRecents(taskId, optsBundle); + return true; + } catch (Exception e) { + return false; + } + } + + /** * Registers a task stack listener with the system. * This should be called on the main thread. */ diff --git a/com/android/systemui/shared/system/ActivityOptionsCompat.java b/com/android/systemui/shared/system/ActivityOptionsCompat.java new file mode 100644 index 00000000..705a2152 --- /dev/null +++ b/com/android/systemui/shared/system/ActivityOptionsCompat.java @@ -0,0 +1,41 @@ +/* + * 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 com.android.systemui.shared.system; + +import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT; +import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; + +import android.app.ActivityOptions; + +/** + * Wrapper around internal ActivityOptions creation. + */ +public abstract class ActivityOptionsCompat { + + /** + * @return ActivityOptions for starting a task in split screen. + */ + public static ActivityOptions makeSplitScreenOptions(boolean dockTopLeft) { + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + options.setSplitScreenCreateMode(dockTopLeft + ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT + : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT); + return options; + } +} diff --git a/com/android/systemui/shared/system/BackgroundExecutor.java b/com/android/systemui/shared/system/BackgroundExecutor.java index cfd1f9a5..0bd89a78 100644 --- a/com/android/systemui/shared/system/BackgroundExecutor.java +++ b/com/android/systemui/shared/system/BackgroundExecutor.java @@ -16,6 +16,7 @@ package com.android.systemui.shared.system; +import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -37,6 +38,13 @@ public class BackgroundExecutor { } /** + * Runs the given {@param callable} on one of the background executor threads. + */ + public <T> Future<T> submit(Callable<T> callable) { + return mExecutorService.submit(callable); + } + + /** * Runs the given {@param runnable} on one of the background executor threads. */ public Future<?> submit(Runnable runnable) { diff --git a/com/android/systemui/shared/system/ChoreographerCompat.java b/com/android/systemui/shared/system/ChoreographerCompat.java new file mode 100644 index 00000000..4d422bb8 --- /dev/null +++ b/com/android/systemui/shared/system/ChoreographerCompat.java @@ -0,0 +1,33 @@ +/* + * 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 com.android.systemui.shared.system; + +import static android.view.Choreographer.CALLBACK_INPUT; + +import android.view.Choreographer; + +/** + * Wraps the internal choreographer. + */ +public class ChoreographerCompat { + + /** + * Posts an input callback to the choreographer. + */ + public static void postInputFrame(Choreographer choreographer, Runnable runnable) { + choreographer.postCallback(CALLBACK_INPUT, runnable, null); + } +} diff --git a/com/android/systemui/shared/system/GraphicBufferCompat.java b/com/android/systemui/shared/system/GraphicBufferCompat.java new file mode 100644 index 00000000..66b8fed1 --- /dev/null +++ b/com/android/systemui/shared/system/GraphicBufferCompat.java @@ -0,0 +1,64 @@ +/* + * 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 com.android.systemui.shared.system; + +import android.graphics.Bitmap; +import android.graphics.GraphicBuffer; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Wraps the internal graphic buffer. + */ +public class GraphicBufferCompat implements Parcelable { + + private GraphicBuffer mBuffer; + + public GraphicBufferCompat(GraphicBuffer buffer) { + mBuffer = buffer; + } + + public GraphicBufferCompat(Parcel in) { + mBuffer = GraphicBuffer.CREATOR.createFromParcel(in); + } + + public Bitmap toBitmap() { + return mBuffer != null + ? Bitmap.createHardwareBitmap(mBuffer) + : null; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + mBuffer.writeToParcel(dest, flags); + } + + public static final Parcelable.Creator<GraphicBufferCompat> CREATOR + = new Parcelable.Creator<GraphicBufferCompat>() { + public GraphicBufferCompat createFromParcel(Parcel in) { + return new GraphicBufferCompat(in); + } + + public GraphicBufferCompat[] newArray(int size) { + return new GraphicBufferCompat[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } +} diff --git a/com/android/systemui/shared/system/WindowManagerWrapper.java b/com/android/systemui/shared/system/WindowManagerWrapper.java index 1477558a..225dbb4a 100644 --- a/com/android/systemui/shared/system/WindowManagerWrapper.java +++ b/com/android/systemui/shared/system/WindowManagerWrapper.java @@ -19,8 +19,15 @@ package com.android.systemui.shared.system; import static android.view.Display.DEFAULT_DISPLAY; import android.graphics.Rect; +import android.os.Handler; +import android.os.IRemoteCallback; +import android.os.RemoteException; +import android.util.Log; import android.view.WindowManagerGlobal; +import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; +import com.android.systemui.shared.recents.view.RecentsTransition; + public class WindowManagerWrapper { private static final String TAG = "WindowManagerWrapper"; @@ -38,8 +45,24 @@ public class WindowManagerWrapper { try { WindowManagerGlobal.getWindowManagerService().getStableInsets(DEFAULT_DISPLAY, outStableInsets); - } catch (Exception e) { - e.printStackTrace(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to get stable insets", e); + } + } + + /** + * Overrides a pending app transition. + */ + public void overridePendingAppTransitionMultiThumbFuture( + AppTransitionAnimationSpecsFuture animationSpecFuture, + Runnable animStartedCallback, Handler animStartedCallbackHandler, boolean scaleUp) { + try { + WindowManagerGlobal.getWindowManagerService() + .overridePendingAppTransitionMultiThumbFuture(animationSpecFuture.getFuture(), + RecentsTransition.wrapStartedListener(animStartedCallbackHandler, + animStartedCallback), scaleUp); + } catch (RemoteException e) { + Log.w(TAG, "Failed to override pending app transition (multi-thumbnail future): ", e); } } } diff --git a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java index 1cda3011..da798848 100644 --- a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java +++ b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java @@ -20,6 +20,8 @@ import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIG import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.os.UserHandle.USER_CURRENT; +import static com.android.systemui.statusbar.phone.NavigationBarGestureHelper.DRAG_MODE_NONE; + import android.app.ActivityManager; import android.content.res.Configuration; import android.os.RemoteException; @@ -36,6 +38,7 @@ import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.stackdivider.Divider; import com.android.systemui.stackdivider.DividerView; +import com.android.systemui.statusbar.phone.NavigationBarGestureHelper; import java.util.List; @@ -89,20 +92,11 @@ public class ShortcutKeyDispatcher extends SystemUI try { int dockSide = mWindowManagerService.getDockedStackSide(); if (dockSide == WindowManager.DOCKED_INVALID) { - // If there is no window docked, we dock the top-most window. + // Split the screen Recents recents = getComponent(Recents.class); - int dockMode = (shortcutCode == SC_DOCK_LEFT) + recents.splitPrimaryTask(DRAG_MODE_NONE, (shortcutCode == SC_DOCK_LEFT) ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT - : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT; - List<ActivityManager.RecentTaskInfo> taskList = - ActivityManagerWrapper.getInstance().getRecentTasks(1, USER_CURRENT); - recents.showRecentApps( - false /* triggeredFromAltTab */, - false /* fromHome */); - if (!taskList.isEmpty()) { - SystemServicesProxy.getInstance(mContext).startTaskInDockedMode( - taskList.get(0).id, dockMode); - } + : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1); } else { // If there is already a docked window, we respond by resizing the docking pane. DividerView dividerView = getComponent(Divider.class).getView(); diff --git a/com/android/systemui/statusbar/ActivatableNotificationView.java b/com/android/systemui/statusbar/ActivatableNotificationView.java index 84b7015f..ff0357a3 100644 --- a/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -695,6 +695,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundNormal.getVisibility() == View.VISIBLE ? 1.0f : 0.0f); } + protected void updateBackgroundClipping() { + mBackgroundNormal.setBottomAmountClips(!isChildInGroup()); + mBackgroundDimmed.setBottomAmountClips(!isChildInGroup()); + } + protected boolean shouldHideBackground() { return mDark; } @@ -901,12 +906,45 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView contentView.setAlpha(contentAlpha); } + @Override + protected void applyRoundness() { + super.applyRoundness(); + applyBackgroundRoundness(getCurrentBackgroundRadiusTop(), + getCurrentBackgroundRadiusBottom()); + } + + protected void applyBackgroundRoundness(float topRadius, float bottomRadius) { + mBackgroundDimmed.setRoundness(topRadius, bottomRadius); + mBackgroundNormal.setRoundness(topRadius, bottomRadius); + } + + @Override + protected void setBackgroundTop(int backgroundTop) { + mBackgroundDimmed.setBackgroundTop(backgroundTop); + mBackgroundNormal.setBackgroundTop(backgroundTop); + } + protected abstract View getContentView(); public int calculateBgColor() { return calculateBgColor(true /* withTint */, true /* withOverRide */); } + @Override + public void setCurrentSidePaddings(float currentSidePaddings) { + super.setCurrentSidePaddings(currentSidePaddings); + mBackgroundNormal.setCurrentSidePaddings(currentSidePaddings); + mBackgroundDimmed.setCurrentSidePaddings(currentSidePaddings); + } + + @Override + protected boolean childNeedsClipping(View child) { + if (child instanceof NotificationBackgroundView && isClippingNeeded()) { + return true; + } + return super.childNeedsClipping(child); + } + /** * @param withTint should a possible tint be factored in? * @param withOverRide should the value be interpolated with {@link #mOverrideTint} diff --git a/com/android/systemui/statusbar/CommandQueue.java b/com/android/systemui/statusbar/CommandQueue.java index 63492750..8e1b1043 100644 --- a/com/android/systemui/statusbar/CommandQueue.java +++ b/com/android/systemui/statusbar/CommandQueue.java @@ -82,6 +82,7 @@ public class CommandQueue extends IStatusBar.Stub { private static final int MSG_TOGGLE_PANEL = 35 << MSG_SHIFT; private static final int MSG_SHOW_SHUTDOWN_UI = 36 << MSG_SHIFT; private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR = 37 << MSG_SHIFT; + private static final int MSG_ROTATION_PROPOSAL = 38 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -142,6 +143,8 @@ public class CommandQueue extends IStatusBar.Stub { default void handleSystemKey(int arg1) { } default void handleShowGlobalActionsMenu() { } default void handleShowShutdownUi(boolean isReboot, String reason) { } + + default void onRotationProposal(int rotation) { } } @VisibleForTesting @@ -458,6 +461,15 @@ public class CommandQueue extends IStatusBar.Stub { } } + @Override + public void onProposedRotationChanged(int rotation) { + synchronized (mLock) { + mHandler.removeMessages(MSG_ROTATION_PROPOSAL); + mHandler.obtainMessage(MSG_ROTATION_PROPOSAL, rotation, 0, + null).sendToTarget(); + } + } + private final class H extends Handler { private H(Looper l) { super(l); @@ -654,6 +666,11 @@ public class CommandQueue extends IStatusBar.Stub { mCallbacks.get(i).setTopAppHidesStatusBar(msg.arg1 != 0); } break; + case MSG_ROTATION_PROPOSAL: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onRotationProposal(msg.arg1); + } + break; } } } diff --git a/com/android/systemui/statusbar/ExpandableNotificationRow.java b/com/android/systemui/statusbar/ExpandableNotificationRow.java index 8ff950ed..23d9caee 100644 --- a/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -26,6 +26,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.content.res.Configuration; +import android.graphics.Path; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.ColorDrawable; @@ -65,7 +66,6 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.statusbar.NotificationGuts.GutsContent; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; -import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.HybridNotificationView; import com.android.systemui.statusbar.notification.NotificationInflater; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -102,7 +102,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private int mIconTransformContentShift; private int mIconTransformContentShiftNoIcon; private int mNotificationMinHeightLegacy; + private int mNotificationMinHeightBeforeP; private int mMaxHeadsUpHeightLegacy; + private int mMaxHeadsUpHeightBeforeP; private int mMaxHeadsUpHeight; private int mMaxHeadsUpHeightIncreased; private int mNotificationMinHeight; @@ -435,9 +437,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView boolean customView = layout.getContractedChild().getId() != com.android.internal.R.id.status_bar_latest_event_content; boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N; + boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P; int minHeight; - if (customView && beforeN && !mIsSummaryWithChildren) { - minHeight = mNotificationMinHeightLegacy; + if (customView && beforeP && !mIsSummaryWithChildren) { + minHeight = beforeN ? mNotificationMinHeightLegacy : mNotificationMinHeightBeforeP; } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) { minHeight = mNotificationMinHeightLarge; } else { @@ -447,8 +450,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView layout.getHeadsUpChild().getId() != com.android.internal.R.id.status_bar_latest_event_content; int headsUpheight; - if (headsUpCustom && beforeN) { - headsUpheight = mMaxHeadsUpHeightLegacy; + if (headsUpCustom && beforeP) { + headsUpheight = beforeN ? mMaxHeadsUpHeightLegacy : mMaxHeadsUpHeightBeforeP; } else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) { headsUpheight = mMaxHeadsUpHeightIncreased; } else { @@ -535,6 +538,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } onChildrenCountChanged(); row.setIsChildInGroup(false, null); + row.setBottomRoundness(0.0f, false /* animate */); } @Override @@ -563,6 +567,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mNotificationParent.updateBackgroundForGroupState(); } updateIconVisibilities(); + updateBackgroundClipping(); } @Override @@ -916,6 +921,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView addView(mMenuRow.getMenuView(), menuIndex); } for (NotificationContentView l : mLayouts) { + l.initView(); l.reInflateViews(); } mNotificationInflater.onDensityOrFontScaleChanged(); @@ -1025,6 +1031,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mKeepInParent = keepInParent; } + @Override public boolean isRemoved() { return mRemoved; } @@ -1264,6 +1271,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private void initDimens() { mNotificationMinHeightLegacy = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_min_height_legacy); + mNotificationMinHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext, + R.dimen.notification_min_height_before_p); mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_min_height); mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext, @@ -1274,6 +1283,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView R.dimen.notification_ambient_height); mMaxHeadsUpHeightLegacy = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_max_heads_up_height_legacy); + mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext, + R.dimen.notification_max_heads_up_height_before_p); mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_max_heads_up_height); mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext, @@ -1752,6 +1763,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPrivateLayout.updateExpandButtons(isExpandable()); updateChildrenHeaderAppearance(); updateChildrenVisibility(); + applyChildrenRoundness(); } public void updateChildrenHeaderAppearance() { @@ -2332,6 +2344,56 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + @Override + protected boolean childNeedsClipping(View child) { + if (child instanceof NotificationContentView) { + NotificationContentView contentView = (NotificationContentView) child; + if (isClippingNeeded()) { + return true; + } else if (!hasNoRoundingAndNoPadding() && contentView.shouldClipToSidePaddings()) { + return true; + } + } else if (child == mChildrenContainer) { + if (isClippingNeeded() || ((isGroupExpanded() || isGroupExpansionChanging()) + && getClipBottomAmount() != 0.0f && getCurrentBottomRoundness() != 0.0f)) { + return true; + } + } else if (child instanceof NotificationGuts) { + return !hasNoRoundingAndNoPadding(); + } + return super.childNeedsClipping(child); + } + + @Override + protected void applyRoundness() { + super.applyRoundness(); + applyChildrenRoundness(); + } + + private void applyChildrenRoundness() { + if (mIsSummaryWithChildren) { + mChildrenContainer.setCurrentBottomRoundness(getCurrentBottomRoundness()); + } + } + + @Override + public Path getCustomClipPath(View child) { + if (child instanceof NotificationGuts) { + return getClipPath(true, /* ignoreTranslation */ + false /* clipRoundedToBottom */); + } + if (child instanceof NotificationChildrenContainer) { + return getClipPath(false, /* ignoreTranslation */ + true /* clipRoundedToBottom */); + } + return super.getCustomClipPath(child); + } + + private boolean hasNoRoundingAndNoPadding() { + return mCurrentSidePaddings == 0 && getCurrentBottomRoundness() == 0.0f + && getCurrentTopRoundness() == 0.0f; + } + public boolean isShowingAmbient() { return mShowAmbient; } @@ -2344,6 +2406,20 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + @Override + public void setCurrentSidePaddings(float currentSidePaddings) { + if (mIsSummaryWithChildren) { + List<ExpandableNotificationRow> notificationChildren = + mChildrenContainer.getNotificationChildren(); + int size = notificationChildren.size(); + for (int i = 0; i < size; i++) { + ExpandableNotificationRow row = notificationChildren.get(i); + row.setCurrentSidePaddings(currentSidePaddings); + } + } + super.setCurrentSidePaddings(currentSidePaddings); + } + public static class NotificationViewState extends ExpandableViewState { private final StackScrollState mOverallState; diff --git a/com/android/systemui/statusbar/ExpandableOutlineView.java b/com/android/systemui/statusbar/ExpandableOutlineView.java index 25568907..b3d6e32d 100644 --- a/com/android/systemui/statusbar/ExpandableOutlineView.java +++ b/com/android/systemui/statusbar/ExpandableOutlineView.java @@ -18,23 +18,58 @@ package com.android.systemui.statusbar; import android.content.Context; import android.content.res.Resources; +import android.graphics.Canvas; import android.graphics.Outline; +import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; import android.view.ViewOutlineProvider; + +import com.android.settingslib.Utils; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.AnimatableProperty; +import com.android.systemui.statusbar.notification.PropertyAnimator; +import com.android.systemui.statusbar.stack.AnimationProperties; +import com.android.systemui.statusbar.stack.StackStateAnimator; /** * Like {@link ExpandableView}, but setting an outline for the height and clipping. */ public abstract class ExpandableOutlineView extends ExpandableView { + private static final AnimatableProperty TOP_ROUNDNESS = AnimatableProperty.from( + "topRoundness", + ExpandableOutlineView::setTopRoundnessInternal, + ExpandableOutlineView::getCurrentTopRoundness, + R.id.top_roundess_animator_tag, + R.id.top_roundess_animator_end_tag, + R.id.top_roundess_animator_start_tag); + private static final AnimatableProperty BOTTOM_ROUNDNESS = AnimatableProperty.from( + "bottomRoundness", + ExpandableOutlineView::setBottomRoundnessInternal, + ExpandableOutlineView::getCurrentBottomRoundness, + R.id.bottom_roundess_animator_tag, + R.id.bottom_roundess_animator_end_tag, + R.id.bottom_roundess_animator_start_tag); + private static final AnimationProperties ROUNDNESS_PROPERTIES = + new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + private static final Path EMPTY_PATH = new Path(); + private final Rect mOutlineRect = new Rect(); private boolean mCustomOutline; private float mOutlineAlpha = -1f; private float mOutlineRadius; + private boolean mAlwaysRoundBothCorners; + private Path mTmpPath = new Path(); + private Path mTmpPath2 = new Path(); + private float mCurrentBottomRoundness; + private float mCurrentTopRoundness; + private float mBottomRoundness; + private float mTopRoundness; + private int mBackgroundTop; + protected int mCurrentSidePaddings; /** * {@code true} if the children views of the {@link ExpandableOutlineView} are translated when @@ -45,61 +80,248 @@ public abstract class ExpandableOutlineView extends ExpandableView { private final ViewOutlineProvider mProvider = new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { - int translation = mShouldTranslateContents ? (int) getTranslation() : 0; - if (!mCustomOutline) { - outline.setRoundRect(translation, - mClipTopAmount, - getWidth() + translation, - Math.max(getActualHeight() - mClipBottomAmount, mClipTopAmount), - mOutlineRadius); - } else { - outline.setRoundRect(mOutlineRect, mOutlineRadius); + Path clipPath = getClipPath(); + if (clipPath != null && clipPath.isConvex()) { + // The path might not be convex in border cases where the view is small and clipped + outline.setConvexPath(clipPath); } outline.setAlpha(mOutlineAlpha); } }; + private Path getClipPath() { + return getClipPath(false, /* ignoreTranslation */ + false /* clipRoundedToBottom */); + } + + protected Path getClipPath(boolean ignoreTranslation, boolean clipRoundedToBottom) { + int left; + int top; + int right; + int bottom; + int height; + Path intersectPath = null; + if (!mCustomOutline) { + int translation = mShouldTranslateContents && !ignoreTranslation + ? (int) getTranslation() : 0; + left = Math.max(translation + mCurrentSidePaddings, mCurrentSidePaddings); + top = mClipTopAmount + mBackgroundTop; + right = getWidth() - mCurrentSidePaddings + Math.min(translation, 0); + bottom = Math.max(getActualHeight(), top); + int intersectBottom = Math.max(getActualHeight() - mClipBottomAmount, top); + if (bottom != intersectBottom) { + if (clipRoundedToBottom) { + bottom = intersectBottom; + } else { + getRoundedRectPath(left, top, right, + intersectBottom, 0.0f, + 0.0f, mTmpPath2); + intersectPath = mTmpPath2; + } + } + } else { + left = mOutlineRect.left; + top = mOutlineRect.top; + right = mOutlineRect.right; + bottom = mOutlineRect.bottom; + left = Math.max(mCurrentSidePaddings, left); + right = Math.min(getWidth() - mCurrentSidePaddings, right); + } + height = bottom - top; + if (height == 0) { + return EMPTY_PATH; + } + float topRoundness = mAlwaysRoundBothCorners + ? mOutlineRadius : mCurrentTopRoundness * mOutlineRadius; + float bottomRoundness = mAlwaysRoundBothCorners + ? mOutlineRadius : mCurrentBottomRoundness * mOutlineRadius; + if (topRoundness + bottomRoundness > height) { + float overShoot = topRoundness + bottomRoundness - height; + topRoundness -= overShoot * mCurrentTopRoundness + / (mCurrentTopRoundness + mCurrentBottomRoundness); + bottomRoundness -= overShoot * mCurrentBottomRoundness + / (mCurrentTopRoundness + mCurrentBottomRoundness); + } + getRoundedRectPath(left, top, right, bottom, topRoundness, + bottomRoundness, mTmpPath); + Path roundedRectPath = mTmpPath; + if (intersectPath != null) { + roundedRectPath.op(intersectPath, Path.Op.INTERSECT); + } + return roundedRectPath; + } + + protected Path getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness, + float bottomRoundness) { + getRoundedRectPath(left, top, right, bottom, topRoundness, bottomRoundness, + mTmpPath); + return mTmpPath; + } + + private void getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness, + float bottomRoundness, Path outPath) { + outPath.reset(); + int width = right - left; + float topRoundnessX = topRoundness; + float bottomRoundnessX = bottomRoundness; + topRoundnessX = Math.min(width / 2, topRoundnessX); + bottomRoundnessX = Math.min(width / 2, bottomRoundnessX); + if (topRoundness > 0.0f) { + outPath.moveTo(left, top + topRoundness); + outPath.quadTo(left, top, left + topRoundnessX, top); + outPath.lineTo(right - topRoundnessX, top); + outPath.quadTo(right, top, right, top + topRoundness); + } else { + outPath.moveTo(left, top); + outPath.lineTo(right, top); + } + if (bottomRoundness > 0.0f) { + outPath.lineTo(right, bottom - bottomRoundness); + outPath.quadTo(right, bottom, right - bottomRoundnessX, bottom); + outPath.lineTo(left + bottomRoundnessX, bottom); + outPath.quadTo(left, bottom, left, bottom - bottomRoundness); + } else { + outPath.lineTo(right, bottom); + outPath.lineTo(left, bottom); + } + outPath.close(); + } + public ExpandableOutlineView(Context context, AttributeSet attrs) { super(context, attrs); setOutlineProvider(mProvider); initDimens(); } + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + canvas.save(); + if (childNeedsClipping(child)) { + Path clipPath = getCustomClipPath(child); + if (clipPath == null) { + clipPath = getClipPath(); + } + if (clipPath != null) { + canvas.clipPath(clipPath); + } + } + boolean result = super.drawChild(canvas, child, drawingTime); + canvas.restore(); + return result; + } + + protected boolean childNeedsClipping(View child) { + return false; + } + + protected boolean isClippingNeeded() { + return mAlwaysRoundBothCorners || mCustomOutline || getTranslation() != 0 ; + + } + private void initDimens() { Resources res = getResources(); mShouldTranslateContents = res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe); mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius); - setClipToOutline(res.getBoolean(R.bool.config_clipNotificationsToOutline)); + mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline); + if (!mAlwaysRoundBothCorners) { + mOutlineRadius = res.getDimensionPixelSize( + Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); + } + setClipToOutline(mAlwaysRoundBothCorners); + } + + public void setTopRoundness(float topRoundness, boolean animate) { + if (mTopRoundness != topRoundness) { + mTopRoundness = topRoundness; + PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness, + ROUNDNESS_PROPERTIES, animate); + } + } + + protected void applyRoundness() { + invalidateOutline(); + invalidate(); + } + + public float getCurrentBackgroundRadiusTop() { + return mCurrentTopRoundness * mOutlineRadius; + } + + public float getCurrentTopRoundness() { + return mCurrentTopRoundness; + } + + public float getCurrentBottomRoundness() { + return mCurrentBottomRoundness; + } + + protected float getCurrentBackgroundRadiusBottom() { + return mCurrentBottomRoundness * mOutlineRadius; + } + + public void setBottomRoundness(float bottomRoundness, boolean animate) { + if (mBottomRoundness != bottomRoundness) { + mBottomRoundness = bottomRoundness; + PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness, + ROUNDNESS_PROPERTIES, animate); + } + } + + protected void setBackgroundTop(int backgroundTop) { + if (mBackgroundTop != backgroundTop) { + mBackgroundTop = backgroundTop; + invalidateOutline(); + } + } + + private void setTopRoundnessInternal(float topRoundness) { + mCurrentTopRoundness = topRoundness; + applyRoundness(); + } + + private void setBottomRoundnessInternal(float bottomRoundness) { + mCurrentBottomRoundness = bottomRoundness; + applyRoundness(); } public void onDensityOrFontScaleChanged() { initDimens(); - invalidateOutline(); + applyRoundness(); } @Override public void setActualHeight(int actualHeight, boolean notifyListeners) { + int previousHeight = getActualHeight(); super.setActualHeight(actualHeight, notifyListeners); - invalidateOutline(); + if (previousHeight != actualHeight) { + applyRoundness(); + } } @Override public void setClipTopAmount(int clipTopAmount) { + int previousAmount = getClipTopAmount(); super.setClipTopAmount(clipTopAmount); - invalidateOutline(); + if (previousAmount != clipTopAmount) { + applyRoundness(); + } } @Override public void setClipBottomAmount(int clipBottomAmount) { + int previousAmount = getClipBottomAmount(); super.setClipBottomAmount(clipBottomAmount); - invalidateOutline(); + if (previousAmount != clipBottomAmount) { + applyRoundness(); + } } protected void setOutlineAlpha(float alpha) { if (alpha != mOutlineAlpha) { mOutlineAlpha = alpha; - invalidateOutline(); + applyRoundness(); } } @@ -113,8 +335,7 @@ public abstract class ExpandableOutlineView extends ExpandableView { setOutlineRect(rect.left, rect.top, rect.right, rect.bottom); } else { mCustomOutline = false; - setClipToOutline(false); - invalidateOutline(); + applyRoundness(); } } @@ -151,15 +372,22 @@ public abstract class ExpandableOutlineView extends ExpandableView { protected void setOutlineRect(float left, float top, float right, float bottom) { mCustomOutline = true; - setClipToOutline(true); mOutlineRect.set((int) left, (int) top, (int) right, (int) bottom); // Outlines need to be at least 1 dp mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom); mOutlineRect.right = (int) Math.max(left, mOutlineRect.right); + applyRoundness(); + } - invalidateOutline(); + public Path getCustomClipPath(View child) { + return null; } + public void setCurrentSidePaddings(float currentSidePaddings) { + mCurrentSidePaddings = (int) currentSidePaddings; + invalidateOutline(); + invalidate(); + } } diff --git a/com/android/systemui/statusbar/ExpandableView.java b/com/android/systemui/statusbar/ExpandableView.java index aac9af8a..18b98602 100644 --- a/com/android/systemui/statusbar/ExpandableView.java +++ b/com/android/systemui/statusbar/ExpandableView.java @@ -202,6 +202,10 @@ public abstract class ExpandableView extends FrameLayout { return mDark; } + public boolean isRemoved() { + return false; + } + /** * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about * the upcoming state of hiding sensitive notifications. It gets called at the very beginning diff --git a/com/android/systemui/statusbar/NotificationBackgroundView.java b/com/android/systemui/statusbar/NotificationBackgroundView.java index 81a99bc1..68cf51c0 100644 --- a/com/android/systemui/statusbar/NotificationBackgroundView.java +++ b/com/android/systemui/statusbar/NotificationBackgroundView.java @@ -19,37 +19,57 @@ package com.android.systemui.statusbar; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Canvas; -import android.graphics.ColorFilter; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.RippleDrawable; import android.util.AttributeSet; import android.view.View; +import com.android.systemui.R; + /** * A view that can be used for both the dimmed and normal background of an notification. */ public class NotificationBackgroundView extends View { + private final boolean mDontModifyCorners; private Drawable mBackground; private int mClipTopAmount; private int mActualHeight; private int mClipBottomAmount; private int mTintColor; + private float[] mCornerRadii = new float[8]; + private int mCurrentSidePaddings; + private boolean mBottomIsRounded; + private int mBackgroundTop; + private boolean mBottomAmountClips = true; public NotificationBackgroundView(Context context, AttributeSet attrs) { super(context, attrs); + mDontModifyCorners = getResources().getBoolean( + R.bool.config_clipNotificationsToOutline); } @Override protected void onDraw(Canvas canvas) { - draw(canvas, mBackground); + if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop) { + canvas.save(); + canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount); + draw(canvas, mBackground); + canvas.restore(); + } } private void draw(Canvas canvas, Drawable drawable) { - int bottom = mActualHeight - mClipBottomAmount; - if (drawable != null && bottom > mClipTopAmount) { - drawable.setBounds(0, mClipTopAmount, getWidth(), bottom); + if (drawable != null) { + int bottom = mActualHeight; + if (mBottomIsRounded && mBottomAmountClips) { + bottom -= mClipBottomAmount; + } + drawable.setBounds(mCurrentSidePaddings, mBackgroundTop, + getWidth() - mCurrentSidePaddings, bottom); drawable.draw(canvas); } } @@ -87,6 +107,7 @@ public class NotificationBackgroundView extends View { unscheduleDrawable(mBackground); } mBackground = background; + mBackground.mutate(); if (mBackground != null) { mBackground.setCallback(this); setTint(mTintColor); @@ -94,6 +115,7 @@ public class NotificationBackgroundView extends View { if (mBackground instanceof RippleDrawable) { ((RippleDrawable) mBackground).setForceSoftware(true); } + updateBackgroundRadii(); invalidate(); } @@ -152,4 +174,45 @@ public class NotificationBackgroundView extends View { public void setDrawableAlpha(int drawableAlpha) { mBackground.setAlpha(drawableAlpha); } + + public void setRoundness(float topRoundness, float bottomRoundNess) { + mBottomIsRounded = bottomRoundNess != 0.0f; + mCornerRadii[0] = topRoundness; + mCornerRadii[1] = topRoundness; + mCornerRadii[2] = topRoundness; + mCornerRadii[3] = topRoundness; + mCornerRadii[4] = bottomRoundNess; + mCornerRadii[5] = bottomRoundNess; + mCornerRadii[6] = bottomRoundNess; + mCornerRadii[7] = bottomRoundNess; + updateBackgroundRadii(); + } + + public void setBottomAmountClips(boolean clips) { + if (clips != mBottomAmountClips) { + mBottomAmountClips = clips; + invalidate(); + } + } + + private void updateBackgroundRadii() { + if (mDontModifyCorners) { + return; + } + if (mBackground instanceof LayerDrawable) { + GradientDrawable gradientDrawable = + (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0); + gradientDrawable.setCornerRadii(mCornerRadii); + } + } + + public void setCurrentSidePaddings(float currentSidePaddings) { + mCurrentSidePaddings = (int) currentSidePaddings; + invalidate(); + } + + public void setBackgroundTop(int backgroundTop) { + mBackgroundTop = backgroundTop; + invalidate(); + } } diff --git a/com/android/systemui/statusbar/NotificationContentView.java b/com/android/systemui/statusbar/NotificationContentView.java index 9e059c89..39c21313 100644 --- a/com/android/systemui/statusbar/NotificationContentView.java +++ b/com/android/systemui/statusbar/NotificationContentView.java @@ -24,6 +24,7 @@ import android.graphics.Rect; import android.os.Build; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; +import android.util.Log; import android.view.NotificationHeaderView; import android.view.View; import android.view.ViewGroup; @@ -34,8 +35,8 @@ import android.widget.ImageView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.NotificationColorUtil; import com.android.systemui.R; -import com.android.systemui.statusbar.notification.HybridNotificationView; import com.android.systemui.statusbar.notification.HybridGroupManager; +import com.android.systemui.statusbar.notification.HybridNotificationView; import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.NotificationViewWrapper; @@ -49,6 +50,7 @@ import com.android.systemui.statusbar.policy.RemoteInputView; */ public class NotificationContentView extends FrameLayout { + private static final String TAG = "NotificationContentView"; public static final int VISIBLE_TYPE_CONTRACTED = 0; public static final int VISIBLE_TYPE_EXPANDED = 1; public static final int VISIBLE_TYPE_HEADSUP = 2; @@ -58,9 +60,9 @@ public class NotificationContentView extends FrameLayout { public static final int UNDEFINED = -1; private final Rect mClipBounds = new Rect(); - private final int mMinContractedHeight; - private final int mNotificationContentMarginEnd; + private int mMinContractedHeight; + private int mNotificationContentMarginEnd; private View mContractedChild; private View mExpandedChild; private View mHeadsUpChild; @@ -134,15 +136,22 @@ public class NotificationContentView extends FrameLayout { private int mClipBottomAmount; private boolean mIsLowPriority; private boolean mIsContentExpandable; + private int mCustomViewSidePaddings; public NotificationContentView(Context context, AttributeSet attrs) { super(context, attrs); mHybridGroupManager = new HybridGroupManager(getContext(), this); + initView(); + } + + public void initView() { mMinContractedHeight = getResources().getDimensionPixelSize( R.dimen.min_notification_layout_height); mNotificationContentMarginEnd = getResources().getDimensionPixelSize( com.android.internal.R.dimen.notification_content_margin_end); + mCustomViewSidePaddings = getResources().getDimensionPixelSize( + R.dimen.notification_content_custom_view_side_padding); } public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight, @@ -178,7 +187,7 @@ public class NotificationContentView extends FrameLayout { : MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY : MeasureSpec.AT_MOST); - mExpandedChild.measure(widthMeasureSpec, spec); + measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, spec, 0); maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight()); } if (mContractedChild != null) { @@ -196,22 +205,22 @@ public class NotificationContentView extends FrameLayout { } else { heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST); } - mContractedChild.measure(widthMeasureSpec, heightSpec); + measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0); int measuredHeight = mContractedChild.getMeasuredHeight(); if (measuredHeight < mMinContractedHeight) { heightSpec = MeasureSpec.makeMeasureSpec(mMinContractedHeight, MeasureSpec.EXACTLY); - mContractedChild.measure(widthMeasureSpec, heightSpec); + measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0); } maxChildHeight = Math.max(maxChildHeight, measuredHeight); if (updateContractedHeaderWidth()) { - mContractedChild.measure(widthMeasureSpec, heightSpec); + measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0); } if (mExpandedChild != null && mContractedChild.getMeasuredHeight() > mExpandedChild.getMeasuredHeight()) { // the Expanded child is smaller then the collapsed. Let's remeasure it. heightSpec = MeasureSpec.makeMeasureSpec(mContractedChild.getMeasuredHeight(), MeasureSpec.EXACTLY); - mExpandedChild.measure(widthMeasureSpec, heightSpec); + measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, heightSpec, 0); } } if (mHeadsUpChild != null) { @@ -223,9 +232,9 @@ public class NotificationContentView extends FrameLayout { size = Math.min(size, layoutParams.height); useExactly = true; } - mHeadsUpChild.measure(widthMeasureSpec, + measureChildWithMargins(mHeadsUpChild, widthMeasureSpec, 0, MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY - : MeasureSpec.AT_MOST)); + : MeasureSpec.AT_MOST), 0); maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight()); } if (mSingleLineView != null) { @@ -382,6 +391,38 @@ public class NotificationContentView extends FrameLayout { mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child, mContainingNotification); mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */); + updateMargins(child); + } + + private void updateMargins(View child) { + if (child == null) { + return; + } + NotificationViewWrapper wrapper = getWrapperForView(child); + boolean isCustomView = wrapper instanceof NotificationCustomViewWrapper; + boolean needsMargins = isCustomView && + child.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P; + int padding = needsMargins ? mCustomViewSidePaddings : 0; + MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams(); + layoutParams.setMarginStart(padding); + layoutParams.setMarginEnd(padding); + child.setLayoutParams(layoutParams); + } + + private NotificationViewWrapper getWrapperForView(View child) { + if (child == mContractedChild) { + return mContractedWrapper; + } + if (child == mExpandedChild) { + return mExpandedWrapper; + } + if (child == mHeadsUpChild) { + return mHeadsUpWrapper; + } + if (child == mAmbientChild) { + return mAmbientWrapper; + } + return null; } public void setExpandedChild(View child) { @@ -415,6 +456,7 @@ public class NotificationContentView extends FrameLayout { mExpandedChild = child; mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child, mContainingNotification); + updateMargins(child); } public void setHeadsUpChild(View child) { @@ -448,6 +490,7 @@ public class NotificationContentView extends FrameLayout { mHeadsUpChild = child; mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child, mContainingNotification); + updateMargins(child); } public void setAmbientChild(View child) { @@ -643,6 +686,13 @@ public class NotificationContentView extends FrameLayout { int endHeight = getViewForVisibleType(mVisibleType).getHeight(); int progress = Math.abs(mContentHeight - startHeight); int totalDistance = Math.abs(endHeight - startHeight); + if (totalDistance == 0) { + Log.wtf(TAG, "the total transformation distance is 0" + + "\n StartType: " + mTransformationStartVisibleType + " height: " + startHeight + + "\n VisibleType: " + mVisibleType + " height: " + endHeight + + "\n mContentHeight: " + mContentHeight); + return 1.0f; + } float amount = (float) progress / (float) totalDistance; return Math.min(1.0f, amount); } @@ -1459,4 +1509,20 @@ public class NotificationContentView extends FrameLayout { } return false; } + + public boolean shouldClipToSidePaddings() { + boolean needsPaddings = shouldClipToSidePaddings(getVisibleType()); + if (mUserExpanding) { + needsPaddings |= shouldClipToSidePaddings(mTransformationStartVisibleType); + } + return needsPaddings; + } + + private boolean shouldClipToSidePaddings(int visibleType) { + NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType); + if (visibleWrapper == null) { + return false; + } + return visibleWrapper.shouldClipToSidePaddings(); + } } diff --git a/com/android/systemui/statusbar/NotificationGutsManager.java b/com/android/systemui/statusbar/NotificationGutsManager.java index b585bdff..f451fda6 100644 --- a/com/android/systemui/statusbar/NotificationGutsManager.java +++ b/com/android/systemui/statusbar/NotificationGutsManager.java @@ -37,6 +37,7 @@ import android.view.accessibility.AccessibilityManager; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.Interpolators; @@ -75,17 +76,20 @@ public class NotificationGutsManager implements Dumpable { private NotificationGuts mNotificationGutsExposed; private NotificationMenuRowPlugin.MenuItem mGutsMenuItem; private final NotificationInfo.CheckSaveListener mCheckSaveListener; + private final OnSettingsClickListener mOnSettingsClickListener; private String mKeyToRemoveOnGutsClosed; public NotificationGutsManager( NotificationPresenter presenter, NotificationStackScrollLayout stackScroller, NotificationInfo.CheckSaveListener checkSaveListener, - Context context) { + Context context, + OnSettingsClickListener onSettingsClickListener) { mPresenter = presenter; mStackScroller = stackScroller; mCheckSaveListener = checkSaveListener; mContext = context; + mOnSettingsClickListener = onSettingsClickListener; Resources res = context.getResources(); mNonBlockablePkgs = new HashSet<>(); @@ -189,6 +193,7 @@ public class NotificationGutsManager implements Dumpable { onSettingsClick = (View v, NotificationChannel channel, int appUid) -> { mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO); guts.resetFalsingCheck(); + mOnSettingsClickListener.onClick(sbn.getKey()); startAppNotificationSettingsActivity(pkg, appUid, channel); }; } @@ -352,4 +357,8 @@ public class NotificationGutsManager implements Dumpable { pw.print("mKeyToRemoveOnGutsClosed: "); pw.println(mKeyToRemoveOnGutsClosed); } + + public interface OnSettingsClickListener { + void onClick(String key); + } } diff --git a/com/android/systemui/statusbar/NotificationMenuRow.java b/com/android/systemui/statusbar/NotificationMenuRow.java index 99b4b079..b2604fe0 100644 --- a/com/android/systemui/statusbar/NotificationMenuRow.java +++ b/com/android/systemui/statusbar/NotificationMenuRow.java @@ -88,6 +88,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl private float mHorizSpaceForIcon = -1; private int mVertSpaceForIcons = -1; private int mIconPadding = -1; + private int mSidePadding; private float mAlpha = 0f; private float mPrevX; @@ -175,6 +176,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl final Resources res = mContext.getResources(); mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size); mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height); + mSidePadding = res.getDimensionPixelSize(R.dimen.notification_lockscreen_side_paddings); mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding); mMenuItems.clear(); // Construct the menu items based on the notification @@ -496,8 +498,8 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl final int count = mMenuContainer.getChildCount(); for (int i = 0; i < count; i++) { final View v = mMenuContainer.getChildAt(i); - final float left = i * mHorizSpaceForIcon; - final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1)); + final float left = mSidePadding + i * mHorizSpaceForIcon; + final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1)) - mSidePadding; v.setX(showOnLeft ? left : right); } mOnLeft = showOnLeft; diff --git a/com/android/systemui/statusbar/NotificationShelf.java b/com/android/systemui/statusbar/NotificationShelf.java index 5557dde7..b7a00ebc 100644 --- a/com/android/systemui/statusbar/NotificationShelf.java +++ b/com/android/systemui/statusbar/NotificationShelf.java @@ -85,6 +85,7 @@ public class NotificationShelf extends ActivatableNotificationView implements private boolean mVibrationOnAnimation; private boolean mUserTouchingScreen; private boolean mTouchActive; + private float mFirstElementRoundness; public NotificationShelf(Context context, AttributeSet attrs) { super(context, attrs); @@ -107,6 +108,7 @@ public class NotificationShelf extends ActivatableNotificationView implements mViewInvertHelper = new ViewInvertHelper(mShelfIcons, NotificationPanelView.DOZE_ANIMATION_DURATION); mShelfState = new ShelfState(); + setBottomRoundness(1.0f, false /* animate */); initDimens(); } @@ -252,6 +254,8 @@ public class NotificationShelf extends ActivatableNotificationView implements boolean expandingAnimated = mAmbientState.isExpansionChanging() && !mAmbientState.isPanelTracking(); int baseZHeight = mAmbientState.getBaseZHeight(); + int backgroundTop = 0; + float firstElementRoundness = 0.0f; while (notificationIndex < mHostLayout.getChildCount()) { ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex); notificationIndex++; @@ -302,9 +306,20 @@ public class NotificationShelf extends ActivatableNotificationView implements if (notGoneIndex != 0 || !aboveShelf) { row.setAboveShelf(false); } + if (notGoneIndex == 0) { + StatusBarIconView icon = row.getEntry().expandedIcon; + NotificationIconContainer.IconState iconState = getIconState(icon); + if (iconState.clampedAppearAmount == 1.0f) { + // only if the first icon is fully in the shelf we want to clip to it! + backgroundTop = (int) (row.getTranslationY() - getTranslationY()); + firstElementRoundness = row.getCurrentTopRoundness(); + } + } notGoneIndex++; previousColor = ownColorUntinted; } + setBackgroundTop(backgroundTop); + setFirstElementRoundness(firstElementRoundness); mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex()); mShelfIcons.calculateIconTranslations(); mShelfIcons.applyIconStates(); @@ -325,6 +340,13 @@ public class NotificationShelf extends ActivatableNotificationView implements } } + private void setFirstElementRoundness(float firstElementRoundness) { + if (mFirstElementRoundness != firstElementRoundness) { + mFirstElementRoundness = firstElementRoundness; + setTopRoundness(firstElementRoundness, false /* animate */); + } + } + private void updateIconClipAmount(ExpandableNotificationRow row) { float maxTop = row.getTranslationY(); StatusBarIconView icon = row.getEntry().expandedIcon; diff --git a/com/android/systemui/statusbar/RemoteInputController.java b/com/android/systemui/statusbar/RemoteInputController.java index 7f28c4c2..ff6c775c 100644 --- a/com/android/systemui/statusbar/RemoteInputController.java +++ b/com/android/systemui/statusbar/RemoteInputController.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar; import com.android.internal.util.Preconditions; import com.android.systemui.Dependency; import com.android.systemui.statusbar.phone.StatusBarWindowManager; -import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.RemoteInputView; import android.util.ArrayMap; @@ -38,11 +37,11 @@ public class RemoteInputController { = new ArrayList<>(); private final ArrayMap<String, Object> mSpinning = new ArrayMap<>(); private final ArrayList<Callback> mCallbacks = new ArrayList<>(3); - private final HeadsUpManager mHeadsUpManager; + private final Delegate mDelegate; - public RemoteInputController(HeadsUpManager headsUpManager) { + public RemoteInputController(Delegate delegate) { addCallback(Dependency.get(StatusBarWindowManager.class)); - mHeadsUpManager = headsUpManager; + mDelegate = delegate; } /** @@ -114,7 +113,7 @@ public class RemoteInputController { } private void apply(NotificationData.Entry entry) { - mHeadsUpManager.setRemoteInputActive(entry, isRemoteInputActive(entry)); + mDelegate.setRemoteInputActive(entry, isRemoteInputActive(entry)); boolean remoteInputActive = isRemoteInputActive(); int N = mCallbacks.size(); for (int i = 0; i < N; i++) { @@ -204,9 +203,35 @@ public class RemoteInputController { } } + public void requestDisallowLongPressAndDismiss() { + mDelegate.requestDisallowLongPressAndDismiss(); + } + + public void lockScrollTo(NotificationData.Entry entry) { + mDelegate.lockScrollTo(entry); + } + public interface Callback { default void onRemoteInputActive(boolean active) {} default void onRemoteInputSent(NotificationData.Entry entry) {} } + + public interface Delegate { + /** + * Activate remote input if necessary. + */ + void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive); + + /** + * Request that the view does not dismiss nor perform long press for the current touch. + */ + void requestDisallowLongPressAndDismiss(); + + /** + * Request that the view is made visible by scrolling to it, and keep the scroll locked until + * the user scrolls, or {@param v} loses focus or is detached. + */ + void lockScrollTo(NotificationData.Entry entry); + } } diff --git a/com/android/systemui/statusbar/ScrimView.java b/com/android/systemui/statusbar/ScrimView.java index a53e348f..88303522 100644 --- a/com/android/systemui/statusbar/ScrimView.java +++ b/com/android/systemui/statusbar/ScrimView.java @@ -41,6 +41,7 @@ import android.view.animation.Interpolator; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.colorextraction.drawable.GradientDrawable; +import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -50,6 +51,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; public class ScrimView extends View implements ConfigurationController.ConfigurationListener { private static final String TAG = "ScrimView"; private final ColorExtractor.GradientColors mColors; + private int mDensity; private boolean mDrawAsSrc; private float mViewAlpha = 1.0f; private ValueAnimator mAlphaAnimator; @@ -72,6 +74,7 @@ public class ScrimView extends View implements ConfigurationController.Configura } }; private Runnable mChangeRunnable; + private int mCornerRadius; public ScrimView(Context context) { this(context, null); @@ -93,6 +96,24 @@ public class ScrimView extends View implements ConfigurationController.Configura mColors = new ColorExtractor.GradientColors(); updateScreenSize(); updateColorWithTint(false); + initView(); + final Configuration currentConfig = mContext.getResources().getConfiguration(); + mDensity = currentConfig.densityDpi; + } + + private void initView() { + mCornerRadius = getResources().getDimensionPixelSize( + Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + int densityDpi = newConfig.densityDpi; + if (mDensity != densityDpi) { + mDensity = densityDpi; + initView(); + } } @Override @@ -145,6 +166,28 @@ public class ScrimView extends View implements ConfigurationController.Configura mDrawable.draw(canvas); canvas.restore(); } + // We also need to draw the rounded corners of the background + canvas.save(); + canvas.clipRect(mExcludedRect.left, mExcludedRect.top, + mExcludedRect.left + mCornerRadius, mExcludedRect.top + mCornerRadius); + mDrawable.draw(canvas); + canvas.restore(); + canvas.save(); + canvas.clipRect(mExcludedRect.right - mCornerRadius, mExcludedRect.top, + mExcludedRect.right, mExcludedRect.top + mCornerRadius); + mDrawable.draw(canvas); + canvas.restore(); + canvas.save(); + canvas.clipRect(mExcludedRect.left, mExcludedRect.bottom - mCornerRadius, + mExcludedRect.left + mCornerRadius, mExcludedRect.bottom); + mDrawable.draw(canvas); + canvas.restore(); + canvas.save(); + canvas.clipRect(mExcludedRect.right - mCornerRadius, + mExcludedRect.bottom - mCornerRadius, + mExcludedRect.right, mExcludedRect.bottom); + mDrawable.draw(canvas); + canvas.restore(); } } } @@ -252,6 +295,13 @@ public class ScrimView extends View implements ConfigurationController.Configura return false; } + /** + * It might look counterintuitive to have another method to set the alpha instead of + * only using {@link #setAlpha(float)}. In this case we're in a hardware layer + * optimizing blend modes, so it makes sense. + * + * @param alpha Gradient alpha from 0 to 1. + */ public void setViewAlpha(float alpha) { if (alpha != mViewAlpha) { mViewAlpha = alpha; diff --git a/com/android/systemui/statusbar/car/CarNavigationBarController.java b/com/android/systemui/statusbar/car/CarNavigationBarController.java index f5c77f26..64c52ed6 100644 --- a/com/android/systemui/statusbar/car/CarNavigationBarController.java +++ b/com/android/systemui/statusbar/car/CarNavigationBarController.java @@ -369,7 +369,7 @@ class CarNavigationBarController { private void onFacetClicked(Intent intent, int index) { String packageName = intent.getPackage(); - if (packageName == null) { + if (packageName == null && !intent.getCategories().contains(Intent.CATEGORY_HOME)) { return; } diff --git a/com/android/systemui/statusbar/notification/AnimatableProperty.java b/com/android/systemui/statusbar/notification/AnimatableProperty.java new file mode 100644 index 00000000..d7b211f9 --- /dev/null +++ b/com/android/systemui/statusbar/notification/AnimatableProperty.java @@ -0,0 +1,77 @@ +/* + * 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 com.android.systemui.statusbar.notification; + +import android.util.FloatProperty; +import android.util.Property; +import android.view.View; + +import com.android.systemui.statusbar.stack.AnimationProperties; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * An animatable property of a view. Used with {@link PropertyAnimator} + */ +public interface AnimatableProperty { + int getAnimationStartTag(); + + int getAnimationEndTag(); + + int getAnimatorTag(); + + Property getProperty(); + + static <T extends View> AnimatableProperty from(String name, BiConsumer<T, Float> setter, + Function<T, Float> getter, int animatorTag, int startValueTag, int endValueTag) { + Property<T, Float> property = new FloatProperty<T>(name) { + + @Override + public Float get(T object) { + return getter.apply(object); + } + + @Override + public void setValue(T object, float value) { + setter.accept(object, value); + } + }; + return new AnimatableProperty() { + @Override + public int getAnimationStartTag() { + return startValueTag; + } + + @Override + public int getAnimationEndTag() { + return endValueTag; + } + + @Override + public int getAnimatorTag() { + return animatorTag; + } + + @Override + public Property getProperty() { + return property; + } + }; + } +} diff --git a/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java index fc420eb7..27defcac 100644 --- a/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java +++ b/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java @@ -89,6 +89,7 @@ public class MessagingLayoutTransformState extends TransformState { private void transformViewInternal(MessagingLayoutTransformState mlt, float transformationAmount, boolean to) { + ensureVisible(); ArrayList<MessagingGroup> ownGroups = filterHiddenGroups( mMessagingLayout.getMessagingGroups()); ArrayList<MessagingGroup> otherGroups = filterHiddenGroups( @@ -332,6 +333,7 @@ public class MessagingLayoutTransformState extends TransformState { @Override public void setVisible(boolean visible, boolean force) { + super.setVisible(visible, force); resetTransformedView(); ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups(); for (int i = 0; i < ownGroups.size(); i++) { diff --git a/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java index bca4b43a..66682e4c 100644 --- a/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java +++ b/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; +import android.os.Build; import android.view.View; import com.android.systemui.R; @@ -37,6 +38,7 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { private final Paint mGreyPaint = new Paint(); private boolean mIsLegacy; private int mLegacyColor; + private boolean mBeforeP; protected NotificationCustomViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { super(ctx, view, row); @@ -115,4 +117,17 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { super.setLegacy(legacy); mIsLegacy = legacy; } + + @Override + public boolean shouldClipToSidePaddings() { + // Before P we ensure that they are now drawing inside out content bounds since we inset + // the view. If they target P, then we don't have that guarantee and we need to be safe. + return !mBeforeP; + } + + @Override + public void onContentUpdated(ExpandableNotificationRow row) { + super.onContentUpdated(row); + mBeforeP = row.getEntry().targetSdk < Build.VERSION_CODES.P; + } } diff --git a/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java index eb211a10..060e6d65 100644 --- a/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java +++ b/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java @@ -58,6 +58,11 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi @Override public boolean isDimmable() { - return false; + return getCustomBackgroundColor() == 0; + } + + @Override + public boolean shouldClipToSidePaddings() { + return true; } } diff --git a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java index fd085d9c..e07112f9 100644 --- a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java +++ b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java @@ -265,6 +265,11 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp updateActionOffset(); } + @Override + public boolean shouldClipToSidePaddings() { + return mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE; + } + private void updateActionOffset() { if (mActionsContainer != null) { // We should never push the actions higher than they are in the headsup view. diff --git a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java index 1cd5f15b..8a767bb7 100644 --- a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java +++ b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java @@ -194,4 +194,8 @@ public abstract class NotificationViewWrapper implements TransformableView { public int getMinLayoutHeight() { return 0; } + + public boolean shouldClipToSidePaddings() { + return false; + } } diff --git a/com/android/systemui/statusbar/notification/PropertyAnimator.java b/com/android/systemui/statusbar/notification/PropertyAnimator.java index 80ba9439..92dcc9e3 100644 --- a/com/android/systemui/statusbar/notification/PropertyAnimator.java +++ b/com/android/systemui/statusbar/notification/PropertyAnimator.java @@ -34,6 +34,19 @@ import com.android.systemui.statusbar.stack.ViewState; */ public class PropertyAnimator { + public static <T extends View> void setProperty(final T view, + AnimatableProperty animatableProperty, float newEndValue, + AnimationProperties properties, boolean animated) { + int animatorTag = animatableProperty.getAnimatorTag(); + ValueAnimator previousAnimator = ViewState.getChildTag(view, animatorTag); + if (previousAnimator != null || animated) { + startAnimation(view, animatableProperty, newEndValue, properties); + } else { + // no new animation needed, let's just apply the value + animatableProperty.getProperty().set(view, newEndValue); + } + } + public static <T extends View> void startAnimation(final T view, AnimatableProperty animatableProperty, float newEndValue, AnimationProperties properties) { @@ -102,10 +115,4 @@ public class PropertyAnimator { view.setTag(animationEndTag, newEndValue); } - public interface AnimatableProperty { - int getAnimationStartTag(); - int getAnimationEndTag(); - int getAnimatorTag(); - Property getProperty(); - } } diff --git a/com/android/systemui/statusbar/notification/TransformState.java b/com/android/systemui/statusbar/notification/TransformState.java index ad07af0a..dec5303f 100644 --- a/com/android/systemui/statusbar/notification/TransformState.java +++ b/com/android/systemui/statusbar/notification/TransformState.java @@ -95,18 +95,22 @@ public class TransformState { public void transformViewFrom(TransformState otherState, float transformationAmount) { mTransformedView.animate().cancel(); if (sameAs(otherState)) { - if (mTransformedView.getVisibility() == View.INVISIBLE - || mTransformedView.getAlpha() != 1.0f) { - // We have the same content, lets show ourselves - mTransformedView.setAlpha(1.0f); - mTransformedView.setVisibility(View.VISIBLE); - } + ensureVisible(); } else { CrossFadeHelper.fadeIn(mTransformedView, transformationAmount); } transformViewFullyFrom(otherState, transformationAmount); } + protected void ensureVisible() { + if (mTransformedView.getVisibility() == View.INVISIBLE + || mTransformedView.getAlpha() != 1.0f) { + // We have the same content, lets show ourselves + mTransformedView.setAlpha(1.0f); + mTransformedView.setVisibility(View.VISIBLE); + } + } + public void transformViewFullyFrom(TransformState otherState, float transformationAmount) { transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount); } diff --git a/com/android/systemui/statusbar/phone/DozeParameters.java b/com/android/systemui/statusbar/phone/DozeParameters.java index 6b7397b3..3f57c2f9 100644 --- a/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/com/android/systemui/statusbar/phone/DozeParameters.java @@ -46,10 +46,8 @@ public class DozeParameters { public void dump(PrintWriter pw) { pw.println(" DozeParameters:"); pw.print(" getDisplayStateSupported(): "); pw.println(getDisplayStateSupported()); - pw.print(" getPulseDuration(pickup=false): "); pw.println(getPulseDuration(false)); - pw.print(" getPulseDuration(pickup=true): "); pw.println(getPulseDuration(true)); - pw.print(" getPulseInDuration(pickup=false): "); pw.println(getPulseInDuration(false)); - pw.print(" getPulseInDuration(pickup=true): "); pw.println(getPulseInDuration(true)); + pw.print(" getPulseDuration(): "); pw.println(getPulseDuration()); + pw.print(" getPulseInDuration(): "); pw.println(getPulseInDuration()); pw.print(" getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration()); pw.print(" getPulseOutDuration(): "); pw.println(getPulseOutDuration()); pw.print(" getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion()); @@ -81,14 +79,12 @@ public class DozeParameters { return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported); } - public int getPulseDuration(boolean pickup) { - return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration(); + public int getPulseDuration() { + return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration(); } - public int getPulseInDuration(boolean pickupOrDoubleTap) { - return pickupOrDoubleTap - ? getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup) - : getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in); + public int getPulseInDuration() { + return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in); } public int getPulseVisibleDuration() { diff --git a/com/android/systemui/statusbar/phone/DozeScrimController.java b/com/android/systemui/statusbar/phone/DozeScrimController.java index 8afb8490..1011383b 100644 --- a/com/android/systemui/statusbar/phone/DozeScrimController.java +++ b/com/android/systemui/statusbar/phone/DozeScrimController.java @@ -16,16 +16,11 @@ package com.android.systemui.statusbar.phone; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.annotation.NonNull; import android.content.Context; import android.os.Handler; import android.util.Log; -import android.view.animation.Interpolator; -import com.android.systemui.Interpolators; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; @@ -40,74 +35,59 @@ public class DozeScrimController { private final Handler mHandler = new Handler(); private final ScrimController mScrimController; - private final Context mContext; - private boolean mDozing; private DozeHost.PulseCallback mPulseCallback; private int mPulseReason; - private Animator mInFrontAnimator; - private Animator mBehindAnimator; - private float mInFrontTarget; - private float mBehindTarget; - private boolean mDozingAborted; - private boolean mWakeAndUnlocking; private boolean mFullyPulsing; - private float mAodFrontScrimOpacity = 0; - private Runnable mSetDozeInFrontAlphaDelayed; + private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() { + @Override + public void onDisplayBlanked() { + if (DEBUG) { + Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason=" + + DozeLog.pulseReasonToString(mPulseReason)); + } + if (!mDozing) { + return; + } + + // Signal that the pulse is ready to turn the screen on and draw. + pulseStarted(); + } + + @Override + public void onFinished() { + if (DEBUG) { + Log.d(TAG, "Pulse in finished, mDozing=" + mDozing); + } + if (!mDozing) { + return; + } + mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration()); + mHandler.postDelayed(mPulseOutExtended, + mDozeParameters.getPulseVisibleDurationExtended()); + mFullyPulsing = true; + } + + /** + * Transition was aborted before it was over. + */ + @Override + public void onCancelled() { + pulseFinished(); + } + }; public DozeScrimController(ScrimController scrimController, Context context) { - mContext = context; mScrimController = scrimController; mDozeParameters = new DozeParameters(context); } - public void setDozing(boolean dozing, boolean animate) { + public void setDozing(boolean dozing) { if (mDozing == dozing) return; mDozing = dozing; - mWakeAndUnlocking = false; - if (mDozing) { - mDozingAborted = false; - abortAnimations(); - mScrimController.setDozeBehindAlpha(1f); - setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() ? mAodFrontScrimOpacity : 1f); - } else { + if (!mDozing) { cancelPulsing(); - if (animate) { - startScrimAnimation(false /* inFront */, 0f /* target */, - NotificationPanelView.DOZE_ANIMATION_DURATION, - Interpolators.LINEAR_OUT_SLOW_IN); - startScrimAnimation(true /* inFront */, 0f /* target */, - NotificationPanelView.DOZE_ANIMATION_DURATION, - Interpolators.LINEAR_OUT_SLOW_IN); - } else { - abortAnimations(); - mScrimController.setDozeBehindAlpha(0f); - setDozeInFrontAlpha(0f); - } - } - } - - /** - * Set the opacity of the front scrim when showing AOD1 - * - * Used to emulate lower brightness values than the hardware supports natively. - */ - public void setAodDimmingScrim(float scrimOpacity) { - mAodFrontScrimOpacity = scrimOpacity; - if (mDozing && !isPulsing() && !mDozingAborted && !mWakeAndUnlocking - && mDozeParameters.getAlwaysOn()) { - setDozeInFrontAlpha(mAodFrontScrimOpacity); - } - } - - public void setWakeAndUnlocking() { - // Immediately abort the doze scrims in case of wake-and-unlock - // for pulsing so the Keyguard fade-out animation scrim can take over. - if (!mWakeAndUnlocking) { - mWakeAndUnlocking = true; - mScrimController.setDozeBehindAlpha(0f); - setDozeInFrontAlpha(0f); } } @@ -118,37 +98,21 @@ public class DozeScrimController { } if (!mDozing || mPulseCallback != null) { + if (DEBUG) { + Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? " + + (mPulseCallback != null)); + } // Pulse suppressed. callback.onPulseFinished(); return; } - // Begin pulse. Note that it's very important that the pulse finished callback + // Begin pulse. Note that it's very important that the pulse finished callback // be invoked when we're done so that the caller can drop the pulse wakelock. mPulseCallback = callback; mPulseReason = reason; - setDozeInFrontAlpha(1f); - mHandler.post(mPulseIn); - } - - /** - * Aborts pulsing immediately. - */ - public void abortPulsing() { - cancelPulsing(); - if (mDozing && !mWakeAndUnlocking) { - mScrimController.setDozeBehindAlpha(1f); - setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() && !mDozingAborted - ? mAodFrontScrimOpacity : 1f); - } - } - /** - * Aborts dozing immediately. - */ - public void abortDoze() { - mDozingAborted = true; - abortPulsing(); + mScrimController.transitionTo(ScrimState.PULSING, mScrimCallback); } public void pulseOutNow() { @@ -157,17 +121,6 @@ public class DozeScrimController { } } - public void onScreenTurnedOn() { - if (isPulsing()) { - final boolean pickupOrDoubleTap = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP - || mPulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP; - startScrimAnimation(true /* inFront */, 0f, - mDozeParameters.getPulseInDuration(pickupOrDoubleTap), - pickupOrDoubleTap ? Interpolators.LINEAR_OUT_SLOW_IN : Interpolators.ALPHA_OUT, - mPulseInFinished); - } - } - public boolean isPulsing() { return mPulseCallback != null; } @@ -181,11 +134,9 @@ public class DozeScrimController { } private void cancelPulsing() { - if (DEBUG) Log.d(TAG, "Cancel pulsing"); - if (mPulseCallback != null) { + if (DEBUG) Log.d(TAG, "Cancel pulsing"); mFullyPulsing = false; - mHandler.removeCallbacks(mPulseIn); mHandler.removeCallbacks(mPulseOut); mHandler.removeCallbacks(mPulseOutExtended); pulseFinished(); @@ -193,151 +144,20 @@ public class DozeScrimController { } private void pulseStarted() { + DozeLog.tracePulseStart(mPulseReason); if (mPulseCallback != null) { mPulseCallback.onPulseStarted(); } } private void pulseFinished() { + DozeLog.tracePulseFinish(); if (mPulseCallback != null) { mPulseCallback.onPulseFinished(); mPulseCallback = null; } } - private void abortAnimations() { - if (mInFrontAnimator != null) { - mInFrontAnimator.cancel(); - } - if (mBehindAnimator != null) { - mBehindAnimator.cancel(); - } - } - - private void startScrimAnimation(final boolean inFront, float target, long duration, - Interpolator interpolator) { - startScrimAnimation(inFront, target, duration, interpolator, null /* endRunnable */); - } - - private void startScrimAnimation(final boolean inFront, float target, long duration, - Interpolator interpolator, final Runnable endRunnable) { - Animator current = getCurrentAnimator(inFront); - if (current != null) { - float currentTarget = getCurrentTarget(inFront); - if (currentTarget == target) { - return; - } - current.cancel(); - } - ValueAnimator anim = ValueAnimator.ofFloat(getDozeAlpha(inFront), target); - anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (float) animation.getAnimatedValue(); - setDozeAlpha(inFront, value); - } - }); - anim.setInterpolator(interpolator); - anim.setDuration(duration); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - setCurrentAnimator(inFront, null); - if (endRunnable != null) { - endRunnable.run(); - } - } - }); - anim.start(); - setCurrentAnimator(inFront, anim); - setCurrentTarget(inFront, target); - } - - private float getCurrentTarget(boolean inFront) { - return inFront ? mInFrontTarget : mBehindTarget; - } - - private void setCurrentTarget(boolean inFront, float target) { - if (inFront) { - mInFrontTarget = target; - } else { - mBehindTarget = target; - } - } - - private Animator getCurrentAnimator(boolean inFront) { - return inFront ? mInFrontAnimator : mBehindAnimator; - } - - private void setCurrentAnimator(boolean inFront, Animator animator) { - if (inFront) { - mInFrontAnimator = animator; - } else { - mBehindAnimator = animator; - } - } - - private void setDozeAlpha(boolean inFront, float alpha) { - if (mWakeAndUnlocking) { - return; - } - if (inFront) { - mScrimController.setDozeInFrontAlpha(alpha); - } else { - mScrimController.setDozeBehindAlpha(alpha); - } - } - - private float getDozeAlpha(boolean inFront) { - return inFront - ? mScrimController.getDozeInFrontAlpha() - : mScrimController.getDozeBehindAlpha(); - } - - private void setDozeInFrontAlpha(float opacity) { - setDozeInFrontAlphaDelayed(opacity, 0 /* delay */); - - } - - private void setDozeInFrontAlphaDelayed(float opacity, long delayMs) { - if (mSetDozeInFrontAlphaDelayed != null) { - mHandler.removeCallbacks(mSetDozeInFrontAlphaDelayed); - mSetDozeInFrontAlphaDelayed = null; - } - if (delayMs <= 0) { - mScrimController.setDozeInFrontAlpha(opacity); - } else { - mHandler.postDelayed(mSetDozeInFrontAlphaDelayed = () -> { - setDozeInFrontAlpha(opacity); - }, delayMs); - } - } - - private final Runnable mPulseIn = new Runnable() { - @Override - public void run() { - if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason=" - + DozeLog.pulseReasonToString(mPulseReason)); - if (!mDozing) return; - DozeLog.tracePulseStart(mPulseReason); - - // Signal that the pulse is ready to turn the screen on and draw. - pulseStarted(); - } - }; - - private final Runnable mPulseInFinished = new Runnable() { - @Override - public void run() { - if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing); - if (!mDozing) return; - mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration()); - mHandler.postDelayed(mPulseOutExtended, - mDozeParameters.getPulseVisibleDurationExtended()); - mFullyPulsing = true; - } - }; - private final Runnable mPulseOutExtended = new Runnable() { @Override public void run() { @@ -354,38 +174,13 @@ public class DozeScrimController { mHandler.removeCallbacks(mPulseOutExtended); if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing); if (!mDozing) return; - startScrimAnimation(true /* inFront */, 1, - mDozeParameters.getPulseOutDuration(), - Interpolators.ALPHA_IN, mPulseOutFinishing); - } - }; - - private final Runnable mPulseOutFinishing = new Runnable() { - @Override - public void run() { - if (DEBUG) Log.d(TAG, "Pulse out finished"); - DozeLog.tracePulseFinish(); - if (mDozeParameters.getAlwaysOn() && mDozing) { - // Setting power states can block rendering. For AOD, delay finishing the pulse and - // setting the power state until the fully black scrim had time to hit the - // framebuffer. - mHandler.postDelayed(mPulseOutFinished, 30); - } else { - mPulseOutFinished.run(); - } - } - }; - - private final Runnable mPulseOutFinished = new Runnable() { - @Override - public void run() { - // Signal that the pulse is all finished so we can turn the screen off now. - DozeScrimController.this.pulseFinished(); - if (mDozeParameters.getAlwaysOn()) { - // Setting power states can happen after we push out the frame. Make sure we - // stay fully opaque until the power state request reaches the lower levels. - setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 100); - } + mScrimController.transitionTo(ScrimState.AOD, + new ScrimController.Callback() { + @Override + public void onDisplayBlanked() { + pulseFinished(); + } + }); } }; -} +}
\ No newline at end of file diff --git a/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/com/android/systemui/statusbar/phone/FingerprintUnlockController.java index 91369dbd..80d4061b 100644 --- a/com/android/systemui/statusbar/phone/FingerprintUnlockController.java +++ b/com/android/systemui/statusbar/phone/FingerprintUnlockController.java @@ -181,9 +181,9 @@ public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback { } private boolean pulsingOrAod() { - boolean pulsing = mDozeScrimController.isPulsing(); - boolean dozingWithScreenOn = mStatusBar.isDozing() && !mStatusBar.isScreenFullyOff(); - return pulsing || dozingWithScreenOn; + final ScrimState scrimState = mScrimController.getState(); + return scrimState == ScrimState.AOD + || scrimState == ScrimState.PULSING; } @Override @@ -246,15 +246,12 @@ public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback { true /* allowEnterAnimation */); } else if (mMode == MODE_WAKE_AND_UNLOCK){ Trace.beginSection("MODE_WAKE_AND_UNLOCK"); - mDozeScrimController.abortDoze(); } else { Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM"); mUpdateMonitor.awakenFromDream(); } mStatusBarWindowManager.setStatusBarFocusable(false); mKeyguardViewMediator.onWakeAndUnlocking(); - mScrimController.setWakeAndUnlocking(); - mDozeScrimController.setWakeAndUnlocking(); if (mStatusBar.getNavigationBarView() != null) { mStatusBar.getNavigationBarView().setWakeAndUnlocking(true); } @@ -269,6 +266,7 @@ public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback { } private void showBouncer() { + mScrimController.transitionTo(ScrimState.BOUNCER); mStatusBarKeyguardViewManager.animateCollapsePanels( FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR); mPendingShowBouncer = false; diff --git a/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index a6691b16..da809c12 100644 --- a/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -333,7 +333,7 @@ public class KeyguardStatusBarView extends RelativeLayout return false; } - public void onOverlayChanged() { + public void onThemeChanged() { @ColorInt int textColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor); @ColorInt int iconColor = Utils.getDefaultColor(mContext, Color.luminance(textColor) < 0.5 ? R.color.dark_mode_icon_color_single_tone : diff --git a/com/android/systemui/statusbar/phone/LockIcon.java b/com/android/systemui/statusbar/phone/LockIcon.java index 5c9446ce..34486dbc 100644 --- a/com/android/systemui/statusbar/phone/LockIcon.java +++ b/com/android/systemui/statusbar/phone/LockIcon.java @@ -250,7 +250,7 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange } break; case STATE_FACE_UNLOCK: - iconRes = com.android.internal.R.drawable.ic_account_circle; + iconRes = R.drawable.ic_account_circle; break; case STATE_FINGERPRINT: // If screen is off and device asleep, use the draw on animation so the first frame diff --git a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index c9500363..b81a3b04 100644 --- a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -80,7 +80,8 @@ public final class NavigationBarTransitions extends BarTransitions { @Override protected boolean isLightsOut(int mode) { - return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible); + return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible + && mode != MODE_WARNING); } public LightBarTransitionsController getLightTransitionsController() { @@ -108,7 +109,9 @@ public final class NavigationBarTransitions extends BarTransitions { // ok, everyone, stop it right there navButtons.animate().cancel(); - final float navButtonsAlpha = lightsOut ? 0.6f : 1f; + // Bump percentage by 10% if dark. + float darkBump = mLightTransitionsController.getCurrentDarkIntensity() / 10; + final float navButtonsAlpha = lightsOut ? 0.6f + darkBump : 1f; if (!animate) { navButtons.setAlpha(navButtonsAlpha); @@ -130,6 +133,9 @@ public final class NavigationBarTransitions extends BarTransitions { for (int i = buttonDispatchers.size() - 1; i >= 0; i--) { buttonDispatchers.valueAt(i).setDarkIntensity(darkIntensity); } + if (mAutoDim) { + applyLightsOut(false, true); + } } private final View.OnTouchListener mLightsOutListener = new View.OnTouchListener() { diff --git a/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 0f246c62..836efffb 100644 --- a/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -209,7 +209,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex); } } - if (mDark && child instanceof StatusBarIconView) { + if (child instanceof StatusBarIconView) { ((StatusBarIconView) child).setDark(mDark, false, 0); } } diff --git a/com/android/systemui/statusbar/phone/NotificationPanelView.java b/com/android/systemui/statusbar/phone/NotificationPanelView.java index 86a8f411..17e35999 100644 --- a/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -239,6 +239,7 @@ public class NotificationPanelView extends PanelView implements private ValueAnimator mDarkAnimator; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private boolean mUserSetupComplete; + private int mQsNotificationTopPadding; public NotificationPanelView(Context context, AttributeSet attrs) { super(context, attrs); @@ -307,6 +308,8 @@ public class NotificationPanelView extends PanelView implements R.dimen.max_notification_fadeout_height); mIndicationBottomPadding = getResources().getDimensionPixelSize( R.dimen.keyguard_indication_bottom_padding); + mQsNotificationTopPadding = getResources().getDimensionPixelSize( + R.dimen.qs_notification_keyguard_padding); } public void updateResources() { @@ -330,7 +333,7 @@ public class NotificationPanelView extends PanelView implements } } - public void onOverlayChanged() { + public void onThemeChanged() { // Re-inflate the status view group. int index = indexOfChild(mKeyguardStatusView); removeView(mKeyguardStatusView); @@ -818,7 +821,7 @@ public class NotificationPanelView extends PanelView implements private float getQsExpansionFraction() { return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight) - / (getTempQsMaxExpansion() - mQsMinExpansionHeight)); + / (mQsMaxExpansionHeight - mQsMinExpansionHeight)); } @Override @@ -1361,7 +1364,7 @@ public class NotificationPanelView extends PanelView implements // take the maximum and linearly interpolate with the panel expansion for a nice motion. int maxNotifications = mClockPositionResult.stackScrollerPadding - mClockPositionResult.stackScrollerPaddingAdjustment; - int maxQs = getTempQsMaxExpansion(); + int maxQs = mQsMaxExpansionHeight + mQsNotificationTopPadding; int max = mStatusBarState == StatusBarState.KEYGUARD ? Math.max(maxNotifications, maxQs) : maxQs; @@ -1375,7 +1378,7 @@ public class NotificationPanelView extends PanelView implements // from a scrolled quick settings. return interpolate(getQsExpansionFraction(), mNotificationStackScroller.getIntrinsicPadding(), - mQsMaxExpansionHeight); + mQsMaxExpansionHeight + mQsNotificationTopPadding); } else { return mQsExpansionHeight; } @@ -1544,7 +1547,7 @@ public class NotificationPanelView extends PanelView implements / (panelHeightQsExpanded - panelHeightQsCollapsed); } setQsExpansion(mQsMinExpansionHeight - + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight)); + + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight)); } updateExpandedHeight(expandedHeight); updateHeader(); @@ -1566,14 +1569,6 @@ public class NotificationPanelView extends PanelView implements } } - /** - * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when - * collapsing QS / the panel when QS was scrolled - */ - private int getTempQsMaxExpansion() { - return mQsMaxExpansionHeight; - } - private int calculatePanelHeightShade() { int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin(); int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin @@ -1596,6 +1591,10 @@ public class NotificationPanelView extends PanelView implements } int maxQsHeight = mQsMaxExpansionHeight; + if (mKeyguardShowing) { + maxQsHeight += mQsNotificationTopPadding; + } + // If an animation is changing the size of the QS panel, take the animated value. if (mQsSizeChangeAnimator != null) { maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue(); diff --git a/com/android/systemui/statusbar/phone/ScrimController.java b/com/android/systemui/statusbar/phone/ScrimController.java index 702afa3a..3a367763 100644 --- a/com/android/systemui/statusbar/phone/ScrimController.java +++ b/com/android/systemui/statusbar/phone/ScrimController.java @@ -25,7 +25,9 @@ import android.content.Context; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Handler; import android.os.Trace; +import android.util.Log; import android.util.MathUtils; import android.view.View; import android.view.ViewGroup; @@ -34,12 +36,14 @@ import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener; import com.android.internal.graphics.ColorUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dependency; +import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.statusbar.ExpandableNotificationRow; @@ -47,7 +51,10 @@ import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.stack.ViewState; +import com.android.systemui.util.wakelock.DelayedWakeLock; +import com.android.systemui.util.wakelock.WakeLock; +import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.function.Consumer; @@ -56,33 +63,54 @@ import java.util.function.Consumer; * security method gets shown). */ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, - OnHeadsUpChangedListener, OnColorsChangedListener { + OnHeadsUpChangedListener, OnColorsChangedListener, Dumpable { + + private static final String TAG = "ScrimController"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + public static final long ANIMATION_DURATION = 220; public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR = new PathInterpolator(0f, 0, 0.7f, 1f); public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED = new PathInterpolator(0.3f, 0f, 0.8f, 1f); - // Default alpha value for most scrims, if unsure use this constant + /** + * Default alpha value for most scrims. + */ public static final float GRADIENT_SCRIM_ALPHA = 0.45f; - // A scrim varies its opacity based on a busyness factor, for example - // how many notifications are currently visible. + /** + * A scrim varies its opacity based on a busyness factor, for example + * how many notifications are currently visible. + */ public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.70f; + /** + * The most common scrim, the one under the keyguard. + */ protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = GRADIENT_SCRIM_ALPHA; + /** + * We fade out the bottom scrim when the bouncer is visible. + */ protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f; - private static final float SCRIM_IN_FRONT_ALPHA = GRADIENT_SCRIM_ALPHA_BUSY; - private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY; - private static final int TAG_KEY_ANIM = R.id.scrim; + /** + * Opacity of the scrim behind the bouncer (the one doing actual background protection.) + */ + protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY; + + static final int TAG_KEY_ANIM = R.id.scrim; + static final int TAG_KEY_ANIM_BLANK = R.id.scrim_blanking; private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target; private static final int TAG_START_ALPHA = R.id.scrim_alpha_start; private static final int TAG_END_ALPHA = R.id.scrim_alpha_end; private static final float NOT_INITIALIZED = -1; - private final LightBarController mLightBarController; + private ScrimState mState = ScrimState.UNINITIALIZED; + private final Context mContext; protected final ScrimView mScrimBehind; protected final ScrimView mScrimInFront; - private final UnlockMethodCache mUnlockMethodCache; private final View mHeadsUpScrim; + private final LightBarController mLightBarController; + private final UnlockMethodCache mUnlockMethodCache; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final DozeParameters mDozeParameters; private final SysuiColorExtractor mColorExtractor; private GradientColors mLockColors; @@ -94,61 +122,53 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD; protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING; - protected boolean mKeyguardShowing; private float mFraction; private boolean mDarkenWhileDragging; - protected boolean mBouncerShowing; - protected boolean mBouncerIsKeyguard = false; - private boolean mWakeAndUnlocking; protected boolean mAnimateChange; private boolean mUpdatePending; private boolean mTracking; private boolean mAnimateKeyguardFadingOut; - protected long mDurationOverride = -1; + protected long mAnimationDuration = -1; private long mAnimationDelay; private Runnable mOnAnimationFinished; private boolean mDeferFinishedListener; private final Interpolator mInterpolator = new DecelerateInterpolator(); - private boolean mDozing; - private float mDozeInFrontAlpha; - private float mDozeBehindAlpha; private float mCurrentInFrontAlpha = NOT_INITIALIZED; private float mCurrentBehindAlpha = NOT_INITIALIZED; - private float mCurrentHeadsUpAlpha = NOT_INITIALIZED; + private int mCurrentInFrontTint; + private int mCurrentBehindTint; private int mPinnedHeadsUpCount; private float mTopHeadsUpDragAmount; private View mDraggedHeadsUpView; - private boolean mForceHideScrims; - private boolean mSkipFirstFrame; - private boolean mDontAnimateBouncerChanges; private boolean mKeyguardFadingOutInProgress; - private boolean mAnimatingDozeUnlock; private ValueAnimator mKeyguardFadeoutAnimation; - /** Wake up from AOD transition is starting; need fully opaque front scrim */ - private boolean mWakingUpFromAodStarting; - /** Wake up from AOD transition is in progress; need black tint */ - private boolean mWakingUpFromAodInProgress; - /** Wake up from AOD transition is animating; need to reset when animation finishes */ - private boolean mWakingUpFromAodAnimationRunning; - private boolean mScrimsVisble; + private boolean mScrimsVisible; private final Consumer<Boolean> mScrimVisibleListener; + private boolean mBlankScreen; + private boolean mScreenBlankingCallbackCalled; + private Callback mCallback; + + private final WakeLock mWakeLock; + private boolean mWakeLockHeld; public ScrimController(LightBarController lightBarController, ScrimView scrimBehind, - ScrimView scrimInFront, View headsUpScrim, - Consumer<Boolean> scrimVisibleListener) { + ScrimView scrimInFront, View headsUpScrim, Consumer<Boolean> scrimVisibleListener, + DozeParameters dozeParameters) { mScrimBehind = scrimBehind; mScrimInFront = scrimInFront; mHeadsUpScrim = headsUpScrim; mScrimVisibleListener = scrimVisibleListener; - final Context context = scrimBehind.getContext(); - mUnlockMethodCache = UnlockMethodCache.getInstance(context); - mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context); + mContext = scrimBehind.getContext(); + mUnlockMethodCache = UnlockMethodCache.getInstance(mContext); + mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); mLightBarController = lightBarController; - mScrimBehindAlphaResValue = context.getResources().getFloat(R.dimen.scrim_behind_alpha); + mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha); + mWakeLock = createWakeLock(); // Scrim alpha is initially set to the value on the resource but might be changed // to make sure that text on top of it is legible. mScrimBehindAlpha = mScrimBehindAlphaResValue; + mDozeParameters = dozeParameters; mColorExtractor = Dependency.get(SysuiColorExtractor.class); mColorExtractor.addOnColorsChangedListener(this); @@ -158,159 +178,155 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, ColorExtractor.TYPE_DARK, true /* ignoreVisibility */); mNeedsDrawableColorUpdate = true; + final ScrimState[] states = ScrimState.values(); + for (int i = 0; i < states.length; i++) { + states[i].init(mScrimInFront, mScrimBehind, mDozeParameters); + states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard); + } + mState = ScrimState.UNINITIALIZED; + updateHeadsUpScrim(false); updateScrims(); } - public void setKeyguardShowing(boolean showing) { - mKeyguardShowing = showing; - - // Showing/hiding the keyguard means that scrim colors have to be switched - mNeedsDrawableColorUpdate = true; - scheduleUpdate(); + public void transitionTo(ScrimState state) { + transitionTo(state, null); } - protected void setScrimBehindValues(float scrimBehindAlphaKeyguard, - float scrimBehindAlphaUnlocking) { - mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; - mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking; - scheduleUpdate(); - } - - public void onTrackingStarted() { - mTracking = true; - mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer(); - } - - public void onExpandingFinished() { - mTracking = false; - } - - public void setPanelExpansion(float fraction) { - if (mFraction != fraction) { - mFraction = fraction; - scheduleUpdate(); - if (mPinnedHeadsUpCount != 0) { - updateHeadsUpScrim(false); - } - if (mKeyguardFadeoutAnimation != null && mTracking) { - mKeyguardFadeoutAnimation.cancel(); - } + public void transitionTo(ScrimState state, Callback callback) { + if (state == mState) { + return; + } else if (DEBUG) { + Log.d(TAG, "State changed to: " + state); } - } - - public void setBouncerShowing(boolean showing) { - mBouncerShowing = showing; - mAnimateChange = !mTracking && !mDontAnimateBouncerChanges && !mKeyguardFadingOutInProgress; - scheduleUpdate(); - } - /** Prepares the wakeUpFromAod animation (while turning on screen); Forces black scrims. */ - public void prepareWakeUpFromAod() { - if (mWakingUpFromAodInProgress) { - return; + if (state == ScrimState.UNINITIALIZED) { + throw new IllegalArgumentException("Cannot change to UNINITIALIZED."); } - mWakingUpFromAodInProgress = true; - mWakingUpFromAodStarting = true; - mAnimateChange = false; - scheduleUpdate(); - onPreDraw(); - } - /** Starts the wakeUpFromAod animation (once screen is on); animate to transparent scrims. */ - public void wakeUpFromAod() { - if (mWakeAndUnlocking || mAnimateKeyguardFadingOut) { - // Wake and unlocking has a separate transition that must not be interfered with. - mWakingUpFromAodStarting = false; - mWakingUpFromAodInProgress = false; - return; + if (mCallback != null) { + mCallback.onCancelled(); } - if (mWakingUpFromAodStarting) { - mWakingUpFromAodInProgress = true; - mWakingUpFromAodStarting = false; - mAnimateChange = true; - scheduleUpdate(); + mCallback = callback; + + state.prepare(mState); + mScreenBlankingCallbackCalled = false; + mAnimationDelay = 0; + mBlankScreen = state.getBlanksScreen(); + mAnimateChange = state.getAnimateChange(); + mAnimationDuration = state.getAnimationDuration(); + mCurrentInFrontTint = state.getFrontTint(); + mCurrentBehindTint = state.getBehindTint(); + mCurrentInFrontAlpha = state.getFrontAlpha(); + mCurrentBehindAlpha = state.getBehindAlpha(); + + // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary + // to do the same when you're just showing the brightness mirror. + mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR; + + if (mKeyguardFadeoutAnimation != null) { + mKeyguardFadeoutAnimation.cancel(); } - } - public void setWakeAndUnlocking() { - mWakeAndUnlocking = true; - mAnimatingDozeUnlock = true; - mWakingUpFromAodStarting = false; - mWakingUpFromAodInProgress = false; - scheduleUpdate(); - } + mState = state; - public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished, - boolean skipFirstFrame) { - mWakeAndUnlocking = false; - mAnimateKeyguardFadingOut = true; - mDurationOverride = duration; - mAnimationDelay = delay; - mAnimateChange = true; - mSkipFirstFrame = skipFirstFrame; - mOnAnimationFinished = onAnimationFinished; + // Do not let the device sleep until we're done with all animations + if (!mWakeLockHeld) { + if (mWakeLock != null) { + mWakeLockHeld = true; + mWakeLock.acquire(); + } else { + Log.w(TAG, "Cannot hold wake lock, it has not been set yet"); + } + } if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) { scheduleUpdate(); - - // No need to wait for the next frame to be drawn for this case - onPreDraw will execute - // the changes we just scheduled. - onPreDraw(); } else { - // In case the user isn't unlocked, make sure to delay a bit because the system is hosed - // with too many things in this case, in order to not skip the initial frames. + // with too many things at this case, in order to not skip the initial frames. mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16); + mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY; } } - public void abortKeyguardFadingOut() { - if (mAnimateKeyguardFadingOut) { - endAnimateKeyguardFadingOut(true /* force */); - } + public ScrimState getState() { + return mState; } - public void animateKeyguardUnoccluding(long duration) { - mAnimateChange = false; - setScrimBehindAlpha(0f); - mAnimateChange = true; + protected void setScrimBehindValues(float scrimBehindAlphaKeyguard, + float scrimBehindAlphaUnlocking) { + mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; + mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking; + ScrimState[] states = ScrimState.values(); + for (int i = 0; i < states.length; i++) { + states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard); + } scheduleUpdate(); - mDurationOverride = duration; } - public void animateGoingToFullShade(long delay, long duration) { - mDurationOverride = duration; - mAnimationDelay = delay; - mAnimateChange = true; - scheduleUpdate(); + public void onTrackingStarted() { + mTracking = true; + mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer(); } - public void setDozing(boolean dozing) { - if (mDozing != dozing) { - mDozing = dozing; - scheduleUpdate(); - } + public void onExpandingFinished() { + mTracking = false; } - public void setDozeInFrontAlpha(float alpha) { - mDozeInFrontAlpha = alpha; - updateScrimColor(mScrimInFront); - } + /** + * Current state of the shade expansion when pulling it from the top. + * This value is 1 when on top of the keyguard and goes to 0 as the user drags up. + * + * The expansion fraction is tied to the scrim opacity. + * + * @param fraction From 0 to 1 where 0 means collapse and 1 expanded. + */ + public void setPanelExpansion(float fraction) { + if (mFraction != fraction) { + mFraction = fraction; - public void setDozeBehindAlpha(float alpha) { - mDozeBehindAlpha = alpha; - updateScrimColor(mScrimBehind); - } + if (mState == ScrimState.UNLOCKED) { + // Darken scrim as you pull down the shade when unlocked + float behindFraction = getInterpolatedFraction(); + behindFraction = (float) Math.pow(behindFraction, 0.8f); + mCurrentBehindAlpha = behindFraction * mScrimBehindAlphaKeyguard; + mCurrentInFrontAlpha = 0; + } else if (mState == ScrimState.KEYGUARD) { + if (mUpdatePending) { + return; + } - public float getDozeBehindAlpha() { - return mDozeBehindAlpha; - } + // Either darken of make the scrim transparent when you + // pull down the shade + float interpolatedFract = getInterpolatedFraction(); + if (mDarkenWhileDragging) { + mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking, + mScrimBehindAlphaKeyguard, interpolatedFract); + mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED; + } else { + mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard, + interpolatedFract); + mCurrentInFrontAlpha = 0; + } + } else { + Log.w(TAG, "Invalid state, cannot set panel expansion when: " + mState); + return; + } - public float getDozeInFrontAlpha() { - return mDozeInFrontAlpha; + if (mPinnedHeadsUpCount != 0) { + updateHeadsUpScrim(false); + } + + updateScrim(false /* animate */, mScrimInFront, mCurrentInFrontAlpha); + updateScrim(false /* animate */, mScrimBehind, mCurrentBehindAlpha); + } } + /** + * Keyguard and shade scrim opacity varies according to how many notifications are visible. + * @param notificationCount Number of visible notifications. + */ public void setNotificationCount(int notificationCount) { final float maxNotificationDensity = 3; float notificationDensity = Math.min(notificationCount / maxNotificationDensity, 1f); @@ -319,15 +335,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, notificationDensity); if (mScrimBehindAlphaKeyguard != newAlpha) { mScrimBehindAlphaKeyguard = newAlpha; - mAnimateChange = true; - scheduleUpdate(); - } - } - private float getScrimInFrontAlpha() { - return mKeyguardUpdateMonitor.needsSlowUnlockTransition() - ? SCRIM_IN_FRONT_ALPHA_LOCKED - : SCRIM_IN_FRONT_ALPHA; + if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) { + scheduleUpdate(); + } + } } /** @@ -352,7 +364,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, if (mNeedsDrawableColorUpdate) { mNeedsDrawableColorUpdate = false; final GradientColors currentScrimColors; - if (mKeyguardShowing) { + if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) { // Always animate color changes if we're seeing the keyguard mScrimInFront.setColors(mLockColors, true /* animated */); mScrimBehind.setColors(mLockColors, true /* animated */); @@ -375,77 +387,31 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mLightBarController.setScrimColor(mScrimInFront.getColors()); } - if (mAnimateKeyguardFadingOut || mForceHideScrims) { - setScrimInFrontAlpha(0f); - setScrimBehindAlpha(0f); - } else if (mWakeAndUnlocking) { - // During wake and unlock, we first hide everything behind a black scrim, which then - // gets faded out from animateKeyguardFadingOut. This must never be animated. - mAnimateChange = false; - if (mDozing) { - setScrimInFrontAlpha(0f); - setScrimBehindAlpha(1f); - } else { - setScrimInFrontAlpha(1f); - setScrimBehindAlpha(0f); - } - } else if (!mKeyguardShowing && !mBouncerShowing && !mWakingUpFromAodStarting) { - updateScrimNormal(); - setScrimInFrontAlpha(0); - } else { - updateScrimKeyguard(); - } - mAnimateChange = false; + setScrimInFrontAlpha(mCurrentInFrontAlpha); + setScrimBehindAlpha(mCurrentBehindAlpha); + dispatchScrimsVisible(); } private void dispatchScrimsVisible() { boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0; - if (mScrimsVisble != scrimsVisible) { - mScrimsVisble = scrimsVisible; + if (mScrimsVisible != scrimsVisible) { + mScrimsVisible = scrimsVisible; mScrimVisibleListener.accept(scrimsVisible); } } - private void updateScrimKeyguard() { - if (mTracking && mDarkenWhileDragging) { - float behindFraction = Math.max(0, Math.min(mFraction, 1)); - float fraction = 1 - behindFraction; - fraction = (float) Math.pow(fraction, 0.8f); - behindFraction = (float) Math.pow(behindFraction, 0.8f); - setScrimInFrontAlpha(fraction * getScrimInFrontAlpha()); - setScrimBehindAlpha(behindFraction * mScrimBehindAlphaKeyguard); - } else if (mBouncerShowing && !mBouncerIsKeyguard) { - setScrimInFrontAlpha(getScrimInFrontAlpha()); - updateScrimNormal(); - } else if (mBouncerShowing) { - setScrimInFrontAlpha(0f); - setScrimBehindAlpha(mScrimBehindAlpha); - } else { - float fraction = Math.max(0, Math.min(mFraction, 1)); - if (mWakingUpFromAodStarting) { - setScrimInFrontAlpha(1f); - } else { - setScrimInFrontAlpha(0f); - } - setScrimBehindAlpha(fraction - * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking) - + mScrimBehindAlphaUnlocking); - } - } - - private void updateScrimNormal() { + private float getInterpolatedFraction() { float frac = mFraction; // let's start this 20% of the way down the screen frac = frac * 1.2f - 0.2f; if (frac <= 0) { - setScrimBehindAlpha(0); + return 0; } else { // woo, special effects - final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f)))); - setScrimBehindAlpha(k * mScrimBehindAlpha); + return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f)))); } } @@ -455,102 +421,76 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private void setScrimInFrontAlpha(float alpha) { setScrimAlpha(mScrimInFront, alpha); - if (alpha == 0f) { - mScrimInFront.setClickable(false); - } else { - // Eat touch events (unless dozing). - mScrimInFront.setClickable(!mDozing); - } } private void setScrimAlpha(View scrim, float alpha) { - updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim)); - } - - protected float getDozeAlpha(View scrim) { - return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha; - } - - protected float getCurrentScrimAlpha(View scrim) { - return scrim == mScrimBehind ? mCurrentBehindAlpha - : scrim == mScrimInFront ? mCurrentInFrontAlpha - : mCurrentHeadsUpAlpha; - } - - private void setCurrentScrimAlpha(View scrim, float alpha) { - if (scrim == mScrimBehind) { - mCurrentBehindAlpha = alpha; - mLightBarController.setScrimAlpha(mCurrentBehindAlpha); - } else if (scrim == mScrimInFront) { - mCurrentInFrontAlpha = alpha; + if (alpha == 0f) { + scrim.setClickable(false); } else { - alpha = Math.max(0.0f, Math.min(1.0f, alpha)); - mCurrentHeadsUpAlpha = alpha; + // Eat touch events (unless dozing). + scrim.setClickable(!(mState == ScrimState.AOD)); } + updateScrim(mAnimateChange, scrim, alpha); } - private void updateScrimColor(View scrim) { - float alpha1 = getCurrentScrimAlpha(scrim); + private void updateScrimColor(View scrim, float alpha, int tint) { + alpha = Math.max(0, Math.min(1.0f, alpha)); if (scrim instanceof ScrimView) { ScrimView scrimView = (ScrimView) scrim; - float dozeAlpha = getDozeAlpha(scrim); - float alpha = 1 - (1 - alpha1) * (1 - dozeAlpha); - alpha = Math.max(0, Math.min(1.0f, alpha)); - scrimView.setViewAlpha(alpha); Trace.traceCounter(Trace.TRACE_TAG_APP, scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha", (int) (alpha * 255)); - int dozeTint = Color.TRANSPARENT; - - boolean dozing = mAnimatingDozeUnlock || mDozing; - boolean frontScrimDozing = mWakingUpFromAodInProgress; - if (dozing || frontScrimDozing && scrim == mScrimInFront) { - dozeTint = Color.BLACK; - } Trace.traceCounter(Trace.TRACE_TAG_APP, scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint", - dozeTint == Color.BLACK ? 1 : 0); + Color.alpha(tint)); - scrimView.setTint(dozeTint); + scrimView.setTint(tint); + scrimView.setViewAlpha(alpha); } else { - scrim.setAlpha(alpha1); + scrim.setAlpha(alpha); } dispatchScrimsVisible(); } - private void startScrimAnimation(final View scrim, float target) { - float current = getCurrentScrimAlpha(scrim); - ValueAnimator anim = ValueAnimator.ofFloat(current, target); + private int getCurrentScrimTint(View scrim) { + return scrim == mScrimInFront ? mCurrentInFrontTint : mCurrentBehindTint; + } + + private void startScrimAnimation(final View scrim, float current, float target) { + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() : + Color.TRANSPARENT; anim.addUpdateListener(animation -> { - float alpha = (float) animation.getAnimatedValue(); - setCurrentScrimAlpha(scrim, alpha); - updateScrimColor(scrim); + final float animAmount = (float) animation.getAnimatedValue(); + final int finalScrimTint = scrim == mScrimInFront ? + mCurrentInFrontTint : mCurrentBehindTint; + float alpha = MathUtils.lerp(current, target, animAmount); + int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount); + updateScrimColor(scrim, alpha, tint); dispatchScrimsVisible(); }); anim.setInterpolator(getInterpolator()); anim.setStartDelay(mAnimationDelay); - anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION); + anim.setDuration(mAnimationDuration); anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - if (!mDeferFinishedListener && mOnAnimationFinished != null) { - mOnAnimationFinished.run(); - mOnAnimationFinished = null; - } if (mKeyguardFadingOutInProgress) { mKeyguardFadeoutAnimation = null; mKeyguardFadingOutInProgress = false; - mAnimatingDozeUnlock = false; - } - if (mWakingUpFromAodAnimationRunning && !mDeferFinishedListener) { - mWakingUpFromAodAnimationRunning = false; - mWakingUpFromAodInProgress = false; } + onFinished(); + scrim.setTag(TAG_KEY_ANIM, null); scrim.setTag(TAG_KEY_ANIM_TARGET, null); dispatchScrimsVisible(); + + if (!mDeferFinishedListener && mOnAnimationFinished != null) { + mOnAnimationFinished.run(); + mOnAnimationFinished = null; + } } }); anim.start(); @@ -558,12 +498,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mKeyguardFadingOutInProgress = true; mKeyguardFadeoutAnimation = anim; } - if (mWakingUpFromAodInProgress) { - mWakingUpFromAodAnimationRunning = true; - } - if (mSkipFirstFrame) { - anim.setCurrentPlayTime(16); - } scrim.setTag(TAG_KEY_ANIM, anim); scrim.setTag(TAG_KEY_ANIM_TARGET, target); } @@ -582,19 +516,33 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, public boolean onPreDraw() { mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this); mUpdatePending = false; - if (mDontAnimateBouncerChanges) { - mDontAnimateBouncerChanges = false; + if (mCallback != null) { + mCallback.onStart(); } updateScrims(); - mDurationOverride = -1; - mAnimationDelay = 0; - mSkipFirstFrame = false; // Make sure that we always call the listener even if we didn't start an animation. endAnimateKeyguardFadingOut(false /* force */); return true; } + private void onFinished() { + if (mWakeLockHeld) { + mWakeLock.release(); + mWakeLockHeld = false; + } + if (mCallback != null) { + mCallback.onFinished(); + mCallback = null; + } + // When unlocking with fingerprint, we'll fade the scrims from black to transparent. + // At the end of the animation we need to remove the tint. + if (mState == ScrimState.UNLOCKED) { + mCurrentInFrontTint = Color.TRANSPARENT; + mCurrentBehindTint = Color.TRANSPARENT; + } + } + private void endAnimateKeyguardFadingOut(boolean force) { mAnimateKeyguardFadingOut = false; if (force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind))) { @@ -603,8 +551,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mOnAnimationFinished = null; } mKeyguardFadingOutInProgress = false; - if (!mWakeAndUnlocking || force) - mAnimatingDozeUnlock = false; } } @@ -641,16 +587,19 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } private void updateHeadsUpScrim(boolean animate) { - updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha); + updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha()); } - private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) { - if (mKeyguardFadingOutInProgress && mKeyguardFadeoutAnimation.getCurrentPlayTime() != 0) { - return; - } + @VisibleForTesting + void setOnAnimationFinished(Runnable onAnimationFinished) { + mOnAnimationFinished = onAnimationFinished; + } + + private void updateScrim(boolean animate, View scrim, float alpha) { + final float currentAlpha = scrim instanceof ScrimView ? ((ScrimView) scrim).getViewAlpha() + : scrim.getAlpha(); - ValueAnimator previousAnimator = ViewState.getChildTag(scrim, - TAG_KEY_ANIM); + ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM); float animEndValue = -1; if (previousAnimator != null) { if (animate || alpha == currentAlpha) { @@ -664,9 +613,37 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, animEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA); } } - if (alpha != currentAlpha && alpha != animEndValue) { + + final boolean blankingInProgress = mScrimInFront.getTag(TAG_KEY_ANIM_BLANK) != null; + if (mBlankScreen || blankingInProgress) { + if (!blankingInProgress) { + blankDisplay(); + } + return; + } else if (!mScreenBlankingCallbackCalled) { + // Not blanking the screen. Letting the callback know that we're ready + // to replace what was on the screen before. + if (mCallback != null) { + mCallback.onDisplayBlanked(); + mScreenBlankingCallbackCalled = true; + } + } + + // TODO factor mLightBarController out of this class + if (scrim == mScrimBehind) { + mLightBarController.setScrimAlpha(alpha); + } + + final ScrimView scrimView = scrim instanceof ScrimView ? (ScrimView) scrim : null; + final boolean wantsAlphaUpdate = alpha != currentAlpha && alpha != animEndValue; + final boolean wantsTintUpdate = scrimView != null + && scrimView.getTint() != getCurrentScrimTint(scrimView); + + if (wantsAlphaUpdate || wantsTintUpdate) { if (animate) { - startScrimAnimation(scrim, alpha); + final float fromAlpha = scrimView == null ? scrim.getAlpha() + : scrimView.getViewAlpha(); + startScrimAnimation(scrim, fromAlpha, alpha); scrim.setTag(TAG_START_ALPHA, currentAlpha); scrim.setTag(TAG_END_ALPHA, alpha); } else { @@ -685,13 +662,62 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); } else { // update the alpha directly - setCurrentScrimAlpha(scrim, alpha); - updateScrimColor(scrim); + updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim)); + onFinished(); + } + } + } else { + onFinished(); + } + } + + private void blankDisplay() { + final float initialAlpha = mScrimInFront.getViewAlpha(); + final int initialTint = mScrimInFront.getTint(); + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + anim.addUpdateListener(animation -> { + final float amount = (float) animation.getAnimatedValue(); + float animAlpha = MathUtils.lerp(initialAlpha, 1, amount); + int animTint = ColorUtils.blendARGB(initialTint, Color.BLACK, amount); + updateScrimColor(mScrimInFront, animAlpha, animTint); + dispatchScrimsVisible(); + }); + anim.setInterpolator(getInterpolator()); + anim.setDuration(mDozeParameters.getPulseInDuration()); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mCallback != null) { + mCallback.onDisplayBlanked(); + mScreenBlankingCallbackCalled = true; } + Runnable blankingCallback = () -> { + mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, null); + mBlankScreen = false; + // Try again. + updateScrims(); + }; + + // Setting power states can happen after we push out the frame. Make sure we + // stay fully opaque until the power state request reaches the lower levels. + getHandler().postDelayed(blankingCallback, 100); + } + }); + anim.start(); + mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, anim); + + // Finish animation if we're already at its final state + if (initialAlpha == 1 && mScrimInFront.getTint() == Color.BLACK) { + anim.end(); } } + @VisibleForTesting + protected Handler getHandler() { + return Handler.getMain(); + } + /** * Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means * the heads up is in its resting space and 1 means it's fully dragged out. @@ -719,23 +745,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, return alpha * expandFactor; } - public void forceHideScrims(boolean hide, boolean animated) { - mForceHideScrims = hide; - mAnimateChange = animated; - scheduleUpdate(); - } - - public void dontAnimateBouncerChangesUntilNextFrame() { - mDontAnimateBouncerChanges = true; - } - public void setExcludedBackgroundArea(Rect area) { mScrimBehind.setExcludedArea(area); } public int getBackgroundColor() { int color = mLockColors.getMainColor(); - return Color.argb((int) (mScrimBehind.getAlpha() * Color.alpha(color)), + return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)), Color.red(color), Color.green(color), Color.blue(color)); } @@ -764,27 +780,41 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } if ((which & WallpaperManager.FLAG_SYSTEM) != 0) { mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, - ColorExtractor.TYPE_DARK, mKeyguardShowing); + ColorExtractor.TYPE_DARK, mState != ScrimState.UNLOCKED); mNeedsDrawableColorUpdate = true; scheduleUpdate(); } } - public void dump(PrintWriter pw) { - pw.println(" ScrimController:"); + @VisibleForTesting + protected WakeLock createWakeLock() { + return new DelayedWakeLock(getHandler(), + WakeLock.createPartial(mContext, "Doze")); + } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(" ScrimController:"); + pw.print(" state:"); pw.println(mState); pw.print(" frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha()); pw.print(" alpha="); pw.print(mCurrentInFrontAlpha); - pw.print(" dozeAlpha="); pw.print(mDozeInFrontAlpha); pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint())); pw.print(" backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha()); pw.print(" alpha="); pw.print(mCurrentBehindAlpha); - pw.print(" dozeAlpha="); pw.print(mDozeBehindAlpha); pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint())); - pw.print(" mBouncerShowing="); pw.println(mBouncerShowing); pw.print(" mTracking="); pw.println(mTracking); - pw.print(" mForceHideScrims="); pw.println(mForceHideScrims); + } + + public interface Callback { + default void onStart() { + } + default void onDisplayBlanked() { + } + default void onFinished() { + } + default void onCancelled() { + } } } diff --git a/com/android/systemui/statusbar/phone/ScrimState.java b/com/android/systemui/statusbar/phone/ScrimState.java new file mode 100644 index 00000000..0db98f37 --- /dev/null +++ b/com/android/systemui/statusbar/phone/ScrimState.java @@ -0,0 +1,208 @@ +/* + * 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 com.android.systemui.statusbar.phone; + +import android.graphics.Color; +import android.os.Trace; + +import com.android.systemui.statusbar.ScrimView; + +/** + * Possible states of the ScrimController state machine. + */ +public enum ScrimState { + + /** + * Initial state. + */ + UNINITIALIZED, + + /** + * On the lock screen. + */ + KEYGUARD { + + @Override + public void prepare(ScrimState previousState) { + // DisplayPowerManager will blank the screen, we'll just + // set our scrim to black in this frame to avoid flickering and + // fade it out afterwards. + mBlankScreen = previousState == ScrimState.AOD; + if (previousState == ScrimState.AOD) { + updateScrimColor(mScrimInFront, 1, Color.BLACK); + } + mCurrentBehindAlpha = mScrimBehindAlphaKeyguard; + mCurrentInFrontAlpha = 0; + } + }, + + /** + * Showing password challenge. + */ + BOUNCER { + @Override + public void prepare(ScrimState previousState) { + mCurrentBehindAlpha = ScrimController.SCRIM_BEHIND_ALPHA_UNLOCKING; + mCurrentInFrontAlpha = ScrimController.SCRIM_IN_FRONT_ALPHA_LOCKED; + } + }, + + /** + * Changing screen brightness from quick settings. + */ + BRIGHTNESS_MIRROR { + @Override + public void prepare(ScrimState previousState) { + mCurrentBehindAlpha = 0; + mCurrentInFrontAlpha = 0; + } + }, + + /** + * Always on display or screen off. + */ + AOD { + @Override + public void prepare(ScrimState previousState) { + if (previousState == ScrimState.PULSING) { + updateScrimColor(mScrimInFront, 1, Color.BLACK); + } + final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn(); + mBlankScreen = previousState == ScrimState.PULSING; + mCurrentBehindAlpha = 1; + mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f; + mCurrentInFrontTint = Color.BLACK; + mCurrentBehindTint = Color.BLACK; + // DisplayPowerManager will blank the screen for us, we just need + // to set our state. + mAnimateChange = false; + } + }, + + /** + * When phone wakes up because you received a notification. + */ + PULSING { + @Override + public void prepare(ScrimState previousState) { + mCurrentBehindAlpha = 1; + mCurrentInFrontAlpha = 0; + mCurrentInFrontTint = Color.BLACK; + mCurrentBehindTint = Color.BLACK; + mBlankScreen = true; + updateScrimColor(mScrimInFront, 1, Color.BLACK); + } + }, + + /** + * Unlocked on top of an app (launcher or any other activity.) + */ + UNLOCKED { + @Override + public void prepare(ScrimState previousState) { + mCurrentBehindAlpha = 0; + mCurrentInFrontAlpha = 0; + mAnimationDuration = StatusBar.FADE_KEYGUARD_DURATION; + + if (previousState == ScrimState.AOD) { + // Fade from black to transparent when coming directly from AOD + updateScrimColor(mScrimInFront, 1, Color.BLACK); + updateScrimColor(mScrimBehind, 1, Color.BLACK); + // Scrims should still be black at the end of the transition. + mCurrentInFrontTint = Color.BLACK; + mCurrentBehindTint = Color.BLACK; + mBlankScreen = true; + } else { + // Scrims should still be black at the end of the transition. + mCurrentInFrontTint = Color.TRANSPARENT; + mCurrentBehindTint = Color.TRANSPARENT; + mBlankScreen = false; + } + } + }; + + boolean mBlankScreen = false; + long mAnimationDuration = ScrimController.ANIMATION_DURATION; + int mCurrentInFrontTint = Color.TRANSPARENT; + int mCurrentBehindTint = Color.TRANSPARENT; + boolean mAnimateChange = true; + float mCurrentInFrontAlpha; + float mCurrentBehindAlpha; + float mAodFrontScrimAlpha; + float mScrimBehindAlphaKeyguard; + ScrimView mScrimInFront; + ScrimView mScrimBehind; + DozeParameters mDozeParameters; + + public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) { + mScrimInFront = scrimInFront; + mScrimBehind = scrimBehind; + mDozeParameters = dozeParameters; + } + + public void prepare(ScrimState previousState) { + } + + public float getFrontAlpha() { + return mCurrentInFrontAlpha; + } + + public float getBehindAlpha() { + return mCurrentBehindAlpha; + } + + public int getFrontTint() { + return mCurrentInFrontTint; + } + + public int getBehindTint() { + return mCurrentBehindTint; + } + + public long getAnimationDuration() { + return mAnimationDuration; + } + + public boolean getBlanksScreen() { + return mBlankScreen; + } + + public void updateScrimColor(ScrimView scrim, float alpha, int tint) { + Trace.traceCounter(Trace.TRACE_TAG_APP, + scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha", + (int) (alpha * 255)); + + Trace.traceCounter(Trace.TRACE_TAG_APP, + scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint", + Color.alpha(tint)); + + scrim.setTint(tint); + scrim.setViewAlpha(alpha); + } + + public boolean getAnimateChange() { + return mAnimateChange; + } + + public void setAodFrontScrimAlpha(float aodFrontScrimAlpha) { + mAodFrontScrimAlpha = aodFrontScrimAlpha; + } + + public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) { + mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; + } +}
\ No newline at end of file diff --git a/com/android/systemui/statusbar/phone/StatusBar.java b/com/android/systemui/statusbar/phone/StatusBar.java index 67756159..dc8100f5 100644 --- a/com/android/systemui/statusbar/phone/StatusBar.java +++ b/com/android/systemui/statusbar/phone/StatusBar.java @@ -79,7 +79,6 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.media.AudioAttributes; import android.media.MediaMetadata; -import android.media.session.MediaSessionManager; import android.metrics.LogMaker; import android.net.Uri; import android.os.AsyncTask; @@ -239,6 +238,7 @@ import com.android.systemui.statusbar.stack.NotificationStackScrollLayout .OnChildLocationsChangedListener; import com.android.systemui.util.NotificationChannels; import com.android.systemui.util.leak.LeakDetector; +import com.android.systemui.util.wakelock.WakeLock; import com.android.systemui.volume.VolumeComponent; import java.io.FileDescriptor; @@ -387,6 +387,7 @@ public class StatusBar extends SystemUI implements DemoMode, private VolumeComponent mVolumeComponent; private BrightnessMirrorController mBrightnessMirrorController; + private boolean mBrightnessMirrorVisible; protected FingerprintUnlockController mFingerprintUnlockController; private LightBarController mLightBarController; protected LockscreenWallpaper mLockscreenWallpaper; @@ -647,6 +648,31 @@ public class StatusBar extends SystemUI implements DemoMode, } }; + // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over, + // this animation is tied to the scrim for historic reasons. + // TODO: notify when keyguard has faded away instead of the scrim. + private final ScrimController.Callback mUnlockScrimCallback = new ScrimController + .Callback() { + @Override + public void onFinished() { + notifyKeyguardState(); + } + + @Override + public void onCancelled() { + notifyKeyguardState(); + } + + private void notifyKeyguardState() { + if (mStatusBarKeyguardViewManager == null) { + Log.w(TAG, "Tried to notify keyguard visibility when " + + "mStatusBarKeyguardViewManager was null"); + return; + } + mStatusBarKeyguardViewManager.onKeyguardFadedAway(); + } + }; + private NotificationMessagingUtil mMessagingUtil; private KeyguardUserSwitcher mKeyguardUserSwitcher; private UserSwitcherController mUserSwitcherController; @@ -919,7 +945,14 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel); mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller); mGutsManager = new NotificationGutsManager(this, mStackScroller, - mCheckSaveListener, mContext); + mCheckSaveListener, mContext, + key -> { + try { + mBarService.onNotificationSettingsViewed(key); + } catch (RemoteException e) { + // if we're here we're dead + } + }); mNotificationPanel.setStatusBar(this); mNotificationPanel.setGroupManager(mGroupManager); mAboveShelfObserver = new AboveShelfObserver(mStackScroller); @@ -1039,7 +1072,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (mStatusBarWindowManager != null) { mStatusBarWindowManager.setScrimsVisible(scrimsVisible); } - }); + }, new DozeParameters(mContext)); if (mScrimSrcModeEnabled) { Runnable runnable = () -> { boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE; @@ -1075,7 +1108,10 @@ public class StatusBar extends SystemUI implements DemoMode, final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this, mIconController); mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow, - mScrimController); + (visible) -> { + mBrightnessMirrorVisible = visible; + updateScrimController(); + }); fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> { QS qs = (QS) f; if (qs instanceof QSFragment) { @@ -1222,13 +1258,13 @@ public class StatusBar extends SystemUI implements DemoMode, reevaluateStyles(); } - private void reinflateViews() { + private void onThemeChanged() { reevaluateStyles(); // Clock and bottom icons - mNotificationPanel.onOverlayChanged(); + mNotificationPanel.onThemeChanged(); // The status bar on the keyguard is a special layout. - if (mKeyguardStatusBar != null) mKeyguardStatusBar.onOverlayChanged(); + if (mKeyguardStatusBar != null) mKeyguardStatusBar.onThemeChanged(); // Recreate Indication controller because internal references changed mKeyguardIndicationController = SystemUIFactory.getInstance().createKeyguardIndicationController(mContext, @@ -1239,11 +1275,8 @@ public class StatusBar extends SystemUI implements DemoMode, .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); mKeyguardIndicationController.setVisible(mState == StatusBarState.KEYGUARD); mKeyguardIndicationController.setDozing(mDozing); - if (mBrightnessMirrorController != null) { - mBrightnessMirrorController.onOverlayChanged(); - } if (mStatusBarKeyguardViewManager != null) { - mStatusBarKeyguardViewManager.onOverlayChanged(); + mStatusBarKeyguardViewManager.onThemeChanged(); } if (mAmbientIndicationContainer instanceof AutoReinflateContainer) { ((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout(); @@ -1258,6 +1291,13 @@ public class StatusBar extends SystemUI implements DemoMode, updateEmptyShadeView(); } + @Override + public void onOverlayChanged() { + if (mBrightnessMirrorController != null) { + mBrightnessMirrorController.onOverlayChanged(); + } + } + private void updateNotificationsOnDensityOrFontScaleChanged() { ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); for (int i = 0; i < activeNotifications.size(); i++) { @@ -1447,8 +1487,7 @@ public class StatusBar extends SystemUI implements DemoMode, mDozeScrimController, keyguardViewMediator, mScrimController, this, UnlockMethodCache.getInstance(mContext)); mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this, - getBouncerContainer(), mScrimController, - mFingerprintUnlockController); + getBouncerContainer(), mFingerprintUnlockController); mKeyguardIndicationController .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); @@ -1470,6 +1509,11 @@ public class StatusBar extends SystemUI implements DemoMode, } }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY); } + try { + mBarService.onNotificationDirectReplied(entry.key); + } catch (RemoteException e) { + // system process is dead if we're here. + } } }); @@ -1785,9 +1829,14 @@ public class StatusBar extends SystemUI implements DemoMode, final int id = n.getId(); final int userId = n.getUserId(); try { - // TODO: record actual dismissal surface + int dismissalSurface = NotificationStats.DISMISSAL_SHADE; + if (isHeadsUp(n.getKey())) { + dismissalSurface = NotificationStats.DISMISSAL_PEEK; + } else if (mStackScroller.hasPulsingNotifications()) { + dismissalSurface = NotificationStats.DISMISSAL_AOD; + } mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), - NotificationStats.DISMISSAL_OTHER); + dismissalSurface); if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(n.getKey())) { mKeysKeptForRemoteInput.remove(n.getKey()); @@ -2620,7 +2669,7 @@ public class StatusBar extends SystemUI implements DemoMode, } public boolean isPulsing() { - return mDozeScrimController.isPulsing(); + return mDozeScrimController != null && mDozeScrimController.isPulsing(); } @Override @@ -3328,7 +3377,7 @@ public class StatusBar extends SystemUI implements DemoMode, } if (mScrimController != null) { - mScrimController.dump(pw); + mScrimController.dump(fd, pw, args); } if (DUMPTRUCK) { @@ -3393,7 +3442,19 @@ public class StatusBar extends SystemUI implements DemoMode, private void addStatusBarWindow() { makeStatusBarView(); mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class); - mRemoteInputController = new RemoteInputController(mHeadsUpManager); + mRemoteInputController = new RemoteInputController(new RemoteInputController.Delegate() { + public void setRemoteInputActive(NotificationData.Entry entry, + boolean remoteInputActive) { + mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); + } + public void lockScrollTo(NotificationData.Entry entry) { + mStackScroller.lockScrollTo(entry.row); + } + public void requestDisallowLongPressAndDismiss() { + mStackScroller.requestDisallowLongPress(); + mStackScroller.requestDisallowDismiss(); + } + }); mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight()); } @@ -4061,7 +4122,6 @@ public class StatusBar extends SystemUI implements DemoMode, releaseGestureWakeLock(); runLaunchTransitionEndRunnable(); mLaunchTransitionFadingAway = false; - mScrimController.forceHideScrims(false /* hide */, false /* animated */); updateMediaMetaData(true /* metaDataChanged */, true); } @@ -4094,7 +4154,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (beforeFading != null) { beforeFading.run(); } - mScrimController.forceHideScrims(true /* hide */, false /* animated */); + updateScrimController(); updateMediaMetaData(false, true); mNotificationPanel.setAlpha(1); mStackScroller.setParentNotFullyVisible(true); @@ -4125,6 +4185,13 @@ public class StatusBar extends SystemUI implements DemoMode, .setStartDelay(0) .setDuration(FADE_KEYGUARD_DURATION_PULSING) .setInterpolator(ScrimController.KEYGUARD_FADE_OUT_INTERPOLATOR) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + hideKeyguard(); + mStatusBarKeyguardViewManager.onKeyguardFadedAway(); + } + }) .start(); } @@ -4132,7 +4199,6 @@ public class StatusBar extends SystemUI implements DemoMode, * Plays the animation when an activity that was occluding Keyguard goes away. */ public void animateKeyguardUnoccluding() { - mScrimController.animateKeyguardUnoccluding(500); mNotificationPanel.setExpandedFraction(0f); animateExpandNotificationsPanel(); } @@ -4320,11 +4386,6 @@ public class StatusBar extends SystemUI implements DemoMode, mAmbientIndicationContainer.setVisibility(View.INVISIBLE); } } - if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { - mScrimController.setKeyguardShowing(true); - } else { - mScrimController.setKeyguardShowing(false); - } mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade); updateTheme(); updateDozingState(); @@ -4332,6 +4393,7 @@ public class StatusBar extends SystemUI implements DemoMode, updateStackScrollerState(goingToFullShade, fromShadeLocked); updateNotifications(); checkBarModes(); + updateScrimController(); updateMediaMetaData(false, mState != StatusBarState.KEYGUARD); mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(), mUnlockMethodCache.isMethodSecure(), @@ -4369,7 +4431,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (mContext.getThemeResId() != themeResId) { mContext.setTheme(themeResId); if (inflated) { - reinflateViews(); + onThemeChanged(); } } @@ -4395,11 +4457,10 @@ public class StatusBar extends SystemUI implements DemoMode, boolean animate = !mDozing && mDozeServiceHost.shouldAnimateWakeup(); mNotificationPanel.setDozing(mDozing, animate); mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation); - mScrimController.setDozing(mDozing); + mDozeScrimController.setDozing(mDozing); mKeyguardIndicationController.setDozing(mDozing); mNotificationPanel.setDark(mDozing, animate); updateQsExpansionEnabled(); - mDozeScrimController.setDozing(mDozing, animate); updateRowStates(); Trace.endSection(); } @@ -4894,6 +4955,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (mStatusBarView != null) mStatusBarView.setBouncerShowing(bouncerShowing); updateHideIconsForBouncer(true /* animate */); recomputeDisableFlags(true /* animate */); + updateScrimController(); } public void cancelCurrentTouch() { @@ -4945,12 +5007,10 @@ public class StatusBar extends SystemUI implements DemoMode, mStackScroller.setAnimationsEnabled(true); mVisualStabilityManager.setScreenOn(true); mNotificationPanel.setTouchDisabled(false); - - maybePrepareWakeUpFromAod(); - mDozeServiceHost.stopDozing(); updateVisibleToUser(); updateIsKeyguard(); + updateScrimController(); } }; @@ -4960,18 +5020,16 @@ public class StatusBar extends SystemUI implements DemoMode, mFalsingManager.onScreenTurningOn(); mNotificationPanel.onScreenTurningOn(); - maybePrepareWakeUpFromAod(); - if (mLaunchCameraOnScreenTurningOn) { mNotificationPanel.launchCamera(false, mLastCameraLaunchSource); mLaunchCameraOnScreenTurningOn = false; } + + updateScrimController(); } @Override public void onScreenTurnedOn() { - mScrimController.wakeUpFromAod(); - mDozeScrimController.onScreenTurnedOn(); } @Override @@ -4989,13 +5047,6 @@ public class StatusBar extends SystemUI implements DemoMode, return mWakefulnessLifecycle.getWakefulness(); } - private void maybePrepareWakeUpFromAod() { - int wakefulness = mWakefulnessLifecycle.getWakefulness(); - if (mDozing && wakefulness == WAKEFULNESS_WAKING && !isPulsing()) { - mScrimController.prepareWakeUpFromAod(); - } - } - private void vibrateForCameraGesture() { // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep. mVibrator.vibrate(mCameraLaunchGestureVibePattern, -1 /* repeat */); @@ -5078,12 +5129,12 @@ public class StatusBar extends SystemUI implements DemoMode, if (!mDeviceInteractive) { // Avoid flickering of the scrim when we instant launch the camera and the bouncer // comes on. - mScrimController.dontAnimateBouncerChangesUntilNextFrame(); mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L); } if (isScreenTurningOnOrOn()) { if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Launching camera"); mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source); + updateScrimController(); } else { // We need to defer the camera launch until the screen comes on, since otherwise // we will dismiss us too early since we are waiting on an activity to be drawn and @@ -5125,15 +5176,16 @@ public class StatusBar extends SystemUI implements DemoMode, private void updateDozing() { Trace.beginSection("StatusBar#updateDozing"); // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked. - mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD + boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD || mFingerprintUnlockController.getMode() == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING; // When in wake-and-unlock we may not have received a change to mState // but we still should not be dozing, manually set to false. if (mFingerprintUnlockController.getMode() == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) { - mDozing = false; + dozing = false; } + mDozing = dozing; mStatusBarWindowManager.setDozing(mDozing); mStatusBarKeyguardViewManager.setDozing(mDozing); if (mAmbientIndicationContainer instanceof DozeReceiver) { @@ -5143,6 +5195,24 @@ public class StatusBar extends SystemUI implements DemoMode, Trace.endSection(); } + public void updateScrimController() { + if (mBouncerShowing) { + mScrimController.transitionTo(ScrimState.BOUNCER); + } else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) { + mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); + } else if (mBrightnessMirrorVisible) { + mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR); + } else if (isPulsing()) { + // Handled in DozeScrimController#setPulsing + } else if (mDozing) { + mScrimController.transitionTo(ScrimState.AOD); + } else if (mIsKeyguard) { + mScrimController.transitionTo(ScrimState.KEYGUARD); + } else { + mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); + } + } + public boolean isKeyguardShowing() { if (mStatusBarKeyguardViewManager == null) { Slog.i(TAG, "isKeyguardShowing() called before startKeyguard(), returning true"); @@ -5202,7 +5272,6 @@ public class StatusBar extends SystemUI implements DemoMode, } mDozeScrimController.pulse(new PulseCallback() { - @Override public void onPulseStarted() { callback.onPulseStarted(); @@ -5287,11 +5356,6 @@ public class StatusBar extends SystemUI implements DemoMode, } @Override - public void abortPulsing() { - mDozeScrimController.abortPulsing(); - } - - @Override public void extendPulse() { mDozeScrimController.extendPulse(); } @@ -5327,7 +5391,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void setAodDimmingScrim(float scrimOpacity) { - mDozeScrimController.setAodDimmingScrim(scrimOpacity); + ScrimState.AOD.setAodFrontScrimAlpha(scrimOpacity); } public void dispatchDoubleTap(float viewX, float viewY) { diff --git a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 09828dcd..ef05bbb4 100644 --- a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -24,7 +24,6 @@ import android.content.ComponentCallbacks2; import android.content.Context; import android.os.Bundle; import android.os.SystemClock; -import android.os.Trace; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; @@ -71,17 +70,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb protected final Context mContext; private final StatusBarWindowManager mStatusBarWindowManager; - private final boolean mDisplayBlanksAfterDoze; protected LockPatternUtils mLockPatternUtils; protected ViewMediatorCallback mViewMediatorCallback; protected StatusBar mStatusBar; - private ScrimController mScrimController; private FingerprintUnlockController mFingerprintUnlockController; private ViewGroup mContainer; - private boolean mScreenTurnedOn; protected KeyguardBouncer mBouncer; protected boolean mShowing; protected boolean mOccluded; @@ -95,12 +91,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private boolean mLastBouncerDismissible; protected boolean mLastRemoteInputActive; private boolean mLastDozing; - private boolean mLastDeferScrimFadeOut; private int mLastFpMode; private OnDismissAction mAfterKeyguardGoneAction; private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>(); - private boolean mDeferScrimFadeOut; // Dismiss action to be launched when we stop dozing or the keyguard is gone. private DismissWithActionRequest mPendingWakeupAction; @@ -125,18 +119,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mLockPatternUtils = lockPatternUtils; mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class); KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback); - mDisplayBlanksAfterDoze = context.getResources().getBoolean( - com.android.internal.R.bool.config_displayBlanksAfterDoze); } public void registerStatusBar(StatusBar statusBar, ViewGroup container, - ScrimController scrimController, FingerprintUnlockController fingerprintUnlockController, DismissCallbackRegistry dismissCallbackRegistry) { mStatusBar = statusBar; mContainer = container; - mScrimController = scrimController; mFingerprintUnlockController = fingerprintUnlockController; mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext, mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry); @@ -149,7 +139,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void show(Bundle options) { mShowing = true; mStatusBarWindowManager.setKeyguardShowing(true); - mScrimController.abortKeyguardFadingOut(); reset(true /* hideBouncerWhenShowing */); } @@ -253,15 +242,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public void onScreenTurnedOn() { - Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurnedOn"); - mScreenTurnedOn = true; - if (mDeferScrimFadeOut) { - mDeferScrimFadeOut = false; - animateScrimControllerKeyguardFadingOut(0, WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS, - true /* skipFirstFrame */); - updateStates(); - } - Trace.endSection(); + // TODO: remove } @Override @@ -285,7 +266,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public void onScreenTurnedOff() { - mScreenTurnedOn = false; + // TODO: remove } public void notifyDeviceWakeUpRequested() { @@ -374,10 +355,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mStatusBarWindowManager.setKeyguardFadingAway(true); hideBouncer(true /* destroyView */); updateStates(); - mScrimController.animateKeyguardFadingOut( - StatusBar.FADE_KEYGUARD_START_DELAY, - StatusBar.FADE_KEYGUARD_DURATION, null, - false /* skipFirstFrame */); } }, new Runnable() { @Override @@ -400,36 +377,16 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mFingerprintUnlockController.startKeyguardFadingAway(); hideBouncer(true /* destroyView */); if (wakeUnlockPulsing) { - mStatusBarWindowManager.setKeyguardFadingAway(true); mStatusBar.fadeKeyguardWhilePulsing(); - animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration, - mStatusBar::hideKeyguard, false /* skipFirstFrame */); + wakeAndUnlockDejank(); } else { mFingerprintUnlockController.startKeyguardFadingAway(); mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration); boolean staying = mStatusBar.hideKeyguard(); if (!staying) { mStatusBarWindowManager.setKeyguardFadingAway(true); - if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) { - boolean turnedOnSinceAuth = - mFingerprintUnlockController.hasScreenTurnedOnSinceAuthenticating(); - if (!mScreenTurnedOn || mDisplayBlanksAfterDoze && !turnedOnSinceAuth) { - // Not ready to animate yet; either because the screen is not on yet, - // or it is on but will turn off before waking out of doze. - mDeferScrimFadeOut = true; - } else { - - // Screen is already on, don't defer with fading out. - animateScrimControllerKeyguardFadingOut(0, - WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS, - true /* skipFirstFrame */); - } - } else { - animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration, - false /* skipFirstFrame */); - } + wakeAndUnlockDejank(); } else { - mScrimController.animateGoingToFullShade(delay, fadeoutDuration); mStatusBar.finishKeyguardFadingAway(); mFingerprintUnlockController.finishKeyguardFadingAway(); } @@ -444,35 +401,22 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb hideBouncer(true /* destroyView */); } - public void onOverlayChanged() { + public void onThemeChanged() { hideBouncer(true /* destroyView */); mBouncer.prepare(); } - private void animateScrimControllerKeyguardFadingOut(long delay, long duration, - boolean skipFirstFrame) { - animateScrimControllerKeyguardFadingOut(delay, duration, null /* endRunnable */, - skipFirstFrame); + public void onKeyguardFadedAway() { + mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false), + 100); + mStatusBar.finishKeyguardFadingAway(); + mFingerprintUnlockController.finishKeyguardFadingAway(); + WindowManagerGlobal.getInstance().trimMemory( + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); + } - private void animateScrimControllerKeyguardFadingOut(long delay, long duration, - final Runnable endRunnable, boolean skipFirstFrame) { - Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "Fading out", 0); - mScrimController.animateKeyguardFadingOut(delay, duration, new Runnable() { - @Override - public void run() { - if (endRunnable != null) { - endRunnable.run(); - } - mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false), - 100); - mStatusBar.finishKeyguardFadingAway(); - mFingerprintUnlockController.finishKeyguardFadingAway(); - WindowManagerGlobal.getInstance().trimMemory( - ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); - Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "Fading out", 0); - } - }, skipFirstFrame); + private void wakeAndUnlockDejank() { if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK && LatencyTracker.isEnabled(mContext)) { DejankUtils.postAfterTraversal(() -> @@ -593,7 +537,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) { mStatusBarWindowManager.setBouncerShowing(bouncerShowing); mStatusBar.setBouncerShowing(bouncerShowing); - mScrimController.setBouncerShowing(bouncerShowing); } KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); @@ -611,7 +554,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mLastBouncerDismissible = bouncerDismissible; mLastRemoteInputActive = remoteInputActive; mLastDozing = mDozing; - mLastDeferScrimFadeOut = mDeferScrimFadeOut; mLastFpMode = mFingerprintUnlockController.getMode(); mStatusBar.onKeyguardViewManagerStatesUpdated(); } @@ -624,7 +566,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb boolean keyguardShowing = mShowing && !mOccluded; boolean hideWhileDozing = mDozing && fpMode != MODE_WAKE_AND_UNLOCK_PULSING; return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing() - || mRemoteInputActive) && !mDeferScrimFadeOut; + || mRemoteInputActive); } /** @@ -634,7 +576,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb boolean keyguardShowing = mLastShowing && !mLastOccluded; boolean hideWhileDozing = mLastDozing && mLastFpMode != MODE_WAKE_AND_UNLOCK_PULSING; return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing - || mLastRemoteInputActive) && !mLastDeferScrimFadeOut; + || mLastRemoteInputActive); } public boolean shouldDismissOnMenuPressed() { diff --git a/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/com/android/systemui/statusbar/policy/BrightnessMirrorController.java index 42ce4c5b..a011952f 100644 --- a/com/android/systemui/statusbar/policy/BrightnessMirrorController.java +++ b/com/android/systemui/statusbar/policy/BrightnessMirrorController.java @@ -16,7 +16,9 @@ package com.android.systemui.statusbar.policy; +import android.annotation.NonNull; import android.util.ArraySet; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewPropertyAnimator; @@ -25,55 +27,52 @@ import android.widget.FrameLayout; import com.android.internal.util.Preconditions; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBarWindowView; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; +import java.util.function.Consumer; + /** * Controls showing and hiding of the brightness mirror. */ public class BrightnessMirrorController implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> { - private final NotificationStackScrollLayout mStackScroller; - public long TRANSITION_DURATION_OUT = 150; - public long TRANSITION_DURATION_IN = 200; + private final static long TRANSITION_DURATION_OUT = 150; + private final static long TRANSITION_DURATION_IN = 200; private final StatusBarWindowView mStatusBarWindow; - private final ScrimController mScrimController; + private final NotificationStackScrollLayout mStackScroller; + private final Consumer<Boolean> mVisibilityCallback; private final View mNotificationPanel; private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>(); private final int[] mInt2Cache = new int[2]; private View mBrightnessMirror; public BrightnessMirrorController(StatusBarWindowView statusBarWindow, - ScrimController scrimController) { + @NonNull Consumer<Boolean> visibilityCallback) { mStatusBarWindow = statusBarWindow; mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror); mNotificationPanel = statusBarWindow.findViewById(R.id.notification_panel); - mStackScroller = (NotificationStackScrollLayout) statusBarWindow.findViewById( - R.id.notification_stack_scroller); - mScrimController = scrimController; + mStackScroller = statusBarWindow.findViewById(R.id.notification_stack_scroller); + mVisibilityCallback = visibilityCallback; } public void showMirror() { mBrightnessMirror.setVisibility(View.VISIBLE); mStackScroller.setFadingOut(true); - mScrimController.forceHideScrims(true /* hide */, true /* animated */); + mVisibilityCallback.accept(true); outAnimation(mNotificationPanel.animate()) .withLayer(); } public void hideMirror() { - mScrimController.forceHideScrims(false /* hide */, true /* animated */); + mVisibilityCallback.accept(false); inAnimation(mNotificationPanel.animate()) .withLayer() - .withEndAction(new Runnable() { - @Override - public void run() { - mBrightnessMirror.setVisibility(View.INVISIBLE); - mStackScroller.setFadingOut(false); - } + .withEndAction(() -> { + mBrightnessMirror.setVisibility(View.INVISIBLE); + mStackScroller.setFadingOut(false); }); } @@ -128,9 +127,11 @@ public class BrightnessMirrorController } private void reinflate() { + ContextThemeWrapper qsThemeContext = + new ContextThemeWrapper(mBrightnessMirror.getContext(), R.style.qs_theme); int index = mStatusBarWindow.indexOfChild(mBrightnessMirror); mStatusBarWindow.removeView(mBrightnessMirror); - mBrightnessMirror = LayoutInflater.from(mBrightnessMirror.getContext()).inflate( + mBrightnessMirror = LayoutInflater.from(qsThemeContext).inflate( R.layout.brightness_mirror, mStatusBarWindow, false); mStatusBarWindow.addView(mBrightnessMirror, index); diff --git a/com/android/systemui/statusbar/policy/RemoteInputView.java b/com/android/systemui/statusbar/policy/RemoteInputView.java index 37b0de40..4fc50442 100644 --- a/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -53,11 +53,9 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.notification.NotificationViewWrapper; -import com.android.systemui.statusbar.stack.ScrollContainer; import com.android.systemui.statusbar.stack.StackStateAnimator; /** @@ -82,8 +80,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private NotificationData.Entry mEntry; - private ScrollContainer mScrollContainer; - private View mScrollContainerChild; private boolean mRemoved; private int mRevealCx; @@ -347,41 +343,16 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { - findScrollContainer(); - if (mScrollContainer != null) { - mScrollContainer.requestDisallowLongPress(); - mScrollContainer.requestDisallowDismiss(); - } + mController.requestDisallowLongPressAndDismiss(); } return super.onInterceptTouchEvent(ev); } public boolean requestScrollTo() { - findScrollContainer(); - mScrollContainer.lockScrollTo(mScrollContainerChild); + mController.lockScrollTo(mEntry); return true; } - private void findScrollContainer() { - if (mScrollContainer == null) { - mScrollContainerChild = null; - ViewParent p = this; - while (p != null) { - if (mScrollContainerChild == null && p instanceof ExpandableView) { - mScrollContainerChild = (View) p; - } - if (p.getParent() instanceof ScrollContainer) { - mScrollContainer = (ScrollContainer) p.getParent(); - if (mScrollContainerChild == null) { - mScrollContainerChild = (View) p; - } - break; - } - p = p.getParent(); - } - } - } - public boolean isActive() { return mEditText.isFocused() && mEditText.isEnabled(); } diff --git a/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java index 527addf1..f5ae88b1 100644 --- a/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java +++ b/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java @@ -154,7 +154,9 @@ public class UserInfoControllerImpl implements UserInfoController { avatar = new UserIconDrawable(avatarSize) .setIcon(rawAvatar).setBadgeIfManagedUser(mContext, userId).bake(); } else { - avatar = UserIcons.getDefaultUserIcon(isGuest? UserHandle.USER_NULL : userId, + avatar = UserIcons.getDefaultUserIcon( + context.getResources(), + isGuest? UserHandle.USER_NULL : userId, lightIcon); } diff --git a/com/android/systemui/statusbar/policy/UserSwitcherController.java b/com/android/systemui/statusbar/policy/UserSwitcherController.java index 700c01a5..7006d389 100644 --- a/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -747,7 +747,8 @@ public class UserSwitcherController { if (item.isAddUser) { return context.getDrawable(R.drawable.ic_add_circle_qs); } - Drawable icon = UserIcons.getDefaultUserIcon(item.resolveId(), /* light= */ false); + Drawable icon = UserIcons.getDefaultUserIcon( + context.getResources(), item.resolveId(), /* light= */ false); if (item.isGuest) { icon.setColorFilter(Utils.getColorAttr(context, android.R.attr.colorForeground), Mode.SRC_IN); @@ -910,6 +911,7 @@ public class UserSwitcherController { context.getString(android.R.string.cancel), this); setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.guest_exit_guest_dialog_remove), this); + SystemUIDialog.setWindowOnTop(this); setCanceledOnTouchOutside(false); mGuestId = guestId; mTargetId = targetId; @@ -937,6 +939,7 @@ public class UserSwitcherController { context.getString(android.R.string.cancel), this); setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok), this); + SystemUIDialog.setWindowOnTop(this); } @Override @@ -957,7 +960,7 @@ public class UserSwitcherController { } int id = user.id; Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon( - id, /* light= */ false)); + mContext.getResources(), id, /* light= */ false)); mUserManager.setUserIcon(id, icon); switchToUserId(id); } diff --git a/com/android/systemui/statusbar/stack/AnimationProperties.java b/com/android/systemui/statusbar/stack/AnimationProperties.java index ebb0a6d2..2f6e6584 100644 --- a/com/android/systemui/statusbar/stack/AnimationProperties.java +++ b/com/android/systemui/statusbar/stack/AnimationProperties.java @@ -36,7 +36,12 @@ public class AnimationProperties { * @return an animation filter for this animation. */ public AnimationFilter getAnimationFilter() { - return new AnimationFilter(); + return new AnimationFilter() { + @Override + public boolean shouldAnimateProperty(Property property) { + return true; + } + }; } /** diff --git a/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java index fe53104f..c0241e36 100644 --- a/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java +++ b/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java @@ -1260,4 +1260,17 @@ public class NotificationChildrenContainer extends ViewGroup { public boolean isUserLocked() { return mUserLocked; } + + public void setCurrentBottomRoundness(float currentBottomRoundness) { + boolean last = true; + for (int i = mChildren.size() - 1; i >= 0; i--) { + ExpandableNotificationRow child = mChildren.get(i); + if (child.getVisibility() == View.GONE) { + continue; + } + float bottomRoundness = last ? currentBottomRoundness : 0.0f; + child.setBottomRoundness(bottomRoundness, isShown() /* animate */); + last = false; + } + } } diff --git a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index efe049ab..ebebfacf 100644 --- a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -30,6 +30,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.PointF; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; @@ -75,6 +76,7 @@ import com.android.systemui.statusbar.ActivatableNotificationView; import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.ExpandableOutlineView; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationGuts; @@ -82,8 +84,10 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationSnooze; import com.android.systemui.statusbar.StackScrollerDecorView; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; @@ -108,8 +112,7 @@ import java.util.List; public class NotificationStackScrollLayout extends ViewGroup implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter, ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener, - NotificationMenuRowPlugin.OnMenuEventListener, ScrollContainer, - VisibilityLocationProvider { + NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider { public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; private static final String TAG = "StackScroller"; @@ -121,12 +124,23 @@ public class NotificationStackScrollLayout extends ViewGroup * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}. */ private static final int INVALID_POINTER = -1; + private static final AnimatableProperty SIDE_PADDINGS = AnimatableProperty.from( + "sidePaddings", + NotificationStackScrollLayout::setCurrentSidePadding, + NotificationStackScrollLayout::getCurrentSidePadding, + R.id.side_padding_animator_tag, + R.id.side_padding_animator_end_tag, + R.id.side_padding_animator_start_tag); + private static final AnimationProperties SIDE_PADDING_PROPERTIES = + new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); private ExpandHelper mExpandHelper; private NotificationSwipeHelper mSwipeHelper; private boolean mSwipingInProgress; private int mCurrentStackHeight = Integer.MAX_VALUE; private final Paint mBackgroundPaint = new Paint(); + private final Path mBackgroundPath = new Path(); + private final float[] mBackgroundRadii = new float[8]; private final boolean mShouldDrawNotificationBackground; private float mExpandedHeight; @@ -157,6 +171,7 @@ public class NotificationStackScrollLayout extends ViewGroup private int mTopPadding; private int mBottomMargin; private int mBottomInset = 0; + private float mCurrentSidePadding; /** * The algorithm which calculates the properties for our children @@ -383,6 +398,9 @@ public class NotificationStackScrollLayout extends ViewGroup private int mCachedBackgroundColor; private boolean mHeadsUpGoingAwayAnimationsAllowed = true; private Runnable mAnimateScroll = this::animateScroll; + private int mCornerRadius; + private int mLockscreenSidePaddings; + private int mSidePaddings; public NotificationStackScrollLayout(Context context) { this(context, null); @@ -419,6 +437,8 @@ public class NotificationStackScrollLayout extends ViewGroup res.getBoolean(R.bool.config_fadeNotificationsOnDismiss); updateWillNotDraw(); + mBackgroundPaint.setAntiAlias(true); + mBackgroundPaint.setStyle(Paint.Style.FILL); if (DEBUG) { mDebugPaint = new Paint(); mDebugPaint.setColor(0xffff0000); @@ -466,8 +486,7 @@ public class NotificationStackScrollLayout extends ViewGroup protected void onDraw(Canvas canvas) { if (mShouldDrawNotificationBackground && !mAmbientState.isDark() && mCurrentBounds.top < mCurrentBounds.bottom) { - canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, - mBackgroundPaint); + canvas.drawPath(mBackgroundPath, mBackgroundPaint); } if (DEBUG) { @@ -520,8 +539,12 @@ public class NotificationStackScrollLayout extends ViewGroup R.dimen.min_top_overscroll_to_qs); mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height); mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom); + mLockscreenSidePaddings = res.getDimensionPixelSize( + R.dimen.notification_lockscreen_side_paddings); mMinInteractionHeight = res.getDimensionPixelSize( R.dimen.notification_min_interaction_height); + mCornerRadius = res.getDimensionPixelSize( + Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); } public void setDrawBackgroundAsSrc(boolean asSrc) { @@ -1195,7 +1218,6 @@ public class NotificationStackScrollLayout extends ViewGroup mScrollingEnabled = enable; } - @Override public void lockScrollTo(View v) { if (mForcedScroll == v) { return; @@ -1204,7 +1226,6 @@ public class NotificationStackScrollLayout extends ViewGroup scrollTo(v); } - @Override public boolean scrollTo(View v) { ExpandableView expandableView = (ExpandableView) v; int positionInLinearLayout = getPositionInLinearLayout(v); @@ -2221,9 +2242,31 @@ public class NotificationStackScrollLayout extends ViewGroup mScrimController.setExcludedBackgroundArea( mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null : mCurrentBounds); + updateBackgroundPath(); invalidate(); } + private void updateBackgroundPath() { + mBackgroundPath.reset(); + float topRoundness = 0; + if (mFirstVisibleBackgroundChild != null) { + topRoundness = mFirstVisibleBackgroundChild.getCurrentBackgroundRadiusTop(); + } + topRoundness = onKeyguard() ? mCornerRadius : topRoundness; + float bottomRoundNess = mCornerRadius; + mBackgroundRadii[0] = topRoundness; + mBackgroundRadii[1] = topRoundness; + mBackgroundRadii[2] = topRoundness; + mBackgroundRadii[3] = topRoundness; + mBackgroundRadii[4] = bottomRoundNess; + mBackgroundRadii[5] = bottomRoundNess; + mBackgroundRadii[6] = bottomRoundNess; + mBackgroundRadii[7] = bottomRoundNess; + mBackgroundPath.addRoundRect(mCurrentSidePadding, mCurrentBounds.top, + getWidth() - mCurrentSidePadding, mCurrentBounds.bottom, mBackgroundRadii, + Path.Direction.CCW); + } + /** * Update the background bounds to the new desired bounds */ @@ -2236,6 +2279,8 @@ public class NotificationStackScrollLayout extends ViewGroup mBackgroundBounds.left = mTempInt2[0]; mBackgroundBounds.right = mTempInt2[0] + getWidth(); } + mBackgroundBounds.left += mCurrentSidePadding; + mBackgroundBounds.right -= mCurrentSidePadding; if (!mIsExpanded) { mBackgroundBounds.top = 0; mBackgroundBounds.bottom = 0; @@ -2820,16 +2865,45 @@ public class NotificationStackScrollLayout extends ViewGroup private void updateFirstAndLastBackgroundViews() { ActivatableNotificationView firstChild = getFirstChildWithBackground(); ActivatableNotificationView lastChild = getLastChildWithBackground(); + boolean firstChanged = firstChild != mFirstVisibleBackgroundChild; + boolean lastChanged = lastChild != mLastVisibleBackgroundChild; if (mAnimationsEnabled && mIsExpanded) { - mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild; - mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild; + mAnimateNextBackgroundTop = firstChanged; + mAnimateNextBackgroundBottom = lastChanged; } else { mAnimateNextBackgroundTop = false; mAnimateNextBackgroundBottom = false; } + if (firstChanged && mFirstVisibleBackgroundChild != null + && !mFirstVisibleBackgroundChild.isRemoved()) { + mFirstVisibleBackgroundChild.setTopRoundness(0.0f, + mFirstVisibleBackgroundChild.isShown()); + } + if (lastChanged && mLastVisibleBackgroundChild != null + && !mLastVisibleBackgroundChild.isRemoved()) { + mLastVisibleBackgroundChild.setBottomRoundness(0.0f, + mLastVisibleBackgroundChild.isShown()); + } mFirstVisibleBackgroundChild = firstChild; mLastVisibleBackgroundChild = lastChild; mAmbientState.setLastVisibleBackgroundChild(lastChild); + applyRoundedNess(); + } + + private void applyRoundedNess() { + if (mFirstVisibleBackgroundChild != null) { + mFirstVisibleBackgroundChild.setTopRoundness( + mStatusBarState == StatusBarState.KEYGUARD ? 1.0f : 0.0f, + mFirstVisibleBackgroundChild.isShown() + && !mChildrenToAddAnimated.contains(mFirstVisibleBackgroundChild)); + } + if (mLastVisibleBackgroundChild != null) { + mLastVisibleBackgroundChild.setBottomRoundness(1.0f, + mLastVisibleBackgroundChild.isShown() + && !mChildrenToAddAnimated.contains(mLastVisibleBackgroundChild)); + } + updateBackgroundPath(); + invalidate(); } private void onViewAddedInternal(View child) { @@ -2838,6 +2912,7 @@ public class NotificationStackScrollLayout extends ViewGroup generateAddAnimation(child, false /* fromMoreCard */); updateAnimationState(child); updateChronometerForChild(child); + updateCurrentSidePaddings(child); } private void updateHideSensitiveForChild(View child) { @@ -3321,12 +3396,10 @@ public class NotificationStackScrollLayout extends ViewGroup } } - @Override public void requestDisallowLongPress() { cancelLongPress(); } - @Override public void requestDisallowDismiss() { mDisallowDismissInThisMotion = true; } @@ -4285,6 +4358,43 @@ public class NotificationStackScrollLayout extends ViewGroup public void setStatusBarState(int statusBarState) { mStatusBarState = statusBarState; mAmbientState.setStatusBarState(statusBarState); + applyRoundedNess(); + updateSidePaddings(); + } + + private void updateSidePaddings() { + int sidePaddings = mStatusBarState == StatusBarState.KEYGUARD ? mLockscreenSidePaddings : 0; + if (sidePaddings != mSidePaddings) { + boolean animate = isShown(); + mSidePaddings = sidePaddings; + PropertyAnimator.setProperty(this, SIDE_PADDINGS, sidePaddings, + SIDE_PADDING_PROPERTIES, animate); + } + } + + protected void setCurrentSidePadding(float sidePadding) { + mCurrentSidePadding = sidePadding; + updateBackground(); + applySidePaddingsToChildren(); + } + + private void applySidePaddingsToChildren() { + for (int i = 0; i < getChildCount(); i++) { + View view = getChildAt(i); + updateCurrentSidePaddings(view); + } + } + + private void updateCurrentSidePaddings(View view) { + if (!(view instanceof ExpandableOutlineView)) { + return; + } + ExpandableOutlineView outlineView = (ExpandableOutlineView) view; + outlineView.setCurrentSidePaddings(mCurrentSidePadding); + } + + protected float getCurrentSidePadding() { + return mCurrentSidePadding; } public void setExpandingVelocity(float expandingVelocity) { diff --git a/com/android/systemui/statusbar/stack/ScrollContainer.java b/com/android/systemui/statusbar/stack/ScrollContainer.java deleted file mode 100644 index b9d12ce8..00000000 --- a/com/android/systemui/statusbar/stack/ScrollContainer.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.stack; - -import android.view.View; - -/** - * Interface for container layouts that scroll and listen for long presses. A child that - * wants to handle long press can use this to cancel the parents long press logic or request - * to be made visible by scrolling to it. - */ -public interface ScrollContainer { - /** - * Request that the view does not perform long press for the current touch. - */ - void requestDisallowLongPress(); - - /** - * Request that the view is made visible by scrolling to it. - * Return true if it scrolls. - */ - boolean scrollTo(View v); - - /** - * Like {@link #scrollTo(View)}, but keeps the scroll locked until the user - * scrolls, or {@param v} loses focus or is detached. - */ - void lockScrollTo(View v); - - /** - * Request that the view does not dismiss for the current touch. - */ - void requestDisallowDismiss(); -} diff --git a/com/android/systemui/statusbar/stack/ViewState.java b/com/android/systemui/statusbar/stack/ViewState.java index 27b730cd..682b8493 100644 --- a/com/android/systemui/statusbar/stack/ViewState.java +++ b/com/android/systemui/statusbar/stack/ViewState.java @@ -21,7 +21,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; -import android.app.Notification; import android.util.Property; import android.view.View; import android.view.animation.Interpolator; @@ -29,7 +28,7 @@ import android.view.animation.Interpolator; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableView; -import com.android.systemui.statusbar.NotificationShelf; +import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -64,8 +63,8 @@ public class ViewState { private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag; private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag; - private static final PropertyAnimator.AnimatableProperty SCALE_X_PROPERTY - = new PropertyAnimator.AnimatableProperty() { + private static final AnimatableProperty SCALE_X_PROPERTY + = new AnimatableProperty() { @Override public int getAnimationStartTag() { @@ -88,8 +87,8 @@ public class ViewState { } }; - private static final PropertyAnimator.AnimatableProperty SCALE_Y_PROPERTY - = new PropertyAnimator.AnimatableProperty() { + private static final AnimatableProperty SCALE_Y_PROPERTY + = new AnimatableProperty() { @Override public int getAnimationStartTag() { @@ -251,7 +250,7 @@ public class ViewState { return getChildTag(view, tag) != null; } - public static boolean isAnimating(View view, PropertyAnimator.AnimatableProperty property) { + public static boolean isAnimating(View view, AnimatableProperty property) { return getChildTag(view, property.getAnimatorTag()) != null; } @@ -403,7 +402,7 @@ public class ViewState { startZTranslationAnimation(view, NO_NEW_ANIMATIONS); } - private void updateAnimation(View view, PropertyAnimator.AnimatableProperty property, + private void updateAnimation(View view, AnimatableProperty property, float endValue) { PropertyAnimator.startAnimation(view, property, endValue, NO_NEW_ANIMATIONS); } diff --git a/com/android/systemui/usb/UsbPermissionActivity.java b/com/android/systemui/usb/UsbPermissionActivity.java index 87d11b24..4606aee3 100644 --- a/com/android/systemui/usb/UsbPermissionActivity.java +++ b/com/android/systemui/usb/UsbPermissionActivity.java @@ -235,7 +235,7 @@ public class UsbPermissionActivity extends AlertActivity intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice); if (mPermissionGranted) { service.grantDevicePermission(mDevice, mUid); - if (mAlwaysUse.isChecked()) { + if (mAlwaysUse != null && mAlwaysUse.isChecked()) { final int userId = UserHandle.getUserId(mUid); service.setDevicePackage(mDevice, mPackageName, userId); } @@ -245,7 +245,7 @@ public class UsbPermissionActivity extends AlertActivity intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory); if (mPermissionGranted) { service.grantAccessoryPermission(mAccessory, mUid); - if (mAlwaysUse.isChecked()) { + if (mAlwaysUse != null && mAlwaysUse.isChecked()) { final int userId = UserHandle.getUserId(mUid); service.setAccessoryPackage(mAccessory, mPackageName, userId); } diff --git a/com/android/systemui/volume/VolumeDialogImpl.java b/com/android/systemui/volume/VolumeDialogImpl.java index 4b8f581a..383d3276 100644 --- a/com/android/systemui/volume/VolumeDialogImpl.java +++ b/com/android/systemui/volume/VolumeDialogImpl.java @@ -207,6 +207,7 @@ public class VolumeDialogImpl implements VolumeDialog { } else { addExistingRows(); } + updateRowsH(getActiveRow()); } private ColorStateList loadColorStateList(int colorResId) { @@ -440,7 +441,8 @@ public class VolumeDialogImpl implements VolumeDialog { .withEndAction(() -> mDialog.dismiss()) .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator()) .start(); - if (mAccessibilityMgr.isEnabled()) { + if (mAccessibilityMgr.isObservedEventType( + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)) { AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); event.setPackageName(mContext.getPackageName()); diff --git a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java index 3d5476d0..7c9aeded 100644 --- a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java +++ b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * Copyright (C) 2013 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. @@ -16,55 +16,24 @@ package com.android.uiautomator.testrunner; -import android.content.Context; +import android.app.Instrumentation; import android.os.Bundle; -import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemClock; -import android.view.inputmethod.InputMethodInfo; +import android.test.InstrumentationTestCase; -import com.android.internal.view.IInputMethodManager; +import com.android.uiautomator.core.InstrumentationUiAutomatorBridge; import com.android.uiautomator.core.UiDevice; -import junit.framework.TestCase; - -import java.util.List; - /** - * UI automation test should extend this class. This class provides access - * to the following: - * {@link UiDevice} instance - * {@link Bundle} for command line parameters. - * @since API Level 16 + * UI Automator test case that is executed on the device. * @deprecated New tests should be written using UI Automator 2.0 which is available as part of the * Android Testing Support Library. */ @Deprecated -public class UiAutomatorTestCase extends TestCase { +public class UiAutomatorTestCase extends InstrumentationTestCase { - private static final String DISABLE_IME = "disable_ime"; - private static final String DUMMY_IME_PACKAGE = "com.android.testing.dummyime"; - private UiDevice mUiDevice; private Bundle mParams; private IAutomationSupport mAutomationSupport; - private boolean mShouldDisableIme = false; - - @Override - protected void setUp() throws Exception { - super.setUp(); - mShouldDisableIme = "true".equals(mParams.getString(DISABLE_IME)); - if (mShouldDisableIme) { - setDummyIme(); - } - } - - @Override - protected void tearDown() throws Exception { - if (mShouldDisableIme) { - restoreActiveIme(); - } - super.tearDown(); - } /** * Get current instance of {@link UiDevice}. Works similar to calling the static @@ -72,7 +41,7 @@ public class UiAutomatorTestCase extends TestCase { * @since API Level 16 */ public UiDevice getUiDevice() { - return mUiDevice; + return UiDevice.getInstance(); } /** @@ -85,34 +54,43 @@ public class UiAutomatorTestCase extends TestCase { return mParams; } + void setAutomationSupport(IAutomationSupport automationSupport) { + mAutomationSupport = automationSupport; + } + /** * Provides support for running tests to report interim status * * @return IAutomationSupport * @since API Level 16 + * @deprecated Use {@link Instrumentation#sendStatus(int, Bundle)} instead */ public IAutomationSupport getAutomationSupport() { + if (mAutomationSupport == null) { + mAutomationSupport = new InstrumentationAutomationSupport(getInstrumentation()); + } return mAutomationSupport; } /** - * package private - * @param uiDevice - */ - void setUiDevice(UiDevice uiDevice) { - mUiDevice = uiDevice; - } - - /** - * package private - * @param params + * Initializes this test case. + * + * @param params Instrumentation arguments. */ - void setParams(Bundle params) { + void initialize(Bundle params) { mParams = params; - } - void setAutomationSupport(IAutomationSupport automationSupport) { - mAutomationSupport = automationSupport; + // check if this is a monkey test mode + String monkeyVal = mParams.getString("monkey"); + if (monkeyVal != null) { + // only if the monkey key is specified, we alter the state of monkey + // else we should leave things as they are. + getInstrumentation().getUiAutomation().setRunAsMonkey(Boolean.valueOf(monkeyVal)); + } + + UiDevice.getInstance().initialize(new InstrumentationUiAutomatorBridge( + getInstrumentation().getContext(), + getInstrumentation().getUiAutomation())); } /** @@ -123,28 +101,4 @@ public class UiAutomatorTestCase extends TestCase { public void sleep(long ms) { SystemClock.sleep(ms); } - - private void setDummyIme() throws RemoteException { - IInputMethodManager im = IInputMethodManager.Stub.asInterface(ServiceManager - .getService(Context.INPUT_METHOD_SERVICE)); - List<InputMethodInfo> infos = im.getInputMethodList(); - String id = null; - for (InputMethodInfo info : infos) { - if (DUMMY_IME_PACKAGE.equals(info.getComponent().getPackageName())) { - id = info.getId(); - } - } - if (id == null) { - throw new RuntimeException(String.format( - "Required testing fixture missing: IME package (%s)", DUMMY_IME_PACKAGE)); - } - im.setInputMethod(null, id); - } - - private void restoreActiveIme() throws RemoteException { - // TODO: figure out a way to restore active IME - // Currently retrieving active IME requires querying secure settings provider, which is hard - // to do without a Context; so the caveat here is that to make the post test device usable, - // the active IME needs to be manually switched. - } } diff --git a/java/lang/Math.java b/java/lang/Math.java index 3ce39d9f..5c5ef1cb 100644 --- a/java/lang/Math.java +++ b/java/lang/Math.java @@ -106,6 +106,9 @@ import sun.misc.DoubleConsts; public final class Math { + // Android-changed: Numerous methods in this class are re-implemented in native for performance. + // Those methods are also annotated @FastNative. + /** * Don't let anyone instantiate this class. */ @@ -754,6 +757,8 @@ public final class Math { return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble(); } + // Android-added: setRandomSeedInternal(long), called after zygote forks. + // This allows different processes to have different random seeds. /** * Set the seed for the pseudo random generator used by {@link #random()} * and {@link #randomIntInternal()}. @@ -764,6 +769,7 @@ public final class Math { RandomNumberGeneratorHolder.randomNumberGenerator.setSeed(seed); } + // Android-added: randomIntInternal() method: like random() but for int. /** * @hide for internal use only. */ @@ -771,6 +777,7 @@ public final class Math { return RandomNumberGeneratorHolder.randomNumberGenerator.nextInt(); } + // Android-added: randomLongInternal() method: like random() but for long. /** * @hide for internal use only. */ @@ -1221,9 +1228,11 @@ public final class Math { * @return the absolute value of the argument. */ public static float abs(float a) { + // Android-changed: Implementation modified to exactly match ART intrinsics behavior. // Note, as a "quality of implementation", rather than pure "spec compliance", // we require that Math.abs() clears the sign bit (but changes nothing else) // for all numbers, including NaN (signaling NaN may become quiet though). + // http://b/30758343 return Float.intBitsToFloat(0x7fffffff & Float.floatToRawIntBits(a)); } @@ -1243,9 +1252,11 @@ public final class Math { * @return the absolute value of the argument. */ public static double abs(double a) { + // Android-changed: Implementation modified to exactly match ART intrinsics behavior. // Note, as a "quality of implementation", rather than pure "spec compliance", // we require that Math.abs() clears the sign bit (but changes nothing else) // for all numbers, including NaN (signaling NaN may become quiet though). + // http://b/30758343 return Double.longBitsToDouble(0x7fffffffffffffffL & Double.doubleToRawLongBits(a)); } diff --git a/java/lang/NoClassDefFoundError.java b/java/lang/NoClassDefFoundError.java index 869c8d9f..e4f764f5 100644 --- a/java/lang/NoClassDefFoundError.java +++ b/java/lang/NoClassDefFoundError.java @@ -60,6 +60,7 @@ class NoClassDefFoundError extends LinkageError { super(s); } + // Android-added: A new constructor for use by the Android runtime. /** * Constructs a new {@code NoClassDefFoundError} with the current stack * trace, the specified detail message and the specified cause. Used diff --git a/java/lang/Package.java b/java/lang/Package.java index 55d2ba7d..33136f32 100644 --- a/java/lang/Package.java +++ b/java/lang/Package.java @@ -279,6 +279,8 @@ public class Package implements java.lang.reflect.AnnotatedElement { */ @CallerSensitive public static Package getPackage(String name) { + // Android-changed: Use VMStack.getCallingClassLoader() to obtain the classloader. + // ClassLoader l = ClassLoader.getClassLoader(Reflection.getCallerClass()); ClassLoader l = VMStack.getCallingClassLoader(); if (l != null) { return l.getPackage(name); @@ -301,6 +303,8 @@ public class Package implements java.lang.reflect.AnnotatedElement { */ @CallerSensitive public static Package[] getPackages() { + // Android-changed: Use VMStack.getCallingClassLoader() to obtain the classloader. + // ClassLoader l = ClassLoader.getClassLoader(Reflection.getCallerClass()); ClassLoader l = VMStack.getCallingClassLoader(); if (l != null) { return l.getPackages(); @@ -358,7 +362,7 @@ public class Package implements java.lang.reflect.AnnotatedElement { * @return the string representation of the package. */ public String toString() { - // Android-changed start + // BEGIN Android-added: Backwards compatibility fix for target API <= 24. // Several apps try to parse the output of toString(). This is a really // bad idea - especially when there's a Package.getName() function as well as a // Class.getName() function that can be used instead. @@ -367,7 +371,7 @@ public class Package implements java.lang.reflect.AnnotatedElement { if (targetSdkVersion > 0 && targetSdkVersion <= 24) { return "package " + pkgName; } - // Android-changed end + // END Android-added: Backwards compatibility fix for target API <= 24. String spec = specTitle; String ver = specVersion; diff --git a/java/lang/ProcessBuilder.java b/java/lang/ProcessBuilder.java index 0c892c23..fc58abc3 100644 --- a/java/lang/ProcessBuilder.java +++ b/java/lang/ProcessBuilder.java @@ -64,7 +64,7 @@ import java.util.Map; * working directory of the current process, usually the directory * named by the system property {@code user.dir}. * - * <li><a name="redirect-input">a source of <i>standard input</i>. + * <li><a name="redirect-input">a source of <i>standard input</i></a>. * By default, the subprocess reads input from a pipe. Java code * can access this pipe via the output stream returned by * {@link Process#getOutputStream()}. However, standard input may @@ -80,7 +80,7 @@ import java.util.Map; * </ul> * * <li><a name="redirect-output">a destination for <i>standard output</i> - * and <i>standard error</i>. By default, the subprocess writes standard + * and <i>standard error</i></a>. By default, the subprocess writes standard * output and standard error to pipes. Java code can access these pipes * via the input streams returned by {@link Process#getInputStream()} and * {@link Process#getErrorStream()}. However, standard output and diff --git a/java/lang/reflect/AccessibleObject.java b/java/lang/reflect/AccessibleObject.java index 08fc8cd6..79413767 100644 --- a/java/lang/reflect/AccessibleObject.java +++ b/java/lang/reflect/AccessibleObject.java @@ -54,6 +54,17 @@ import java.lang.annotation.Annotation; */ public class AccessibleObject implements AnnotatedElement { + // Android-removed: Code associated with SecurityManager calls. + /* + /** + * The Permission object that is used to check whether a client + * has sufficient privilege to defeat Java language access + * control checks. + * + static final private java.security.Permission ACCESS_PERMISSION = + new ReflectPermission("suppressAccessChecks"); + */ + /** * Convenience method to set the {@code accessible} flag for an * array of objects with a single security check (for efficiency). @@ -81,6 +92,9 @@ public class AccessibleObject implements AnnotatedElement { */ public static void setAccessible(AccessibleObject[] array, boolean flag) throws SecurityException { + // Android-removed: SecurityManager calls. + // SecurityManager sm = System.getSecurityManager(); + // if (sm != null) sm.checkPermission(ACCESS_PERMISSION); for (int i = 0; i < array.length; i++) { setAccessible0(array[i], flag); } @@ -112,6 +126,9 @@ public class AccessibleObject implements AnnotatedElement { * @see java.lang.RuntimePermission */ public void setAccessible(boolean flag) throws SecurityException { + // Android-removed: SecurityManager calls. + // SecurityManager sm = System.getSecurityManager(); + // if (sm != null) sm.checkPermission(ACCESS_PERMISSION); setAccessible0(this, flag); } @@ -122,7 +139,12 @@ public class AccessibleObject implements AnnotatedElement { { if (obj instanceof Constructor && flag == true) { Constructor<?> c = (Constructor<?>)obj; - // Android-changed: Added additional checks below. + // BEGIN Android-changed: Disallow making Method & Field constructors accessible. + // if (c.getDeclaringClass() == Class.class) { + // throw new SecurityException("Cannot make a java.lang.Class" + + // " constructor accessible"); + // } + Class<?> clazz = c.getDeclaringClass(); if (c.getDeclaringClass() == Class.class) { throw new SecurityException("Can not make a java.lang.Class" + @@ -134,6 +156,7 @@ public class AccessibleObject implements AnnotatedElement { throw new SecurityException("Can not make a java.lang.reflect.Field" + " constructor accessible"); } + // END Android-changed: Disallow making Method & Field constructors accessible. } obj.override = flag; } @@ -160,6 +183,15 @@ public class AccessibleObject implements AnnotatedElement { // outside this package. boolean override; + /* Android-removed: reflectionFactory: it is not used on Android. + // Reflection factory used by subclasses for creating field, + // method, and constructor accessors. Note that this is called + // very early in the bootstrapping process. + static final ReflectionFactory reflectionFactory = + AccessController.doPrivileged( + new sun.reflect.ReflectionFactory.GetReflectionFactoryAction()); + */ + /** * @throws NullPointerException {@inheritDoc} * @since 1.5 @@ -224,4 +256,74 @@ public class AccessibleObject implements AnnotatedElement { public Annotation[] getDeclaredAnnotations() { throw new AssertionError("All subclasses should override this method"); } + + // BEGIN Android-removed: Shared access checking logic: Not used on Android. + /* + // For non-public members or members in package-private classes, + // it is necessary to perform somewhat expensive security checks. + // If the security check succeeds for a given class, it will + // always succeed (it is not affected by the granting or revoking + // of permissions); we speed up the check in the common case by + // remembering the last Class for which the check succeeded. + // + // The simple security check for Constructor is to see if + // the caller has already been seen, verified, and cached. + // (See also Class.newInstance(), which uses a similar method.) + // + // A more complicated security check cache is needed for Method and Field + // The cache can be either null (empty cache), a 2-array of {caller,target}, + // or a caller (with target implicitly equal to this.clazz). + // In the 2-array case, the target is always different from the clazz. + volatile Object securityCheckCache; + + void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers) + throws IllegalAccessException + { + if (caller == clazz) { // quick check + return; // ACCESS IS OK + } + Object cache = securityCheckCache; // read volatile + Class<?> targetClass = clazz; + if (obj != null + && Modifier.isProtected(modifiers) + && ((targetClass = obj.getClass()) != clazz)) { + // Must match a 2-list of { caller, targetClass }. + if (cache instanceof Class[]) { + Class<?>[] cache2 = (Class<?>[]) cache; + if (cache2[1] == targetClass && + cache2[0] == caller) { + return; // ACCESS IS OK + } + // (Test cache[1] first since range check for [1] + // subsumes range check for [0].) + } + } else if (cache == caller) { + // Non-protected case (or obj.class == this.clazz). + return; // ACCESS IS OK + } + + // If no return, fall through to the slow path. + slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass); + } + + // Keep all this slow stuff out of line: + void slowCheckMemberAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers, + Class<?> targetClass) + throws IllegalAccessException + { + Reflection.ensureMemberAccess(caller, clazz, obj, modifiers); + + // Success: Update the cache. + Object cache = ((targetClass == clazz) + ? caller + : new Class<?>[] { caller, targetClass }); + + // Note: The two cache elements are not volatile, + // but they are effectively final. The Java memory model + // guarantees that the initializing stores for the cache + // elements will occur before the volatile write. + securityCheckCache = cache; // write volatile + } + */ + // END Android-removed: Shared access checking logic: Not used on Android. } diff --git a/java/lang/reflect/AnnotatedElement.java b/java/lang/reflect/AnnotatedElement.java index a88d6c1e..cf6af474 100644 --- a/java/lang/reflect/AnnotatedElement.java +++ b/java/lang/reflect/AnnotatedElement.java @@ -31,7 +31,7 @@ import java.util.Objects; import libcore.reflect.AnnotatedElements; // Android-changed: Removed some references to bytecode spec below that do not -// apply and added a note about annotation ordering. +// apply to DEX and added a note about annotation ordering. /** * Represents an annotated element of the program currently running in this * VM. This interface allows annotations to be read reflectively. All @@ -316,6 +316,9 @@ public interface AnnotatedElement { * @since 1.8 */ default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) { + // Android-changed: Altered method implementation for getAnnotationsByType(Class). + // Android's annotation code is customized because of the use of the DEX format on Android + // and code sharing with the runtime. // This method does not handle inherited annotations and is intended for use for // {@code Method}, {@code Field}, {@code Package}. The {@link Class#getAnnotationsByType} // is implemented explicitly. Therefore this implementation does not fulfill the documented @@ -402,6 +405,9 @@ public interface AnnotatedElement { * @since 1.8 */ default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) { + // Android-changed: Altered method implementation for getAnnotationsByType(Class). + // Android's annotation code is customized because of the use of the DEX format on Android + // and code sharing with the runtime. return AnnotatedElements.getDirectOrIndirectAnnotationsByType(this, annotationClass); } diff --git a/java/lang/reflect/Array.java b/java/lang/reflect/Array.java index 790a930b..95a80915 100644 --- a/java/lang/reflect/Array.java +++ b/java/lang/reflect/Array.java @@ -57,6 +57,7 @@ class Array { * Array.newInstance(componentType, x); * </pre> * </blockquote> + * * <p>The number of dimensions of the new array must not * exceed 255. * @@ -110,6 +111,7 @@ class Array { */ public static Object newInstance(Class<?> componentType, int... dimensions) throws IllegalArgumentException, NegativeArraySizeException { + // Android-changed: New implementation of newInstance(Class, int...) if (dimensions.length <= 0 || dimensions.length > 255) { throw new IllegalArgumentException("Bad number of dimensions: " + dimensions.length); } @@ -130,7 +132,10 @@ class Array { * @exception IllegalArgumentException if the object argument is not * an array */ - public static int getLength(Object array) { + // Android-changed: Non-native implementation of getLength(Object) + // Android-changed: Removal of explicit throws IllegalArgumentException from method signature. + public static int getLength(Object array) + /* throws IllegalArgumentException */ { if (array instanceof Object[]) { return ((Object[]) array).length; } else if (array instanceof boolean[]) { @@ -169,7 +174,9 @@ class Array { * argument is negative, or if it is greater than or equal to the * length of the specified array */ - public static Object get(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of get(Object, int) + public static Object get(Object array, int index) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof Object[]) { return ((Object[]) array)[index]; } @@ -219,7 +226,9 @@ class Array { * length of the specified array * @see Array#get */ - public static boolean getBoolean(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of getBoolean(Object, int) + public static boolean getBoolean(Object array, int index) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof boolean[]) { return ((boolean[]) array)[index]; } @@ -242,7 +251,9 @@ class Array { * length of the specified array * @see Array#get */ - public static byte getByte(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of getByte(Object, int) + public static byte getByte(Object array, int index) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof byte[]) { return ((byte[]) array)[index]; } @@ -265,7 +276,9 @@ class Array { * length of the specified array * @see Array#get */ - public static char getChar(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of getChar(Object, int) + public static char getChar(Object array, int index) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof char[]) { return ((char[]) array)[index]; } @@ -288,7 +301,9 @@ class Array { * length of the specified array * @see Array#get */ - public static short getShort(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of getShort(Object, int) + public static short getShort(Object array, int index) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof short[]) { return ((short[]) array)[index]; } else if (array instanceof byte[]) { @@ -313,7 +328,9 @@ class Array { * length of the specified array * @see Array#get */ - public static int getInt(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of getInt(Object, int) + public static int getInt(Object array, int index) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof int[]) { return ((int[]) array)[index]; } else if (array instanceof byte[]) { @@ -342,7 +359,9 @@ class Array { * length of the specified array * @see Array#get */ - public static long getLong(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of getLong(Object, int) + public static long getLong(Object array, int index) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof long[]) { return ((long[]) array)[index]; } else if (array instanceof byte[]) { @@ -373,7 +392,9 @@ class Array { * length of the specified array * @see Array#get */ - public static float getFloat(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of getFloat(Object, int) + public static float getFloat(Object array, int index) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof float[]) { return ((float[]) array)[index]; } else if (array instanceof byte[]) { @@ -406,7 +427,9 @@ class Array { * length of the specified array * @see Array#get */ - public static double getDouble(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of getDouble(Object, int) + public static double getDouble(Object array, int index) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof double[]) { return ((double[]) array)[index]; } else if (array instanceof byte[]) { @@ -442,7 +465,9 @@ class Array { * argument is negative, or if it is greater than or equal to * the length of the specified array */ - public static void set(Object array, int index, Object value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of set(Object, int, Object) + public static void set(Object array, int index, Object value) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (!array.getClass().isArray()) { throw notAnArray(array); } @@ -481,7 +506,7 @@ class Array { * object to the specified {@code boolean} value. * @param array the array * @param index the index into the array - * @param value the new value of the indexed component + * @param z the new value of the indexed component * @exception NullPointerException If the specified object argument * is null * @exception IllegalArgumentException If the specified object argument @@ -493,10 +518,12 @@ class Array { * the length of the specified array * @see Array#set */ - // Android-changed param name s/z/value - public static void setBoolean(Object array, int index, boolean value) { + // Android-changed: Non-native implementation of setBoolean(Object, int, boolean) + // Android-changed: Removal of explicit runtime exceptions throws clause + public static void setBoolean(Object array, int index, boolean z) + /* throws IllegalArgumentException, ArrayIndexOutOfBoundsException */ { if (array instanceof boolean[]) { - ((boolean[]) array)[index] = value; + ((boolean[]) array)[index] = z; } else { throw badArray(array); } @@ -507,7 +534,7 @@ class Array { * object to the specified {@code byte} value. * @param array the array * @param index the index into the array - * @param value the new value of the indexed component + * @param b the new value of the indexed component * @exception NullPointerException If the specified object argument * is null * @exception IllegalArgumentException If the specified object argument @@ -519,20 +546,21 @@ class Array { * the length of the specified array * @see Array#set */ - // Android-changed param name s/b/value - public static void setByte(Object array, int index, byte value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of setByte(Object, int, byte) + public static void setByte(Object array, int index, byte b) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof byte[]) { - ((byte[]) array)[index] = value; + ((byte[]) array)[index] = b; } else if (array instanceof double[]) { - ((double[]) array)[index] = value; + ((double[]) array)[index] = b; } else if (array instanceof float[]) { - ((float[]) array)[index] = value; + ((float[]) array)[index] = b; } else if (array instanceof int[]) { - ((int[]) array)[index] = value; + ((int[]) array)[index] = b; } else if (array instanceof long[]) { - ((long[]) array)[index] = value; + ((long[]) array)[index] = b; } else if (array instanceof short[]) { - ((short[]) array)[index] = value; + ((short[]) array)[index] = b; } else { throw badArray(array); } @@ -543,7 +571,7 @@ class Array { * object to the specified {@code char} value. * @param array the array * @param index the index into the array - * @param value the new value of the indexed component + * @param c the new value of the indexed component * @exception NullPointerException If the specified object argument * is null * @exception IllegalArgumentException If the specified object argument @@ -555,18 +583,19 @@ class Array { * the length of the specified array * @see Array#set */ - // Android-changed param name s/c/value - public static void setChar(Object array, int index, char value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of setChar(Object, int, char) + public static void setChar(Object array, int index, char c) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof char[]) { - ((char[]) array)[index] = value; + ((char[]) array)[index] = c; } else if (array instanceof double[]) { - ((double[]) array)[index] = value; + ((double[]) array)[index] = c; } else if (array instanceof float[]) { - ((float[]) array)[index] = value; + ((float[]) array)[index] = c; } else if (array instanceof int[]) { - ((int[]) array)[index] = value; + ((int[]) array)[index] = c; } else if (array instanceof long[]) { - ((long[]) array)[index] = value; + ((long[]) array)[index] = c; } else { throw badArray(array); } @@ -577,7 +606,7 @@ class Array { * object to the specified {@code short} value. * @param array the array * @param index the index into the array - * @param value the new value of the indexed component + * @param s the new value of the indexed component * @exception NullPointerException If the specified object argument * is null * @exception IllegalArgumentException If the specified object argument @@ -589,18 +618,19 @@ class Array { * the length of the specified array * @see Array#set */ - // Android-changed param name s/s/value - public static void setShort(Object array, int index, short value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of setShort(Object, int, short) + public static void setShort(Object array, int index, short s) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof short[]) { - ((short[]) array)[index] = value; + ((short[]) array)[index] = s; } else if (array instanceof double[]) { - ((double[]) array)[index] = value; + ((double[]) array)[index] = s; } else if (array instanceof float[]) { - ((float[]) array)[index] = value; + ((float[]) array)[index] = s; } else if (array instanceof int[]) { - ((int[]) array)[index] = value; + ((int[]) array)[index] = s; } else if (array instanceof long[]) { - ((long[]) array)[index] = value; + ((long[]) array)[index] = s; } else { throw badArray(array); } @@ -611,7 +641,7 @@ class Array { * object to the specified {@code int} value. * @param array the array * @param index the index into the array - * @param value the new value of the indexed component + * @param i the new value of the indexed component * @exception NullPointerException If the specified object argument * is null * @exception IllegalArgumentException If the specified object argument @@ -623,16 +653,17 @@ class Array { * the length of the specified array * @see Array#set */ - // Android-changed param name s/i/value - public static void setInt(Object array, int index, int value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of setInt(Object, int, int) + public static void setInt(Object array, int index, int i) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof int[]) { - ((int[]) array)[index] = value; + ((int[]) array)[index] = i; } else if (array instanceof double[]) { - ((double[]) array)[index] = value; + ((double[]) array)[index] = i; } else if (array instanceof float[]) { - ((float[]) array)[index] = value; + ((float[]) array)[index] = i; } else if (array instanceof long[]) { - ((long[]) array)[index] = value; + ((long[]) array)[index] = i; } else { throw badArray(array); } @@ -643,7 +674,7 @@ class Array { * object to the specified {@code long} value. * @param array the array * @param index the index into the array - * @param value the new value of the indexed component + * @param l the new value of the indexed component * @exception NullPointerException If the specified object argument * is null * @exception IllegalArgumentException If the specified object argument @@ -655,14 +686,15 @@ class Array { * the length of the specified array * @see Array#set */ - // Android-changed param name s/l/value - public static void setLong(Object array, int index, long value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of setBoolean(Object, int, long) + public static void setLong(Object array, int index, long l) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof long[]) { - ((long[]) array)[index] = value; + ((long[]) array)[index] = l; } else if (array instanceof double[]) { - ((double[]) array)[index] = value; + ((double[]) array)[index] = l; } else if (array instanceof float[]) { - ((float[]) array)[index] = value; + ((float[]) array)[index] = l; } else { throw badArray(array); } @@ -673,7 +705,7 @@ class Array { * object to the specified {@code float} value. * @param array the array * @param index the index into the array - * @param value the new value of the indexed component + * @param f the new value of the indexed component * @exception NullPointerException If the specified object argument * is null * @exception IllegalArgumentException If the specified object argument @@ -685,12 +717,12 @@ class Array { * the length of the specified array * @see Array#set */ - // Android-changed param name s/f/value - public static void setFloat(Object array, int index, float value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + public static void setFloat(Object array, int index, float f) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof float[]) { - ((float[]) array)[index] = value; + ((float[]) array)[index] = f; } else if (array instanceof double[]) { - ((double[]) array)[index] = value; + ((double[]) array)[index] = f; } else { throw badArray(array); } @@ -701,7 +733,7 @@ class Array { * object to the specified {@code double} value. * @param array the array * @param index the index into the array - * @param value the new value of the indexed component + * @param d the new value of the indexed component * @exception NullPointerException If the specified object argument * is null * @exception IllegalArgumentException If the specified object argument @@ -713,21 +745,21 @@ class Array { * the length of the specified array * @see Array#set */ - // Android-changed param name s/d/value - public static void setDouble(Object array, int index, double value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + // Android-changed: Non-native implementation of setDouble(Object, int, double) + public static void setDouble(Object array, int index, double d) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { if (array instanceof double[]) { - ((double[]) array)[index] = value; + ((double[]) array)[index] = d; } else { throw badArray(array); } } /* - * Create a multi-dimensional array of objects with the specified type. + * Private */ - @FastNative - private static native Object createMultiArray(Class<?> componentType, int[] dimensions) throws NegativeArraySizeException; + // Android-added: Added javadocs for newArray(Class, int) /** * Returns a new array of the specified component type and length. * Equivalent to {@code new componentType[size]}. @@ -737,36 +769,55 @@ class Array { * @throws NegativeArraySizeException * if {@code size < 0} */ - private static Object newArray(Class<?> componentType, int size) throws NegativeArraySizeException { + // Android-changed: Non-native implementation of newArray(Class, int) + private static Object newArray(Class<?> componentType, int length) + throws NegativeArraySizeException { if (!componentType.isPrimitive()) { - return createObjectArray(componentType, size); + return createObjectArray(componentType, length); } else if (componentType == char.class) { - return new char[size]; + return new char[length]; } else if (componentType == int.class) { - return new int[size]; + return new int[length]; } else if (componentType == byte.class) { - return new byte[size]; + return new byte[length]; } else if (componentType == boolean.class) { - return new boolean[size]; + return new boolean[length]; } else if (componentType == short.class) { - return new short[size]; + return new short[length]; } else if (componentType == long.class) { - return new long[size]; + return new long[length]; } else if (componentType == float.class) { - return new float[size]; + return new float[length]; } else if (componentType == double.class) { - return new double[size]; + return new double[length]; } else if (componentType == void.class) { throw new IllegalArgumentException("Can't allocate an array of void"); } throw new AssertionError(); } + // Android-removed: multiNewArray(Class, int[]) method. createMultiArray used instead. + /* + private static native Object multiNewArray(Class<?> componentType, + int[] dimensions) + throws IllegalArgumentException, NegativeArraySizeException; + */ + + // Android-added: createMultiArray(Class, int[]) method. Used instead of multiNewArray + /* + * Create a multi-dimensional array of objects with the specified type. + */ + @FastNative + private static native Object createMultiArray(Class<?> componentType, int[] dimensions) + throws NegativeArraySizeException; + + // BEGIN Android-added: Helper methods to support custom method implementations. /* * Create a one-dimensional array of objects with the specified type. */ @FastNative - private static native Object createObjectArray(Class<?> componentType, int length) throws NegativeArraySizeException; + private static native Object createObjectArray(Class<?> componentType, int length) + throws NegativeArraySizeException; private static IllegalArgumentException notAnArray(Object o) { throw new IllegalArgumentException("Not an array: " + o.getClass()); @@ -785,4 +836,5 @@ class Array { throw incompatibleType(array); } } + // END Android-added: Helper methods to support custom method implementations. } diff --git a/java/text/DateFormatSymbols.java b/java/text/DateFormatSymbols.java index 305f6f2a..946ab0d2 100644 --- a/java/text/DateFormatSymbols.java +++ b/java/text/DateFormatSymbols.java @@ -229,8 +229,9 @@ public class DateFormatSymbols implements Serializable, Cloneable { * Unlocalized date-time pattern characters. For example: 'y', 'd', etc. * All locales use the same these unlocalized pattern characters. */ - // Android-changed: Add 'c' (standalone day of week). - static final String patternChars = "GyMdkHmsSEDFwWahKzZYuXLc"; + // Android-changed: Add 'c' (standalone day of week), 'b' (day period), + // 'B' (flexible day period) + static final String patternChars = "GyMdkHmsSEDFwWahKzZYuXLcbB"; static final int PATTERN_ERA = 0; // G static final int PATTERN_YEAR = 1; // y @@ -257,6 +258,9 @@ public class DateFormatSymbols implements Serializable, Cloneable { static final int PATTERN_MONTH_STANDALONE = 22; // L // Android-added: Constant for standalone day of week. static final int PATTERN_STANDALONE_DAY_OF_WEEK = 23; // c + // Android-added: Constant for pattern letter 'b', 'B' + static final int PATTERN_DAY_PERIOD = 24; // b + static final int PATTERN_FLEXIBLE_DAY_PERIOD = 25; // B /** * Localized date-time pattern characters. For example, a locale may diff --git a/java/text/SimpleDateFormat.java b/java/text/SimpleDateFormat.java index c294e5a2..dfbd1218 100644 --- a/java/text/SimpleDateFormat.java +++ b/java/text/SimpleDateFormat.java @@ -1077,7 +1077,11 @@ public class SimpleDateFormat extends DateFormat { Calendar.ZONE_OFFSET, Calendar.MONTH, // Android-added: 'c' for standalone day of week. - Calendar.DAY_OF_WEEK + Calendar.DAY_OF_WEEK, + // Android-added: Support for 'b'/'B' (day period). Calendar.AM_PM is just used as a + // placeholder in the absence of full support for day period. + Calendar.AM_PM, + Calendar.AM_PM }; // Map index into pattern character string to DateFormat field number @@ -1106,7 +1110,11 @@ public class SimpleDateFormat extends DateFormat { DateFormat.TIMEZONE_FIELD, DateFormat.MONTH_FIELD, // Android-added: 'c' for standalone day of week. - DateFormat.DAY_OF_WEEK_FIELD + DateFormat.DAY_OF_WEEK_FIELD, + // Android-added: Support for 'b'/'B' (day period). DateFormat.AM_PM_FIELD is just used as a + // placeholder in the absence of full support for day period. + DateFormat.AM_PM_FIELD, + DateFormat.AM_PM_FIELD }; // Maps from DecimalFormatSymbols index to Field constant @@ -1135,7 +1143,11 @@ public class SimpleDateFormat extends DateFormat { Field.TIME_ZONE, Field.MONTH, // Android-added: 'c' for standalone day of week. - Field.DAY_OF_WEEK + Field.DAY_OF_WEEK, + // Android-added: Support for 'b'/'B' (day period). Field.AM_PM is just used as a + // placeholder in the absence of full support for day period. + Field.AM_PM, + Field.AM_PM }; // BEGIN Android-added: Special handling for UTC time zone. @@ -1264,6 +1276,13 @@ public class SimpleDateFormat extends DateFormat { } break; + // Android-added: Ignore 'b' and 'B' introduced in CLDR 32+ pattern data. http://b/68139386 + // Not currently supported here. + case PATTERN_DAY_PERIOD: + case PATTERN_FLEXIBLE_DAY_PERIOD: + current = ""; + break; + case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM if (current == null) { if (value == 0) { diff --git a/javax/obex/ServerOperation.java b/javax/obex/ServerOperation.java index 56a675ac..15ea3678 100644 --- a/javax/obex/ServerOperation.java +++ b/javax/obex/ServerOperation.java @@ -195,7 +195,12 @@ public final class ServerOperation implements Operation, BaseStream { if(!handleObexPacket(packet)) { return; } - if (!mHasBody) { + /* Don't Pre-Send continue when Remote requested for SRM + * Let the Application confirm. + */ + if (V) Log.v(TAG, "Get App confirmation if SRM ENABLED case: " + mSrmEnabled + + " not hasBody case: " + mHasBody); + if (!mHasBody && !mSrmEnabled) { while ((!mGetOperation) && (!finalBitSet)) { sendReply(ResponseCodes.OBEX_HTTP_CONTINUE); if (mPrivateInput.available() > 0) { @@ -204,8 +209,13 @@ public final class ServerOperation implements Operation, BaseStream { } } } - - while ((!mGetOperation) && (!finalBitSet) && (mPrivateInput.available() == 0)) { + /* Don't Pre-Send continue when Remote requested for SRM + * Let the Application confirm. + */ + if (V) Log.v(TAG, "Get App confirmation if SRM ENABLED case: " + mSrmEnabled + + " not finalPacket: " + finalBitSet + " not GETOp Case: " + mGetOperation); + while ((!mSrmEnabled) && (!mGetOperation) && (!finalBitSet) + && (mPrivateInput.available() == 0)) { sendReply(ResponseCodes.OBEX_HTTP_CONTINUE); if (mPrivateInput.available() > 0) { break; |