diff options
Diffstat (limited to 'services')
83 files changed, 3284 insertions, 1832 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index a43a2a6..ebe21ff 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -746,14 +746,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } /** - * Gets the bounds of the accessibility focus in the active window. + * Gets a point within the accessibility focused node where we can send down + * and up events to perform a click. * - * @param outBounds The output to which to write the focus bounds. - * @return Whether accessibility focus was found and the bounds are populated. + * @param outPoint The click point to populate. + * @return Whether accessibility a click point was found and set. */ // TODO: (multi-display) Make sure this works for multiple displays. - boolean getAccessibilityFocusBounds(Rect outBounds) { - return getInteractionBridgeLocked().getAccessibilityFocusBoundsNotLocked(outBounds); + boolean getAccessibilityFocusClickPointInScreen(Point outPoint) { + return getInteractionBridgeLocked() + .getAccessibilityFocusClickPointInScreenNotLocked(outPoint); } /** @@ -2196,8 +2198,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId); try { connection.findAccessibilityNodeInfosByViewId(accessibilityNodeId, viewIdResName, - partialInteractiveRegion, interactionId, callback, mFetchFlags, interrogatingPid, - interrogatingTid, spec); + partialInteractiveRegion, interactionId, callback, mFetchFlags, + interrogatingPid, interrogatingTid, spec); return true; } catch (RemoteException re) { if (DEBUG) { @@ -2248,8 +2250,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId); try { connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text, - partialInteractiveRegion, interactionId, callback, mFetchFlags, interrogatingPid, - interrogatingTid, spec); + partialInteractiveRegion, interactionId, callback, mFetchFlags, + interrogatingPid, interrogatingTid, spec); return true; } catch (RemoteException re) { if (DEBUG) { @@ -2352,8 +2354,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final long identityToken = Binder.clearCallingIdentity(); MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId); try { - connection.findFocus(accessibilityNodeId, focusType, partialInteractiveRegion, interactionId, - callback, mFetchFlags, interrogatingPid, interrogatingTid, spec); + connection.findFocus(accessibilityNodeId, focusType, partialInteractiveRegion, + interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, + spec); return true; } catch (RemoteException re) { if (DEBUG) { @@ -2403,8 +2406,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final long identityToken = Binder.clearCallingIdentity(); MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId); try { - connection.focusSearch(accessibilityNodeId, direction, partialInteractiveRegion, interactionId, - callback, mFetchFlags, interrogatingPid, interrogatingTid, spec); + connection.focusSearch(accessibilityNodeId, direction, partialInteractiveRegion, + interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, + spec); return true; } catch (RemoteException re) { if (DEBUG) { @@ -2460,6 +2464,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return true; } + @Override public boolean performGlobalAction(int action) { synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles @@ -2501,6 +2506,57 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } @Override + public boolean computeClickPointInScreen(int accessibilityWindowId, + long accessibilityNodeId, int interactionId, + IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) + throws RemoteException { + final int resolvedWindowId; + IAccessibilityInteractionConnection connection = null; + Region partialInteractiveRegion = mTempRegion; + synchronized (mLock) { + // We treat calls from a profile as if made by its parent as profiles + // share the accessibility state of the parent. The call below + // performs the current profile parent resolution. + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.getCallingUserId()); + if (resolvedUserId != mCurrentUserId) { + return false; + } + resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); + final boolean permissionGranted = + mSecurityPolicy.canRetrieveWindowContentLocked(this); + if (!permissionGranted) { + return false; + } else { + connection = getConnectionLocked(resolvedWindowId); + if (connection == null) { + return false; + } + } + if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( + resolvedWindowId, partialInteractiveRegion)) { + partialInteractiveRegion = null; + } + } + final int interrogatingPid = Binder.getCallingPid(); + final long identityToken = Binder.clearCallingIdentity(); + MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId); + try { + connection.computeClickPointInScreen(accessibilityNodeId, partialInteractiveRegion, + interactionId, callback, interrogatingPid, interrogatingTid, spec); + return true; + } catch (RemoteException re) { + if (DEBUG) { + Slog.e(LOG_TAG, "Error computeClickPointInScreen()."); + } + } finally { + Binder.restoreCallingIdentity(identityToken); + } + return false; + } + + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP); synchronized (mLock) { @@ -3119,30 +3175,43 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } - public boolean getAccessibilityFocusBoundsNotLocked(Rect outBounds) { + public boolean getAccessibilityFocusClickPointInScreenNotLocked(Point outPoint) { AccessibilityNodeInfo focus = getAccessibilityFocusNotLocked(); if (focus == null) { return false; } synchronized (mLock) { - focus.getBoundsInScreen(outBounds); + Point point = mClient.computeClickPointInScreen(mConnectionId, + focus.getWindowId(), focus.getSourceNodeId()); + + if (point == null) { + return false; + } MagnificationSpec spec = getCompatibleMagnificationSpecLocked(focus.getWindowId()); if (spec != null && !spec.isNop()) { - outBounds.offset((int) -spec.offsetX, (int) -spec.offsetY); - outBounds.scale(1 / spec.scale); + point.offset((int) -spec.offsetX, (int) -spec.offsetY); + point.x = (int) (point.x * (1 / spec.scale)); + point.y = (int) (point.y * (1 / spec.scale)); } - // Clip to the window rectangle. + // Make sure the point is within the window. Rect windowBounds = mTempRect; getActiveWindowBounds(windowBounds); - outBounds.intersect(windowBounds); + if (!windowBounds.contains(point.x, point.y)) { + return false; + } - // Clip to the screen rectangle. - mDefaultDisplay.getRealSize(mTempPoint); - outBounds.intersect(0, 0, mTempPoint.x, mTempPoint.y); + // Make sure the point is within the screen. + Point screenSize = mTempPoint; + mDefaultDisplay.getRealSize(screenSize); + if (point.x < 0 || point.x > screenSize.x + || point.y < 0 || point.y > screenSize.y) { + return false; + } + outPoint.set(point.x, point.y); return true; } } diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java index af5c13d..9e63433 100644 --- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java @@ -24,6 +24,7 @@ import android.gesture.GesturePoint; import android.gesture.GestureStore; import android.gesture.GestureStroke; import android.gesture.Prediction; +import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.os.SystemClock; @@ -171,6 +172,9 @@ class TouchExplorer implements EventStreamTransformation { // Temporary rectangle to avoid instantiation. private final Rect mTempRect = new Rect(); + // Temporary point to avoid instantiation. + private final Point mTempPoint = new Point(); + // Context in which this explorer operates. private final Context mContext; @@ -708,7 +712,6 @@ class TouchExplorer implements EventStreamTransformation { // Send an event to the end of the drag gesture. sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); } - mCurrentState = STATE_TOUCH_EXPLORING; } break; case MotionEvent.ACTION_UP: { mAms.onTouchInteractionEnd(); @@ -1158,18 +1161,18 @@ class TouchExplorer implements EventStreamTransformation { mInjectedPointerTracker.getLastInjectedHoverEventForClick(); if (lastExploreEvent == null) { // No last touch explored event but there is accessibility focus in - // the active window. We click in the middle of the focus bounds. - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBounds(focusBounds)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); + // the active window. We click in the focus bounds. + Point point = mTempPoint; + if (mAms.getAccessibilityFocusClickPointInScreen(point)) { + clickLocationX = point.x; + clickLocationY = point.y; } else { // Out of luck - do nothing. return; } } else { // If the click is within the active window but not within the - // accessibility focus bounds we click in the focus center. + // accessibility focus bounds we click in the focus bounds. final int lastExplorePointerIndex = lastExploreEvent.getActionIndex(); clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex); clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex); @@ -1177,12 +1180,10 @@ class TouchExplorer implements EventStreamTransformation { if (mLastTouchedWindowId == mAms.getActiveWindowId()) { mAms.getActiveWindowBounds(activeWindowBounds); if (activeWindowBounds.contains(clickLocationX, clickLocationY)) { - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBounds(focusBounds)) { - if (!focusBounds.contains(clickLocationX, clickLocationY)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); - } + Point point = mTempPoint; + if (mAms.getAccessibilityFocusClickPointInScreen(point)) { + clickLocationX = point.x; + clickLocationY = point.y; } } } @@ -1331,18 +1332,18 @@ class TouchExplorer implements EventStreamTransformation { mInjectedPointerTracker.getLastInjectedHoverEventForClick(); if (lastExploreEvent == null) { // No last touch explored event but there is accessibility focus in - // the active window. We click in the middle of the focus bounds. - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBounds(focusBounds)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); + // the active window. We click in the focus bounds. + Point point = mTempPoint; + if (mAms.getAccessibilityFocusClickPointInScreen(point)) { + clickLocationX = point.x; + clickLocationY = point.y; } else { // Out of luck - do nothing. return; } } else { // If the click is within the active window but not within the - // accessibility focus bounds we click in the focus center. + // accessibility focus bounds we click in the focus bounds. final int lastExplorePointerIndex = lastExploreEvent.getActionIndex(); clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex); clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex); @@ -1350,12 +1351,10 @@ class TouchExplorer implements EventStreamTransformation { if (mLastTouchedWindowId == mAms.getActiveWindowId()) { mAms.getActiveWindowBounds(activeWindowBounds); if (activeWindowBounds.contains(clickLocationX, clickLocationY)) { - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBounds(focusBounds)) { - if (!focusBounds.contains(clickLocationX, clickLocationY)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); - } + Point point = mTempPoint; + if (mAms.getAccessibilityFocusClickPointInScreen(point)) { + clickLocationX = point.x; + clickLocationY = point.y; } } } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 7f7e5c3..d05de69 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -440,12 +440,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Permission Denial: can't dump from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - } + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, + "Permission Denial: can't dump from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); synchronized (mLock) { int N = mProviders.size(); @@ -1863,7 +1861,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void deleteProviderLocked(Provider provider) { int N = provider.widgets.size(); - for (int i = 0; i < N; i++) { + for (int i = N - 1; i >= 0; i--) { Widget widget = provider.widgets.remove(i); // Call back with empty RemoteViews updateAppWidgetInstanceLocked(widget, null, false); @@ -4038,4 +4036,4 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } } -}
\ No newline at end of file +} diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 3bab1bf..75090db 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -425,8 +425,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { TelephonyManager mTelephonyManager; - // sequence number for Networks - private final static int MIN_NET_ID = 10; // some reserved marks + // sequence number for Networks; keep in sync with system/netd/NetworkController.cpp + private final static int MIN_NET_ID = 100; // some reserved marks private final static int MAX_NET_ID = 65535; private int mNextNetId = MIN_NET_ID; @@ -793,14 +793,28 @@ public class ConnectivityService extends IConnectivityManager.Stub { } /** - * Check if UID should be blocked from using the network represented by the - * given {@link NetworkStateTracker}. + * Check if UID should be blocked from using the network represented by the given networkType. + * @deprecated Uses mLegacyTypeTracker; cannot deal with multiple Networks of the same type. */ private boolean isNetworkBlocked(int networkType, int uid) { + return isNetworkWithLinkPropertiesBlocked(getLinkPropertiesForType(networkType), uid); + } + + /** + * Check if UID should be blocked from using the network represented by the given + * NetworkAgentInfo. + */ + private boolean isNetworkBlocked(NetworkAgentInfo nai, int uid) { + return isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid); + } + + /** + * Check if UID should be blocked from using the network with the given LinkProperties. + */ + private boolean isNetworkWithLinkPropertiesBlocked(LinkProperties lp, int uid) { final boolean networkCostly; final int uidRules; - LinkProperties lp = getLinkPropertiesForType(networkType); final String iface = (lp == null ? "" : lp.getInterfaceName()); synchronized (mRulesLock) { networkCostly = mMeteredIfaces.contains(iface); @@ -819,17 +833,36 @@ public class ConnectivityService extends IConnectivityManager.Stub { * Return a filtered {@link NetworkInfo}, potentially marked * {@link DetailedState#BLOCKED} based on * {@link #isNetworkBlocked}. + * @deprecated Uses mLegacyTypeTracker; cannot deal with multiple Networks of the same type. */ private NetworkInfo getFilteredNetworkInfo(int networkType, int uid) { NetworkInfo info = getNetworkInfoForType(networkType); return getFilteredNetworkInfo(info, networkType, uid); } + /* + * @deprecated Uses mLegacyTypeTracker; cannot deal with multiple Networks of the same type. + */ private NetworkInfo getFilteredNetworkInfo(NetworkInfo info, int networkType, int uid) { if (isNetworkBlocked(networkType, uid)) { // network is blocked; clone and override state info = new NetworkInfo(info); info.setDetailedState(DetailedState.BLOCKED, null, null); + if (VDBG) log("returning Blocked NetworkInfo"); + } + if (mLockdownTracker != null) { + info = mLockdownTracker.augmentNetworkInfo(info); + if (VDBG) log("returning Locked NetworkInfo"); + } + return info; + } + + private NetworkInfo getFilteredNetworkInfo(NetworkAgentInfo nai, int uid) { + NetworkInfo info = nai.networkInfo; + if (isNetworkBlocked(nai, uid)) { + // network is blocked; clone and override state + info = new NetworkInfo(info); + info.setDetailedState(DetailedState.BLOCKED, null, null); if (DBG) log("returning Blocked NetworkInfo"); } if (mLockdownTracker != null) { @@ -946,7 +979,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { synchronized (nai) { if (nai.networkInfo == null) return null; - return getFilteredNetworkInfo(nai.networkInfo, nai.networkInfo.getType(), uid); + return getFilteredNetworkInfo(nai, uid); } } @@ -1315,6 +1348,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { // } } + private void enforceInternetPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.INTERNET, + "ConnectivityService"); + } + private void enforceAccessPermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, @@ -2468,7 +2507,22 @@ public class ConnectivityService extends IConnectivityManager.Stub { } public void reportBadNetwork(Network network) { - //TODO + enforceAccessPermission(); + enforceInternetPermission(); + + if (network == null) return; + + final int uid = Binder.getCallingUid(); + NetworkAgentInfo nai = null; + synchronized (mNetworkForNetId) { + nai = mNetworkForNetId.get(network.netId); + } + if (nai == null) return; + synchronized (nai) { + if (isNetworkBlocked(nai, uid)) return; + + nai.networkMonitor.sendMessage(NetworkMonitor.CMD_FORCE_REEVALUATION, uid); + } } public ProxyInfo getProxy() { @@ -4436,10 +4490,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("Unknown NetworkAgentInfo in handleLingerComplete"); return; } - if (DBG) log("handleLingerComplete for " + oldNetwork.name()); if (DBG) { - if (oldNetwork.networkRequests.size() != 0) { - loge("Dead network still had " + oldNetwork.networkRequests.size() + " requests"); + log("handleLingerComplete for " + oldNetwork.name()); + for (int i = 0; i < oldNetwork.networkRequests.size(); i++) { + NetworkRequest nr = oldNetwork.networkRequests.valueAt(i); + // Ignore listening requests. + if (mNetworkRequests.get(nr).isRequest == false) continue; + loge("Dead network still had at least " + nr); + break; } } oldNetwork.asyncChannel.disconnect(); @@ -4463,6 +4521,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("Unknown NetworkAgentInfo in handleConnectionValidated"); return; } + if (newNetwork.validated) return; + newNetwork.validated = true; boolean keep = newNetwork.isVPN(); boolean isNewDefault = false; if (DBG) log("handleConnectionValidated for "+newNetwork.name()); @@ -4686,6 +4746,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void updateNetworkScore(NetworkAgentInfo nai, int score) { if (DBG) log("updateNetworkScore for " + nai.name() + " to " + score); + if (score < 0) { + loge("updateNetworkScore for " + nai.name() + " got a negative score (" + score + + "). Bumping score to min of 0"); + score = 0; + } nai.currentScore = score; @@ -4706,6 +4771,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { // code will fire. for (int i = 0; i < nai.networkRequests.size(); i++) { NetworkRequest nr = nai.networkRequests.valueAt(i); + // Don't send listening requests to factories. b/17393458 + if (mNetworkRequests.get(nr).isRequest == false) continue; sendUpdatedScoreToFactories(nr, score); } } diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index c8718e3..060c8e3 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -106,6 +106,7 @@ import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; +import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; import android.widget.ArrayAdapter; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; @@ -3455,9 +3456,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE); final boolean isAuxiliary = "1".equals(String.valueOf( parser.getAttributeValue(null, ATTR_IS_AUXILIARY))); - final InputMethodSubtype subtype = - new InputMethodSubtype(label, icon, imeSubtypeLocale, - imeSubtypeMode, imeSubtypeExtraValue, isAuxiliary); + final InputMethodSubtype subtype = new InputMethodSubtypeBuilder() + .setSubtypeNameResId(label) + .setSubtypeIconResId(icon) + .setSubtypeLocale(imeSubtypeLocale) + .setSubtypeMode(imeSubtypeMode) + .setSubtypeExtraValue(imeSubtypeExtraValue) + .setIsAuxiliary(isAuxiliary) + .build(); tempSubtypesArray.add(subtype); } } diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java index 65ad1ce..b452a38 100644 --- a/services/core/java/com/android/server/MmsServiceBroker.java +++ b/services/core/java/com/android/server/MmsServiceBroker.java @@ -193,7 +193,7 @@ public class MmsServiceBroker extends SystemService { private final class BinderService extends IMms.Stub { @Override public void sendMessage(long subId, String callingPkg, Uri contentUri, - String locationUrl, ContentValues configOverrides, PendingIntent sentIntent) + String locationUrl, Bundle configOverrides, PendingIntent sentIntent) throws RemoteException { mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, "Send MMS message"); if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), @@ -206,7 +206,7 @@ public class MmsServiceBroker extends SystemService { @Override public void downloadMessage(long subId, String callingPkg, String locationUrl, - Uri contentUri, ContentValues configOverrides, + Uri contentUri, Bundle configOverrides, PendingIntent downloadedIntent) throws RemoteException { mContext.enforceCallingPermission(Manifest.permission.RECEIVE_MMS, "Download MMS message"); @@ -333,7 +333,7 @@ public class MmsServiceBroker extends SystemService { @Override public void sendStoredMessage(long subId, String callingPkg, Uri messageUri, - ContentValues configOverrides, PendingIntent sentIntent) throws RemoteException { + Bundle configOverrides, PendingIntent sentIntent) throws RemoteException { mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, "Send stored MMS message"); if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), diff --git a/services/core/java/com/android/server/NativeDaemonEvent.java b/services/core/java/com/android/server/NativeDaemonEvent.java index 2095152..59d50bd 100644 --- a/services/core/java/com/android/server/NativeDaemonEvent.java +++ b/services/core/java/com/android/server/NativeDaemonEvent.java @@ -201,20 +201,16 @@ public class NativeDaemonEvent { } while (current < length) { // find the end of the word - if (quoted) { - wordEnd = current; - while ((wordEnd = rawEvent.indexOf('\"', wordEnd)) != -1) { - if (rawEvent.charAt(wordEnd - 1) != '\\') { - break; - } else { - wordEnd++; // skip this escaped quote and keep looking - } + char terminator = quoted ? '\"' : ' '; + wordEnd = current; + while (wordEnd < length && rawEvent.charAt(wordEnd) != terminator) { + if (rawEvent.charAt(wordEnd) == '\\') { + // skip the escaped char + ++wordEnd; } - } else { - wordEnd = rawEvent.indexOf(' ', current); + ++wordEnd; } - // if we didn't find the end-o-word token, take the rest of the string - if (wordEnd == -1) wordEnd = length; + if (wordEnd > length) wordEnd = length; String word = rawEvent.substring(current, wordEnd); current += word.length(); if (!quoted) { diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java index cf7e65c..cb1748d 100644 --- a/services/core/java/com/android/server/NsdService.java +++ b/services/core/java/com/android/server/NsdService.java @@ -397,8 +397,7 @@ public class NsdService extends INsdManager.Stub { break; case NsdManager.NATIVE_DAEMON_EVENT: NativeEvent event = (NativeEvent) msg.obj; - if (!handleNativeEvent(event.code, event.raw, - NativeDaemonEvent.unescapeArgs(event.raw))) { + if (!handleNativeEvent(event.code, event.raw, event.cooked)) { result = NOT_HANDLED; } break; @@ -474,8 +473,14 @@ public class NsdService extends INsdManager.Stub { case NativeResponseCode.SERVICE_RESOLVED: /* NNN resolveId fullName hostName port txtlen txtdata */ if (DBG) Slog.d(TAG, "SERVICE_RESOLVED Raw: " + raw); - int index = cooked[2].indexOf("."); - if (index == -1) { + int index = 0; + while (index < cooked[2].length() && cooked[2].charAt(index) != '.') { + if (cooked[2].charAt(index) == '\\') { + ++index; + } + ++index; + } + if (index >= cooked[2].length()) { Slog.e(TAG, "Invalid service found " + raw); break; } @@ -483,6 +488,8 @@ public class NsdService extends INsdManager.Stub { String rest = cooked[2].substring(index); String type = rest.replace(".local.", ""); + name = unescape(name); + clientInfo.mResolvedService.setServiceName(name); clientInfo.mResolvedService.setServiceType(type); clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4])); @@ -541,6 +548,30 @@ public class NsdService extends INsdManager.Stub { } } + private String unescape(String s) { + StringBuilder sb = new StringBuilder(s.length()); + for (int i = 0; i < s.length(); ++i) { + char c = s.charAt(i); + if (c == '\\') { + if (++i >= s.length()) { + Slog.e(TAG, "Unexpected end of escape sequence in: " + s); + break; + } + c = s.charAt(i); + if (c != '.' && c != '\\') { + if (i + 2 >= s.length()) { + Slog.e(TAG, "Unexpected end of escape sequence in: " + s); + break; + } + c = (char) ((c-'0') * 100 + (s.charAt(i+1)-'0') * 10 + (s.charAt(i+2)-'0')); + i += 2; + } + } + sb.append(c); + } + return sb.toString(); + } + private NativeDaemonConnector mNativeConnector; private final CountDownLatch mNativeDaemonConnected = new CountDownLatch(1); @@ -625,10 +656,12 @@ public class NsdService extends INsdManager.Stub { private class NativeEvent { final int code; final String raw; + final String[] cooked; - NativeEvent(int code, String raw) { + NativeEvent(int code, String raw, String[] cooked) { this.code = code; this.raw = raw; + this.cooked = cooked; } } @@ -644,7 +677,7 @@ public class NsdService extends INsdManager.Stub { public boolean onEvent(int code, String raw, String[] cooked) { // TODO: NDC translates a message to a callback, we could enhance NDC to // directly interact with a state machine through messages - NativeEvent event = new NativeEvent(code, raw); + NativeEvent event = new NativeEvent(code, raw, cooked); mNsdStateMachine.sendMessage(NsdManager.NATIVE_DAEMON_EVENT, event); return true; } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 7624314..37c23bb 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -196,7 +196,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (VDBG) log("MSG_USER_SWITCHED userId=" + msg.arg1); int numPhones = TelephonyManager.getDefault().getPhoneCount(); for (int sub = 0; sub < numPhones; sub++) { - TelephonyRegistry.this.notifyCellLocationUsingSubId(sub, + TelephonyRegistry.this.notifyCellLocationForSubscriber(sub, mCellLocation[sub]); } break; @@ -326,7 +326,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } @Override - public void listenUsingSubId(long subId, String pkgForDebug, IPhoneStateListener callback, + public void listenForSubscriber(long subId, String pkgForDebug, IPhoneStateListener callback, int events, boolean notifyNow) { listen(pkgForDebug, callback, events, notifyNow, subId, false); } @@ -542,12 +542,12 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { broadcastCallStateChanged(state, incomingNumber, mDefaultSubId); } - public void notifyCallStateUsingSubId(long subId, int state, String incomingNumber) { + public void notifyCallStateForSubscriber(long subId, int state, String incomingNumber) { if (!checkNotifyPermission("notifyCallState()")) { return; } if (VDBG) { - log("notifyCallStateUsingSubId: subId=" + subId + log("notifyCallStateForSubscriber: subId=" + subId + " state=" + state + " incomingNumber=" + incomingNumber); } synchronized (mRecords) { @@ -573,38 +573,38 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyServiceState(ServiceState state) { - notifyServiceStateUsingSubId(mDefaultSubId, state); + notifyServiceStateForSubscriber(mDefaultSubId, state); } - public void notifyServiceStateUsingSubId(long subId, ServiceState state) { + public void notifyServiceStateForSubscriber(long subId, ServiceState state) { if (!checkNotifyPermission("notifyServiceState()")){ return; } if (subId == SubscriptionManager.DEFAULT_SUB_ID) { subId = mDefaultSubId; - if (VDBG) log("notifyServiceStateUsingSubId: using mDefaultSubId=" + mDefaultSubId); + if (VDBG) log("notifyServiceStateForSubscriber: using mDefaultSubId=" + mDefaultSubId); } synchronized (mRecords) { int phoneId = SubscriptionManager.getPhoneId(subId); if (VDBG) { - log("notifyServiceStateUsingSubId: subId=" + subId + " phoneId=" + phoneId + log("notifyServiceStateForSubscriber: subId=" + subId + " phoneId=" + phoneId + " state=" + state); } if (validatePhoneId(phoneId)) { mServiceState[phoneId] = state; - logServiceStateChanged("notifyServiceStateUsingSubId", subId, phoneId, state); - if (VDBG) toStringLogSSC("notifyServiceStateUsingSubId"); + logServiceStateChanged("notifyServiceStateForSubscriber", subId, phoneId, state); + if (VDBG) toStringLogSSC("notifyServiceStateForSubscriber"); for (Record r : mRecords) { if (VDBG) { - log("notifyServiceStateUsingSubId: r=" + r + " subId=" + subId + log("notifyServiceStateForSubscriber: r=" + r + " subId=" + subId + " phoneId=" + phoneId + " state=" + state); } if (((r.events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) && (r.phoneId == phoneId)) { try { if (DBG) { - log("notifyServiceStateUsingSubId: callback.onSSC r=" + r + log("notifyServiceStateForSubscriber: callback.onSSC r=" + r + " subId=" + subId + " phoneId=" + phoneId + " state=" + state); } @@ -615,7 +615,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } } else { - log("notifyServiceStateUsingSubId: INVALID phoneId=" + phoneId); + log("notifyServiceStateForSubscriber: INVALID phoneId=" + phoneId); } handleRemoveListLocked(); } @@ -623,33 +623,33 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifySignalStrength(SignalStrength signalStrength) { - notifySignalStrengthUsingSubId(mDefaultSubId, signalStrength); + notifySignalStrengthForSubscriber(mDefaultSubId, signalStrength); } - public void notifySignalStrengthUsingSubId(long subId, SignalStrength signalStrength) { + public void notifySignalStrengthForSubscriber(long subId, SignalStrength signalStrength) { if (!checkNotifyPermission("notifySignalStrength()")) { return; } if (VDBG) { - log("notifySignalStrengthUsingSubId: subId=" + subId + log("notifySignalStrengthForSubscriber: subId=" + subId + " signalStrength=" + signalStrength); - toStringLogSSC("notifySignalStrengthUsingSubId"); + toStringLogSSC("notifySignalStrengthForSubscriber"); } synchronized (mRecords) { int phoneId = SubscriptionManager.getPhoneId(subId); if (validatePhoneId(phoneId)) { - if (VDBG) log("notifySignalStrengthUsingSubId: valid phoneId=" + phoneId); + if (VDBG) log("notifySignalStrengthForSubscriber: valid phoneId=" + phoneId); mSignalStrength[phoneId] = signalStrength; for (Record r : mRecords) { if (VDBG) { - log("notifySignalStrengthUsingSubId: r=" + r + " subId=" + subId + log("notifySignalStrengthForSubscriber: r=" + r + " subId=" + subId + " phoneId=" + phoneId + " ss=" + signalStrength); } if (((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) && (r.phoneId == phoneId)) { try { if (DBG) { - log("notifySignalStrengthUsingSubId: callback.onSsS r=" + r + log("notifySignalStrengthForSubscriber: callback.onSsS r=" + r + " subId=" + subId + " phoneId=" + phoneId + " ss=" + signalStrength); } @@ -664,7 +664,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { int gsmSignalStrength = signalStrength.getGsmSignalStrength(); int ss = (gsmSignalStrength == 99 ? -1 : gsmSignalStrength); if (DBG) { - log("notifySignalStrengthUsingSubId: callback.onSS r=" + r + log("notifySignalStrengthForSubscriber: callback.onSS r=" + r + " subId=" + subId + " phoneId=" + phoneId + " gsmSS=" + gsmSignalStrength + " ss=" + ss); } @@ -675,7 +675,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } } else { - log("notifySignalStrengthUsingSubId: invalid phoneId=" + phoneId); + log("notifySignalStrengthForSubscriber: invalid phoneId=" + phoneId); } handleRemoveListLocked(); } @@ -683,15 +683,15 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyCellInfo(List<CellInfo> cellInfo) { - notifyCellInfoUsingSubId(mDefaultSubId, cellInfo); + notifyCellInfoForSubscriber(mDefaultSubId, cellInfo); } - public void notifyCellInfoUsingSubId(long subId, List<CellInfo> cellInfo) { + public void notifyCellInfoForSubscriber(long subId, List<CellInfo> cellInfo) { if (!checkNotifyPermission("notifyCellInfo()")) { return; } if (VDBG) { - log("notifyCellInfoUsingSubId: subId=" + subId + log("notifyCellInfoForSubscriber: subId=" + subId + " cellInfo=" + cellInfo); } @@ -743,15 +743,15 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyMessageWaitingChanged(boolean mwi) { - notifyMessageWaitingChangedUsingSubId(mDefaultSubId, mwi); + notifyMessageWaitingChangedForSubscriber(mDefaultSubId, mwi); } - public void notifyMessageWaitingChangedUsingSubId(long subId, boolean mwi) { + public void notifyMessageWaitingChangedForSubscriber(long subId, boolean mwi) { if (!checkNotifyPermission("notifyMessageWaitingChanged()")) { return; } if (VDBG) { - log("notifyMessageWaitingChangedUsingSubId: subId=" + subId + log("notifyMessageWaitingChangedForSubscriber: subId=" + subId + " mwi=" + mwi); } synchronized (mRecords) { @@ -774,15 +774,15 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyCallForwardingChanged(boolean cfi) { - notifyCallForwardingChangedUsingSubId(mDefaultSubId, cfi); + notifyCallForwardingChangedForSubscriber(mDefaultSubId, cfi); } - public void notifyCallForwardingChangedUsingSubId(long subId, boolean cfi) { + public void notifyCallForwardingChangedForSubscriber(long subId, boolean cfi) { if (!checkNotifyPermission("notifyCallForwardingChanged()")) { return; } if (VDBG) { - log("notifyCallForwardingChangedUsingSubId: subId=" + subId + log("notifyCallForwardingChangedForSubscriber: subId=" + subId + " cfi=" + cfi); } synchronized (mRecords) { @@ -805,10 +805,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyDataActivity(int state) { - notifyDataActivityUsingSubId(mDefaultSubId, state); + notifyDataActivityForSubscriber(mDefaultSubId, state); } - public void notifyDataActivityUsingSubId(long subId, int state) { + public void notifyDataActivityForSubscriber(long subId, int state) { if (!checkNotifyPermission("notifyDataActivity()" )) { return; } @@ -831,12 +831,12 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { public void notifyDataConnection(int state, boolean isDataConnectivityPossible, String reason, String apn, String apnType, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, int networkType, boolean roaming) { - notifyDataConnectionUsingSubId(mDefaultSubId, state, isDataConnectivityPossible, + notifyDataConnectionForSubscriber(mDefaultSubId, state, isDataConnectivityPossible, reason, apn, apnType, linkProperties, networkCapabilities, networkType, roaming); } - public void notifyDataConnectionUsingSubId(long subId, int state, + public void notifyDataConnectionForSubscriber(long subId, int state, boolean isDataConnectivityPossible, String reason, String apn, String apnType, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, int networkType, boolean roaming) { @@ -844,7 +844,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { return; } if (VDBG) { - log("notifyDataConnectionUsingSubId: subId=" + subId + log("notifyDataConnectionForSubscriber: subId=" + subId + " state=" + state + " isDataConnectivityPossible=" + isDataConnectivityPossible + " reason='" + reason + "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType @@ -921,16 +921,16 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyDataConnectionFailed(String reason, String apnType) { - notifyDataConnectionFailedUsingSubId(mDefaultSubId, reason, apnType); + notifyDataConnectionFailedForSubscriber(mDefaultSubId, reason, apnType); } - public void notifyDataConnectionFailedUsingSubId(long subId, + public void notifyDataConnectionFailedForSubscriber(long subId, String reason, String apnType) { if (!checkNotifyPermission("notifyDataConnectionFailed()")) { return; } if (VDBG) { - log("notifyDataConnectionFailedUsingSubId: subId=" + subId + log("notifyDataConnectionFailedForSubscriber: subId=" + subId + " reason=" + reason + " apnType=" + apnType); } synchronized (mRecords) { @@ -954,17 +954,17 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyCellLocation(Bundle cellLocation) { - notifyCellLocationUsingSubId(mDefaultSubId, cellLocation); + notifyCellLocationForSubscriber(mDefaultSubId, cellLocation); } - public void notifyCellLocationUsingSubId(long subId, Bundle cellLocation) { - log("notifyCellLocationUsingSubId: subId=" + subId + public void notifyCellLocationForSubscriber(long subId, Bundle cellLocation) { + log("notifyCellLocationForSubscriber: subId=" + subId + " cellLocation=" + cellLocation); if (!checkNotifyPermission("notifyCellLocation()")) { return; } if (VDBG) { - log("notifyCellLocationUsingSubId: subId=" + subId + log("notifyCellLocationForSubscriber: subId=" + subId + " cellLocation=" + cellLocation); } synchronized (mRecords) { diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index c469b42..888fa1a 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -265,7 +265,11 @@ public class AccountManagerService mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context1, Intent intent) { - purgeOldGrantsAll(); + // Don't delete accounts when updating a authenticator's + // package. + if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + purgeOldGrantsAll(); + } } }, intentFilter); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 0bdb964..599c3b9 100755 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -308,7 +308,14 @@ public final class ActiveServices { return new ComponentName("!", res.permission != null ? res.permission : "private to package"); } + ServiceRecord r = res.record; + + if (!mAm.getUserManagerLocked().exists(r.userId)) { + Slog.d(TAG, "Trying to start service with non-existent user! " + r.userId); + return null; + } + NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked( callingUid, r.packageName, service, service.getFlags(), null, r.userId); if (unscheduleServiceRestartLocked(r, callingUid, false)) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2e9df8a..4f179ce 100755 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -196,6 +196,7 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; +import dalvik.system.VMRuntime; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -887,6 +888,8 @@ public final class ActivityManagerService extends ActivityManagerNative boolean mProcessesReady = false; boolean mSystemReady = false; boolean mBooting = false; + boolean mCallFinishBooting = false; + boolean mBootAnimationComplete = false; boolean mWaitingUpdate = false; boolean mDidUpdate = false; boolean mOnBattery = false; @@ -1074,13 +1077,16 @@ public final class ActivityManagerService extends ActivityManagerNative /** * Runtime CPU use collection thread. This object's lock is used to - * protect all related state. + * perform synchronization with the thread (notifying it to run). */ final Thread mProcessCpuThread; /** - * Used to collect process stats when showing not responding dialog. - * Protected by mProcessCpuThread. + * Used to collect per-process CPU use for ANRs, battery stats, etc. + * Must acquire this object's lock when accessing it. + * NOTE: this lock will be held while doing long operations (trawling + * through all processes in /proc), so it should never be acquired by + * any critical paths such as when holding the main activity manager lock. */ final ProcessCpuTracker mProcessCpuTracker = new ProcessCpuTracker( MONITOR_THREAD_CPU_USAGE); @@ -1594,7 +1600,7 @@ public final class ActivityManagerService extends ActivityManagerNative infoMap.put(mi.pid, mi); } updateCpuStatsNow(); - synchronized (mProcessCpuThread) { + synchronized (mProcessCpuTracker) { final int N = mProcessCpuTracker.countStats(); for (int i=0; i<N; i++) { ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); @@ -1893,7 +1899,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (memInfo != null) { updateCpuStatsNow(); long nativeTotalPss = 0; - synchronized (mProcessCpuThread) { + synchronized (mProcessCpuTracker) { final int N = mProcessCpuTracker.countStats(); for (int j=0; j<N; j++) { ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(j); @@ -1911,7 +1917,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } memInfo.readMemInfo(); - synchronized (this) { + synchronized (ActivityManagerService.this) { if (DEBUG_PSS) Slog.d(TAG, "Collected native and kernel memory in " + (SystemClock.uptimeMillis()-start) + "ms"); mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(), @@ -2182,7 +2188,7 @@ public final class ActivityManagerService extends ActivityManagerNative return; } - synchronized (mActivityManagerService.mProcessCpuThread) { + synchronized (mActivityManagerService.mProcessCpuTracker) { pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentLoad()); pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentState( SystemClock.uptimeMillis())); @@ -2378,7 +2384,7 @@ public final class ActivityManagerService extends ActivityManagerNative } void updateCpuStatsNow() { - synchronized (mProcessCpuThread) { + synchronized (mProcessCpuTracker) { mProcessCpuMutexFree.set(false); final long now = SystemClock.uptimeMillis(); boolean haveNewCpuStats = false; @@ -3124,6 +3130,11 @@ public final class ActivityManagerService extends ActivityManagerNative requiredAbi = Build.SUPPORTED_ABIS[0]; } + String instructionSet = null; + if (app.info.primaryCpuAbi != null) { + instructionSet = VMRuntime.getInstructionSet(app.info.primaryCpuAbi); + } + // Start the process. It will either succeed and return a result containing // the PID of the new process, or else throw a RuntimeException. boolean isActivityProcess = (entryPoint == null); @@ -3131,7 +3142,8 @@ public final class ActivityManagerService extends ActivityManagerNative checkTime(startTime, "startProcess: asking zygote to start proc"); Process.ProcessStartResult startResult = Process.start(entryPoint, app.processName, uid, uid, gids, debugFlags, mountExternal, - app.info.targetSdkVersion, app.info.seinfo, requiredAbi, entryPointArgs); + app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet, + entryPointArgs); checkTime(startTime, "startProcess: returned from zygote!"); if (app.isolated) { @@ -3215,7 +3227,6 @@ public final class ActivityManagerService extends ActivityManagerNative if (resumed) { if (mUsageStatsService != null) { mUsageStatsService.reportEvent(component.realActivity, component.userId, - System.currentTimeMillis(), UsageEvents.Event.MOVE_TO_FOREGROUND); } synchronized (stats) { @@ -3224,7 +3235,6 @@ public final class ActivityManagerService extends ActivityManagerNative } else { if (mUsageStatsService != null) { mUsageStatsService.reportEvent(component.realActivity, component.userId, - System.currentTimeMillis(), UsageEvents.Event.MOVE_TO_BACKGROUND); } synchronized (stats) { @@ -5069,7 +5079,7 @@ public final class ActivityManagerService extends ActivityManagerNative String cpuInfo = null; if (MONITOR_CPU_USAGE) { updateCpuStatsNow(); - synchronized (mProcessCpuThread) { + synchronized (mProcessCpuTracker) { cpuInfo = mProcessCpuTracker.printCurrentState(anrTime); } info.append(processCpuTracker.printCurrentLoad()); @@ -6190,6 +6200,14 @@ public final class ActivityManagerService extends ActivityManagerNative } final void finishBooting() { + synchronized (this) { + if (!mBootAnimationComplete) { + mCallFinishBooting = true; + return; + } + mCallFinishBooting = false; + } + // Register receivers to handle package update events mPackageMonitor.register(mContext, Looper.getMainLooper(), false); @@ -6248,6 +6266,18 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override + public void bootAnimationComplete() { + final boolean callFinishBooting; + synchronized (this) { + callFinishBooting = mCallFinishBooting; + mBootAnimationComplete = true; + } + if (callFinishBooting) { + finishBooting(); + } + } + final void ensureBootCompleted() { boolean booting; boolean enableScreen; @@ -6280,12 +6310,12 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override - public final void activityPaused(IBinder token, PersistableBundle persistentState) { + public final void activityPaused(IBinder token) { final long origId = Binder.clearCallingIdentity(); synchronized(this) { ActivityStack stack = ActivityRecord.getStackLocked(token); if (stack != null) { - stack.activityPausedLocked(token, false, persistentState); + stack.activityPausedLocked(token, false); } } Binder.restoreCallingIdentity(origId); @@ -7441,12 +7471,33 @@ public final class ActivityManagerService extends ActivityManagerNative // Does the caller have this permission on the URI? if (!checkHoldingPermissionsLocked(pm, pi, grantUri, callingUid, modeFlags)) { - // Right now, if you are not the original owner of the permission, - // you are not allowed to revoke it. - //if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) { - throw new SecurityException("Uid " + callingUid - + " does not have permission to uri " + grantUri); - //} + // Have they don't have direct access to the URI, then revoke any URI + // permissions that have been granted to them. + final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(callingUid); + if (perms != null) { + boolean persistChanged = false; + for (Iterator<UriPermission> it = perms.values().iterator(); it.hasNext();) { + final UriPermission perm = it.next(); + if (perm.uri.sourceUserId == grantUri.sourceUserId + && perm.uri.uri.isPathPrefixMatch(grantUri.uri)) { + if (DEBUG_URI_PERMISSION) + Slog.v(TAG, + "Revoking " + perm.targetUid + " permission to " + perm.uri); + persistChanged |= perm.revokeModes( + modeFlags | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + if (perm.modeFlags == 0) { + it.remove(); + } + } + } + if (perms.isEmpty()) { + mGrantedUriPermissions.remove(callingUid); + } + if (persistChanged) { + schedulePersistUriGrants(); + } + } + return; } boolean persistChanged = false; @@ -9660,14 +9711,9 @@ public final class ActivityManagerService extends ActivityManagerNative long ident = 0; boolean clearedIdentity = false; userId = unsafeConvertIncomingUser(userId); - if (UserHandle.getUserId(callingUid) != userId) { - if (checkComponentPermission(INTERACT_ACROSS_USERS, callingPid, - callingUid, -1, true) == PackageManager.PERMISSION_GRANTED - || checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid, - callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) { - clearedIdentity = true; - ident = Binder.clearCallingIdentity(); - } + if (canClearIdentity(callingPid, callingUid, userId)) { + clearedIdentity = true; + ident = Binder.clearCallingIdentity(); } ContentProviderHolder holder = null; try { @@ -9695,6 +9741,19 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } + private boolean canClearIdentity(int callingPid, int callingUid, int userId) { + if (UserHandle.getUserId(callingUid) == userId) { + return true; + } + if (checkComponentPermission(INTERACT_ACROSS_USERS, callingPid, + callingUid, -1, true) == PackageManager.PERMISSION_GRANTED + || checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid, + callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) { + return true; + } + return false; + } + // ========================================================= // GLOBAL MANAGEMENT // ========================================================= @@ -9947,7 +10006,7 @@ public final class ActivityManagerService extends ActivityManagerNative } private void updateEventDispatchingLocked() { - mWindowManager.setEventDispatching(mBooted && !mWentToSleep && !mShuttingDown); + mWindowManager.setEventDispatching(mBooted && !mShuttingDown); } public void setLockScreenShown(boolean shown) { @@ -10346,8 +10405,8 @@ public final class ActivityManagerService extends ActivityManagerNative final boolean translucentChanged = r.changeWindowTranslucency(false); if (translucentChanged) { r.task.stack.convertToTranslucent(r); - mStackSupervisor.ensureActivitiesVisibleLocked(null, 0); } + mStackSupervisor.ensureActivitiesVisibleLocked(null, 0); mWindowManager.setAppFullscreen(token, false); return translucentChanged; } @@ -13695,7 +13754,7 @@ public final class ActivityManagerService extends ActivityManagerNative findPid = Integer.parseInt(args[opti]); } catch (NumberFormatException e) { } - synchronized (mProcessCpuThread) { + synchronized (mProcessCpuTracker) { final int N = mProcessCpuTracker.countStats(); for (int i=0; i<N; i++) { ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); @@ -13857,7 +13916,7 @@ public final class ActivityManagerService extends ActivityManagerNative // If we are showing aggregations, also look for native processes to // include so that our aggregations are more accurate. updateCpuStatsNow(); - synchronized (mProcessCpuThread) { + synchronized (mProcessCpuTracker) { final int N = mProcessCpuTracker.countStats(); for (int i=0; i<N; i++) { ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); @@ -15936,6 +15995,7 @@ public final class ActivityManagerService extends ActivityManagerNative newConfig.seq = mConfigurationSeq; mConfiguration = newConfig; Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig); + mUsageStatsService.reportConfigurationChange(newConfig, mCurrentUserId); //mUsageStatsService.noteStartConfig(newConfig); final Configuration configCopy = new Configuration(mConfiguration); diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 77b4cc9..e043f03 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -698,12 +698,12 @@ final class ActivityRecord { case ActivityOptions.ANIM_SCALE_UP: service.mWindowManager.overridePendingAppTransitionScaleUp( pendingOptions.getStartX(), pendingOptions.getStartY(), - pendingOptions.getStartWidth(), pendingOptions.getStartHeight()); + pendingOptions.getWidth(), pendingOptions.getHeight()); if (intent.getSourceBounds() == null) { intent.setSourceBounds(new Rect(pendingOptions.getStartX(), pendingOptions.getStartY(), - pendingOptions.getStartX()+pendingOptions.getStartWidth(), - pendingOptions.getStartY()+pendingOptions.getStartHeight())); + pendingOptions.getStartX()+pendingOptions.getWidth(), + pendingOptions.getStartY()+pendingOptions.getHeight())); } break; case ActivityOptions.ANIM_THUMBNAIL_SCALE_UP: @@ -728,15 +728,14 @@ final class ActivityRecord { service.mWindowManager.overridePendingAppTransitionAspectScaledThumb( pendingOptions.getThumbnail(), pendingOptions.getStartX(), pendingOptions.getStartY(), + pendingOptions.getWidth(), pendingOptions.getHeight(), pendingOptions.getOnAnimationStartListener(), (animationType == ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP)); if (intent.getSourceBounds() == null) { intent.setSourceBounds(new Rect(pendingOptions.getStartX(), pendingOptions.getStartY(), - pendingOptions.getStartX() - + pendingOptions.getThumbnail().getWidth(), - pendingOptions.getStartY() - + pendingOptions.getThumbnail().getHeight())); + pendingOptions.getStartX() + pendingOptions.getWidth(), + pendingOptions.getStartY() + pendingOptions.getHeight())); } break; default: diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 3efd049..d71f94e 100755 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -241,6 +241,9 @@ final class ActivityStack { /** Run all ActivityStacks through this */ final ActivityStackSupervisor mStackSupervisor; + /** Used to keep resumeTopActivityLocked() from being entered recursively */ + private boolean inResumeTopActivity; + static final int PAUSE_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 1; static final int DESTROY_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 2; static final int LAUNCH_TICK_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 3; @@ -281,7 +284,7 @@ final class ActivityStack { if (r.app != null) { mService.logAppTooSlow(r.app, r.pauseTime, "pausing " + r); } - activityPausedLocked(r.appToken, true, r.persistentState); + activityPausedLocked(r.appToken, true); } } break; case LAUNCH_TICK_MSG: { @@ -422,13 +425,13 @@ final class ActivityStack { } final ActivityRecord topActivity() { - // Iterate to find the first non-empty task stack. Note that this code can - // be simplified once we stop storing tasks with empty mActivities lists. for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; - final int topActivityNdx = activities.size() - 1; - if (topActivityNdx >= 0) { - return activities.get(topActivityNdx); + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if (!r.finishing) { + return r; + } } } return null; @@ -456,7 +459,7 @@ final class ActivityStack { final ActivityRecord r = ActivityRecord.forToken(token); if (r != null) { final TaskRecord task = r.task; - if (task.mActivities.contains(r) && mTaskHistory.contains(task)) { + if (task != null && task.mActivities.contains(r) && mTaskHistory.contains(task)) { if (task.stack != this) Slog.w(TAG, "Illegal state! task does not point to stack it is in."); return r; @@ -709,7 +712,7 @@ final class ActivityStack { // Still have something resumed; can't sleep until it is paused. if (DEBUG_PAUSE) Slog.v(TAG, "Sleep needs to pause " + mResumedActivity); if (DEBUG_USER_LEAVING) Slog.v(TAG, "Sleep => pause with userLeaving=false"); - startPausingLocked(false, true); + startPausingLocked(false, true, false, false); return true; } if (mPausingActivity != null) { @@ -787,22 +790,38 @@ final class ActivityStack { return null; } - final void startPausingLocked(boolean userLeaving, boolean uiSleeping) { + /** + * Start pausing the currently resumed activity. It is an error to call this if there + * is already an activity being paused or there is no resumed activity. + * + * @param userLeaving True if this should result in an onUserLeaving to the current activity. + * @param uiSleeping True if this is happening with the user interface going to sleep (the + * screen turning off). + * @param resuming True if this is being called as part of resuming the top activity, so + * we shouldn't try to instigate a resume here. + * @param dontWait True if the caller does not want to wait for the pause to complete. If + * set to true, we will immediately complete the pause here before returning. + * @return Returns true if an activity now is in the PAUSING state, and we are waiting for + * it to tell us when it is done. + */ + final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping, boolean resuming, + boolean dontWait) { if (mPausingActivity != null) { - Slog.e(TAG, "Trying to pause when pause is already pending for " - + mPausingActivity, new RuntimeException("here").fillInStackTrace()); + Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity); + completePauseLocked(false); } ActivityRecord prev = mResumedActivity; if (prev == null) { - Slog.e(TAG, "Trying to pause when nothing is resumed", - new RuntimeException("here").fillInStackTrace()); - mStackSupervisor.resumeTopActivitiesLocked(); - return; + if (!resuming) { + Slog.wtf(TAG, "Trying to pause when nothing is resumed"); + mStackSupervisor.resumeTopActivitiesLocked(); + } + return false; } if (mActivityContainer.mParentActivity == null) { // Top level stack, not a child. Look for child stacks. - mStackSupervisor.pauseChildStacks(prev, userLeaving, uiSleeping); + mStackSupervisor.pauseChildStacks(prev, userLeaving, uiSleeping, resuming, dontWait); } if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSING: " + prev); @@ -831,7 +850,7 @@ final class ActivityStack { prev.shortComponentName); mService.updateUsageStats(prev, false); prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing, - userLeaving, prev.configChangeFlags); + userLeaving, prev.configChangeFlags, dontWait); } catch (Exception e) { // Ignore exception, if process died other code will cleanup. Slog.w(TAG, "Exception thrown during pause", e); @@ -862,39 +881,46 @@ final class ActivityStack { if (DEBUG_PAUSE) Slog.v(TAG, "Key dispatch not paused for screen off"); } - // Schedule a pause timeout in case the app doesn't respond. - // We don't give it much time because this directly impacts the - // responsiveness seen by the user. - Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG); - msg.obj = prev; - prev.pauseTime = SystemClock.uptimeMillis(); - mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT); - if (DEBUG_PAUSE) Slog.v(TAG, "Waiting for pause to complete..."); + if (dontWait) { + // If the caller said they don't want to wait for the pause, then complete + // the pause now. + completePauseLocked(false); + return false; + + } else { + // Schedule a pause timeout in case the app doesn't respond. + // We don't give it much time because this directly impacts the + // responsiveness seen by the user. + Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG); + msg.obj = prev; + prev.pauseTime = SystemClock.uptimeMillis(); + mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT); + if (DEBUG_PAUSE) Slog.v(TAG, "Waiting for pause to complete..."); + return true; + } + } else { // This activity failed to schedule the // pause, so just treat it as being paused now. if (DEBUG_PAUSE) Slog.v(TAG, "Activity not running, resuming next."); - mStackSupervisor.getFocusedStack().resumeTopActivityLocked(null); + if (!resuming) { + mStackSupervisor.getFocusedStack().resumeTopActivityLocked(null); + } + return false; } } - final void activityPausedLocked(IBinder token, boolean timeout, - PersistableBundle persistentState) { + final void activityPausedLocked(IBinder token, boolean timeout) { if (DEBUG_PAUSE) Slog.v( TAG, "Activity paused: token=" + token + ", timeout=" + timeout); final ActivityRecord r = isInStackLocked(token); if (r != null) { mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); - if (persistentState != null) { - r.persistentState = persistentState; - mService.notifyTaskPersisterLocked(r.task, false); - } if (mPausingActivity == r) { if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSED: " + r + (timeout ? " (due to timeout)" : " (pause complete)")); - r.state = ActivityState.PAUSED; - completePauseLocked(); + completePauseLocked(true); } else { EventLog.writeEvent(EventLogTags.AM_FAILED_TO_PAUSE, r.userId, System.identityHashCode(r), r.shortComponentName, @@ -945,11 +971,12 @@ final class ActivityStack { } } - private void completePauseLocked() { + private void completePauseLocked(boolean resumeNext) { ActivityRecord prev = mPausingActivity; if (DEBUG_PAUSE) Slog.v(TAG, "Complete pause: " + prev); if (prev != null) { + prev.state = ActivityState.PAUSED; if (prev.finishing) { if (DEBUG_PAUSE) Slog.v(TAG, "Executing finish of activity: " + prev); prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false); @@ -992,19 +1019,21 @@ final class ActivityStack { mPausingActivity = null; } - final ActivityStack topStack = mStackSupervisor.getFocusedStack(); - if (!mService.isSleepingOrShuttingDown()) { - mStackSupervisor.resumeTopActivitiesLocked(topStack, prev, null); - } else { - mStackSupervisor.checkReadyForSleepLocked(); - ActivityRecord top = topStack.topRunningActivityLocked(null); - if (top == null || (prev != null && top != prev)) { - // If there are no more activities available to run, - // do resume anyway to start something. Also if the top - // activity on the stack is not the just paused activity, - // we need to go ahead and resume it to ensure we complete - // an in-flight app switch. - mStackSupervisor.resumeTopActivitiesLocked(topStack, null, null); + if (resumeNext) { + final ActivityStack topStack = mStackSupervisor.getFocusedStack(); + if (!mService.isSleepingOrShuttingDown()) { + mStackSupervisor.resumeTopActivitiesLocked(topStack, prev, null); + } else { + mStackSupervisor.checkReadyForSleepLocked(); + ActivityRecord top = topStack.topRunningActivityLocked(null); + if (top == null || (prev != null && top != prev)) { + // If there are no more activities available to run, + // do resume anyway to start something. Also if the top + // activity on the stack is not the just paused activity, + // we need to go ahead and resume it to ensure we complete + // an in-flight app switch. + mStackSupervisor.resumeTopActivitiesLocked(topStack, null, null); + } } } @@ -1013,11 +1042,8 @@ final class ActivityStack { if (prev.app != null && prev.cpuTimeAtResume > 0 && mService.mBatteryStatsService.isOnBattery()) { - long diff; - synchronized (mService.mProcessCpuThread) { - diff = mService.mProcessCpuTracker.getCpuTimeForPid(prev.app.pid) - - prev.cpuTimeAtResume; - } + long diff = mService.mProcessCpuTracker.getCpuTimeForPid(prev.app.pid) + - prev.cpuTimeAtResume; if (diff > 0) { BatteryStatsImpl bsi = mService.mBatteryStatsService.getActiveStatistics(); synchronized (bsi) { @@ -1068,9 +1094,7 @@ final class ActivityStack { // TODO: To be more accurate, the mark should be before the onCreate, // not after the onResume. But for subsequent starts, onResume is fine. if (next.app != null) { - synchronized (mService.mProcessCpuThread) { - next.cpuTimeAtResume = mService.mProcessCpuTracker.getCpuTimeForPid(next.app.pid); - } + next.cpuTimeAtResume = mService.mProcessCpuTracker.getCpuTimeForPid(next.app.pid); } else { next.cpuTimeAtResume = 0; // Couldn't get the cpu time of process } @@ -1439,6 +1463,23 @@ final class ActivityStack { } final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) { + if (inResumeTopActivity) { + // Don't even start recursing. + return false; + } + + boolean result = false; + try { + // Protect against recursion. + inResumeTopActivity = true; + result = resumeTopActivityInnerLocked(prev, options); + } finally { + inResumeTopActivity = false; + } + return result; + } + + final boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) { if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen(""); ActivityRecord parent = mActivityContainer.mParentActivity; @@ -1587,10 +1628,10 @@ final class ActivityStack { // We need to start pausing the current activity so the top one // can be resumed... - boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving); + boolean dontWaitForPause = (next.info.flags&ActivityInfo.FLAG_RESUME_WHILE_PAUSING) != 0; + boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, true, dontWaitForPause); if (mResumedActivity != null) { - pausing = true; - startPausingLocked(userLeaving, false); + pausing |= startPausingLocked(userLeaving, false, true, dontWaitForPause); if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Pausing " + mResumedActivity); } if (pausing) { @@ -2700,7 +2741,7 @@ final class ActivityStack { if (mPausingActivity == null) { if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r); if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false"); - startPausingLocked(false, false); + startPausingLocked(false, false, false, false); } if (endTask) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 780efa1..b8261a4 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -592,7 +592,7 @@ public final class ActivityStackSupervisor implements DisplayListener { * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving(). * @return true if any activity was paused as a result of this call. */ - boolean pauseBackStacks(boolean userLeaving) { + boolean pauseBackStacks(boolean userLeaving, boolean resuming, boolean dontWait) { boolean someActivityPaused = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; @@ -601,8 +601,8 @@ public final class ActivityStackSupervisor implements DisplayListener { if (!isFrontStack(stack) && stack.mResumedActivity != null) { if (DEBUG_STATES) Slog.d(TAG, "pauseBackStacks: stack=" + stack + " mResumedActivity=" + stack.mResumedActivity); - stack.startPausingLocked(userLeaving, false); - someActivityPaused = true; + someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming, + dontWait); } } } @@ -631,7 +631,8 @@ public final class ActivityStackSupervisor implements DisplayListener { return pausing; } - void pauseChildStacks(ActivityRecord parent, boolean userLeaving, boolean uiSleeping) { + void pauseChildStacks(ActivityRecord parent, boolean userLeaving, boolean uiSleeping, + boolean resuming, boolean dontWait) { // TODO: Put all stacks in supervisor and iterate through them instead. for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; @@ -639,7 +640,7 @@ public final class ActivityStackSupervisor implements DisplayListener { final ActivityStack stack = stacks.get(stackNdx); if (stack.mResumedActivity != null && stack.mActivityContainer.mParentActivity == parent) { - stack.startPausingLocked(userLeaving, uiSleeping); + stack.startPausingLocked(userLeaving, uiSleeping, resuming, dontWait); } } } @@ -1640,57 +1641,8 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - if (sourceRecord == null) { - // This activity is not being started from another... in this - // case we -always- start a new task. - if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0 && inTask == null) { - Slog.w(TAG, "startActivity called from non-Activity context; forcing " + - "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); - launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; - } - } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { - // The original activity who is starting us is running as a single - // instance... this new activity it is starting must go on its - // own task. - launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; - } else if (launchSingleInstance || launchSingleTask) { - // The activity being started is a single instance... it always - // gets launched into its own task. - launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; - } - - ActivityInfo newTaskInfo = null; - Intent newTaskIntent = null; - ActivityStack sourceStack; - if (sourceRecord != null) { - if (sourceRecord.finishing) { - // If the source is finishing, we can't further count it as our source. This - // is because the task it is associated with may now be empty and on its way out, - // so we don't want to blindly throw it in to that task. Instead we will take - // the NEW_TASK flow and try to find a task for it. But save the task information - // so it can be used when creating the new task. - if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { - Slog.w(TAG, "startActivity called from finishing " + sourceRecord - + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); - launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; - newTaskInfo = sourceRecord.info; - newTaskIntent = sourceRecord.task.intent; - } - sourceRecord = null; - sourceStack = null; - } else { - sourceStack = sourceRecord.task.stack; - } - } else { - sourceStack = null; - } - boolean addingToTask = false; - boolean movedHome = false; TaskRecord reuseTask = null; - ActivityStack targetStack; - - intent.setFlags(launchFlags); // If the caller is not coming from another activity, but has given us an // explicit task into which they would like us to launch the new activity, @@ -1746,6 +1698,58 @@ public final class ActivityStackSupervisor implements DisplayListener { inTask = null; } + if (inTask == null) { + if (sourceRecord == null) { + // This activity is not being started from another... in this + // case we -always- start a new task. + if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0 && inTask == null) { + Slog.w(TAG, "startActivity called from non-Activity context; forcing " + + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } + } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + // The original activity who is starting us is running as a single + // instance... this new activity it is starting must go on its + // own task. + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } else if (launchSingleInstance || launchSingleTask) { + // The activity being started is a single instance... it always + // gets launched into its own task. + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } + } + + ActivityInfo newTaskInfo = null; + Intent newTaskIntent = null; + ActivityStack sourceStack; + if (sourceRecord != null) { + if (sourceRecord.finishing) { + // If the source is finishing, we can't further count it as our source. This + // is because the task it is associated with may now be empty and on its way out, + // so we don't want to blindly throw it in to that task. Instead we will take + // the NEW_TASK flow and try to find a task for it. But save the task information + // so it can be used when creating the new task. + if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { + Slog.w(TAG, "startActivity called from finishing " + sourceRecord + + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + newTaskInfo = sourceRecord.info; + newTaskIntent = sourceRecord.task.intent; + } + sourceRecord = null; + sourceStack = null; + } else { + sourceStack = sourceRecord.task.stack; + } + } else { + sourceStack = null; + } + + boolean movedHome = false; + ActivityStack targetStack; + + intent.setFlags(launchFlags); + // We may want to try to place the new activity in to an existing task. We always // do this if the target activity is singleTask or singleInstance; we will also do // this if NEW_TASK has been requested, and there is not an additional qualifier telling @@ -1768,7 +1772,7 @@ public final class ActivityStackSupervisor implements DisplayListener { if (intentActivity != null) { if (isLockTaskModeViolation(intentActivity.task)) { showLockTaskToast(); - Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode"); + Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode"); return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; } if (r.task == null) { @@ -2779,9 +2783,8 @@ public final class ActivityStackSupervisor implements DisplayListener { } // A non-top activity is reporting a visibility change. - if ((visible && (top.fullscreen || top.state != ActivityState.RESUMED)) || - top.app == null || top.app.thread == null) { - // Can't carry out this request. + if (visible && top.fullscreen) { + // Let the caller know that it can't be seen. if (DEBUG_VISIBLE_BEHIND) Slog.d(TAG, "requestVisibleBehind: returning top.fullscreen=" + top.fullscreen + " top.state=" + top.state + " top.app=" + top.app + " top.app.thread=" + top.app.thread); @@ -2803,9 +2806,12 @@ public final class ActivityStackSupervisor implements DisplayListener { mService.convertFromTranslucent(next.appToken); } } - try { - top.app.thread.scheduleBackgroundVisibleBehindChanged(top.appToken, visible); - } catch (RemoteException e) { + if (top.app != null && top.app.thread != null) { + // Notify the top app of the change. + try { + top.app.thread.scheduleBackgroundVisibleBehindChanged(top.appToken, visible); + } catch (RemoteException e) { + } } return true; } @@ -3840,7 +3846,7 @@ public final class ActivityStackSupervisor implements DisplayListener { mContainerState = CONTAINER_STATE_NO_SURFACE; ((VirtualActivityDisplay) mActivityDisplay).setSurface(null); if (mStack.mPausingActivity == null && mStack.mResumedActivity != null) { - mStack.startPausingLocked(false, true); + mStack.startPausingLocked(false, true, false, false); } } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 5a97aee..bba786d 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -47,6 +47,7 @@ public class NetworkAgentInfo { public final NetworkMonitor networkMonitor; public final NetworkMisc networkMisc; public boolean created; + public boolean validated; // The list of NetworkRequests being satisfied by this Network. public final SparseArray<NetworkRequest> networkRequests = new SparseArray<NetworkRequest>(); @@ -68,6 +69,7 @@ public class NetworkAgentInfo { networkMonitor = new NetworkMonitor(context, handler, this); networkMisc = misc; created = false; + validated = false; } public void addRequest(NetworkRequest networkRequest) { diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index ff319d3..96872a7 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -27,6 +27,7 @@ import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; +import android.net.TrafficStats; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Handler; @@ -140,34 +141,28 @@ public class NetworkMonitor extends StateMachine { private static final int CMD_REEVALUATE = BASE + 6; /** - * Message to self indicating network evaluation is complete. - * arg1 = Token to ignore old messages. - * arg2 = HTTP response code of network evaluation. - */ - private static final int EVENT_REEVALUATION_COMPLETE = BASE + 7; - - /** * Inform NetworkMonitor that the network has disconnected. */ - public static final int CMD_NETWORK_DISCONNECTED = BASE + 8; + public static final int CMD_NETWORK_DISCONNECTED = BASE + 7; /** * Force evaluation even if it has succeeded in the past. + * arg1 = UID responsible for requesting this reeval. Will be billed for data. */ - public static final int CMD_FORCE_REEVALUATION = BASE + 9; + public static final int CMD_FORCE_REEVALUATION = BASE + 8; /** * Message to self indicating captive portal login is complete. * arg1 = Token to ignore old messages. * arg2 = 1 if we should use this network, 0 otherwise. */ - private static final int CMD_CAPTIVE_PORTAL_LOGGED_IN = BASE + 10; + private static final int CMD_CAPTIVE_PORTAL_LOGGED_IN = BASE + 9; /** * Message to self indicating user desires to log into captive portal. * arg1 = Token to ignore old messages. */ - private static final int CMD_USER_WANTS_SIGN_IN = BASE + 11; + private static final int CMD_USER_WANTS_SIGN_IN = BASE + 10; /** * Request ConnectivityService display provisioning notification. @@ -175,22 +170,22 @@ public class NetworkMonitor extends StateMachine { * arg2 = NetID. * obj = Intent to be launched when notification selected by user, null if !arg1. */ - public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 12; + public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 11; /** * Message to self indicating sign-in app bypassed captive portal. */ - private static final int EVENT_APP_BYPASSED_CAPTIVE_PORTAL = BASE + 13; + private static final int EVENT_APP_BYPASSED_CAPTIVE_PORTAL = BASE + 12; /** * Message to self indicating no sign-in app responded. */ - private static final int EVENT_NO_APP_RESPONSE = BASE + 14; + private static final int EVENT_NO_APP_RESPONSE = BASE + 13; /** * Message to self indicating sign-in app indicates sign-in is not possible. */ - private static final int EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE = BASE + 15; + private static final int EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE = BASE + 14; private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; // Default to 30s linger time-out. @@ -205,6 +200,8 @@ public class NetworkMonitor extends StateMachine { private static final int MAX_RETRIES = 10; private final int mReevaluateDelayMs; private int mReevaluateToken = 0; + private static final int INVALID_UID = -1; + private int mUidResponsibleForReeval = INVALID_UID; private int mCaptivePortalLoggedInToken = 0; private int mUserPromptedToken = 0; @@ -282,6 +279,7 @@ public class NetworkMonitor extends StateMachine { return HANDLED; case CMD_FORCE_REEVALUATION: if (DBG) log("Forcing reevaluation"); + mUidResponsibleForReeval = message.arg1; transitionTo(mEvaluatingState); return HANDLED; default: @@ -322,20 +320,14 @@ public class NetworkMonitor extends StateMachine { private class EvaluatingState extends State { private int mRetries; - private class EvaluateInternetConnectivity extends Thread { - private int mToken; - EvaluateInternetConnectivity(int token) { - mToken = token; - } - public void run() { - sendMessage(EVENT_REEVALUATION_COMPLETE, mToken, isCaptivePortal()); - } - } - @Override public void enter() { mRetries = 0; sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); + if (mUidResponsibleForReeval != INVALID_UID) { + TrafficStats.setThreadStatsUid(mUidResponsibleForReeval); + mUidResponsibleForReeval = INVALID_UID; + } } @Override @@ -356,14 +348,7 @@ public class NetworkMonitor extends StateMachine { transitionTo(mValidatedState); return HANDLED; } - // Kick off a thread to perform internet connectivity evaluation. - Thread thread = new EvaluateInternetConnectivity(mReevaluateToken); - thread.run(); - return HANDLED; - case EVENT_REEVALUATION_COMPLETE: - if (message.arg1 != mReevaluateToken) - return HANDLED; - int httpResponseCode = message.arg2; + int httpResponseCode = isCaptivePortal(); if (httpResponseCode == 204) { transitionTo(mValidatedState); } else if (httpResponseCode >= 200 && httpResponseCode <= 399) { @@ -375,10 +360,18 @@ public class NetworkMonitor extends StateMachine { sendMessageDelayed(msg, mReevaluateDelayMs); } return HANDLED; + case CMD_FORCE_REEVALUATION: + // Ignore duplicate requests. + return HANDLED; default: return NOT_HANDLED; } } + + @Override + public void exit() { + TrafficStats.clearThreadStatsUid(); + } } private class UserPromptedState extends State { diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index f6beb9a..9292d45 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -16,6 +16,7 @@ package com.android.server.content; +import android.Manifest; import android.accounts.Account; import android.accounts.AccountAndUser; import android.accounts.AccountManager; @@ -478,7 +479,7 @@ public class SyncManager { mContext.registerReceiverAsUser(mAccountsUpdatedReceiver, UserHandle.ALL, new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION), - null, null); + Manifest.permission.ACCOUNT_MANAGER, null); } // Pick a random second in a day to seed all periodic syncs diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index aa1310d..6ce235b 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -159,6 +159,8 @@ final class Constants { static final int TRUE = 1; static final int FALSE = 0; + // Internal abort error code. It's the same as success. + static final int ABORT_NO_ERROR = -1; // Constants related to operands of HDMI CEC commands. // Refer to CEC Table 29 in HDMI Spec v1.4b. // [Abort Reason] diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index bb22b4d..c5a6dbd 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -31,8 +31,6 @@ import com.android.server.hdmi.HdmiControlService.DevicePollingCallback; import libcore.util.EmptyArray; -import java.io.FileDescriptor; -import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -102,11 +100,6 @@ final class HdmiCecController { // Stores the local CEC devices in the system. Device type is used for key. private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>(); - @IoThreadOnly - private final HdmiLogger mIoThreadLogger = new HdmiLogger(TAG); - @ServiceThreadOnly - private final HdmiLogger mServiceThreadLogger = new HdmiLogger(TAG); - // Private constructor. Use HdmiCecController.create(). private HdmiCecController(HdmiControlService service) { mService = service; @@ -210,9 +203,8 @@ final class HdmiCecController { } final int assignedAddress = logicalAddress; - mIoThreadLogger.debug( - String.format("New logical address for device [%d]: [preferred:%d, assigned:%d]", - deviceType, preferredAddress, assignedAddress)); + HdmiLogger.debug("New logical address for device [%d]: [preferred:%d, assigned:%d]", + deviceType, preferredAddress, assignedAddress); if (callback != null) { runOnServiceThread(new Runnable() { @Override @@ -449,7 +441,7 @@ final class HdmiCecController { allocated.add(address); } } - mIoThreadLogger.debug("[P]:Allocated Address=" + allocated); + HdmiLogger.debug("[P]:Allocated Address=" + allocated); if (callback != null) { runOnServiceThread(new Runnable() { @Override @@ -551,7 +543,7 @@ final class HdmiCecController { runOnIoThread(new Runnable() { @Override public void run() { - mIoThreadLogger.debug("[S]:" + cecMessage); + HdmiLogger.debug("[S]:" + cecMessage); byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams()); int i = 0; int errorCode = Constants.SEND_RESULT_SUCCESS; @@ -586,7 +578,7 @@ final class HdmiCecController { private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) { assertRunOnServiceThread(); HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body); - mServiceThreadLogger.debug("[R]:" + command); + HdmiLogger.debug("[R]:" + command); onReceiveCommand(command); } @@ -596,8 +588,7 @@ final class HdmiCecController { @ServiceThreadOnly private void handleHotplug(int port, boolean connected) { assertRunOnServiceThread(); - mServiceThreadLogger.debug( - "Hotplug event:[port:" + port + " , connected:" + connected + "]"); + HdmiLogger.debug("Hotplug event:[port:%d, connected:%b]", port, connected); mService.onHotplug(port, connected); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java index 85f5be2..b2300a6 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java @@ -43,9 +43,6 @@ import java.util.List; */ abstract class HdmiCecFeatureAction { private static final String TAG = "HdmiCecFeatureAction"; - // As all actions run in the same thread (service thread), it's fine to have single logger. - // TODO: create global logger for each threads and use them. - protected static final HdmiLogger DLOGGER = new HdmiLogger(TAG); // Timer handler message used for timeout event protected static final int MSG_TIMEOUT = 100; @@ -264,10 +261,7 @@ abstract class HdmiCecFeatureAction { } protected final void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) { - sendCommand(HdmiCecMessageBuilder.buildUserControlPressed( - getSourceAddress(), targetAddress, uiCommand)); - sendCommand(HdmiCecMessageBuilder.buildUserControlReleased( - getSourceAddress(), targetAddress)); + mSource.sendUserControlPressedAndReleased(targetAddress, uiCommand); } protected final void addOnFinishedCallback(HdmiCecFeatureAction action, Runnable runnable) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index bc6a299..c00c5d0 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -274,6 +274,12 @@ abstract class HdmiCecLocalDevice { return handleRecordTvScreen(message); case Constants.MESSAGE_TIMER_CLEARED_STATUS: return handleTimerClearedStatus(message); + case Constants.MESSAGE_REPORT_POWER_STATUS: + return handleReportPowerStatus(message); + case Constants.MESSAGE_TIMER_STATUS: + return handleTimerStatus(message); + case Constants.MESSAGE_RECORD_STATUS: + return handleRecordStatus(message); default: return false; } @@ -541,6 +547,18 @@ abstract class HdmiCecLocalDevice { return false; } + protected boolean handleReportPowerStatus(HdmiCecMessage message) { + return false; + } + + protected boolean handleTimerStatus(HdmiCecMessage message) { + return false; + } + + protected boolean handleRecordStatus(HdmiCecMessage message) { + return false; + } + @ServiceThreadOnly final void handleAddressAllocated(int logicalAddress, int reason) { assertRunOnServiceThread(); @@ -810,6 +828,13 @@ abstract class HdmiCecLocalDevice { Slog.w(TAG, "sendKeyEvent not implemented"); } + void sendUserControlPressedAndReleased(int targetAddress, int cecKeycode) { + mService.sendCecCommand(HdmiCecMessageBuilder.buildUserControlPressed( + mAddress, targetAddress, cecKeycode)); + mService.sendCecCommand(HdmiCecMessageBuilder.buildUserControlReleased( + mAddress, targetAddress)); + } + /** * Dump internal status of HdmiCecLocalDevice object. */ diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 7ae2198..58ccbdb 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -120,8 +120,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // other CEC devices since they might not have logical address. private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>(); - private final HdmiLogger mSafeLogger = new HdmiLogger(TAG); - HdmiCecLocalDeviceTv(HdmiControlService service) { super(service, HdmiDeviceInfo.DEVICE_TV); mPrevPortId = Constants.INVALID_PORT_ID; @@ -476,6 +474,25 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return true; } + @Override + protected boolean handleReportPowerStatus(HdmiCecMessage command) { + int newStatus = command.getParams()[0] & 0xFF; + updateDevicePowerStatus(command.getSource(), newStatus); + return true; + } + + @Override + protected boolean handleTimerStatus(HdmiCecMessage message) { + // Do nothing. + return true; + } + + @Override + protected boolean handleRecordStatus(HdmiCecMessage message) { + // Do nothing. + return true; + } + boolean updateCecSwitchInfo(int address, int type, int path) { if (address == Constants.ADDR_UNREGISTERED && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) { @@ -700,8 +717,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // # Seq 25 void setSystemAudioMode(boolean on, boolean updateSetting) { - mSafeLogger.debug(String.format("System Audio Mode change[old:%b new:%b]", - mSystemAudioActivated, on)); + HdmiLogger.debug("System Audio Mode change[old:%b new:%b]", mSystemAudioActivated, on); if (updateSetting) { mService.writeBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, on); @@ -832,6 +848,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { AudioManager.STREAM_MUSIC); mService.setAudioStatus(mute, VolumeControlAction.scaleToCustomVolume(volume, maxVolume)); + displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED, + mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume); } } @@ -855,12 +873,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } - // Remove existing volume action. - removeAction(VolumeControlAction.class); - - HdmiDeviceInfo avr = getAvrDeviceInfo(); - addAndStartAction(VolumeControlAction.ofVolumeChange(this, avr.getLogicalAddress(), - cecVolume, delta > 0)); + List<VolumeControlAction> actions = getActions(VolumeControlAction.class); + if (actions.isEmpty()) { + addAndStartAction(new VolumeControlAction(this, + getAvrDeviceInfo().getLogicalAddress(), delta > 0)); + } else { + actions.get(0).handleVolumeChange(delta > 0); + } } @ServiceThreadOnly @@ -872,8 +891,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // Remove existing volume action. removeAction(VolumeControlAction.class); - HdmiDeviceInfo avr = getAvrDeviceInfo(); - addAndStartAction(VolumeControlAction.ofMute(this, avr.getLogicalAddress(), mute)); + sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(), + mute ? HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION : + HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION); } @Override @@ -935,8 +955,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { assertRunOnServiceThread(); if (!isMessageForSystemAudio(message)) { - mSafeLogger.warning("Invalid <Set System Audio Mode> message:" + message); - return false; + HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message); + mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return true; } SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null); @@ -949,8 +970,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { assertRunOnServiceThread(); if (!isMessageForSystemAudio(message)) { - mSafeLogger.warning("Invalid <System Audio Mode Status> message:" + message); - return false; + HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message); + // Ignore this message. + return true; } setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message), true); return true; @@ -973,7 +995,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { int recorderAddress = message.getSource(); byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress); - startOneTouchRecord(recorderAddress, recordSource); + int reason = startOneTouchRecord(recorderAddress, recordSource); + if (reason != Constants.ABORT_NO_ERROR) { + mService.maySendFeatureAbortCommand(message, reason); + } return true; } @@ -998,7 +1023,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } private boolean isMessageForSystemAudio(HdmiCecMessage message) { - return message.getSource() == Constants.ADDR_AUDIO_SYSTEM + return mService.isControlEnabled() + && message.getSource() == Constants.ADDR_AUDIO_SYSTEM && (message.getDestination() == Constants.ADDR_TV || message.getDestination() == Constants.ADDR_BROADCAST) && getAvrDeviceInfo() != null; @@ -1445,31 +1471,38 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mService.displayOsd(messageId); } + @ServiceThreadOnly + void displayOsd(int messageId, int extra) { + assertRunOnServiceThread(); + mService.displayOsd(messageId, extra); + } + // Seq #54 and #55 @ServiceThreadOnly - void startOneTouchRecord(int recorderAddress, byte[] recordSource) { + int startOneTouchRecord(int recorderAddress, byte[] recordSource) { assertRunOnServiceThread(); if (!mService.isControlEnabled()) { Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); announceOneTouchRecordResult(ONE_TOUCH_RECORD_CEC_DISABLED); - return; + return Constants.ABORT_NOT_IN_CORRECT_MODE; } if (!checkRecorder(recorderAddress)) { Slog.w(TAG, "Invalid recorder address:" + recorderAddress); announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); - return; + return Constants.ABORT_NOT_IN_CORRECT_MODE; } if (!checkRecordSource(recordSource)) { Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); announceOneTouchRecordResult(ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN); - return; + return Constants.ABORT_UNABLE_TO_DETERMINE; } addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource)); Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:" + Arrays.toString(recordSource)); + return Constants.ABORT_NO_ERROR; } @ServiceThreadOnly diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java index 8b345cf..0b3d9fb 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java @@ -51,7 +51,6 @@ public final class HdmiCecMessageValidator { } final SparseArray<ValidationInfo> mValidationInfo = new SparseArray<>(); - private final HdmiLogger mSpamSafeLogger = new HdmiLogger(TAG); public HdmiCecMessageValidator(HdmiControlService service) { mService = service; @@ -183,32 +182,32 @@ public final class HdmiCecMessageValidator { int opcode = message.getOpcode(); ValidationInfo info = mValidationInfo.get(opcode); if (info == null) { - mSpamSafeLogger.warning("No validation information for the message: " + message); + HdmiLogger.warning("No validation information for the message: " + message); return true; } // Check the source field. if (message.getSource() == Constants.ADDR_UNREGISTERED && (info.addressType & SRC_UNREGISTERED) == 0) { - mSpamSafeLogger.warning("Unexpected source: " + message); + HdmiLogger.warning("Unexpected source: " + message); return false; } // Check the destination field. if (message.getDestination() == Constants.ADDR_BROADCAST) { if ((info.addressType & DEST_BROADCAST) == 0) { - mSpamSafeLogger.warning("Unexpected broadcast message: " + message); + HdmiLogger.warning("Unexpected broadcast message: " + message); return false; } } else { // Direct addressing. if ((info.addressType & DEST_DIRECT) == 0) { - mSpamSafeLogger.warning("Unexpected direct message: " + message); + HdmiLogger.warning("Unexpected direct message: " + message); return false; } } // Check the parameter type. if (!info.parameterValidator.isValid(message.getParams())) { - mSpamSafeLogger.warning("Unexpected parameters: " + message); + HdmiLogger.warning("Unexpected parameters: " + message); return false; } return true; diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 9314cf8..5cd7c01 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -166,8 +166,6 @@ public final class HdmiControlService extends SystemService { // Type of logical devices hosted in the system. Stored in the unmodifiable list. private final List<Integer> mLocalDevices; - private final HdmiLogger mSpamSafeLogger = new HdmiLogger(TAG); - // List of records for hotplug event listener to handle the the caller killed in action. @GuardedBy("mLock") private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords = @@ -258,7 +256,7 @@ public final class HdmiControlService extends SystemService { private List<HdmiDeviceInfo> mMhlDevices; @Nullable - private HdmiMhlController mMhlController; + private HdmiMhlControllerStub mMhlController; // Last input port before switching to the MHL port. Should switch back to this port // when the mobile device sends the request one touch play with off. @@ -307,7 +305,7 @@ public final class HdmiControlService extends SystemService { Slog.i(TAG, "Device does not support HDMI-CEC."); } - mMhlController = HdmiMhlController.create(this); + mMhlController = HdmiMhlControllerStub.create(this); if (!mMhlController.isReady()) { Slog.i(TAG, "Device does not support MHL-control."); } @@ -656,7 +654,7 @@ public final class HdmiControlService extends SystemService { if (mMessageValidator.isValid(command)) { mCecController.sendCommand(command, callback); } else { - mSpamSafeLogger.error("Invalid message type:" + command); + HdmiLogger.error("Invalid message type:" + command); if (callback != null) { callback.onSendCompleted(Constants.SEND_RESULT_FAILURE); } @@ -705,7 +703,7 @@ public final class HdmiControlService extends SystemService { } if (message.getDestination() != Constants.ADDR_BROADCAST) { - mSpamSafeLogger.warning("Unhandled cec command:" + message); + HdmiLogger.warning("Unhandled cec command:" + message); } return false; } @@ -794,7 +792,6 @@ public final class HdmiControlService extends SystemService { // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing // volume change notification back to hdmi control service. audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, - AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME); } } @@ -2033,4 +2030,14 @@ public final class HdmiControlService extends SystemService { getContext().sendBroadcastAsUser(intent, UserHandle.ALL, HdmiControlService.PERMISSION); } + + @ServiceThreadOnly + void displayOsd(int messageId, int extra) { + assertRunOnServiceThread(); + Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); + intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); + intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRAM_PARAM1, extra); + getContext().sendBroadcastAsUser(intent, UserHandle.ALL, + HdmiControlService.PERMISSION); + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiLogger.java b/services/core/java/com/android/server/hdmi/HdmiLogger.java index c7add75..2562ffc 100644 --- a/services/core/java/com/android/server/hdmi/HdmiLogger.java +++ b/services/core/java/com/android/server/hdmi/HdmiLogger.java @@ -26,44 +26,83 @@ import java.util.HashMap; /** * A logger that prevents spammy log. For the same log message, it logs once every 20seconds. * This class is not thread-safe. + * <p> + * For convenience, use single character prefix for all messages. + * Here are common acronyms + * <ul> + * <li>[T]: Timout + * <li>[R]: Received message + * <li>[S]: Sent message + * <li>[P]: Device polling result + * </ul> */ final class HdmiLogger { + private static final String TAG = "HDMI"; // Logging duration for same error message. private static final long ERROR_LOG_DURATTION_MILLIS = 20 * 1000; // 20s private static final boolean DEBUG = false; + private static final ThreadLocal<HdmiLogger> sLogger = new ThreadLocal<>(); + // Key (String): log message. // Value (Pair(Long, Integer)): a pair of last log time millis and the number of logMessage. // Cache for warning. private final HashMap<String, Pair<Long, Integer>> mWarningTimingCache = new HashMap<>(); // Cache for error. private final HashMap<String, Pair<Long, Integer>> mErrorTimingCache = new HashMap<>(); - private final String mTag; - HdmiLogger(String tag) { - mTag = "HDMI:" + tag; + private HdmiLogger() { + } + + static final void warning(String logMessage, Object... objs) { + getLogger().warningInternal(toLogString(logMessage, objs)); } - void warning(String logMessage) { + private void warningInternal(String logMessage) { String log = updateLog(mWarningTimingCache, logMessage); if (!log.isEmpty()) { - Slog.w(mTag, log); + Slog.w(TAG, log); } } - void error(String logMessage) { + static final void error(String logMessage, Object... objs) { + getLogger().errorInternal(toLogString(logMessage, objs)); + } + + private void errorInternal(String logMessage) { String log = updateLog(mErrorTimingCache, logMessage); if (!log.isEmpty()) { - Slog.e(mTag, log); + Slog.e(TAG, log); } } - void debug(String logMessage) { + static final void debug(String logMessage, Object... objs) { + getLogger().debugInternal(toLogString(logMessage, objs)); + } + + private void debugInternal(String logMessage) { if (!DEBUG) { return; } - Slog.d(mTag, logMessage); + Slog.d(TAG, logMessage); + } + + private static final String toLogString(String logMessage, Object[] objs) { + if (objs.length > 0) { + return String.format(logMessage, objs); + } else { + return logMessage; + } + } + + private static HdmiLogger getLogger() { + HdmiLogger logger = sLogger.get(); + if (logger == null) { + logger = new HdmiLogger(); + sLogger.set(logger); + } + return logger; } private static String updateLog(HashMap<String, Pair<Long, Integer>> cache, String logMessage) { diff --git a/services/core/java/com/android/server/hdmi/HdmiMhlControllerStub.java b/services/core/java/com/android/server/hdmi/HdmiMhlControllerStub.java new file mode 100644 index 0000000..c27cf18 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/HdmiMhlControllerStub.java @@ -0,0 +1,149 @@ +/* + * 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.hdmi; + +import android.hardware.hdmi.HdmiPortInfo; +import android.util.SparseArray; + +import com.android.server.hdmi.HdmiControlService.SendMessageCallback; + +/** + * A handler class for MHL control command. It converts user's command into MHL command and pass it + * to MHL HAL layer. + * <p> + * It can be created only by {@link HdmiMhlControllerStub#create}. + */ +final class HdmiMhlControllerStub { + + private static final SparseArray<HdmiMhlLocalDevice> mLocalDevices = new SparseArray<>(); + private static final HdmiPortInfo[] EMPTY_PORT_INFO = new HdmiPortInfo[0]; + private static final int INVALID_MHL_VERSION = 0; + private static final int NO_SUPPORTED_FEATURES = 0; + private static final int INVALID_DEVICE_ROLES = 0; + + // Private constructor. Use HdmiMhlControllerStub.create(). + private HdmiMhlControllerStub(HdmiControlService service) { + } + + // Returns true if MHL controller is initialized and ready to use. + boolean isReady() { + return false; + } + + static HdmiMhlControllerStub create(HdmiControlService service) { + return new HdmiMhlControllerStub(service); + } + + HdmiPortInfo[] getPortInfos() { + return EMPTY_PORT_INFO; + } + + /** + * Return {@link HdmiMhlLocalDevice} matched with the given port id. + * + * @return null if has no matched port id + */ + HdmiMhlLocalDevice getLocalDevice(int portId) { + return null; + } + + /** + * Return {@link HdmiMhlLocalDevice} matched with the given device id. + * + * @return null if has no matched id + */ + HdmiMhlLocalDevice getLocalDeviceById(int deviceId) { + return null; + } + + SparseArray<HdmiMhlLocalDevice> getAllLocalDevices() { + return mLocalDevices; + } + + /** + * Remove a {@link HdmiMhlLocalDevice} matched with the given port id. + * + * @return removed {@link HdmiMhlLocalDevice}. Return null if no matched port id. + */ + HdmiMhlLocalDevice removeLocalDevice(int portId) { + return null; + } + + /** + * Add a new {@link HdmiMhlLocalDevice}. + * + * @return old {@link HdmiMhlLocalDevice} having same port id + */ + HdmiMhlLocalDevice addLocalDevice(HdmiMhlLocalDevice device) { + return null; + } + + void clearAllLocalDevices() { + } + + /** + * Send MHL MSC-Subcommand to the device connected to the given port. + */ + void sendSubcommand(int portId, HdmiMhlSubcommand command) { + } + + void sendSubcommand(final int portId, final HdmiMhlSubcommand command, + SendMessageCallback callback) { + } + + + void sendScratchpadCommand(int portId, int offset, int length, byte[] data) { + } + + void setOption(int flag, int value) { + } + + /** + * Get the MHL version supported by underlying hardware port of the given {@code portId}. + * MHL specification version 2.0 returns 0x20, 3.0 will return 0x30 respectively. + * The return value is stored in 'version'. Return INVALID_VERSION if MHL hardware layer + * is not ready. + */ + int getMhlVersion(int portId) { + return INVALID_MHL_VERSION; + } + + /** + * Get MHL version of a device which is connected to a port of the given {@code portId}. + * MHL specification version 2.0 returns 0x20, 3.0 will return 0x30 respectively. + * The return value is stored in 'version'. + */ + int getPeerMhlVersion(int portId) { + return INVALID_MHL_VERSION; + } + + /** + * Get the bit flags describing the features supported by the system. Refer to feature support + * flag register info in MHL specification. + */ + int getSupportedFeatures(int portId) { + return NO_SUPPORTED_FEATURES; + } + + /** + * Get the bit flags describing the roles which ECBUS device can play. Refer to the + * ECBUS_DEV_ROLES Register info MHL3.0 specification + */ + int getEcbusDeviceRoles(int portId) { + return INVALID_DEVICE_ROLES; + } +} diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java index 03fbb95..1e29fd6 100644 --- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java +++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java @@ -77,7 +77,7 @@ public class PowerStatusMonitorAction extends HdmiCecFeatureAction { // if no device exists for incoming message, hands it over to other actions. return false; } - int newStatus = cmd.getParams()[0]; + int newStatus = cmd.getParams()[0] & 0xFF; updatePowerStatus(sourceAddress, newStatus, true); return true; } diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java index 17c2d6c..3fb450f 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java @@ -85,6 +85,7 @@ abstract class RequestArcAction extends HdmiCecFeatureAction { if (mState != state || state != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE) { return; } + HdmiLogger.debug("[T]RequestArcAction."); disableArcTransmission(); finish(); } diff --git a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java index f25363d..d9e1f24 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java @@ -35,15 +35,15 @@ final class RequestArcInitiationAction extends RequestArcAction { @Override boolean start() { + mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE; + addTimer(mState, HdmiConfig.TIMEOUT_MS); + HdmiCecMessage command = HdmiCecMessageBuilder.buildRequestArcInitiation( getSourceAddress(), mAvrAddress); sendCommand(command, new HdmiControlService.SendMessageCallback() { @Override public void onSendCompleted(int error) { - if (error == Constants.SEND_RESULT_SUCCESS) { - mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE; - addTimer(mState, HdmiConfig.TIMEOUT_MS); - } else { + if (error != Constants.SEND_RESULT_SUCCESS) { // If failed to send <Request ARC Initiation>, start "Disabled" // ARC transmission action. disableArcTransmission(); diff --git a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java index 1491c72..f5a0115 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java @@ -35,15 +35,15 @@ final class RequestArcTerminationAction extends RequestArcAction { @Override boolean start() { + mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE; + addTimer(mState, HdmiConfig.TIMEOUT_MS); + HdmiCecMessage command = HdmiCecMessageBuilder.buildRequestArcTermination(getSourceAddress(), mAvrAddress); sendCommand(command, new HdmiControlService.SendMessageCallback() { @Override public void onSendCompleted(int error) { - if (error == Constants.SEND_RESULT_SUCCESS) { - mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE; - addTimer(mState, HdmiConfig.TIMEOUT_MS); - } else { + if (error != Constants.SEND_RESULT_SUCCESS) { // If failed to send <Request ARC Termination>, start "Disabled" ARC // transmission action. disableArcTransmission(); diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java index 30519f3..bffa854 100644 --- a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java +++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java @@ -53,6 +53,18 @@ final class SetArcTransmissionStateAction extends HdmiCecFeatureAction { @Override boolean start() { if (mEnabled) { + // Enable ARC status immediately after sending <Report Arc Initiated>. + // If AVR responds with <Feature Abort>, disable ARC status again. + // This is different from spec that says that turns ARC status to + // "Enabled" if <Report ARC Initiated> is acknowledged and no + // <Feature Abort> is received. + // But implemented this way to save the time having to wait for + // <Feature Abort>. + setArcStatus(true); + // If succeeds to send <Report ARC Initiated>, wait general timeout + // to check whether there is no <Feature Abort> for <Report ARC Initiated>. + mState = STATE_WAITING_TIMEOUT; + addTimer(mState, HdmiConfig.TIMEOUT_MS); sendReportArcInitiated(); } else { setArcStatus(false); @@ -67,23 +79,11 @@ final class SetArcTransmissionStateAction extends HdmiCecFeatureAction { sendCommand(command, new HdmiControlService.SendMessageCallback() { @Override public void onSendCompleted(int error) { - if (error == Constants.SEND_RESULT_SUCCESS) { - // Enable ARC status immediately after sending <Report Arc Initiated>. - // If AVR responds with <Feature Abort>, disable ARC status again. - // This is different from spec that says that turns ARC status to - // "Enabled" if <Report ARC Initiated> is acknowledged and no - // <Feature Abort> is received. - // But implemented this way to save the time having to wait for - // <Feature Abort>. - setArcStatus(true); - // If succeeds to send <Report ARC Initiated>, wait general timeout - // to check whether there is no <Feature Abort> for <Report ARC Initiated>. - mState = STATE_WAITING_TIMEOUT; - addTimer(mState, HdmiConfig.TIMEOUT_MS); - } else { + if (error != Constants.SEND_RESULT_SUCCESS) { // If fails to send <Report ARC Initiated>, disable ARC and // send <Report ARC Terminated> directly. setArcStatus(false); + HdmiLogger.debug("Failed to send <Report Arc Initiated>."); finish(); } } @@ -112,6 +112,7 @@ final class SetArcTransmissionStateAction extends HdmiCecFeatureAction { if (opcode == Constants.MESSAGE_FEATURE_ABORT) { int originalOpcode = cmd.getParams()[0] & 0xFF; if (originalOpcode == Constants.MESSAGE_REPORT_ARC_INITIATED) { + HdmiLogger.debug("Feature aborted for <Report Arc Initiated>"); setArcStatus(false); finish(); return true; diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java index d15ffb0..0871194 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java @@ -98,7 +98,7 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction { @Override public void onSendCompleted(int error) { if (error != Constants.SEND_RESULT_SUCCESS) { - DLOGGER.debug("Failed to send <System Audio Mode Request>:" + error); + HdmiLogger.debug("Failed to send <System Audio Mode Request>:" + error); setSystemAudioMode(false); finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); } @@ -111,7 +111,7 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction { private void handleSendSystemAudioModeRequestTimeout() { if (!mTargetAudioStatus // Don't retry for Off case. || mSendRetryCount++ >= MAX_SEND_RETRY_COUNT) { - DLOGGER.debug("[T]:wait for <Set System Audio Mode>."); + HdmiLogger.debug("[T]:wait for <Set System Audio Mode>."); setSystemAudioMode(false); finishWithCallback(HdmiControlManager.RESULT_TIMEOUT); return; @@ -130,7 +130,7 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction { if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT && (cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST) { - DLOGGER.debug("Failed to start system audio mode request."); + HdmiLogger.debug("Failed to start system audio mode request."); setSystemAudioMode(false); finishWithCallback(HdmiControlManager.RESULT_EXCEPTION); return true; @@ -145,7 +145,7 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction { startAudioStatusAction(); return true; } else { - DLOGGER.debug("Unexpected system audio mode request:" + receivedStatus); + HdmiLogger.debug("Unexpected system audio mode request:" + receivedStatus); // Unexpected response, consider the request is newly initiated by AVR. // To return 'false' will initiate new SystemAudioActionFromAvr by the control // service. diff --git a/services/core/java/com/android/server/hdmi/VolumeControlAction.java b/services/core/java/com/android/server/hdmi/VolumeControlAction.java index ddc267a..4338fc7 100644 --- a/services/core/java/com/android/server/hdmi/VolumeControlAction.java +++ b/services/core/java/com/android/server/hdmi/VolumeControlAction.java @@ -17,66 +17,35 @@ package com.android.server.hdmi; import static com.android.server.hdmi.Constants.IRT_MS; +import static com.android.server.hdmi.Constants.MESSAGE_FEATURE_ABORT; +import static com.android.server.hdmi.Constants.MESSAGE_REPORT_AUDIO_STATUS; +import static com.android.server.hdmi.Constants.MESSAGE_USER_CONTROL_PRESSED; -import com.android.internal.util.Preconditions; +import android.media.AudioManager; /** * Feature action that transmits volume change to Audio Receiver. * <p> - * This action is created when a user pressed volume up/down. However, Since Android only provides a - * listener for delta of some volume change, we will set a target volume, and check reported volume - * from Audio Receiver(AVR). If TV receives no <Report Audio Status> from AVR, this action - * will be finished in {@link #IRT_MS} * {@link #VOLUME_CHANGE_TIMEOUT_MAX_COUNT} (ms). + * This action is created when a user pressed volume up/down. However, Android only provides a + * listener for delta of some volume change instead of individual key event. Also it's hard to know + * Audio Receiver's number of volume steps for a single volume control key. Because of this, it + * sends key-down event until IRT timeout happens, and it will send key-up event if no additional + * volume change happens; otherwise, it will send again key-down as press and hold feature does. */ final class VolumeControlAction extends HdmiCecFeatureAction { private static final String TAG = "VolumeControlAction"; - private static final int VOLUME_MUTE = 101; - private static final int VOLUME_RESTORE = 102; + // State that wait for next volume press. + private static final int STATE_WAIT_FOR_NEXT_VOLUME_PRESS = 1; private static final int MAX_VOLUME = 100; - private static final int MIN_VOLUME = 0; - // State where to wait for <Report Audio Status> - private static final int STATE_WAIT_FOR_REPORT_VOLUME_STATUS = 1; - - // Maximum count of time out used to finish volume action. - private static final int VOLUME_CHANGE_TIMEOUT_MAX_COUNT = 2; + private static final int UNKNOWN_AVR_VOLUME = -1; private final int mAvrAddress; - private final int mTargetVolume; - private final boolean mIsVolumeUp; - private int mTimeoutCount; - - /** - * Create a {@link VolumeControlAction} for mute/restore change - * - * @param source source device sending volume change - * @param avrAddress address of audio receiver - * @param mute whether to mute sound or not. {@code true} for mute on; {@code false} for mute - * off, i.e restore volume - * @return newly created {@link VolumeControlAction} - */ - public static VolumeControlAction ofMute(HdmiCecLocalDevice source, int avrAddress, - boolean mute) { - return new VolumeControlAction(source, avrAddress, mute ? VOLUME_MUTE : VOLUME_RESTORE, - false); - } - - /** - * Create a {@link VolumeControlAction} for volume up/down change - * - * @param source source device sending volume change - * @param avrAddress address of audio receiver - * @param targetVolume target volume to be set to AVR. It should be in range of [0-100] - * @param isVolumeUp whether to volume up or not. {@code true} for volume up; {@code false} for - * volume down - * @return newly created {@link VolumeControlAction} - */ - public static VolumeControlAction ofVolumeChange(HdmiCecLocalDevice source, int avrAddress, - int targetVolume, boolean isVolumeUp) { - Preconditions.checkArgumentInRange(targetVolume, MIN_VOLUME, MAX_VOLUME, "volume"); - return new VolumeControlAction(source, avrAddress, targetVolume, isVolumeUp); - } + private boolean mIsVolumeUp; + private long mLastKeyUpdateTime; + private int mLastAvrVolume; + private boolean mSentKeyPressed; /** * Scale a custom volume value to cec volume scale. @@ -94,123 +63,141 @@ final class VolumeControlAction extends HdmiCecFeatureAction { * * @param cecVolume volume value in cec volume scale. It should be in a range of [0-100] * @param scale scale of custom volume (max volume) - * @return a volume value scaled to custom volume range + * @return a volume scaled to custom volume range */ public static int scaleToCustomVolume(int cecVolume, int scale) { return (cecVolume * scale) / MAX_VOLUME; } - private VolumeControlAction(HdmiCecLocalDevice source, int avrAddress, int targetVolume, - boolean isVolumeUp) { + VolumeControlAction(HdmiCecLocalDevice source, int avrAddress, boolean isVolumeUp) { super(source); - mAvrAddress = avrAddress; - mTargetVolume = targetVolume; mIsVolumeUp = isVolumeUp; + mLastAvrVolume = UNKNOWN_AVR_VOLUME; + mSentKeyPressed = false; + + updateLastKeyUpdateTime(); + } + + private void updateLastKeyUpdateTime() { + mLastKeyUpdateTime = System.currentTimeMillis(); } @Override boolean start() { - if (isForMute()) { - sendMuteChange(mTargetVolume == VOLUME_MUTE); - finish(); - return true; - } - - startVolumeChange(); + mState = STATE_WAIT_FOR_NEXT_VOLUME_PRESS; + sendVolumeKeyPressed(); + resetTimer(); return true; } - - private boolean isForMute() { - return mTargetVolume == VOLUME_MUTE || mTargetVolume == VOLUME_RESTORE; + private void sendVolumeKeyPressed() { + sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(), mAvrAddress, + mIsVolumeUp ? HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP + : HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN)); + mSentKeyPressed = true; } - private void startVolumeChange() { - mTimeoutCount = 0; - sendVolumeChange(mIsVolumeUp); - mState = STATE_WAIT_FOR_REPORT_VOLUME_STATUS; - addTimer(mState, IRT_MS); + private void resetTimer() { + mActionTimer.clearTimerMessage(); + addTimer(STATE_WAIT_FOR_NEXT_VOLUME_PRESS, IRT_MS); } - private void sendVolumeChange(boolean up) { - sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(), mAvrAddress, - up ? HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP - : HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN)); + void handleVolumeChange(boolean isVolumeUp) { + if (mIsVolumeUp != isVolumeUp) { + HdmiLogger.debug("Volume Key Status Changed[old:%b new:%b]", mIsVolumeUp, isVolumeUp); + sendVolumeKeyReleased(); + mIsVolumeUp = isVolumeUp; + } + updateLastKeyUpdateTime(); } - private void sendMuteChange(boolean mute) { - sendUserControlPressedAndReleased(mAvrAddress, - mute ? HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION : - HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION); + private void sendVolumeKeyReleased() { + sendCommand(HdmiCecMessageBuilder.buildUserControlReleased( + getSourceAddress(), mAvrAddress)); + mSentKeyPressed = false; } @Override boolean processCommand(HdmiCecMessage cmd) { - if (mState != STATE_WAIT_FOR_REPORT_VOLUME_STATUS) { + if (mState != STATE_WAIT_FOR_NEXT_VOLUME_PRESS || cmd.getSource() != mAvrAddress) { return false; } switch (cmd.getOpcode()) { - case Constants.MESSAGE_REPORT_AUDIO_STATUS: - handleReportAudioStatus(cmd); - return true; - case Constants.MESSAGE_FEATURE_ABORT: - int originalOpcode = cmd.getParams()[0] & 0xFF; - if (originalOpcode == Constants.MESSAGE_USER_CONTROL_PRESSED - || originalOpcode == Constants.MESSAGE_USER_CONTROL_RELEASED) { - // TODO: handle feature abort. - finish(); - return true; - } - default: // fall through + case MESSAGE_REPORT_AUDIO_STATUS: + return handleReportAudioStatus(cmd); + case MESSAGE_FEATURE_ABORT: + return handleFeatureAbort(cmd); + default: return false; } } - private void handleReportAudioStatus(HdmiCecMessage cmd) { - byte[] params = cmd.getParams(); + private boolean handleReportAudioStatus(HdmiCecMessage cmd) { + byte params[] = cmd.getParams(); + boolean mute = (params[0] & 0x80) == 0x80; int volume = params[0] & 0x7F; - // Update volume with new value. - // Note that it will affect system volume change. - tv().setAudioStatus(false, volume); + mLastAvrVolume = volume; + if (shouldUpdateAudioVolume(mute)) { + HdmiLogger.debug("Force volume change[mute:%b, volume=%d]", mute, volume); + tv().setAudioStatus(mute, volume); + } + return true; + } + + private boolean shouldUpdateAudioVolume(boolean mute) { + // Do nothing if in mute. + if (mute) { + return true; + } + + // Update audio status if current volume position is edge of volume bar, + // i.e max or min volume. + AudioManager audioManager = tv().getService().getAudioManager(); + int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); if (mIsVolumeUp) { - if (mTargetVolume <= volume) { - finishWithVolumeChangeRelease(); - return; - } + int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + return currentVolume == maxVolume; } else { - if (mTargetVolume >= volume) { - finishWithVolumeChangeRelease(); - return; - } + return currentVolume == 0; } + } - // Clear action status and send another volume change command. - clear(); - startVolumeChange(); + private boolean handleFeatureAbort(HdmiCecMessage cmd) { + int originalOpcode = cmd.getParams()[0] & 0xFF; + // Since it sends <User Control Released> only when it finishes this action, + // it takes care of <User Control Pressed> only here. + if (originalOpcode == MESSAGE_USER_CONTROL_PRESSED) { + finish(); + return true; + } + return false; } - private void finishWithVolumeChangeRelease() { - sendCommand(HdmiCecMessageBuilder.buildUserControlReleased( - getSourceAddress(), mAvrAddress)); - finish(); + @Override + protected void clear() { + super.clear(); + if (mSentKeyPressed) { + sendVolumeKeyReleased(); + } + if (mLastAvrVolume != UNKNOWN_AVR_VOLUME) { + tv().setAudioStatus(false, mLastAvrVolume); + mLastAvrVolume = UNKNOWN_AVR_VOLUME; + } } @Override void handleTimerEvent(int state) { - if (mState != STATE_WAIT_FOR_REPORT_VOLUME_STATUS) { + if (state != STATE_WAIT_FOR_NEXT_VOLUME_PRESS) { return; } - // If no report volume action after IRT * VOLUME_CHANGE_TIMEOUT_MAX_COUNT just stop volume - // action. - if (++mTimeoutCount == VOLUME_CHANGE_TIMEOUT_MAX_COUNT) { - finishWithVolumeChangeRelease(); - return; + if (System.currentTimeMillis() - mLastKeyUpdateTime >= IRT_MS) { + finish(); + } else { + sendVolumeKeyPressed(); + resetTimer(); } - - sendVolumeChange(mIsVolumeUp); - addTimer(mState, IRT_MS); } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 93dceff..9c567ac 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -241,6 +241,9 @@ public class InputManagerService extends IInputManager.Stub /** Switch code: Headphone/Microphone Jack. When set, something is inserted. */ public static final int SW_JACK_PHYSICAL_INSERT = 0x07; + /** Switch code: Camera lens cover. When set the lens is covered. */ + public static final int SW_CAMERA_LENS_COVER = 0x09; + public static final int SW_LID_BIT = 1 << SW_LID; public static final int SW_KEYPAD_SLIDE_BIT = 1 << SW_KEYPAD_SLIDE; public static final int SW_HEADPHONE_INSERT_BIT = 1 << SW_HEADPHONE_INSERT; @@ -249,6 +252,7 @@ public class InputManagerService extends IInputManager.Stub public static final int SW_JACK_PHYSICAL_INSERT_BIT = 1 << SW_JACK_PHYSICAL_INSERT; public static final int SW_JACK_BITS = SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_JACK_PHYSICAL_INSERT_BIT | SW_LINEOUT_INSERT_BIT; + public static final int SW_CAMERA_LENS_COVER_BIT = 1 << SW_CAMERA_LENS_COVER; /** Whether to use the dev/input/event or uevent subsystem for the audio jack. */ final boolean mUseDevInputEventForAudioJack; @@ -693,7 +697,9 @@ public class InputManagerService extends IInputManager.Stub synchronized (mDataStore) { for (int i = 0; i < numFullKeyboards; i++) { final InputDevice inputDevice = mTempFullKeyboards.get(i); - if (mDataStore.getCurrentKeyboardLayout(inputDevice.getDescriptor()) == null) { + final String layout = + getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier()); + if (layout == null) { missingLayoutForExternalKeyboard = true; if (i < numFullKeyboardsAdded) { missingLayoutForExternalKeyboardAdded = true; @@ -1380,6 +1386,11 @@ public class InputManagerService extends IInputManager.Stub mWindowManagerCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen); } + if ((switchMask & SW_CAMERA_LENS_COVER_BIT) != 0) { + final boolean lensCovered = ((switchValues & SW_CAMERA_LENS_COVER_BIT) == 0); + mWindowManagerCallbacks.notifyCameraLensCoverSwitchChanged(whenNanos, lensCovered); + } + if (mUseDevInputEventForAudioJack && (switchMask & SW_JACK_BITS) != 0) { mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues, switchMask); @@ -1420,8 +1431,8 @@ public class InputManagerService extends IInputManager.Stub } // Native callback. - private int interceptWakeMotionBeforeQueueing(long whenNanos, int policyFlags) { - return mWindowManagerCallbacks.interceptWakeMotionBeforeQueueing( + private int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) { + return mWindowManagerCallbacks.interceptMotionBeforeQueueingNonInteractive( whenNanos, policyFlags); } @@ -1575,6 +1586,8 @@ public class InputManagerService extends IInputManager.Stub public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen); + public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered); + public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle); public long notifyANR(InputApplicationHandle inputApplicationHandle, @@ -1582,7 +1595,7 @@ public class InputManagerService extends IInputManager.Stub public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags); - public int interceptWakeMotionBeforeQueueing(long whenNanos, int policyFlags); + public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags); public long interceptKeyBeforeDispatching(InputWindowHandle focus, KeyEvent event, int policyFlags); diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java index 2ef9828..11818d8 100644 --- a/services/core/java/com/android/server/location/GpsLocationProvider.java +++ b/services/core/java/com/android/server/location/GpsLocationProvider.java @@ -331,6 +331,7 @@ public class GpsLocationProvider implements LocationProviderInterface { private int mSuplServerPort; private String mC2KServerHost; private int mC2KServerPort; + private boolean mSuplEsEnabled = false; private final Context mContext; private final NtpTrustedTime mNtpTime; @@ -453,11 +454,10 @@ public class GpsLocationProvider implements LocationProviderInterface { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); + if (DEBUG) Log.d(TAG, "receive broadcast intent, action: " + action); if (action.equals(ALARM_WAKEUP)) { - if (DEBUG) Log.d(TAG, "ALARM_WAKEUP"); startNavigating(false); } else if (action.equals(ALARM_TIMEOUT)) { - if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT"); hibernate(); } else if (action.equals(Intents.DATA_SMS_RECEIVED_ACTION)) { checkSmsSuplInit(intent); @@ -494,6 +494,7 @@ public class GpsLocationProvider implements LocationProviderInterface { Log.d(TAG, "SIM STATE is ready, SIM MCC/MNC is " + mccMnc); synchronized (mLock) { reloadGpsProperties(context, mProperties); + mNIHandler.setSuplEsEnablement(mSuplEsEnabled); } } } @@ -571,6 +572,16 @@ public class GpsLocationProvider implements LocationProviderInterface { } catch (IOException ex) { Log.w(TAG, "failed to dump properties contents"); } + + // SUPL_ES configuration. + String suplESProperty = mProperties.getProperty("SUPL_ES"); + if (suplESProperty != null) { + try { + mSuplEsEnabled = (Integer.parseInt(suplESProperty) == 1); + } catch (NumberFormatException e) { + Log.e(TAG, "unable to parse SUPL_ES: " + suplESProperty); + } + } } private void loadPropertiesFromResource(Context context, @@ -612,7 +623,6 @@ public class GpsLocationProvider implements LocationProviderInterface { mContext = context; mNtpTime = NtpTrustedTime.getInstance(context); mILocationManager = ilocationManager; - mNIHandler = new GpsNetInitiatedHandler(context); mLocation.setExtras(mLocationExtras); @@ -639,6 +649,11 @@ public class GpsLocationProvider implements LocationProviderInterface { mProperties = new Properties(); reloadGpsProperties(mContext, mProperties); + // Create a GPS net-initiated handler. + mNIHandler = new GpsNetInitiatedHandler(context, + mNetInitiatedListener, + mSuplEsEnabled); + // construct handler, listen for events mHandler = new ProviderHandler(looper); listenForBroadcasts(); @@ -691,7 +706,6 @@ public class GpsLocationProvider implements LocationProviderInterface { intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(Intent.ACTION_SCREEN_ON); - intentFilter = new IntentFilter(); intentFilter.addAction(SIM_STATE_CHANGED); mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, mHandler); } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index b936130d..5097927 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -229,8 +229,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { * {@link AudioManager#ADJUST_SAME}. * * @param direction The direction to adjust volume in. + * @param flags Any of the flags from {@link AudioManager}. + * @param packageName The package that made the original volume request. + * @param uid The uid that made the original volume request. + * @param useSuggested True to use adjustSuggestedStreamVolume instead of + * adjustStreamVolume. */ - public void adjustVolume(int direction, int flags, String packageName, int uid) { + public void adjustVolume(int direction, int flags, String packageName, int uid, + boolean useSuggested) { if (isPlaybackActive(false) || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) { flags &= ~AudioManager.FLAG_PLAY_SOUND; } @@ -241,8 +247,13 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) { int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs); - mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags, packageName, - uid); + if (useSuggested) { + mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction, flags, + packageName, uid); + } else { + mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags, + packageName, uid); + } } else { if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) { // Nothing to do, the volume cannot be changed @@ -1020,7 +1031,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { - MediaSessionRecord.this.adjustVolume(direction, flags, packageName, uid); + MediaSessionRecord.this.adjustVolume(direction, flags, packageName, uid, false); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 0c6d46c..a12dd1c 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -814,7 +814,7 @@ public class MediaSessionService extends SystemService implements Monitor { } } else { session.adjustVolume(direction, flags, getContext().getPackageName(), - UserHandle.myUserId()); + UserHandle.myUserId(), true); if (session.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE && mRvc != null) { try { diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index 189131c..05ad1fe 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -545,8 +545,9 @@ public class ConditionProviders extends ManagedServices { setZenModeCondition(condition, "downtime"); } // exit downtime - if (!inDowntime && mode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS - && mDowntime.isDowntimeCondition(mExitCondition)) { + if (!inDowntime && mDowntime.isDowntimeCondition(mExitCondition) + && (mode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + || mode == Global.ZEN_MODE_NO_INTERRUPTIONS)) { mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "downtimeExit"); } } diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java index 317ebef..b71bad8 100644 --- a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java +++ b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java @@ -43,6 +43,7 @@ import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.Objects; +import java.util.TimeZone; /** Built-in zen condition provider for managing downtime */ public class DowntimeConditionProvider extends ConditionProviderService { @@ -275,6 +276,9 @@ public class DowntimeConditionProvider extends ConditionProviderService { final long schTime = intent.getLongExtra(EXTRA_TIME, 0); if (DEBUG) Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s", action, ts(schTime), ts(now), now - schTime)); + } else if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) { + if (DEBUG) Slog.d(TAG, "timezone changed to " + TimeZone.getDefault()); + mCalendar.setTimeZone(TimeZone.getDefault()); } else { if (DEBUG) Slog.d(TAG, action + " fired at " + now); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index d0f4054..c09eff8 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -49,6 +49,7 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.media.AudioAttributes; import android.media.AudioManager; +import android.media.AudioSystem; import android.media.IRingtonePlayer; import android.net.Uri; import android.os.Binder; @@ -69,6 +70,7 @@ import android.service.notification.Condition; import android.service.notification.IConditionListener; import android.service.notification.IConditionProvider; import android.service.notification.INotificationListener; +import android.service.notification.IStatusBarNotificationHolder; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationRankingUpdate; import android.service.notification.StatusBarNotification; @@ -1126,6 +1128,19 @@ public class NotificationManagerService extends SystemService { return mRankingHelper.getPackagePriority(pkg, uid); } + @Override + public void setPackageVisibilityOverride(String pkg, int uid, int visibility) { + checkCallerIsSystem(); + mRankingHelper.setPackageVisibilityOverride(pkg, uid, visibility); + savePolicyFile(); + } + + @Override + public int getPackageVisibilityOverride(String pkg, int uid) { + checkCallerIsSystem(); + return mRankingHelper.getPackageVisibilityOverride(pkg, uid); + } + /** * System-only API for getting a list of current (i.e. not cleared) notifications. * @@ -1457,7 +1472,14 @@ public class NotificationManagerService extends SystemService { @Override public boolean matchesCallFilter(Bundle extras) { enforceSystemOrSystemUI("INotificationManager.matchesCallFilter"); - return mZenModeHelper.matchesCallFilter(extras, + return matchesCallFilterAsUser(extras, Binder.getCallingUid()); + } + + @Override + public boolean matchesCallFilterAsUser(Bundle extras, int userId) { + enforceSystemOrSystemUI("INotificationManager.matchesCallFilter"); + UserHandle userHandle = new UserHandle(userId); + return mZenModeHelper.matchesCallFilter(userHandle, extras, mRankingHelper.findExtractor(ValidateNotificationPeople.class)); } }; @@ -1924,14 +1946,19 @@ public class NotificationManagerService extends SystemService { } private static AudioAttributes audioAttributesForNotification(Notification n) { - if (n.audioAttributes != null - && !Notification.AUDIO_ATTRIBUTES_DEFAULT.equals(n.audioAttributes)) { + if (n.audioAttributes != null) { return n.audioAttributes; + } else if (n.audioStreamType >= 0 && n.audioStreamType < AudioSystem.getNumStreamTypes()) { + // the stream type is valid, use it + return new AudioAttributes.Builder() + .setInternalLegacyStreamType(n.audioStreamType) + .build(); + } else if (n.audioStreamType == AudioSystem.STREAM_DEFAULT) { + return Notification.AUDIO_ATTRIBUTES_DEFAULT; + } else { + Log.w(TAG, String.format("Invalid stream type: %d", n.audioStreamType)); + return Notification.AUDIO_ATTRIBUTES_DEFAULT; } - return new AudioAttributes.Builder() - .setLegacyStreamType(n.audioStreamType) - .setUsage(AudioAttributes.usageForLegacyStreamType(n.audioStreamType)) - .build(); } void showNextToastLocked() { @@ -2045,12 +2072,15 @@ public class NotificationManagerService extends SystemService { } int indexBefore = findNotificationRecordIndexLocked(record); boolean interceptBefore = record.isIntercepted(); + int visibilityBefore = record.getPackageVisibilityOverride(); recon.applyChangesLocked(record); applyZenModeLocked(record); mRankingHelper.sort(mNotificationList); int indexAfter = findNotificationRecordIndexLocked(record); boolean interceptAfter = record.isIntercepted(); - changed = indexBefore != indexAfter || interceptBefore != interceptAfter; + int visibilityAfter = record.getPackageVisibilityOverride(); + changed = indexBefore != indexAfter || interceptBefore != interceptAfter + || visibilityBefore != visibilityAfter; if (interceptBefore && !interceptAfter) { buzzBeepBlinkLocked(record); } @@ -2064,14 +2094,18 @@ public class NotificationManagerService extends SystemService { synchronized (mNotificationList) { final int N = mNotificationList.size(); ArrayList<String> orderBefore = new ArrayList<String>(N); + int[] visibilities = new int[N]; for (int i = 0; i < N; i++) { final NotificationRecord r = mNotificationList.get(i); orderBefore.add(r.getKey()); + visibilities[i] = r.getPackageVisibilityOverride(); mRankingHelper.extractSignals(r); } - mRankingHelper.sort(mNotificationList); for (int i = 0; i < N; i++) { - if (!orderBefore.get(i).equals(mNotificationList.get(i).getKey())) { + mRankingHelper.sort(mNotificationList); + final NotificationRecord r = mNotificationList.get(i); + if (!orderBefore.get(i).equals(r.getKey()) + || visibilities[i] != r.getPackageVisibilityOverride()) { scheduleSendRankingUpdate(); return; } @@ -2601,6 +2635,7 @@ public class NotificationManagerService extends SystemService { final int N = mNotificationList.size(); ArrayList<String> keys = new ArrayList<String>(N); ArrayList<String> interceptedKeys = new ArrayList<String>(N); + Bundle visibilityOverrides = new Bundle(); for (int i = 0; i < N; i++) { NotificationRecord record = mNotificationList.get(i); if (!isVisibleToListener(record.sbn, info)) { @@ -2610,14 +2645,28 @@ public class NotificationManagerService extends SystemService { if (record.isIntercepted()) { interceptedKeys.add(record.sbn.getKey()); } + if (record.getPackageVisibilityOverride() + != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) { + visibilityOverrides.putInt(record.sbn.getKey(), + record.getPackageVisibilityOverride()); + } + // Find first min-prio notification for speedbump placement. if (speedBumpIndex == -1 && + // Intrusiveness trumps priority, hence ignore intrusives. + !record.isRecentlyIntrusive() && + // Currently, package priority is either PRIORITY_DEFAULT or PRIORITY_MAX, so + // scanning for PRIORITY_MIN within the package bucket PRIORITY_DEFAULT + // (or lower as a safeguard) is sufficient to find the speedbump index. + // We'll have to revisit this when more package priority buckets are introduced. + record.getPackagePriority() <= Notification.PRIORITY_DEFAULT && record.sbn.getNotification().priority == Notification.PRIORITY_MIN) { speedBumpIndex = keys.size() - 1; } } String[] keysAr = keys.toArray(new String[keys.size()]); String[] interceptedKeysAr = interceptedKeys.toArray(new String[interceptedKeys.size()]); - return new NotificationRankingUpdate(keysAr, interceptedKeysAr, speedBumpIndex); + return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides, + speedBumpIndex); } private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) { @@ -2811,8 +2860,9 @@ public class NotificationManagerService extends SystemService { private void notifyPosted(final ManagedServiceInfo info, final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) { final INotificationListener listener = (INotificationListener)info.service; + StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn); try { - listener.onNotificationPosted(sbn, rankingUpdate); + listener.onNotificationPosted(sbnHolder, rankingUpdate); } catch (RemoteException ex) { Log.e(TAG, "unable to notify listener (posted): " + listener, ex); } @@ -2824,8 +2874,9 @@ public class NotificationManagerService extends SystemService { return; } final INotificationListener listener = (INotificationListener) info.service; + StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn); try { - listener.onNotificationRemoved(sbn, rankingUpdate); + listener.onNotificationRemoved(sbnHolder, rankingUpdate); } catch (RemoteException ex) { Log.e(TAG, "unable to notify listener (removed): " + listener, ex); } @@ -2913,4 +2964,25 @@ public class NotificationManagerService extends SystemService { return zen ? "zen" : ('\'' + pkgFilter + '\''); } } + + /** + * Wrapper for a StatusBarNotification object that allows transfer across a oneway + * binder without sending large amounts of data over a oneway transaction. + */ + private static final class StatusBarNotificationHolder + extends IStatusBarNotificationHolder.Stub { + private StatusBarNotification mValue; + + public StatusBarNotificationHolder(StatusBarNotification value) { + mValue = value; + } + + /** Get the held value and clear it. This function should only be called once per holder */ + @Override + public StatusBarNotification get() { + StatusBarNotification value = mValue; + mValue = null; + return value; + } + } } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index f2bd676..fd34aa5 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -21,6 +21,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.Bitmap; import android.media.AudioAttributes; +import android.os.UserHandle; import android.service.notification.StatusBarNotification; import com.android.internal.annotations.VisibleForTesting; @@ -65,6 +66,7 @@ public final class NotificationRecord { private int mAuthoritativeRank; private String mGlobalSortKey; + private int mPackageVisibility; @VisibleForTesting public NotificationRecord(StatusBarNotification sbn, int score) @@ -79,6 +81,7 @@ public final class NotificationRecord { mContactAffinity = previous.mContactAffinity; mRecentlyIntrusive = previous.mRecentlyIntrusive; mPackagePriority = previous.mPackagePriority; + mPackageVisibility = previous.mPackageVisibility; mIntercept = previous.mIntercept; mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs()); // Don't copy mGlobalSortKey, recompute it. @@ -86,8 +89,10 @@ public final class NotificationRecord { public Notification getNotification() { return sbn.getNotification(); } public int getFlags() { return sbn.getNotification().flags; } - public int getUserId() { return sbn.getUserId(); } + public UserHandle getUser() { return sbn.getUser(); } public String getKey() { return sbn.getKey(); } + /** @deprecated Use {@link #getUser()} instead. */ + public int getUserId() { return sbn.getUserId(); } void dump(PrintWriter pw, String prefix, Context baseContext) { final Notification notification = sbn.getNotification(); @@ -155,6 +160,7 @@ public final class NotificationRecord { pw.println(prefix + " mContactAffinity=" + mContactAffinity); pw.println(prefix + " mRecentlyIntrusive=" + mRecentlyIntrusive); pw.println(prefix + " mPackagePriority=" + mPackagePriority); + pw.println(prefix + " mPackageVisibility=" + mPackageVisibility); pw.println(prefix + " mIntercept=" + mIntercept); pw.println(prefix + " mGlobalSortKey=" + mGlobalSortKey); pw.println(prefix + " mRankingTimeMs=" + mRankingTimeMs); @@ -216,6 +222,14 @@ public final class NotificationRecord { return mPackagePriority; } + public void setPackageVisibilityOverride(int packageVisibility) { + mPackageVisibility = packageVisibility; + } + + public int getPackageVisibilityOverride() { + return mPackageVisibility; + } + public boolean setIntercepted(boolean intercept) { mIntercept = intercept; return mIntercept; diff --git a/services/core/java/com/android/server/notification/PackageVisibilityExtractor.java b/services/core/java/com/android/server/notification/PackageVisibilityExtractor.java new file mode 100644 index 0000000..f720f9f --- /dev/null +++ b/services/core/java/com/android/server/notification/PackageVisibilityExtractor.java @@ -0,0 +1,53 @@ +/* +* 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.notification; + +import android.content.Context; +import android.util.Slog; + +public class PackageVisibilityExtractor implements NotificationSignalExtractor { + private static final String TAG = "PackageVisibilityExtractor"; + private static final boolean DBG = false; + + private RankingConfig mConfig; + + public void initialize(Context ctx) { + if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + "."); + } + + public RankingReconsideration process(NotificationRecord record) { + if (record == null || record.getNotification() == null) { + if (DBG) Slog.d(TAG, "skipping empty notification"); + return null; + } + + if (mConfig == null) { + if (DBG) Slog.d(TAG, "missing config"); + return null; + } + + final int packageVisibility = mConfig.getPackageVisibilityOverride( + record.sbn.getPackageName(), record.sbn.getUid()); + record.setPackageVisibilityOverride(packageVisibility); + + return null; + } + + @Override + public void setConfig(RankingConfig config) { + mConfig = config; + } +} diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index 7d0bd59..aea137b 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -19,4 +19,8 @@ public interface RankingConfig { int getPackagePriority(String packageName, int uid); void setPackagePriority(String packageName, int uid, int priority); + + int getPackageVisibilityOverride(String packageName, int uid); + + void setPackageVisibilityOverride(String packageName, int uid, int visibility); } diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 435177b..aeddecb 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -20,8 +20,10 @@ import android.content.Context; import android.os.Handler; import android.os.Message; import android.os.UserHandle; +import android.service.notification.NotificationListenerService; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import android.util.Slog; import android.util.SparseIntArray; @@ -33,7 +35,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; +import java.util.Set; import java.util.concurrent.TimeUnit; public class RankingHelper implements RankingConfig { @@ -49,6 +51,7 @@ public class RankingHelper implements RankingConfig { private static final String ATT_NAME = "name"; private static final String ATT_UID = "uid"; private static final String ATT_PRIORITY = "priority"; + private static final String ATT_VISIBILITY = "visibility"; private final NotificationSignalExtractor[] mSignalExtractors; private final NotificationComparator mPreliminaryComparator = new NotificationComparator(); @@ -56,6 +59,7 @@ public class RankingHelper implements RankingConfig { // Package name to uid, to priority. Would be better as Table<String, Int, Int> private final ArrayMap<String, SparseIntArray> mPackagePriorities; + private final ArrayMap<String, SparseIntArray> mPackageVisibilities; private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp; private final Context mContext; @@ -65,6 +69,7 @@ public class RankingHelper implements RankingConfig { mContext = context; mRankingHandler = rankingHandler; mPackagePriorities = new ArrayMap<String, SparseIntArray>(); + mPackageVisibilities = new ArrayMap<String, SparseIntArray>(); final int N = extractorNames.length; mSignalExtractors = new NotificationSignalExtractor[N]; @@ -132,15 +137,27 @@ public class RankingHelper implements RankingConfig { if (TAG_PACKAGE.equals(tag)) { int uid = safeInt(parser, ATT_UID, UserHandle.USER_ALL); int priority = safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT); + int vis = safeInt(parser, ATT_VISIBILITY, + NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE); String name = parser.getAttributeValue(null, ATT_NAME); - if (!TextUtils.isEmpty(name) && priority != Notification.PRIORITY_DEFAULT) { - SparseIntArray priorityByUid = mPackagePriorities.get(name); - if (priorityByUid == null) { - priorityByUid = new SparseIntArray(); - mPackagePriorities.put(name, priorityByUid); + if (!TextUtils.isEmpty(name)) { + if (priority != Notification.PRIORITY_DEFAULT) { + SparseIntArray priorityByUid = mPackagePriorities.get(name); + if (priorityByUid == null) { + priorityByUid = new SparseIntArray(); + mPackagePriorities.put(name, priorityByUid); + } + priorityByUid.put(uid, priority); + } + if (vis != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) { + SparseIntArray visibilityByUid = mPackageVisibilities.get(name); + if (visibilityByUid == null) { + visibilityByUid = new SparseIntArray(); + mPackageVisibilities.put(name, visibilityByUid); + } + visibilityByUid.put(uid, vis); } - priorityByUid.put(uid, priority); } } } @@ -152,18 +169,43 @@ public class RankingHelper implements RankingConfig { out.startTag(null, TAG_RANKING); out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION)); - final int N = mPackagePriorities.size(); - for (int i = 0; i < N; i ++) { - String name = mPackagePriorities.keyAt(i); - SparseIntArray priorityByUid = mPackagePriorities.get(name); - final int M = priorityByUid.size(); - for (int j = 0; j < M; j++) { - int uid = priorityByUid.keyAt(j); - int priority = priorityByUid.get(uid); + final Set<String> packageNames = new ArraySet<>(mPackagePriorities.size() + + mPackageVisibilities.size()); + packageNames.addAll(mPackagePriorities.keySet()); + packageNames.addAll(mPackageVisibilities.keySet()); + final Set<Integer> packageUids = new ArraySet<>(); + for (String packageName : packageNames) { + packageUids.clear(); + SparseIntArray priorityByUid = mPackagePriorities.get(packageName); + SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName); + if (priorityByUid != null) { + final int M = priorityByUid.size(); + for (int j = 0; j < M; j++) { + packageUids.add(priorityByUid.keyAt(j)); + } + } + if (visibilityByUid != null) { + final int M = visibilityByUid.size(); + for (int j = 0; j < M; j++) { + packageUids.add(visibilityByUid.keyAt(j)); + } + } + for (Integer uid : packageUids) { out.startTag(null, TAG_PACKAGE); - out.attribute(null, ATT_NAME, name); + out.attribute(null, ATT_NAME, packageName); + if (priorityByUid != null) { + final int priority = priorityByUid.get(uid); + if (priority != Notification.PRIORITY_DEFAULT) { + out.attribute(null, ATT_PRIORITY, Integer.toString(priority)); + } + } + if (visibilityByUid != null) { + final int visibility = visibilityByUid.get(uid); + if (visibility != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) { + out.attribute(null, ATT_VISIBILITY, Integer.toString(visibility)); + } + } out.attribute(null, ATT_UID, Integer.toString(uid)); - out.attribute(null, ATT_PRIORITY, Integer.toString(priority)); out.endTag(null, TAG_PACKAGE); } } @@ -293,6 +335,31 @@ public class RankingHelper implements RankingConfig { updateConfig(); } + @Override + public int getPackageVisibilityOverride(String packageName, int uid) { + int visibility = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE; + SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName); + if (visibilityByUid != null) { + visibility = visibilityByUid.get(uid, + NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE); + } + return visibility; + } + + @Override + public void setPackageVisibilityOverride(String packageName, int uid, int visibility) { + if (visibility == getPackageVisibilityOverride(packageName, uid)) { + return; + } + SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName); + if (visibilityByUid == null) { + visibilityByUid = new SparseIntArray(); + mPackageVisibilities.put(packageName, visibilityByUid); + } + visibilityByUid.put(uid, visibility); + updateConfig(); + } + public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) { if (filter == null) { final int N = mSignalExtractors.length; diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java index aa47858..f266916 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -18,18 +18,23 @@ package com.android.server.notification; import android.app.Notification; import android.content.Context; +import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; +import android.os.UserHandle; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.provider.Settings; import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; import android.util.LruCache; import android.util.Slog; import java.util.ArrayList; import java.util.LinkedList; +import java.util.Map; /** * This {@link NotificationSignalExtractor} attempts to validate @@ -65,21 +70,88 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { static final float STARRED_CONTACT = 1f; protected boolean mEnabled; - private Context mContext; + private Context mBaseContext; // maps raw person handle to resolved person object private LruCache<String, LookupResult> mPeopleCache; + private Map<Integer, Context> mUserToContextMap; - private RankingReconsideration validatePeople(final NotificationRecord record) { + public void initialize(Context context) { + if (DEBUG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + "."); + mUserToContextMap = new ArrayMap<>(); + mBaseContext = context; + mPeopleCache = new LruCache<String, LookupResult>(PEOPLE_CACHE_SIZE); + mEnabled = ENABLE_PEOPLE_VALIDATOR && 1 == Settings.Global.getInt( + mBaseContext.getContentResolver(), SETTING_ENABLE_PEOPLE_VALIDATOR, 1); + } + + public RankingReconsideration process(NotificationRecord record) { + if (!mEnabled) { + if (INFO) Slog.i(TAG, "disabled"); + return null; + } + if (record == null || record.getNotification() == null) { + if (INFO) Slog.i(TAG, "skipping empty notification"); + return null; + } + if (record.getUserId() == UserHandle.USER_ALL) { + if (INFO) Slog.i(TAG, "skipping global notification"); + return null; + } + Context context = getContextAsUser(record.getUser()); + if (context == null) { + if (INFO) Slog.i(TAG, "skipping notification that lacks a context"); + return null; + } + return validatePeople(context, record); + } + + @Override + public void setConfig(RankingConfig config) { + // ignore: config has no relevant information yet. + } + + public float getContactAffinity(UserHandle userHandle, Bundle extras) { + if (extras == null) return NONE; + final String key = Long.toString(System.nanoTime()); + final float[] affinityOut = new float[1]; + Context context = getContextAsUser(userHandle); + if (context == null) { + return NONE; + } + final PeopleRankingReconsideration prr = validatePeople(context, key, extras, affinityOut); + float affinity = affinityOut[0]; + if (prr != null) { + prr.work(); + affinity = Math.max(prr.getContactAffinity(), affinity); + } + return affinity; + } + + private Context getContextAsUser(UserHandle userHandle) { + Context context = mUserToContextMap.get(userHandle.getIdentifier()); + if (context == null) { + try { + context = mBaseContext.createPackageContextAsUser("android", 0, userHandle); + mUserToContextMap.put(userHandle.getIdentifier(), context); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "failed to create package context for lookups", e); + } + } + return context; + } + + private RankingReconsideration validatePeople(Context context, + final NotificationRecord record) { final String key = record.getKey(); final Bundle extras = record.getNotification().extras; final float[] affinityOut = new float[1]; - final RankingReconsideration rr = validatePeople(key, extras, affinityOut); + final RankingReconsideration rr = validatePeople(context, key, extras, affinityOut); record.setContactAffinity(affinityOut[0]); return rr; } - private PeopleRankingReconsideration validatePeople(String key, Bundle extras, + private PeopleRankingReconsideration validatePeople(Context context, String key, Bundle extras, float[] affinityOut) { float affinity = NONE; if (extras == null) { @@ -98,7 +170,8 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { if (TextUtils.isEmpty(handle)) continue; synchronized (mPeopleCache) { - LookupResult lookupResult = mPeopleCache.get(handle); + final String cacheKey = getCacheKey(context.getUserId(), handle); + LookupResult lookupResult = mPeopleCache.get(cacheKey); if (lookupResult == null || lookupResult.isExpired()) { pendingLookups.add(handle); } else { @@ -119,7 +192,11 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } if (DEBUG) Slog.d(TAG, "Pending: future work scheduled for: " + key); - return new PeopleRankingReconsideration(key, pendingLookups); + return new PeopleRankingReconsideration(context, key, pendingLookups); + } + + private String getCacheKey(int userId, String handle) { + return Integer.toString(userId) + ":" + handle; } // VisibleForTesting @@ -185,24 +262,24 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { return null; } - private LookupResult resolvePhoneContact(final String number) { + private LookupResult resolvePhoneContact(Context context, final String number) { Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); - return searchContacts(phoneUri); + return searchContacts(context, phoneUri); } - private LookupResult resolveEmailContact(final String email) { + private LookupResult resolveEmailContact(Context context, final String email) { Uri numberUri = Uri.withAppendedPath( ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI, Uri.encode(email)); - return searchContacts(numberUri); + return searchContacts(context, numberUri); } - private LookupResult searchContacts(Uri lookupUri) { + private LookupResult searchContacts(Context context, Uri lookupUri) { LookupResult lookupResult = new LookupResult(); Cursor c = null; try { - c = mContext.getContentResolver().query(lookupUri, LOOKUP_PROJECTION, null, null, null); + c = context.getContentResolver().query(lookupUri, LOOKUP_PROJECTION, null, null, null); if (c != null && c.getCount() > 0) { c.moveToFirst(); lookupResult.readContact(c); @@ -217,44 +294,6 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { return lookupResult; } - public void initialize(Context context) { - if (DEBUG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + "."); - mContext = context; - mPeopleCache = new LruCache<String, LookupResult>(PEOPLE_CACHE_SIZE); - mEnabled = ENABLE_PEOPLE_VALIDATOR && 1 == Settings.Global.getInt( - mContext.getContentResolver(), SETTING_ENABLE_PEOPLE_VALIDATOR, 1); - } - - public RankingReconsideration process(NotificationRecord record) { - if (!mEnabled) { - if (INFO) Slog.i(TAG, "disabled"); - return null; - } - if (record == null || record.getNotification() == null) { - if (INFO) Slog.i(TAG, "skipping empty notification"); - return null; - } - return validatePeople(record); - } - - @Override - public void setConfig(RankingConfig config) { - // ignore: config has no relevant information yet. - } - - public float getContactAffinity(Bundle extras) { - if (extras == null) return NONE; - final String key = Long.toString(System.nanoTime()); - final float[] affinityOut = new float[1]; - final PeopleRankingReconsideration prr = validatePeople(key, extras, affinityOut); - float affinity = affinityOut[0]; - if (prr != null) { - prr.work(); - affinity = Math.max(prr.getContactAffinity(), affinity); - } - return affinity; - } - private static class LookupResult { private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000; // 1hr public static final int INVALID_ID = -1; @@ -317,11 +356,13 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { private class PeopleRankingReconsideration extends RankingReconsideration { private final LinkedList<String> mPendingLookups; + private final Context mContext; private float mContactAffinity = NONE; - private PeopleRankingReconsideration(String key, LinkedList<String> pendingLookups) { + private PeopleRankingReconsideration(Context context, String key, LinkedList<String> pendingLookups) { super(key); + mContext = context; mPendingLookups = pendingLookups; } @@ -333,20 +374,21 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { final Uri uri = Uri.parse(handle); if ("tel".equals(uri.getScheme())) { if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle); - lookupResult = resolvePhoneContact(uri.getSchemeSpecificPart()); + lookupResult = resolvePhoneContact(mContext, uri.getSchemeSpecificPart()); } else if ("mailto".equals(uri.getScheme())) { if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle); - lookupResult = resolveEmailContact(uri.getSchemeSpecificPart()); + lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart()); } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) { if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle); - lookupResult = searchContacts(uri); + lookupResult = searchContacts(mContext, uri); } else { lookupResult = new LookupResult(); // invalid person for the cache Slog.w(TAG, "unsupported URI " + handle); } if (lookupResult != null) { synchronized (mPeopleCache) { - mPeopleCache.put(handle, lookupResult); + final String cacheKey = getCacheKey(mContext.getUserId(), handle); + mPeopleCache.put(cacheKey, lookupResult); } mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity()); } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index fd35ede..41d7fa8 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -18,7 +18,6 @@ package com.android.server.notification; import static android.media.AudioAttributes.USAGE_ALARM; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; -import static android.media.AudioAttributes.USAGE_UNKNOWN; import android.app.AppOpsManager; import android.app.Notification; @@ -225,11 +224,6 @@ public class ZenModeHelper { muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, exceptionPackages); - // restrict vibrations with no hints - mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, USAGE_UNKNOWN, - zen ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, - exceptionPackages); - // alarm restrictions final boolean muteAlarms = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS; mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, USAGE_ALARM, @@ -307,8 +301,8 @@ public class ZenModeHelper { final int ringerMode = mAudioManager.getRingerMode(); int newZen = -1; if (ringerMode == AudioManager.RINGER_MODE_SILENT) { - if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS) { - newZen = Global.ZEN_MODE_NO_INTERRUPTIONS; + if (mZenMode == Global.ZEN_MODE_OFF) { + newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; } } else if ((ringerMode == AudioManager.RINGER_MODE_NORMAL || ringerMode == AudioManager.RINGER_MODE_VIBRATE) @@ -373,13 +367,14 @@ public class ZenModeHelper { return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record); } - public boolean matchesCallFilter(Bundle extras, ValidateNotificationPeople validator) { + public boolean matchesCallFilter(UserHandle userHandle, Bundle extras, + ValidateNotificationPeople validator) { final int zen = mZenMode; if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) { if (!mConfig.allowCalls) return false; // no calls get through if (validator != null) { - final float contactAffinity = validator.getContactAffinity(extras); + final float contactAffinity = validator.getContactAffinity(userHandle, extras); return audienceMatches(contactAffinity); } } diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 355f34f..a7eebf8 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -75,7 +75,7 @@ public class BackgroundDexOptService extends JobService { schedule(BackgroundDexOptService.this); return; } - pm.performDexOpt(pkg, null /* instruction set */, false); + pm.performDexOpt(pkg, null /* instruction set */, true); } // ran to completion, so we abandon our timeslice and do not reschedule jobFinished(jobParams, false); diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index b7b1c23..dcc4f8d 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -199,8 +199,7 @@ public class LauncherAppsService extends SystemService { mainIntent.setPackage(packageName); long ident = Binder.clearCallingIdentity(); try { - List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent, - PackageManager.NO_CROSS_PROFILE, // We only want the apps for this user + List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent, 0 /* flags */, user.getIdentifier()); return apps; } finally { @@ -288,8 +287,7 @@ public class LauncherAppsService extends SystemService { // as calling startActivityAsUser ignores the category and just // resolves based on the component if present. List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(launchIntent, - PackageManager.NO_CROSS_PROFILE, // We only want the apps for this user - user.getIdentifier()); + 0 /* flags */, user.getIdentifier()); final int size = apps.size(); for (int i = 0; i < size; ++i) { ActivityInfo activityInfo = apps.get(i).activityInfo; diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 3f7a607..496c136 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -42,23 +42,18 @@ import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; -import android.content.pm.ApplicationInfo; import android.content.pm.IPackageInstaller; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageInstallerSession; import android.content.pm.PackageInstaller; -import android.content.pm.PackageParser; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; -import android.content.pm.PackageParser.PackageLite; -import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Environment; -import android.os.Environment.UserEnvironment; import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; @@ -70,7 +65,6 @@ import android.os.RemoteException; import android.os.SELinux; import android.os.UserHandle; import android.os.UserManager; -import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; @@ -126,6 +120,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private static final String ATTR_CREATED_MILLIS = "createdMillis"; private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir"; private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid"; + private static final String ATTR_PREPARED = "prepared"; private static final String ATTR_SEALED = "sealed"; private static final String ATTR_MODE = "mode"; private static final String ATTR_INSTALL_FLAGS = "installFlags"; @@ -148,7 +143,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private final Context mContext; private final PackageManagerService mPm; private final AppOpsManager mAppOps; - private final StorageManager mStorage; private final File mStagingDir; private final HandlerThread mInstallThread; @@ -190,7 +184,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { mContext = context; mPm = pm; mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); - mStorage = StorageManager.from(mContext); mStagingDir = stagingDir; @@ -266,7 +259,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub { try { final int sessionId = allocateSessionIdLocked(); mLegacySessions.put(sessionId, true); - return prepareInternalStageDir(sessionId); + final File stageDir = buildInternalStageDir(sessionId); + prepareInternalStageDir(stageDir); + return stageDir; } catch (IllegalStateException e) { throw new IOException(e); } @@ -345,6 +340,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR); final File stageDir = (stageDirRaw != null) ? new File(stageDirRaw) : null; final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID); + final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true); final boolean sealed = readBooleanAttribute(in, ATTR_SEALED); final SessionParams params = new SessionParams( @@ -362,7 +358,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { return new PackageInstallerSession(mInternalCallback, mContext, mPm, mInstallThread.getLooper(), sessionId, userId, installerPackageName, params, - createdMillis, stageDir, stageCid, sealed); + createdMillis, stageDir, stageCid, prepared, sealed); } private void writeSessionsLocked() { @@ -410,6 +406,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { if (session.stageCid != null) { writeStringAttribute(out, ATTR_SESSION_STAGE_CID, session.stageCid); } + writeBooleanAttribute(out, ATTR_PREPARED, session.isPrepared()); writeBooleanAttribute(out, ATTR_SEALED, session.isSealed()); writeIntAttribute(out, ATTR_MODE, params.mode); @@ -483,14 +480,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } - // TODO: treat INHERIT_EXISTING as install for user - - // Figure out where we're going to be staging session data - final boolean stageInternal; - - if (params.mode == SessionParams.MODE_FULL_INSTALL) { - // Brand new install, use best resolved location. This also verifies - // that target has enough free space for the install. + if (params.mode == SessionParams.MODE_FULL_INSTALL + || params.mode == SessionParams.MODE_INHERIT_EXISTING) { + // Resolve best location for install, based on combination of + // requested install flags, delta size, and manifest settings. final long ident = Binder.clearCallingIdentity(); try { final int resolved = PackageHelper.resolveInstallLocation(mContext, @@ -498,46 +491,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub { params.installFlags); if (resolved == PackageHelper.RECOMMEND_INSTALL_INTERNAL) { - stageInternal = true; + params.setInstallFlagsInternal(); } else if (resolved == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) { - stageInternal = false; + params.setInstallFlagsExternal(); } else { throw new IOException("No storage with enough free space; res=" + resolved); } } finally { Binder.restoreCallingIdentity(ident); } - } else if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { - // Inheriting existing install, so stay on the same storage medium. - final ApplicationInfo existingApp = mPm.getApplicationInfo(params.appPackageName, 0, - userId); - if (existingApp == null) { - throw new IllegalStateException( - "Missing existing app " + params.appPackageName); - } - - final long existingSize; - try { - final PackageLite existingPkg = PackageParser.parsePackageLite( - new File(existingApp.getCodePath()), 0); - existingSize = PackageHelper.calculateInstalledSize(existingPkg, false, - params.abiOverride); - } catch (PackageParserException e) { - throw new IllegalStateException( - "Failed to calculate size of " + params.appPackageName); - } - - if ((existingApp.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0) { - // Internal we can link existing install into place, so we only - // need enough space for the new data. - checkInternalStorage(params.sizeBytes); - stageInternal = true; - } else { - // External we're going to copy existing install into our - // container, so we need footprint of both. - checkExternalStorage(params.sizeBytes + existingSize); - stageInternal = false; - } } else { throw new IllegalArgumentException("Invalid install mode: " + params.mode); } @@ -563,15 +525,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub { // We're staging to exactly one location File stageDir = null; String stageCid = null; - if (stageInternal) { - stageDir = prepareInternalStageDir(sessionId); + if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { + stageDir = buildInternalStageDir(sessionId); } else { - stageCid = prepareExternalStageCid(sessionId, params.sizeBytes); + stageCid = buildExternalStageCid(sessionId); } session = new PackageInstallerSession(mInternalCallback, mContext, mPm, mInstallThread.getLooper(), sessionId, userId, installerPackageName, params, - createdMillis, stageDir, stageCid, false); + createdMillis, stageDir, stageCid, false, false); mSessions.put(sessionId, session); } @@ -581,42 +543,50 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } @Override - public void abandonSession(int sessionId) { + public void updateSessionAppIcon(int sessionId, Bitmap appIcon) { synchronized (mSessions) { final PackageInstallerSession session = mSessions.get(sessionId); if (session == null || !isCallingUidOwner(session)) { throw new SecurityException("Caller has no access to session " + sessionId); } - session.abandon(); + session.params.appIcon = appIcon; + mInternalCallback.onSessionBadgingChanged(session); } } - private void checkInternalStorage(long sizeBytes) throws IOException { - if (sizeBytes <= 0) return; - - final File target = Environment.getDataDirectory(); - final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target); - - mPm.freeStorage(targetBytes); - if (target.getUsableSpace() < targetBytes) { - throw new IOException("Not enough internal space to write " + sizeBytes + " bytes"); + @Override + public void updateSessionAppLabel(int sessionId, String appLabel) { + synchronized (mSessions) { + final PackageInstallerSession session = mSessions.get(sessionId); + if (session == null || !isCallingUidOwner(session)) { + throw new SecurityException("Caller has no access to session " + sessionId); + } + session.params.appLabel = appLabel; + mInternalCallback.onSessionBadgingChanged(session); } } - private void checkExternalStorage(long sizeBytes) throws IOException { - if (sizeBytes <= 0) return; - - final File target = new UserEnvironment(UserHandle.USER_OWNER) - .getExternalStorageDirectory(); - final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target); - - if (target.getUsableSpace() < targetBytes) { - throw new IOException("Not enough external space to write " + sizeBytes + " bytes"); + @Override + public void abandonSession(int sessionId) { + synchronized (mSessions) { + final PackageInstallerSession session = mSessions.get(sessionId); + if (session == null || !isCallingUidOwner(session)) { + throw new SecurityException("Caller has no access to session " + sessionId); + } + session.abandon(); } } @Override public IPackageInstallerSession openSession(int sessionId) { + try { + return openSessionInternal(sessionId); + } catch (IOException e) { + throw ExceptionUtils.wrap(e); + } + } + + private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException { synchronized (mSessions) { final PackageInstallerSession session = mSessions.get(sessionId); if (session == null || !isCallingUidOwner(session)) { @@ -641,49 +611,43 @@ public class PackageInstallerService extends IPackageInstaller.Stub { throw new IllegalStateException("Failed to allocate session ID"); } - private File prepareInternalStageDir(int sessionId) throws IOException { - final File file = new File(mStagingDir, "vmdl" + sessionId + ".tmp"); + private File buildInternalStageDir(int sessionId) { + return new File(mStagingDir, "vmdl" + sessionId + ".tmp"); + } - if (file.exists()) { - throw new IOException("Session dir already exists: " + file); + static void prepareInternalStageDir(File stageDir) throws IOException { + if (stageDir.exists()) { + throw new IOException("Session dir already exists: " + stageDir); } try { - Os.mkdir(file.getAbsolutePath(), 0755); - Os.chmod(file.getAbsolutePath(), 0755); + Os.mkdir(stageDir.getAbsolutePath(), 0755); + Os.chmod(stageDir.getAbsolutePath(), 0755); } catch (ErrnoException e) { // This purposefully throws if directory already exists - throw new IOException("Failed to prepare session dir", e); + throw new IOException("Failed to prepare session dir: " + stageDir, e); } - if (!SELinux.restorecon(file)) { - throw new IOException("Failed to restorecon session dir"); + if (!SELinux.restorecon(stageDir)) { + throw new IOException("Failed to restorecon session dir: " + stageDir); } - - return file; } - private String prepareExternalStageCid(int sessionId, long sizeBytes) throws IOException { - if (sizeBytes <= 0) { - throw new IOException("Session must provide valid size for ASEC"); - } + private String buildExternalStageCid(int sessionId) { + return "smdl" + sessionId + ".tmp"; + } - final String cid = "smdl" + sessionId + ".tmp"; - if (PackageHelper.createSdDir(sizeBytes, cid, PackageManagerService.getEncryptKey(), + static void prepareExternalStageCid(String stageCid, long sizeBytes) throws IOException { + if (PackageHelper.createSdDir(sizeBytes, stageCid, PackageManagerService.getEncryptKey(), Process.SYSTEM_UID, true) == null) { - throw new IOException("Failed to create ASEC"); + throw new IOException("Failed to create session cid: " + stageCid); } - - return cid; } @Override public SessionInfo getSessionInfo(int sessionId) { synchronized (mSessions) { final PackageInstallerSession session = mSessions.get(sessionId); - if (!isCallingUidOwner(session)) { - enforceCallerCanReadSessions(); - } return session != null ? session.generateInfo() : null; } } @@ -691,7 +655,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { @Override public List<SessionInfo> getAllSessions(int userId) { mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getAllSessions"); - enforceCallerCanReadSessions(); final List<SessionInfo> result = new ArrayList<>(); synchronized (mSessions) { @@ -755,8 +718,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { @Override public void registerCallback(IPackageInstallerCallback callback, int userId) { mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerCallback"); - enforceCallerCanReadSessions(); - mCallbacks.register(callback, userId); } @@ -787,21 +748,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } - /** - * We allow those with permission, or the current home app. - */ - private void enforceCallerCanReadSessions() { - final boolean hasPermission = (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.READ_INSTALL_SESSIONS) - == PackageManager.PERMISSION_GRANTED); - final boolean isHomeApp = mPm.checkCallerIsHomeApp(); - if (hasPermission || isHomeApp) { - return; - } else { - throw new SecurityException("Caller must be current home app to read install sessions"); - } - } - static class PackageDeleteObserverAdapter extends PackageDeleteObserver { private final Context mContext; private final IntentSender mTarget; @@ -893,9 +839,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private static class Callbacks extends Handler { private static final int MSG_SESSION_CREATED = 1; - private static final int MSG_SESSION_OPENED = 2; - private static final int MSG_SESSION_PROGRESS_CHANGED = 3; - private static final int MSG_SESSION_CLOSED = 4; + private static final int MSG_SESSION_BADGING_CHANGED = 2; + private static final int MSG_SESSION_ACTIVE_CHANGED = 3; + private static final int MSG_SESSION_PROGRESS_CHANGED = 4; private static final int MSG_SESSION_FINISHED = 5; private final RemoteCallbackList<IPackageInstallerCallback> @@ -938,15 +884,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub { case MSG_SESSION_CREATED: callback.onSessionCreated(sessionId); break; - case MSG_SESSION_OPENED: - callback.onSessionOpened(sessionId); + case MSG_SESSION_BADGING_CHANGED: + callback.onSessionBadgingChanged(sessionId); + break; + case MSG_SESSION_ACTIVE_CHANGED: + callback.onSessionActiveChanged(sessionId, (boolean) msg.obj); break; case MSG_SESSION_PROGRESS_CHANGED: callback.onSessionProgressChanged(sessionId, (float) msg.obj); break; - case MSG_SESSION_CLOSED: - callback.onSessionClosed(sessionId); - break; case MSG_SESSION_FINISHED: callback.onSessionFinished(sessionId, (boolean) msg.obj); break; @@ -957,16 +903,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub { obtainMessage(MSG_SESSION_CREATED, sessionId, userId).sendToTarget(); } - private void notifySessionOpened(int sessionId, int userId) { - obtainMessage(MSG_SESSION_OPENED, sessionId, userId).sendToTarget(); + private void notifySessionBadgingChanged(int sessionId, int userId) { + obtainMessage(MSG_SESSION_BADGING_CHANGED, sessionId, userId).sendToTarget(); } - private void notifySessionProgressChanged(int sessionId, int userId, float progress) { - obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, userId, progress).sendToTarget(); + private void notifySessionActiveChanged(int sessionId, int userId, boolean active) { + obtainMessage(MSG_SESSION_ACTIVE_CHANGED, sessionId, userId, active).sendToTarget(); } - private void notifySessionClosed(int sessionId, int userId) { - obtainMessage(MSG_SESSION_CLOSED, sessionId, userId).sendToTarget(); + private void notifySessionProgressChanged(int sessionId, int userId, float progress) { + obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, userId, progress).sendToTarget(); } public void notifySessionFinished(int sessionId, int userId, boolean success) { @@ -1006,16 +952,17 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } class InternalCallback { - public void onSessionProgressChanged(PackageInstallerSession session, float progress) { - mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress); + public void onSessionBadgingChanged(PackageInstallerSession session) { + mCallbacks.notifySessionBadgingChanged(session.sessionId, session.userId); + writeSessionsAsync(); } - public void onSessionOpened(PackageInstallerSession session) { - mCallbacks.notifySessionOpened(session.sessionId, session.userId); + public void onSessionActiveChanged(PackageInstallerSession session, boolean active) { + mCallbacks.notifySessionActiveChanged(session.sessionId, session.userId, active); } - public void onSessionClosed(PackageInstallerSession session) { - mCallbacks.notifySessionClosed(session.sessionId, session.userId); + public void onSessionProgressChanged(PackageInstallerSession session, float progress) { + mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress); } public void onSessionFinished(PackageInstallerSession session, boolean success) { @@ -1027,6 +974,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub { writeSessionsAsync(); } + public void onSessionPrepared(PackageInstallerSession session) { + // We prepared the destination to write into; we want to persist + // this, but it's not critical enough to block for. + writeSessionsAsync(); + } + public void onSessionSealed(PackageInstallerSession session) { // It's very important that we block until we've recorded the // session as being sealed, since we never want to allow mutation diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 5264fc4..06f550d 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -17,15 +17,15 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED; -import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; -import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_WRONLY; +import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid; +import static com.android.server.pm.PackageInstallerService.prepareInternalStageDir; import android.content.Context; import android.content.Intent; @@ -88,8 +88,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // TODO: enforce INSTALL_ALLOW_TEST // TODO: enforce INSTALL_ALLOW_DOWNGRADE - // TODO: treat INHERIT_EXISTING as installExistingPackage() - private final PackageInstallerService.InternalCallback mCallback; private final Context mContext; private final PackageManagerService mPm; @@ -108,18 +106,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** Note that UID is not persisted; it's always derived at runtime. */ final int installerUid; - private final AtomicInteger mOpenCount = new AtomicInteger(); + private final AtomicInteger mActiveCount = new AtomicInteger(); private final Object mLock = new Object(); @GuardedBy("mLock") private float mClientProgress = 0; @GuardedBy("mLock") + private float mInternalProgress = 0; + + @GuardedBy("mLock") private float mProgress = 0; @GuardedBy("mLock") private float mReportedProgress = -1; @GuardedBy("mLock") + private boolean mPrepared = false; + @GuardedBy("mLock") private boolean mSealed = false; @GuardedBy("mLock") private boolean mPermissionsAccepted = false; @@ -184,7 +187,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { public PackageInstallerSession(PackageInstallerService.InternalCallback callback, Context context, PackageManagerService pm, Looper looper, int sessionId, int userId, String installerPackageName, SessionParams params, long createdMillis, - File stageDir, String stageCid, boolean sealed) { + File stageDir, String stageCid, boolean prepared, boolean sealed) { mCallback = callback; mContext = context; mPm = pm; @@ -203,6 +206,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { "Exactly one of stageDir or stageCid stage must be set"); } + mPrepared = prepared; mSealed = sealed; // Always derived at runtime @@ -214,8 +218,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } else { mPermissionsAccepted = false; } - - computeProgressLocked(); } public SessionInfo generateInfo() { @@ -227,7 +229,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mResolvedBaseFile.getAbsolutePath() : null; info.progress = mProgress; info.sealed = mSealed; - info.open = mOpenCount.get() > 0; + info.active = mActiveCount.get() > 0; info.mode = params.mode; info.sizeBytes = params.sizeBytes; @@ -238,14 +240,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return info; } + public boolean isPrepared() { + synchronized (mLock) { + return mPrepared; + } + } + public boolean isSealed() { synchronized (mLock) { return mSealed; } } - private void assertNotSealed(String cookie) { + private void assertPreparedAndNotSealed(String cookie) { synchronized (mLock) { + if (!mPrepared) { + throw new IllegalStateException(cookie + " before prepared"); + } if (mSealed) { throw new SecurityException(cookie + " not allowed after commit"); } @@ -278,28 +289,26 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void setClientProgress(float progress) { synchronized (mLock) { + // Always publish first staging movement + final boolean forcePublish = (mClientProgress == 0); mClientProgress = progress; - computeProgressLocked(); + computeProgressLocked(forcePublish); } - maybePublishProgress(); } @Override public void addClientProgress(float progress) { synchronized (mLock) { - mClientProgress += progress; - computeProgressLocked(); + setClientProgress(mClientProgress + progress); } - maybePublishProgress(); } - private void computeProgressLocked() { - mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f); - } + private void computeProgressLocked(boolean forcePublish) { + mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f) + + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f); - private void maybePublishProgress() { // Only publish when meaningful change - if (Math.abs(mProgress - mReportedProgress) > 0.01) { + if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) { mReportedProgress = mProgress; mCallback.onSessionProgressChanged(this, mProgress); } @@ -307,7 +316,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public String[] getNames() { - assertNotSealed("getNames"); + assertPreparedAndNotSealed("getNames"); try { return resolveStageDir().list(); } catch (IOException e) { @@ -331,7 +340,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // will block any attempted install transitions. final FileBridge bridge; synchronized (mLock) { - assertNotSealed("openWrite"); + assertPreparedAndNotSealed("openWrite"); bridge = new FileBridge(); mBridges.add(bridge); @@ -385,7 +394,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } private ParcelFileDescriptor openReadInternal(String name) throws IOException { - assertNotSealed("openRead"); + assertPreparedAndNotSealed("openRead"); try { if (!FileUtils.isValidExtFilename(name)) { @@ -405,6 +414,30 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { public void commit(IntentSender statusReceiver) { Preconditions.checkNotNull(statusReceiver); + synchronized (mLock) { + if (!mSealed) { + // Verify that all writers are hands-off + for (FileBridge bridge : mBridges) { + if (!bridge.isClosed()) { + throw new SecurityException("Files still open"); + } + } + + // Persist the fact that we've sealed ourselves to prevent + // mutations of any hard links we create. + mSealed = true; + mCallback.onSessionSealed(this); + } + } + + // Client staging is fully done at this point + mClientProgress = 1f; + computeProgressLocked(true); + + // This ongoing commit should keep session active, even though client + // will probably close their end. + mActiveCount.incrementAndGet(); + final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext, statusReceiver, sessionId); mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget(); @@ -412,22 +445,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private void commitLocked() throws PackageManagerException { if (mDestroyed) { - throw new PackageManagerException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session"); + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed"); } - - // Verify that all writers are hands-off if (!mSealed) { - for (FileBridge bridge : mBridges) { - if (!bridge.isClosed()) { - throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED, - "Files still open"); - } - } - mSealed = true; - - // Persist the fact that we've sealed ourselves to prevent mutations - // of any hard links we create below. - mCallback.onSessionSealed(this); + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed"); } try { @@ -456,6 +477,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mRemoteObserver.onUserActionRequired(intent); } catch (RemoteException ignored) { } + + // Commit was keeping session marked as active until now; release + // that extra refcount so session appears idle. + close(); return; } @@ -485,7 +510,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } // TODO: surface more granular state from dexopt - mCallback.onSessionProgressChanged(this, 0.9f); + mInternalProgress = 0.5f; + computeProgressLocked(true); // Unpack native libraries extractNativeLibraries(mResolvedStageDir, params.abiOverride); @@ -553,8 +579,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { try { apk = PackageParser.parseApkLite(file, PackageParser.PARSE_COLLECT_CERTIFICATES); } catch (PackageParserException e) { - throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, - "Failed to parse " + file + ": " + e); + throw PackageManagerException.from(e); } if (!stagedSplits.add(apk.splitName)) { @@ -620,8 +645,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { existingBase = PackageParser.parseApkLite(new File(app.getBaseCodePath()), PackageParser.PARSE_COLLECT_CERTIFICATES); } catch (PackageParserException e) { - throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, - "Failed to parse existing package " + app.getCodePath() + ": " + e); + throw PackageManagerException.from(e); } assertApkConsistent("Existing base", existingBase); @@ -673,8 +697,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { try { baseApk = PackageParser.parseApkLite(mResolvedBaseFile, 0); } catch (PackageParserException e) { - throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, - "Failed to parse base package " + mResolvedBaseFile + ": " + e); + throw PackageManagerException.from(e); } final List<String> splitPaths = new ArrayList<>(); @@ -828,16 +851,36 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - public void open() { - if (mOpenCount.getAndIncrement() == 0) { - mCallback.onSessionOpened(this); + public void open() throws IOException { + if (mActiveCount.getAndIncrement() == 0) { + mCallback.onSessionActiveChanged(this, true); + } + + synchronized (mLock) { + if (!mPrepared) { + if (stageDir != null) { + prepareInternalStageDir(stageDir); + } else if (stageCid != null) { + prepareExternalStageCid(stageCid, params.sizeBytes); + + // TODO: deliver more granular progress for ASEC allocation + mInternalProgress = 0.25f; + computeProgressLocked(true); + } else { + throw new IllegalArgumentException( + "Exactly one of stageDir or stageCid stage must be set"); + } + + mPrepared = true; + mCallback.onSessionPrepared(this); + } } } @Override public void close() { - if (mOpenCount.decrementAndGet() == 0) { - mCallback.onSessionClosed(this); + if (mActiveCount.decrementAndGet() == 0) { + mCallback.onSessionActiveChanged(this, false); } } @@ -866,6 +909,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { synchronized (mLock) { mSealed = true; mDestroyed = true; + + // Force shut down all bridges + for (FileBridge bridge : mBridges) { + bridge.forceClose(); + } } if (stageDir != null) { FileUtils.deleteContents(stageDir); diff --git a/services/core/java/com/android/server/pm/PackageManagerException.java b/services/core/java/com/android/server/pm/PackageManagerException.java index 0cbdcdc..a41636e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerException.java +++ b/services/core/java/com/android/server/pm/PackageManagerException.java @@ -16,6 +16,8 @@ package com.android.server.pm; +import android.content.pm.PackageParser.PackageParserException; + /** {@hide} */ public class PackageManagerException extends Exception { public final int error; @@ -29,4 +31,9 @@ public class PackageManagerException extends Exception { super(detailMessage, throwable); this.error = error; } + + public static PackageManagerException from(PackageParserException e) + throws PackageManagerException { + throw new PackageManagerException(e.error, e.getMessage(), e.getCause()); + } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 228c120..a5e84f6 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -312,6 +312,12 @@ public class PackageManagerService extends IPackageManager.Stub { final PackageHandler mHandler; + /** + * Messages for {@link #mHandler} that need to wait for system ready before + * being dispatched. + */ + private ArrayList<Message> mPostSystemReadyMessages; + final int mSdkVersion = Build.VERSION.SDK_INT; final Context mContext; @@ -446,9 +452,9 @@ public class PackageManagerService extends IPackageManager.Stub { /** Token for keys in mPendingVerification. */ private int mPendingVerificationToken = 0; - boolean mSystemReady; - boolean mSafeMode; - boolean mHasSystemUidErrors; + volatile boolean mSystemReady; + volatile boolean mSafeMode; + volatile boolean mHasSystemUidErrors; ApplicationInfo mAndroidApplication; final ActivityInfo mResolveActivity = new ActivityInfo(); @@ -3177,24 +3183,6 @@ public class PackageManagerService extends IPackageManager.Stub { if (matches.get(i).getTargetUserId() == targetUserId) return true; } } - ArrayList<String> packageNames = null; - SparseArray<ArrayList<String>> fromSource = - mSettings.mCrossProfilePackageInfo.get(sourceUserId); - if (fromSource != null) { - packageNames = fromSource.get(targetUserId); - if (packageNames != null) { - // We need the package name, so we try to resolve with the loosest flags possible - List<ResolveInfo> resolveInfos = mActivities.queryIntent(intent, resolvedType, - PackageManager.GET_UNINSTALLED_PACKAGES, targetUserId); - int count = resolveInfos.size(); - for (int i = 0; i < count; i++) { - ResolveInfo resolveInfo = resolveInfos.get(i); - if (packageNames.contains(resolveInfo.activityInfo.packageName)) { - return true; - } - } - } - } return false; } @@ -3234,32 +3222,21 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { final String pkgName = intent.getPackage(); - boolean queryCrossProfile = (flags & PackageManager.NO_CROSS_PROFILE) == 0; if (pkgName == null) { - ResolveInfo resolveInfo = null; - if (queryCrossProfile) { - // Check if the intent needs to be forwarded to another user for this package - ArrayList<ResolveInfo> crossProfileResult = - queryIntentActivitiesCrossProfilePackage( - intent, resolvedType, flags, userId); - if (!crossProfileResult.isEmpty()) { - // Skip the current profile - return crossProfileResult; - } - List<CrossProfileIntentFilter> matchingFilters = - getMatchingCrossProfileIntentFilters(intent, resolvedType, userId); - // Check for results that need to skip the current profile. - resolveInfo = querySkipCurrentProfileIntents(matchingFilters, intent, - resolvedType, flags, userId); - if (resolveInfo != null) { - List<ResolveInfo> result = new ArrayList<ResolveInfo>(1); - result.add(resolveInfo); - return result; - } - // Check for cross profile results. - resolveInfo = queryCrossProfileIntents( - matchingFilters, intent, resolvedType, flags, userId); + List<CrossProfileIntentFilter> matchingFilters = + getMatchingCrossProfileIntentFilters(intent, resolvedType, userId); + // Check for results that need to skip the current profile. + ResolveInfo resolveInfo = querySkipCurrentProfileIntents(matchingFilters, intent, + resolvedType, flags, userId); + if (resolveInfo != null) { + List<ResolveInfo> result = new ArrayList<ResolveInfo>(1); + result.add(resolveInfo); + return result; } + // Check for cross profile results. + resolveInfo = queryCrossProfileIntents( + matchingFilters, intent, resolvedType, flags, userId); + // Check for results in the current profile. List<ResolveInfo> result = mActivities.queryIntent( intent, resolvedType, flags, userId); @@ -3271,15 +3248,6 @@ public class PackageManagerService extends IPackageManager.Stub { } final PackageParser.Package pkg = mPackages.get(pkgName); if (pkg != null) { - if (queryCrossProfile) { - ArrayList<ResolveInfo> crossProfileResult = - queryIntentActivitiesCrossProfilePackage( - intent, resolvedType, flags, userId, pkg, pkgName); - if (!crossProfileResult.isEmpty()) { - // Skip the current profile - return crossProfileResult; - } - } return mActivities.queryIntentForPackage(intent, resolvedType, flags, pkg.activities, userId); } @@ -3308,56 +3276,6 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } - private ArrayList<ResolveInfo> queryIntentActivitiesCrossProfilePackage( - Intent intent, String resolvedType, int flags, int userId) { - ArrayList<ResolveInfo> matchingResolveInfos = new ArrayList<ResolveInfo>(); - SparseArray<ArrayList<String>> sourceForwardingInfo = - mSettings.mCrossProfilePackageInfo.get(userId); - if (sourceForwardingInfo != null) { - int NI = sourceForwardingInfo.size(); - for (int i = 0; i < NI; i++) { - int targetUserId = sourceForwardingInfo.keyAt(i); - ArrayList<String> packageNames = sourceForwardingInfo.valueAt(i); - List<ResolveInfo> resolveInfos = mActivities.queryIntent( - intent, resolvedType, flags, targetUserId); - int NJ = resolveInfos.size(); - for (int j = 0; j < NJ; j++) { - ResolveInfo resolveInfo = resolveInfos.get(j); - if (packageNames.contains(resolveInfo.activityInfo.packageName)) { - matchingResolveInfos.add(createForwardingResolveInfo( - resolveInfo.filter, userId, targetUserId)); - } - } - } - } - return matchingResolveInfos; - } - - private ArrayList<ResolveInfo> queryIntentActivitiesCrossProfilePackage( - Intent intent, String resolvedType, int flags, int userId, PackageParser.Package pkg, - String packageName) { - ArrayList<ResolveInfo> matchingResolveInfos = new ArrayList<ResolveInfo>(); - SparseArray<ArrayList<String>> sourceForwardingInfo = - mSettings.mCrossProfilePackageInfo.get(userId); - if (sourceForwardingInfo != null) { - int NI = sourceForwardingInfo.size(); - for (int i = 0; i < NI; i++) { - int targetUserId = sourceForwardingInfo.keyAt(i); - if (sourceForwardingInfo.valueAt(i).contains(packageName)) { - List<ResolveInfo> resolveInfos = mActivities.queryIntentForPackage( - intent, resolvedType, flags, pkg.activities, targetUserId); - int NJ = resolveInfos.size(); - for (int j = 0; j < NJ; j++) { - ResolveInfo resolveInfo = resolveInfos.get(j); - matchingResolveInfos.add(createForwardingResolveInfo( - resolveInfo.filter, userId, targetUserId)); - } - } - } - } - return matchingResolveInfos; - } - // Return matching ResolveInfo if any for skip current profile intent filters. private ResolveInfo queryCrossProfileIntents( List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType, @@ -3777,21 +3695,25 @@ public class PackageManagerService extends IPackageManager.Stub { } else { pi = generatePackageInfoFromSettingsLPw(ps.name, flags, userId); } - if ((flags&PackageManager.GET_PERMISSIONS) == 0) { - if (numMatch == permissions.length) { - pi.requestedPermissions = permissions; - } else { - pi.requestedPermissions = new String[numMatch]; - numMatch = 0; - for (int i=0; i<permissions.length; i++) { - if (tmp[i]) { - pi.requestedPermissions[numMatch] = permissions[i]; - numMatch++; + // The above might return null in cases of uninstalled apps or install-state + // skew across users/profiles. + if (pi != null) { + if ((flags&PackageManager.GET_PERMISSIONS) == 0) { + if (numMatch == permissions.length) { + pi.requestedPermissions = permissions; + } else { + pi.requestedPermissions = new String[numMatch]; + numMatch = 0; + for (int i=0; i<permissions.length; i++) { + if (tmp[i]) { + pi.requestedPermissions[numMatch] = permissions[i]; + numMatch++; + } } } } + list.add(pi); } - list.add(pi); } @Override @@ -4155,8 +4077,7 @@ public class PackageManagerService extends IPackageManager.Stub { pp.collectCertificates(pkg, parseFlags); pp.collectManifestDigest(pkg); } catch (PackageParserException e) { - throw new PackageManagerException(e.error, "Failed to collect certificates for " - + pkg.packageName + ": " + e.getMessage()); + throw PackageManagerException.from(e); } } @@ -4181,8 +4102,7 @@ public class PackageManagerService extends IPackageManager.Stub { try { pkg = pp.parsePackage(scanFile, parseFlags); } catch (PackageParserException e) { - throw new PackageManagerException(e.error, - "Failed to scan " + scanFile + ": " + e.getMessage()); + throw PackageManagerException.from(e); } PackageSetting ps = null; @@ -4512,7 +4432,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public boolean performDexOptIfNeeded(String packageName, String instructionSet) { - return performDexOpt(packageName, instructionSet, true); + return performDexOpt(packageName, instructionSet, false); } private static String getPrimaryInstructionSet(ApplicationInfo info) { @@ -7685,16 +7605,18 @@ public class PackageManagerService extends IPackageManager.Stub { } void schedulePackageCleaning(String packageName, int userId, boolean andCode) { - if (false) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.d(TAG, "Schedule cleaning " + packageName + " user=" + userId - + " andCode=" + andCode, here); + final Message msg = mHandler.obtainMessage(START_CLEANING_PACKAGE, + userId, andCode ? 1 : 0, packageName); + if (mSystemReady) { + msg.sendToTarget(); + } else { + if (mPostSystemReadyMessages == null) { + mPostSystemReadyMessages = new ArrayList<>(); + } + mPostSystemReadyMessages.add(msg); } - mHandler.sendMessage(mHandler.obtainMessage(START_CLEANING_PACKAGE, - userId, andCode ? 1 : 0, packageName)); } - + void startCleaningPackages() { // reader synchronized (mPackages) { @@ -10245,8 +10167,18 @@ public class PackageManagerService extends IPackageManager.Stub { if (bp != null) { // If the defining package is signed with our cert, it's okay. This // also includes the "updating the same package" case, of course. - if (compareSignatures(bp.packageSetting.signatures.mSignatures, - pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) { + // "updating same package" could also involve key-rotation. + final boolean sigsOk; + if (!bp.sourcePackage.equals(pkg.packageName) + || !(bp.packageSetting instanceof PackageSetting) + || !bp.packageSetting.keySetData.isUsingUpgradeKeySets() + || ((PackageSetting) bp.packageSetting).sharedUser != null) { + sigsOk = compareSignatures(bp.packageSetting.signatures.mSignatures, + pkg.mSignatures) == PackageManager.SIGNATURE_MATCH; + } else { + sigsOk = checkUpgradeKeySetLP((PackageSetting) bp.packageSetting, pkg); + } + if (!sigsOk) { // If the owning package is the system itself, we log but allow // install to proceed; we fail the install on all other permission // redefinitions. @@ -11642,24 +11574,6 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - public void addCrossProfileIntentsForPackage(String packageName, - int sourceUserId, int targetUserId) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); - mSettings.addCrossProfilePackage(packageName, sourceUserId, targetUserId); - mSettings.writePackageRestrictionsLPr(sourceUserId); - } - - @Override - public void removeCrossProfileIntentsForPackage(String packageName, - int sourceUserId, int targetUserId) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); - mSettings.removeCrossProfilePackage(packageName, sourceUserId, targetUserId); - mSettings.writePackageRestrictionsLPr(sourceUserId); - } - - @Override public void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage, int ownerUserId) { mContext.enforceCallingOrSelfPermission( @@ -11728,47 +11642,6 @@ public class PackageManagerService extends IPackageManager.Stub { preferred.activityInfo.name); } - /** - * Check if calling UID is the current home app. This handles both the case - * where the user has selected a specific home app, and where there is only - * one home app. - */ - public boolean checkCallerIsHomeApp() { - final Intent intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(Intent.CATEGORY_HOME); - - final int callingUid = Binder.getCallingUid(); - final int callingUserId = UserHandle.getCallingUserId(); - final List<ResolveInfo> allHomes = queryIntentActivities(intent, null, 0, callingUserId); - final ResolveInfo preferredHome = findPreferredActivity(intent, null, 0, allHomes, 0, true, - false, false, callingUserId); - - if (preferredHome != null) { - if (callingUid == preferredHome.activityInfo.applicationInfo.uid) { - return true; - } - } else { - for (ResolveInfo info : allHomes) { - if (callingUid == info.activityInfo.applicationInfo.uid) { - return true; - } - } - } - - return false; - } - - /** - * Enforce that calling UID is the current home app. This handles both the - * case where the user has selected a specific home app, and where there is - * only one home app. - */ - public void enforceCallerIsHomeApp() { - if (!checkCallerIsHomeApp()) { - throw new SecurityException("Caller is not currently selected home app"); - } - } - @Override public void setApplicationEnabledSetting(String appPackageName, int newState, int flags, int userId, String callingPackage) { @@ -12028,6 +11901,14 @@ public class PackageManagerService extends IPackageManager.Stub { } } sUserManager.systemReady(); + + // Kick off any messages waiting for system ready + if (mPostSystemReadyMessages != null) { + for (Message msg : mPostSystemReadyMessages) { + msg.sendToTarget(); + } + mPostSystemReadyMessages = null; + } } @Override @@ -13025,6 +12906,9 @@ public class PackageManagerService extends IPackageManager.Stub { Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator(); while (psit.hasNext()) { PackageSetting ps = psit.next(); + if (ps.pkg == null) { + continue; + } final String packageName = ps.pkg.packageName; // Skip over if system app if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index c346f71..a74e3f1 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -36,7 +36,6 @@ import android.os.FileUtils; import android.os.PatternMatcher; import android.os.Process; import android.os.UserHandle; -import android.os.UserManager; import android.util.LogPrinter; import com.android.internal.util.FastXmlSerializer; @@ -138,10 +137,6 @@ final class Settings { "persistent-preferred-activities"; static final String TAG_CROSS_PROFILE_INTENT_FILTERS = "crossProfile-intent-filters"; - private static final String TAG_CROSS_PROFILE_PACKAGE_INFO = "cross-profile-package-info"; - private static final String CROSS_PROFILE_PACKAGE_INFO_ATTR_TARGET_USER_ID = "target-user-id"; - private static final String CROSS_PROFILE_PACKAGE_INFO_TAG_PACKAGE_NAME = "package-name"; - private static final String CROSS_PROFILE_PACKAGE_INFO_ATTR_PACKAGE_NAME = "value"; private static final String ATTR_NAME = "name"; private static final String ATTR_USER = "user"; @@ -249,23 +244,15 @@ final class Settings { */ private final ArrayList<PendingPackage> mPendingPackages = new ArrayList<PendingPackage>(); - private final Context mContext; - private final File mSystemDir; public final KeySetManagerService mKeySetManagerService = new KeySetManagerService(mPackages); - // A mapping of (sourceUserId, targetUserId, packageNames) for forwarding the intents of a - // package. - final SparseArray<SparseArray<ArrayList<String>>> - mCrossProfilePackageInfo = new SparseArray<SparseArray<ArrayList<String>>>(); - Settings(Context context) { this(context, Environment.getDataDirectory()); } Settings(Context context, File dataDir) { - mContext = context; mSystemDir = new File(dataDir, "system"); mSystemDir.mkdirs(); FileUtils.setPermissions(mSystemDir.toString(), @@ -282,47 +269,6 @@ final class Settings { mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); } - public void addCrossProfilePackage( - String packageName, int sourceUserId, int targetUserId) { - synchronized(mCrossProfilePackageInfo) { - SparseArray<ArrayList<String>> sourceForwardingInfo = - mCrossProfilePackageInfo.get(sourceUserId); - if (sourceForwardingInfo == null) { - sourceForwardingInfo = new SparseArray<ArrayList<String>>(); - mCrossProfilePackageInfo.put(sourceUserId, sourceForwardingInfo); - } - ArrayList<String> packageNames = sourceForwardingInfo.get(targetUserId); - if (packageNames == null) { - packageNames = new ArrayList<String>(); - sourceForwardingInfo.put(targetUserId, packageNames); - } - if (!packageNames.contains(packageName)) { - packageNames.add(packageName); - } - } - } - - public void removeCrossProfilePackage( - String packageName, int sourceUserId, int targetUserId) { - synchronized(mCrossProfilePackageInfo) { - SparseArray<ArrayList<String>> sourceForwardingInfo = - mCrossProfilePackageInfo.get(sourceUserId); - if (sourceForwardingInfo == null) { - return; - } - ArrayList<String> packageNames = sourceForwardingInfo.get(targetUserId); - if (packageNames != null && packageNames.contains(packageName)) { - packageNames.remove(packageName); - if (packageNames.isEmpty()) { - sourceForwardingInfo.remove(targetUserId); - if (sourceForwardingInfo.size() == 0) { - mCrossProfilePackageInfo.remove(sourceUserId); - } - } - } - } - } - PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage, String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, String legacyNativeLibraryPathString, String primaryCpuAbi, String secondaryCpuAbi, @@ -602,11 +548,8 @@ final class Settings { // original default value is true), or we are being // asked to install for all users, or this is the // user we are installing for. - // In this context all users (USER_ALL) implies an adb install, - // so we additionally check whether that is allowed for this user. final boolean installed = installUser == null - || (installUser.getIdentifier() == UserHandle.USER_ALL - && (!isUnknownSourcesDisallowed(user.id))) + || installUser.getIdentifier() == UserHandle.USER_ALL || installUser.getIdentifier() == user.id; p.setUserState(user.id, COMPONENT_ENABLED_STATE_DEFAULT, installed, @@ -673,10 +616,7 @@ final class Settings { List<UserInfo> users = getAllUsers(); if (users != null) { for (UserInfo user : users) { - // Installing for USER_ALL implies an adb install, so we - // additionally check whether that is allowed for this user. - if ((installUser.getIdentifier() == UserHandle.USER_ALL - && (!isUnknownSourcesDisallowed(user.id))) + if (installUser.getIdentifier() == UserHandle.USER_ALL || installUser.getIdentifier() == user.id) { boolean installed = p.getInstalled(user.id); if (!installed) { @@ -691,12 +631,6 @@ final class Settings { return p; } - boolean isUnknownSourcesDisallowed(int userId) { - UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - return um.getUserRestrictions(new UserHandle(userId)).getBoolean( - UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, false); - } - void insertPackageSettingLPw(PackageSetting p, PackageParser.Package pkg) { p.pkg = pkg; // pkg.mSetEnabled = p.getEnabled(userId); @@ -1080,68 +1014,6 @@ final class Settings { } } - private void readCrossProfilePackageInfoLPw(XmlPullParser parser, int userId) - throws XmlPullParserException, IOException { - int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } - String tagName = parser.getName(); - if (tagName.equals(TAG_ITEM)) { - String targetUserIdString = parser.getAttributeValue(null, - CROSS_PROFILE_PACKAGE_INFO_ATTR_TARGET_USER_ID); - if (targetUserIdString == null) { - String msg = "Missing element under " + TAG +": " - + CROSS_PROFILE_PACKAGE_INFO_ATTR_TARGET_USER_ID + " at " + - parser.getPositionDescription(); - PackageManagerService.reportSettingsProblem(Log.WARN, msg); - continue; - } - int targetUserId = Integer.parseInt(targetUserIdString); - readCrossProfilePackageInfoForTargetLPw(parser, userId, targetUserId); - } else { - String msg = "Unknown element under " + TAG_CROSS_PROFILE_PACKAGE_INFO + ": " + - parser.getName(); - PackageManagerService.reportSettingsProblem(Log.WARN, msg); - XmlUtils.skipCurrentTag(parser); - } - } - } - - private void readCrossProfilePackageInfoForTargetLPw( - XmlPullParser parser, int sourceUserId, int targetUserId) - throws XmlPullParserException, IOException { - int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } - String tagName = parser.getName(); - if (tagName.equals(CROSS_PROFILE_PACKAGE_INFO_TAG_PACKAGE_NAME)) { - String packageName = parser.getAttributeValue( - null, CROSS_PROFILE_PACKAGE_INFO_ATTR_PACKAGE_NAME); - if (packageName == null) { - String msg = "Missing element under " + TAG +": " - + CROSS_PROFILE_PACKAGE_INFO_TAG_PACKAGE_NAME + " at " + - parser.getPositionDescription(); - PackageManagerService.reportSettingsProblem(Log.WARN, msg); - continue; - } - addCrossProfilePackage(packageName, sourceUserId, targetUserId); - } else { - String msg = "Unknown element under " + TAG_ITEM + ": " + - parser.getName(); - PackageManagerService.reportSettingsProblem(Log.WARN, msg); - XmlUtils.skipCurrentTag(parser); - } - } - } - void readPackageRestrictionsLPr(int userId) { if (DEBUG_MU) { Log.i(TAG, "Reading package restrictions for user=" + userId); @@ -1283,8 +1155,6 @@ final class Settings { readPersistentPreferredActivitiesLPw(parser, userId); } else if (tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) { readCrossProfileIntentFiltersLPw(parser, userId); - } else if (tagName.equals(TAG_CROSS_PROFILE_PACKAGE_INFO)){ - readCrossProfilePackageInfoLPw(parser, userId); } else { Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: " + parser.getName()); @@ -1376,32 +1246,6 @@ final class Settings { serializer.endTag(null, TAG_CROSS_PROFILE_INTENT_FILTERS); } - void writeCrossProfilePackageInfoLPr(XmlSerializer serializer, int userId) - throws IllegalArgumentException, IllegalStateException, IOException { - SparseArray<ArrayList<String>> sourceForwardingInfo = mCrossProfilePackageInfo.get(userId); - if (sourceForwardingInfo == null) { - return; - } - serializer.startTag(null, TAG_CROSS_PROFILE_PACKAGE_INFO); - int NI = sourceForwardingInfo.size(); - for (int i = 0; i < NI; i++) { - int targetUserId = sourceForwardingInfo.keyAt(i); - ArrayList<String> packageNames = sourceForwardingInfo.valueAt(i); - serializer.startTag(null, TAG_ITEM); - serializer.attribute(null, CROSS_PROFILE_PACKAGE_INFO_ATTR_TARGET_USER_ID, - Integer.toString(targetUserId)); - int NJ = packageNames.size(); - for (int j = 0; j < NJ; j++) { - serializer.startTag(null, CROSS_PROFILE_PACKAGE_INFO_TAG_PACKAGE_NAME); - serializer.attribute(null, CROSS_PROFILE_PACKAGE_INFO_ATTR_PACKAGE_NAME, - packageNames.get(j)); - serializer.endTag(null, CROSS_PROFILE_PACKAGE_INFO_TAG_PACKAGE_NAME); - } - serializer.endTag(null, TAG_ITEM); - } - serializer.endTag(null, TAG_CROSS_PROFILE_PACKAGE_INFO); - } - void writePackageRestrictionsLPr(int userId) { if (DEBUG_MU) { Log.i(TAG, "Writing package restrictions for user=" + userId); @@ -1506,8 +1350,6 @@ final class Settings { writeCrossProfileIntentFiltersLPr(serializer, userId); - writeCrossProfilePackageInfoLPr(serializer, userId); - serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS); serializer.endDocument(); @@ -3192,7 +3034,6 @@ final class Settings { file = getUserPackagesStateBackupFile(userId); file.delete(); removeCrossProfileIntentFiltersLPw(userId); - removeCrossProfilePackagesLPw(userId); } void removeCrossProfileIntentFiltersLPw(int userId) { @@ -3223,27 +3064,6 @@ final class Settings { } } - public void removeCrossProfilePackagesLPw(int userId) { - synchronized(mCrossProfilePackageInfo) { - // userId is the source user - if (mCrossProfilePackageInfo.get(userId) != null) { - mCrossProfilePackageInfo.remove(userId); - writePackageRestrictionsLPr(userId); - } - // userId is the target user - int count = mCrossProfilePackageInfo.size(); - for (int i = 0; i < count; i++) { - int sourceUserId = mCrossProfilePackageInfo.keyAt(i); - SparseArray<ArrayList<String>> sourceForwardingInfo = - mCrossProfilePackageInfo.valueAt(i); - if (sourceForwardingInfo.get(userId) != null) { - sourceForwardingInfo.remove(userId); - writePackageRestrictionsLPr(sourceUserId); - } - } - } - } - // This should be called (at least) whenever an application is removed private void setFirstAvailableUid(int uid) { if (uid > mFirstAvailableUid) { diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 4a2cece..d032d29 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -21,7 +21,6 @@ import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerNative; -import android.app.ActivityThread; import android.app.IStopUserCallback; import android.content.BroadcastReceiver; import android.content.Context; @@ -92,6 +91,7 @@ public class UserManagerService extends IUserManager.Stub { private static final String ATTR_SERIAL_NO = "serialNumber"; private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber"; private static final String ATTR_PARTIAL = "partial"; + private static final String ATTR_GUEST_TO_REMOVE = "guestToRemove"; private static final String ATTR_USER_VERSION = "version"; private static final String ATTR_PROFILE_GROUP_ID = "profileGroupId"; private static final String TAG_GUEST_RESTRICTIONS = "guestRestrictions"; @@ -228,7 +228,7 @@ public class UserManagerService extends IUserManager.Stub { ArrayList<UserInfo> partials = new ArrayList<UserInfo>(); for (int i = 0; i < mUsers.size(); i++) { UserInfo ui = mUsers.valueAt(i); - if (ui.partial && i != 0) { + if ((ui.partial || ui.guestToRemove) && i != 0) { partials.add(ui); } } @@ -759,6 +759,9 @@ public class UserManagerService extends IUserManager.Stub { if (userInfo.partial) { serializer.attribute(null, ATTR_PARTIAL, "true"); } + if (userInfo.guestToRemove) { + serializer.attribute(null, ATTR_GUEST_TO_REMOVE, "true"); + } if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) { serializer.attribute(null, ATTR_PROFILE_GROUP_ID, Integer.toString(userInfo.profileGroupId)); @@ -806,7 +809,7 @@ public class UserManagerService extends IUserManager.Stub { serializer.attribute(null, ATTR_NEXT_SERIAL_NO, Integer.toString(mNextSerialNumber)); serializer.attribute(null, ATTR_USER_VERSION, Integer.toString(mUserVersion)); - serializer.startTag(null, TAG_GUEST_RESTRICTIONS); + serializer.startTag(null, TAG_GUEST_RESTRICTIONS); writeRestrictionsLocked(serializer, mGuestRestrictions); serializer.endTag(null, TAG_GUEST_RESTRICTIONS); for (int i = 0; i < mUsers.size(); i++) { @@ -855,6 +858,8 @@ public class UserManagerService extends IUserManager.Stub { writeBoolean(serializer, restrictions, UserManager.DISALLOW_OUTGOING_CALLS); writeBoolean(serializer, restrictions, UserManager.DISALLOW_SMS); writeBoolean(serializer, restrictions, UserManager.DISALLOW_CREATE_WINDOWS); + writeBoolean(serializer, restrictions, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE); + writeBoolean(serializer, restrictions, UserManager.DISALLOW_OUTGOING_BEAM); serializer.endTag(null, TAG_RESTRICTIONS); } @@ -871,6 +876,7 @@ public class UserManagerService extends IUserManager.Stub { int profileGroupId = UserInfo.NO_PROFILE_GROUP_ID; long lastAttemptTime = 0L; boolean partial = false; + boolean guestToRemove = false; Bundle restrictions = new Bundle(); FileInputStream fis = null; @@ -918,6 +924,10 @@ public class UserManagerService extends IUserManager.Stub { if ("true".equals(valueString)) { partial = true; } + valueString = parser.getAttributeValue(null, ATTR_GUEST_TO_REMOVE); + if ("true".equals(valueString)) { + guestToRemove = true; + } int outerDepth = parser.getDepth(); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -942,6 +952,7 @@ public class UserManagerService extends IUserManager.Stub { userInfo.creationTime = creationTime; userInfo.lastLoggedInTime = lastLoggedInTime; userInfo.partial = partial; + userInfo.guestToRemove = guestToRemove; userInfo.profileGroupId = profileGroupId; mUserRestrictions.append(id, restrictions); if (salt != 0L) { @@ -999,6 +1010,8 @@ public class UserManagerService extends IUserManager.Stub { readBoolean(parser, restrictions, UserManager.DISALLOW_OUTGOING_CALLS); readBoolean(parser, restrictions, UserManager.DISALLOW_SMS); readBoolean(parser, restrictions, UserManager.DISALLOW_CREATE_WINDOWS); + readBoolean(parser, restrictions, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE); + readBoolean(parser, restrictions, UserManager.DISALLOW_OUTGOING_BEAM); } private void readBoolean(XmlPullParser parser, Bundle restrictions, @@ -1119,7 +1132,7 @@ public class UserManagerService extends IUserManager.Stub { return null; } // If we're adding a guest and there already exists one, bail. - if (isGuest && numberOfUsersOfTypeLocked(UserInfo.FLAG_GUEST, true) > 0) { + if (isGuest && findCurrentGuestUserLocked() != null) { return null; } // Limit number of managed profiles that can be created @@ -1180,6 +1193,23 @@ public class UserManagerService extends IUserManager.Stub { } /** + * Find the current guest user. If the Guest user is partial, + * then do not include it in the results as it is about to die. + * This is different than {@link #numberOfUsersOfTypeLocked(int, boolean)} due to + * the special handling of Guests being removed. + */ + private UserInfo findCurrentGuestUserLocked() { + final int size = mUsers.size(); + for (int i = 0; i < size; i++) { + final UserInfo user = mUsers.valueAt(i); + if (user.isGuest() && !user.guestToRemove && !mRemovingUserIds.get(user.id)) { + return user; + } + } + return null; + } + + /** * Mark this guest user for deletion to allow us to create another guest * and switch to that user before actually removing this guest. * @param userHandle the userid of the current guest @@ -1204,14 +1234,15 @@ public class UserManagerService extends IUserManager.Stub { if (!user.isGuest()) { return false; } - // Set this to a partially created user, so that the user will be purged - // on next startup, in case the runtime stops now before stopping and - // removing the user completely. - user.partial = true; + // We set this to a guest user that is to be removed. This is a temporary state + // where we are allowed to add new Guest users, even if this one is still not + // removed. This user will still show up in getUserInfo() calls. + // If we don't get around to removing this Guest user, it will be purged on next + // startup. + user.guestToRemove = true; // Mark it as disabled, so that it isn't returned any more when // profiles are queried. user.flags |= UserInfo.FLAG_DISABLED; - user.flags &= ~UserInfo.FLAG_GUEST; writeUserLocked(user); } } finally { diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 5f97a00..7808800 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -91,8 +91,6 @@ public final class PowerManagerService extends com.android.server.SystemService private static final int MSG_SANDMAN = 2; // Message: Sent when the screen on blocker is released. private static final int MSG_SCREEN_ON_BLOCKER_RELEASED = 3; - // Message: Sent to poll whether the boot animation has terminated. - private static final int MSG_CHECK_IF_BOOT_ANIMATION_FINISHED = 4; // Dirty bit: mWakeLocks changed private static final int DIRTY_WAKE_LOCKS = 1 << 0; @@ -154,12 +152,6 @@ public final class PowerManagerService extends com.android.server.SystemService // provider populates the actual default value (R.integer.def_screen_off_timeout). private static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15 * 1000; - // The name of the boot animation service in init.rc. - private static final String BOOT_ANIMATION_SERVICE = "bootanim"; - - // Poll interval in milliseconds for watching boot animation finished. - private static final int BOOT_ANIMATION_POLL_INTERVAL = 200; - // Power hints defined in hardware/libhardware/include/hardware/power.h. private static final int POWER_HINT_INTERACTION = 2; private static final int POWER_HINT_LOW_POWER = 5; @@ -478,14 +470,15 @@ public final class PowerManagerService extends com.android.server.SystemService @Override public void onBootPhase(int phase) { - if (phase == PHASE_BOOT_COMPLETED) { - // This is our early signal that the system thinks it has finished booting. - // However, the boot animation may still be running for a few more seconds - // since it is ultimately in charge of when it terminates. - // Defer transitioning into the boot completed state until the animation exits. - // We do this so that the screen does not start to dim prematurely before - // the user has actually had a chance to interact with the device. - startWatchingForBootAnimationFinished(); + synchronized (mLock) { + if (phase == PHASE_BOOT_COMPLETED) { + final long now = SystemClock.uptimeMillis(); + mBootCompleted = true; + mDirty |= DIRTY_BOOT_COMPLETED; + userActivityNoUpdateLocked( + now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); + updatePowerStateLocked(); + } } } @@ -2076,38 +2069,6 @@ public final class PowerManagerService extends com.android.server.SystemService updatePowerStateLocked(); } - private void startWatchingForBootAnimationFinished() { - mHandler.sendEmptyMessage(MSG_CHECK_IF_BOOT_ANIMATION_FINISHED); - } - - private void checkIfBootAnimationFinished() { - if (DEBUG) { - Slog.d(TAG, "Check if boot animation finished..."); - } - - if (SystemService.isRunning(BOOT_ANIMATION_SERVICE)) { - mHandler.sendEmptyMessageDelayed(MSG_CHECK_IF_BOOT_ANIMATION_FINISHED, - BOOT_ANIMATION_POLL_INTERVAL); - return; - } - - synchronized (mLock) { - if (!mBootCompleted) { - Slog.i(TAG, "Boot animation finished."); - handleBootCompletedLocked(); - } - } - } - - private void handleBootCompletedLocked() { - final long now = SystemClock.uptimeMillis(); - mBootCompleted = true; - mDirty |= DIRTY_BOOT_COMPLETED; - userActivityNoUpdateLocked( - now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); - updatePowerStateLocked(); - } - private void shutdownOrRebootInternal(final boolean shutdown, final boolean confirm, final String reason, boolean wait) { if (mHandler == null || !mSystemReady) { @@ -2528,9 +2489,6 @@ public final class PowerManagerService extends com.android.server.SystemService case MSG_SCREEN_ON_BLOCKER_RELEASED: handleScreenOnBlockerReleased(); break; - case MSG_CHECK_IF_BOOT_ANIMATION_FINISHED: - checkIfBootAnimationFinished(); - break; } } } diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index c8b5b3e..fefbe0a 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -33,6 +33,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; @@ -48,6 +49,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.service.trust.TrustAgentService; import android.util.ArraySet; import android.util.AttributeSet; @@ -97,6 +99,7 @@ public class TrustManagerService extends SystemService { private final SparseBooleanArray mUserHasAuthenticatedSinceBoot = new SparseBooleanArray(); /* package */ final TrustArchive mArchive = new TrustArchive(); private final Context mContext; + private final LockPatternUtils mLockPatternUtils; private UserManager mUserManager; @@ -104,6 +107,7 @@ public class TrustManagerService extends SystemService { super(context); mContext = context; mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mLockPatternUtils = new LockPatternUtils(context); } @Override @@ -116,6 +120,7 @@ public class TrustManagerService extends SystemService { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY && !isSafeMode()) { mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true); mReceiver.register(mContext); + maybeEnableFactoryTrustAgents(mLockPatternUtils, UserHandle.USER_OWNER); refreshAgentList(UserHandle.USER_ALL); } } @@ -159,6 +164,11 @@ public class TrustManagerService extends SystemService { void refreshAgentList(int userId) { if (DEBUG) Slog.d(TAG, "refreshAgentList()"); + if (userId != UserHandle.USER_ALL && userId < UserHandle.USER_OWNER) { + Log.e(TAG, "refreshAgentList(userId=" + userId + "): Invalid user handle," + + " must be USER_ALL or a specific user.", new Throwable("here")); + userId = UserHandle.USER_ALL; + } PackageManager pm = mContext.getPackageManager(); List<UserInfo> userInfos; @@ -168,12 +178,13 @@ public class TrustManagerService extends SystemService { userInfos = new ArrayList<>(); userInfos.add(mUserManager.getUserInfo(userId)); } - LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext); + LockPatternUtils lockPatternUtils = mLockPatternUtils; ArraySet<AgentInfo> obsoleteAgents = new ArraySet<>(); obsoleteAgents.addAll(mActiveAgents); for (UserInfo userInfo : userInfos) { + if (!userInfo.supportsSwitchTo()) continue; if (lockPatternUtils.getKeyguardStoredPasswordQuality(userInfo.id) == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) continue; if (!mUserHasAuthenticatedSinceBoot.get(userInfo.id)) continue; @@ -186,22 +197,11 @@ public class TrustManagerService extends SystemService { if (enabledAgents == null) { continue; } - List<ResolveInfo> resolveInfos = pm.queryIntentServicesAsUser(TRUST_AGENT_INTENT, - PackageManager.GET_META_DATA, userInfo.id); + List<ResolveInfo> resolveInfos = resolveAllowedTrustAgents(pm, userInfo.id); for (ResolveInfo resolveInfo : resolveInfos) { - if (resolveInfo.serviceInfo == null) continue; - - String packageName = resolveInfo.serviceInfo.packageName; - if (pm.checkPermission(PERMISSION_PROVIDE_AGENT, packageName) - != PackageManager.PERMISSION_GRANTED) { - Log.w(TAG, "Skipping agent because package " + packageName - + " does not have permission " + PERMISSION_PROVIDE_AGENT + "."); - continue; - } - ComponentName name = getComponentName(resolveInfo); - if (!enabledAgents.contains(name)) continue; + if (!enabledAgents.contains(name)) continue; if (disableTrustAgents) { List<String> features = dpm.getTrustAgentFeaturesEnabled(null /* admin */, name); @@ -228,11 +228,13 @@ public class TrustManagerService extends SystemService { boolean trustMayHaveChanged = false; for (int i = 0; i < obsoleteAgents.size(); i++) { AgentInfo info = obsoleteAgents.valueAt(i); - if (info.agent.isManagingTrust()) { - trustMayHaveChanged = true; + if (userId == UserHandle.USER_ALL || userId == info.userId) { + if (info.agent.isManagingTrust()) { + trustMayHaveChanged = true; + } + info.agent.unbind(); + mActiveAgents.remove(info); } - info.agent.unbind(); - mActiveAgents.remove(info); } if (trustMayHaveChanged) { @@ -342,6 +344,54 @@ public class TrustManagerService extends SystemService { return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); } + private void maybeEnableFactoryTrustAgents(LockPatternUtils utils, int userId) { + if (0 != Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.TRUST_AGENTS_INITIALIZED, 0, userId)) { + return; + } + PackageManager pm = mContext.getPackageManager(); + List<ResolveInfo> resolveInfos = resolveAllowedTrustAgents(pm, userId); + ArraySet<ComponentName> discoveredAgents = new ArraySet<>(); + for (ResolveInfo resolveInfo : resolveInfos) { + ComponentName componentName = getComponentName(resolveInfo); + int applicationInfoFlags = resolveInfo.serviceInfo.applicationInfo.flags; + if ((applicationInfoFlags & ApplicationInfo.FLAG_SYSTEM) == 0) { + Log.i(TAG, "Leaving agent " + componentName + " disabled because package " + + "is not a system package."); + continue; + } + discoveredAgents.add(componentName); + } + + List<ComponentName> previouslyEnabledAgents = utils.getEnabledTrustAgents(userId); + if (previouslyEnabledAgents != null) { + discoveredAgents.addAll(previouslyEnabledAgents); + } + utils.setEnabledTrustAgents(discoveredAgents, userId); + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, userId); + } + + private List<ResolveInfo> resolveAllowedTrustAgents(PackageManager pm, int userId) { + List<ResolveInfo> resolveInfos = pm.queryIntentServicesAsUser(TRUST_AGENT_INTENT, + 0 /* flags */, userId); + ArrayList<ResolveInfo> allowedAgents = new ArrayList<>(resolveInfos.size()); + for (ResolveInfo resolveInfo : resolveInfos) { + if (resolveInfo.serviceInfo == null) continue; + if (resolveInfo.serviceInfo.applicationInfo == null) continue; + String packageName = resolveInfo.serviceInfo.packageName; + if (pm.checkPermission(PERMISSION_PROVIDE_AGENT, packageName) + != PackageManager.PERMISSION_GRANTED) { + ComponentName name = getComponentName(resolveInfo); + Log.w(TAG, "Skipping agent " + name + " because package does not have" + + " permission " + PERMISSION_PROVIDE_AGENT + "."); + continue; + } + allowedAgents.add(resolveInfo); + } + return allowedAgents; + } + // Agent dispatch and aggregation private boolean aggregateIsTrusted(int userId) { @@ -414,6 +464,7 @@ public class TrustManagerService extends SystemService { } } mTrustListeners.add(listener); + updateTrustAll(); } private void removeListener(ITrustListener listener) { @@ -616,12 +667,19 @@ public class TrustManagerService extends SystemService { @Override public void onReceive(Context context, Intent intent) { - if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals( - intent.getAction())) { + String action = intent.getAction(); + if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) { refreshAgentList(getSendingUserId()); updateDevicePolicyFeatures(); - } else if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) { + } else if (Intent.ACTION_USER_PRESENT.equals(action)) { updateUserHasAuthenticated(getSendingUserId()); + } else if (Intent.ACTION_USER_ADDED.equals(action)) { + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -100); + if (userId > 0) { + maybeEnableFactoryTrustAgents(mLockPatternUtils, userId); + } else { + Log.wtf(TAG, "EXTRA_USER_HANDLE missing or invalid, value=" + userId); + } } } @@ -629,6 +687,7 @@ public class TrustManagerService extends SystemService { IntentFilter filter = new IntentFilter(); filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); filter.addAction(Intent.ACTION_USER_PRESENT); + filter.addAction(Intent.ACTION_USER_ADDED); context.registerReceiverAsUser(this, UserHandle.ALL, filter, diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 3380f71..adae84f 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -85,6 +85,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.HashSet; import java.util.Iterator; @@ -139,7 +140,7 @@ public final class TvInputManagerService extends SystemService { registerBroadcastReceivers(); } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { synchronized (mLock) { - buildTvInputListLocked(mCurrentUserId); + buildTvInputListLocked(mCurrentUserId, null); buildTvContentRatingSystemListLocked(mCurrentUserId); } } @@ -148,19 +149,64 @@ public final class TvInputManagerService extends SystemService { private void registerBroadcastReceivers() { PackageMonitor monitor = new PackageMonitor() { + private void buildTvInputList(String[] packages) { + synchronized (mLock) { + buildTvInputListLocked(getChangingUserId(), packages); + buildTvContentRatingSystemListLocked(getChangingUserId()); + } + } + + @Override + public void onPackageUpdateFinished(String packageName, int uid) { + if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")"); + // This callback is invoked when the TV input is reinstalled. + // In this case, isReplacing() always returns true. + buildTvInputList(new String[] { packageName }); + } + + @Override + public void onPackagesAvailable(String[] packages) { + if (DEBUG) { + Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")"); + } + // This callback is invoked when the media on which some packages exist become + // available. + if (isReplacing()) { + buildTvInputList(packages); + } + } + + @Override + public void onPackagesUnavailable(String[] packages) { + // This callback is invoked when the media on which some packages exist become + // unavailable. + if (DEBUG) { + Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages) + + ")"); + } + if (isReplacing()) { + buildTvInputList(packages); + } + } + @Override public void onSomePackagesChanged() { + // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage + // the TV inputs. if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()"); - synchronized (mLock) { - buildTvInputListLocked(mCurrentUserId); - buildTvContentRatingSystemListLocked(mCurrentUserId); + if (isReplacing()) { + if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing"); + // When the package is updated, buildTvInputListLocked is called in other + // methods instead. + return; } + buildTvInputList(null); } @Override public void onPackageRemoved(String packageName, int uid) { synchronized (mLock) { - UserState userState = getUserStateLocked(mCurrentUserId); + UserState userState = getUserStateLocked(getChangingUserId()); if (!userState.packageSet.contains(packageName)) { // Not a TV input package. return; @@ -218,13 +264,11 @@ public final class TvInputManagerService extends SystemService { component.getPackageName()) == PackageManager.PERMISSION_GRANTED; } - private void buildTvInputListLocked(int userId) { + private void buildTvInputListLocked(int userId, String[] updatedPackages) { UserState userState = getUserStateLocked(userId); userState.packageSet.clear(); - if (DEBUG) { - Slog.d(TAG, "buildTvInputList"); - } + if (DEBUG) Slog.d(TAG, "buildTvInputList"); PackageManager pm = mContext.getPackageManager(); List<ResolveInfo> services = pm.queryIntentServices( new Intent(TvInputService.SERVICE_INTERFACE), @@ -278,6 +322,15 @@ public final class TvInputManagerService extends SystemService { for (String inputId : inputMap.keySet()) { if (!userState.inputMap.containsKey(inputId)) { notifyInputAddedLocked(userState, inputId); + } else if (updatedPackages != null) { + // Notify the package updates + TvInputState inputState = inputMap.get(inputId); + for (String updatedPackage : updatedPackages) { + if (inputState.info.getComponent().getPackageName().equals(updatedPackage)) { + notifyInputUpdatedLocked(userState, inputId); + break; + } + } } } @@ -337,7 +390,7 @@ public final class TvInputManagerService extends SystemService { userState = new UserState(mContext, userId); } mUserStates.put(userId, userState); - buildTvInputListLocked(userId); + buildTvInputListLocked(userId, null); buildTvContentRatingSystemListLocked(userId); } } @@ -649,6 +702,19 @@ public final class TvInputManagerService extends SystemService { } } + private void notifyInputUpdatedLocked(UserState userState, String inputId) { + if (DEBUG) { + Slog.d(TAG, "notifyInputUpdatedLocked(inputId=" + inputId + ")"); + } + for (ITvInputManagerCallback callback : userState.callbackSet) { + try { + callback.onInputUpdated(inputId); + } catch (RemoteException e) { + Slog.e(TAG, "failed to report updated input to callback", e); + } + } + } + private void notifyInputStateChangedLocked(UserState userState, String inputId, int state, ITvInputManagerCallback targetCallback) { if (DEBUG) { @@ -1813,7 +1879,7 @@ public final class TvInputManagerService extends SystemService { private void addTvInputLocked(TvInputInfo inputInfo) { ServiceState serviceState = getServiceStateLocked(mComponent, mUserId); serviceState.inputList.add(inputInfo); - buildTvInputListLocked(mUserId); + buildTvInputListLocked(mUserId, null); } @Override @@ -1851,7 +1917,7 @@ public final class TvInputManagerService extends SystemService { } } if (removed) { - buildTvInputListLocked(mUserId); + buildTvInputListLocked(mUserId, null); mTvInputHardwareManager.removeTvInput(inputId); } else { Slog.e(TAG, "failed to remove input " + inputId); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 3eb2d5f..e1ade63 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -42,6 +42,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Point; +import android.graphics.Rect; import android.os.Binder; import android.os.Bundle; import android.os.Environment; @@ -85,6 +86,7 @@ import org.xmlpull.v1.XmlSerializer; import com.android.internal.content.PackageMonitor; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; +import com.android.internal.R; public class WallpaperManagerService extends IWallpaperManager.Stub { static final String TAG = "WallpaperManagerService"; @@ -99,12 +101,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { static final long MIN_WALLPAPER_CRASH_TIME = 10000; static final String WALLPAPER = "wallpaper"; static final String WALLPAPER_INFO = "wallpaper_info.xml"; - /** - * Name of the component used to display bitmap wallpapers from either the gallery or - * built-in wallpapers. - */ - static final ComponentName IMAGE_WALLPAPER = new ComponentName("com.android.systemui", - "com.android.systemui.ImageWallpaper"); /** * Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks @@ -146,7 +142,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { if (event == CLOSE_WRITE) { mWallpaper.imageWallpaperPending = false; } - bindWallpaperComponentLocked(IMAGE_WALLPAPER, true, + bindWallpaperComponentLocked(mImageWallpaper, true, false, mWallpaper, null); saveSettingsLocked(mWallpaper); } @@ -161,6 +157,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { final MyPackageMonitor mMonitor; WallpaperData mLastWallpaper; + /** + * Name of the component used to display bitmap wallpapers from either the gallery or + * built-in wallpapers. + */ + final ComponentName mImageWallpaper; + SparseArray<WallpaperData> mWallpaperMap = new SparseArray<WallpaperData>(); int mCurrentUserId; @@ -205,6 +207,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { int width = -1; int height = -1; + final Rect padding = new Rect(0, 0, 0, 0); + WallpaperData(int userId) { this.userId = userId; wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER); @@ -221,6 +225,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { IRemoteCallback mReply; boolean mDimensionsChanged = false; + boolean mPaddingChanged = false; public WallpaperConnection(WallpaperInfo info, WallpaperData wallpaper) { mInfo = info; @@ -282,6 +287,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } mDimensionsChanged = false; } + if (mPaddingChanged) { + try { + mEngine.setDisplayPadding(mWallpaper.padding); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to set wallpaper padding", e); + } + mPaddingChanged = false; + } } } @@ -447,6 +460,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { public WallpaperManagerService(Context context) { if (DEBUG) Slog.v(TAG, "WallpaperService startup"); mContext = context; + mImageWallpaper = ComponentName.unflattenFromString( + context.getResources().getString(R.string.image_wallpaper_component)); mIWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService(Context.WINDOW_SERVICE)); mIPackageManager = AppGlobals.getPackageManager(); @@ -599,33 +614,35 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { f.delete(); } final long ident = Binder.clearCallingIdentity(); - RuntimeException e = null; try { - wallpaper.imageWallpaperPending = false; - if (userId != mCurrentUserId) return; - if (bindWallpaperComponentLocked(defaultFailed - ? IMAGE_WALLPAPER - : null, true, false, wallpaper, reply)) { - return; + RuntimeException e = null; + try { + wallpaper.imageWallpaperPending = false; + if (userId != mCurrentUserId) return; + if (bindWallpaperComponentLocked(defaultFailed + ? mImageWallpaper + : null, true, false, wallpaper, reply)) { + return; + } + } catch (IllegalArgumentException e1) { + e = e1; + } + + // This can happen if the default wallpaper component doesn't + // exist. This should be a system configuration problem, but + // let's not let it crash the system and just live with no + // wallpaper. + Slog.e(TAG, "Default wallpaper component not found!", e); + clearWallpaperComponentLocked(wallpaper); + if (reply != null) { + try { + reply.sendResult(null); + } catch (RemoteException e1) { + } } - } catch (IllegalArgumentException e1) { - e = e1; } finally { Binder.restoreCallingIdentity(ident); } - - // This can happen if the default wallpaper component doesn't - // exist. This should be a system configuration problem, but - // let's not let it crash the system and just live with no - // wallpaper. - Slog.e(TAG, "Default wallpaper component not found!", e); - clearWallpaperComponentLocked(wallpaper); - if (reply != null) { - try { - reply.sendResult(null); - } catch (RemoteException e1) { - } - } } public boolean hasNamedWallpaper(String name) { @@ -714,6 +731,40 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } } + public void setDisplayPadding(Rect padding) { + checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS); + synchronized (mLock) { + int userId = UserHandle.getCallingUserId(); + WallpaperData wallpaper = mWallpaperMap.get(userId); + if (wallpaper == null) { + throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); + } + if (padding.left < 0 || padding.top < 0 || padding.right < 0 || padding.bottom < 0) { + throw new IllegalArgumentException("padding must be positive: " + padding); + } + + if (!padding.equals(wallpaper.padding)) { + wallpaper.padding.set(padding); + saveSettingsLocked(wallpaper); + if (mCurrentUserId != userId) return; // Don't change the properties now + if (wallpaper.connection != null) { + if (wallpaper.connection.mEngine != null) { + try { + wallpaper.connection.mEngine.setDisplayPadding(padding); + } catch (RemoteException e) { + } + notifyCallbacksLocked(wallpaper); + } else if (wallpaper.connection.mService != null) { + // We've attached to the service but the engine hasn't attached back to us + // yet. This means it will be created with the previous dimensions, so we + // need to update it to the new dimensions once it attaches. + wallpaper.connection.mPaddingChanged = true; + } + } + } + } + } + public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb, Bundle outParams) { synchronized (mLock) { @@ -848,7 +899,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { componentName = WallpaperManager.getDefaultWallpaperComponent(mContext); if (componentName == null) { // Fall back to static image wallpaper - componentName = IMAGE_WALLPAPER; + componentName = mImageWallpaper; //clearWallpaperComponentLocked(); //return; if (DEBUG) Slog.v(TAG, "Using image wallpaper"); @@ -876,7 +927,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { WallpaperInfo wi = null; Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); - if (componentName != null && !componentName.equals(IMAGE_WALLPAPER)) { + if (componentName != null && !componentName.equals(mImageWallpaper)) { // Make sure the selected service is actually a wallpaper service. List<ResolveInfo> ris = mIPackageManager.queryIntentServices(intent, @@ -1001,7 +1052,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { try { conn.mService.attach(conn, conn.mToken, WindowManager.LayoutParams.TYPE_WALLPAPER, false, - wallpaper.width, wallpaper.height); + wallpaper.width, wallpaper.height, wallpaper.padding); } catch (RemoteException e) { Slog.w(TAG, "Failed attaching wallpaper; clearing", e); if (!wallpaper.wallpaperUpdating) { @@ -1050,9 +1101,21 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { out.startTag(null, "wp"); out.attribute(null, "width", Integer.toString(wallpaper.width)); out.attribute(null, "height", Integer.toString(wallpaper.height)); + if (wallpaper.padding.left != 0) { + out.attribute(null, "paddingLeft", Integer.toString(wallpaper.padding.left)); + } + if (wallpaper.padding.top != 0) { + out.attribute(null, "paddingTop", Integer.toString(wallpaper.padding.top)); + } + if (wallpaper.padding.right != 0) { + out.attribute(null, "paddingRight", Integer.toString(wallpaper.padding.right)); + } + if (wallpaper.padding.bottom != 0) { + out.attribute(null, "paddingBottom", Integer.toString(wallpaper.padding.bottom)); + } out.attribute(null, "name", wallpaper.name); if (wallpaper.wallpaperComponent != null - && !wallpaper.wallpaperComponent.equals(IMAGE_WALLPAPER)) { + && !wallpaper.wallpaperComponent.equals(mImageWallpaper)) { out.attribute(null, "component", wallpaper.wallpaperComponent.flattenToShortString()); } @@ -1086,6 +1149,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } } + private int getAttributeInt(XmlPullParser parser, String name, int defValue) { + String value = parser.getAttributeValue(null, name); + if (value == null) { + return defValue; + } + return Integer.parseInt(value); + } + private void loadSettingsLocked(int userId) { if (DEBUG) Slog.v(TAG, "loadSettingsLocked"); @@ -1116,6 +1187,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { wallpaper.width = Integer.parseInt(parser.getAttributeValue(null, "width")); wallpaper.height = Integer.parseInt(parser .getAttributeValue(null, "height")); + wallpaper.padding.left = getAttributeInt(parser, "paddingLeft", 0); + wallpaper.padding.top = getAttributeInt(parser, "paddingTop", 0); + wallpaper.padding.right = getAttributeInt(parser, "paddingRight", 0); + wallpaper.padding.bottom = getAttributeInt(parser, "paddingBottom", 0); wallpaper.name = parser.getAttributeValue(null, "name"); String comp = parser.getAttributeValue(null, "component"); wallpaper.nextWallpaperComponent = comp != null @@ -1124,7 +1199,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { if (wallpaper.nextWallpaperComponent == null || "android".equals(wallpaper.nextWallpaperComponent .getPackageName())) { - wallpaper.nextWallpaperComponent = IMAGE_WALLPAPER; + wallpaper.nextWallpaperComponent = mImageWallpaper; } if (DEBUG) { @@ -1162,6 +1237,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { if (!success) { wallpaper.width = -1; wallpaper.height = -1; + wallpaper.padding.set(0, 0, 0, 0); wallpaper.name = ""; } @@ -1196,7 +1272,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { loadSettingsLocked(0); wallpaper = mWallpaperMap.get(0); if (wallpaper.nextWallpaperComponent != null - && !wallpaper.nextWallpaperComponent.equals(IMAGE_WALLPAPER)) { + && !wallpaper.nextWallpaperComponent.equals(mImageWallpaper)) { if (!bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false, wallpaper, null)) { // No such live wallpaper or other failure; fall back to the default @@ -1325,13 +1401,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { WallpaperData wallpaper = mWallpaperMap.valueAt(i); pw.println(" User " + wallpaper.userId + ":"); pw.print(" mWidth="); - pw.print(wallpaper.width); - pw.print(" mHeight="); - pw.println(wallpaper.height); - pw.print(" mName="); - pw.println(wallpaper.name); - pw.print(" mWallpaperComponent="); - pw.println(wallpaper.wallpaperComponent); + pw.print(wallpaper.width); + pw.print(" mHeight="); + pw.println(wallpaper.height); + pw.print(" mPadding="); pw.println(wallpaper.padding); + pw.print(" mName="); pw.println(wallpaper.name); + pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent); if (wallpaper.connection != null) { WallpaperConnection conn = wallpaper.connection; pw.print(" Wallpaper connection "); diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index aabb8f7..bfc7659 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -569,9 +569,9 @@ public class AppTransition implements Dump { int appWidth, int appHeight, int orientation, int transit, Rect containingFrame, Rect contentInsets, boolean isFullScreen) { Animation a; - final int thumbWidthI = mNextAppTransitionThumbnail.getWidth(); + final int thumbWidthI = mNextAppTransitionStartWidth; final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; - final int thumbHeightI = mNextAppTransitionThumbnail.getHeight(); + final int thumbHeightI = mNextAppTransitionStartHeight; final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; // Used for the ENTER_SCALE_UP and EXIT_SCALE_DOWN transitions @@ -993,7 +993,7 @@ public class AppTransition implements Dump { } void overridePendingAppTransitionAspectScaledThumb(Bitmap srcThumb, int startX, int startY, - IRemoteCallback startedCallback, boolean scaleUp) { + int targetWidth, int targetHeight, IRemoteCallback startedCallback, boolean scaleUp) { if (isTransitionSet()) { mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN; @@ -1002,6 +1002,8 @@ public class AppTransition implements Dump { mNextAppTransitionScaleUp = scaleUp; mNextAppTransitionStartX = startX; mNextAppTransitionStartY = startY; + mNextAppTransitionStartWidth = targetWidth; + mNextAppTransitionStartHeight = targetHeight; postAnimationCallback(); mNextAppTransitionCallback = startedCallback; } else { @@ -1138,6 +1140,10 @@ public class AppTransition implements Dump { pw.print(mNextAppTransitionStartX); pw.print(" mNextAppTransitionStartY="); pw.println(mNextAppTransitionStartY); + pw.print(" mNextAppTransitionStartWidth="); + pw.print(mNextAppTransitionStartWidth); + pw.print(" mNextAppTransitionStartHeight="); + pw.println(mNextAppTransitionStartHeight); pw.print(" mNextAppTransitionScaleUp="); pw.println(mNextAppTransitionScaleUp); break; } diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java index 64b852b..a7d41fa 100644 --- a/services/core/java/com/android/server/wm/CircularDisplayMask.java +++ b/services/core/java/com/android/server/wm/CircularDisplayMask.java @@ -63,8 +63,14 @@ class CircularDisplayMask { SurfaceControl ctrl = null; try { - ctrl = new SurfaceControl(session, "CircularDisplayMask", - mScreenSize.x, mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); + if (WindowManagerService.DEBUG_SURFACE_TRACE) { + ctrl = new WindowStateAnimator.SurfaceTrace(session, "CircularDisplayMask", + mScreenSize.x, mScreenSize.y, PixelFormat.TRANSLUCENT, + SurfaceControl.HIDDEN); + } else { + ctrl = new SurfaceControl(session, "CircularDisplayMask", mScreenSize.x, + mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); + } ctrl.setLayerStack(display.getLayerStack()); ctrl.setLayer(zOrder); ctrl.setPosition(0, 0); diff --git a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java new file mode 100644 index 0000000..62f2b48 --- /dev/null +++ b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java @@ -0,0 +1,127 @@ +/* + * 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 android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.Slog; +import android.view.Display; +import android.view.Surface; +import android.view.Surface.OutOfResourcesException; +import android.view.SurfaceControl; +import android.view.SurfaceSession; + +class EmulatorDisplayOverlay { + private static final String TAG = "EmulatorDisplayOverlay"; + + // Display dimensions + private Point mScreenSize; + + private final SurfaceControl mSurfaceControl; + private final Surface mSurface = new Surface(); + private int mLastDW; + private int mLastDH; + private boolean mDrawNeeded; + private Drawable mOverlay; + private int mRotation; + private boolean mVisible; + + public EmulatorDisplayOverlay(Context context, Display display, SurfaceSession session, + int zOrder) { + mScreenSize = new Point(); + display.getSize(mScreenSize); + + SurfaceControl ctrl = null; + try { + if (WindowManagerService.DEBUG_SURFACE_TRACE) { + ctrl = new WindowStateAnimator.SurfaceTrace(session, "EmulatorDisplayOverlay", + mScreenSize.x, mScreenSize.y, PixelFormat.TRANSLUCENT, + SurfaceControl.HIDDEN); + } else { + ctrl = new SurfaceControl(session, "EmulatorDisplayOverlay", mScreenSize.x, + mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); + } + ctrl.setLayerStack(display.getLayerStack()); + ctrl.setLayer(zOrder); + ctrl.setPosition(0, 0); + ctrl.show(); + mSurface.copyFrom(ctrl); + } catch (OutOfResourcesException e) { + } + mSurfaceControl = ctrl; + mDrawNeeded = true; + mOverlay = context.getDrawable( + com.android.internal.R.drawable.emulator_circular_window_overlay); + } + + private void drawIfNeeded() { + if (!mDrawNeeded || !mVisible) { + return; + } + mDrawNeeded = false; + + Rect dirty = new Rect(0, 0, mScreenSize.x, mScreenSize.y); + Canvas c = null; + try { + c = mSurface.lockCanvas(dirty); + } catch (IllegalArgumentException e) { + } catch (OutOfResourcesException e) { + } + if (c == null) { + return; + } + c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.SRC); + mSurfaceControl.setPosition(0, 0); + mOverlay.setBounds(0, 0, mScreenSize.x, mScreenSize.y); + mOverlay.draw(c); + mSurface.unlockCanvasAndPost(c); + } + + // Note: caller responsible for being inside + // Surface.openTransaction() / closeTransaction() + public void setVisibility(boolean on) { + if (mSurfaceControl == null) { + return; + } + mVisible = on; + drawIfNeeded(); + if (on) { + mSurfaceControl.show(); + } else { + mSurfaceControl.hide(); + } + } + + void positionSurface(int dw, int dh, int rotation) { + if (mLastDW == dw && mLastDH == dh && mRotation == rotation) { + return; + } + mLastDW = dw; + mLastDH = dh; + mDrawNeeded = true; + mRotation = rotation; + drawIfNeeded(); + } + +} diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index f02c0e6..46aefb6 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -352,6 +352,12 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { mService.mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen); } + /* Notifies that the camera lens cover state has changed. */ + @Override + public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered) { + mService.mPolicy.notifyCameraLensCoverSwitchChanged(whenNanos, lensCovered); + } + /* Provides an opportunity for the window manager policy to intercept early key * processing as soon as the key has been read from the device. */ @Override @@ -359,12 +365,13 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags); } - /* Provides an opportunity for the window manager policy to intercept early - * motion event processing when the screen is off since these events are normally + /* Provides an opportunity for the window manager policy to intercept early motion event + * processing when the device is in a non-interactive state since these events are normally * dropped. */ @Override - public int interceptWakeMotionBeforeQueueing(long whenNanos, int policyFlags) { - return mService.mPolicy.interceptWakeMotionBeforeQueueing(whenNanos, policyFlags); + public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) { + return mService.mPolicy.interceptMotionBeforeQueueingNonInteractive( + whenNanos, policyFlags); } /* Provides an opportunity for the window manager policy to process a key before diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index f2703ad..d737e7f 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -415,6 +415,18 @@ final class Session extends IWindowSession.Stub mService.wallpaperOffsetsComplete(window); } + public void setWallpaperDisplayOffset(IBinder window, int x, int y) { + synchronized(mService.mWindowMap) { + long ident = Binder.clearCallingIdentity(); + try { + mService.setWindowWallpaperDisplayOffsetLocked( + mService.windowForClientLocked(this, window, true), x, y); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, int z, Bundle extras, boolean sync) { synchronized(mService.mWindowMap) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 08343d8..54af851 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -20,6 +20,8 @@ import static android.view.WindowManager.LayoutParams.*; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import android.app.AppOpsManager; +import android.os.Build; +import android.os.SystemService; import android.util.ArraySet; import android.util.TimeUtils; import android.view.IWindowId; @@ -271,6 +273,12 @@ public class WindowManagerService extends IWindowManager.Stub // Default input dispatching timeout in nanoseconds. static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS = 5000 * 1000000L; + // Poll interval in milliseconds for watching boot animation finished. + private static final int BOOT_ANIMATION_POLL_INTERVAL = 200; + + // The name of the boot animation service in init.rc. + private static final String BOOT_ANIMATION_SERVICE = "bootanim"; + /** Minimum value for attachStack and resizeStack weight value */ public static final float STACK_WEIGHT_MIN = 0.2f; @@ -295,6 +303,8 @@ public class WindowManagerService extends IWindowManager.Stub private static final int SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular"; + final private KeyguardDisableHandler mKeyguardDisableHandler; final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -441,6 +451,7 @@ public class WindowManagerService extends IWindowManager.Stub Watermark mWatermark; StrictModeFlash mStrictModeFlash; CircularDisplayMask mCircularDisplayMask; + EmulatorDisplayOverlay mEmulatorDisplayOverlay; FocusedStackFrame mFocusedStackFrame; int mFocusedStackLayer; @@ -454,6 +465,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean mSystemBooted = false; boolean mForceDisplayEnabled = false; boolean mShowingBootMessages = false; + boolean mBootAnimationStopped = false; String mLastANRState; @@ -572,6 +584,8 @@ public class WindowManagerService extends IWindowManager.Stub float mLastWallpaperY = -1; float mLastWallpaperXStep = -1; float mLastWallpaperYStep = -1; + int mLastWallpaperDisplayOffsetX = Integer.MIN_VALUE; + int mLastWallpaperDisplayOffsetY = Integer.MIN_VALUE; // This is set when we are waiting for a wallpaper to tell us it is done // changing its scroll position. WindowState mWaitingOnWallpaper; @@ -879,6 +893,7 @@ public class WindowManagerService extends IWindowManager.Stub } showCircularDisplayMaskIfNeeded(); + showEmulatorDisplayOverlayIfNeeded(); } public InputMonitor getInputMonitor() { @@ -1892,6 +1907,12 @@ public class WindowManagerService extends IWindowManager.Stub mLastWallpaperY = mWallpaperTarget.mWallpaperY; mLastWallpaperYStep = mWallpaperTarget.mWallpaperYStep; } + if (mWallpaperTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + mLastWallpaperDisplayOffsetX = mWallpaperTarget.mWallpaperDisplayOffsetX; + } + if (mWallpaperTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + mLastWallpaperDisplayOffsetY = mWallpaperTarget.mWallpaperDisplayOffsetY; + } } // Start stepping backwards from here, ensuring that our wallpaper windows @@ -2030,6 +2051,9 @@ public class WindowManagerService extends IWindowManager.Stub float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f; int availw = wallpaperWin.mFrame.right-wallpaperWin.mFrame.left-dw; int offset = availw > 0 ? -(int)(availw*wpx+.5f) : 0; + if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + offset += mLastWallpaperDisplayOffsetX; + } changed = wallpaperWin.mXOffset != offset; if (changed) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper " @@ -2046,6 +2070,9 @@ public class WindowManagerService extends IWindowManager.Stub float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f; int availh = wallpaperWin.mFrame.bottom-wallpaperWin.mFrame.top-dh; offset = availh > 0 ? -(int)(availh*wpy+.5f) : 0; + if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + offset += mLastWallpaperDisplayOffsetY; + } if (wallpaperWin.mYOffset != offset) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper " + wallpaperWin + " y: " + offset); @@ -2130,6 +2157,16 @@ public class WindowManagerService extends IWindowManager.Stub } else if (changingTarget.mWallpaperY >= 0) { mLastWallpaperY = changingTarget.mWallpaperY; } + if (target.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + mLastWallpaperDisplayOffsetX = target.mWallpaperDisplayOffsetX; + } else if (changingTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + mLastWallpaperDisplayOffsetX = changingTarget.mWallpaperDisplayOffsetX; + } + if (target.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + mLastWallpaperDisplayOffsetY = target.mWallpaperDisplayOffsetY; + } else if (changingTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + mLastWallpaperDisplayOffsetY = changingTarget.mWallpaperDisplayOffsetY; + } } int curTokenIndex = mWallpaperTokens.size(); @@ -2826,6 +2863,14 @@ public class WindowManagerService extends IWindowManager.Stub } } + public void setWindowWallpaperDisplayOffsetLocked(WindowState window, int x, int y) { + if (window.mWallpaperDisplayOffsetX != x || window.mWallpaperDisplayOffsetY != y) { + window.mWallpaperDisplayOffsetX = x; + window.mWallpaperDisplayOffsetY = y; + updateWallpaperOffsetLocked(window, true); + } + } + public Bundle sendWindowWallpaperCommandLocked(WindowState window, String action, int x, int y, int z, Bundle extras, boolean sync) { if (window == mWallpaperTarget || window == mLowerWallpaperTarget @@ -4031,10 +4076,11 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void overridePendingAppTransitionAspectScaledThumb(Bitmap srcThumb, int startX, - int startY, IRemoteCallback startedCallback, boolean scaleUp) { + int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback, + boolean scaleUp) { synchronized(mWindowMap) { mAppTransition.overridePendingAppTransitionAspectScaledThumb(srcThumb, startX, - startY, startedCallback, scaleUp); + startY, targetWidth, targetHeight, startedCallback, scaleUp); } } @@ -4303,20 +4349,28 @@ public class WindowManagerService extends IWindowManager.Stub } public void setAppFullscreen(IBinder token, boolean toOpaque) { - AppWindowToken atoken = findAppWindowToken(token); - if (atoken != null) { - atoken.appFullscreen = toOpaque; - setWindowOpaque(token, toOpaque); - requestTraversal(); + synchronized (mWindowMap) { + AppWindowToken atoken = findAppWindowToken(token); + if (atoken != null) { + atoken.appFullscreen = toOpaque; + setWindowOpaqueLocked(token, toOpaque); + requestTraversalLocked(); + } } } public void setWindowOpaque(IBinder token, boolean isOpaque) { + synchronized (mWindowMap) { + setWindowOpaqueLocked(token, isOpaque); + } + } + + public void setWindowOpaqueLocked(IBinder token, boolean isOpaque) { AppWindowToken wtoken = findAppWindowToken(token); if (wtoken != null) { WindowState win = wtoken.findMainWindow(); if (win != null) { - win.mWinAnimator.setOpaque(isOpaque); + win.mWinAnimator.setOpaqueLocked(isOpaque); } } } @@ -5432,6 +5486,23 @@ public class WindowManagerService extends IWindowManager.Stub } } + // Called by window manager policy. Not exposed externally. + @Override + public int getCameraLensCoverState() { + int sw = mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, + InputManagerService.SW_CAMERA_LENS_COVER); + if (sw > 0) { + // Switch state: AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL. + return CAMERA_LENS_COVERED; + } else if (sw == 0) { + // Switch state: AKEY_STATE_UP. + return CAMERA_LENS_UNCOVERED; + } else { + // Switch state: AKEY_STATE_UNKNOWN. + return CAMERA_LENS_COVER_ABSENT; + } + } + // Called by window manager policy. Not exposed externally. @Override public void switchKeyboardLayout(int deviceId, int direction) { @@ -5544,17 +5615,70 @@ public class WindowManagerService extends IWindowManager.Stub performEnableScreen(); } + private boolean checkWaitingForWindowsLocked() { + + boolean haveBootMsg = false; + boolean haveApp = false; + // if the wallpaper service is disabled on the device, we're never going to have + // wallpaper, don't bother waiting for it + boolean haveWallpaper = false; + boolean wallpaperEnabled = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_enableWallpaperService) + && !mOnlyCore; + boolean haveKeyguard = true; + // TODO(multidisplay): Expand to all displays? + final WindowList windows = getDefaultWindowListLocked(); + final int N = windows.size(); + for (int i=0; i<N; i++) { + WindowState w = windows.get(i); + if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) { + return true; + } + if (w.isDrawnLw()) { + if (w.mAttrs.type == TYPE_BOOT_PROGRESS) { + haveBootMsg = true; + } else if (w.mAttrs.type == TYPE_APPLICATION) { + haveApp = true; + } else if (w.mAttrs.type == TYPE_WALLPAPER) { + haveWallpaper = true; + } else if (w.mAttrs.type == TYPE_STATUS_BAR) { + haveKeyguard = mPolicy.isKeyguardDrawnLw(); + } + } + } + + if (DEBUG_SCREEN_ON || DEBUG_BOOT) { + Slog.i(TAG, "******** booted=" + mSystemBooted + " msg=" + mShowingBootMessages + + " haveBoot=" + haveBootMsg + " haveApp=" + haveApp + + " haveWall=" + haveWallpaper + " wallEnabled=" + wallpaperEnabled + + " haveKeyguard=" + haveKeyguard); + } + + // If we are turning on the screen to show the boot message, + // don't do it until the boot message is actually displayed. + if (!mSystemBooted && !haveBootMsg) { + return true; + } + + // If we are turning on the screen after the boot is completed + // normally, don't do so until we have the application and + // wallpaper. + if (mSystemBooted && ((!haveApp && !haveKeyguard) || + (wallpaperEnabled && !haveWallpaper))) { + return true; + } + + return false; + } + public void performEnableScreen() { synchronized(mWindowMap) { - if (DEBUG_BOOT) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.i(TAG, "performEnableScreen: mDisplayEnabled=" + mDisplayEnabled - + " mForceDisplayEnabled=" + mForceDisplayEnabled - + " mShowingBootMessages=" + mShowingBootMessages - + " mSystemBooted=" + mSystemBooted - + " mOnlyCore=" + mOnlyCore, here); - } + if (DEBUG_BOOT) Slog.i(TAG, "performEnableScreen: mDisplayEnabled=" + mDisplayEnabled + + " mForceDisplayEnabled=" + mForceDisplayEnabled + + " mShowingBootMessages=" + mShowingBootMessages + + " mSystemBooted=" + mSystemBooted + + " mOnlyCore=" + mOnlyCore, + new RuntimeException("here").fillInStackTrace()); if (mDisplayEnabled) { return; } @@ -5562,94 +5686,64 @@ public class WindowManagerService extends IWindowManager.Stub return; } - if (!mForceDisplayEnabled) { - // Don't enable the screen until all existing windows - // have been drawn. - boolean haveBootMsg = false; - boolean haveApp = false; - // if the wallpaper service is disabled on the device, we're never going to have - // wallpaper, don't bother waiting for it - boolean haveWallpaper = false; - boolean wallpaperEnabled = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_enableWallpaperService) - && !mOnlyCore; - boolean haveKeyguard = true; - // TODO(multidisplay): Expand to all displays? - final WindowList windows = getDefaultWindowListLocked(); - final int N = windows.size(); - for (int i=0; i<N; i++) { - WindowState w = windows.get(i); - if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) { - return; - } - if (w.isDrawnLw()) { - if (w.mAttrs.type == TYPE_BOOT_PROGRESS) { - haveBootMsg = true; - } else if (w.mAttrs.type == TYPE_APPLICATION) { - haveApp = true; - } else if (w.mAttrs.type == TYPE_WALLPAPER) { - haveWallpaper = true; - } else if (w.mAttrs.type == TYPE_STATUS_BAR) { - haveKeyguard = mPolicy.isKeyguardDrawnLw(); - } - } - } - - if (DEBUG_SCREEN_ON || DEBUG_BOOT) { - Slog.i(TAG, "******** booted=" + mSystemBooted + " msg=" + mShowingBootMessages - + " haveBoot=" + haveBootMsg + " haveApp=" + haveApp - + " haveWall=" + haveWallpaper + " wallEnabled=" + wallpaperEnabled - + " haveKeyguard=" + haveKeyguard); - } + // Don't enable the screen until all existing windows have been drawn. + if (!mForceDisplayEnabled && checkWaitingForWindowsLocked()) { + return; + } - // If we are turning on the screen to show the boot message, - // don't do it until the boot message is actually displayed. - if (!mSystemBooted && !haveBootMsg) { - return; + if (!mBootAnimationStopped) { + // Do this one time. + try { + IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger"); + if (surfaceFlinger != null) { + //Slog.i(TAG, "******* TELLING SURFACE FLINGER WE ARE BOOTED!"); + Parcel data = Parcel.obtain(); + data.writeInterfaceToken("android.ui.ISurfaceComposer"); + surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED + data, null, 0); + data.recycle(); + } + } catch (RemoteException ex) { + Slog.e(TAG, "Boot completed: SurfaceFlinger is dead!"); } + mBootAnimationStopped = true; + } - // If we are turning on the screen after the boot is completed - // normally, don't do so until we have the application and - // wallpaper. - if (mSystemBooted && ((!haveApp && !haveKeyguard) || - (wallpaperEnabled && !haveWallpaper))) { - return; - } + if (!mForceDisplayEnabled && !checkBootAnimationCompleteLocked()) { + if (DEBUG_BOOT) Slog.i(TAG, "performEnableScreen: Waiting for anim complete"); + return; } mDisplayEnabled = true; if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG, "******************** ENABLING SCREEN!"); - if (false) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new FastPrintWriter(sw, false, 1024); - this.dump(null, pw, null); - pw.flush(); - Slog.i(TAG, sw.toString()); - } - try { - IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger"); - if (surfaceFlinger != null) { - //Slog.i(TAG, "******* TELLING SURFACE FLINGER WE ARE BOOTED!"); - Parcel data = Parcel.obtain(); - data.writeInterfaceToken("android.ui.ISurfaceComposer"); - surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED - data, null, 0); - data.recycle(); - } - } catch (RemoteException ex) { - Slog.e(TAG, "Boot completed: SurfaceFlinger is dead!"); - } // Enable input dispatch. mInputMonitor.setEventDispatchingLw(mEventDispatchingEnabled); } + try { + mActivityManager.bootAnimationComplete(); + } catch (RemoteException e) { + } + mPolicy.enableScreenAfterBoot(); // Make sure the last requested orientation has been applied. updateRotationUnchecked(false, false); } + private boolean checkBootAnimationCompleteLocked() { + if (SystemService.isRunning(BOOT_ANIMATION_SERVICE)) { + mH.removeMessages(H.CHECK_IF_BOOT_ANIMATION_FINISHED); + mH.sendEmptyMessageDelayed(H.CHECK_IF_BOOT_ANIMATION_FINISHED, + BOOT_ANIMATION_POLL_INTERVAL); + if (DEBUG_BOOT) Slog.i(TAG, "checkBootAnimationComplete: Waiting for anim complete"); + return false; + } + if (DEBUG_BOOT) Slog.i(TAG, "checkBootAnimationComplete: Animation complete!"); + return true; + } + public void showBootMessage(final CharSequence msg, final boolean always) { boolean first = false; synchronized(mWindowMap) { @@ -5709,7 +5803,16 @@ public class WindowManagerService extends IWindowManager.Stub com.android.internal.R.bool.config_windowIsRound) && mContext.getResources().getBoolean( com.android.internal.R.bool.config_windowShowCircularMask)) { - mH.sendMessage(mH.obtainMessage(H.SHOW_DISPLAY_MASK)); + mH.sendMessage(mH.obtainMessage(H.SHOW_CIRCULAR_DISPLAY_MASK)); + } + } + + public void showEmulatorDisplayOverlayIfNeeded() { + if (mContext.getResources().getBoolean( + com.android.internal.R.bool.config_windowEnableCircularEmulatorDisplayOverlay) + && SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false) + && Build.HARDWARE.contains("goldfish")) { + mH.sendMessage(mH.obtainMessage(H.SHOW_EMULATOR_DISPLAY_OVERLAY)); } } @@ -5717,12 +5820,12 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, - ">>> OPEN TRANSACTION showDisplayMask"); + ">>> OPEN TRANSACTION showCircularMask"); SurfaceControl.openTransaction(); try { // TODO(multi-display): support multiple displays if (mCircularDisplayMask == null) { - int screenOffset = (int) mContext.getResources().getDimensionPixelSize( + int screenOffset = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.circular_display_mask_offset); mCircularDisplayMask = new CircularDisplayMask( @@ -5736,7 +5839,32 @@ public class WindowManagerService extends IWindowManager.Stub } finally { SurfaceControl.closeTransaction(); if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, - "<<< CLOSE TRANSACTION showDisplayMask"); + "<<< CLOSE TRANSACTION showCircularMask"); + } + } + } + + public void showEmulatorDisplayOverlay() { + synchronized(mWindowMap) { + + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, + ">>> OPEN TRANSACTION showEmulatorDisplayOverlay"); + SurfaceControl.openTransaction(); + try { + if (mEmulatorDisplayOverlay == null) { + mEmulatorDisplayOverlay = new EmulatorDisplayOverlay( + mContext, + getDefaultDisplayContentLocked().getDisplay(), + mFxSession, + mPolicy.windowTypeToLayerLw( + WindowManager.LayoutParams.TYPE_POINTER) + * TYPE_LAYER_MULTIPLIER + 10); + } + mEmulatorDisplayOverlay.setVisibility(true); + } finally { + SurfaceControl.closeTransaction(); + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, + "<<< CLOSE TRANSACTION showEmulatorDisplayOverlay"); } } } @@ -5834,7 +5962,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public Bitmap screenshotApplications(IBinder appToken, int displayId, int width, int height, boolean force565) { - if (!checkCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER, + if (!checkCallingPermission(Manifest.permission.READ_FRAME_BUFFER, "screenshotApplications()")) { throw new SecurityException("Requires READ_FRAME_BUFFER permission"); } @@ -5854,7 +5982,7 @@ public class WindowManagerService extends IWindowManager.Stub return null; } - Bitmap rawss = null; + Bitmap bm = null; int maxLayer = 0; final Rect frame = new Rect(); @@ -5995,10 +6123,8 @@ public class WindowManagerService extends IWindowManager.Stub // The screenshot API does not apply the current screen rotation. rot = getDefaultDisplayContentLocked().getDisplay().getRotation(); + if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) { - final int tmp = width; - width = height; - height = tmp; rot = (rot == Surface.ROTATION_90) ? Surface.ROTATION_270 : Surface.ROTATION_90; } @@ -6024,9 +6150,9 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG, "Taking screenshot while rotating"); - rawss = SurfaceControl.screenshot(crop, width, height, minLayer, maxLayer, - inRotation); - if (rawss == null) { + bm = SurfaceControl.screenshot(crop, width, height, minLayer, maxLayer, + inRotation, rot); + if (bm == null) { Slog.w(TAG, "Screenshot failure taking screenshot for (" + dw + "x" + dh + ") to layer " + maxLayer); return null; @@ -6036,17 +6162,6 @@ public class WindowManagerService extends IWindowManager.Stub break; } - Bitmap bm = Bitmap.createBitmap(width, height, force565 ? - Config.RGB_565 : rawss.getConfig()); - if (DEBUG_SCREENSHOT) { - bm.eraseColor(0xFF000000); - } - Matrix matrix = new Matrix(); - ScreenRotationAnimation.createRotationMatrix(rot, width, height, matrix); - Canvas canvas = new Canvas(bm); - canvas.drawBitmap(rawss, matrix, null); - canvas.setBitmap(null); - if (DEBUG_SCREENSHOT) { // TEST IF IT's ALL BLACK int[] buffer = new int[bm.getWidth() * bm.getHeight()]; @@ -6067,9 +6182,12 @@ public class WindowManagerService extends IWindowManager.Stub } } - rawss.recycle(); - - return bm; + // Copy the screenshot bitmap to another buffer so that the gralloc backed + // bitmap will not have a long lifetime. Gralloc memory can be pinned or + // duplicated and might have a higher cost than a skia backed buffer. + Bitmap ret = bm.copy(bm.getConfig(),true); + bm.recycle(); + return ret; } /** @@ -7367,7 +7485,10 @@ public class WindowManagerService extends IWindowManager.Stub public static final int NEW_ANIMATOR_SCALE = 34; - public static final int SHOW_DISPLAY_MASK = 35; + public static final int SHOW_CIRCULAR_DISPLAY_MASK = 35; + public static final int SHOW_EMULATOR_DISPLAY_OVERLAY = 36; + + public static final int CHECK_IF_BOOT_ANIMATION_FINISHED = 37; @Override public void handleMessage(Message msg) { @@ -7763,11 +7884,16 @@ public class WindowManagerService extends IWindowManager.Stub break; } - case SHOW_DISPLAY_MASK: { + case SHOW_CIRCULAR_DISPLAY_MASK: { showCircularMask(); break; } + case SHOW_EMULATOR_DISPLAY_OVERLAY: { + showEmulatorDisplayOverlay(); + break; + } + case DO_ANIMATION_CALLBACK: { try { ((IRemoteCallback)msg.obj).sendResult(null); @@ -7848,6 +7974,17 @@ public class WindowManagerService extends IWindowManager.Stub } } break; + case CHECK_IF_BOOT_ANIMATION_FINISHED: { + final boolean bootAnimationComplete; + synchronized (mWindowMap) { + if (DEBUG_BOOT) Slog.i(TAG, "CHECK_IF_BOOT_ANIMATION_FINISHED:"); + bootAnimationComplete = checkBootAnimationCompleteLocked(); + } + if (bootAnimationComplete) { + performEnableScreen(); + } + } + break; } if (DEBUG_WINDOW_TRACE) { Slog.v(TAG, "handleMessage: exit"); @@ -9337,6 +9474,9 @@ public class WindowManagerService extends IWindowManager.Stub if (mCircularDisplayMask != null) { mCircularDisplayMask.positionSurface(defaultDw, defaultDh, mRotation); } + if (mEmulatorDisplayOverlay != null) { + mEmulatorDisplayOverlay.positionSurface(defaultDw, defaultDh, mRotation); + } boolean focusDisplayed = false; @@ -10863,6 +11003,12 @@ public class WindowManagerService extends IWindowManager.Stub } pw.print(" mLastWallpaperX="); pw.print(mLastWallpaperX); pw.print(" mLastWallpaperY="); pw.println(mLastWallpaperY); + if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE + || mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + pw.print(" mLastWallpaperDisplayOffsetX="); pw.print(mLastWallpaperDisplayOffsetX); + pw.print(" mLastWallpaperDisplayOffsetY="); + pw.println(mLastWallpaperDisplayOffsetY); + } if (mInputMethodAnimLayerAdjustment != 0 || mWallpaperAnimLayerAdjustment != 0) { pw.print(" mInputMethodAnimLayerAdjustment="); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index e74de38..0baa2be 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -247,6 +247,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { float mWallpaperXStep = -1; float mWallpaperYStep = -1; + // If a window showing a wallpaper: a raw pixel offset to forcibly apply + // to its window; if a wallpaper window: not used. + int mWallpaperDisplayOffsetX = Integer.MIN_VALUE; + int mWallpaperDisplayOffsetY = Integer.MIN_VALUE; + // Wallpaper windows: pixels offset based on above variables. int mXOffset; int mYOffset; @@ -1584,6 +1589,13 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.print(prefix); pw.print("mWallpaperXStep="); pw.print(mWallpaperXStep); pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep); } + if (mWallpaperDisplayOffsetX != Integer.MIN_VALUE + || mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + pw.print(prefix); pw.print("mWallpaperDisplayOffsetX="); + pw.print(mWallpaperDisplayOffsetX); + pw.print(" mWallpaperDisplayOffsetY="); + pw.println(mWallpaperDisplayOffsetY); + } } String makeInputChannelName() { diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 3d4be12..dd611ce 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1526,8 +1526,9 @@ class WindowStateAnimator { } void setWallpaperOffset(RectF shownFrame) { - final int left = (int) shownFrame.left; - final int top = (int) shownFrame.top; + final LayoutParams attrs = mWin.getAttrs(); + final int left = ((int) shownFrame.left) - attrs.surfaceInsets.left; + final int top = ((int) shownFrame.top) - attrs.surfaceInsets.top; if (mSurfaceX != left || mSurfaceY != top) { mSurfaceX = left; mSurfaceY = top; @@ -1556,11 +1557,11 @@ class WindowStateAnimator { } } - void setOpaque(boolean isOpaque) { + void setOpaqueLocked(boolean isOpaque) { if (mSurfaceControl == null) { return; } - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setOpaque"); + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setOpaqueLocked"); SurfaceControl.openTransaction(); try { if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin, "isOpaque=" + isOpaque, @@ -1568,7 +1569,7 @@ class WindowStateAnimator { mSurfaceControl.setOpaque(isOpaque); } finally { SurfaceControl.closeTransaction(); - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setOpaque"); + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setOpaqueLocked"); } } diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index 1f377c7..d81cdd9 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -11,7 +11,6 @@ LOCAL_SRC_FILES += \ $(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \ $(LOCAL_REL_DIR)/com_android_server_ConsumerIrService.cpp \ $(LOCAL_REL_DIR)/com_android_server_hdmi_HdmiCecController.cpp \ - $(LOCAL_REL_DIR)/com_android_server_hdmi_HdmiMhlController.cpp \ $(LOCAL_REL_DIR)/com_android_server_input_InputApplicationHandle.cpp \ $(LOCAL_REL_DIR)/com_android_server_input_InputManagerService.cpp \ $(LOCAL_REL_DIR)/com_android_server_input_InputWindowHandle.cpp \ diff --git a/services/core/jni/com_android_server_UsbHostManager.cpp b/services/core/jni/com_android_server_UsbHostManager.cpp index bc866d3..65a28c0 100644 --- a/services/core/jni/com_android_server_UsbHostManager.cpp +++ b/services/core/jni/com_android_server_UsbHostManager.cpp @@ -21,7 +21,6 @@ #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include "android_runtime/Log.h" -#include "utils/Vector.h" #include <usbhost/usbhost.h> @@ -68,8 +67,6 @@ static int usb_device_added(const char *devname, void* client_data) { JNIEnv* env = AndroidRuntime::getJNIEnv(); jobject thiz = (jobject)client_data; - Vector<int> interfaceValues; - Vector<int> endpointValues; const usb_device_descriptor* deviceDesc = usb_device_get_device_descriptor(device); char *manufacturer = usb_device_get_manufacturer_name(device); diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 8ed74be..cddca92 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -71,7 +71,7 @@ static struct { jmethodID notifyANR; jmethodID filterInputEvent; jmethodID interceptKeyBeforeQueueing; - jmethodID interceptWakeMotionBeforeQueueing; + jmethodID interceptMotionBeforeQueueingNonInteractive; jmethodID interceptKeyBeforeDispatching; jmethodID dispatchUnhandledKey; jmethodID checkInjectEventsPermission; @@ -854,7 +854,9 @@ void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent, handleInterceptActions(wmActions, when, /*byref*/ policyFlags); } else { - policyFlags |= POLICY_FLAG_PASS_TO_USER; + if (mInteractive) { + policyFlags |= POLICY_FLAG_PASS_TO_USER; + } } } @@ -870,20 +872,22 @@ void NativeInputManager::interceptMotionBeforeQueueing(nsecs_t when, uint32_t& p if ((policyFlags & POLICY_FLAG_TRUSTED) && !(policyFlags & POLICY_FLAG_INJECTED)) { if (policyFlags & POLICY_FLAG_INTERACTIVE) { policyFlags |= POLICY_FLAG_PASS_TO_USER; - } else if (policyFlags & POLICY_FLAG_WAKE) { + } else { JNIEnv* env = jniEnv(); jint wmActions = env->CallIntMethod(mServiceObj, - gServiceClassInfo.interceptWakeMotionBeforeQueueing, + gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive, when, policyFlags); if (checkAndClearExceptionFromCallback(env, - "interceptWakeMotionBeforeQueueing")) { + "interceptMotionBeforeQueueingNonInteractive")) { wmActions = 0; } handleInterceptActions(wmActions, when, /*byref*/ policyFlags); } } else { - policyFlags |= POLICY_FLAG_PASS_TO_USER; + if (mInteractive) { + policyFlags |= POLICY_FLAG_PASS_TO_USER; + } } } @@ -1441,8 +1445,8 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.interceptKeyBeforeQueueing, clazz, "interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;I)I"); - GET_METHOD_ID(gServiceClassInfo.interceptWakeMotionBeforeQueueing, clazz, - "interceptWakeMotionBeforeQueueing", "(JI)I"); + GET_METHOD_ID(gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive, clazz, + "interceptMotionBeforeQueueingNonInteractive", "(JI)I"); GET_METHOD_ID(gServiceClassInfo.interceptKeyBeforeDispatching, clazz, "interceptKeyBeforeDispatching", diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 39b70a8..7b2e408 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -38,7 +38,6 @@ int register_android_server_location_GpsLocationProvider(JNIEnv* env); int register_android_server_location_FlpHardwareProvider(JNIEnv* env); int register_android_server_connectivity_Vpn(JNIEnv* env); int register_android_server_hdmi_HdmiCecController(JNIEnv* env); -int register_android_server_hdmi_HdmiMhlController(JNIEnv* env); int register_android_server_tv_TvInputHal(JNIEnv* env); int register_android_server_PersistentDataBlockService(JNIEnv* env); int register_android_server_fingerprint_FingerprintService(JNIEnv* env); @@ -76,7 +75,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) register_android_server_ConsumerIrService(env); register_android_server_BatteryStatsService(env); register_android_server_hdmi_HdmiCecController(env); - register_android_server_hdmi_HdmiMhlController(env); register_android_server_tv_TvInputHal(env); register_android_server_PersistentDataBlockService(env); register_android_server_fingerprint_FingerprintService(env); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 8800ba9..5ad9825 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3001,10 +3001,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - synchronized (this) { - DevicePolicyData policy = getUserData(userHandle); - long ident = Binder.clearCallingIdentity(); - try { + long ident = Binder.clearCallingIdentity(); + try { + boolean wipeData = false; + int identifier = 0; + synchronized (this) { + DevicePolicyData policy = getUserData(userHandle); policy.mFailedPasswordAttempts++; saveSettingsLocked(userHandle); if (mHasFeature) { @@ -3016,15 +3018,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Wipe the user/profile associated with the policy that was violated. This // is not necessarily calling user: if the policy that fired was from a // managed profile rather than the main user profile, we wipe former only. - wipeDeviceOrUserLocked(0, strictestAdmin.getUserHandle().getIdentifier()); + wipeData = true; + identifier = strictestAdmin.getUserHandle().getIdentifier(); } sendAdminCommandToSelfAndProfilesLocked( DeviceAdminReceiver.ACTION_PASSWORD_FAILED, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, userHandle); } - } finally { - Binder.restoreCallingIdentity(ident); } + if (wipeData) { + // Call without holding lock. + wipeDeviceOrUserLocked(0, identifier); + } + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -3659,11 +3666,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null); - if (mUserManager.getUserInfo(userHandle) == null) { + UserInfo info = mUserManager.getUserInfo(userHandle); + if (info == null) { // User doesn't exist. throw new IllegalArgumentException( "Attempted to set profile owner for invalid userId: " + userHandle); } + if (info.isGuest()) { + throw new IllegalStateException("Cannot set a profile owner on a guest"); + } if (who == null || !DeviceOwner.isInstalledForUser(who.getPackageName(), userHandle)) { diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java index 64242ba..6785cb8 100644 --- a/services/print/java/com/android/server/print/PrintManagerService.java +++ b/services/print/java/com/android/server/print/PrintManagerService.java @@ -627,26 +627,40 @@ public final class PrintManagerService extends SystemService { return userState; } - private void handleUserStarted(int userId) { - UserState userState; - synchronized (mLock) { - userState = getOrCreateUserStateLocked(userId); - userState.updateIfNeededLocked(); - } - // This is the first time we switch to this user after boot, so - // now is the time to remove obsolete print jobs since they - // are from the last boot and no application would query them. - userState.removeObsoletePrintJobs(); + private void handleUserStarted(final int userId) { + // This code will touch the remote print spooler which + // must be called off the main thread, so post the work. + BackgroundThread.getHandler().post(new Runnable() { + @Override + public void run() { + UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(userId); + userState.updateIfNeededLocked(); + } + // This is the first time we switch to this user after boot, so + // now is the time to remove obsolete print jobs since they + // are from the last boot and no application would query them. + userState.removeObsoletePrintJobs(); + } + }); } - private void handleUserStopped(int userId) { - synchronized (mLock) { - UserState userState = mUserStates.get(userId); - if (userState != null) { - userState.destroyLocked(); - mUserStates.remove(userId); + private void handleUserStopped(final int userId) { + // This code will touch the remote print spooler which + // must be called off the main thread, so post the work. + BackgroundThread.getHandler().post(new Runnable() { + @Override + public void run() { + synchronized (mLock) { + UserState userState = mUserStates.get(userId); + if (userState != null) { + userState.destroyLocked(); + mUserStates.remove(userId); + } + } } - } + }); } private int resolveCallingProfileParentLocked(int userId) { diff --git a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java index fb29b6a..218f899 100644 --- a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java +++ b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java @@ -139,7 +139,7 @@ public final class RestrictionsManagerService extends SystemService { } @Override - public Intent getLocalApprovalIntent() throws RemoteException { + public Intent createLocalApprovalIntent() throws RemoteException { if (DEBUG) { Log.i(LOG_TAG, "requestPermission"); } diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index dc036e2..5f639ab 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -15,9 +15,11 @@ */ package com.android.server.usage; +import android.app.usage.ConfigurationStats; import android.app.usage.TimeSparseArray; import android.app.usage.UsageEvents; import android.app.usage.UsageStats; +import android.content.res.Configuration; import android.util.ArrayMap; import android.util.ArraySet; @@ -25,7 +27,9 @@ class IntervalStats { public long beginTime; public long endTime; public long lastTimeSaved; - public final ArrayMap<String, UsageStats> stats = new ArrayMap<>(); + public final ArrayMap<String, UsageStats> packageStats = new ArrayMap<>(); + public final ArrayMap<Configuration, ConfigurationStats> configurations = new ArrayMap<>(); + public Configuration activeConfiguration; public TimeSparseArray<UsageEvents.Event> events; // A string cache. This is important as when we're parsing XML files, we don't want to @@ -34,18 +38,49 @@ class IntervalStats { // strings that had identical copies in the cache. private final ArraySet<String> mStringCache = new ArraySet<>(); + /** + * Gets the UsageStats object for the given package, or creates one and adds it internally. + */ UsageStats getOrCreateUsageStats(String packageName) { - UsageStats usageStats = stats.get(packageName); + UsageStats usageStats = packageStats.get(packageName); if (usageStats == null) { usageStats = new UsageStats(); - usageStats.mPackageName = packageName; + usageStats.mPackageName = getCachedStringRef(packageName); usageStats.mBeginTimeStamp = beginTime; usageStats.mEndTimeStamp = endTime; - stats.put(packageName, usageStats); + packageStats.put(usageStats.mPackageName, usageStats); } return usageStats; } + /** + * Gets the ConfigurationStats object for the given configuration, or creates one and adds it + * internally. + */ + ConfigurationStats getOrCreateConfigurationStats(Configuration config) { + ConfigurationStats configStats = configurations.get(config); + if (configStats == null) { + configStats = new ConfigurationStats(); + configStats.mBeginTimeStamp = beginTime; + configStats.mEndTimeStamp = endTime; + configStats.mConfiguration = config; + configurations.put(config, configStats); + } + return configStats; + } + + /** + * Builds a UsageEvents.Event, but does not add it internally. + */ + UsageEvents.Event buildEvent(String packageName, String className) { + UsageEvents.Event event = new UsageEvents.Event(); + event.mPackage = getCachedStringRef(packageName); + if (className != null) { + event.mClass = getCachedStringRef(className); + } + return event; + } + void update(String packageName, long timeStamp, int eventType) { UsageStats usageStats = getOrCreateUsageStats(packageName); @@ -61,6 +96,28 @@ class IntervalStats { usageStats.mLastEvent = eventType; usageStats.mLastTimeUsed = timeStamp; usageStats.mEndTimeStamp = timeStamp; + + if (eventType == UsageEvents.Event.MOVE_TO_FOREGROUND) { + usageStats.mLaunchCount += 1; + } + + endTime = timeStamp; + } + + void updateConfigurationStats(Configuration config, long timeStamp) { + if (activeConfiguration != null) { + ConfigurationStats activeStats = configurations.get(activeConfiguration); + activeStats.mTotalTimeActive += timeStamp - activeStats.mLastTimeActive; + activeStats.mLastTimeActive = timeStamp - 1; + } + + if (config != null) { + ConfigurationStats configStats = getOrCreateConfigurationStats(config); + configStats.mLastTimeActive = timeStamp; + configStats.mActivationCount += 1; + activeConfiguration = configStats.mConfiguration; + } + endTime = timeStamp; } @@ -72,13 +129,4 @@ class IntervalStats { } return mStringCache.valueAt(index); } - - UsageEvents.Event buildEvent(String packageName, String className) { - UsageEvents.Event event = new UsageEvents.Event(); - event.mPackage = getCachedStringRef(packageName); - if (className != null) { - event.mClass = getCachedStringRef(className); - } - return event; - } } diff --git a/services/usage/java/com/android/server/usage/UnixCalendar.java b/services/usage/java/com/android/server/usage/UnixCalendar.java new file mode 100644 index 0000000..ce06a91 --- /dev/null +++ b/services/usage/java/com/android/server/usage/UnixCalendar.java @@ -0,0 +1,99 @@ +/** + * 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.usage; + +import android.app.usage.UsageStatsManager; + +/** + * A handy calendar object that knows nothing of Locale's or TimeZones. This simplifies + * interval book-keeping. It is *NOT* meant to be used as a user-facing calendar, as it has + * no concept of Locale or TimeZone. + */ +public class UnixCalendar { + private static final long DAY_IN_MILLIS = 24 * 60 * 60 * 1000; + private static final long WEEK_IN_MILLIS = 7 * DAY_IN_MILLIS; + private static final long MONTH_IN_MILLIS = 30 * DAY_IN_MILLIS; + private static final long YEAR_IN_MILLIS = 365 * DAY_IN_MILLIS; + private long mTime; + + public UnixCalendar(long time) { + mTime = time; + } + + public void truncateToDay() { + mTime -= mTime % DAY_IN_MILLIS; + } + + public void truncateToWeek() { + mTime -= mTime % WEEK_IN_MILLIS; + } + + public void truncateToMonth() { + mTime -= mTime % MONTH_IN_MILLIS; + } + + public void truncateToYear() { + mTime -= mTime % YEAR_IN_MILLIS; + } + + public void addDays(int val) { + mTime += val * DAY_IN_MILLIS; + } + + public void addWeeks(int val) { + mTime += val * WEEK_IN_MILLIS; + } + + public void addMonths(int val) { + mTime += val * MONTH_IN_MILLIS; + } + + public void addYears(int val) { + mTime += val * YEAR_IN_MILLIS; + } + + public void setTimeInMillis(long time) { + mTime = time; + } + + public long getTimeInMillis() { + return mTime; + } + + public static void truncateTo(UnixCalendar calendar, int intervalType) { + switch (intervalType) { + case UsageStatsManager.INTERVAL_YEARLY: + calendar.truncateToYear(); + break; + + case UsageStatsManager.INTERVAL_MONTHLY: + calendar.truncateToMonth(); + break; + + case UsageStatsManager.INTERVAL_WEEKLY: + calendar.truncateToWeek(); + break; + + case UsageStatsManager.INTERVAL_DAILY: + calendar.truncateToDay(); + break; + + default: + throw new UnsupportedOperationException("Can't truncate date to interval " + + intervalType); + } + } +} diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index e6ce0fe..62a7ec0 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -17,29 +17,34 @@ package com.android.server.usage; import android.app.usage.TimeSparseArray; -import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; import android.util.AtomicFile; import android.util.Slog; +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; -import java.util.Calendar; import java.util.List; /** * Provides an interface to query for UsageStat data from an XML database. */ class UsageStatsDatabase { + private static final int CURRENT_VERSION = 2; + private static final String TAG = "UsageStatsDatabase"; private static final boolean DEBUG = UsageStatsService.DEBUG; private final Object mLock = new Object(); private final File[] mIntervalDirs; private final TimeSparseArray<AtomicFile>[] mSortedStatFiles; - private final Calendar mCal; + private final UnixCalendar mCal; + private final File mVersionFile; public UsageStatsDatabase(File dir) { mIntervalDirs = new File[] { @@ -48,8 +53,9 @@ class UsageStatsDatabase { new File(dir, "monthly"), new File(dir, "yearly"), }; + mVersionFile = new File(dir, "version"); mSortedStatFiles = new TimeSparseArray[mIntervalDirs.length]; - mCal = Calendar.getInstance(); + mCal = new UnixCalendar(0); } /** @@ -65,6 +71,8 @@ class UsageStatsDatabase { } } + checkVersionLocked(); + final FilenameFilter backupFileFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { @@ -82,7 +90,45 @@ class UsageStatsDatabase { } for (File f : files) { - mSortedStatFiles[i].put(Long.parseLong(f.getName()), new AtomicFile(f)); + final AtomicFile af = new AtomicFile(f); + mSortedStatFiles[i].put(UsageStatsXml.parseBeginTime(af), af); + } + } + } + } + } + + private void checkVersionLocked() { + int version; + try (BufferedReader reader = new BufferedReader(new FileReader(mVersionFile))) { + version = Integer.parseInt(reader.readLine()); + } catch (NumberFormatException | IOException e) { + version = 0; + } + + if (version != CURRENT_VERSION) { + Slog.i(TAG, "Upgrading from version " + version + " to " + CURRENT_VERSION); + doUpgradeLocked(version); + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(mVersionFile))) { + writer.write(Integer.toString(CURRENT_VERSION)); + } catch (IOException e) { + Slog.e(TAG, "Failed to write new version"); + throw new RuntimeException(e); + } + } + } + + private void doUpgradeLocked(int thisVersion) { + if (thisVersion < 2) { + // Delete all files if we are version 0. This is a pre-release version, + // so this is fine. + Slog.i(TAG, "Deleting all usage stats files"); + for (int i = 0; i < mIntervalDirs.length; i++) { + File[] files = mIntervalDirs[i].listFiles(); + if (files != null) { + for (File f : files) { + f.delete(); } } } @@ -133,33 +179,77 @@ class UsageStatsDatabase { } /** - * Find all {@link UsageStats} for the given range and interval type. + * Figures out what to extract from the given IntervalStats object. */ - public List<UsageStats> queryUsageStats(int intervalType, long beginTime, long endTime) { + interface StatCombiner<T> { + + /** + * Implementations should extract interesting from <code>stats</code> and add it + * to the <code>accumulatedResult</code> list. + * + * If the <code>stats</code> object is mutable, <code>mutable</code> will be true, + * which means you should make a copy of the data before adding it to the + * <code>accumulatedResult</code> list. + * + * @param stats The {@link IntervalStats} object selected. + * @param mutable Whether or not the data inside the stats object is mutable. + * @param accumulatedResult The list to which to add extracted data. + */ + void combine(IntervalStats stats, boolean mutable, List<T> accumulatedResult); + } + + /** + * Find all {@link IntervalStats} for the given range and interval type. + */ + public <T> List<T> queryUsageStats(int intervalType, long beginTime, long endTime, + StatCombiner<T> combiner) { synchronized (mLock) { if (intervalType < 0 || intervalType >= mIntervalDirs.length) { throw new IllegalArgumentException("Bad interval type " + intervalType); } - if (endTime < beginTime) { + final TimeSparseArray<AtomicFile> intervalStats = mSortedStatFiles[intervalType]; + + if (endTime <= beginTime) { + if (DEBUG) { + Slog.d(TAG, "endTime(" + endTime + ") <= beginTime(" + beginTime + ")"); + } return null; } - final int startIndex = mSortedStatFiles[intervalType].closestIndexOnOrBefore(beginTime); + int startIndex = intervalStats.closestIndexOnOrBefore(beginTime); if (startIndex < 0) { - return null; + // All the stats available have timestamps after beginTime, which means they all + // match. + startIndex = 0; } - int endIndex = mSortedStatFiles[intervalType].closestIndexOnOrAfter(endTime); + int endIndex = intervalStats.closestIndexOnOrBefore(endTime); if (endIndex < 0) { - endIndex = mSortedStatFiles[intervalType].size() - 1; + // All the stats start after this range ends, so nothing matches. + if (DEBUG) { + Slog.d(TAG, "No results for this range. All stats start after."); + } + return null; + } + + if (intervalStats.keyAt(endIndex) == endTime) { + // The endTime is exclusive, so if we matched exactly take the one before. + endIndex--; + if (endIndex < 0) { + // All the stats start after this range ends, so nothing matches. + if (DEBUG) { + Slog.d(TAG, "No results for this range. All stats start after."); + } + return null; + } } try { IntervalStats stats = new IntervalStats(); - ArrayList<UsageStats> results = new ArrayList<>(); + ArrayList<T> results = new ArrayList<>(); for (int i = startIndex; i <= endIndex; i++) { - final AtomicFile f = mSortedStatFiles[intervalType].valueAt(i); + final AtomicFile f = intervalStats.valueAt(i); if (DEBUG) { Slog.d(TAG, "Reading stat file " + f.getBaseFile().getAbsolutePath()); @@ -167,7 +257,7 @@ class UsageStatsDatabase { UsageStatsXml.read(f, stats); if (beginTime < stats.endTime) { - results.addAll(stats.stats.values()); + combiner.combine(stats, false, results); } } return results; @@ -209,19 +299,23 @@ class UsageStatsDatabase { public void prune() { synchronized (mLock) { long timeNow = System.currentTimeMillis(); + mCal.setTimeInMillis(timeNow); + mCal.addYears(-3); + pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_YEARLY], + mCal.getTimeInMillis()); mCal.setTimeInMillis(timeNow); - mCal.add(Calendar.MONTH, -6); + mCal.addMonths(-6); pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_MONTHLY], mCal.getTimeInMillis()); mCal.setTimeInMillis(timeNow); - mCal.add(Calendar.WEEK_OF_YEAR, -4); + mCal.addWeeks(-4); pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_WEEKLY], mCal.getTimeInMillis()); mCal.setTimeInMillis(timeNow); - mCal.add(Calendar.DAY_OF_YEAR, -7); + mCal.addDays(-7); pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_DAILY], mCal.getTimeInMillis()); } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 0e8b427..2dcdcc4 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -18,6 +18,7 @@ package com.android.server.usage; import android.Manifest; import android.app.AppOpsManager; +import android.app.usage.ConfigurationStats; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; import android.app.usage.UsageStats; @@ -30,11 +31,14 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; +import android.content.res.Configuration; import android.os.Binder; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.util.ArraySet; @@ -59,9 +63,7 @@ public class UsageStatsService extends SystemService implements static final boolean DEBUG = false; private static final long TEN_SECONDS = 10 * 1000; private static final long TWENTY_MINUTES = 20 * 60 * 1000; - private static final long TWO_MINUTES = 2 * 60 * 1000; private static final long FLUSH_INTERVAL = DEBUG ? TEN_SECONDS : TWENTY_MINUTES; - private static final long END_TIME_DELAY = DEBUG ? 0 : TWO_MINUTES; // Handler message types. static final int MSG_REPORT_EVENT = 0; @@ -75,6 +77,8 @@ public class UsageStatsService extends SystemService implements private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>(); private File mUsageStatsDir; + long mRealTimeSnapshot; + long mSystemTimeSnapshot; public UsageStatsService(Context context) { super(context); @@ -101,6 +105,9 @@ public class UsageStatsService extends SystemService implements cleanUpRemovedUsersLocked(); } + mRealTimeSnapshot = SystemClock.elapsedRealtime(); + mSystemTimeSnapshot = System.currentTimeMillis(); + publishLocalService(UsageStatsManagerInternal.class, new LocalService()); publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService()); } @@ -175,7 +182,7 @@ public class UsageStatsService extends SystemService implements } /** - * Called by the Bunder stub + * Called by the Binder stub */ void shutdown() { synchronized (mLock) { @@ -218,8 +225,7 @@ public class UsageStatsService extends SystemService implements * Called by the Binder stub. */ List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime) { - final long timeNow = System.currentTimeMillis(); - if (beginTime > timeNow) { + if (!validRange(beginTime, endTime)) { return null; } @@ -232,15 +238,23 @@ public class UsageStatsService extends SystemService implements /** * Called by the Binder stub. */ - UsageEvents queryEvents(int userId, long beginTime, long endTime) { - final long timeNow = System.currentTimeMillis(); + List<ConfigurationStats> queryConfigurationStats(int userId, int bucketType, long beginTime, + long endTime) { + if (!validRange(beginTime, endTime)) { + return null; + } - // Adjust the endTime so that we don't query for the latest events. - // This is to prevent apps from making decision based on what app launched them, - // etc. - endTime = Math.min(endTime, timeNow - END_TIME_DELAY); + synchronized (mLock) { + UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId); + return service.queryConfigurationStats(bucketType, beginTime, endTime); + } + } - if (beginTime > endTime) { + /** + * Called by the Binder stub. + */ + UsageEvents queryEvents(int userId, long beginTime, long endTime) { + if (!validRange(beginTime, endTime)) { return null; } @@ -250,6 +264,11 @@ public class UsageStatsService extends SystemService implements } } + private static boolean validRange(long beginTime, long endTime) { + final long timeNow = System.currentTimeMillis(); + return beginTime <= timeNow && beginTime < endTime; + } + private void flushToDiskLocked() { final int userCount = mUserState.size(); for (int i = 0; i < userCount; i++) { @@ -323,6 +342,28 @@ public class UsageStatsService extends SystemService implements } @Override + public ParceledListSlice<ConfigurationStats> queryConfigurationStats(int bucketType, + long beginTime, long endTime, String callingPackage) throws RemoteException { + if (!hasPermission(callingPackage)) { + return null; + } + + final int userId = UserHandle.getCallingUserId(); + final long token = Binder.clearCallingIdentity(); + try { + final List<ConfigurationStats> results = + UsageStatsService.this.queryConfigurationStats(userId, bucketType, + beginTime, endTime); + if (results != null) { + return new ParceledListSlice<>(results); + } + } finally { + Binder.restoreCallingIdentity(token); + } + return null; + } + + @Override public UsageEvents queryEvents(long beginTime, long endTime, String callingPackage) { if (!hasPermission(callingPackage)) { return null; @@ -345,9 +386,16 @@ public class UsageStatsService extends SystemService implements */ private class LocalService extends UsageStatsManagerInternal { + /** + * The system may have its time change, so at least make sure the events + * are monotonic in order. + */ + private long computeMonotonicSystemTime(long realTime) { + return (realTime - mRealTimeSnapshot) + mSystemTimeSnapshot; + } + @Override - public void reportEvent(ComponentName component, int userId, - long timeStamp, int eventType) { + public void reportEvent(ComponentName component, int userId, int eventType) { if (component == null) { Slog.w(TAG, "Event reported without a component name"); return; @@ -356,12 +404,27 @@ public class UsageStatsService extends SystemService implements UsageEvents.Event event = new UsageEvents.Event(); event.mPackage = component.getPackageName(); event.mClass = component.getClassName(); - event.mTimeStamp = timeStamp; + event.mTimeStamp = computeMonotonicSystemTime(SystemClock.elapsedRealtime()); event.mEventType = eventType; mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @Override + public void reportConfigurationChange(Configuration config, int userId) { + if (config == null) { + Slog.w(TAG, "Configuration event reported with a null config"); + return; + } + + UsageEvents.Event event = new UsageEvents.Event(); + event.mPackage = "android"; + event.mTimeStamp = computeMonotonicSystemTime(SystemClock.elapsedRealtime()); + event.mEventType = UsageEvents.Event.CONFIGURATION_CHANGE; + event.mConfiguration = new Configuration(config); + mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); + } + + @Override public void prepareShutdown() { // This method *WILL* do IO work, but we must block until it is finished or else // we might not shutdown cleanly. This is ok to do with the 'am' lock held, because diff --git a/services/usage/java/com/android/server/usage/UsageStatsUtils.java b/services/usage/java/com/android/server/usage/UsageStatsUtils.java deleted file mode 100644 index dd5f3b9..0000000 --- a/services/usage/java/com/android/server/usage/UsageStatsUtils.java +++ /dev/null @@ -1,63 +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.usage; - -import android.app.usage.UsageStatsManager; - -import java.util.Calendar; - -/** - * A collection of utility methods used by the UsageStatsService and accompanying classes. - */ -final class UsageStatsUtils { - private UsageStatsUtils() {} - - /** - * Truncates the date to the given UsageStats bucket. For example, if the bucket is - * {@link UsageStatsManager#INTERVAL_YEARLY}, the date is truncated to the 1st day of the year, - * with the time set to 00:00:00. - * - * @param bucket The UsageStats bucket to truncate to. - * @param cal The date to truncate. - */ - public static void truncateDateTo(int bucket, Calendar cal) { - cal.set(Calendar.HOUR_OF_DAY, 0); - cal.set(Calendar.MINUTE, 0); - cal.set(Calendar.SECOND, 0); - cal.set(Calendar.MILLISECOND, 0); - - switch (bucket) { - case UsageStatsManager.INTERVAL_YEARLY: - cal.set(Calendar.DAY_OF_YEAR, 0); - break; - - case UsageStatsManager.INTERVAL_MONTHLY: - cal.set(Calendar.DAY_OF_MONTH, 0); - break; - - case UsageStatsManager.INTERVAL_WEEKLY: - cal.set(Calendar.DAY_OF_WEEK, 0); - break; - - case UsageStatsManager.INTERVAL_DAILY: - break; - - default: - throw new UnsupportedOperationException("Can't truncate date to bucket " + bucket); - } - } -} diff --git a/services/usage/java/com/android/server/usage/UsageStatsXml.java b/services/usage/java/com/android/server/usage/UsageStatsXml.java index 48881d0..9ce6d63 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXml.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXml.java @@ -37,10 +37,15 @@ public class UsageStatsXml { private static final String USAGESTATS_TAG = "usagestats"; private static final String VERSION_ATTR = "version"; + public static long parseBeginTime(AtomicFile file) { + return Long.parseLong(file.getBaseFile().getName()); + } + public static void read(AtomicFile file, IntervalStats statsOut) throws IOException { try { FileInputStream in = file.openRead(); try { + statsOut.beginTime = parseBeginTime(file); read(in, statsOut); statsOut.lastTimeSaved = file.getLastModifiedTime(); } finally { diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java index 374429a..ef95a7b 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java @@ -15,16 +15,17 @@ */ package com.android.server.usage; -import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; +import android.app.usage.ConfigurationStats; import android.app.usage.TimeSparseArray; import android.app.usage.UsageEvents; import android.app.usage.UsageStats; -import android.content.ComponentName; +import android.content.res.Configuration; import java.io.IOException; import java.net.ProtocolException; @@ -33,103 +34,144 @@ import java.net.ProtocolException; * UsageStats reader/writer for version 1 of the XML format. */ final class UsageStatsXmlV1 { - private static final String BEGIN_TIME_ATTR = "beginTime"; - private static final String END_TIME_ATTR = "endTime"; + private static final String PACKAGES_TAG = "packages"; private static final String PACKAGE_TAG = "package"; - private static final String NAME_ATTR = "name"; + + private static final String CONFIGURATIONS_TAG = "configurations"; + private static final String CONFIG_TAG = "config"; + + private static final String EVENT_LOG_TAG = "event-log"; + private static final String EVENT_TAG = "event"; + + // Attributes private static final String PACKAGE_ATTR = "package"; private static final String CLASS_ATTR = "class"; - private static final String TOTAL_TIME_ACTIVE_ATTR = "totalTimeActive"; - private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive"; + private static final String TOTAL_TIME_ACTIVE_ATTR = "timeActive"; + private static final String COUNT_ATTR = "count"; + private static final String ACTIVE_ATTR = "active"; private static final String LAST_EVENT_ATTR = "lastEvent"; - private static final String EVENT_LOG_TAG = "event-log"; private static final String TYPE_ATTR = "type"; + + // Time attributes stored as an offset of the beginTime. + private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive"; + private static final String END_TIME_ATTR = "endTime"; private static final String TIME_ATTR = "time"; - private static UsageStats readNextUsageStats(XmlPullParser parser) + private static void loadUsageStats(XmlPullParser parser, IntervalStats statsOut) throws XmlPullParserException, IOException { - if (parser.getEventType() != XmlPullParser.START_TAG) { - XmlUtils.nextElement(parser); + final String pkg = parser.getAttributeValue(null, PACKAGE_ATTR); + if (pkg == null) { + throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present"); } - if (parser.getEventType() != XmlPullParser.START_TAG || - !parser.getName().equals(PACKAGE_TAG)) { - return null; - } + final UsageStats stats = statsOut.getOrCreateUsageStats(pkg); - final String name = parser.getAttributeValue(null, NAME_ATTR); - if (name == null) { - throw new ProtocolException("no " + NAME_ATTR + " attribute present"); - } + // Apply the offset to the beginTime to find the absolute time. + stats.mLastTimeUsed = statsOut.beginTime + XmlUtils.readLongAttribute( + parser, LAST_TIME_ACTIVE_ATTR); - UsageStats stats = new UsageStats(); - stats.mPackageName = name; stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR); - stats.mLastTimeUsed = XmlUtils.readLongAttribute(parser, LAST_TIME_ACTIVE_ATTR); stats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR); - XmlUtils.skipCurrentTag(parser); - return stats; } - private static UsageEvents.Event readNextEvent(XmlPullParser parser, IntervalStats statsOut) + private static void loadConfigStats(XmlPullParser parser, IntervalStats statsOut) throws XmlPullParserException, IOException { - if (parser.getEventType() != XmlPullParser.START_TAG) { - XmlUtils.nextElement(parser); - } + final Configuration config = new Configuration(); + Configuration.readXmlAttrs(parser, config); - if (parser.getEventType() != XmlPullParser.START_TAG || - !parser.getName().equals(EVENT_LOG_TAG)) { - return null; + final ConfigurationStats configStats = statsOut.getOrCreateConfigurationStats(config); + + // Apply the offset to the beginTime to find the absolute time. + configStats.mLastTimeActive = statsOut.beginTime + XmlUtils.readLongAttribute( + parser, LAST_TIME_ACTIVE_ATTR); + + configStats.mTotalTimeActive = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR); + configStats.mActivationCount = XmlUtils.readIntAttribute(parser, COUNT_ATTR); + if (XmlUtils.readBooleanAttribute(parser, ACTIVE_ATTR)) { + statsOut.activeConfiguration = configStats.mConfiguration; } + } - String packageName = XmlUtils.readStringAttribute(parser, PACKAGE_ATTR); - String className; + private static void loadEvent(XmlPullParser parser, IntervalStats statsOut) + throws XmlPullParserException, IOException { + final String packageName = XmlUtils.readStringAttribute(parser, PACKAGE_ATTR); if (packageName == null) { - // Try getting the component name if it exists. - final String componentName = XmlUtils.readStringAttribute(parser, NAME_ATTR); - if (componentName == null) { - throw new ProtocolException("no " + NAME_ATTR + " or " + PACKAGE_ATTR + - " attribute present"); - } - ComponentName component = ComponentName.unflattenFromString(componentName); - if (component == null) { - throw new ProtocolException("ComponentName " + componentName + " is invalid"); - } - - packageName = component.getPackageName(); - className = component.getClassName(); - } else { - className = XmlUtils.readStringAttribute(parser, CLASS_ATTR); + throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present"); } - UsageEvents.Event event = statsOut.buildEvent(packageName, className); + final String className = XmlUtils.readStringAttribute(parser, CLASS_ATTR); + + final UsageEvents.Event event = statsOut.buildEvent(packageName, className); + + // Apply the offset to the beginTime to find the absolute time of this event. + event.mTimeStamp = statsOut.beginTime + XmlUtils.readLongAttribute(parser, TIME_ATTR); + event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR); - event.mTimeStamp = XmlUtils.readLongAttribute(parser, TIME_ATTR); - XmlUtils.skipCurrentTag(parser); - return event; + if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) { + event.mConfiguration = new Configuration(); + Configuration.readXmlAttrs(parser, event.mConfiguration); + } + + if (statsOut.events == null) { + statsOut.events = new TimeSparseArray<>(); + } + statsOut.events.put(event.mTimeStamp, event); + } + + private static void writeUsageStats(XmlSerializer xml, final IntervalStats stats, + final UsageStats usageStats) throws IOException { + xml.startTag(null, PACKAGE_TAG); + + // Write the time offset. + XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR, + usageStats.mLastTimeUsed - stats.beginTime); + + XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName); + XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground); + XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.mLastEvent); + + xml.endTag(null, PACKAGE_TAG); } - private static void writeUsageStats(FastXmlSerializer serializer, UsageStats stats) - throws IOException { - serializer.startTag(null, PACKAGE_TAG); - serializer.attribute(null, NAME_ATTR, stats.mPackageName); - serializer.attribute(null, TOTAL_TIME_ACTIVE_ATTR, - Long.toString(stats.mTotalTimeInForeground)); - serializer.attribute(null, LAST_TIME_ACTIVE_ATTR, Long.toString(stats.mLastTimeUsed)); - serializer.attribute(null, LAST_EVENT_ATTR, Integer.toString(stats.mLastEvent)); - serializer.endTag(null, PACKAGE_TAG); + private static void writeConfigStats(XmlSerializer xml, final IntervalStats stats, + final ConfigurationStats configStats, boolean isActive) throws IOException { + xml.startTag(null, CONFIG_TAG); + + // Write the time offset. + XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR, + configStats.mLastTimeActive - stats.beginTime); + + XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, configStats.mTotalTimeActive); + XmlUtils.writeIntAttribute(xml, COUNT_ATTR, configStats.mActivationCount); + if (isActive) { + XmlUtils.writeBooleanAttribute(xml, ACTIVE_ATTR, true); + } + + // Now write the attributes representing the configuration object. + Configuration.writeXmlAttrs(xml, configStats.mConfiguration); + + xml.endTag(null, CONFIG_TAG); } - private static void writeEvent(FastXmlSerializer serializer, UsageEvents.Event event) - throws IOException { - serializer.startTag(null, EVENT_LOG_TAG); - serializer.attribute(null, PACKAGE_ATTR, event.mPackage); + private static void writeEvent(XmlSerializer xml, final IntervalStats stats, + final UsageEvents.Event event) throws IOException { + xml.startTag(null, EVENT_TAG); + + // Store the time offset. + XmlUtils.writeLongAttribute(xml, TIME_ATTR, event.mTimeStamp - stats.beginTime); + + XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, event.mPackage); if (event.mClass != null) { - serializer.attribute(null, CLASS_ATTR, event.mClass); + XmlUtils.writeStringAttribute(xml, CLASS_ATTR, event.mClass); + } + XmlUtils.writeIntAttribute(xml, TYPE_ATTR, event.mEventType); + + if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE + && event.mConfiguration != null) { + Configuration.writeXmlAttrs(xml, event.mConfiguration); } - serializer.attribute(null, TYPE_ATTR, Integer.toString(event.getEventType())); - serializer.attribute(null, TIME_ATTR, Long.toString(event.getTimeStamp())); - serializer.endTag(null, EVENT_LOG_TAG); + + xml.endTag(null, EVENT_TAG); } /** @@ -141,55 +183,74 @@ final class UsageStatsXmlV1 { */ public static void read(XmlPullParser parser, IntervalStats statsOut) throws XmlPullParserException, IOException { - statsOut.stats.clear(); + statsOut.packageStats.clear(); + statsOut.configurations.clear(); + statsOut.activeConfiguration = null; if (statsOut.events != null) { statsOut.events.clear(); } - statsOut.beginTime = XmlUtils.readLongAttribute(parser, BEGIN_TIME_ATTR); statsOut.endTime = XmlUtils.readLongAttribute(parser, END_TIME_ATTR); - XmlUtils.nextElement(parser); - UsageStats pkgStats; - while ((pkgStats = readNextUsageStats(parser)) != null) { - pkgStats.mBeginTimeStamp = statsOut.beginTime; - pkgStats.mEndTimeStamp = statsOut.endTime; - statsOut.stats.put(pkgStats.mPackageName, pkgStats); - } + int eventCode; + int outerDepth = parser.getDepth(); + while ((eventCode = parser.next()) != XmlPullParser.END_DOCUMENT + && (eventCode != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (eventCode != XmlPullParser.START_TAG) { + continue; + } - UsageEvents.Event event; - while ((event = readNextEvent(parser, statsOut)) != null) { - if (statsOut.events == null) { - statsOut.events = new TimeSparseArray<>(); + final String tag = parser.getName(); + switch (tag) { + case PACKAGE_TAG: + loadUsageStats(parser, statsOut); + break; + + case CONFIG_TAG: + loadConfigStats(parser, statsOut); + break; + + case EVENT_TAG: + loadEvent(parser, statsOut); + break; } - statsOut.events.put(event.getTimeStamp(), event); } } /** - * Writes the stats object to an XML file. The {@link FastXmlSerializer} + * Writes the stats object to an XML file. The {@link XmlSerializer} * has already written the <code><usagestats></code> tag, but attributes may still * be added. * - * @param serializer The serializer to which to write the stats data. + * @param xml The serializer to which to write the packageStats data. * @param stats The stats object to write to the XML file. */ - public static void write(FastXmlSerializer serializer, IntervalStats stats) throws IOException { - serializer.attribute(null, BEGIN_TIME_ATTR, Long.toString(stats.beginTime)); - serializer.attribute(null, END_TIME_ATTR, Long.toString(stats.endTime)); + public static void write(XmlSerializer xml, IntervalStats stats) throws IOException { + XmlUtils.writeLongAttribute(xml, END_TIME_ATTR, stats.endTime - stats.beginTime); - final int statsCount = stats.stats.size(); + xml.startTag(null, PACKAGES_TAG); + final int statsCount = stats.packageStats.size(); for (int i = 0; i < statsCount; i++) { - writeUsageStats(serializer, stats.stats.valueAt(i)); + writeUsageStats(xml, stats, stats.packageStats.valueAt(i)); } + xml.endTag(null, PACKAGES_TAG); - if (stats.events != null) { - final int eventCount = stats.events.size(); - for (int i = 0; i < eventCount; i++) { - writeEvent(serializer, stats.events.valueAt(i)); - } + + xml.startTag(null, CONFIGURATIONS_TAG); + final int configCount = stats.configurations.size(); + for (int i = 0; i < configCount; i++) { + boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i)); + writeConfigStats(xml, stats, stats.configurations.valueAt(i), active); + } + xml.endTag(null, CONFIGURATIONS_TAG); + + xml.startTag(null, EVENT_LOG_TAG); + final int eventCount = stats.events != null ? stats.events.size() : 0; + for (int i = 0; i < eventCount; i++) { + writeEvent(xml, stats, stats.events.valueAt(i)); } + xml.endTag(null, EVENT_LOG_TAG); } private UsageStatsXmlV1() { diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 6951590..2769666 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -16,19 +16,22 @@ package com.android.server.usage; +import android.app.usage.ConfigurationStats; import android.app.usage.TimeSparseArray; import android.app.usage.UsageEvents; import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; +import android.content.res.Configuration; import android.util.ArraySet; import android.util.Slog; +import com.android.server.usage.UsageStatsDatabase.StatCombiner; + import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Calendar; import java.util.List; /** @@ -43,7 +46,7 @@ class UserUsageStatsService { private final UsageStatsDatabase mDatabase; private final IntervalStats[] mCurrentStats; private boolean mStatsChanged = false; - private final Calendar mDailyExpiryDate; + private final UnixCalendar mDailyExpiryDate; private final StatsUpdatedListener mListener; private final String mLogPrefix; @@ -52,7 +55,7 @@ class UserUsageStatsService { } UserUsageStatsService(int userId, File usageStatsDir, StatsUpdatedListener listener) { - mDailyExpiryDate = Calendar.getInstance(); + mDailyExpiryDate = new UnixCalendar(0); mDatabase = new UsageStatsDatabase(usageStatsDir); mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT]; mListener = listener; @@ -62,6 +65,7 @@ class UserUsageStatsService { void init() { mDatabase.init(); + final long timeNow = System.currentTimeMillis(); int nullCount = 0; for (int i = 0; i < mCurrentStats.length; i++) { mCurrentStats[i] = mDatabase.getLatestUsageStats(i); @@ -69,6 +73,11 @@ class UserUsageStatsService { // Find out how many intervals we don't have data for. // Ideally it should be all or none. nullCount++; + } else if (mCurrentStats[i].beginTime > timeNow) { + Slog.e(TAG, mLogPrefix + "Interval " + i + " has stat in the future " + + mCurrentStats[i].beginTime); + mCurrentStats[i] = null; + nullCount++; } } @@ -90,17 +99,18 @@ class UserUsageStatsService { // that is reported. mDailyExpiryDate.setTimeInMillis( mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime); - mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1); - UsageStatsUtils.truncateDateTo(UsageStatsManager.INTERVAL_DAILY, mDailyExpiryDate); - Slog.i(TAG, mLogPrefix + "Rollover scheduled for " - + sDateFormat.format(mDailyExpiryDate.getTime())); + mDailyExpiryDate.addDays(1); + mDailyExpiryDate.truncateToDay(); + Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " + + sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + + "(" + mDailyExpiryDate.getTimeInMillis() + ")"); } // Now close off any events that were open at the time this was saved. for (IntervalStats stat : mCurrentStats) { - final int pkgCount = stat.stats.size(); + final int pkgCount = stat.packageStats.size(); for (int i = 0; i < pkgCount; i++) { - UsageStats pkgStats = stat.stats.valueAt(i); + UsageStats pkgStats = stat.packageStats.valueAt(i); if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND || pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) { stat.update(pkgStats.mPackageName, stat.lastTimeSaved, @@ -108,121 +118,202 @@ class UserUsageStatsService { notifyStatsChanged(); } } + + stat.updateConfigurationStats(null, stat.lastTimeSaved); } } void reportEvent(UsageEvents.Event event) { if (DEBUG) { Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage - + "[" + event.getTimeStamp() + "]: " - + eventToString(event.getEventType())); + + "[" + event.mTimeStamp + "]: " + + eventToString(event.mEventType)); } - if (event.getTimeStamp() >= mDailyExpiryDate.getTimeInMillis()) { + if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) { // Need to rollover rolloverStats(); } - if (mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events == null) { - mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events = new TimeSparseArray<>(); + final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY]; + + final Configuration newFullConfig = event.mConfiguration; + if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE && + currentDailyStats.activeConfiguration != null) { + // Make the event configuration a delta. + event.mConfiguration = Configuration.generateDelta( + currentDailyStats.activeConfiguration, newFullConfig); + } + + // Add the event to the daily list. + if (currentDailyStats.events == null) { + currentDailyStats.events = new TimeSparseArray<>(); } - mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events.put(event.getTimeStamp(), event); + currentDailyStats.events.put(event.mTimeStamp, event); for (IntervalStats stats : mCurrentStats) { - stats.update(event.mPackage, event.getTimeStamp(), - event.getEventType()); + if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) { + stats.updateConfigurationStats(newFullConfig, event.mTimeStamp); + } else { + stats.update(event.mPackage, event.mTimeStamp, event.mEventType); + } } notifyStatsChanged(); } - List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) { - if (bucketType == UsageStatsManager.INTERVAL_BEST) { - bucketType = mDatabase.findBestFitBucket(beginTime, endTime); + private static final StatCombiner<UsageStats> sUsageStatsCombiner = + new StatCombiner<UsageStats>() { + @Override + public void combine(IntervalStats stats, boolean mutable, + List<UsageStats> accResult) { + if (!mutable) { + accResult.addAll(stats.packageStats.values()); + return; + } + + final int statCount = stats.packageStats.size(); + for (int i = 0; i < statCount; i++) { + accResult.add(new UsageStats(stats.packageStats.valueAt(i))); + } + } + }; + + private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner = + new StatCombiner<ConfigurationStats>() { + @Override + public void combine(IntervalStats stats, boolean mutable, + List<ConfigurationStats> accResult) { + if (!mutable) { + accResult.addAll(stats.configurations.values()); + return; + } + + final int configCount = stats.configurations.size(); + for (int i = 0; i < configCount; i++) { + accResult.add(new ConfigurationStats(stats.configurations.valueAt(i))); + } + } + }; + + /** + * Generic query method that selects the appropriate IntervalStats for the specified time range + * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner} + * provided to select the stats to use from the IntervalStats object. + */ + private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime, + StatCombiner<T> combiner) { + if (intervalType == UsageStatsManager.INTERVAL_BEST) { + intervalType = mDatabase.findBestFitBucket(beginTime, endTime); + if (intervalType < 0) { + // Nothing saved to disk yet, so every stat is just as equal (no rollover has + // occurred. + intervalType = UsageStatsManager.INTERVAL_DAILY; + } } - if (bucketType < 0 || bucketType >= mCurrentStats.length) { + if (intervalType < 0 || intervalType >= mCurrentStats.length) { if (DEBUG) { - Slog.d(TAG, mLogPrefix + "Bad bucketType used " + bucketType); + Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType); } return null; } - if (beginTime >= mCurrentStats[bucketType].endTime) { + final IntervalStats currentStats = mCurrentStats[intervalType]; + + if (DEBUG) { + Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= " + + beginTime + " AND endTime < " + endTime); + } + + if (beginTime >= currentStats.endTime) { if (DEBUG) { Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is " - + mCurrentStats[bucketType].endTime); + + currentStats.endTime); } // Nothing newer available. return null; - - } else if (beginTime >= mCurrentStats[bucketType].beginTime) { - if (DEBUG) { - Slog.d(TAG, mLogPrefix + "Returning in-memory stats for bucket " + bucketType); - } - // Fast path for retrieving in-memory state. - ArrayList<UsageStats> results = new ArrayList<>(); - final int packageCount = mCurrentStats[bucketType].stats.size(); - for (int i = 0; i < packageCount; i++) { - results.add(new UsageStats(mCurrentStats[bucketType].stats.valueAt(i))); - } - return results; } - // Flush any changes that were made to disk before we do a disk query. - // If we're not grabbing the ongoing stats, no need to persist. - persistActiveStats(); + // Truncate the endTime to just before the in-memory stats. Then, we'll append the + // in-memory stats to the results (if necessary) so as to avoid writing to disk too + // often. + final long truncatedEndTime = Math.min(currentStats.beginTime, endTime); + // Get the stats from disk. + List<T> results = mDatabase.queryUsageStats(intervalType, beginTime, + truncatedEndTime, combiner); if (DEBUG) { - Slog.d(TAG, mLogPrefix + "SELECT * FROM " + bucketType + " WHERE beginTime >= " - + beginTime + " AND endTime < " + endTime); + Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk"); + Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime + + " endTime=" + currentStats.endTime); + } + + // Now check if the in-memory stats match the range and add them if they do. + if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) { + if (DEBUG) { + Slog.d(TAG, mLogPrefix + "Returning in-memory stats"); + } + + if (results == null) { + results = new ArrayList<>(); + } + combiner.combine(currentStats, true, results); } - final List<UsageStats> results = mDatabase.queryUsageStats(bucketType, beginTime, endTime); if (DEBUG) { - Slog.d(TAG, mLogPrefix + "Results: " + (results == null ? 0 : results.size())); + Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0)); } return results; } - UsageEvents queryEvents(long beginTime, long endTime) { - if (endTime > mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime) { - if (beginTime > mCurrentStats[UsageStatsManager.INTERVAL_DAILY].endTime) { - return null; - } + List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) { + return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner); + } - TimeSparseArray<UsageEvents.Event> events = - mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events; - if (events == null) { - return null; - } + List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) { + return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner); + } - final int startIndex = events.closestIndexOnOrAfter(beginTime); - if (startIndex < 0) { - return null; - } + UsageEvents queryEvents(final long beginTime, final long endTime) { + final ArraySet<String> names = new ArraySet<>(); + List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY, + beginTime, endTime, new StatCombiner<UsageEvents.Event>() { + @Override + public void combine(IntervalStats stats, boolean mutable, + List<UsageEvents.Event> accumulatedResult) { + if (stats.events == null) { + return; + } + + final int startIndex = stats.events.closestIndexOnOrAfter(beginTime); + if (startIndex < 0) { + return; + } + + final int size = stats.events.size(); + for (int i = startIndex; i < size; i++) { + if (stats.events.keyAt(i) >= endTime) { + return; + } + + final UsageEvents.Event event = stats.events.valueAt(i); + names.add(event.mPackage); + if (event.mClass != null) { + names.add(event.mClass); + } + accumulatedResult.add(event); + } + } + }); - ArraySet<String> names = new ArraySet<>(); - ArrayList<UsageEvents.Event> results = new ArrayList<>(); - final int size = events.size(); - for (int i = startIndex; i < size; i++) { - if (events.keyAt(i) >= endTime) { - break; - } - final UsageEvents.Event event = events.valueAt(i); - names.add(event.mPackage); - if (event.mClass != null) { - names.add(event.mClass); - } - results.add(event); - } - String[] table = names.toArray(new String[names.size()]); - Arrays.sort(table); - return new UsageEvents(results, table); + if (results == null || results.isEmpty()) { + return null; } - // TODO(adamlesinski): Query the previous days. - return null; + String[] table = names.toArray(new String[names.size()]); + Arrays.sort(table); + return new UsageEvents(results, table); } void persistActiveStats() { @@ -245,19 +336,23 @@ class UserUsageStatsService { // Finish any ongoing events with an END_OF_DAY event. Make a note of which components // need a new CONTINUE_PREVIOUS_DAY entry. + final Configuration previousConfig = + mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration; ArraySet<String> continuePreviousDay = new ArraySet<>(); for (IntervalStats stat : mCurrentStats) { - final int pkgCount = stat.stats.size(); + final int pkgCount = stat.packageStats.size(); for (int i = 0; i < pkgCount; i++) { - UsageStats pkgStats = stat.stats.valueAt(i); + UsageStats pkgStats = stat.packageStats.valueAt(i); if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND || pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) { continuePreviousDay.add(pkgStats.mPackageName); - stat.update(pkgStats.mPackageName, - mDailyExpiryDate.getTimeInMillis() - 1, UsageEvents.Event.END_OF_DAY); + stat.update(pkgStats.mPackageName, mDailyExpiryDate.getTimeInMillis() - 1, + UsageEvents.Event.END_OF_DAY); mStatsChanged = true; } } + + stat.updateConfigurationStats(null, mDailyExpiryDate.getTimeInMillis() - 1); } persistActiveStats(); @@ -267,9 +362,10 @@ class UserUsageStatsService { final int continueCount = continuePreviousDay.size(); for (int i = 0; i < continueCount; i++) { String name = continuePreviousDay.valueAt(i); + final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime; for (IntervalStats stat : mCurrentStats) { - stat.update(name, mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime, - UsageEvents.Event.CONTINUE_PREVIOUS_DAY); + stat.update(name, beginTime, UsageEvents.Event.CONTINUE_PREVIOUS_DAY); + stat.updateConfigurationStats(previousConfig, beginTime); mStatsChanged = true; } } @@ -290,54 +386,53 @@ class UserUsageStatsService { private void loadActiveStats() { final long timeNow = System.currentTimeMillis(); - Calendar tempCal = mDailyExpiryDate; - for (int bucketType = 0; bucketType < mCurrentStats.length; bucketType++) { + final UnixCalendar tempCal = mDailyExpiryDate; + for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) { tempCal.setTimeInMillis(timeNow); - UsageStatsUtils.truncateDateTo(bucketType, tempCal); + UnixCalendar.truncateTo(tempCal, intervalType); - if (mCurrentStats[bucketType] != null && - mCurrentStats[bucketType].beginTime == tempCal.getTimeInMillis()) { + if (mCurrentStats[intervalType] != null && + mCurrentStats[intervalType].beginTime == tempCal.getTimeInMillis()) { // These are the same, no need to load them (in memory stats are always newer // than persisted stats). continue; } - final long lastBeginTime = mDatabase.getLatestUsageStatsBeginTime(bucketType); - if (lastBeginTime >= tempCal.getTimeInMillis()) { + final long lastBeginTime = mDatabase.getLatestUsageStatsBeginTime(intervalType); + if (lastBeginTime > timeNow) { + Slog.e(TAG, mLogPrefix + "Latest usage stats for interval " + + intervalType + " begins in the future"); + mCurrentStats[intervalType] = null; + } else if (lastBeginTime >= tempCal.getTimeInMillis()) { if (DEBUG) { - Slog.d(TAG, mLogPrefix + "Loading existing stats (" + lastBeginTime + - ") for bucket " + bucketType); - } - mCurrentStats[bucketType] = mDatabase.getLatestUsageStats(bucketType); - if (DEBUG) { - if (mCurrentStats[bucketType] != null) { - Slog.d(TAG, mLogPrefix + "Found " + - (mCurrentStats[bucketType].events == null ? - 0 : mCurrentStats[bucketType].events.size()) + - " events"); - } + Slog.d(TAG, mLogPrefix + "Loading existing stats @ " + + sDateFormat.format(lastBeginTime) + "(" + lastBeginTime + + ") for interval " + intervalType); } + mCurrentStats[intervalType] = mDatabase.getLatestUsageStats(intervalType); } else { - mCurrentStats[bucketType] = null; + mCurrentStats[intervalType] = null; } - if (mCurrentStats[bucketType] == null) { + if (mCurrentStats[intervalType] == null) { if (DEBUG) { - Slog.d(TAG, "Creating new stats (" + tempCal.getTimeInMillis() + - ") for bucket " + bucketType); + Slog.d(TAG, "Creating new stats @ " + + sDateFormat.format(tempCal.getTimeInMillis()) + "(" + + tempCal.getTimeInMillis() + ") for interval " + intervalType); } - mCurrentStats[bucketType] = new IntervalStats(); - mCurrentStats[bucketType].beginTime = tempCal.getTimeInMillis(); - mCurrentStats[bucketType].endTime = timeNow; + mCurrentStats[intervalType] = new IntervalStats(); + mCurrentStats[intervalType].beginTime = tempCal.getTimeInMillis(); + mCurrentStats[intervalType].endTime = timeNow; } } mStatsChanged = false; mDailyExpiryDate.setTimeInMillis(timeNow); - mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1); - UsageStatsUtils.truncateDateTo(UsageStatsManager.INTERVAL_DAILY, mDailyExpiryDate); - Slog.i(TAG, mLogPrefix + "Rollover scheduled for " - + sDateFormat.format(mDailyExpiryDate.getTime())); + mDailyExpiryDate.addDays(1); + mDailyExpiryDate.truncateToDay(); + Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " + + sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" + + tempCal.getTimeInMillis() + ")"); } @@ -353,6 +448,8 @@ class UserUsageStatsService { return "END_OF_DAY"; case UsageEvents.Event.CONTINUE_PREVIOUS_DAY: return "CONTINUE_PREVIOUS_DAY"; + case UsageEvents.Event.CONFIGURATION_CHANGE: + return "CONFIGURATION_CHANGE"; default: return "UNKNOWN"; } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java index 376230b..ad38b22 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java @@ -67,7 +67,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { final ModuleProperties moduleProperties; /** The properties for the DSP module */ - private final SoundTriggerModule mModule; + private SoundTriggerModule mModule; private final Object mLock = new Object(); private final Context mContext; private final TelephonyManager mTelephonyManager; @@ -105,7 +105,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } else { // TODO: Figure out how to determine which module corresponds to the DSP hardware. moduleProperties = modules.get(0); - mModule = SoundTrigger.attachModule(moduleProperties.id, this, null); } } @@ -155,10 +154,17 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { mIsPowerSaveMode = mPowerManager.isPowerSaveMode(); } - if (moduleProperties == null || mModule == null) { + if (moduleProperties == null) { Slog.w(TAG, "Attempting startRecognition without the capability"); return STATUS_ERROR; } + if (mModule == null) { + mModule = SoundTrigger.attachModule(moduleProperties.id, this, null); + if (mModule == null) { + Slog.w(TAG, "startRecognition cannot attach to sound trigger module"); + return STATUS_ERROR; + } + } if (mCurrentSoundModelHandle != INVALID_VALUE && !soundModel.uuid.equals(mCurrentSoundModelUuid)) { @@ -446,6 +452,10 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { Slog.w(TAG, "RemoteException in onError", e); } finally { internalClearStateLocked(); + if (mModule != null) { + mModule.detach(); + mModule = null; + } } } |