diff options
author | Ricardo Cerqueira <ricardo@cyngn.com> | 2015-11-05 02:27:28 +0000 |
---|---|---|
committer | Ricardo Cerqueira <ricardo@cyngn.com> | 2015-11-05 15:13:26 +0000 |
commit | a89168479cb14f482268af6f1a650b28eba0b393 (patch) | |
tree | 8552b9d49ecfad6a453ee5f743571b700b4d3d3c /services | |
parent | 8331d3e508498a19c296fed41b9d4b23ea960031 (diff) | |
parent | 25b5096f154721c8142d54f1b3af9e2998deffe9 (diff) | |
download | frameworks_base-a89168479cb14f482268af6f1a650b28eba0b393.zip frameworks_base-a89168479cb14f482268af6f1a650b28eba0b393.tar.gz frameworks_base-a89168479cb14f482268af6f1a650b28eba0b393.tar.bz2 |
Merge tag 'android-6.0.0_r26' into HEAD
Android 6.0.0 release 26
Conflicts:
cmds/bootanimation/BootAnimation.cpp
core/java/android/accounts/AccountManager.java
core/java/android/app/AppOpsManager.java
core/java/android/os/PowerManagerInternal.java
core/java/android/os/storage/IMountService.java
core/java/android/provider/Settings.java
core/java/com/android/internal/widget/ILockSettings.aidl
core/res/res/values-mcc204-mnc12/config.xml
core/res/res/values-mcc219-mnc02/config.xml
core/res/res/values-mcc730-mnc07/config.xml
core/res/res/values/config.xml
core/res/res/values/symbols.xml
packages/SystemUI/res/values/config.xml
packages/SystemUI/src/com/android/systemui/doze/DozeService.java
packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
services/core/java/com/android/server/LocationManagerService.java
services/core/java/com/android/server/am/ActivityManagerService.java
services/core/java/com/android/server/pm/PackageManagerService.java
services/core/java/com/android/server/power/PowerManagerService.java
telecomm/java/android/telecom/Phone.java
telephony/java/android/telephony/CarrierConfigManager.java
telephony/java/android/telephony/RadioAccessFamily.java
telephony/java/android/telephony/ServiceState.java
telephony/java/android/telephony/SignalStrength.java
telephony/java/android/telephony/TelephonyManager.java
telephony/java/com/android/ims/ImsCallProfile.java
telephony/java/com/android/ims/ImsReasonInfo.java
telephony/java/com/android/ims/ImsSuppServiceNotification.aidl
telephony/java/com/android/ims/ImsSuppServiceNotification.java
telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl
telephony/java/com/android/internal/telephony/RILConstants.java
Change-Id: I99c6edb8e25a77145b5adef97d0d55bfbe676959
Diffstat (limited to 'services')
63 files changed, 4301 insertions, 730 deletions
diff --git a/services/core/Android.mk b/services/core/Android.mk index 54dade1..a00bc73 100644 --- a/services/core/Android.mk +++ b/services/core/Android.mk @@ -9,7 +9,7 @@ LOCAL_SRC_FILES += \ java/com/android/server/EventLogTags.logtags \ java/com/android/server/am/EventLogTags.logtags -LOCAL_JAVA_LIBRARIES := telephony-common +LOCAL_JAVA_LIBRARIES := services.net telephony-common LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update LOCAL_JAVA_LIBRARIES += services.accessibility diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 8a84246..94c2e75 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -130,6 +130,7 @@ public final class BatteryService extends SystemService { private int mLastBatteryVoltage; private int mLastBatteryTemperature; private boolean mLastBatteryLevelCritical; + private int mLastMaxChargingCurrent; private int mInvalidCharger; private int mLastInvalidCharger; @@ -351,6 +352,7 @@ public final class BatteryService extends SystemService { + "chargerAcOnline=" + mBatteryProps.chargerAcOnline + ", chargerUsbOnline=" + mBatteryProps.chargerUsbOnline + ", chargerWirelessOnline=" + mBatteryProps.chargerWirelessOnline + + ", maxChargingCurrent" + mBatteryProps.maxChargingCurrent + ", batteryStatus=" + mBatteryProps.batteryStatus + ", batteryHealth=" + mBatteryProps.batteryHealth + ", batteryPresent=" + mBatteryProps.batteryPresent @@ -381,6 +383,7 @@ public final class BatteryService extends SystemService { mPlugType != mLastPlugType || mBatteryProps.batteryVoltage != mLastBatteryVoltage || mBatteryProps.batteryTemperature != mLastBatteryTemperature || + mBatteryProps.maxChargingCurrent != mLastMaxChargingCurrent || mInvalidCharger != mLastInvalidCharger)) { if (mPlugType != mLastPlugType) { @@ -507,6 +510,7 @@ public final class BatteryService extends SystemService { mLastPlugType = mPlugType; mLastBatteryVoltage = mBatteryProps.batteryVoltage; mLastBatteryTemperature = mBatteryProps.batteryTemperature; + mLastMaxChargingCurrent = mBatteryProps.maxChargingCurrent; mLastBatteryLevelCritical = mBatteryLevelCritical; mLastInvalidCharger = mInvalidCharger; } @@ -531,17 +535,21 @@ public final class BatteryService extends SystemService { intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryProps.batteryTemperature); intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryProps.batteryTechnology); intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger); + intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mBatteryProps.maxChargingCurrent); if (DEBUG) { Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. level:" + mBatteryProps.batteryLevel + ", scale:" + BATTERY_SCALE + ", status:" + mBatteryProps.batteryStatus + - ", health:" + mBatteryProps.batteryHealth + ", present:" + mBatteryProps.batteryPresent + + ", health:" + mBatteryProps.batteryHealth + + ", present:" + mBatteryProps.batteryPresent + ", voltage: " + mBatteryProps.batteryVoltage + ", temperature: " + mBatteryProps.batteryTemperature + ", technology: " + mBatteryProps.batteryTechnology + - ", AC powered:" + mBatteryProps.chargerAcOnline + ", USB powered:" + mBatteryProps.chargerUsbOnline + + ", AC powered:" + mBatteryProps.chargerAcOnline + + ", USB powered:" + mBatteryProps.chargerUsbOnline + ", Wireless powered:" + mBatteryProps.chargerWirelessOnline + - ", icon:" + icon + ", invalid charger:" + mInvalidCharger); + ", icon:" + icon + ", invalid charger:" + mInvalidCharger + + ", maxChargingCurrent:" + mBatteryProps.maxChargingCurrent); } mHandler.post(new Runnable() { @@ -646,6 +654,7 @@ public final class BatteryService extends SystemService { pw.println(" AC powered: " + mBatteryProps.chargerAcOnline); pw.println(" USB powered: " + mBatteryProps.chargerUsbOnline); pw.println(" Wireless powered: " + mBatteryProps.chargerWirelessOnline); + pw.println(" Max charging current: " + mBatteryProps.maxChargingCurrent); pw.println(" status: " + mBatteryProps.batteryStatus); pw.println(" health: " + mBatteryProps.batteryHealth); pw.println(" present: " + mBatteryProps.batteryPresent); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index ecfd45d..aae73fe 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -47,6 +47,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; import android.net.ConnectivityManager; +import android.net.ConnectivityManager.PacketKeepalive; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; import android.net.INetworkPolicyListener; @@ -118,6 +119,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; import com.android.server.am.BatteryStatsService; import com.android.server.connectivity.DataConnectionStats; +import com.android.server.connectivity.KeepaliveTracker; import com.android.server.connectivity.NetworkDiagnostics; import com.android.server.connectivity.Nat464Xlat; import com.android.server.connectivity.NetworkAgentInfo; @@ -149,6 +151,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -356,6 +360,9 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31; + /** Handler thread used for both of the handlers below. */ + @VisibleForTesting + protected final HandlerThread mHandlerThread; /** Handler used for internal events. */ final private InternalHandler mHandler; /** Handler used for incoming {@link NetworkStateTracker} events. */ @@ -400,6 +407,8 @@ public class ConnectivityService extends IConnectivityManager.Stub TelephonyManager mTelephonyManager; + private KeepaliveTracker mKeepaliveTracker; + // 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; @@ -506,7 +515,6 @@ public class ConnectivityService extends IConnectivityManager.Stub ArrayList<NetworkAgentInfo> list = mTypeLists[type]; if (list.contains(nai)) { - loge("Attempting to register duplicate agent for type " + type + ": " + nai); return; } @@ -610,6 +618,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker(); + @VisibleForTesting + protected HandlerThread createHandlerThread() { + return new HandlerThread("ConnectivityServiceThread"); + } + public ConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager) { if (DBG) log("ConnectivityService starting up"); @@ -623,10 +636,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mDefaultMobileDataRequest = createInternetRequestForTransport( NetworkCapabilities.TRANSPORT_CELLULAR); - HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread"); - handlerThread.start(); - mHandler = new InternalHandler(handlerThread.getLooper()); - mTrackerHandler = new NetworkStateTrackerHandler(handlerThread.getLooper()); + mHandlerThread = createHandlerThread(); + mHandlerThread.start(); + mHandler = new InternalHandler(mHandlerThread.getLooper()); + mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper()); // setup our unique device name String hostname = Settings.Secure.getString(context.getContentResolver(), @@ -770,6 +783,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mPacManager = new PacManager(mContext, mHandler, EVENT_PROXY_HAS_CHANGED); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + + mKeepaliveTracker = new KeepaliveTracker(mHandler); } private NetworkRequest createInternetRequestForTransport(int transportType) { @@ -1456,6 +1471,10 @@ public class ConnectivityService extends IConnectivityManager.Stub "ConnectivityService"); } + private void enforceKeepalivePermission() { + mContext.enforceCallingOrSelfPermission(KeepaliveTracker.PERMISSION, "ConnectivityService"); + } + public void sendConnectedBroadcast(NetworkInfo info) { enforceConnectivityInternalPermission(); sendGeneralBroadcast(info, CONNECTIVITY_ACTION); @@ -1518,10 +1537,14 @@ public class ConnectivityService extends IConnectivityManager.Stub final long ident = Binder.clearCallingIdentity(); if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { + final NetworkInfo ni = intent.getParcelableExtra( + ConnectivityManager.EXTRA_NETWORK_INFO); + if (ni.getType() == ConnectivityManager.TYPE_MOBILE_SUPL) { + intent.setAction(ConnectivityManager.CONNECTIVITY_ACTION_SUPL); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + } final IBatteryStats bs = BatteryStatsService.getService(); try { - NetworkInfo ni = intent.getParcelableExtra( - ConnectivityManager.EXTRA_NETWORK_INFO); bs.noteConnectivityChanged(intent.getIntExtra( ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_NONE), ni != null ? ni.getState().toString() : "?"); @@ -1847,10 +1870,13 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println(", last requested never"); } } - pw.println(); + pw.println(); mTethering.dump(fd, pw, args); + pw.println(); + mKeepaliveTracker.dump(pw); + if (mInetLog != null && mInetLog.size() > 0) { pw.println(); pw.println("Inet condition reports:"); @@ -1928,7 +1954,12 @@ public class ConnectivityService extends IConnectivityManager.Stub (NetworkCapabilities)msg.obj; if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL) || networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { - Slog.wtf(TAG, "BUG: " + nai + " has stateful capability."); + Slog.wtf(TAG, "BUG: " + nai + " has CS-managed capability."); + } + if (nai.created && !nai.networkCapabilities.equalImmutableCapabilities( + networkCapabilities)) { + Slog.wtf(TAG, "BUG: " + nai + " changed immutable capabilities: " + + nai.networkCapabilities + " -> " + networkCapabilities); } updateCapabilities(nai, networkCapabilities); } @@ -2012,6 +2043,15 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.networkMisc.acceptUnvalidated = (boolean) msg.obj; break; } + case NetworkAgent.EVENT_PACKET_KEEPALIVE: { + NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo); + if (nai == null) { + loge("EVENT_PACKET_KEEPALIVE from unknown NetworkAgent"); + break; + } + mKeepaliveTracker.handleEventPacketKeepalive(nai, msg); + break; + } case NetworkMonitor.EVENT_NETWORK_TESTED: { NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj; if (isLiveNetworkAgent(nai, "EVENT_NETWORK_TESTED")) { @@ -2154,6 +2194,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // sending all CALLBACK_LOST messages (for requests, not listens) at the end // of rematchAllNetworksAndRequests notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST); + mKeepaliveTracker.handleStopAllKeepalives(nai, + ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK); nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED); mNetworkAgentInfos.remove(msg.replyTo); updateClat(null, nai.linkProperties, nai); @@ -2232,6 +2274,14 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleRegisterNetworkRequest(NetworkRequestInfo nri) { mNetworkRequests.put(nri.request, nri); mNetworkRequestInfoLogs.log("REGISTER " + nri); + if (!nri.isRequest) { + for (NetworkAgentInfo network : mNetworkAgentInfos.values()) { + if (nri.request.networkCapabilities.hasSignalStrength() && + network.satisfiesImmutableCapabilitiesOf(nri.request)) { + updateSignalStrengthThresholds(network, "REGISTER", nri.request); + } + } + } rematchAllNetworksAndRequests(null, 0); if (nri.isRequest && mNetworkForRequestId.get(nri.request.requestId) == null) { sendUpdatedScoreToFactories(nri.request, 0); @@ -2345,6 +2395,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // if this listen request applies and remove it. for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { nai.networkRequests.remove(nri.request.requestId); + if (nri.request.networkCapabilities.hasSignalStrength() && + nai.satisfiesImmutableCapabilitiesOf(nri.request)) { + updateSignalStrengthThresholds(nai, "RELEASE", nri.request); + } } } callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED); @@ -2511,6 +2565,19 @@ public class ConnectivityService extends IConnectivityManager.Stub handleMobileDataAlwaysOn(); break; } + // Sent by KeepaliveTracker to process an app request on the state machine thread. + case NetworkAgent.CMD_START_PACKET_KEEPALIVE: { + mKeepaliveTracker.handleStartKeepalive(msg); + break; + } + // Sent by KeepaliveTracker to process an app request on the state machine thread. + case NetworkAgent.CMD_STOP_PACKET_KEEPALIVE: { + NetworkAgentInfo nai = getNetworkAgentInfoForNetwork((Network) msg.obj); + int slot = msg.arg1; + int reason = msg.arg2; + mKeepaliveTracker.handleStopKeepalive(nai, slot, reason); + break; + } case EVENT_SYSTEM_READY: { for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { nai.networkMonitor.systemReady = true; @@ -3568,15 +3635,47 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void ensureImmutableCapabilities(NetworkCapabilities networkCapabilities) { - if (networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { - throw new IllegalArgumentException( - "Cannot request network with NET_CAPABILITY_VALIDATED"); + private void ensureRequestableCapabilities(NetworkCapabilities networkCapabilities) { + final String badCapability = networkCapabilities.describeFirstNonRequestableCapability(); + if (badCapability != null) { + throw new IllegalArgumentException("Cannot request network with " + badCapability); + } + } + + private ArrayList<Integer> getSignalStrengthThresholds(NetworkAgentInfo nai) { + final SortedSet<Integer> thresholds = new TreeSet(); + synchronized (nai) { + for (NetworkRequestInfo nri : mNetworkRequests.values()) { + if (nri.request.networkCapabilities.hasSignalStrength() && + nai.satisfiesImmutableCapabilitiesOf(nri.request)) { + thresholds.add(nri.request.networkCapabilities.getSignalStrength()); + } + } } - if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) { - throw new IllegalArgumentException( - "Cannot request network with NET_CAPABILITY_CAPTIVE_PORTAL"); + return new ArrayList<Integer>(thresholds); + } + + private void updateSignalStrengthThresholds( + NetworkAgentInfo nai, String reason, NetworkRequest request) { + ArrayList<Integer> thresholdsArray = getSignalStrengthThresholds(nai); + Bundle thresholds = new Bundle(); + thresholds.putIntegerArrayList("thresholds", thresholdsArray); + + // TODO: Switch to VDBG. + if (DBG) { + String detail; + if (request != null && request.networkCapabilities.hasSignalStrength()) { + detail = reason + " " + request.networkCapabilities.getSignalStrength(); + } else { + detail = reason; + } + log(String.format("updateSignalStrengthThresholds: %s, sending %s to %s", + detail, Arrays.toString(thresholdsArray.toArray()), nai.name())); } + + nai.asyncChannel.sendMessage( + android.net.NetworkAgent.CMD_SET_SIGNAL_STRENGTH_THRESHOLDS, + 0, 0, thresholds); } @Override @@ -3585,7 +3684,7 @@ public class ConnectivityService extends IConnectivityManager.Stub networkCapabilities = new NetworkCapabilities(networkCapabilities); enforceNetworkRequestPermissions(networkCapabilities); enforceMeteredApnPolicy(networkCapabilities); - ensureImmutableCapabilities(networkCapabilities); + ensureRequestableCapabilities(networkCapabilities); if (timeoutMs < 0 || timeoutMs > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS) { throw new IllegalArgumentException("Bad timeout specified"); @@ -3654,7 +3753,7 @@ public class ConnectivityService extends IConnectivityManager.Stub networkCapabilities = new NetworkCapabilities(networkCapabilities); enforceNetworkRequestPermissions(networkCapabilities); enforceMeteredApnPolicy(networkCapabilities); - ensureImmutableCapabilities(networkCapabilities); + ensureRequestableCapabilities(networkCapabilities); NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE, nextNetworkRequestId()); @@ -3880,6 +3979,8 @@ public class ConnectivityService extends IConnectivityManager.Stub notifyIfacesChanged(); notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED); } + + mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent); } private void updateClat(LinkProperties newLp, LinkProperties oldLp, NetworkAgentInfo nai) { @@ -4217,7 +4318,7 @@ public class ConnectivityService extends IConnectivityManager.Stub boolean keep = newNetwork.isVPN(); boolean isNewDefault = false; NetworkAgentInfo oldDefaultNetwork = null; - if (DBG) log("rematching " + newNetwork.name()); + if (VDBG) log("rematching " + newNetwork.name()); // Find and migrate to this Network any NetworkRequests for // which this network is now the best. ArrayList<NetworkAgentInfo> affectedNetworks = new ArrayList<NetworkAgentInfo>(); @@ -4254,6 +4355,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (currentNetwork == null || currentNetwork.getCurrentScore() < newNetwork.getCurrentScore()) { + if (DBG) log("rematch for " + newNetwork.name()); if (currentNetwork != null) { if (DBG) log(" accepting network in place of " + currentNetwork.name()); currentNetwork.networkRequests.remove(nri.request.requestId); @@ -4546,6 +4648,15 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO: support proxy per network. } + // Whether a particular NetworkRequest listen should cause signal strength thresholds to + // be communicated to a particular NetworkAgent depends only on the network's immutable, + // capabilities, so it only needs to be done once on initial connect, not every time the + // network's capabilities change. Note that we do this before rematching the network, + // so we could decide to tear it down immediately afterwards. That's fine though - on + // disconnection NetworkAgents should stop any signal strength monitoring they have been + // doing. + updateSignalStrengthThresholds(networkAgent, "CONNECT", null); + // Consider network even though it is not yet validated. rematchNetworkAndRequests(networkAgent, ReapUnvalidatedNetworks.REAP); @@ -4725,6 +4836,22 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override + public void startNattKeepalive(Network network, int intervalSeconds, Messenger messenger, + IBinder binder, String srcAddr, int srcPort, String dstAddr) { + enforceKeepalivePermission(); + mKeepaliveTracker.startNattKeepalive( + getNetworkAgentInfoForNetwork(network), + intervalSeconds, messenger, binder, + srcAddr, srcPort, dstAddr, ConnectivityManager.PacketKeepalive.NATT_PORT); + } + + @Override + public void stopKeepalive(Network network, int slot) { + mHandler.sendMessage(mHandler.obtainMessage( + NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, slot, PacketKeepalive.SUCCESS, network)); + } + + @Override public void factoryReset() { enforceConnectivityInternalPermission(); diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index 7aebc1a..ab2ea8b 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -244,3 +244,8 @@ option java_package com.android.server # --------------------------- 40000 volume_changed (stream|1), (prev_level|1), (level|1), (max_level|1), (caller|3) 40001 stream_devices_changed (stream|1), (prev_devices|1), (devices|1) + +# --------------------------- +# GestureLauncherService.java +# --------------------------- +40100 camera_gesture_triggered (gesture_on_time|2|3), (sensor1_on_time|2|3), (sensor2_on_time|2|3), (event_extra|1|1) diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java new file mode 100644 index 0000000..2aa0390 --- /dev/null +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.app.ActivityManager; +import android.app.StatusBarManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Handler; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.provider.Settings; +import android.util.Slog; +import android.view.KeyEvent; + +import com.android.internal.logging.MetricsLogger; +import com.android.server.statusbar.StatusBarManagerInternal; + +/** + * The service that listens for gestures detected in sensor firmware and starts the intent + * accordingly. + * <p>For now, only camera launch gesture is supported, and in the future, more gestures can be + * added.</p> + * @hide + */ +public class GestureLauncherService extends SystemService { + private static final boolean DBG = false; + private static final String TAG = "GestureLauncherService"; + + /** + * Time in milliseconds in which the power button must be pressed twice so it will be considered + * as a camera launch. + */ + private static final long CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS = 300; + private static final long CAMERA_POWER_DOUBLE_TAP_MIN_TIME_MS = 120; + + /** The listener that receives the gesture event. */ + private final GestureEventListener mGestureListener = new GestureEventListener(); + + private Sensor mCameraLaunchSensor; + private Context mContext; + + /** The wake lock held when a gesture is detected. */ + private WakeLock mWakeLock; + private boolean mRegistered; + private int mUserId; + + // Below are fields used for event logging only. + /** Elapsed real time when the camera gesture is turned on. */ + private long mCameraGestureOnTimeMs = 0L; + + /** Elapsed real time when the last camera gesture was detected. */ + private long mCameraGestureLastEventTime = 0L; + + /** + * How long the sensor 1 has been turned on since camera launch sensor was + * subscribed to and when the last camera launch gesture was detected. + * <p>Sensor 1 is the main sensor used to detect camera launch gesture.</p> + */ + private long mCameraGestureSensor1LastOnTimeMs = 0L; + + /** + * If applicable, how long the sensor 2 has been turned on since camera + * launch sensor was subscribed to and when the last camera launch + * gesture was detected. + * <p>Sensor 2 is the secondary sensor used to detect camera launch gesture. + * This is optional and if only sensor 1 is used for detect camera launch + * gesture, this value would always be 0.</p> + */ + private long mCameraGestureSensor2LastOnTimeMs = 0L; + + /** + * Extra information about the event when the last camera launch gesture + * was detected. + */ + private int mCameraLaunchLastEventExtra = 0; + + /** + * Whether camera double tap power button gesture is currently enabled; + */ + private boolean mCameraDoubleTapPowerEnabled; + private long mLastPowerDown; + + public GestureLauncherService(Context context) { + super(context); + mContext = context; + } + + public void onStart() { + LocalServices.addService(GestureLauncherService.class, this); + } + + public void onBootPhase(int phase) { + if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { + Resources resources = mContext.getResources(); + if (!isGestureLauncherEnabled(resources)) { + if (DBG) Slog.d(TAG, "Gesture launcher is disabled in system properties."); + return; + } + + PowerManager powerManager = (PowerManager) mContext.getSystemService( + Context.POWER_SERVICE); + mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "GestureLauncherService"); + updateCameraRegistered(); + updateCameraDoubleTapPowerEnabled(); + + mUserId = ActivityManager.getCurrentUser(); + mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED)); + registerContentObservers(); + } + } + + private void registerContentObservers() { + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED), + false, mSettingObserver, mUserId); + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED), + false, mSettingObserver, mUserId); + } + + private void updateCameraRegistered() { + Resources resources = mContext.getResources(); + if (isCameraLaunchSettingEnabled(mContext, mUserId)) { + registerCameraLaunchGesture(resources); + } else { + unregisterCameraLaunchGesture(); + } + } + + private void updateCameraDoubleTapPowerEnabled() { + boolean enabled = isCameraDoubleTapPowerSettingEnabled(mContext, mUserId); + synchronized (this) { + mCameraDoubleTapPowerEnabled = enabled; + } + } + + private void unregisterCameraLaunchGesture() { + if (mRegistered) { + mRegistered = false; + mCameraGestureOnTimeMs = 0L; + mCameraGestureLastEventTime = 0L; + mCameraGestureSensor1LastOnTimeMs = 0; + mCameraGestureSensor2LastOnTimeMs = 0; + mCameraLaunchLastEventExtra = 0; + + SensorManager sensorManager = (SensorManager) mContext.getSystemService( + Context.SENSOR_SERVICE); + sensorManager.unregisterListener(mGestureListener); + } + } + + /** + * Registers for the camera launch gesture. + */ + private void registerCameraLaunchGesture(Resources resources) { + if (mRegistered) { + return; + } + mCameraGestureOnTimeMs = SystemClock.elapsedRealtime(); + mCameraGestureLastEventTime = mCameraGestureOnTimeMs; + SensorManager sensorManager = (SensorManager) mContext.getSystemService( + Context.SENSOR_SERVICE); + int cameraLaunchGestureId = resources.getInteger( + com.android.internal.R.integer.config_cameraLaunchGestureSensorType); + if (cameraLaunchGestureId != -1) { + mRegistered = false; + String sensorName = resources.getString( + com.android.internal.R.string.config_cameraLaunchGestureSensorStringType); + mCameraLaunchSensor = sensorManager.getDefaultSensor( + cameraLaunchGestureId, + true /*wakeUp*/); + + // Compare the camera gesture string type to that in the resource file to make + // sure we are registering the correct sensor. This is redundant check, it + // makes the code more robust. + if (mCameraLaunchSensor != null) { + if (sensorName.equals(mCameraLaunchSensor.getStringType())) { + mRegistered = sensorManager.registerListener(mGestureListener, + mCameraLaunchSensor, 0); + } else { + String message = String.format("Wrong configuration. Sensor type and sensor " + + "string type don't match: %s in resources, %s in the sensor.", + sensorName, mCameraLaunchSensor.getStringType()); + throw new RuntimeException(message); + } + } + if (DBG) Slog.d(TAG, "Camera launch sensor registered: " + mRegistered); + } else { + if (DBG) Slog.d(TAG, "Camera launch sensor is not specified."); + } + } + + public static boolean isCameraLaunchSettingEnabled(Context context, int userId) { + return isCameraLaunchEnabled(context.getResources()) + && (Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.CAMERA_GESTURE_DISABLED, 0, userId) == 0); + } + + public static boolean isCameraDoubleTapPowerSettingEnabled(Context context, int userId) { + return isCameraDoubleTapPowerEnabled(context.getResources()) + && (Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0); + } + + /** + * Whether to enable the camera launch gesture. + */ + public static boolean isCameraLaunchEnabled(Resources resources) { + boolean configSet = resources.getInteger( + com.android.internal.R.integer.config_cameraLaunchGestureSensorType) != -1; + return configSet && + !SystemProperties.getBoolean("gesture.disable_camera_launch", false); + } + + public static boolean isCameraDoubleTapPowerEnabled(Resources resources) { + return resources.getBoolean( + com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled); + } + + /** + * Whether GestureLauncherService should be enabled according to system properties. + */ + public static boolean isGestureLauncherEnabled(Resources resources) { + return isCameraLaunchEnabled(resources) || isCameraDoubleTapPowerEnabled(resources); + } + + public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive) { + boolean launched = false; + boolean intercept = false; + long doubleTapInterval; + synchronized (this) { + doubleTapInterval = event.getEventTime() - mLastPowerDown; + if (mCameraDoubleTapPowerEnabled + && doubleTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS + && doubleTapInterval > CAMERA_POWER_DOUBLE_TAP_MIN_TIME_MS) { + launched = true; + intercept = interactive; + } + mLastPowerDown = event.getEventTime(); + } + if (launched) { + Slog.i(TAG, "Power button double tap gesture detected, launching camera. Interval=" + + doubleTapInterval + "ms"); + launched = handleCameraLaunchGesture(false /* useWakelock */, + StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP); + if (launched) { + MetricsLogger.action(mContext, MetricsLogger.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, + (int) doubleTapInterval); + } + } + MetricsLogger.histogram(mContext, "power_double_tap_interval", (int) doubleTapInterval); + return intercept && launched; + } + + /** + * @return true if camera was launched, false otherwise. + */ + private boolean handleCameraLaunchGesture(boolean useWakelock, int source) { + boolean userSetupComplete = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 0) != 0; + if (!userSetupComplete) { + if (DBG) Slog.d(TAG, String.format( + "userSetupComplete = %s, ignoring camera launch gesture.", + userSetupComplete)); + return false; + } + if (DBG) Slog.d(TAG, String.format( + "userSetupComplete = %s, performing camera launch gesture.", + userSetupComplete)); + + if (useWakelock) { + // Make sure we don't sleep too early + mWakeLock.acquire(500L); + } + StatusBarManagerInternal service = LocalServices.getService( + StatusBarManagerInternal.class); + service.onCameraLaunchGestureDetected(source); + return true; + } + + private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { + mUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); + mContext.getContentResolver().unregisterContentObserver(mSettingObserver); + registerContentObservers(); + updateCameraRegistered(); + updateCameraDoubleTapPowerEnabled(); + } + } + }; + + private final ContentObserver mSettingObserver = new ContentObserver(new Handler()) { + public void onChange(boolean selfChange, android.net.Uri uri, int userId) { + if (userId == mUserId) { + updateCameraRegistered(); + updateCameraDoubleTapPowerEnabled(); + } + } + }; + + private final class GestureEventListener implements SensorEventListener { + @Override + public void onSensorChanged(SensorEvent event) { + if (!mRegistered) { + if (DBG) Slog.d(TAG, "Ignoring gesture event because it's unregistered."); + return; + } + if (event.sensor == mCameraLaunchSensor) { + if (DBG) { + float[] values = event.values; + Slog.d(TAG, String.format("Received a camera launch event: " + + "values=[%.4f, %.4f, %.4f].", values[0], values[1], values[2])); + } + if (handleCameraLaunchGesture(true /* useWakelock */, + StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)) { + MetricsLogger.action(mContext, MetricsLogger.ACTION_WIGGLE_CAMERA_GESTURE); + trackCameraLaunchEvent(event); + } + return; + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Ignored. + } + + private void trackCameraLaunchEvent(SensorEvent event) { + long now = SystemClock.elapsedRealtime(); + long totalDuration = now - mCameraGestureOnTimeMs; + // values[0]: ratio between total time duration when accel is turned on and time + // duration since camera launch gesture is subscribed. + // values[1]: ratio between total time duration when gyro is turned on and time duration + // since camera launch gesture is subscribed. + // values[2]: extra information + float[] values = event.values; + + long sensor1OnTime = (long) (totalDuration * (double) values[0]); + long sensor2OnTime = (long) (totalDuration * (double) values[1]); + int extra = (int) values[2]; + + // We only log the difference in the event log to make aggregation easier. + long gestureOnTimeDiff = now - mCameraGestureLastEventTime; + long sensor1OnTimeDiff = sensor1OnTime - mCameraGestureSensor1LastOnTimeMs; + long sensor2OnTimeDiff = sensor2OnTime - mCameraGestureSensor2LastOnTimeMs; + int extraDiff = extra - mCameraLaunchLastEventExtra; + + // Gating against negative time difference. This doesn't usually happen, but it may + // happen because of numeric errors. + if (gestureOnTimeDiff < 0 || sensor1OnTimeDiff < 0 || sensor2OnTimeDiff < 0) { + if (DBG) Slog.d(TAG, "Skipped event logging because negative numbers."); + return; + } + + if (DBG) Slog.d(TAG, String.format("totalDuration: %d, sensor1OnTime: %s, " + + "sensor2OnTime: %d, extra: %d", + gestureOnTimeDiff, + sensor1OnTimeDiff, + sensor2OnTimeDiff, + extraDiff)); + EventLogTags.writeCameraGestureTriggered( + gestureOnTimeDiff, + sensor1OnTimeDiff, + sensor2OnTimeDiff, + extraDiff); + + mCameraGestureLastEventTime = now; + mCameraGestureSensor1LastOnTimeMs = sensor1OnTime; + mCameraGestureSensor2LastOnTimeMs = sensor2OnTime; + mCameraLaunchLastEventExtra = extra; + } + } +} diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 00e86de..ebe8759 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -579,23 +579,25 @@ public class LocationManagerService extends ILocationManager.Stub { Slog.e(TAG, "Unable to bind FLP Geofence proxy."); } - // bind to the hardware activity recognition if supported - if (ActivityRecognitionHardware.isSupported()) { - ActivityRecognitionProxy proxy = ActivityRecognitionProxy.createAndBind( - mContext, - mLocationHandler, - ActivityRecognitionHardware.getInstance(mContext), - com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay, - com.android.internal.R.string.config_activityRecognitionHardwarePackageName, - com.android.internal.R.array.config_locationProviderPackageNames); - - if (proxy == null) { - Slog.e(TAG, "Unable to bind ActivityRecognitionProxy."); - } - + // bind to hardware activity recognition + boolean activityRecognitionHardwareIsSupported = ActivityRecognitionHardware.isSupported(); + ActivityRecognitionHardware activityRecognitionHardware = null; + if (activityRecognitionHardwareIsSupported) { + activityRecognitionHardware = ActivityRecognitionHardware.getInstance(mContext); } else { Slog.e(TAG, "Hardware Activity-Recognition not supported."); } + ActivityRecognitionProxy proxy = ActivityRecognitionProxy.createAndBind( + mContext, + mLocationHandler, + activityRecognitionHardwareIsSupported, + activityRecognitionHardware, + com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay, + com.android.internal.R.string.config_activityRecognitionHardwarePackageName, + com.android.internal.R.array.config_locationProviderPackageNames); + if (proxy == null) { + Slog.e(TAG, "Unable to bind ActivityRecognitionProxy."); + } mComboNlpPackageName = resources.getString( com.android.internal.R.string.config_comboNetworkLocationProvider); diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index 11b7ebc..1dbb054 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -18,6 +18,7 @@ package com.android.server; import android.app.admin.DevicePolicyManager; import android.app.backup.BackupManager; +import android.app.trust.IStrongAuthTracker; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -28,6 +29,8 @@ import android.content.pm.UserInfo; import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; import static android.content.Context.USER_SERVICE; import static android.Manifest.permission.READ_CONTACTS; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; + import android.database.sqlite.SQLiteDatabase; import android.os.Binder; import android.os.IBinder; @@ -75,6 +78,7 @@ public class LockSettingsService extends ILockSettings.Stub { private final Context mContext; private final LockSettingsStorage mStorage; + private final LockSettingsStrongAuth mStrongAuth = new LockSettingsStrongAuth(); private LockPatternUtils mLockPatternUtils; private boolean mFirstCallToVold; @@ -99,6 +103,7 @@ public class LockSettingsService extends ILockSettings.Stub { filter.addAction(Intent.ACTION_USER_ADDED); filter.addAction(Intent.ACTION_USER_STARTING); filter.addAction(Intent.ACTION_USER_REMOVED); + filter.addAction(Intent.ACTION_USER_PRESENT); mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); mStorage = new LockSettingsStorage(context, new LockSettingsStorage.Callback() { @@ -128,6 +133,8 @@ public class LockSettingsService extends ILockSettings.Stub { } else if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) { final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); mStorage.prefetchUser(userHandle); + } else if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) { + mStrongAuth.reportUnlock(getSendingUserId()); } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) { final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); if (userHandle > 0) { @@ -689,6 +696,10 @@ public class LockSettingsService extends ILockSettings.Stub { if (shouldReEnroll) { credentialUtil.setCredential(credential, credential, userId); } + } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { + if (response.getTimeout() > 0) { + requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId); + } } return response; @@ -743,6 +754,7 @@ public class LockSettingsService extends ILockSettings.Stub { private void removeUser(int userId) { mStorage.removeUser(userId); + mStrongAuth.removeUser(userId); final KeyStore ks = KeyStore.getInstance(); ks.onUserRemoved(userId); @@ -757,6 +769,24 @@ public class LockSettingsService extends ILockSettings.Stub { } } + @Override + public void registerStrongAuthTracker(IStrongAuthTracker tracker) { + checkPasswordReadPermission(UserHandle.USER_ALL); + mStrongAuth.registerStrongAuthTracker(tracker); + } + + @Override + public void unregisterStrongAuthTracker(IStrongAuthTracker tracker) { + checkPasswordReadPermission(UserHandle.USER_ALL); + mStrongAuth.unregisterStrongAuthTracker(tracker); + } + + @Override + public void requireStrongAuth(int strongAuthReason, int userId) { + checkWritePermission(userId); + mStrongAuth.requireStrongAuth(strongAuthReason, userId); + } + private static final String[] VALID_SETTINGS = new String[] { LockPatternUtils.LOCKOUT_PERMANENT_KEY, LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE, @@ -827,5 +857,4 @@ public class LockSettingsService extends ILockSettings.Stub { Slog.e(TAG, "Unable to acquire GateKeeperService"); return null; } - } diff --git a/services/core/java/com/android/server/LockSettingsStrongAuth.java b/services/core/java/com/android/server/LockSettingsStrongAuth.java new file mode 100644 index 0000000..c023f4a --- /dev/null +++ b/services/core/java/com/android/server/LockSettingsStrongAuth.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker; + +import android.app.trust.IStrongAuthTracker; +import android.os.DeadObjectException; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Slog; +import android.util.SparseIntArray; + +import java.util.ArrayList; + +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; + +/** + * Keeps track of requests for strong authentication. + */ +public class LockSettingsStrongAuth { + + private static final String TAG = "LockSettings"; + + private static final int MSG_REQUIRE_STRONG_AUTH = 1; + private static final int MSG_REGISTER_TRACKER = 2; + private static final int MSG_UNREGISTER_TRACKER = 3; + private static final int MSG_REMOVE_USER = 4; + + private final ArrayList<IStrongAuthTracker> mStrongAuthTrackers = new ArrayList<>(); + private final SparseIntArray mStrongAuthForUser = new SparseIntArray(); + + private void handleAddStrongAuthTracker(IStrongAuthTracker tracker) { + for (int i = 0; i < mStrongAuthTrackers.size(); i++) { + if (mStrongAuthTrackers.get(i).asBinder() == tracker.asBinder()) { + return; + } + } + mStrongAuthTrackers.add(tracker); + + for (int i = 0; i < mStrongAuthForUser.size(); i++) { + int key = mStrongAuthForUser.keyAt(i); + int value = mStrongAuthForUser.valueAt(i); + try { + tracker.onStrongAuthRequiredChanged(value, key); + } catch (RemoteException e) { + Slog.e(TAG, "Exception while adding StrongAuthTracker.", e); + } + } + } + + private void handleRemoveStrongAuthTracker(IStrongAuthTracker tracker) { + for (int i = 0; i < mStrongAuthTrackers.size(); i++) { + if (mStrongAuthTrackers.get(i).asBinder() == tracker.asBinder()) { + mStrongAuthTrackers.remove(i); + return; + } + } + } + + private void handleRequireStrongAuth(int strongAuthReason, int userId) { + if (userId == UserHandle.USER_ALL) { + for (int i = 0; i < mStrongAuthForUser.size(); i++) { + int key = mStrongAuthForUser.keyAt(i); + handleRequireStrongAuthOneUser(strongAuthReason, key); + } + } else { + handleRequireStrongAuthOneUser(strongAuthReason, userId); + } + } + + private void handleRequireStrongAuthOneUser(int strongAuthReason, int userId) { + int oldValue = mStrongAuthForUser.get(userId, LockPatternUtils.StrongAuthTracker.DEFAULT); + int newValue = strongAuthReason == STRONG_AUTH_NOT_REQUIRED + ? STRONG_AUTH_NOT_REQUIRED + : (oldValue | strongAuthReason); + if (oldValue != newValue) { + mStrongAuthForUser.put(userId, newValue); + notifyStrongAuthTrackers(newValue, userId); + } + } + + private void handleRemoveUser(int userId) { + int index = mStrongAuthForUser.indexOfKey(userId); + if (index >= 0) { + mStrongAuthForUser.removeAt(index); + notifyStrongAuthTrackers(StrongAuthTracker.DEFAULT, userId); + } + } + + private void notifyStrongAuthTrackers(int strongAuthReason, int userId) { + for (int i = 0; i < mStrongAuthTrackers.size(); i++) { + try { + mStrongAuthTrackers.get(i).onStrongAuthRequiredChanged(strongAuthReason, userId); + } catch (DeadObjectException e) { + Slog.d(TAG, "Removing dead StrongAuthTracker."); + mStrongAuthTrackers.remove(i); + i--; + } catch (RemoteException e) { + Slog.e(TAG, "Exception while notifying StrongAuthTracker.", e); + } + } + } + + public void registerStrongAuthTracker(IStrongAuthTracker tracker) { + mHandler.obtainMessage(MSG_REGISTER_TRACKER, tracker).sendToTarget(); + } + + public void unregisterStrongAuthTracker(IStrongAuthTracker tracker) { + mHandler.obtainMessage(MSG_UNREGISTER_TRACKER, tracker).sendToTarget(); + } + + public void removeUser(int userId) { + mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget(); + } + + public void requireStrongAuth(int strongAuthReason, int userId) { + if (userId == UserHandle.USER_ALL || userId >= UserHandle.USER_OWNER) { + mHandler.obtainMessage(MSG_REQUIRE_STRONG_AUTH, strongAuthReason, + userId).sendToTarget(); + } else { + throw new IllegalArgumentException( + "userId must be an explicit user id or USER_ALL"); + } + } + + public void reportUnlock(int userId) { + requireStrongAuth(STRONG_AUTH_NOT_REQUIRED, userId); + } + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_REGISTER_TRACKER: + handleAddStrongAuthTracker((IStrongAuthTracker) msg.obj); + break; + case MSG_UNREGISTER_TRACKER: + handleRemoveStrongAuthTracker((IStrongAuthTracker) msg.obj); + break; + case MSG_REQUIRE_STRONG_AUTH: + handleRequireStrongAuth(msg.arg1, msg.arg2); + break; + case MSG_REMOVE_USER: + handleRemoveUser(msg.arg1); + break; + } + } + }; +} diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index 620bc29..2602ad3 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -368,11 +368,17 @@ class MountService extends IMountService.Stub private boolean shouldBenchmark() { final long benchInterval = Settings.Global.getLong(mContext.getContentResolver(), Settings.Global.STORAGE_BENCHMARK_INTERVAL, DateUtils.WEEK_IN_MILLIS); + if (benchInterval == -1) { + return false; + } else if (benchInterval == 0) { + return true; + } + synchronized (mLock) { for (int i = 0; i < mVolumes.size(); i++) { final VolumeInfo vol = mVolumes.valueAt(i); final VolumeRecord rec = mRecords.get(vol.fsUuid); - if (vol.isMountedReadable() && rec != null) { + if (vol.isMountedWritable() && rec != null) { final long benchAge = System.currentTimeMillis() - rec.lastBenchMillis; if (benchAge >= benchInterval) { return true; @@ -2602,6 +2608,63 @@ class MountService extends IMountService.Stub } @Override + public void createNewUserDir(int userHandle, String path) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only SYSTEM_UID can create user directories"); + } + + waitForReady(); + + if (DEBUG_EVENTS) { + Slog.i(TAG, "Creating new user dir"); + } + + try { + NativeDaemonEvent event = mCryptConnector.execute( + "cryptfs", "createnewuserdir", userHandle, path); + if (!"0".equals(event.getMessage())) { + String error = "createnewuserdir sent unexpected message: " + + event.getMessage(); + Slog.e(TAG, error); + // ext4enc:TODO is this the right exception? + throw new RuntimeException(error); + } + } catch (NativeDaemonConnectorException e) { + Slog.e(TAG, "createnewuserdir threw exception", e); + throw new RuntimeException("createnewuserdir threw exception", e); + } + } + + // ext4enc:TODO duplication between this and createNewUserDir is nasty + @Override + public void deleteUserKey(int userHandle) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only SYSTEM_UID can delete user keys"); + } + + waitForReady(); + + if (DEBUG_EVENTS) { + Slog.i(TAG, "Deleting user key"); + } + + try { + NativeDaemonEvent event = mCryptConnector.execute( + "cryptfs", "deleteuserkey", userHandle); + if (!"0".equals(event.getMessage())) { + String error = "deleteuserkey sent unexpected message: " + + event.getMessage(); + Slog.e(TAG, error); + // ext4enc:TODO is this the right exception? + throw new RuntimeException(error); + } + } catch (NativeDaemonConnectorException e) { + Slog.e(TAG, "deleteuserkey threw exception", e); + throw new RuntimeException("deleteuserkey threw exception", e); + } + } + + @Override public int mkdirs(String callingPkg, String appPath) { final int userId = UserHandle.getUserId(Binder.getCallingUid()); final UserEnvironment userEnv = new UserEnvironment(userId); diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 30f4dce..c228422 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -59,6 +59,7 @@ public class VibratorService extends IVibratorService.Stub implements InputManager.InputDeviceListener { private static final String TAG = "VibratorService"; private static final boolean DEBUG = false; + private static final String SYSTEM_UI_PACKAGE = "com.android.systemui"; private final LinkedList<Vibration> mVibrations; private final LinkedList<VibrationInfo> mPreviousVibrations; @@ -147,7 +148,8 @@ public class VibratorService extends IVibratorService.Stub } public boolean isSystemHapticFeedback() { - return (mUid == Process.SYSTEM_UID || mUid == 0) && mRepeat < 0; + return (mUid == Process.SYSTEM_UID || mUid == 0 || SYSTEM_UI_PACKAGE.equals(mOpPkg)) + && mRepeat < 0; } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index fb65a15..3440b2a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -324,6 +324,9 @@ public final class ActivityManagerService extends ActivityManagerNative // How long we wait for a launched process to attach to the activity manager // before we decide it's never going to come up for real. static final int PROC_START_TIMEOUT = 10*1000; + // How long we wait for an attached process to publish its content providers + // before we decide it must be hung. + static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000; // How long we wait for a launched process to attach to the activity manager // before we decide it's never going to come up for real, when the process was @@ -412,6 +415,17 @@ public final class ActivityManagerService extends ActivityManagerNative private boolean mHomeKilled = false; private String mHomeProcessName = null; + + // Delay to disable app launch boost + static final int APP_BOOST_MESSAGE_DELAY = 3000; + // Lower delay than APP_BOOST_MESSAGE_DELAY to disable the boost + static final int APP_BOOST_TIMEOUT = 2500; + + private static native int nativeMigrateToBoost(); + private static native int nativeMigrateFromBoost(); + private boolean mIsBoosted = false; + private long mBoostStartTime = 0; + /** All system services */ SystemServiceManager mSystemServiceManager; @@ -1371,8 +1385,10 @@ public final class ActivityManagerService extends ActivityManagerNative static final int REPORT_TIME_TRACKER_MSG = 55; static final int REPORT_USER_SWITCH_COMPLETE_MSG = 56; static final int SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG = 57; - static final int POST_PRIVACY_NOTIFICATION_MSG = 58; - static final int CANCEL_PRIVACY_NOTIFICATION_MSG = 59; + static final int APP_BOOST_DEACTIVATE_MSG = 58; + static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG = 59; + static final int POST_PRIVACY_NOTIFICATION_MSG = 60; + static final int CANCEL_PRIVACY_NOTIFICATION_MSG = 61; static final int FIRST_ACTIVITY_STACK_MSG = 100; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -1716,6 +1732,12 @@ public final class ActivityManagerService extends ActivityManagerNative processStartTimedOutLocked(app); } } break; + case CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG: { + ProcessRecord app = (ProcessRecord)msg.obj; + synchronized (ActivityManagerService.this) { + processContentProviderPublishTimedOutLocked(app); + } + } break; case DO_PENDING_ACTIVITY_LAUNCHES_MSG: { synchronized (ActivityManagerService.this) { mStackSupervisor.doPendingActivityLaunchesLocked(true); @@ -2058,6 +2080,20 @@ public final class ActivityManagerService extends ActivityManagerNative // it is finished we make sure it is reset to its default. mUserIsMonkey = false; } break; + case APP_BOOST_DEACTIVATE_MSG : { + synchronized(ActivityManagerService.this) { + if (mIsBoosted) { + if (mBoostStartTime < (SystemClock.uptimeMillis() - APP_BOOST_TIMEOUT)) { + nativeMigrateFromBoost(); + mIsBoosted = false; + mBoostStartTime = 0; + } else { + Message newmsg = mHandler.obtainMessage(APP_BOOST_DEACTIVATE_MSG); + mHandler.sendMessageDelayed(newmsg, APP_BOOST_TIMEOUT); + } + } + } + } break; case POST_PRIVACY_NOTIFICATION_MSG: { INotificationManager inm = NotificationManager.getService(); if (inm == null) { @@ -3213,6 +3249,16 @@ public final class ActivityManagerService extends ActivityManagerNative app = null; } + // app launch boost for big.little configurations + // use cpusets to migrate freshly launched tasks to big cores + synchronized(ActivityManagerService.this) { + nativeMigrateToBoost(); + mIsBoosted = true; + mBoostStartTime = SystemClock.uptimeMillis(); + Message msg = mHandler.obtainMessage(APP_BOOST_DEACTIVATE_MSG); + mHandler.sendMessageDelayed(msg, APP_BOOST_MESSAGE_DELAY); + } + // We don't have to do anything more if: // (1) There is an existing application record; and // (2) The caller doesn't think it is dead, OR there is no thread @@ -6045,6 +6091,11 @@ public final class ActivityManagerService extends ActivityManagerNative return needRestart; } + private final void processContentProviderPublishTimedOutLocked(ProcessRecord app) { + cleanupAppInLaunchingProvidersLocked(app, true); + removeProcessLocked(app, false, true, "timeout publishing content providers"); + } + private final void processStartTimedOutLocked(ProcessRecord app) { final int pid = app.pid; boolean gone = false; @@ -6071,7 +6122,7 @@ public final class ActivityManagerService extends ActivityManagerNative mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid); } // Take care of any launching providers waiting for this process. - checkAppInLaunchingProvidersLocked(app, true); + cleanupAppInLaunchingProvidersLocked(app, true); // Take care of any services that are waiting for the process. mServices.processStartTimedOutLocked(app); app.kill("start timeout", true); @@ -6167,6 +6218,12 @@ public final class ActivityManagerService extends ActivityManagerNative boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info); List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null; + if (providers != null && checkAppInLaunchingProvidersLocked(app)) { + Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG); + msg.obj = app; + mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT); + } + if (!normalMode) { Slog.i(TAG, "Launching preboot mode app: " + app); } @@ -10007,7 +10064,7 @@ public final class ActivityManagerService extends ActivityManagerNative final long origId = Binder.clearCallingIdentity(); final int N = providers.size(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { ContentProviderHolder src = providers.get(i); if (src == null || src.info == null || src.provider == null) { continue; @@ -10022,15 +10079,20 @@ public final class ActivityManagerService extends ActivityManagerNative mProviderMap.putProviderByName(names[j], dst); } - int NL = mLaunchingProviders.size(); + int launchingCount = mLaunchingProviders.size(); int j; - for (j=0; j<NL; j++) { + boolean wasInLaunchingProviders = false; + for (j = 0; j < launchingCount; j++) { if (mLaunchingProviders.get(j) == dst) { mLaunchingProviders.remove(j); + wasInLaunchingProviders = true; j--; - NL--; + launchingCount--; } } + if (wasInLaunchingProviders) { + mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r); + } synchronized (dst) { dst.provider = src.provider; dst.proc = r; @@ -15682,7 +15744,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.pubProviders.clear(); // Take care of any launching providers waiting for this process. - if (checkAppInLaunchingProvidersLocked(app, false)) { + if (cleanupAppInLaunchingProvidersLocked(app, false)) { restart = true; } @@ -15805,7 +15867,17 @@ public final class ActivityManagerService extends ActivityManagerNative return false; } - boolean checkAppInLaunchingProvidersLocked(ProcessRecord app, boolean alwaysBad) { + boolean checkAppInLaunchingProvidersLocked(ProcessRecord app) { + for (int i = mLaunchingProviders.size() - 1; i >= 0; i--) { + ContentProviderRecord cpr = mLaunchingProviders.get(i); + if (cpr.launchingApp == app) { + return true; + } + } + return false; + } + + boolean cleanupAppInLaunchingProvidersLocked(ProcessRecord app, boolean alwaysBad) { // Look through the content providers we are waiting to have launched, // and if any run in this process then either schedule a restart of // the process or kill the client waiting for it if this process has diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 335288d..62768c3 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -171,12 +171,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub public void publish(Context context) { mContext = context; - ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder()); - mStats.setNumSpeedSteps(new PowerProfile(mContext).getNumSpeedSteps()); mStats.setRadioScanningTimeout(mContext.getResources().getInteger( com.android.internal.R.integer.config_radioScanningTimeout) * 1000L); mStats.setPowerProfile(new PowerProfile(context)); + ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder()); } /** diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 1d72fd9..8738bcb 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -538,7 +538,6 @@ public class AudioService extends IAudioService.Stub { private volatile IRingtonePlayer mRingtonePlayer; private int mDeviceOrientation = Configuration.ORIENTATION_UNDEFINED; - private int mDeviceRotation = Surface.ROTATION_0; // Request to override default use of A2DP for media. private boolean mBluetoothA2dpEnabled; @@ -584,8 +583,6 @@ public class AudioService extends IAudioService.Stub { // If absolute volume is supported in AVRCP device private boolean mAvrcpAbsVolSupported = false; - private AudioOrientationEventListener mOrientationListener; - private static Long mLastDeviceConnectMsgTime = new Long(0); private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate; @@ -713,15 +710,7 @@ public class AudioService extends IAudioService.Stub { } mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false); if (mMonitorRotation) { - mDeviceRotation = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)) - .getDefaultDisplay().getRotation(); - Log.v(TAG, "monitoring device rotation, initial=" + mDeviceRotation); - - mOrientationListener = new AudioOrientationEventListener(mContext); - mOrientationListener.enable(); - - // initialize rotation in AudioSystem - setRotationForAudioSystem(); + RotationHelper.init(mContext, mAudioHandler); } context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null); @@ -851,7 +840,7 @@ public class AudioService extends IAudioService.Stub { setOrientationForAudioSystem(); } if (mMonitorRotation) { - setRotationForAudioSystem(); + RotationHelper.updateOrientation(); } synchronized (mBluetoothA2dpEnabledLock) { @@ -1198,25 +1187,6 @@ public class AudioService extends IAudioService.Stub { return (index * mStreamStates[dstStream].getMaxIndex() + mStreamStates[srcStream].getMaxIndex() / 2) / mStreamStates[srcStream].getMaxIndex(); } - private class AudioOrientationEventListener - extends OrientationEventListener { - public AudioOrientationEventListener(Context context) { - super(context); - } - - @Override - public void onOrientationChanged(int orientation) { - //Even though we're responding to phone orientation events, - //use display rotation so audio stays in sync with video/dialogs - int newRotation = ((WindowManager) mContext.getSystemService( - Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation(); - if (newRotation != mDeviceRotation) { - mDeviceRotation = newRotation; - setRotationForAudioSystem(); - } - } - } - /////////////////////////////////////////////////////////////////////////// // IPC methods /////////////////////////////////////////////////////////////////////////// @@ -2708,11 +2678,14 @@ public class AudioService extends IAudioService.Stub { } /** @see AudioManager#setBluetoothScoOn(boolean) */ - public void setBluetoothScoOn(boolean on){ + public void setBluetoothScoOn(boolean on) { if (!checkAudioSettingsPermission("setBluetoothScoOn()")) { return; } + setBluetoothScoOnInt(on); + } + public void setBluetoothScoOnInt(boolean on) { if (on) { mForcedUseForComm = AudioSystem.FORCE_BT_SCO; } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { @@ -3073,6 +3046,8 @@ public class AudioService extends IAudioService.Stub { mScoAudioState = SCO_STATE_INACTIVE; broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); } + AudioSystem.setParameters("A2dpSuspended=false"); + setBluetoothScoOnInt(false); } private void broadcastScoConnectionState(int state) { @@ -5298,14 +5273,13 @@ public class AudioService extends IAudioService.Stub { } } else if (action.equals(Intent.ACTION_SCREEN_ON)) { if (mMonitorRotation) { - mOrientationListener.onOrientationChanged(0); //argument is ignored anyway - mOrientationListener.enable(); + RotationHelper.enable(); } AudioSystem.setParameters("screen_state=on"); } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { if (mMonitorRotation) { //reduce wakeups (save current) by only listening when display is on - mOrientationListener.disable(); + RotationHelper.disable(); } AudioSystem.setParameters("screen_state=off"); } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { @@ -5562,6 +5536,7 @@ public class AudioService extends IAudioService.Stub { } } + //TODO move to an external "orientation helper" class private void setOrientationForAudioSystem() { switch (mDeviceOrientation) { case Configuration.ORIENTATION_LANDSCAPE: @@ -5585,26 +5560,6 @@ public class AudioService extends IAudioService.Stub { } } - private void setRotationForAudioSystem() { - switch (mDeviceRotation) { - case Surface.ROTATION_0: - AudioSystem.setParameters("rotation=0"); - break; - case Surface.ROTATION_90: - AudioSystem.setParameters("rotation=90"); - break; - case Surface.ROTATION_180: - AudioSystem.setParameters("rotation=180"); - break; - case Surface.ROTATION_270: - AudioSystem.setParameters("rotation=270"); - break; - default: - Log.e(TAG, "Unknown device rotation"); - } - } - - // Handles request to override default use of A2DP for media. // Must be called synchronized on mConnectedDevices public void setBluetoothA2dpOnInt(boolean on) { diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java new file mode 100644 index 0000000..359cc36 --- /dev/null +++ b/services/core/java/com/android/server/audio/RotationHelper.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.audio; + +import android.content.Context; +import android.media.AudioSystem; +import android.os.Handler; +import android.util.Log; +import android.view.OrientationEventListener; +import android.view.Surface; +import android.view.WindowManager; + +import com.android.server.policy.WindowOrientationListener; + +/** + * Class to handle device rotation events for AudioService, and forward device rotation + * to the audio HALs through AudioSystem. + * + * The role of this class is to monitor device orientation changes, and upon rotation, + * verify the UI orientation. In case of a change, send the new orientation, in increments + * of 90deg, through AudioSystem. + * + * Note that even though we're responding to device orientation events, we always + * query the display rotation so audio stays in sync with video/dialogs. This is + * done with .getDefaultDisplay().getRotation() from WINDOW_SERVICE. + */ +class RotationHelper { + + private static final String TAG = "AudioService.RotationHelper"; + + private static AudioOrientationListener sOrientationListener; + private static AudioWindowOrientationListener sWindowOrientationListener; + + private static final Object sRotationLock = new Object(); + private static int sDeviceRotation = Surface.ROTATION_0; // R/W synchronized on sRotationLock + + private static Context sContext; + + /** + * post conditions: + * - (sWindowOrientationListener != null) xor (sOrientationListener != null) + * - sWindowOrientationListener xor sOrientationListener is enabled + * - sContext != null + */ + static void init(Context context, Handler handler) { + if (context == null) { + throw new IllegalArgumentException("Invalid null context"); + } + sContext = context; + sWindowOrientationListener = new AudioWindowOrientationListener(context, handler); + sWindowOrientationListener.enable(); + if (!sWindowOrientationListener.canDetectOrientation()) { + // cannot use com.android.server.policy.WindowOrientationListener, revert to public + // orientation API + Log.i(TAG, "Not using WindowOrientationListener, reverting to OrientationListener"); + sWindowOrientationListener.disable(); + sWindowOrientationListener = null; + sOrientationListener = new AudioOrientationListener(context); + sOrientationListener.enable(); + } + } + + static void enable() { + if (sWindowOrientationListener != null) { + sWindowOrientationListener.enable(); + } else { + sOrientationListener.enable(); + } + updateOrientation(); + } + + static void disable() { + if (sWindowOrientationListener != null) { + sWindowOrientationListener.disable(); + } else { + sOrientationListener.disable(); + } + } + + /** + * Query current display rotation and publish the change if any. + */ + static void updateOrientation() { + // Even though we're responding to device orientation events, + // use display rotation so audio stays in sync with video/dialogs + int newRotation = ((WindowManager) sContext.getSystemService( + Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation(); + synchronized(sRotationLock) { + if (newRotation != sDeviceRotation) { + sDeviceRotation = newRotation; + publishRotation(sDeviceRotation); + } + } + } + + private static void publishRotation(int rotation) { + Log.v(TAG, "publishing device rotation =" + rotation + " (x90deg)"); + switch (rotation) { + case Surface.ROTATION_0: + AudioSystem.setParameters("rotation=0"); + break; + case Surface.ROTATION_90: + AudioSystem.setParameters("rotation=90"); + break; + case Surface.ROTATION_180: + AudioSystem.setParameters("rotation=180"); + break; + case Surface.ROTATION_270: + AudioSystem.setParameters("rotation=270"); + break; + default: + Log.e(TAG, "Unknown device rotation"); + } + } + + /** + * Uses android.view.OrientationEventListener + */ + final static class AudioOrientationListener extends OrientationEventListener { + AudioOrientationListener(Context context) { + super(context); + } + + @Override + public void onOrientationChanged(int orientation) { + updateOrientation(); + } + } + + /** + * Uses com.android.server.policy.WindowOrientationListener + */ + final static class AudioWindowOrientationListener extends WindowOrientationListener { + private static RotationCheckThread sRotationCheckThread; + + AudioWindowOrientationListener(Context context, Handler handler) { + super(context, handler); + } + + public void onProposedRotationChanged(int rotation) { + updateOrientation(); + if (sRotationCheckThread != null) { + sRotationCheckThread.endCheck(); + } + sRotationCheckThread = new RotationCheckThread(); + sRotationCheckThread.beginCheck(); + } + } + + /** + * When com.android.server.policy.WindowOrientationListener report an orientation change, + * the UI may not have rotated yet. This thread polls with gradually increasing delays + * the new orientation. + */ + final static class RotationCheckThread extends Thread { + // how long to wait between each rotation check + private final int[] WAIT_TIMES_MS = { 10, 20, 50, 100, 100, 200, 200, 500 }; + private int mWaitCounter; + private final Object mCounterLock = new Object(); + + RotationCheckThread() { + super("RotationCheck"); + } + + void beginCheck() { + synchronized(mCounterLock) { + mWaitCounter = 0; + } + try { + start(); + } catch (IllegalStateException e) { } + } + + void endCheck() { + synchronized(mCounterLock) { + mWaitCounter = WAIT_TIMES_MS.length; + } + } + + public void run() { + while (mWaitCounter < WAIT_TIMES_MS.length) { + int waitTimeMs; + synchronized(mCounterLock) { + waitTimeMs = mWaitCounter < WAIT_TIMES_MS.length ? + WAIT_TIMES_MS[mWaitCounter] : 0; + mWaitCounter++; + } + try { + if (waitTimeMs > 0) { + sleep(waitTimeMs); + updateOrientation(); + } + } catch (InterruptedException e) { } + } + } + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/camera/CameraService.java b/services/core/java/com/android/server/camera/CameraService.java index 0be24f4..f82454a 100644 --- a/services/core/java/com/android/server/camera/CameraService.java +++ b/services/core/java/com/android/server/camera/CameraService.java @@ -23,13 +23,17 @@ import android.content.IntentFilter; import android.content.pm.UserInfo; import android.hardware.ICameraService; import android.hardware.ICameraServiceProxy; +import android.nfc.INfcAdapter; import android.os.Handler; import android.os.IBinder; +import android.os.Binder; import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.UserManager; +import android.os.SystemProperties; import android.util.Slog; +import android.util.ArraySet; import com.android.server.ServiceThread; import com.android.server.SystemService; @@ -44,8 +48,10 @@ import java.util.Set; * * @hide */ -public class CameraService extends SystemService implements Handler.Callback { +public class CameraService extends SystemService + implements Handler.Callback, IBinder.DeathRecipient { private static final String TAG = "CameraService_proxy"; + private static final boolean DEBUG = false; /** * This must match the ICameraService.aidl definition @@ -58,6 +64,16 @@ public class CameraService extends SystemService implements Handler.Callback { public static final int NO_EVENT = 0; // NOOP public static final int USER_SWITCHED = 1; // User changed, argument is the new user handle + // State arguments to use with the notifyCameraState call from camera service: + public static final int CAMERA_STATE_OPEN = 0; + public static final int CAMERA_STATE_ACTIVE = 1; + public static final int CAMERA_STATE_IDLE = 2; + public static final int CAMERA_STATE_CLOSED = 3; + + // Flags arguments to NFC adapter to enable/disable NFC + public static final int DISABLE_POLLING_FLAGS = 0x1000; + public static final int ENABLE_POLLING_FLAGS = 0x0000; + // Handler message codes private static final int MSG_SWITCH_USER = 1; @@ -72,6 +88,17 @@ public class CameraService extends SystemService implements Handler.Callback { private Set<Integer> mEnabledCameraUsers; private int mLastUser; + private ICameraService mCameraServiceRaw; + + private final ArraySet<String> mActiveCameraIds = new ArraySet<>(); + + private static final String NFC_NOTIFICATION_PROP = "ro.camera.notify_nfc"; + private static final String NFC_SERVICE_BINDER_NAME = "nfc"; + private static final IBinder nfcInterfaceToken = new Binder(); + + private final boolean mNotifyNfc; + private int mActiveCameraCount = 0; + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -102,6 +129,14 @@ public class CameraService extends SystemService implements Handler.Callback { public void pingForUserUpdate() { notifySwitchWithRetries(30); } + + @Override + public void notifyCameraState(String cameraId, int newCameraState) { + String state = cameraStateToString(newCameraState); + if (DEBUG) Slog.v(TAG, "Camera " + cameraId + " state now " + state); + + updateActivityCount(cameraId, newCameraState); + } }; public CameraService(Context context) { @@ -110,6 +145,9 @@ public class CameraService extends SystemService implements Handler.Callback { mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_DISPLAY, /*allowTo*/false); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper(), this); + + mNotifyNfc = SystemProperties.getInt(NFC_NOTIFICATION_PROP, 0) > 0; + if (DEBUG) Slog.v(TAG, "Notify NFC behavior is " + (mNotifyNfc ? "active" : "disabled")); } @Override @@ -161,13 +199,32 @@ public class CameraService extends SystemService implements Handler.Callback { } } + /** + * Handle the death of the native camera service + */ + @Override + public void binderDied() { + if (DEBUG) Slog.w(TAG, "Native camera service has died"); + synchronized(mLock) { + mCameraServiceRaw = null; + + // All cameras reset to idle on camera service death + boolean wasEmpty = mActiveCameraIds.isEmpty(); + mActiveCameraIds.clear(); + + if ( mNotifyNfc && !wasEmpty ) { + notifyNfcService(/*enablePolling*/ true); + } + } + } + private void switchUserLocked(int userHandle) { Set<Integer> currentUserHandles = getEnabledUserHandles(userHandle); mLastUser = userHandle; if (mEnabledCameraUsers == null || !mEnabledCameraUsers.equals(currentUserHandles)) { // Some user handles have been added or removed, update mediaserver. mEnabledCameraUsers = currentUserHandles; - notifyMediaserver(USER_SWITCHED, currentUserHandles); + notifyMediaserverLocked(USER_SWITCHED, currentUserHandles); } } @@ -187,7 +244,7 @@ public class CameraService extends SystemService implements Handler.Callback { if (mEnabledCameraUsers == null) { return; } - if (notifyMediaserver(USER_SWITCHED, mEnabledCameraUsers)) { + if (notifyMediaserverLocked(USER_SWITCHED, mEnabledCameraUsers)) { retries = 0; } } @@ -199,19 +256,27 @@ public class CameraService extends SystemService implements Handler.Callback { RETRY_DELAY_TIME); } - private boolean notifyMediaserver(int eventType, Set<Integer> updatedUserHandles) { + private boolean notifyMediaserverLocked(int eventType, Set<Integer> updatedUserHandles) { // Forward the user switch event to the native camera service running in the mediaserver // process. - IBinder cameraServiceBinder = getBinderService(CAMERA_SERVICE_BINDER_NAME); - if (cameraServiceBinder == null) { - Slog.w(TAG, "Could not notify mediaserver, camera service not available."); - return false; // Camera service not active, cannot evict user clients. - } + if (mCameraServiceRaw == null) { + IBinder cameraServiceBinder = getBinderService(CAMERA_SERVICE_BINDER_NAME); + if (cameraServiceBinder == null) { + Slog.w(TAG, "Could not notify mediaserver, camera service not available."); + return false; // Camera service not active, cannot evict user clients. + } + try { + cameraServiceBinder.linkToDeath(this, /*flags*/ 0); + } catch (RemoteException e) { + Slog.w(TAG, "Could not link to death of native camera service"); + return false; + } - ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder); + mCameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder); + } try { - cameraServiceRaw.notifySystemEvent(eventType, toArray(updatedUserHandles)); + mCameraServiceRaw.notifySystemEvent(eventType, toArray(updatedUserHandles)); } catch (RemoteException e) { Slog.w(TAG, "Could not notify mediaserver, remote exception: " + e); // Not much we can do if camera service is dead. @@ -220,6 +285,44 @@ public class CameraService extends SystemService implements Handler.Callback { return true; } + private void updateActivityCount(String cameraId, int newCameraState) { + synchronized(mLock) { + boolean wasEmpty = mActiveCameraIds.isEmpty(); + switch (newCameraState) { + case CAMERA_STATE_OPEN: + break; + case CAMERA_STATE_ACTIVE: + mActiveCameraIds.add(cameraId); + break; + case CAMERA_STATE_IDLE: + case CAMERA_STATE_CLOSED: + mActiveCameraIds.remove(cameraId); + break; + } + boolean isEmpty = mActiveCameraIds.isEmpty(); + if ( mNotifyNfc && (wasEmpty != isEmpty) ) { + notifyNfcService(isEmpty); + } + } + } + + private void notifyNfcService(boolean enablePolling) { + + IBinder nfcServiceBinder = getBinderService(NFC_SERVICE_BINDER_NAME); + if (nfcServiceBinder == null) { + Slog.w(TAG, "Could not connect to NFC service to notify it of camera state"); + return; + } + INfcAdapter nfcAdapterRaw = INfcAdapter.Stub.asInterface(nfcServiceBinder); + int flags = enablePolling ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS; + if (DEBUG) Slog.v(TAG, "Setting NFC reader mode to flags " + flags); + try { + nfcAdapterRaw.setReaderMode(nfcInterfaceToken, null, flags, null); + } catch (RemoteException e) { + Slog.w(TAG, "Could not notify NFC service, remote exception: " + e); + } + } + private static int[] toArray(Collection<Integer> c) { int len = c.size(); int[] ret = new int[len]; @@ -229,4 +332,15 @@ public class CameraService extends SystemService implements Handler.Callback { } return ret; } + + private static String cameraStateToString(int newCameraState) { + switch (newCameraState) { + case CAMERA_STATE_OPEN: return "CAMERA_STATE_OPEN"; + case CAMERA_STATE_ACTIVE: return "CAMERA_STATE_ACTIVE"; + case CAMERA_STATE_IDLE: return "CAMERA_STATE_IDLE"; + case CAMERA_STATE_CLOSED: return "CAMERA_STATE_CLOSED"; + default: break; + } + return "CAMERA_STATE_UNKNOWN"; + } } diff --git a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java new file mode 100644 index 0000000..2ccfdd1 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import android.system.OsConstants; +import android.net.ConnectivityManager; +import android.net.NetworkUtils; +import android.net.util.IpUtils; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static android.net.ConnectivityManager.PacketKeepalive.*; + +/** + * Represents the actual packets that are sent by the + * {@link android.net.ConnectivityManager.PacketKeepalive} API. + * + * @hide + */ +public class KeepalivePacketData { + /** Protocol of the packet to send; one of the OsConstants.ETH_P_* values. */ + public final int protocol; + + /** Source IP address */ + public final InetAddress srcAddress; + + /** Destination IP address */ + public final InetAddress dstAddress; + + /** Source port */ + public final int srcPort; + + /** Destination port */ + public final int dstPort; + + /** Destination MAC address. Can change if routing changes. */ + public byte[] dstMac; + + /** Packet data. A raw byte string of packet data, not including the link-layer header. */ + public final byte[] data; + + private static final int IPV4_HEADER_LENGTH = 20; + private static final int UDP_HEADER_LENGTH = 8; + + protected KeepalivePacketData(InetAddress srcAddress, int srcPort, + InetAddress dstAddress, int dstPort, byte[] data) throws InvalidPacketException { + this.srcAddress = srcAddress; + this.dstAddress = dstAddress; + this.srcPort = srcPort; + this.dstPort = dstPort; + this.data = data; + + // Check we have two IP addresses of the same family. + if (srcAddress == null || dstAddress == null || + !srcAddress.getClass().getName().equals(dstAddress.getClass().getName())) { + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); + } + + // Set the protocol. + if (this.dstAddress instanceof Inet4Address) { + this.protocol = OsConstants.ETH_P_IP; + } else if (this.dstAddress instanceof Inet6Address) { + this.protocol = OsConstants.ETH_P_IPV6; + } else { + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); + } + + // Check the ports. + if (!IpUtils.isValidUdpOrTcpPort(srcPort) || !IpUtils.isValidUdpOrTcpPort(dstPort)) { + throw new InvalidPacketException(ERROR_INVALID_PORT); + } + } + + public static class InvalidPacketException extends Exception { + final public int error; + public InvalidPacketException(int error) { + this.error = error; + } + } + + /** + * Creates an IPsec NAT-T keepalive packet with the specified parameters. + */ + public static KeepalivePacketData nattKeepalivePacket( + InetAddress srcAddress, int srcPort, + InetAddress dstAddress, int dstPort) throws InvalidPacketException { + + if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) { + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); + } + + if (dstPort != NATT_PORT) { + throw new InvalidPacketException(ERROR_INVALID_PORT); + } + + int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1; + ByteBuffer buf = ByteBuffer.allocate(length); + buf.order(ByteOrder.BIG_ENDIAN); + buf.putShort((short) 0x4500); // IP version and TOS + buf.putShort((short) length); + buf.putInt(0); // ID, flags, offset + buf.put((byte) 64); // TTL + buf.put((byte) OsConstants.IPPROTO_UDP); + int ipChecksumOffset = buf.position(); + buf.putShort((short) 0); // IP checksum + buf.put(srcAddress.getAddress()); + buf.put(dstAddress.getAddress()); + buf.putShort((short) srcPort); + buf.putShort((short) dstPort); + buf.putShort((short) (length - 20)); // UDP length + int udpChecksumOffset = buf.position(); + buf.putShort((short) 0); // UDP checksum + buf.put((byte) 0xff); // NAT-T keepalive + buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0)); + buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH)); + + return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array()); + } +} diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java new file mode 100644 index 0000000..90c9ddf --- /dev/null +++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import com.android.internal.util.HexDump; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.connectivity.KeepalivePacketData; +import com.android.server.connectivity.NetworkAgentInfo; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.PacketKeepalive; +import android.net.LinkAddress; +import android.net.NetworkAgent; +import android.net.NetworkUtils; +import android.net.util.IpUtils; +import android.os.Binder; +import android.os.IBinder; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.os.Process; +import android.os.RemoteException; +import android.system.OsConstants; +import android.util.Log; +import android.util.Pair; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.HashMap; + +import static android.net.ConnectivityManager.PacketKeepalive.*; +import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE; +import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE; +import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE; + +/** + * Manages packet keepalive requests. + * + * Provides methods to stop and start keepalive requests, and keeps track of keepalives across all + * networks. This class is tightly coupled to ConnectivityService. It is not thread-safe and its + * methods must be called only from the ConnectivityService handler thread. + */ +public class KeepaliveTracker { + + private static final String TAG = "KeepaliveTracker"; + private static final boolean DBG = true; + + public static final String PERMISSION = android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD; + + /** Keeps track of keepalive requests. */ + private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives = + new HashMap<> (); + private final Handler mConnectivityServiceHandler; + + public KeepaliveTracker(Handler handler) { + mConnectivityServiceHandler = handler; + } + + /** + * Tracks information about a packet keepalive. + * + * All information about this keepalive is known at construction time except the slot number, + * which is only returned when the hardware has successfully started the keepalive. + */ + class KeepaliveInfo implements IBinder.DeathRecipient { + // Bookkeping data. + private final Messenger mMessenger; + private final IBinder mBinder; + private final int mUid; + private final int mPid; + private final NetworkAgentInfo mNai; + + /** Keepalive slot. A small integer that identifies this keepalive among the ones handled + * by this network. */ + private int mSlot = PacketKeepalive.NO_KEEPALIVE; + + // Packet data. + private final KeepalivePacketData mPacket; + private final int mInterval; + + // Whether the keepalive is started or not. + public boolean isStarted; + + public KeepaliveInfo(Messenger messenger, IBinder binder, NetworkAgentInfo nai, + KeepalivePacketData packet, int interval) { + mMessenger = messenger; + mBinder = binder; + mPid = Binder.getCallingPid(); + mUid = Binder.getCallingUid(); + + mNai = nai; + mPacket = packet; + mInterval = interval; + + try { + mBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + binderDied(); + } + } + + public NetworkAgentInfo getNai() { + return mNai; + } + + public String toString() { + return new StringBuffer("KeepaliveInfo [") + .append(" network=").append(mNai.network) + .append(" isStarted=").append(isStarted) + .append(" ") + .append(IpUtils.addressAndPortToString(mPacket.srcAddress, mPacket.srcPort)) + .append("->") + .append(IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort)) + .append(" interval=" + mInterval) + .append(" data=" + HexDump.toHexString(mPacket.data)) + .append(" uid=").append(mUid).append(" pid=").append(mPid) + .append(" ]") + .toString(); + } + + /** Sends a message back to the application via its PacketKeepalive.Callback. */ + void notifyMessenger(int slot, int err) { + KeepaliveTracker.this.notifyMessenger(mMessenger, slot, err); + } + + /** Called when the application process is killed. */ + public void binderDied() { + // Not called from ConnectivityService handler thread, so send it a message. + mConnectivityServiceHandler.obtainMessage( + NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, + mSlot, PacketKeepalive.BINDER_DIED, mNai.network).sendToTarget(); + } + + void unlinkDeathRecipient() { + if (mBinder != null) { + mBinder.unlinkToDeath(this, 0); + } + } + + private int checkNetworkConnected() { + if (!mNai.networkInfo.isConnectedOrConnecting()) { + return ERROR_INVALID_NETWORK; + } + return SUCCESS; + } + + private int checkSourceAddress() { + // Check that we have the source address. + for (InetAddress address : mNai.linkProperties.getAddresses()) { + if (address.equals(mPacket.srcAddress)) { + return SUCCESS; + } + } + return ERROR_INVALID_IP_ADDRESS; + } + + private int checkInterval() { + return mInterval >= 20 ? SUCCESS : ERROR_INVALID_INTERVAL; + } + + private int isValid() { + synchronized (mNai) { + int error = checkInterval(); + if (error == SUCCESS) error = checkNetworkConnected(); + if (error == SUCCESS) error = checkSourceAddress(); + return error; + } + } + + void start(int slot) { + int error = isValid(); + if (error == SUCCESS) { + mSlot = slot; + Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.name()); + mNai.asyncChannel.sendMessage(CMD_START_PACKET_KEEPALIVE, slot, mInterval, mPacket); + } else { + notifyMessenger(NO_KEEPALIVE, error); + return; + } + } + + void stop(int reason) { + int uid = Binder.getCallingUid(); + if (uid != mUid && uid != Process.SYSTEM_UID) { + if (DBG) { + Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network); + } + } + if (isStarted) { + Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name()); + mNai.asyncChannel.sendMessage(CMD_STOP_PACKET_KEEPALIVE, mSlot); + } + // TODO: at the moment we unconditionally return failure here. In cases where the + // NetworkAgent is alive, should we ask it to reply, so it can return failure? + notifyMessenger(mSlot, reason); + unlinkDeathRecipient(); + } + } + + void notifyMessenger(Messenger messenger, int slot, int err) { + Message message = Message.obtain(); + message.what = EVENT_PACKET_KEEPALIVE; + message.arg1 = slot; + message.arg2 = err; + message.obj = null; + try { + messenger.send(message); + } catch (RemoteException e) { + // Process died? + } + } + + private int findFirstFreeSlot(NetworkAgentInfo nai) { + HashMap networkKeepalives = mKeepalives.get(nai); + if (networkKeepalives == null) { + networkKeepalives = new HashMap<Integer, KeepaliveInfo>(); + mKeepalives.put(nai, networkKeepalives); + } + + // Find the lowest-numbered free slot. Slot numbers start from 1, because that's what two + // separate chipset implementations independently came up with. + int slot; + for (slot = 1; slot <= networkKeepalives.size(); slot++) { + if (networkKeepalives.get(slot) == null) { + return slot; + } + } + return slot; + } + + public void handleStartKeepalive(Message message) { + KeepaliveInfo ki = (KeepaliveInfo) message.obj; + NetworkAgentInfo nai = ki.getNai(); + int slot = findFirstFreeSlot(nai); + mKeepalives.get(nai).put(slot, ki); + ki.start(slot); + } + + public void handleStopAllKeepalives(NetworkAgentInfo nai, int reason) { + HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai); + if (networkKeepalives != null) { + for (KeepaliveInfo ki : networkKeepalives.values()) { + ki.stop(reason); + } + networkKeepalives.clear(); + mKeepalives.remove(nai); + } + } + + public void handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason) { + String networkName = (nai == null) ? "(null)" : nai.name(); + HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai); + if (networkKeepalives == null) { + Log.e(TAG, "Attempt to stop keepalive on nonexistent network " + networkName); + return; + } + KeepaliveInfo ki = networkKeepalives.get(slot); + if (ki == null) { + Log.e(TAG, "Attempt to stop nonexistent keepalive " + slot + " on " + networkName); + return; + } + ki.stop(reason); + networkKeepalives.remove(slot); + if (networkKeepalives.isEmpty()) { + mKeepalives.remove(nai); + } + } + + public void handleCheckKeepalivesStillValid(NetworkAgentInfo nai) { + HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai); + if (networkKeepalives != null) { + ArrayList<Pair<Integer, Integer>> invalidKeepalives = new ArrayList<>(); + for (int slot : networkKeepalives.keySet()) { + int error = networkKeepalives.get(slot).isValid(); + if (error != SUCCESS) { + invalidKeepalives.add(Pair.create(slot, error)); + } + } + for (Pair<Integer, Integer> slotAndError: invalidKeepalives) { + handleStopKeepalive(nai, slotAndError.first, slotAndError.second); + } + } + } + + public void handleEventPacketKeepalive(NetworkAgentInfo nai, Message message) { + int slot = message.arg1; + int reason = message.arg2; + + KeepaliveInfo ki = null; + try { + ki = mKeepalives.get(nai).get(slot); + } catch(NullPointerException e) {} + if (ki == null) { + Log.e(TAG, "Event for unknown keepalive " + slot + " on " + nai.name()); + return; + } + + if (reason == SUCCESS && !ki.isStarted) { + // Keepalive successfully started. + if (DBG) Log.d(TAG, "Started keepalive " + slot + " on " + nai.name()); + ki.isStarted = true; + ki.notifyMessenger(slot, reason); + } else { + // Keepalive successfully stopped, or error. + ki.isStarted = false; + if (reason == SUCCESS) { + if (DBG) Log.d(TAG, "Successfully stopped keepalive " + slot + " on " + nai.name()); + } else { + if (DBG) Log.d(TAG, "Keepalive " + slot + " on " + nai.name() + " error " + reason); + } + handleStopKeepalive(nai, slot, reason); + } + } + + public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger, + IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) { + if (nai == null) { + notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK); + return; + } + + InetAddress srcAddress, dstAddress; + try { + srcAddress = NetworkUtils.numericToInetAddress(srcAddrString); + dstAddress = NetworkUtils.numericToInetAddress(dstAddrString); + } catch (IllegalArgumentException e) { + notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_IP_ADDRESS); + return; + } + + KeepalivePacketData packet; + try { + packet = KeepalivePacketData.nattKeepalivePacket( + srcAddress, srcPort, dstAddress, NATT_PORT); + } catch (KeepalivePacketData.InvalidPacketException e) { + notifyMessenger(messenger, NO_KEEPALIVE, e.error); + return; + } + KeepaliveInfo ki = new KeepaliveInfo(messenger, binder, nai, packet, intervalSeconds); + Log.d(TAG, "Created keepalive: " + ki.toString()); + mConnectivityServiceHandler.obtainMessage( + NetworkAgent.CMD_START_PACKET_KEEPALIVE, ki).sendToTarget(); + } + + public void dump(IndentingPrintWriter pw) { + pw.println("Packet keepalives:"); + pw.increaseIndent(); + for (NetworkAgentInfo nai : mKeepalives.keySet()) { + pw.println(nai.name()); + pw.increaseIndent(); + for (int slot : mKeepalives.get(nai).keySet()) { + KeepaliveInfo ki = mKeepalives.get(nai).get(slot); + pw.println(slot + ": " + ki.toString()); + } + pw.decreaseIndent(); + } + pw.decreaseIndent(); + } +} diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 39333f6..0029279 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -195,6 +195,12 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { request.networkCapabilities.satisfiedByNetworkCapabilities(networkCapabilities); } + public boolean satisfiesImmutableCapabilitiesOf(NetworkRequest request) { + return created && + request.networkCapabilities.satisfiedByImmutableNetworkCapabilities( + networkCapabilities); + } + public boolean isVPN() { return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN); } diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java index aca6991..5fd39c0 100644 --- a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java +++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java @@ -18,6 +18,7 @@ package com.android.server.connectivity; import static android.system.OsConstants.*; +import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkUtils; @@ -27,6 +28,7 @@ import android.system.ErrnoException; import android.system.Os; import android.system.StructTimeval; import android.text.TextUtils; +import android.util.Pair; import com.android.internal.util.IndentingPrintWriter; @@ -149,6 +151,8 @@ public class NetworkDiagnostics { } private final Map<InetAddress, Measurement> mIcmpChecks = new HashMap<>(); + private final Map<Pair<InetAddress, InetAddress>, Measurement> mExplicitSourceIcmpChecks = + new HashMap<>(); private final Map<InetAddress, Measurement> mDnsUdpChecks = new HashMap<>(); private final String mDescription; @@ -178,7 +182,11 @@ public class NetworkDiagnostics { for (RouteInfo route : mLinkProperties.getRoutes()) { if (route.hasGateway()) { - prepareIcmpMeasurement(route.getGateway()); + InetAddress gateway = route.getGateway(); + prepareIcmpMeasurement(gateway); + if (route.isIPv6Default()) { + prepareExplicitSourceIcmpMeasurements(gateway); + } } } for (InetAddress nameserver : mLinkProperties.getDnsServers()) { @@ -213,6 +221,20 @@ public class NetworkDiagnostics { } } + private void prepareExplicitSourceIcmpMeasurements(InetAddress target) { + for (LinkAddress l : mLinkProperties.getLinkAddresses()) { + InetAddress source = l.getAddress(); + if (source instanceof Inet6Address && l.isGlobalPreferred()) { + Pair<InetAddress, InetAddress> srcTarget = new Pair<>(source, target); + if (!mExplicitSourceIcmpChecks.containsKey(srcTarget)) { + Measurement measurement = new Measurement(); + measurement.thread = new Thread(new IcmpCheck(source, target, measurement)); + mExplicitSourceIcmpChecks.put(srcTarget, measurement); + } + } + } + } + private void prepareDnsMeasurement(InetAddress target) { if (!mDnsUdpChecks.containsKey(target)) { Measurement measurement = new Measurement(); @@ -222,13 +244,16 @@ public class NetworkDiagnostics { } private int totalMeasurementCount() { - return mIcmpChecks.size() + mDnsUdpChecks.size(); + return mIcmpChecks.size() + mExplicitSourceIcmpChecks.size() + mDnsUdpChecks.size(); } private void startMeasurements() { for (Measurement measurement : mIcmpChecks.values()) { measurement.thread.start(); } + for (Measurement measurement : mExplicitSourceIcmpChecks.values()) { + measurement.thread.start(); + } for (Measurement measurement : mDnsUdpChecks.values()) { measurement.thread.start(); } @@ -261,6 +286,10 @@ public class NetworkDiagnostics { pw.println(entry.getValue().toString()); } } + for (Map.Entry<Pair<InetAddress, InetAddress>, Measurement> entry : + mExplicitSourceIcmpChecks.entrySet()) { + pw.println(entry.getValue().toString()); + } for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) { if (entry.getKey() instanceof Inet4Address) { pw.println(entry.getValue().toString()); @@ -276,13 +305,15 @@ public class NetworkDiagnostics { private class SimpleSocketCheck implements Closeable { + protected final InetAddress mSource; // Usually null. protected final InetAddress mTarget; protected final int mAddressFamily; protected final Measurement mMeasurement; protected FileDescriptor mFileDescriptor; protected SocketAddress mSocketAddress; - protected SimpleSocketCheck(InetAddress target, Measurement measurement) { + protected SimpleSocketCheck( + InetAddress source, InetAddress target, Measurement measurement) { mMeasurement = measurement; if (target instanceof Inet6Address) { @@ -301,6 +332,14 @@ public class NetworkDiagnostics { mTarget = target; mAddressFamily = AF_INET; } + + // We don't need to check the scope ID here because we currently only do explicit-source + // measurements from global IPv6 addresses. + mSource = source; + } + + protected SimpleSocketCheck(InetAddress target, Measurement measurement) { + this(null, target, measurement); } protected void setupSocket( @@ -314,6 +353,9 @@ public class NetworkDiagnostics { SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(readTimeout)); // TODO: Use IP_RECVERR/IPV6_RECVERR, pending OsContants availability. mNetwork.bindSocket(mFileDescriptor); + if (mSource != null) { + Os.bind(mFileDescriptor, mSource, 0); + } Os.connect(mFileDescriptor, mTarget, dstPort); mSocketAddress = Os.getsockname(mFileDescriptor); } @@ -343,8 +385,8 @@ public class NetworkDiagnostics { private final int mProtocol; private final int mIcmpType; - public IcmpCheck(InetAddress target, Measurement measurement) { - super(target, measurement); + public IcmpCheck(InetAddress source, InetAddress target, Measurement measurement) { + super(source, target, measurement); if (mAddressFamily == AF_INET6) { mProtocol = IPPROTO_ICMPV6; @@ -359,6 +401,10 @@ public class NetworkDiagnostics { mMeasurement.description += " dst{" + mTarget.getHostAddress() + "}"; } + public IcmpCheck(InetAddress target, Measurement measurement) { + this(null, target, measurement); + } + @Override public void run() { // Check if this measurement has already failed during setup. diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java index 6ba25a5..701b9f1 100644 --- a/services/core/java/com/android/server/display/DisplayAdapter.java +++ b/services/core/java/com/android/server/display/DisplayAdapter.java @@ -49,6 +49,13 @@ abstract class DisplayAdapter { */ private static final AtomicInteger NEXT_DISPLAY_MODE_ID = new AtomicInteger(1); // 0 = no mode. + /** + * Used to generate globally unique color transform ids. + * + * Valid IDs start at 1 with 0 as the sentinel value for the default mode. + */ + private static final AtomicInteger NEXT_COLOR_TRANSFORM_ID = new AtomicInteger(1); + // Called with SyncRoot lock held. public DisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, Listener listener, String name) { @@ -134,6 +141,11 @@ abstract class DisplayAdapter { NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate); } + public static Display.ColorTransform createColorTransform(int colorTransform) { + return new Display.ColorTransform( + NEXT_COLOR_TRANSFORM_ID.getAndIncrement(), colorTransform); + } + public interface Listener { public void onDisplayDeviceEvent(DisplayDevice device, int event); public void onTraversalRequested(); diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 93bda46..7af0bdb 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -135,7 +135,7 @@ abstract class DisplayDevice { /** * Sets the mode, if supported. */ - public void requestModeInTransactionLocked(int id) { + public void requestColorTransformAndModeInTransactionLocked(int colorTransformId, int modeId) { } /** diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 97ada15..55ba302 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -155,6 +155,15 @@ final class DisplayDeviceInfo { */ public Display.Mode[] supportedModes = Display.Mode.EMPTY_ARRAY; + /** The active color transform of the display */ + public int colorTransformId; + + /** The default color transform of the display */ + public int defaultColorTransformId; + + /** The supported color transforms of the display */ + public Display.ColorTransform[] supportedColorTransforms = Display.ColorTransform.EMPTY_ARRAY; + /** * The nominal apparent density of the display in DPI used for layout calculations. * This density is sensitive to the viewing distance. A big TV and a tablet may have @@ -276,6 +285,9 @@ final class DisplayDeviceInfo { || modeId != other.modeId || defaultModeId != other.defaultModeId || !Arrays.equals(supportedModes, other.supportedModes) + || colorTransformId != other.colorTransformId + || defaultColorTransformId != other.defaultColorTransformId + || !Arrays.equals(supportedColorTransforms, other.supportedColorTransforms) || densityDpi != other.densityDpi || xDpi != other.xDpi || yDpi != other.yDpi @@ -306,6 +318,9 @@ final class DisplayDeviceInfo { modeId = other.modeId; defaultModeId = other.defaultModeId; supportedModes = other.supportedModes; + colorTransformId = other.colorTransformId; + defaultColorTransformId = other.defaultColorTransformId; + supportedColorTransforms = other.supportedColorTransforms; densityDpi = other.densityDpi; xDpi = other.xDpi; yDpi = other.yDpi; @@ -331,6 +346,9 @@ final class DisplayDeviceInfo { sb.append(", modeId ").append(modeId); sb.append(", defaultModeId ").append(defaultModeId); sb.append(", supportedModes ").append(Arrays.toString(supportedModes)); + sb.append(", colorTransformId ").append(colorTransformId); + sb.append(", defaultColorTransformId ").append(defaultColorTransformId); + sb.append(", supportedColorTransforms ").append(Arrays.toString(supportedColorTransforms)); sb.append(", density ").append(densityDpi); sb.append(", ").append(xDpi).append(" x ").append(yDpi).append(" dpi"); sb.append(", appVsyncOff ").append(appVsyncOffsetNanos); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 81c3791..e8a857b 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -542,6 +542,17 @@ public final class DisplayManagerService extends SystemService { } } + private void requestColorTransformInternal(int displayId, int colorTransformId) { + synchronized (mSyncRoot) { + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display != null && + display.getRequestedColorTransformIdLocked() != colorTransformId) { + display.setRequestedColorTransformIdLocked(colorTransformId); + scheduleTraversalLocked(false); + } + } + } + private int createVirtualDisplayInternal(IVirtualDisplayCallback callback, IMediaProjection projection, int callingUid, String packageName, String name, int width, int height, int densityDpi, Surface surface, int flags) { @@ -1342,6 +1353,19 @@ public final class DisplayManagerService extends SystemService { } @Override // Binder call + public void requestColorTransform(int displayId, int colorTransformId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.CONFIGURE_DISPLAY_COLOR_TRANSFORM, + "Permission required to change the display color transform"); + final long token = Binder.clearCallingIdentity(); + try { + requestColorTransformInternal(displayId, colorTransformId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call public int createVirtualDisplay(IVirtualDisplayCallback callback, IMediaProjection projection, String packageName, String name, int width, int height, int densityDpi, Surface surface, int flags) { diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java index f53ccc9..2eabd32 100644 --- a/services/core/java/com/android/server/display/DisplayPowerState.java +++ b/services/core/java/com/android/server/display/DisplayPowerState.java @@ -341,7 +341,8 @@ final class DisplayPowerState { private int mPendingBacklight = INITIAL_BACKLIGHT; private int mActualState = INITIAL_SCREEN_STATE; private int mActualBacklight = INITIAL_BACKLIGHT; - private boolean mChangeInProgress; + private boolean mStateChangeInProgress; + private boolean mBacklightChangeInProgress; public PhotonicModulator() { super("PhotonicModulator"); @@ -349,7 +350,9 @@ final class DisplayPowerState { public boolean setState(int state, int backlight) { synchronized (mLock) { - if (state != mPendingState || backlight != mPendingBacklight) { + boolean stateChanged = state != mPendingState; + boolean backlightChanged = backlight != mPendingBacklight; + if (stateChanged || backlightChanged) { if (DEBUG) { Slog.d(TAG, "Requesting new screen state: state=" + Display.stateToString(state) + ", backlight=" + backlight); @@ -358,12 +361,15 @@ final class DisplayPowerState { mPendingState = state; mPendingBacklight = backlight; - if (!mChangeInProgress) { - mChangeInProgress = true; + boolean changeInProgress = mStateChangeInProgress || mBacklightChangeInProgress; + mStateChangeInProgress = stateChanged; + mBacklightChangeInProgress = backlightChanged; + + if (!changeInProgress) { mLock.notifyAll(); } } - return !mChangeInProgress; + return !mStateChangeInProgress; } } @@ -375,7 +381,8 @@ final class DisplayPowerState { pw.println(" mPendingBacklight=" + mPendingBacklight); pw.println(" mActualState=" + Display.stateToString(mActualState)); pw.println(" mActualBacklight=" + mActualBacklight); - pw.println(" mChangeInProgress=" + mChangeInProgress); + pw.println(" mStateChangeInProgress=" + mStateChangeInProgress); + pw.println(" mBacklightChangeInProgress=" + mBacklightChangeInProgress); } } @@ -392,10 +399,15 @@ final class DisplayPowerState { stateChanged = (state != mActualState); backlight = mPendingBacklight; backlightChanged = (backlight != mActualBacklight); - if (!stateChanged && !backlightChanged) { - // All changed applied, notify outer class and wait for more. - mChangeInProgress = false; + if (!stateChanged) { + // State changed applied, notify outer class. postScreenUpdateThreadSafe(); + mStateChangeInProgress = false; + } + if (!backlightChanged) { + mBacklightChangeInProgress = false; + } + if (!stateChanged && !backlightChanged) { try { mLock.wait(); } catch (InterruptedException ex) { } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 6d7af9d..b2207f3 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -31,6 +31,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.view.Display; import android.view.DisplayEventReceiver; import android.view.Surface; @@ -38,6 +39,7 @@ import android.view.SurfaceControl; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; /** * A display adapter for the local displays managed by Surface Flinger. @@ -144,14 +146,22 @@ final class LocalDisplayAdapter extends DisplayAdapter { private final int mBuiltInDisplayId; private final Light mBacklight; private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>(); + private final SparseArray<Display.ColorTransform> mSupportedColorTransforms = + new SparseArray<>(); private DisplayDeviceInfo mInfo; private boolean mHavePendingChanges; private int mState = Display.STATE_UNKNOWN; private int mBrightness = PowerManager.BRIGHTNESS_DEFAULT; + private int mActivePhysIndex; private int mDefaultModeId; private int mActiveModeId; private boolean mActiveModeInvalid; + private int mDefaultColorTransformId; + private int mActiveColorTransformId; + private boolean mActiveColorTransformInvalid; + + private SurfaceControl.PhysicalDisplayInfo mDisplayInfos[]; public LocalDisplayDevice(IBinder displayToken, int builtInDisplayId, SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo) { @@ -168,22 +178,73 @@ final class LocalDisplayAdapter extends DisplayAdapter { public boolean updatePhysicalDisplayInfoLocked( SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo) { + mDisplayInfos = Arrays.copyOf(physicalDisplayInfos, physicalDisplayInfos.length); + mActivePhysIndex = activeDisplayInfo; + ArrayList<Display.ColorTransform> colorTransforms = new ArrayList<>(); + + // Build an updated list of all existing color transforms. + boolean colorTransformsAdded = false; + Display.ColorTransform activeColorTransform = null; + for (int i = 0; i < physicalDisplayInfos.length; i++) { + SurfaceControl.PhysicalDisplayInfo info = physicalDisplayInfos[i]; + // First check to see if we've already added this color transform + boolean existingMode = false; + for (int j = 0; j < colorTransforms.size(); j++) { + if (colorTransforms.get(j).getColorTransform() == info.colorTransform) { + existingMode = true; + break; + } + } + if (existingMode) { + continue; + } + Display.ColorTransform colorTransform = findColorTransform(info); + if (colorTransform == null) { + colorTransform = createColorTransform(info.colorTransform); + colorTransformsAdded = true; + } + colorTransforms.add(colorTransform); + if (i == activeDisplayInfo) { + activeColorTransform = colorTransform; + } + } + // Build an updated list of all existing modes. - boolean modesAdded = false; - DisplayModeRecord activeRecord = null; ArrayList<DisplayModeRecord> records = new ArrayList<DisplayModeRecord>(); + boolean modesAdded = false; for (int i = 0; i < physicalDisplayInfos.length; i++) { SurfaceControl.PhysicalDisplayInfo info = physicalDisplayInfos[i]; + // First, check to see if we've already added a matching mode. Since not all + // configuration options are exposed via Display.Mode, it's possible that we have + // multiple PhysicalDisplayInfos that would generate the same Display.Mode. + boolean existingMode = false; + for (int j = 0; j < records.size(); j++) { + if (records.get(j).hasMatchingMode(info)) { + existingMode = true; + break; + } + } + if (existingMode) { + continue; + } + // If we haven't already added a mode for this configuration to the new set of + // supported modes then check to see if we have one in the prior set of supported + // modes to reuse. DisplayModeRecord record = findDisplayModeRecord(info); - if (record != null) { - record.mPhysIndex = i; - } else { - record = new DisplayModeRecord(info, i); + if (record == null) { + record = new DisplayModeRecord(info); modesAdded = true; } records.add(record); - if (i == activeDisplayInfo) { + } + + // Get the currently active mode + DisplayModeRecord activeRecord = null; + for (int i = 0; i < records.size(); i++) { + DisplayModeRecord record = records.get(i); + if (record.hasMatchingMode(physicalDisplayInfos[activeDisplayInfo])){ activeRecord = record; + break; } } // Check whether surface flinger spontaneously changed modes out from under us. Schedule @@ -193,25 +254,48 @@ final class LocalDisplayAdapter extends DisplayAdapter { mActiveModeInvalid = true; sendTraversalRequestLocked(); } - // If no modes were added and we have the same number of modes as before, then nothing - // actually changed except possibly the physical index (which we only care about when - // setting the mode) so we're done. - if (records.size() == mSupportedModes.size() && !modesAdded) { + // Check whether surface flinger spontaneously changed color transforms out from under + // us. + if (mActiveColorTransformId != 0 + && mActiveColorTransformId != activeColorTransform.getId()) { + mActiveColorTransformInvalid = true; + sendTraversalRequestLocked(); + } + + boolean colorTransformsChanged = + colorTransforms.size() != mSupportedColorTransforms.size() + || colorTransformsAdded; + boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded; + // If neither the records nor the supported color transforms have changed then we're + // done here. + if (!recordsChanged && !colorTransformsChanged) { return false; } // Update the index of modes. mHavePendingChanges = true; + mSupportedModes.clear(); for (DisplayModeRecord record : records) { mSupportedModes.put(record.mMode.getModeId(), record); } - // Update the default mode if needed. - if (mSupportedModes.indexOfKey(mDefaultModeId) < 0) { + mSupportedColorTransforms.clear(); + for (Display.ColorTransform colorTransform : colorTransforms) { + mSupportedColorTransforms.put(colorTransform.getId(), colorTransform); + } + + // Update the default mode and color transform if needed. This needs to be done in + // tandem so we always have a default state to fall back to. + if (findDisplayInfoIndexLocked(mDefaultColorTransformId, mDefaultModeId) < 0) { if (mDefaultModeId != 0) { - Slog.w(TAG, "Default display mode no longer available, using currently active" - + " mode as default."); + Slog.w(TAG, "Default display mode no longer available, using currently" + + " active mode as default."); } mDefaultModeId = activeRecord.mMode.getModeId(); + if (mDefaultColorTransformId != 0) { + Slog.w(TAG, "Default color transform no longer available, using currently" + + " active color transform as default"); + } + mDefaultColorTransformId = activeColorTransform.getId(); } // Determine whether the active mode is still there. if (mSupportedModes.indexOfKey(mActiveModeId) < 0) { @@ -222,6 +306,16 @@ final class LocalDisplayAdapter extends DisplayAdapter { mActiveModeId = mDefaultModeId; mActiveModeInvalid = true; } + + // Determine whether the active color transform is still there. + if (mSupportedColorTransforms.indexOfKey(mActiveColorTransformId) < 0) { + if (mActiveColorTransformId != 0) { + Slog.w(TAG, "Active color transform no longer available, reverting" + + " to default transform."); + } + mActiveColorTransformId = mDefaultColorTransformId; + mActiveColorTransformInvalid = true; + } // Schedule traversals so that we apply pending changes. sendTraversalRequestLocked(); return true; @@ -230,13 +324,23 @@ final class LocalDisplayAdapter extends DisplayAdapter { private DisplayModeRecord findDisplayModeRecord(SurfaceControl.PhysicalDisplayInfo info) { for (int i = 0; i < mSupportedModes.size(); i++) { DisplayModeRecord record = mSupportedModes.valueAt(i); - if (record.mPhys.equals(info)) { + if (record.hasMatchingMode(info)) { return record; } } return null; } + private Display.ColorTransform findColorTransform(SurfaceControl.PhysicalDisplayInfo info) { + for (int i = 0; i < mSupportedColorTransforms.size(); i++) { + Display.ColorTransform transform = mSupportedColorTransforms.valueAt(i); + if (transform.getColorTransform() == info.colorTransform) { + return transform; + } + } + return null; + } + @Override public void applyPendingDisplayDeviceInfoChangesLocked() { if (mHavePendingChanges) { @@ -248,7 +352,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { @Override public DisplayDeviceInfo getDisplayDeviceInfoLocked() { if (mInfo == null) { - SurfaceControl.PhysicalDisplayInfo phys = mSupportedModes.get(mActiveModeId).mPhys; + SurfaceControl.PhysicalDisplayInfo phys = mDisplayInfos[mActivePhysIndex]; mInfo = new DisplayDeviceInfo(); mInfo.width = phys.width; mInfo.height = phys.height; @@ -259,6 +363,13 @@ final class LocalDisplayAdapter extends DisplayAdapter { DisplayModeRecord record = mSupportedModes.valueAt(i); mInfo.supportedModes[i] = record.mMode; } + mInfo.colorTransformId = mActiveColorTransformId; + mInfo.defaultColorTransformId = mDefaultColorTransformId; + mInfo.supportedColorTransforms = + new Display.ColorTransform[mSupportedColorTransforms.size()]; + for (int i = 0; i < mSupportedColorTransforms.size(); i++) { + mInfo.supportedColorTransforms[i] = mSupportedColorTransforms.valueAt(i); + } mInfo.appVsyncOffsetNanos = phys.appVsyncOffsetNanos; mInfo.presentationDeadlineNanos = phys.presentationDeadlineNanos; mInfo.state = mState; @@ -403,7 +514,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { } @Override - public void requestModeInTransactionLocked(int modeId) { + public void requestColorTransformAndModeInTransactionLocked( + int colorTransformId, int modeId) { if (modeId == 0) { modeId = mDefaultModeId; } else if (mSupportedModes.indexOfKey(modeId) < 0) { @@ -411,13 +523,37 @@ final class LocalDisplayAdapter extends DisplayAdapter { + " reverting to default display mode."); modeId = mDefaultModeId; } - if (mActiveModeId == modeId && !mActiveModeInvalid) { + + if (colorTransformId == 0) { + colorTransformId = mDefaultColorTransformId; + } else if (mSupportedColorTransforms.indexOfKey(colorTransformId) < 0) { + Slog.w(TAG, "Requested color transform " + colorTransformId + " is not supported" + + " by this display, reverting to the default color transform"); + colorTransformId = mDefaultColorTransformId; + } + int physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId); + if (physIndex < 0) { + Slog.w(TAG, "Requested color transform, mode ID pair (" + colorTransformId + ", " + + modeId + ") not available, trying color transform with default mode ID"); + modeId = mDefaultModeId; + physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId); + if (physIndex < 0) { + Slog.w(TAG, "Requested color transform with default mode ID still not" + + " available, falling back to default color transform with default" + + " mode."); + colorTransformId = mDefaultColorTransformId; + physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId); + } + } + if (mActivePhysIndex == physIndex) { return; } - DisplayModeRecord record = mSupportedModes.get(modeId); - SurfaceControl.setActiveConfig(getDisplayTokenLocked(), record.mPhysIndex); + SurfaceControl.setActiveConfig(getDisplayTokenLocked(), physIndex); + mActivePhysIndex = physIndex; mActiveModeId = modeId; mActiveModeInvalid = false; + mActiveColorTransformId = colorTransformId; + mActiveColorTransformInvalid = false; updateDeviceInfoLocked(); } @@ -425,10 +561,43 @@ final class LocalDisplayAdapter extends DisplayAdapter { public void dumpLocked(PrintWriter pw) { super.dumpLocked(pw); pw.println("mBuiltInDisplayId=" + mBuiltInDisplayId); + pw.println("mActivePhysIndex=" + mActivePhysIndex); pw.println("mActiveModeId=" + mActiveModeId); + pw.println("mActiveColorTransformId=" + mActiveColorTransformId); pw.println("mState=" + Display.stateToString(mState)); pw.println("mBrightness=" + mBrightness); pw.println("mBacklight=" + mBacklight); + pw.println("mDisplayInfos="); + for (int i = 0; i < mDisplayInfos.length; i++) { + pw.println(" " + mDisplayInfos[i]); + } + pw.println("mSupportedModes="); + for (int i = 0; i < mSupportedModes.size(); i++) { + pw.println(" " + mSupportedModes.valueAt(i)); + } + pw.println("mSupportedColorTransforms=["); + for (int i = 0; i < mSupportedColorTransforms.size(); i++) { + if (i != 0) { + pw.print(", "); + } + pw.print(mSupportedColorTransforms.valueAt(i)); + } + pw.println("]"); + } + + private int findDisplayInfoIndexLocked(int colorTransformId, int modeId) { + DisplayModeRecord record = mSupportedModes.get(modeId); + Display.ColorTransform transform = mSupportedColorTransforms.get(colorTransformId); + if (record != null && transform != null) { + for (int i = 0; i < mDisplayInfos.length; i++) { + SurfaceControl.PhysicalDisplayInfo info = mDisplayInfos[i]; + if (info.colorTransform == transform.getColorTransform() + && record.hasMatchingMode(info)){ + return i; + } + } + } + return -1; } private void updateDeviceInfoLocked() { @@ -442,13 +611,28 @@ final class LocalDisplayAdapter extends DisplayAdapter { */ private static final class DisplayModeRecord { public final Display.Mode mMode; - public final SurfaceControl.PhysicalDisplayInfo mPhys; - public int mPhysIndex; - public DisplayModeRecord(SurfaceControl.PhysicalDisplayInfo phys, int physIndex) { + public DisplayModeRecord(SurfaceControl.PhysicalDisplayInfo phys) { mMode = createMode(phys.width, phys.height, phys.refreshRate); - mPhys = phys; - mPhysIndex = physIndex; + } + + /** + * Returns whether the mode generated by the given PhysicalDisplayInfo matches the mode + * contained by the record modulo mode ID. + * + * Note that this doesn't necessarily mean the the PhysicalDisplayInfos are identical, just + * that they generate identical modes. + */ + public boolean hasMatchingMode(SurfaceControl.PhysicalDisplayInfo info) { + int modeRefreshRate = Float.floatToIntBits(mMode.getRefreshRate()); + int displayInfoRefreshRate = Float.floatToIntBits(info.refreshRate); + return mMode.getPhysicalWidth() == info.width + && mMode.getPhysicalHeight() == info.height + && modeRefreshRate == displayInfoRefreshRate; + } + + public String toString() { + return "DisplayModeRecord{mMode=" + mMode + "}"; } } diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 6efc99a..6dae397 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -74,6 +74,7 @@ final class LogicalDisplay { private boolean mHasContent; private int mRequestedModeId; + private int mRequestedColorTransformId; // The display offsets to apply to the display projection. private int mDisplayOffsetX; @@ -235,6 +236,11 @@ final class LogicalDisplay { mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId; mBaseDisplayInfo.supportedModes = Arrays.copyOf( deviceInfo.supportedModes, deviceInfo.supportedModes.length); + mBaseDisplayInfo.colorTransformId = deviceInfo.colorTransformId; + mBaseDisplayInfo.defaultColorTransformId = deviceInfo.defaultColorTransformId; + mBaseDisplayInfo.supportedColorTransforms = Arrays.copyOf( + deviceInfo.supportedColorTransforms, + deviceInfo.supportedColorTransforms.length); mBaseDisplayInfo.logicalDensityDpi = deviceInfo.densityDpi; mBaseDisplayInfo.physicalXDpi = deviceInfo.xDpi; mBaseDisplayInfo.physicalYDpi = deviceInfo.yDpi; @@ -275,11 +281,12 @@ final class LogicalDisplay { // Set the layer stack. device.setLayerStackInTransactionLocked(isBlanked ? BLANK_LAYER_STACK : mLayerStack); - // Set the mode. + // Set the color transform and mode. if (device == mPrimaryDisplayDevice) { - device.requestModeInTransactionLocked(mRequestedModeId); + device.requestColorTransformAndModeInTransactionLocked( + mRequestedColorTransformId, mRequestedModeId); } else { - device.requestModeInTransactionLocked(0); // Revert to default. + device.requestColorTransformAndModeInTransactionLocked(0, 0); // Revert to default. } // Only grab the display info now as it may have been changed based on the requests above. @@ -383,6 +390,18 @@ final class LogicalDisplay { } /** + * Requests the given color transform. + */ + public void setRequestedColorTransformIdLocked(int colorTransformId) { + mRequestedColorTransformId = colorTransformId; + } + + /** Returns the pending requested color transform. */ + public int getRequestedColorTransformIdLocked() { + return mRequestedColorTransformId; + } + + /** * Gets the burn-in offset in X. */ public int getDisplayOffsetXLocked() { @@ -409,6 +428,7 @@ final class LogicalDisplay { pw.println("mLayerStack=" + mLayerStack); pw.println("mHasContent=" + mHasContent); pw.println("mRequestedMode=" + mRequestedModeId); + pw.println("mRequestedColorTransformId=" + mRequestedColorTransformId); pw.println("mDisplayOffset=(" + mDisplayOffsetX + ", " + mDisplayOffsetY + ")"); pw.println("mPrimaryDisplayDevice=" + (mPrimaryDisplayDevice != null ? mPrimaryDisplayDevice.getNameLocked() : "null")); diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index 0bddff0..cf6264a 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java @@ -310,7 +310,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { } @Override - public void requestModeInTransactionLocked(int id) { + public void requestColorTransformAndModeInTransactionLocked(int color, int id) { int index = -1; if (id == 0) { // Use the default. diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 2c9d82b..ec7c1c4 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -16,21 +16,29 @@ package com.android.server.fingerprint; +import android.Manifest; import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManagerNative; +import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.IUserSwitchObserver; -import android.content.ContentResolver; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback; import android.os.Binder; +import android.os.DeadObjectException; import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.IRemoteCallback; -import android.os.Looper; -import android.os.MessageQueue; import android.os.PowerManager; import android.os.RemoteException; import android.os.SELinux; @@ -40,8 +48,13 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; +import com.android.internal.logging.MetricsLogger; import com.android.server.SystemService; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IFingerprintService; @@ -49,10 +62,14 @@ import android.hardware.fingerprint.IFingerprintDaemon; import android.hardware.fingerprint.IFingerprintDaemonCallback; import android.hardware.fingerprint.IFingerprintServiceReceiver; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.Manifest.permission.MANAGE_FINGERPRINT; +import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT; import static android.Manifest.permission.USE_FINGERPRINT; import java.io.File; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -73,16 +90,21 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe private static final String FINGERPRINTD = "android.hardware.fingerprint.IFingerprintDaemon"; private static final int MSG_USER_SWITCHING = 10; private static final int ENROLLMENT_TIMEOUT_MS = 60 * 1000; // 1 minute + private static final String ACTION_LOCKOUT_RESET = + "com.android.server.fingerprint.ACTION_LOCKOUT_RESET"; private ClientMonitor mAuthClient = null; private ClientMonitor mEnrollClient = null; private ClientMonitor mRemoveClient = null; + private final ArrayList<FingerprintServiceLockoutResetMonitor> mLockoutMonitors = + new ArrayList<>(); private final AppOpsManager mAppOps; private static final long MS_PER_SEC = 1000; private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000; private static final int MAX_FAILED_ATTEMPTS = 5; private static final int FINGERPRINT_ACQUIRED_GOOD = 0; + private final String mKeyguardPackage; Handler mHandler = new Handler() { @Override @@ -103,9 +125,19 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe private long mHalDeviceId; private int mFailedAttempts; private IFingerprintDaemon mDaemon; - private PowerManager mPowerManager; + private final PowerManager mPowerManager; + private final AlarmManager mAlarmManager; - private final Runnable mLockoutReset = new Runnable() { + private final BroadcastReceiver mLockoutReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_LOCKOUT_RESET.equals(intent.getAction())) { + resetFailedAttempts(); + } + } + }; + + private final Runnable mResetFailedAttemptsRunnable = new Runnable() { @Override public void run() { resetFailedAttempts(); @@ -115,15 +147,20 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe public FingerprintService(Context context) { super(context); mContext = context; + mKeyguardPackage = ComponentName.unflattenFromString(context.getResources().getString( + com.android.internal.R.string.config_keyguardComponent)).getPackageName(); mAppOps = context.getSystemService(AppOpsManager.class); - mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mPowerManager = mContext.getSystemService(PowerManager.class); + mAlarmManager = mContext.getSystemService(AlarmManager.class); + mContext.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET), + RESET_FINGERPRINT_LOCKOUT, null /* handler */); } @Override public void binderDied() { Slog.v(TAG, "fingerprintd died"); mDaemon = null; - dispatchError(mHalDeviceId, FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE); + handleError(mHalDeviceId, FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE); } public IFingerprintDaemon getFingerprintDaemon() { @@ -151,7 +188,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe return mDaemon; } - protected void dispatchEnumerate(long deviceId, int[] fingerIds, int[] groupIds) { + protected void handleEnumerate(long deviceId, int[] fingerIds, int[] groupIds) { if (fingerIds.length != groupIds.length) { Slog.w(TAG, "fingerIds and groupIds differ in length: f[]=" + fingerIds + ", g[]=" + groupIds); @@ -161,7 +198,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe // TODO: update fingerprint/name pairs } - protected void dispatchRemoved(long deviceId, int fingerId, int groupId) { + protected void handleRemoved(long deviceId, int fingerId, int groupId) { final ClientMonitor client = mRemoveClient; if (fingerId != 0) { removeTemplateForUser(mRemoveClient, fingerId); @@ -171,7 +208,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } } - protected void dispatchError(long deviceId, int error) { + protected void handleError(long deviceId, int error) { if (mEnrollClient != null) { final IBinder token = mEnrollClient.token; if (mEnrollClient.sendError(error)) { @@ -187,7 +224,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } } - protected void dispatchAuthenticated(long deviceId, int fingerId, int groupId) { + protected void handleAuthenticated(long deviceId, int fingerId, int groupId) { if (mAuthClient != null) { final IBinder token = mAuthClient.token; if (mAuthClient.sendAuthenticated(fingerId, groupId)) { @@ -197,7 +234,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } } - protected void dispatchAcquired(long deviceId, int acquiredInfo) { + protected void handleAcquired(long deviceId, int acquiredInfo) { if (mEnrollClient != null) { if (mEnrollClient.sendAcquired(acquiredInfo)) { removeClient(mEnrollClient); @@ -209,16 +246,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } } - private void userActivity() { - long now = SystemClock.uptimeMillis(); - mPowerManager.userActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); - } - - void handleUserSwitching(int userId) { - updateActiveGroup(userId); - } - - protected void dispatchEnrollResult(long deviceId, int fingerId, int groupId, int remaining) { + protected void handleEnrollResult(long deviceId, int fingerId, int groupId, int remaining) { if (mEnrollClient != null) { if (mEnrollClient.sendEnrollResult(fingerId, groupId, remaining)) { if (remaining == 0) { @@ -229,6 +257,15 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } } + private void userActivity() { + long now = SystemClock.uptimeMillis(); + mPowerManager.userActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); + } + + void handleUserSwitching(int userId) { + updateActiveGroup(userId); + } + private void removeClient(ClientMonitor client) { if (client == null) return; client.destroy(); @@ -242,7 +279,21 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } private boolean inLockoutMode() { - return mFailedAttempts > MAX_FAILED_ATTEMPTS; + return mFailedAttempts >= MAX_FAILED_ATTEMPTS; + } + + private void scheduleLockoutReset() { + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS, getLockoutResetIntent()); + } + + private void cancelLockoutReset() { + mAlarmManager.cancel(getLockoutResetIntent()); + } + + private PendingIntent getLockoutResetIntent() { + return PendingIntent.getBroadcast(mContext, 0, + new Intent(ACTION_LOCKOUT_RESET), PendingIntent.FLAG_UPDATE_CURRENT); } private void resetFailedAttempts() { @@ -250,14 +301,17 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe Slog.v(TAG, "Reset fingerprint lockout"); } mFailedAttempts = 0; + // If we're asked to reset failed attempts externally (i.e. from Keyguard), the alarm might + // still be pending; remove it. + cancelLockoutReset(); + notifyLockoutResetMonitors(); } private boolean handleFailedAttempt(ClientMonitor clientMonitor) { mFailedAttempts++; - if (mFailedAttempts > MAX_FAILED_ATTEMPTS) { + if (inLockoutMode()) { // Failing multiple times will continue to push out the lockout time. - mHandler.removeCallbacks(mLockoutReset); - mHandler.postDelayed(mLockoutReset, FAIL_LOCKOUT_TIMEOUT_MS); + scheduleLockoutReset(); if (clientMonitor != null && !clientMonitor.sendError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)) { Slog.w(TAG, "Cannot send lockout message to client"); @@ -283,12 +337,13 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe return; } stopPendingOperations(true); - mEnrollClient = new ClientMonitor(token, receiver, groupId, restricted); + mEnrollClient = new ClientMonitor(token, receiver, groupId, restricted, token.toString()); final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC); try { final int result = daemon.enroll(cryptoToken, groupId, timeout); if (result != 0) { Slog.w(TAG, "startEnroll failed, result=" + result); + handleError(mHalDeviceId, FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE); } } catch (RemoteException e) { Slog.e(TAG, "startEnroll failed", e); @@ -362,14 +417,15 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } void startAuthentication(IBinder token, long opId, int groupId, - IFingerprintServiceReceiver receiver, int flags, boolean restricted) { + IFingerprintServiceReceiver receiver, int flags, boolean restricted, + String opPackageName) { IFingerprintDaemon daemon = getFingerprintDaemon(); if (daemon == null) { Slog.w(TAG, "startAuthentication: no fingeprintd!"); return; } stopPendingOperations(true); - mAuthClient = new ClientMonitor(token, receiver, groupId, restricted); + mAuthClient = new ClientMonitor(token, receiver, groupId, restricted, opPackageName); if (inLockoutMode()) { Slog.v(TAG, "In lockout mode; disallowing authentication"); if (!mAuthClient.sendError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)) { @@ -382,6 +438,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe final int result = daemon.authenticate(opId, groupId); if (result != 0) { Slog.w(TAG, "startAuthentication failed, result=" + result); + handleError(mHalDeviceId, FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE); } } catch (RemoteException e) { Slog.e(TAG, "startAuthentication failed", e); @@ -424,12 +481,14 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe return; } - mRemoveClient = new ClientMonitor(token, receiver, userId, restricted); + stopPendingOperations(true); + mRemoveClient = new ClientMonitor(token, receiver, userId, restricted, token.toString()); // The fingerprint template ids will be removed when we get confirmation from the HAL try { final int result = daemon.remove(fingerId, userId); if (result != 0) { Slog.w(TAG, "startRemove with id = " + fingerId + " failed, result=" + result); + handleError(mHalDeviceId, FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE); } } catch (RemoteException e) { Slog.e(TAG, "startRemove failed", e); @@ -480,10 +539,67 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe return false; } - private boolean canUseFingerprint(String opPackageName) { + private boolean isForegroundActivity(int uid, int pid) { + try { + List<RunningAppProcessInfo> procs = + ActivityManagerNative.getDefault().getRunningAppProcesses(); + int N = procs.size(); + for (int i = 0; i < N; i++) { + RunningAppProcessInfo proc = procs.get(i); + if (proc.pid == pid && proc.uid == uid + && proc.importance == IMPORTANCE_FOREGROUND) { + return true; + } + } + } catch (RemoteException e) { + Slog.w(TAG, "am.getRunningAppProcesses() failed"); + } + return false; + } + + /** + * @param opPackageName name of package for caller + * @param foregroundOnly only allow this call while app is in the foreground + * @return true if caller can use fingerprint API + */ + private boolean canUseFingerprint(String opPackageName, boolean foregroundOnly) { checkPermission(USE_FINGERPRINT); - return mAppOps.noteOp(AppOpsManager.OP_USE_FINGERPRINT, Binder.getCallingUid(), - opPackageName) == AppOpsManager.MODE_ALLOWED; + final int uid = Binder.getCallingUid(); + final int pid = Binder.getCallingPid(); + if (opPackageName.equals(mKeyguardPackage)) { + return true; // Keyguard is always allowed + } + if (!isCurrentUserOrProfile(UserHandle.getCallingUserId())) { + Slog.w(TAG,"Rejecting " + opPackageName + " ; not a current user or profile"); + return false; + } + if (mAppOps.noteOp(AppOpsManager.OP_USE_FINGERPRINT, uid, opPackageName) + != AppOpsManager.MODE_ALLOWED) { + Slog.w(TAG, "Rejecting " + opPackageName + " ; permission denied"); + return false; + } + if (foregroundOnly && !isForegroundActivity(uid, pid)) { + Slog.w(TAG, "Rejecting " + opPackageName + " ; not in foreground"); + return false; + } + return true; + } + + private void addLockoutResetMonitor(FingerprintServiceLockoutResetMonitor monitor) { + if (!mLockoutMonitors.contains(monitor)) { + mLockoutMonitors.add(monitor); + } + } + + private void removeLockoutResetCallback( + FingerprintServiceLockoutResetMonitor monitor) { + mLockoutMonitors.remove(monitor); + } + + private void notifyLockoutResetMonitors() { + for (int i = 0; i < mLockoutMonitors.size(); i++) { + mLockoutMonitors.get(i).sendLockoutReset(); + } } private class ClientMonitor implements IBinder.DeathRecipient { @@ -491,13 +607,15 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe IFingerprintServiceReceiver receiver; int userId; boolean restricted; // True if client does not have MANAGE_FINGERPRINT permission + String owner; public ClientMonitor(IBinder token, IFingerprintServiceReceiver receiver, int userId, - boolean restricted) { + boolean restricted, String owner) { this.token = token; this.receiver = receiver; this.userId = userId; this.restricted = restricted; + this.owner = owner; // name of the client that owns this - for debugging try { token.linkToDeath(this, 0); } catch (RemoteException e) { @@ -557,6 +675,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe private boolean sendEnrollResult(int fpId, int groupId, int remaining) { if (receiver == null) return true; // client not listening FingerprintUtils.vibrateFingerprintSuccess(getContext()); + MetricsLogger.action(mContext, MetricsLogger.ACTION_FINGERPRINT_ENROLL); try { receiver.onEnrollResult(mHalDeviceId, fpId, groupId, remaining); return remaining == 0; @@ -574,9 +693,15 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe boolean authenticated = fpId != 0; if (receiver != null) { try { + MetricsLogger.action(mContext, MetricsLogger.ACTION_FINGERPRINT_AUTH, + authenticated); if (!authenticated) { receiver.onAuthenticationFailed(mHalDeviceId); } else { + if (DEBUG) { + Slog.v(TAG, "onAuthenticated(owner=" + mAuthClient.owner + + ", id=" + fpId + ", gp=" + groupId + ")"); + } Fingerprint fp = !restricted ? new Fingerprint("" /* TODO */, groupId, fpId, mHalDeviceId) : null; receiver.onAuthenticationSucceeded(mHalDeviceId, fp); @@ -589,12 +714,16 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe result = true; // client not listening } if (fpId == 0) { - FingerprintUtils.vibrateFingerprintError(getContext()); + if (receiver != null) { + FingerprintUtils.vibrateFingerprintError(getContext()); + } result |= handleFailedAttempt(this); } else { - FingerprintUtils.vibrateFingerprintSuccess(getContext()); + if (receiver != null) { + FingerprintUtils.vibrateFingerprintSuccess(getContext()); + } result |= true; // we have a valid fingerprint - mLockoutReset.run(); + resetFailedAttempts(); } return result; } @@ -634,38 +763,98 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } } + private class FingerprintServiceLockoutResetMonitor { + + private final IFingerprintServiceLockoutResetCallback mCallback; + + public FingerprintServiceLockoutResetMonitor( + IFingerprintServiceLockoutResetCallback callback) { + mCallback = callback; + } + + public void sendLockoutReset() { + if (mCallback != null) { + try { + mCallback.onLockoutReset(mHalDeviceId); + } catch (DeadObjectException e) { + Slog.w(TAG, "Death object while invoking onLockoutReset: ", e); + mHandler.post(mRemoveCallbackRunnable); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke onLockoutReset: ", e); + } + } + } + + private final Runnable mRemoveCallbackRunnable = new Runnable() { + @Override + public void run() { + removeLockoutResetCallback(FingerprintServiceLockoutResetMonitor.this); + } + }; + } + private IFingerprintDaemonCallback mDaemonCallback = new IFingerprintDaemonCallback.Stub() { @Override - public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) { - dispatchEnrollResult(deviceId, fingerId, groupId, remaining); + public void onEnrollResult(final long deviceId, final int fingerId, final int groupId, + final int remaining) { + mHandler.post(new Runnable() { + @Override + public void run() { + handleEnrollResult(deviceId, fingerId, groupId, remaining); + } + }); } @Override - public void onAcquired(long deviceId, int acquiredInfo) { - dispatchAcquired(deviceId, acquiredInfo); + public void onAcquired(final long deviceId, final int acquiredInfo) { + mHandler.post(new Runnable() { + @Override + public void run() { + handleAcquired(deviceId, acquiredInfo); + } + }); } @Override - public void onAuthenticated(long deviceId, int fingerId, int groupId) { - dispatchAuthenticated(deviceId, fingerId, groupId); + public void onAuthenticated(final long deviceId, final int fingerId, final int groupId) { + mHandler.post(new Runnable() { + @Override + public void run() { + handleAuthenticated(deviceId, fingerId, groupId); + } + }); } @Override - public void onError(long deviceId, int error) { - dispatchError(deviceId, error); + public void onError(final long deviceId, final int error) { + mHandler.post(new Runnable() { + @Override + public void run() { + handleError(deviceId, error); + } + }); } @Override - public void onRemoved(long deviceId, int fingerId, int groupId) { - dispatchRemoved(deviceId, fingerId, groupId); + public void onRemoved(final long deviceId, final int fingerId, final int groupId) { + mHandler.post(new Runnable() { + @Override + public void run() { + handleRemoved(deviceId, fingerId, groupId); + } + }); } @Override - public void onEnumerate(long deviceId, int[] fingerIds, int[] groupIds) { - dispatchEnumerate(deviceId, fingerIds, groupIds); + public void onEnumerate(final long deviceId, final int[] fingerIds, final int[] groupIds) { + mHandler.post(new Runnable() { + @Override + public void run() { + handleEnumerate(deviceId, fingerIds, groupIds); + } + }); } - }; private final class FingerprintServiceWrapper extends IFingerprintService.Stub { @@ -732,12 +921,8 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe public void authenticate(final IBinder token, final long opId, final int groupId, final IFingerprintServiceReceiver receiver, final int flags, final String opPackageName) { - if (!isCurrentUserOrProfile(UserHandle.getCallingUserId())) { - Slog.w(TAG, "Can't authenticate non-current user"); - return; - } - if (!canUseFingerprint(opPackageName)) { - Slog.w(TAG, "Calling not granted permission to use fingerprint"); + if (!canUseFingerprint(opPackageName, true /* foregroundOnly */)) { + if (DEBUG) Slog.v(TAG, "authenticate(): reject " + opPackageName); return; } @@ -749,14 +934,16 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe mHandler.post(new Runnable() { @Override public void run() { - startAuthentication(token, opId, effectiveGroupId, receiver, flags, restricted); + MetricsLogger.histogram(mContext, "fingerprint_token", opId != 0L ? 1 : 0); + startAuthentication(token, opId, effectiveGroupId, receiver, flags, restricted, + opPackageName); } }); } @Override // Binder call public void cancelAuthentication(final IBinder token, String opPackageName) { - if (!canUseFingerprint(opPackageName)) { + if (!canUseFingerprint(opPackageName, false /* foregroundOnly */)) { return; } mHandler.post(new Runnable() { @@ -787,7 +974,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe @Override // Binder call public boolean isHardwareDetected(long deviceId, String opPackageName) { - if (!canUseFingerprint(opPackageName)) { + if (!canUseFingerprint(opPackageName, false /* foregroundOnly */)) { return false; } return mHalDeviceId != 0; @@ -811,7 +998,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe @Override // Binder call public List<Fingerprint> getEnrolledFingerprints(int userId, String opPackageName) { - if (!canUseFingerprint(opPackageName)) { + if (!canUseFingerprint(opPackageName, false /* foregroundOnly */)) { return Collections.emptyList(); } int effectiveUserId = getEffectiveUserId(userId); @@ -821,7 +1008,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe @Override // Binder call public boolean hasEnrolledFingerprints(int userId, String opPackageName) { - if (!canUseFingerprint(opPackageName)) { + if (!canUseFingerprint(opPackageName, false /* foregroundOnly */)) { return false; } @@ -849,6 +1036,64 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe return FingerprintService.this.getAuthenticatorId(); } + + @Override // Binder call + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump Fingerprint from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + final long ident = Binder.clearCallingIdentity(); + try { + dumpInternal(pw); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + @Override // Binder call + public void resetTimeout(byte [] token) { + checkPermission(RESET_FINGERPRINT_LOCKOUT); + // TODO: confirm security token when we move timeout management into the HAL layer. + mHandler.post(mResetFailedAttemptsRunnable); + } + + @Override + public void addLockoutResetCallback(final IFingerprintServiceLockoutResetCallback callback) + throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + addLockoutResetMonitor( + new FingerprintServiceLockoutResetMonitor(callback)); + } + }); + } + } + + private void dumpInternal(PrintWriter pw) { + JSONObject dump = new JSONObject(); + try { + dump.put("service", "Fingerprint Manager"); + + JSONArray sets = new JSONArray(); + for (UserInfo user : UserManager.get(getContext()).getUsers()) { + final int userId = user.getUserHandle().getIdentifier(); + final int N = mFingerprintUtils.getFingerprintsForUser(mContext, userId).size(); + JSONObject set = new JSONObject(); + set.put("id", userId); + set.put("count", N); + sets.put(set); + } + + dump.put("prints", sets); + } catch (JSONException e) { + Slog.e(TAG, "dump formatting failure", e); + } + pw.println(dump); } @Override diff --git a/services/core/java/com/android/server/fingerprint/FingerprintUtils.java b/services/core/java/com/android/server/fingerprint/FingerprintUtils.java index d274412..49dc8e4 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintUtils.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintUtils.java @@ -19,6 +19,7 @@ package com.android.server.fingerprint; import android.content.Context; import android.hardware.fingerprint.Fingerprint; import android.os.Vibrator; +import android.text.TextUtils; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -64,6 +65,10 @@ public class FingerprintUtils { } public void renameFingerprintForUser(Context ctx, int fingerId, int userId, CharSequence name) { + if (TextUtils.isEmpty(name)) { + // Don't do the rename if it's empty + return; + } getStateForUser(ctx, userId).renameFingerprint(fingerId, name); } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 1919671..e1cf047 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -17,6 +17,7 @@ package com.android.server.input; import android.view.Display; +import com.android.internal.os.SomeArgs; import com.android.internal.R; import com.android.internal.util.XmlUtils; import com.android.server.DisplayThread; @@ -52,6 +53,7 @@ import android.hardware.input.IInputManager; import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.InputManager; import android.hardware.input.InputManagerInternal; +import android.hardware.input.ITabletModeChangedListener; import android.hardware.input.KeyboardLayout; import android.hardware.input.TouchCalibration; import android.os.Binder; @@ -93,6 +95,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import libcore.io.Streams; import libcore.util.Objects; @@ -112,6 +115,7 @@ public class InputManagerService extends IInputManager.Stub private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3; private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4; private static final int MSG_RELOAD_DEVICE_ALIASES = 5; + private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 6; // Pointer to native input manager service object. private final long mPtr; @@ -124,6 +128,13 @@ public class InputManagerService extends IInputManager.Stub private boolean mSystemReady; private NotificationManager mNotificationManager; + private final Object mTabletModeLock = new Object(); + // List of currently registered tablet mode changed listeners by process id + private final SparseArray<TabletModeChangedListenerRecord> mTabletModeChangedListeners = + new SparseArray<>(); // guarded by mTabletModeLock + private final List<TabletModeChangedListenerRecord> mTempTabletModeChangedListenersToNotify = + new ArrayList<>(); + // Persistent data store. Must be locked each time during use. private final PersistentDataStore mDataStore = new PersistentDataStore(); @@ -230,6 +241,11 @@ public class InputManagerService extends IInputManager.Stub /** Switch code: Lid switch. When set, lid is shut. */ public static final int SW_LID = 0x00; + /** Switch code: Tablet mode switch. + * When set, the device is in tablet mode (i.e. no keyboard is connected). + */ + public static final int SW_TABLET_MODE = 0x01; + /** Switch code: Keypad slide. When set, keyboard is exposed. */ public static final int SW_KEYPAD_SLIDE = 0x0a; @@ -249,6 +265,7 @@ public class InputManagerService extends IInputManager.Stub public static final int SW_CAMERA_LENS_COVER = 0x09; public static final int SW_LID_BIT = 1 << SW_LID; + public static final int SW_TABLET_MODE_BIT = 1 << SW_TABLET_MODE; public static final int SW_KEYPAD_SLIDE_BIT = 1 << SW_KEYPAD_SLIDE; public static final int SW_HEADPHONE_INSERT_BIT = 1 << SW_HEADPHONE_INSERT; public static final int SW_MICROPHONE_INSERT_BIT = 1 << SW_MICROPHONE_INSERT; @@ -819,6 +836,57 @@ public class InputManagerService extends IInputManager.Stub } } + @Override // Binder call + public void registerTabletModeChangedListener(ITabletModeChangedListener listener) { + if (!checkCallingPermission(android.Manifest.permission.TABLET_MODE_LISTENER, + "registerTabletModeChangedListener()")) { + throw new SecurityException("Requires TABLET_MODE_LISTENER permission"); + } + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + synchronized (mTabletModeLock) { + final int callingPid = Binder.getCallingPid(); + if (mTabletModeChangedListeners.get(callingPid) != null) { + throw new IllegalStateException("The calling process has already registered " + + "a TabletModeChangedListener."); + } + TabletModeChangedListenerRecord record = + new TabletModeChangedListenerRecord(callingPid, listener); + try { + IBinder binder = listener.asBinder(); + binder.linkToDeath(record, 0); + } catch (RemoteException ex) { + throw new RuntimeException(ex); + } + mTabletModeChangedListeners.put(callingPid, record); + } + } + + private void onTabletModeChangedListenerDied(int pid) { + synchronized (mTabletModeLock) { + mTabletModeChangedListeners.remove(pid); + } + } + + // Must be called on handler + private void deliverTabletModeChanged(long whenNanos, boolean inTabletMode) { + mTempTabletModeChangedListenersToNotify.clear(); + final int numListeners; + synchronized (mTabletModeLock) { + numListeners = mTabletModeChangedListeners.size(); + for (int i = 0; i < numListeners; i++) { + mTempTabletModeChangedListenersToNotify.add( + mTabletModeChangedListeners.valueAt(i)); + } + } + for (int i = 0; i < numListeners; i++) { + mTempTabletModeChangedListenersToNotify.get(i).notifyTabletModeChanged( + whenNanos, inTabletMode); + } + } + // Must be called on handler. private void showMissingKeyboardLayoutNotification(InputDevice device) { if (!mKeyboardLayoutNotificationShown) { @@ -1516,6 +1584,15 @@ public class InputManagerService extends IInputManager.Stub mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues, switchMask); } + + if ((switchMask & SW_TABLET_MODE) != 0) { + SomeArgs args = SomeArgs.obtain(); + args.argi1 = (int) (whenNanos & 0xFFFFFFFF); + args.argi2 = (int) (whenNanos >> 32); + args.arg1 = Boolean.valueOf((switchValues & SW_TABLET_MODE_BIT) != 0); + mHandler.obtainMessage(MSG_DELIVER_TABLET_MODE_CHANGED, + args).sendToTarget(); + } } // Native callback. @@ -1768,6 +1845,12 @@ public class InputManagerService extends IInputManager.Stub case MSG_RELOAD_DEVICE_ALIASES: reloadDeviceAliases(); break; + case MSG_DELIVER_TABLET_MODE_CHANGED: + SomeArgs args = (SomeArgs) msg.obj; + long whenNanos = (args.argi1 & 0xFFFFFFFFl) | ((long) args.argi2 << 32); + boolean inTabletMode = (boolean) args.arg1; + deliverTabletModeChanged(whenNanos, inTabletMode); + break; } } } @@ -1919,6 +2002,34 @@ public class InputManagerService extends IInputManager.Stub } } + private final class TabletModeChangedListenerRecord implements DeathRecipient { + private final int mPid; + private final ITabletModeChangedListener mListener; + + public TabletModeChangedListenerRecord(int pid, ITabletModeChangedListener listener) { + mPid = pid; + mListener = listener; + } + + @Override + public void binderDied() { + if (DEBUG) { + Slog.d(TAG, "Tablet mode changed listener for pid " + mPid + " died."); + } + onTabletModeChangedListenerDied(mPid); + } + + public void notifyTabletModeChanged(long whenNanos, boolean inTabletMode) { + try { + mListener.onTabletModeChanged(whenNanos, inTabletMode); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + mPid + + " that tablet mode changed, assuming it died.", ex); + binderDied(); + } + } + } + private final class VibratorToken implements DeathRecipient { public final int mDeviceId; public final IBinder mToken; diff --git a/services/core/java/com/android/server/location/ActivityRecognitionProxy.java b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java index 607805b..55222dc 100644 --- a/services/core/java/com/android/server/location/ActivityRecognitionProxy.java +++ b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java @@ -20,8 +20,10 @@ import com.android.server.ServiceWatcher; import android.content.Context; import android.hardware.location.ActivityRecognitionHardware; +import android.hardware.location.IActivityRecognitionHardwareClient; import android.hardware.location.IActivityRecognitionHardwareWatcher; import android.os.Handler; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -34,21 +36,24 @@ public class ActivityRecognitionProxy { private static final String TAG = "ActivityRecognitionProxy"; private final ServiceWatcher mServiceWatcher; - private final ActivityRecognitionHardware mActivityRecognitionHardware; + private final boolean mIsSupported; + private final ActivityRecognitionHardware mInstance; private ActivityRecognitionProxy( Context context, Handler handler, + boolean activityRecognitionHardwareIsSupported, ActivityRecognitionHardware activityRecognitionHardware, int overlaySwitchResId, int defaultServicePackageNameResId, int initialPackageNameResId) { - mActivityRecognitionHardware = activityRecognitionHardware; + mIsSupported = activityRecognitionHardwareIsSupported; + mInstance = activityRecognitionHardware; Runnable newServiceWork = new Runnable() { @Override public void run() { - bindProvider(mActivityRecognitionHardware); + bindProvider(); } }; @@ -72,6 +77,7 @@ public class ActivityRecognitionProxy { public static ActivityRecognitionProxy createAndBind( Context context, Handler handler, + boolean activityRecognitionHardwareIsSupported, ActivityRecognitionHardware activityRecognitionHardware, int overlaySwitchResId, int defaultServicePackageNameResId, @@ -79,6 +85,7 @@ public class ActivityRecognitionProxy { ActivityRecognitionProxy activityRecognitionProxy = new ActivityRecognitionProxy( context, handler, + activityRecognitionHardwareIsSupported, activityRecognitionHardware, overlaySwitchResId, defaultServicePackageNameResId, @@ -89,25 +96,58 @@ public class ActivityRecognitionProxy { Log.e(TAG, "ServiceWatcher could not start."); return null; } - return activityRecognitionProxy; } /** * Helper function to bind the FusedLocationHardware to the appropriate FusedProvider instance. */ - private void bindProvider(ActivityRecognitionHardware activityRecognitionHardware) { - IActivityRecognitionHardwareWatcher watcher = - IActivityRecognitionHardwareWatcher.Stub.asInterface(mServiceWatcher.getBinder()); - if (watcher == null) { - Log.e(TAG, "No provider instance found on connection."); + private void bindProvider() { + IBinder binder = mServiceWatcher.getBinder(); + if (binder == null) { + Log.e(TAG, "Null binder found on connection."); return; } - + String descriptor; try { - watcher.onInstanceChanged(mActivityRecognitionHardware); + descriptor = binder.getInterfaceDescriptor(); } catch (RemoteException e) { - Log.e(TAG, "Error delivering hardware interface.", e); + Log.e(TAG, "Unable to get interface descriptor.", e); + return; + } + + if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(descriptor)) { + IActivityRecognitionHardwareWatcher watcher = + IActivityRecognitionHardwareWatcher.Stub.asInterface(binder); + if (watcher == null) { + Log.e(TAG, "No watcher found on connection."); + return; + } + if (mInstance == null) { + // to keep backwards compatibility do not update the watcher when there is no + // instance available, or it will cause an NPE + Log.d(TAG, "AR HW instance not available, binding will be a no-op."); + return; + } + try { + watcher.onInstanceChanged(mInstance); + } catch (RemoteException e) { + Log.e(TAG, "Error delivering hardware interface to watcher.", e); + } + } else if (IActivityRecognitionHardwareClient.class.getCanonicalName().equals(descriptor)) { + IActivityRecognitionHardwareClient client = + IActivityRecognitionHardwareClient.Stub.asInterface(binder); + if (client == null) { + Log.e(TAG, "No client found on connection."); + return; + } + try { + client.onAvailabilityChanged(mIsSupported, mInstance); + } catch (RemoteException e) { + Log.e(TAG, "Error delivering hardware interface to client.", e); + } + } else { + Log.e(TAG, "Invalid descriptor found on connection: " + descriptor); } } } diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java index 13db771..42b8783 100644 --- a/services/core/java/com/android/server/location/GpsLocationProvider.java +++ b/services/core/java/com/android/server/location/GpsLocationProvider.java @@ -444,7 +444,8 @@ public class GpsLocationProvider implements LocationProviderInterface { checkSmsSuplInit(intent); } else if (action.equals(Intents.WAP_PUSH_RECEIVED_ACTION)) { checkWapSuplInit(intent); - } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) + || action.equals(ConnectivityManager.CONNECTIVITY_ACTION_SUPL)) { // retrieve NetworkInfo result for this UID NetworkInfo info = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); @@ -2045,6 +2046,7 @@ public class GpsLocationProvider implements LocationProviderInterface { intentFilter.addAction(ALARM_WAKEUP); intentFilter.addAction(ALARM_TIMEOUT); intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_SUPL); intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java index b89a654..c2e4349 100644 --- a/services/core/java/com/android/server/notification/ZenModeConditions.java +++ b/services/core/java/com/android/server/notification/ZenModeConditions.java @@ -104,7 +104,7 @@ public class ZenModeConditions implements ConditionProviders.Callback { public void onServiceAdded(ComponentName component) { if (DEBUG) Log.d(TAG, "onServiceAdded " + component); if (isAutomaticActive(component)) { - mHelper.setConfig(mHelper.getConfig(), "zmc.onServiceAdded"); + mHelper.setConfigAsync(mHelper.getConfig(), "zmc.onServiceAdded"); } } @@ -120,7 +120,7 @@ public class ZenModeConditions implements ConditionProviders.Callback { updated |= updateSnoozing(automaticRule); } if (updated) { - mHelper.setConfig(config, "conditionChanged"); + mHelper.setConfigAsync(config, "conditionChanged"); } } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 57d7758..461c3a2 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -315,6 +315,10 @@ public class ZenModeHelper { return setConfig(config, reason, true /*setRingerMode*/); } + public void setConfigAsync(ZenModeConfig config, String reason) { + mHandler.postSetConfig(config, reason); + } + private boolean setConfig(ZenModeConfig config, String reason, boolean setRingerMode) { if (config == null || !config.isValid()) { Log.w(TAG, "Invalid config in setConfig; " + config); @@ -743,6 +747,17 @@ public class ZenModeHelper { private final class H extends Handler { private static final int MSG_DISPATCH = 1; private static final int MSG_METRICS = 2; + private static final int MSG_SET_CONFIG = 3; + + private final class ConfigMessageData { + public final ZenModeConfig config; + public final String reason; + + ConfigMessageData(ZenModeConfig config, String reason) { + this.config = config; + this.reason = reason; + } + } private static final long METRICS_PERIOD_MS = 6 * 60 * 60 * 1000; @@ -760,6 +775,10 @@ public class ZenModeHelper { sendEmptyMessageDelayed(MSG_METRICS, METRICS_PERIOD_MS); } + private void postSetConfig(ZenModeConfig config, String reason) { + sendMessage(obtainMessage(MSG_SET_CONFIG, new ConfigMessageData(config, reason))); + } + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -769,6 +788,10 @@ public class ZenModeHelper { case MSG_METRICS: mMetrics.emit(); break; + case MSG_SET_CONFIG: + ConfigMessageData configData = (ConfigMessageData)msg.obj; + setConfig(configData.config, configData.reason); + break; } } } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index dba38a3..5f183a2 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -77,24 +77,37 @@ public final class Installer extends SystemService { public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet, int dexoptNeeded) { + return dexopt(apkPath, uid, isPublic, instructionSet, dexoptNeeded, true); + } + + public int dexopt(String apkPath, int uid, boolean isPublic, + String instructionSet, int dexoptNeeded, boolean bootComplete) { if (!isValidInstructionSet(instructionSet)) { Slog.e(TAG, "Invalid instruction set: " + instructionSet); return -1; } - return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet, dexoptNeeded); + return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet, dexoptNeeded, + bootComplete); } public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName, String instructionSet, int dexoptNeeded, boolean vmSafeMode, boolean debuggable, @Nullable String outputPath) { + return dexopt(apkPath, uid, isPublic, pkgName, instructionSet, dexoptNeeded, vmSafeMode, + debuggable, outputPath, true); + } + + public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName, + String instructionSet, int dexoptNeeded, boolean vmSafeMode, + boolean debuggable, @Nullable String outputPath, boolean bootComplete) { if (!isValidInstructionSet(instructionSet)) { Slog.e(TAG, "Invalid instruction set: " + instructionSet); return -1; } return mInstaller.dexopt(apkPath, uid, isPublic, pkgName, instructionSet, dexoptNeeded, vmSafeMode, - debuggable, outputPath); + debuggable, outputPath, bootComplete); } public int idmap(String targetApkPath, String overlayApkPath, String cachePath, diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 7024ec8..b692def 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -71,7 +71,7 @@ final class PackageDexOptimizer { * {@link PackageManagerService#mInstallLock}. */ int performDexOpt(PackageParser.Package pkg, String[] instructionSets, - boolean forceDex, boolean defer, boolean inclDependencies) { + boolean forceDex, boolean defer, boolean inclDependencies, boolean bootComplete) { ArraySet<String> done; if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) { done = new ArraySet<String>(); @@ -86,7 +86,7 @@ final class PackageDexOptimizer { mDexoptWakeLock.acquire(); } try { - return performDexOptLI(pkg, instructionSets, forceDex, defer, done); + return performDexOptLI(pkg, instructionSets, forceDex, defer, bootComplete, done); } finally { if (useLock) { mDexoptWakeLock.release(); @@ -96,18 +96,19 @@ final class PackageDexOptimizer { } private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets, - boolean forceDex, boolean defer, ArraySet<String> done) { + boolean forceDex, boolean defer, boolean bootComplete, ArraySet<String> done) { final String[] instructionSets = targetInstructionSets != null ? targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo); if (done != null) { done.add(pkg.packageName); if (pkg.usesLibraries != null) { - performDexOptLibsLI(pkg.usesLibraries, instructionSets, forceDex, defer, done); + performDexOptLibsLI(pkg.usesLibraries, instructionSets, forceDex, defer, + bootComplete, done); } if (pkg.usesOptionalLibraries != null) { performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSets, forceDex, defer, - done); + bootComplete, done); } } @@ -174,11 +175,11 @@ final class PackageDexOptimizer { Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg=" + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable - + " oatDir = " + oatDir); + + " oatDir = " + oatDir + " bootComplete=" + bootComplete); final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); final int ret = mPackageManagerService.mInstaller.dexopt(path, sharedGid, !pkg.isForwardLocked(), pkg.packageName, dexCodeInstructionSet, - dexoptNeeded, vmSafeMode, debuggable, oatDir); + dexoptNeeded, vmSafeMode, debuggable, oatDir, bootComplete); // Dex2oat might fail due to compiler / verifier errors. We soldier on // regardless, and attempt to interpret the app as a safety net. @@ -217,8 +218,7 @@ final class PackageDexOptimizer { @Nullable private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet) throws IOException { - if ((pkg.isSystemApp() && !pkg.isUpdatedSystemApp()) || pkg.isForwardLocked() - || pkg.applicationInfo.isExternalAsec()) { + if (!pkg.canHaveOatDir()) { return null; } File codePath = new File(pkg.codePath); @@ -236,12 +236,12 @@ final class PackageDexOptimizer { } private void performDexOptLibsLI(ArrayList<String> libs, String[] instructionSets, - boolean forceDex, boolean defer, ArraySet<String> done) { + boolean forceDex, boolean defer, boolean bootComplete, ArraySet<String> done) { for (String libName : libs) { PackageParser.Package libPkg = mPackageManagerService.findSharedNonSystemLibrary( libName); if (libPkg != null && !done.contains(libName)) { - performDexOptLI(libPkg, instructionSets, forceDex, defer, done); + performDexOptLI(libPkg, instructionSets, forceDex, defer, bootComplete, done); } } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8fcade2..15065c1 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2072,7 +2072,7 @@ public class PackageManagerService extends IPackageManager.Stub { int dexoptNeeded = DexFile.getDexOptNeeded(lib, null, dexCodeInstructionSet, false); if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) { alreadyDexOpted.add(lib); - mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded); + mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded, false); } } catch (FileNotFoundException e) { Slog.w(TAG, "Library not found: " + lib); @@ -2124,7 +2124,7 @@ public class PackageManagerService extends IPackageManager.Stub { try { int dexoptNeeded = DexFile.getDexOptNeeded(path, null, dexCodeInstructionSet, false); if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) { - mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded); + mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded, false); } } catch (FileNotFoundException e) { Slog.w(TAG, "Jar not found: " + path); @@ -2362,7 +2362,8 @@ public class PackageManagerService extends IPackageManager.Stub { // the rest of the commands above) because there's precious little we // can do about it. A settings error is reported, though. adjustCpuAbisForSharedUserLPw(setting.packages, null /* scanned package */, - false /* force dexopt */, false /* defer dexopt */); + false /* force dexopt */, false /* defer dexopt */, + false /* boot complete */); } // Now that we know all the packages we are keeping, @@ -6448,7 +6449,8 @@ public class PackageManagerService extends IPackageManager.Stub { PackageParser.Package p = pkg; synchronized (mInstallLock) { mPackageDexOptimizer.performDexOpt(p, null /* instruction sets */, - false /* force dex */, false /* defer */, true /* include dependencies */); + false /* force dex */, false /* defer */, true /* include dependencies */, + false /* boot complete */); } } @@ -6491,7 +6493,8 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mInstallLock) { final String[] instructionSets = new String[] { targetInstructionSet }; int result = mPackageDexOptimizer.performDexOpt(p, instructionSets, - false /* forceDex */, false /* defer */, true /* inclDependencies */); + false /* forceDex */, false /* defer */, true /* inclDependencies */, + true /* boot complete */); return result == PackageDexOptimizer.DEX_OPT_PERFORMED; } } finally { @@ -6538,7 +6541,8 @@ public class PackageManagerService extends IPackageManager.Stub { final String[] instructionSets = new String[] { getPrimaryInstructionSet(pkg.applicationInfo) }; final int res = mPackageDexOptimizer.performDexOpt(pkg, instructionSets, - true /*forceDex*/, false /* defer */, true /* inclDependencies */); + true /*forceDex*/, false /* defer */, true /* inclDependencies */, + true /* boot complete */); if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) { throw new IllegalStateException("Failed to dexopt: " + res); } @@ -7354,12 +7358,13 @@ public class PackageManagerService extends IPackageManager.Stub { // we can avoid redundant dexopts, and also to make sure we've got the // code and package path correct. adjustCpuAbisForSharedUserLPw(pkgSetting.sharedUser.packages, - pkg, forceDex, (scanFlags & SCAN_DEFER_DEX) != 0); + pkg, forceDex, (scanFlags & SCAN_DEFER_DEX) != 0, true /* boot complete */); } if ((scanFlags & SCAN_NO_DEX) == 0) { int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instruction sets */, - forceDex, (scanFlags & SCAN_DEFER_DEX) != 0, false /* inclDependencies */); + forceDex, (scanFlags & SCAN_DEFER_DEX) != 0, false /* inclDependencies */, + (scanFlags & SCAN_BOOTING) == 0); if (result == PackageDexOptimizer.DEX_OPT_FAILED) { throw new PackageManagerException(INSTALL_FAILED_DEXOPT, "scanPackageLI"); } @@ -7435,7 +7440,8 @@ public class PackageManagerService extends IPackageManager.Stub { PackageParser.Package clientPkg = clientLibPkgs.get(i); int result = mPackageDexOptimizer.performDexOpt(clientPkg, null /* instruction sets */, forceDex, - (scanFlags & SCAN_DEFER_DEX) != 0, false); + (scanFlags & SCAN_DEFER_DEX) != 0, false, + (scanFlags & SCAN_BOOTING) == 0); if (result == PackageDexOptimizer.DEX_OPT_FAILED) { throw new PackageManagerException(INSTALL_FAILED_DEXOPT, "scanPackageLI failed to dexopt clientLibPkgs"); @@ -8052,7 +8058,8 @@ public class PackageManagerService extends IPackageManager.Stub { * adds unnecessary complexity. */ private void adjustCpuAbisForSharedUserLPw(Set<PackageSetting> packagesForUser, - PackageParser.Package scannedPackage, boolean forceDexOpt, boolean deferDexOpt) { + PackageParser.Package scannedPackage, boolean forceDexOpt, boolean deferDexOpt, + boolean bootComplete) { String requiredInstructionSet = null; if (scannedPackage != null && scannedPackage.applicationInfo.primaryCpuAbi != null) { requiredInstructionSet = VMRuntime.getInstructionSet( @@ -8116,7 +8123,8 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.i(TAG, "Adjusting ABI for : " + ps.name + " to " + adjustedAbi); int result = mPackageDexOptimizer.performDexOpt(ps.pkg, - null /* instruction sets */, forceDexOpt, deferDexOpt, true); + null /* instruction sets */, forceDexOpt, deferDexOpt, true, + bootComplete); if (result == PackageDexOptimizer.DEX_OPT_FAILED) { ps.primaryCpuAbiString = null; ps.pkg.applicationInfo.primaryCpuAbi = null; @@ -13050,7 +13058,8 @@ public class PackageManagerService extends IPackageManager.Stub { // Run dexopt before old package gets removed, to minimize time when app is unavailable int result = mPackageDexOptimizer .performDexOpt(pkg, null /* instruction sets */, false /* forceDex */, - false /* defer */, false /* inclDependencies */); + false /* defer */, false /* inclDependencies */, + true /* boot complete */); if (result == PackageDexOptimizer.DEX_OPT_FAILED) { res.setError(INSTALL_FAILED_DEXOPT, "Dexopt failed for " + pkg.codePath); return; @@ -14322,7 +14331,21 @@ public class PackageManagerService extends IPackageManager.Stub { // TODO(multiArch): Extend getSizeInfo to look at *all* instruction sets, not // just the primary. String[] dexCodeInstructionSets = getDexCodeInstructionSets(getAppDexInstructionSets(ps)); - int res = mInstaller.getSizeInfo(p.volumeUuid, packageName, userHandle, p.baseCodePath, + + String apkPath; + File packageDir = new File(p.codePath); + + if (packageDir.isDirectory() && p.canHaveOatDir()) { + apkPath = packageDir.getAbsolutePath(); + // If libDirRoot is inside a package dir, set it to null to avoid it being counted twice + if (libDirRoot != null && libDirRoot.startsWith(apkPath)) { + libDirRoot = null; + } + } else { + apkPath = p.baseCodePath; + } + + int res = mInstaller.getSizeInfo(p.volumeUuid, packageName, userHandle, apkPath, libDirRoot, publicSrcDir, asecPath, dexCodeInstructionSets, pStats); if (res < 0) { return false; @@ -16365,14 +16388,28 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private void loadPrivatePackages(VolumeInfo vol) { + private void loadPrivatePackages(final VolumeInfo vol) { + mHandler.post(new Runnable() { + @Override + public void run() { + loadPrivatePackagesInner(vol); + } + }); + } + + private void loadPrivatePackagesInner(VolumeInfo vol) { final ArrayList<ApplicationInfo> loaded = new ArrayList<>(); final int parseFlags = mDefParseFlags | PackageParser.PARSE_EXTERNAL_STORAGE; - synchronized (mInstallLock) { + + final VersionInfo ver; + final List<PackageSetting> packages; synchronized (mPackages) { - final VersionInfo ver = mSettings.findOrCreateVersion(vol.fsUuid); - final List<PackageSetting> packages = mSettings.getVolumePackagesLPr(vol.fsUuid); - for (PackageSetting ps : packages) { + ver = mSettings.findOrCreateVersion(vol.fsUuid); + packages = mSettings.getVolumePackagesLPr(vol.fsUuid); + } + + for (PackageSetting ps : packages) { + synchronized (mInstallLock) { final PackageParser.Package pkg; try { pkg = scanPackageLI(ps.codePath, parseFlags, SCAN_INITIAL, 0L, null); @@ -16385,7 +16422,9 @@ public class PackageManagerService extends IPackageManager.Stub { deleteCodeCacheDirsLI(ps.volumeUuid, ps.name); } } + } + synchronized (mPackages) { int updateFlags = UPDATE_PERMISSIONS_ALL; if (ver.sdkVersion != mSdkVersion) { logCriticalInfo(Log.INFO, "Platform changed from " + ver.sdkVersion + " to " @@ -16399,13 +16438,21 @@ public class PackageManagerService extends IPackageManager.Stub { mSettings.writeLPr(); } - } if (DEBUG_INSTALL) Slog.d(TAG, "Loaded packages " + loaded); sendResourcesChangedBroadcast(true, false, loaded, null); } - private void unloadPrivatePackages(VolumeInfo vol) { + private void unloadPrivatePackages(final VolumeInfo vol) { + mHandler.post(new Runnable() { + @Override + public void run() { + unloadPrivatePackagesInner(vol); + } + }); + } + + private void unloadPrivatePackagesInner(VolumeInfo vol) { final ArrayList<ApplicationInfo> unloaded = new ArrayList<>(); synchronized (mInstallLock) { synchronized (mPackages) { @@ -16480,7 +16527,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (userDir.exists()) continue; try { - UserManagerService.prepareUserDirectory(userDir); + UserManagerService.prepareUserDirectory(mContext, volumeUuid, user.id); UserManagerService.enforceSerialNumber(userDir, user.serialNumber); } catch (IOException e) { Log.wtf(TAG, "Failed to create user directory on " + volumeUuid, e); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index f514e71..56b9a57 100755 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -41,10 +41,13 @@ import android.os.Binder; import android.os.Build; import android.os.Environment; import android.os.FileUtils; +import android.os.IBinder; import android.os.Handler; import android.os.Message; import android.os.PatternMatcher; import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 0607525..06c3682 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1285,7 +1285,7 @@ public class UserManagerService extends IUserManager.Stub { try { final File userDir = Environment.getDataUserDirectory(volumeUuid, userId); - prepareUserDirectory(userDir); + prepareUserDirectory(mContext, volumeUuid, userId); enforceSerialNumber(userDir, userInfo.serialNumber); } catch (IOException e) { Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e); @@ -1493,6 +1493,8 @@ public class UserManagerService extends IUserManager.Stub { } private void removeUserStateLocked(final int userHandle) { + mContext.getSystemService(StorageManager.class) + .deleteUserKey(userHandle); // Cleanup package manager settings mPm.cleanUpUserLILPw(this, userHandle); @@ -1899,16 +1901,10 @@ public class UserManagerService extends IUserManager.Stub { * Create new {@code /data/user/[id]} directory and sets default * permissions. */ - public static void prepareUserDirectory(File file) throws IOException { - if (!file.exists()) { - if (!file.mkdir()) { - throw new IOException("Failed to create " + file); - } - } - if (FileUtils.setPermissions(file.getAbsolutePath(), 0771, Process.SYSTEM_UID, - Process.SYSTEM_UID) != 0) { - throw new IOException("Failed to prepare " + file); - } + public static void prepareUserDirectory(Context context, String volumeUuid, int userId) { + final StorageManager storage = context.getSystemService(StorageManager.class); + final File userDir = Environment.getDataUserDirectory(volumeUuid, userId); + storage.createNewUserDir(userId, userDir); } /** diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index ca586e8..cbd821f 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -67,6 +67,7 @@ import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.PowerManager; +import android.os.PowerManagerInternal; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -130,6 +131,7 @@ import com.android.internal.util.gesture.EdgeGesturePosition; import com.android.internal.util.gesture.EdgeServiceConstants; import com.android.internal.view.RotationPolicy; import com.android.internal.widget.PointerLocationView; +import com.android.server.GestureLauncherService; import com.android.server.LocalServices; import com.android.server.policy.keyguard.KeyguardServiceDelegate; import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener; @@ -138,7 +140,6 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.lang.reflect.Constructor; @@ -320,6 +321,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { PowerManager mPowerManager; ActivityManagerInternal mActivityManagerInternal; DreamManagerInternal mDreamManagerInternal; + PowerManagerInternal mPowerManagerInternal; IStatusBarService mStatusBarService; boolean mPreloadedRecentApps; final Object mServiceAquireLock = new Object(); @@ -946,6 +948,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { } class MyOrientationListener extends WindowOrientationListener { + private final Runnable mUpdateRotationRunnable = new Runnable() { + @Override + public void run() { + // send interaction hint to improve redraw performance + mPowerManagerInternal.powerHint(PowerManagerInternal.POWER_HINT_INTERACTION, 0); + updateRotation(false); + } + }; + MyOrientationListener(Context context, Handler handler) { super(context, handler); } @@ -953,7 +964,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public void onProposedRotationChanged(int rotation) { if (localLOGV) Slog.v(TAG, "onProposedRotationChanged, rotation=" + rotation); - updateRotation(false); + mHandler.post(mUpdateRotationRunnable); } } MyOrientationListener mOrientationListener; @@ -1169,10 +1180,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + GestureLauncherService gestureService = LocalServices.getService( + GestureLauncherService.class); + boolean gesturedServiceIntercepted = false; + if (gestureService != null) { + gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive); + } + // If the power key has still not yet been handled, then detect short // press, long press, or multi press and decide what to do. mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered - || mScreenshotChordVolumeUpKeyTriggered; + || mScreenshotChordVolumeUpKeyTriggered || gesturedServiceIntercepted; if (!mPowerKeyHandled) { if (interactive) { // When interactive, we're already awake. @@ -1601,6 +1619,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class); + mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); // Init display burn-in protection @@ -1792,6 +1811,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } @Override + public void onFling(int duration) { + if (mPowerManagerInternal != null) { + mPowerManagerInternal.powerHint( + PowerManagerInternal.POWER_HINT_INTERACTION, duration); + } + } + @Override public void onDebug() { // no-op } @@ -4975,6 +5001,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (attrs.type == TYPE_STATUS_BAR) { if ((attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) { mForceStatusBarFromKeyguard = true; + mShowingLockscreen = true; } if ((attrs.privateFlags & PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT) != 0) { mForceStatusBarTransparent = true; @@ -4995,9 +5022,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { mForceStatusBar = true; } } - if ((attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) { - mShowingLockscreen = true; - } if (attrs.type == TYPE_DREAM) { // If the lockscreen was showing when the dream started then wait // for the dream to draw before hiding the lockscreen. @@ -5021,7 +5045,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mAppsToBeHidden.isEmpty()) { if (dismissKeyguard && !mKeyguardSecure) { mAppsThatDismissKeyguard.add(appToken); - } else { + } else if (win.isDrawnLw() || win.hasAppShownWindows()) { mWinShowWhenLocked = win; mHideLockScreen = true; mForceStatusBarFromKeyguard = false; @@ -5055,7 +5079,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWinDismissingKeyguard = win; mSecureDismissingKeyguard = mKeyguardSecure; mForceStatusBarFromKeyguard = mShowingLockscreen && mKeyguardSecure; - } else if (mAppsToBeHidden.isEmpty() && showWhenLocked) { + } else if (mAppsToBeHidden.isEmpty() && showWhenLocked + && (win.isDrawnLw() || win.hasAppShownWindows())) { if (DEBUG_LAYOUT) Slog.v(TAG, "Setting mHideLockScreen to true by win " + win); mHideLockScreen = true; @@ -6861,6 +6886,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mKeyguardDelegate.bindService(mContext); mKeyguardDelegate.onBootCompleted(); } + mSystemGestures.systemReady(); } /** {@inheritDoc} */ @@ -6887,6 +6913,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } startedWakingUp(); screenTurningOn(null); + screenTurnedOn(); } ProgressDialog mBootMsgDialog = null; @@ -7837,5 +7864,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mBurnInProtectionHelper != null) { mBurnInProtectionHelper.dump(prefix, pw); } + if (mKeyguardDelegate != null) { + mKeyguardDelegate.dump(prefix, pw); + } } } diff --git a/services/core/java/com/android/server/policy/StatusBarController.java b/services/core/java/com/android/server/policy/StatusBarController.java index d1b50da..b1ae922 100644 --- a/services/core/java/com/android/server/policy/StatusBarController.java +++ b/services/core/java/com/android/server/policy/StatusBarController.java @@ -72,7 +72,9 @@ public class StatusBarController extends BarController { if (statusbar != null) { long startTime = calculateStatusBarTransitionStartTime(openAnimation, closeAnimation); - statusbar.appTransitionStarting(startTime, TRANSITION_DURATION); + long duration = closeAnimation != null || openAnimation != null + ? TRANSITION_DURATION : 0; + statusbar.appTransitionStarting(startTime, duration); } } catch (RemoteException e) { Slog.e(mTag, "RemoteException when app transition is starting", e); diff --git a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java index 31fc653..5e694a5 100644 --- a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java +++ b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java @@ -17,9 +17,14 @@ package com.android.server.policy; import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; import android.util.Slog; +import android.view.GestureDetector; import android.view.MotionEvent; import android.view.WindowManagerPolicy.PointerEventListener; +import android.widget.OverScroller; /* * Listens for system-wide input gestures, firing callbacks when detected. @@ -31,6 +36,7 @@ public class SystemGesturesPointerEventListener implements PointerEventListener private static final long SWIPE_TIMEOUT_MS = 500; private static final int MAX_TRACKED_POINTERS = 32; // max per input system private static final int UNTRACKED_POINTER = -1; + private static final int MAX_FLING_TIME_MILLIS = 5000; private static final int SWIPE_NONE = 0; private static final int SWIPE_FROM_TOP = 1; @@ -38,6 +44,7 @@ public class SystemGesturesPointerEventListener implements PointerEventListener private static final int SWIPE_FROM_RIGHT = 3; private static final int SWIPE_FROM_LEFT = 4; + private final Context mContext; private final int mSwipeStartThreshold; private final int mSwipeDistanceThreshold; private final Callbacks mCallbacks; @@ -46,13 +53,18 @@ public class SystemGesturesPointerEventListener implements PointerEventListener private final float[] mDownY = new float[MAX_TRACKED_POINTERS]; private final long[] mDownTime = new long[MAX_TRACKED_POINTERS]; + private GestureDetector mGestureDetector; + private OverScroller mOverscroller; + int screenHeight; int screenWidth; private int mDownPointers; private boolean mSwipeFireable; private boolean mDebugFireable; + private long mLastFlingTime; public SystemGesturesPointerEventListener(Context context, Callbacks callbacks) { + mContext = context; mCallbacks = checkNull("callbacks", callbacks); mSwipeStartThreshold = checkNull("context", context).getResources() .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); @@ -68,8 +80,17 @@ public class SystemGesturesPointerEventListener implements PointerEventListener return arg; } + public void systemReady() { + Handler h = new Handler(Looper.myLooper()); + mGestureDetector = new GestureDetector(mContext, new FlingGestureDetector(), h); + mOverscroller = new OverScroller(mContext); + } + @Override public void onPointerEvent(MotionEvent event) { + if (mGestureDetector != null) { + mGestureDetector.onTouchEvent(event); + } switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mSwipeFireable = true; @@ -199,10 +220,40 @@ public class SystemGesturesPointerEventListener implements PointerEventListener return SWIPE_NONE; } + private final class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener { + @Override + public boolean onSingleTapUp(MotionEvent e) { + if (!mOverscroller.isFinished()) { + mOverscroller.forceFinished(true); + } + return true; + } + @Override + public boolean onFling(MotionEvent down, MotionEvent up, + float velocityX, float velocityY) { + mOverscroller.computeScrollOffset(); + long now = SystemClock.uptimeMillis(); + + if (mLastFlingTime != 0 && now > mLastFlingTime + MAX_FLING_TIME_MILLIS) { + mOverscroller.forceFinished(true); + } + mOverscroller.fling(0, 0, (int)velocityX, (int)velocityY, + Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); + int duration = mOverscroller.getDuration(); + if (duration > MAX_FLING_TIME_MILLIS) { + duration = MAX_FLING_TIME_MILLIS; + } + mLastFlingTime = now; + mCallbacks.onFling(duration); + return true; + } + } + interface Callbacks { void onSwipeFromTop(); void onSwipeFromBottom(); void onSwipeFromRight(); + void onFling(int durationMs); void onDown(); void onUpOrCancel(); void onSwipeFromLeft(); diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/policy/WindowOrientationListener.java index c71b48f..9916223 100644 --- a/services/core/java/com/android/server/policy/WindowOrientationListener.java +++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java @@ -24,10 +24,12 @@ import android.hardware.SensorManager; import android.os.Handler; import android.os.SystemClock; import android.os.SystemProperties; +import android.text.TextUtils; import android.util.Slog; import java.io.PrintWriter; import java.util.Arrays; +import java.util.List; /** * A special helper class used by the WindowManager @@ -52,8 +54,9 @@ public abstract class WindowOrientationListener { private SensorManager mSensorManager; private boolean mEnabled; private int mRate; + private String mSensorType; private Sensor mSensor; - private SensorEventListenerImpl mSensorEventListener; + private OrientationJudge mOrientationJudge; private int mCurrentRotation = -1; private final Object mLock = new Object(); @@ -67,7 +70,7 @@ public abstract class WindowOrientationListener { public WindowOrientationListener(Context context, Handler handler) { this(context, handler, SensorManager.SENSOR_DELAY_UI); } - + /** * Creates a new WindowOrientationListener. * @@ -84,11 +87,31 @@ public abstract class WindowOrientationListener { mHandler = handler; mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); mRate = rate; - mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR - ? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER); - if (mSensor != null) { - // Create listener only if sensors do exist - mSensorEventListener = new SensorEventListenerImpl(context); + + mSensorType = context.getResources().getString( + com.android.internal.R.string.config_orientationSensorType); + if (!TextUtils.isEmpty(mSensorType)) { + List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL); + final int N = sensors.size(); + for (int i = 0; i < N; i++) { + Sensor sensor = sensors.get(i); + if (mSensorType.equals(sensor.getStringType())) { + mSensor = sensor; + break; + } + } + if (mSensor != null) { + mOrientationJudge = new OrientationSensorJudge(); + } + } + + if (mOrientationJudge == null) { + mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR + ? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER); + if (mSensor != null) { + // Create listener only if sensors do exist + mOrientationJudge = new AccelSensorJudge(context); + } } } @@ -106,8 +129,8 @@ public abstract class WindowOrientationListener { if (LOG) { Slog.d(TAG, "WindowOrientationListener enabled"); } - mSensorEventListener.resetLocked(); - mSensorManager.registerListener(mSensorEventListener, mSensor, mRate, mHandler); + mOrientationJudge.resetLocked(); + mSensorManager.registerListener(mOrientationJudge, mSensor, mRate, mHandler); mEnabled = true; } } @@ -126,7 +149,7 @@ public abstract class WindowOrientationListener { if (LOG) { Slog.d(TAG, "WindowOrientationListener disabled"); } - mSensorManager.unregisterListener(mSensorEventListener); + mSensorManager.unregisterListener(mOrientationJudge); mEnabled = false; } } @@ -134,8 +157,8 @@ public abstract class WindowOrientationListener { public void onTouchStart() { synchronized (mLock) { - if (mSensorEventListener != null) { - mSensorEventListener.onTouchStartLocked(); + if (mOrientationJudge != null) { + mOrientationJudge.onTouchStartLocked(); } } } @@ -144,8 +167,8 @@ public abstract class WindowOrientationListener { long whenElapsedNanos = SystemClock.elapsedRealtimeNanos(); synchronized (mLock) { - if (mSensorEventListener != null) { - mSensorEventListener.onTouchEndLocked(whenElapsedNanos); + if (mOrientationJudge != null) { + mOrientationJudge.onTouchEndLocked(whenElapsedNanos); } } } @@ -172,7 +195,7 @@ public abstract class WindowOrientationListener { public int getProposedRotation() { synchronized (mLock) { if (mEnabled) { - return mSensorEventListener.getProposedRotationLocked(); + return mOrientationJudge.getProposedRotationLocked(); } return -1; } @@ -194,6 +217,8 @@ public abstract class WindowOrientationListener { * It is called each time the orientation determination transitions from being * uncertain to being certain again, even if it is the same orientation as before. * + * This should only be called on the Handler thread. + * * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants. * @see android.view.Surface */ @@ -205,15 +230,77 @@ public abstract class WindowOrientationListener { prefix += " "; pw.println(prefix + "mEnabled=" + mEnabled); pw.println(prefix + "mCurrentRotation=" + mCurrentRotation); + pw.println(prefix + "mSensorType=" + mSensorType); pw.println(prefix + "mSensor=" + mSensor); pw.println(prefix + "mRate=" + mRate); - if (mSensorEventListener != null) { - mSensorEventListener.dumpLocked(pw, prefix); + if (mOrientationJudge != null) { + mOrientationJudge.dumpLocked(pw, prefix); } } } + abstract class OrientationJudge implements SensorEventListener { + // Number of nanoseconds per millisecond. + protected static final long NANOS_PER_MS = 1000000; + + // Number of milliseconds per nano second. + protected static final float MILLIS_PER_NANO = 0.000001f; + + // The minimum amount of time that must have elapsed since the screen was last touched + // before the proposed rotation can change. + protected static final long PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS = + 500 * NANOS_PER_MS; + + /** + * Gets the proposed rotation. + * + * This method only returns a rotation if the orientation listener is certain + * of its proposal. If the rotation is indeterminate, returns -1. + * + * Should only be called when holding WindowOrientationListener lock. + * + * @return The proposed rotation, or -1 if unknown. + */ + public abstract int getProposedRotationLocked(); + + /** + * Notifies the orientation judge that the screen is being touched. + * + * Should only be called when holding WindowOrientationListener lock. + */ + public abstract void onTouchStartLocked(); + + /** + * Notifies the orientation judge that the screen is no longer being touched. + * + * Should only be called when holding WindowOrientationListener lock. + * + * @param whenElapsedNanos Given in the elapsed realtime nanos time base. + */ + public abstract void onTouchEndLocked(long whenElapsedNanos); + + /** + * Resets the state of the judge. + * + * Should only be called when holding WindowOrientationListener lock. + */ + public abstract void resetLocked(); + + /** + * Dumps internal state of the orientation judge. + * + * Should only be called when holding WindowOrientationListener lock. + */ + public abstract void dumpLocked(PrintWriter pw, String prefix); + + @Override + public abstract void onAccuracyChanged(Sensor sensor, int accuracy); + + @Override + public abstract void onSensorChanged(SensorEvent event); + } + /** * This class filters the raw accelerometer data and tries to detect actual changes in * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters, @@ -252,13 +339,10 @@ public abstract class WindowOrientationListener { * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for * signal processing background. */ - final class SensorEventListenerImpl implements SensorEventListener { + final class AccelSensorJudge extends OrientationJudge { // We work with all angles in degrees in this class. private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI); - // Number of nanoseconds per millisecond. - private static final long NANOS_PER_MS = 1000000; - // Indices into SensorEvent.values for the accelerometer sensor. private static final int ACCELEROMETER_DATA_X = 0; private static final int ACCELEROMETER_DATA_Y = 1; @@ -286,11 +370,6 @@ public abstract class WindowOrientationListener { private static final long PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS = 500 * NANOS_PER_MS; - // The minimum amount of time that must have elapsed since the screen was last touched - // before the proposed rotation can change. - private static final long PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS = - 500 * NANOS_PER_MS; - // If the tilt angle remains greater than the specified angle for a minimum of // the specified time, then the device is deemed to be lying flat // (just chillin' on a table). @@ -434,7 +513,7 @@ public abstract class WindowOrientationListener { private long[] mTiltHistoryTimestampNanos = new long[TILT_HISTORY_SIZE]; private int mTiltHistoryIndex; - public SensorEventListenerImpl(Context context) { + public AccelSensorJudge(Context context) { // Load tilt tolerance configuration. int[] tiltTolerance = context.getResources().getIntArray( com.android.internal.R.array.config_autoRotationTiltTolerance); @@ -455,11 +534,15 @@ public abstract class WindowOrientationListener { } } + @Override public int getProposedRotationLocked() { return mProposedRotation; } + @Override public void dumpLocked(PrintWriter pw, String prefix) { + pw.println(prefix + "AccelSensorJudge"); + prefix += " "; pw.println(prefix + "mProposedRotation=" + mProposedRotation); pw.println(prefix + "mPredictedRotation=" + mPredictedRotation); pw.println(prefix + "mLastFilteredX=" + mLastFilteredX); @@ -689,6 +772,33 @@ public abstract class WindowOrientationListener { } } + @Override + public void onTouchStartLocked() { + mTouched = true; + } + + @Override + public void onTouchEndLocked(long whenElapsedNanos) { + mTouched = false; + mTouchEndedTimestampNanos = whenElapsedNanos; + } + + @Override + public void resetLocked() { + mLastFilteredTimestampNanos = Long.MIN_VALUE; + mProposedRotation = -1; + mFlatTimestampNanos = Long.MIN_VALUE; + mFlat = false; + mSwingTimestampNanos = Long.MIN_VALUE; + mSwinging = false; + mAccelerationTimestampNanos = Long.MIN_VALUE; + mAccelerating = false; + mOverhead = false; + clearPredictedRotationLocked(); + clearTiltHistoryLocked(); + } + + /** * Returns true if the tilt angle is acceptable for a given predicted rotation. */ @@ -787,20 +897,6 @@ public abstract class WindowOrientationListener { return true; } - private void resetLocked() { - mLastFilteredTimestampNanos = Long.MIN_VALUE; - mProposedRotation = -1; - mFlatTimestampNanos = Long.MIN_VALUE; - mFlat = false; - mSwingTimestampNanos = Long.MIN_VALUE; - mSwinging = false; - mAccelerationTimestampNanos = Long.MIN_VALUE; - mAccelerating = false; - mOverhead = false; - clearPredictedRotationLocked(); - clearTiltHistoryLocked(); - } - private void clearPredictedRotationLocked() { mPredictedRotation = -1; mPredictedRotationTimestampNanos = Long.MIN_VALUE; @@ -869,14 +965,147 @@ public abstract class WindowOrientationListener { private float remainingMS(long now, long until) { return now >= until ? 0 : (until - now) * 0.000001f; } + } - private void onTouchStartLocked() { - mTouched = true; + final class OrientationSensorJudge extends OrientationJudge { + private boolean mTouching; + private long mTouchEndedTimestampNanos = Long.MIN_VALUE; + private int mProposedRotation = -1; + private int mDesiredRotation = -1; + private boolean mRotationEvaluationScheduled; + + @Override + public int getProposedRotationLocked() { + return mProposedRotation; } - private void onTouchEndLocked(long whenElapsedNanos) { - mTouched = false; + @Override + public void onTouchStartLocked() { + mTouching = true; + } + + @Override + public void onTouchEndLocked(long whenElapsedNanos) { + mTouching = false; mTouchEndedTimestampNanos = whenElapsedNanos; + if (mDesiredRotation != mProposedRotation) { + final long now = SystemClock.elapsedRealtimeNanos(); + scheduleRotationEvaluationIfNecessaryLocked(now); + } + } + + + @Override + public void onSensorChanged(SensorEvent event) { + int newRotation; + synchronized (mLock) { + mDesiredRotation = (int) event.values[0]; + newRotation = evaluateRotationChangeLocked(); + } + if (newRotation >=0) { + onProposedRotationChanged(newRotation); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { } + + @Override + public void dumpLocked(PrintWriter pw, String prefix) { + pw.println(prefix + "OrientationSensorJudge"); + prefix += " "; + pw.println(prefix + "mDesiredRotation=" + mDesiredRotation); + pw.println(prefix + "mProposedRotation=" + mProposedRotation); + pw.println(prefix + "mTouching=" + mTouching); + pw.println(prefix + "mTouchEndedTimestampNanos=" + mTouchEndedTimestampNanos); } + + @Override + public void resetLocked() { + mProposedRotation = -1; + mDesiredRotation = -1; + mTouching = false; + mTouchEndedTimestampNanos = Long.MIN_VALUE; + unscheduleRotationEvaluationLocked(); + } + + public int evaluateRotationChangeLocked() { + unscheduleRotationEvaluationLocked(); + if (mDesiredRotation == mProposedRotation) { + return -1; + } + final long now = SystemClock.elapsedRealtimeNanos(); + if (isDesiredRotationAcceptableLocked(now)) { + mProposedRotation = mDesiredRotation; + return mProposedRotation; + } else { + scheduleRotationEvaluationIfNecessaryLocked(now); + } + return -1; + } + + private boolean isDesiredRotationAcceptableLocked(long now) { + if (mTouching) { + return false; + } + if (now < mTouchEndedTimestampNanos + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS) { + return false; + } + return true; + } + + private void scheduleRotationEvaluationIfNecessaryLocked(long now) { + if (mRotationEvaluationScheduled || mDesiredRotation == mProposedRotation) { + if (LOG) { + Slog.d(TAG, "scheduleRotationEvaluationLocked: " + + "ignoring, an evaluation is already scheduled or is unnecessary."); + } + return; + } + if (mTouching) { + if (LOG) { + Slog.d(TAG, "scheduleRotationEvaluationLocked: " + + "ignoring, user is still touching the screen."); + } + return; + } + long timeOfNextPossibleRotationNanos = + mTouchEndedTimestampNanos + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS; + if (now >= timeOfNextPossibleRotationNanos) { + if (LOG) { + Slog.d(TAG, "scheduleRotationEvaluationLocked: " + + "ignoring, already past the next possible time of rotation."); + } + return; + } + // Use a delay instead of an absolute time since handlers are in uptime millis and we + // use elapsed realtime. + final long delayMs = + (long) Math.ceil((timeOfNextPossibleRotationNanos - now) * MILLIS_PER_NANO); + mHandler.postDelayed(mRotationEvaluator, delayMs); + mRotationEvaluationScheduled = true; + } + + private void unscheduleRotationEvaluationLocked() { + if (!mRotationEvaluationScheduled) { + return; + } + mHandler.removeCallbacks(mRotationEvaluator); + mRotationEvaluationScheduled = false; + } + + private Runnable mRotationEvaluator = new Runnable() { + @Override + public void run() { + int newRotation; + synchronized (mLock) { + mRotationEvaluationScheduled = false; + newRotation = evaluateRotationChangeLocked(); + } + if (newRotation >= 0) { + onProposedRotationChanged(newRotation); + } + } + }; } } diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index 5d52307..7ae3c79 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -23,6 +23,8 @@ import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardService; +import java.io.PrintWriter; + /** * A local class that keeps a cache of keyguard state that can be restored in the event * keyguard crashes. It currently also allows runtime-selectable @@ -32,6 +34,14 @@ public class KeyguardServiceDelegate { private static final String TAG = "KeyguardServiceDelegate"; private static final boolean DEBUG = true; + private static final int SCREEN_STATE_OFF = 0; + private static final int SCREEN_STATE_TURNING_ON = 1; + private static final int SCREEN_STATE_ON = 2; + + private static final int INTERACTIVE_STATE_SLEEP = 0; + private static final int INTERACTIVE_STATE_AWAKE = 1; + private static final int INTERACTIVE_STATE_GOING_TO_SLEEP = 2; + protected KeyguardServiceWrapper mKeyguardService; private final Context mContext; private final View mScrim; // shown if keyguard crashes @@ -61,6 +71,8 @@ public class KeyguardServiceDelegate { public int offReason; public int currentUser; public boolean bootCompleted; + public int screenState; + public int interactiveState; }; public interface DrawnListener { @@ -144,10 +156,17 @@ public class KeyguardServiceDelegate { // If the system is ready, it means keyguard crashed and restarted. mKeyguardService.onSystemReady(); // This is used to hide the scrim once keyguard displays. - mKeyguardService.onStartedWakingUp(); - mKeyguardService.onScreenTurningOn( - new KeyguardShowDelegate(mDrawnListenerWhenConnect)); - mKeyguardService.onScreenTurnedOn(); + if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE) { + mKeyguardService.onStartedWakingUp(); + } + if (mKeyguardState.screenState == SCREEN_STATE_ON + || mKeyguardState.screenState == SCREEN_STATE_TURNING_ON) { + mKeyguardService.onScreenTurningOn( + new KeyguardShowDelegate(mDrawnListenerWhenConnect)); + } + if (mKeyguardState.screenState == SCREEN_STATE_ON) { + mKeyguardService.onScreenTurnedOn(); + } mDrawnListenerWhenConnect = null; } if (mKeyguardState.bootCompleted) { @@ -231,6 +250,7 @@ public class KeyguardServiceDelegate { if (DEBUG) Log.v(TAG, "onStartedWakingUp()"); mKeyguardService.onStartedWakingUp(); } + mKeyguardState.interactiveState = INTERACTIVE_STATE_AWAKE; } public void onScreenTurnedOff() { @@ -238,6 +258,7 @@ public class KeyguardServiceDelegate { if (DEBUG) Log.v(TAG, "onScreenTurnedOff()"); mKeyguardService.onScreenTurnedOff(); } + mKeyguardState.screenState = SCREEN_STATE_OFF; } public void onScreenTurningOn(final DrawnListener drawnListener) { @@ -252,6 +273,7 @@ public class KeyguardServiceDelegate { mDrawnListenerWhenConnect = drawnListener; showScrim(); } + mKeyguardState.screenState = SCREEN_STATE_TURNING_ON; } public void onScreenTurnedOn() { @@ -259,6 +281,7 @@ public class KeyguardServiceDelegate { if (DEBUG) Log.v(TAG, "onScreenTurnedOn()"); mKeyguardService.onScreenTurnedOn(); } + mKeyguardState.screenState = SCREEN_STATE_ON; } public void onStartedGoingToSleep(int why) { @@ -266,12 +289,14 @@ public class KeyguardServiceDelegate { mKeyguardService.onStartedGoingToSleep(why); } mKeyguardState.offReason = why; + mKeyguardState.interactiveState = INTERACTIVE_STATE_GOING_TO_SLEEP; } public void onFinishedGoingToSleep(int why) { if (mKeyguardService != null) { mKeyguardService.onFinishedGoingToSleep(why); } + mKeyguardState.interactiveState = INTERACTIVE_STATE_SLEEP; } public void setKeyguardEnabled(boolean enabled) { @@ -370,4 +395,26 @@ public class KeyguardServiceDelegate { mKeyguardService.onActivityDrawn(); } } + + public void dump(String prefix, PrintWriter pw) { + pw.println(prefix + TAG); + prefix += " "; + pw.println(prefix + "showing=" + mKeyguardState.showing); + pw.println(prefix + "showingAndNotOccluded=" + mKeyguardState.showingAndNotOccluded); + pw.println(prefix + "inputRestricted=" + mKeyguardState.inputRestricted); + pw.println(prefix + "occluded=" + mKeyguardState.occluded); + pw.println(prefix + "secure=" + mKeyguardState.secure); + pw.println(prefix + "dreaming=" + mKeyguardState.dreaming); + pw.println(prefix + "systemIsReady=" + mKeyguardState.systemIsReady); + pw.println(prefix + "deviceHasKeyguard=" + mKeyguardState.deviceHasKeyguard); + pw.println(prefix + "enabled=" + mKeyguardState.enabled); + pw.println(prefix + "offReason=" + mKeyguardState.offReason); + pw.println(prefix + "currentUser=" + mKeyguardState.currentUser); + pw.println(prefix + "bootCompleted=" + mKeyguardState.bootCompleted); + pw.println(prefix + "screenState=" + mKeyguardState.screenState); + pw.println(prefix + "interactiveState=" + mKeyguardState.interactiveState); + if (mKeyguardService != null) { + mKeyguardService.dump(prefix, pw); + } + } } diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java index cd88b66..429b188 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java @@ -27,6 +27,8 @@ import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardService; import com.android.internal.policy.IKeyguardStateCallback; +import java.io.PrintWriter; + /** * A wrapper class for KeyguardService. It implements IKeyguardService to ensure the interface * remains consistent. @@ -239,4 +241,8 @@ public class KeyguardServiceWrapper implements IKeyguardService { public boolean isInputRestricted() { return mKeyguardStateMonitor.isInputRestricted(); } + + public void dump(String prefix, PrintWriter pw) { + mKeyguardStateMonitor.dump(prefix, pw); + } }
\ No newline at end of file diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java index f1f9c50..30cff03 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java @@ -25,6 +25,8 @@ import com.android.internal.policy.IKeyguardService; import com.android.internal.policy.IKeyguardStateCallback; import com.android.internal.widget.LockPatternUtils; +import java.io.PrintWriter; + /** * Maintains a cached copy of Keyguard's state. * @hide @@ -90,4 +92,13 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { public void onInputRestrictedStateChanged(boolean inputRestricted) { mInputRestricted = inputRestricted; } + + public void dump(String prefix, PrintWriter pw) { + pw.println(prefix + TAG); + prefix += " "; + pw.println(prefix + "mIsShowing=" + mIsShowing); + pw.println(prefix + "mSimSecure=" + mSimSecure); + pw.println(prefix + "mInputRestricted=" + mInputRestricted); + pw.println(prefix + "mCurrentUserId=" + mCurrentUserId); + } }
\ No newline at end of file diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 3682950..dd7401d 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -84,6 +84,7 @@ import java.util.Arrays; import libcore.util.Objects; +import static android.os.PowerManagerInternal.POWER_HINT_INTERACTION; import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING; @@ -160,7 +161,6 @@ public final class PowerManagerService extends SystemService private static final int SCREEN_BRIGHTNESS_BOOST_TIMEOUT = 5 * 1000; // 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; // Power features defined in hardware/libhardware/include/hardware/power.h. diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index 130815e..25d646d 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -28,4 +28,5 @@ public interface StatusBarManagerInternal { void showScreenPinningRequest(); void showAssistDisclosure(); void startAssist(Bundle args); + void onCameraLaunchGestureDetected(int source); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 2a817ea..e9ace29 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -176,6 +176,16 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } } } + + @Override + public void onCameraLaunchGestureDetected(int source) { + if (mBar != null) { + try { + mBar.onCameraLaunchGestureDetected(source); + } catch (RemoteException e) { + } + } + } }; // ================================================================================ diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 15da829..6c70fe9 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -19,6 +19,7 @@ package com.android.server.trust; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker; import com.android.server.SystemService; import org.xmlpull.v1.XmlPullParser; @@ -59,6 +60,7 @@ import android.util.AttributeSet; import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; +import android.util.SparseIntArray; import android.util.Xml; import android.view.IWindowManager; import android.view.WindowManagerGlobal; @@ -96,16 +98,15 @@ public class TrustManagerService extends SystemService { private static final int MSG_UNREGISTER_LISTENER = 2; private static final int MSG_DISPATCH_UNLOCK_ATTEMPT = 3; private static final int MSG_ENABLED_AGENTS_CHANGED = 4; - private static final int MSG_REQUIRE_CREDENTIAL_ENTRY = 5; private static final int MSG_KEYGUARD_SHOWING_CHANGED = 6; private static final int MSG_START_USER = 7; private static final int MSG_CLEANUP_USER = 8; private static final int MSG_SWITCH_USER = 9; - private final ArraySet<AgentInfo> mActiveAgents = new ArraySet<AgentInfo>(); - private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<ITrustListener>(); + private final ArraySet<AgentInfo> mActiveAgents = new ArraySet<>(); + private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<>(); private final Receiver mReceiver = new Receiver(); - private final SparseBooleanArray mUserHasAuthenticated = new SparseBooleanArray(); + /* package */ final TrustArchive mArchive = new TrustArchive(); private final Context mContext; private final LockPatternUtils mLockPatternUtils; @@ -118,9 +119,6 @@ public class TrustManagerService extends SystemService { @GuardedBy("mDeviceLockedForUser") private final SparseBooleanArray mDeviceLockedForUser = new SparseBooleanArray(); - @GuardedBy("mUserHasAuthenticatedSinceBoot") - private final SparseBooleanArray mUserHasAuthenticatedSinceBoot = new SparseBooleanArray(); - private boolean mTrustAgentsCanRun = false; private int mCurrentUser = UserHandle.USER_OWNER; @@ -146,6 +144,7 @@ public class TrustManagerService extends SystemService { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true); mReceiver.register(mContext); + mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker); } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { mTrustAgentsCanRun = true; refreshAgentList(UserHandle.USER_ALL); @@ -230,7 +229,7 @@ public class TrustManagerService extends SystemService { if (!userInfo.supportsSwitchTo()) continue; if (!mActivityManager.isUserRunning(userInfo.id)) continue; if (!lockPatternUtils.isSecure(userInfo.id)) continue; - if (!getUserHasAuthenticated(userInfo.id)) continue; + if (!mStrongAuthTracker.isTrustAllowedForUser(userInfo.id)) continue; DevicePolicyManager dpm = lockPatternUtils.getDevicePolicyManager(); int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, userInfo.id); final boolean disableTrustAgents = @@ -509,7 +508,7 @@ public class TrustManagerService extends SystemService { // Agent dispatch and aggregation private boolean aggregateIsTrusted(int userId) { - if (!getUserHasAuthenticated(userId)) { + if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) { return false; } for (int i = 0; i < mActiveAgents.size(); i++) { @@ -524,7 +523,7 @@ public class TrustManagerService extends SystemService { } private boolean aggregateIsTrustManaged(int userId) { - if (!getUserHasAuthenticated(userId)) { + if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) { return false; } for (int i = 0; i < mActiveAgents.size(); i++) { @@ -545,54 +544,6 @@ public class TrustManagerService extends SystemService { info.agent.onUnlockAttempt(successful); } } - - if (successful) { - updateUserHasAuthenticated(userId); - } - } - - private void updateUserHasAuthenticated(int userId) { - boolean changed = setUserHasAuthenticated(userId); - if (changed) { - refreshAgentList(userId); - } - } - - private boolean getUserHasAuthenticated(int userId) { - return mUserHasAuthenticated.get(userId); - } - - /** - * @return whether the value has changed - */ - private boolean setUserHasAuthenticated(int userId) { - if (!mUserHasAuthenticated.get(userId)) { - mUserHasAuthenticated.put(userId, true); - synchronized (mUserHasAuthenticatedSinceBoot) { - mUserHasAuthenticatedSinceBoot.put(userId, true); - } - return true; - } - return false; - } - - private void clearUserHasAuthenticated(int userId) { - if (userId == UserHandle.USER_ALL) { - mUserHasAuthenticated.clear(); - } else { - mUserHasAuthenticated.put(userId, false); - } - } - - private boolean getUserHasAuthenticatedSinceBoot(int userId) { - synchronized (mUserHasAuthenticatedSinceBoot) { - return mUserHasAuthenticatedSinceBoot.get(userId); - } - } - - private void requireCredentialEntry(int userId) { - clearUserHasAuthenticated(userId); - refreshAgentList(userId); } // Listeners @@ -681,17 +632,6 @@ public class TrustManagerService extends SystemService { } @Override - public void reportRequireCredentialEntry(int userId) throws RemoteException { - enforceReportPermission(); - if (userId == UserHandle.USER_ALL || userId >= UserHandle.USER_OWNER) { - mHandler.obtainMessage(MSG_REQUIRE_CREDENTIAL_ENTRY, userId, 0).sendToTarget(); - } else { - throw new IllegalArgumentException( - "userId must be an explicit user id or USER_ALL"); - } - } - - @Override public void reportKeyguardShowingChanged() throws RemoteException { enforceReportPermission(); // coalesce refresh messages. @@ -734,18 +674,6 @@ public class TrustManagerService extends SystemService { } } - @Override - public boolean hasUserAuthenticatedSinceBoot(int userId) throws RemoteException { - mContext.enforceCallingOrSelfPermission( - Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE, null); - long token = Binder.clearCallingIdentity(); - try { - return getUserHasAuthenticatedSinceBoot(userId); - } finally { - Binder.restoreCallingIdentity(token); - } - } - private void enforceReportPermission() { mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE, "reporting trust events"); @@ -794,9 +722,8 @@ public class TrustManagerService extends SystemService { fout.print(": trusted=" + dumpBool(aggregateIsTrusted(user.id))); fout.print(", trustManaged=" + dumpBool(aggregateIsTrustManaged(user.id))); fout.print(", deviceLocked=" + dumpBool(isDeviceLockedInner(user.id))); - fout.print(", hasAuthenticated=" + dumpBool(getUserHasAuthenticated(user.id))); - fout.print(", hasAuthenticatedSinceBoot=" - + dumpBool(getUserHasAuthenticatedSinceBoot(user.id))); + fout.print(", strongAuthRequired=" + dumpHex( + mStrongAuthTracker.getStrongAuthForUser(user.id))); fout.println(); fout.println(" Enabled agents:"); boolean duplicateSimpleNames = false; @@ -831,6 +758,10 @@ public class TrustManagerService extends SystemService { private String dumpBool(boolean b) { return b ? "1" : "0"; } + + private String dumpHex(int i) { + return "0x" + Integer.toHexString(i); + } }; private int resolveProfileParent(int userId) { @@ -864,9 +795,6 @@ public class TrustManagerService extends SystemService { // This is also called when the security mode of a user changes. refreshDeviceLockedForUser(UserHandle.USER_ALL); break; - case MSG_REQUIRE_CREDENTIAL_ENTRY: - requireCredentialEntry(msg.arg1); - break; case MSG_KEYGUARD_SHOWING_CHANGED: refreshDeviceLockedForUser(mCurrentUser); break; @@ -900,6 +828,13 @@ public class TrustManagerService extends SystemService { } }; + private final StrongAuthTracker mStrongAuthTracker = new StrongAuthTracker() { + @Override + public void onStrongAuthRequiredChanged(int userId) { + refreshAgentList(userId); + } + }; + private class Receiver extends BroadcastReceiver { @Override @@ -908,8 +843,6 @@ public class TrustManagerService extends SystemService { if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) { refreshAgentList(getSendingUserId()); updateDevicePolicyFeatures(); - } else if (Intent.ACTION_USER_PRESENT.equals(action)) { - updateUserHasAuthenticated(getSendingUserId()); } else if (Intent.ACTION_USER_ADDED.equals(action)) { int userId = getUserId(intent); if (userId > 0) { @@ -918,7 +851,6 @@ public class TrustManagerService extends SystemService { } else if (Intent.ACTION_USER_REMOVED.equals(action)) { int userId = getUserId(intent); if (userId > 0) { - mUserHasAuthenticated.delete(userId); synchronized (mUserIsTrusted) { mUserIsTrusted.delete(userId); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 867292a..4e4af62 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -10469,8 +10469,8 @@ public class WindowManagerService extends IWindowManager.Stub ": removed=" + win.mRemoved + " visible=" + win.isVisibleLw() + " mHasSurface=" + win.mHasSurface + " drawState=" + win.mWinAnimator.mDrawState); - if (win.mRemoved || !win.mHasSurface) { - // Window has been removed; no draw will now happen, so stop waiting. + if (win.mRemoved || !win.mHasSurface || !win.mPolicyVisibility) { + // Window has been removed or hidden; no draw will now happen, so stop waiting. if (DEBUG_SCREEN_ON) Slog.w(TAG, "Aborted waiting for drawn: " + win); mWaitingForDrawn.remove(win); } else if (win.hasDrawnLw()) { @@ -12023,12 +12023,18 @@ public class WindowManagerService extends IWindowManager.Stub final WindowList windows = getDefaultWindowListLocked(); for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) { final WindowState win = windows.get(winNdx); + final boolean isForceHiding = mPolicy.isForceHiding(win.mAttrs); if (win.isVisibleLw() - && (win.mAppToken != null || mPolicy.isForceHiding(win.mAttrs))) { + && (win.mAppToken != null || isForceHiding)) { win.mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING; // Force add to mResizingWindows. win.mLastContentInsets.set(-1, -1, -1, -1); mWaitingForDrawn.add(win); + + // No need to wait for the windows below Keyguard. + if (isForceHiding) { + break; + } } } requestTraversalLocked(); diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index 0f03039..69488b1 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -4,9 +4,16 @@ LOCAL_REL_DIR := core/jni LOCAL_CFLAGS += -Wall -Werror -Wno-unused-parameter +ifneq ($(ENABLE_CPUSETS),) +ifneq ($(ENABLE_SCHED_BOOST),) +LOCAL_CFLAGS += -DUSE_SCHED_BOOST +endif +endif + LOCAL_SRC_FILES += \ $(LOCAL_REL_DIR)/com_android_server_AlarmManagerService.cpp \ $(LOCAL_REL_DIR)/com_android_server_am_BatteryStatsService.cpp \ + $(LOCAL_REL_DIR)/com_android_server_am_ActivityManagerService.cpp \ $(LOCAL_REL_DIR)/com_android_server_AssetAtlasService.cpp \ $(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \ $(LOCAL_REL_DIR)/com_android_server_ConsumerIrService.cpp \ diff --git a/services/core/jni/com_android_server_am_ActivityManagerService.cpp b/services/core/jni/com_android_server_am_ActivityManagerService.cpp new file mode 100644 index 0000000..52217b9 --- /dev/null +++ b/services/core/jni/com_android_server_am_ActivityManagerService.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "ActivityManagerService" +//#define LOG_NDEBUG 0 + +#include <android_runtime/AndroidRuntime.h> +#include <jni.h> + +#include <ScopedLocalRef.h> +#include <ScopedPrimitiveArray.h> + +#include <cutils/log.h> +#include <utils/misc.h> +#include <utils/Log.h> + +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <semaphore.h> +#include <stddef.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +namespace android +{ + + // migrate from foreground to foreground_boost + static jint migrateToBoost(JNIEnv *env, jobject _this) + { +#ifdef USE_SCHED_BOOST + // File descriptors open to /dev/cpuset/../tasks, setup by initialize, or -1 on error + FILE* fg_cpuset_file = NULL; + int boost_cpuset_fd = 0; + if (!access("/dev/cpuset/tasks", F_OK)) { + fg_cpuset_file = fopen("/dev/cpuset/foreground/tasks", "r+"); + if (ferror(fg_cpuset_file)) { + return 0; + } + boost_cpuset_fd = open("/dev/cpuset/foreground/boost/tasks", O_WRONLY); + if (boost_cpuset_fd < 0) { + fclose(fg_cpuset_file); + return 0; + } + + } + if (!fg_cpuset_file || !boost_cpuset_fd) { + fclose(fg_cpuset_file); + close(boost_cpuset_fd); + return 0; + } + char buf[17]; + while (fgets(buf, 16, fg_cpuset_file)) { + int i = 0; + for (; i < 16; i++) { + if (buf[i] == '\n') { + buf[i] = 0; + break; + } + } + if (write(boost_cpuset_fd, buf, i) < 0) { + // ignore error + } + if (feof(fg_cpuset_file)) + break; + } + fclose(fg_cpuset_file); + close(boost_cpuset_fd); +#endif + return 0; + } + + // migrate from foreground_boost to foreground + static jint migrateFromBoost(JNIEnv *env, jobject _this) + { +#ifdef USE_SCHED_BOOST + // File descriptors open to /dev/cpuset/../tasks, setup by initialize, or -1 on error + int fg_cpuset_fd = 0; + FILE* boost_cpuset_file = NULL; + if (!access("/dev/cpuset/tasks", F_OK)) { + boost_cpuset_file = fopen("/dev/cpuset/foreground/boost/tasks", "r+"); + if (ferror(boost_cpuset_file)) { + return 0; + } + fg_cpuset_fd = open("/dev/cpuset/foreground/tasks", O_WRONLY); + if (fg_cpuset_fd < 0) { + fclose(boost_cpuset_file); + return 0; + } + + } + if (!boost_cpuset_file || !fg_cpuset_fd) { + fclose(boost_cpuset_file); + close(fg_cpuset_fd); + return 0; + } + char buf[17]; + char *curBuf = buf; + while (fgets(buf, 16, boost_cpuset_file)) { + //ALOGE("Appending FD %s to fg", buf); + int i = 0; + for (; i < 16; i++) { + if (buf[i] == '\n') { + buf[i] = 0; + break; + } + } + if (write(fg_cpuset_fd, buf, i) < 0) { + //ALOGE("Appending FD %s to fg ERROR", buf); + // handle error? + } + if (feof(boost_cpuset_file)) + break; + } + + close(fg_cpuset_fd); + fclose(boost_cpuset_file); + +#endif + return 0; + + } + + + static JNINativeMethod method_table[] = { + { "nativeMigrateToBoost", "()I", (void*)migrateToBoost }, + { "nativeMigrateFromBoost", "()I", (void*)migrateFromBoost }, + }; + + int register_android_server_ActivityManagerService(JNIEnv *env) + { + return jniRegisterNativeMethods(env, "com/android/server/am/ActivityManagerService", + method_table, NELEM(method_table)); + } + +} diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 67872da..1f3fde6 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -20,6 +20,7 @@ #include "utils/misc.h" namespace android { +int register_android_server_ActivityManagerService(JNIEnv* env); int register_android_server_AlarmManagerService(JNIEnv* env); int register_android_server_AssetAtlasService(JNIEnv* env); int register_android_server_BatteryStatsService(JNIEnv* env); @@ -57,6 +58,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) } ALOG_ASSERT(env, "Could not retrieve the env!"); + register_android_server_ActivityManagerService(env); register_android_server_PowerManagerService(env); register_android_server_SerialService(env); register_android_server_InputApplicationHandle(env); @@ -80,5 +82,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_PersistentDataBlockService(env); register_android_server_Watchdog(env); + return JNI_VERSION_1_4; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 3b8d3c4..3901082 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -21,6 +21,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; import static android.content.pm.PackageManager.GET_UNINSTALLED_PACKAGES; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.END_TAG; import static org.xmlpull.v1.XmlPullParser.TEXT; @@ -44,6 +45,7 @@ import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.IDevicePolicyManager; import android.app.admin.SystemUpdatePolicy; import android.app.backup.IBackupManager; +import android.app.trust.TrustManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -2958,7 +2960,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0; if (requireEntry) { - utils.requireCredentialEntry(UserHandle.USER_ALL); + utils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW, + UserHandle.USER_ALL); } synchronized (this) { int newOwner = requireEntry ? callingUid : -1; @@ -3090,7 +3093,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN, 0); // Ensure the device is locked - new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL); + new LockPatternUtils(mContext).requireStrongAuth( + STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW, UserHandle.USER_ALL); getWindowManager().lockNow(null); } catch (RemoteException e) { } finally { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 30cd759..8cfbac6 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -903,6 +903,11 @@ public final class SystemServer { if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_VOICE_RECOGNIZERS)) { mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS); } + + if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) { + Slog.i(TAG, "Gesture Launcher Service"); + mSystemServiceManager.startService(GestureLauncherService.class); + } } try { diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java index 9ee9cf4..e0d2ac1 100644 --- a/services/net/java/android/net/dhcp/DhcpClient.java +++ b/services/net/java/android/net/dhcp/DhcpClient.java @@ -244,8 +244,9 @@ public class DhcpClient extends BaseDhcpStateMachine { private PendingIntent createStateMachineCommandIntent(final String cmdName, final int cmd) { String action = DhcpClient.class.getName() + "." + mIfaceName + "." + cmdName; - Intent intent = new Intent(action, null) - .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + Intent intent = new Intent(action, null).addFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | + Intent.FLAG_RECEIVER_FOREGROUND); // TODO: The intent's package covers the whole of the system server, so it's pretty generic. // Consider adding some sort of token as well. intent.setPackage(mContext.getPackageName()); diff --git a/services/net/java/android/net/util/IpUtils.java b/services/net/java/android/net/util/IpUtils.java new file mode 100644 index 0000000..e037c40 --- /dev/null +++ b/services/net/java/android/net/util/IpUtils.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.util; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ShortBuffer; + +import static android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.IPPROTO_UDP; + +/** + * @hide + */ +public class IpUtils { + /** + * Converts a signed short value to an unsigned int value. Needed + * because Java does not have unsigned types. + */ + private static int intAbs(short v) { + return v & 0xFFFF; + } + + /** + * Performs an IP checksum (used in IP header and across UDP + * payload) on the specified portion of a ByteBuffer. The seed + * allows the checksum to commence with a specified value. + */ + private static int checksum(ByteBuffer buf, int seed, int start, int end) { + int sum = seed; + final int bufPosition = buf.position(); + + // set position of original ByteBuffer, so that the ShortBuffer + // will be correctly initialized + buf.position(start); + ShortBuffer shortBuf = buf.asShortBuffer(); + + // re-set ByteBuffer position + buf.position(bufPosition); + + final int numShorts = (end - start) / 2; + for (int i = 0; i < numShorts; i++) { + sum += intAbs(shortBuf.get(i)); + } + start += numShorts * 2; + + // see if a singleton byte remains + if (end != start) { + short b = buf.get(start); + + // make it unsigned + if (b < 0) { + b += 256; + } + + sum += b * 256; + } + + sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF); + sum = ((sum + ((sum >> 16) & 0xFFFF)) & 0xFFFF); + int negated = ~sum; + return intAbs((short) negated); + } + + private static int pseudoChecksumIPv4( + ByteBuffer buf, int headerOffset, int protocol, int transportLen) { + int partial = protocol + transportLen; + partial += intAbs(buf.getShort(headerOffset + 12)); + partial += intAbs(buf.getShort(headerOffset + 14)); + partial += intAbs(buf.getShort(headerOffset + 16)); + partial += intAbs(buf.getShort(headerOffset + 18)); + return partial; + } + + private static int pseudoChecksumIPv6( + ByteBuffer buf, int headerOffset, int protocol, int transportLen) { + int partial = protocol + transportLen; + for (int offset = 8; offset < 40; offset += 2) { + partial += intAbs(buf.getShort(headerOffset + offset)); + } + return partial; + } + + private static byte ipversion(ByteBuffer buf, int headerOffset) { + return (byte) ((buf.get(headerOffset) & (byte) 0xf0) >> 4); + } + + public static short ipChecksum(ByteBuffer buf, int headerOffset) { + byte ihl = (byte) (buf.get(headerOffset) & 0x0f); + return (short) checksum(buf, 0, headerOffset, headerOffset + ihl * 4); + } + + private static short transportChecksum(ByteBuffer buf, int protocol, + int ipOffset, int transportOffset, int transportLen) { + if (transportLen < 0) { + throw new IllegalArgumentException("Transport length < 0: " + transportLen); + } + int sum; + byte ver = ipversion(buf, ipOffset); + if (ver == 4) { + sum = pseudoChecksumIPv4(buf, ipOffset, protocol, transportLen); + } else if (ver == 6) { + sum = pseudoChecksumIPv6(buf, ipOffset, protocol, transportLen); + } else { + throw new UnsupportedOperationException("Checksum must be IPv4 or IPv6"); + } + + sum = checksum(buf, sum, transportOffset, transportOffset + transportLen); + if (protocol == IPPROTO_UDP && sum == 0) { + sum = (short) 0xffff; + } + return (short) sum; + } + + public static short udpChecksum(ByteBuffer buf, int ipOffset, int transportOffset) { + int transportLen = intAbs(buf.getShort(transportOffset + 4)); + return transportChecksum(buf, IPPROTO_UDP, ipOffset, transportOffset, transportLen); + } + + public static short tcpChecksum(ByteBuffer buf, int ipOffset, int transportOffset, + int transportLen) { + return transportChecksum(buf, IPPROTO_TCP, ipOffset, transportOffset, transportLen); + } + + public static String addressAndPortToString(InetAddress address, int port) { + return String.format( + (address instanceof Inet6Address) ? "[%s]:%d" : "%s:%d", + address.getHostAddress(), port); + } + + public static boolean isValidUdpOrTcpPort(int port) { + return port > 0 && port < 65536; + } +} diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 919293a..8bb20a6 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -39,6 +39,7 @@ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> + <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" /> <application> <uses-library android:name="android.test.runner" /> diff --git a/services/tests/servicestests/src/android/net/IpUtilsTest.java b/services/tests/servicestests/src/android/net/IpUtilsTest.java new file mode 100644 index 0000000..c2d1608 --- /dev/null +++ b/services/tests/servicestests/src/android/net/IpUtilsTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.util; + +import android.net.util.IpUtils; +import android.test.suitebuilder.annotation.SmallTest; + +import java.nio.ByteBuffer; + +import junit.framework.TestCase; + + +public class IpUtilsTest extends TestCase { + + private static final int IPV4_HEADER_LENGTH = 20; + private static final int IPV6_HEADER_LENGTH = 40; + private static final int TCP_HEADER_LENGTH = 20; + private static final int UDP_HEADER_LENGTH = 8; + private static final int IP_CHECKSUM_OFFSET = 10; + private static final int TCP_CHECKSUM_OFFSET = 16; + private static final int UDP_CHECKSUM_OFFSET = 6; + + private int getUnsignedByte(ByteBuffer buf, int offset) { + return buf.get(offset) & 0xff; + } + + private int getChecksum(ByteBuffer buf, int offset) { + return getUnsignedByte(buf, offset) * 256 + getUnsignedByte(buf, offset + 1); + } + + private void assertChecksumEquals(int expected, short actual) { + assertEquals(Integer.toHexString(expected), Integer.toHexString(actual & 0xffff)); + } + + // Generate test packets using Python code like this:: + // + // from scapy import all as scapy + // + // def JavaPacketDefinition(bytes): + // out = " ByteBuffer packet = ByteBuffer.wrap(new byte[] {\n " + // for i in xrange(len(bytes)): + // out += "(byte) 0x%02x" % ord(bytes[i]) + // if i < len(bytes) - 1: + // if i % 4 == 3: + // out += ",\n " + // else: + // out += ", " + // out += "\n });" + // return out + // + // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2") / + // scapy.UDP(sport=12345, dport=7) / + // "hello") + // print JavaPacketDefinition(str(packet)) + + @SmallTest + public void testIpv6TcpChecksum() throws Exception { + // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80) / + // scapy.TCP(sport=12345, dport=7, + // seq=1692871236, ack=128376451, flags=16, + // window=32768) / + // "hello, world") + ByteBuffer packet = ByteBuffer.wrap(new byte[] { + (byte) 0x68, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x20, (byte) 0x06, (byte) 0x40, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, + (byte) 0x30, (byte) 0x39, (byte) 0x00, (byte) 0x07, + (byte) 0x64, (byte) 0xe7, (byte) 0x2a, (byte) 0x44, + (byte) 0x07, (byte) 0xa6, (byte) 0xde, (byte) 0x83, + (byte) 0x50, (byte) 0x10, (byte) 0x80, (byte) 0x00, + (byte) 0xee, (byte) 0x71, (byte) 0x00, (byte) 0x00, + (byte) 0x68, (byte) 0x65, (byte) 0x6c, (byte) 0x6c, + (byte) 0x6f, (byte) 0x2c, (byte) 0x20, (byte) 0x77, + (byte) 0x6f, (byte) 0x72, (byte) 0x6c, (byte) 0x64 + }); + + // Check that a valid packet has checksum 0. + int transportLen = packet.limit() - IPV6_HEADER_LENGTH; + assertEquals(0, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen)); + + // Check that we can calculate the checksum from scratch. + int sumOffset = IPV6_HEADER_LENGTH + TCP_CHECKSUM_OFFSET; + int sum = getUnsignedByte(packet, sumOffset) * 256 + getUnsignedByte(packet, sumOffset + 1); + assertEquals(0xee71, sum); + + packet.put(sumOffset, (byte) 0); + packet.put(sumOffset + 1, (byte) 0); + assertChecksumEquals(sum, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen)); + + // Check that writing the checksum back into the packet results in a valid packet. + packet.putShort( + sumOffset, + IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen)); + assertEquals(0, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen)); + } + + @SmallTest + public void testIpv4UdpChecksum() { + // packet = (scapy.IP(src="192.0.2.1", dst="192.0.2.2", tos=0x40) / + // scapy.UDP(sport=32012, dport=4500) / + // "\xff") + ByteBuffer packet = ByteBuffer.wrap(new byte[] { + (byte) 0x45, (byte) 0x40, (byte) 0x00, (byte) 0x1d, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, + (byte) 0x40, (byte) 0x11, (byte) 0xf6, (byte) 0x8b, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x02, + (byte) 0x7d, (byte) 0x0c, (byte) 0x11, (byte) 0x94, + (byte) 0x00, (byte) 0x09, (byte) 0xee, (byte) 0x36, + (byte) 0xff + }); + + // Check that a valid packet has IP checksum 0 and UDP checksum 0xffff (0 is not a valid + // UDP checksum, so the udpChecksum rewrites 0 to 0xffff). + assertEquals(0, IpUtils.ipChecksum(packet, 0)); + assertEquals((short) 0xffff, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH)); + + // Check that we can calculate the checksums from scratch. + final int ipSumOffset = IP_CHECKSUM_OFFSET; + final int ipSum = getChecksum(packet, ipSumOffset); + assertEquals(0xf68b, ipSum); + + packet.put(ipSumOffset, (byte) 0); + packet.put(ipSumOffset + 1, (byte) 0); + assertChecksumEquals(ipSum, IpUtils.ipChecksum(packet, 0)); + + final int udpSumOffset = IPV4_HEADER_LENGTH + UDP_CHECKSUM_OFFSET; + final int udpSum = getChecksum(packet, udpSumOffset); + assertEquals(0xee36, udpSum); + + packet.put(udpSumOffset, (byte) 0); + packet.put(udpSumOffset + 1, (byte) 0); + assertChecksumEquals(udpSum, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH)); + + // Check that writing the checksums back into the packet results in a valid packet. + packet.putShort(ipSumOffset, IpUtils.ipChecksum(packet, 0)); + packet.putShort(udpSumOffset, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH)); + assertEquals(0, IpUtils.ipChecksum(packet, 0)); + assertEquals((short) 0xffff, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java index b4c76b7..97e16da 100644 --- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java @@ -20,35 +20,9 @@ import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.getNetworkTypeName; -import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; -import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; -import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; -import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS; -import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA; -import static android.net.NetworkCapabilities.NET_CAPABILITY_IA; -import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS; -import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; -import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; -import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS; -import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL; -import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; -import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP; -import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkCapabilities.TRANSPORT_WIFI; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.isA; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; +import static android.net.NetworkCapabilities.*; + import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -58,8 +32,12 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; +import android.net.ConnectivityManager.PacketKeepalive; +import android.net.ConnectivityManager.PacketKeepaliveCallback; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; +import android.net.IpPrefix; +import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgent; @@ -74,8 +52,11 @@ import android.net.RouteInfo; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; -import android.os.Looper; import android.os.INetworkManagementService; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue; +import android.os.MessageQueue.IdleHandler; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; @@ -84,10 +65,10 @@ import android.util.LogPrinter; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkMonitor; -import org.mockito.ArgumentCaptor; - import java.net.InetAddress; -import java.util.concurrent.Future; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -99,24 +80,7 @@ import java.util.concurrent.atomic.AtomicBoolean; public class ConnectivityServiceTest extends AndroidTestCase { private static final String TAG = "ConnectivityServiceTest"; - private static final String MOBILE_IFACE = "rmnet3"; - private static final String WIFI_IFACE = "wlan6"; - - private static final RouteInfo MOBILE_ROUTE_V4 = RouteInfo.makeHostRoute(parse("10.0.0.33"), - MOBILE_IFACE); - private static final RouteInfo MOBILE_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::33"), - MOBILE_IFACE); - - private static final RouteInfo WIFI_ROUTE_V4 = RouteInfo.makeHostRoute(parse("192.168.0.66"), - parse("192.168.0.1"), - WIFI_IFACE); - private static final RouteInfo WIFI_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::66"), - parse("fd00::"), - WIFI_IFACE); - - private INetworkManagementService mNetManager; - private INetworkStatsService mStatsService; - private INetworkPolicyManager mPolicyService; + private static final int TIMEOUT_MS = 500; private BroadcastInterceptingContext mServiceContext; private WrappedConnectivityService mService; @@ -148,14 +112,93 @@ public class ConnectivityServiceTest extends AndroidTestCase { } } + /** + * A subclass of HandlerThread that allows callers to wait for it to become idle. waitForIdle + * will return immediately if the handler is already idle. + */ + private class IdleableHandlerThread extends HandlerThread { + private IdleHandler mIdleHandler; + + public IdleableHandlerThread(String name) { + super(name); + } + + public void waitForIdle(int timeoutMs) { + final ConditionVariable cv = new ConditionVariable(); + final MessageQueue queue = getLooper().getQueue(); + + synchronized (queue) { + if (queue.isIdle()) { + return; + } + + assertNull("BUG: only one idle handler allowed", mIdleHandler); + mIdleHandler = new IdleHandler() { + public boolean queueIdle() { + cv.open(); + mIdleHandler = null; + return false; // Remove the handler. + } + }; + queue.addIdleHandler(mIdleHandler); + } + + if (!cv.block(timeoutMs)) { + fail("HandlerThread " + getName() + + " did not become idle after " + timeoutMs + " ms"); + queue.removeIdleHandler(mIdleHandler); + } + } + } + + // Tests that IdleableHandlerThread works as expected. + public void testIdleableHandlerThread() { + final int attempts = 50; // Causes the test to take about 200ms on bullhead-eng. + + // Tests that waitForIdle returns immediately if the service is already idle. + for (int i = 0; i < attempts; i++) { + mService.waitForIdle(); + } + + // Bring up a network that we can use to send messages to ConnectivityService. + ConditionVariable cv = waitForConnectivityBroadcasts(1); + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + waitFor(cv); + Network n = mWiFiNetworkAgent.getNetwork(); + assertNotNull(n); + + // Tests that calling waitForIdle waits for messages to be processed. + for (int i = 0; i < attempts; i++) { + mWiFiNetworkAgent.setSignalStrength(i); + mService.waitForIdle(); + assertEquals(i, mCm.getNetworkCapabilities(n).getSignalStrength()); + } + + // Ensure that not calling waitForIdle causes a race condition. + for (int i = 0; i < attempts; i++) { + mWiFiNetworkAgent.setSignalStrength(i); + if (i != mCm.getNetworkCapabilities(n).getSignalStrength()) { + // We hit a race condition, as expected. Pass the test. + return; + } + } + + // No race? There is a bug in this test. + fail("expected race condition at least once in " + attempts + " attempts"); + } + private class MockNetworkAgent { private final WrappedNetworkMonitor mWrappedNetworkMonitor; private final NetworkInfo mNetworkInfo; private final NetworkCapabilities mNetworkCapabilities; - private final Thread mThread; + private final IdleableHandlerThread mHandlerThread; private final ConditionVariable mDisconnected = new ConditionVariable(); private int mScore; private NetworkAgent mNetworkAgent; + private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED; + private int mStopKeepaliveError = PacketKeepalive.NO_KEEPALIVE; + private Integer mExpectedKeepaliveSlot = null; MockNetworkAgent(int transport) { final int type = transportToLegacyType(transport); @@ -173,26 +216,42 @@ public class ConnectivityServiceTest extends AndroidTestCase { default: throw new UnsupportedOperationException("unimplemented network type"); } - final ConditionVariable initComplete = new ConditionVariable(); - final ConditionVariable networkMonitorAvailable = mService.getNetworkMonitorCreatedCV(); - mThread = new Thread() { - public void run() { - Looper.prepare(); - mNetworkAgent = new NetworkAgent(Looper.myLooper(), mServiceContext, - "Mock" + typeName, mNetworkInfo, mNetworkCapabilities, - new LinkProperties(), mScore, new NetworkMisc()) { - public void unwanted() { mDisconnected.open(); } - }; - initComplete.open(); - Looper.loop(); + mHandlerThread = new IdleableHandlerThread("Mock-" + typeName); + mHandlerThread.start(); + mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext, + "Mock-" + typeName, mNetworkInfo, mNetworkCapabilities, + new LinkProperties(), mScore, new NetworkMisc()) { + @Override + public void unwanted() { mDisconnected.open(); } + + @Override + public void startPacketKeepalive(Message msg) { + int slot = msg.arg1; + if (mExpectedKeepaliveSlot != null) { + assertEquals((int) mExpectedKeepaliveSlot, slot); + } + onPacketKeepaliveEvent(slot, mStartKeepaliveError); + } + + @Override + public void stopPacketKeepalive(Message msg) { + onPacketKeepaliveEvent(msg.arg1, mStopKeepaliveError); } }; - mThread.start(); - waitFor(initComplete); - waitFor(networkMonitorAvailable); + // Waits for the NetworkAgent to be registered, which includes the creation of the + // NetworkMonitor. + mService.waitForIdle(); mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor(); } + public void waitForIdle(int timeoutMs) { + mHandlerThread.waitForIdle(timeoutMs); + } + + public void waitForIdle() { + waitForIdle(TIMEOUT_MS); + } + public void adjustScore(int change) { mScore += change; mNetworkAgent.sendNetworkScore(mScore); @@ -203,6 +262,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); } + public void setSignalStrength(int signalStrength) { + mNetworkCapabilities.setSignalStrength(signalStrength); + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + public void connectWithoutInternet() { mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); mNetworkAgent.sendNetworkInfo(mNetworkInfo); @@ -272,15 +336,46 @@ public class ConnectivityServiceTest extends AndroidTestCase { public WrappedNetworkMonitor getWrappedNetworkMonitor() { return mWrappedNetworkMonitor; } + + public void sendLinkProperties(LinkProperties lp) { + mNetworkAgent.sendLinkProperties(lp); + } + + public void setStartKeepaliveError(int error) { + mStartKeepaliveError = error; + } + + public void setStopKeepaliveError(int error) { + mStopKeepaliveError = error; + } + + public void setExpectedKeepaliveSlot(Integer slot) { + mExpectedKeepaliveSlot = slot; + } } + /** + * A NetworkFactory that allows tests to wait until any in-flight NetworkRequest add or remove + * operations have been processed. Before ConnectivityService can add or remove any requests, + * the factory must be told to expect those operations by calling expectAddRequests or + * expectRemoveRequests. + */ private static class MockNetworkFactory extends NetworkFactory { private final ConditionVariable mNetworkStartedCV = new ConditionVariable(); private final ConditionVariable mNetworkStoppedCV = new ConditionVariable(); - private final ConditionVariable mNetworkRequestedCV = new ConditionVariable(); - private final ConditionVariable mNetworkReleasedCV = new ConditionVariable(); private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false); + // Used to expect that requests be removed or added on a separate thread, without sleeping. + // Callers can call either expectAddRequests() or expectRemoveRequests() exactly once, then + // cause some other thread to add or remove requests, then call waitForRequests(). We can + // either expect requests to be added or removed, but not both, because CountDownLatch can + // only count in one direction. + private CountDownLatch mExpectations; + + // Whether we are currently expecting requests to be added or removed. Valid only if + // mExpectations is non-null. + private boolean mExpectingAdditions; + public MockNetworkFactory(Looper looper, Context context, String logTag, NetworkCapabilities filter) { super(looper, context, logTag, filter); @@ -314,28 +409,75 @@ public class ConnectivityServiceTest extends AndroidTestCase { return mNetworkStoppedCV; } - protected void needNetworkFor(NetworkRequest networkRequest, int score) { - super.needNetworkFor(networkRequest, score); - mNetworkRequestedCV.open(); + @Override + protected void handleAddRequest(NetworkRequest request, int score) { + // If we're expecting anything, we must be expecting additions. + if (mExpectations != null && !mExpectingAdditions) { + fail("Can't add requests while expecting requests to be removed"); + } + + // Add the request. + super.handleAddRequest(request, score); + + // Reduce the number of request additions we're waiting for. + if (mExpectingAdditions) { + assertTrue("Added more requests than expected", mExpectations.getCount() > 0); + mExpectations.countDown(); + } + } + + @Override + protected void handleRemoveRequest(NetworkRequest request) { + // If we're expecting anything, we must be expecting removals. + if (mExpectations != null && mExpectingAdditions) { + fail("Can't remove requests while expecting requests to be added"); + } + + // Remove the request. + super.handleRemoveRequest(request); + + // Reduce the number of request removals we're waiting for. + if (!mExpectingAdditions) { + assertTrue("Removed more requests than expected", mExpectations.getCount() > 0); + mExpectations.countDown(); + } } - protected void releaseNetworkFor(NetworkRequest networkRequest) { - super.releaseNetworkFor(networkRequest); - mNetworkReleasedCV.open(); + private void assertNoExpectations() { + if (mExpectations != null) { + fail("Can't add expectation, " + mExpectations.getCount() + " already pending"); + } + } + + // Expects that count requests will be added. + public void expectAddRequests(final int count) { + assertNoExpectations(); + mExpectingAdditions = true; + mExpectations = new CountDownLatch(count); } - public ConditionVariable getNetworkRequestedCV() { - mNetworkRequestedCV.close(); - return mNetworkRequestedCV; + // Expects that count requests will be removed. + public void expectRemoveRequests(final int count) { + assertNoExpectations(); + mExpectingAdditions = false; + mExpectations = new CountDownLatch(count); } - public ConditionVariable getNetworkReleasedCV() { - mNetworkReleasedCV.close(); - return mNetworkReleasedCV; + // Waits for the expected request additions or removals to happen within a timeout. + public void waitForRequests() throws InterruptedException { + assertNotNull("Nothing to wait for", mExpectations); + mExpectations.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); + final long count = mExpectations.getCount(); + final String msg = count + " requests still not " + + (mExpectingAdditions ? "added" : "removed") + + " after " + TIMEOUT_MS + " ms"; + assertEquals(msg, 0, count); + mExpectations = null; } - public void waitForNetworkRequests(final int count) { - waitFor(new Criteria() { public boolean get() { return count == getRequestCount(); } }); + public void waitForNetworkRequests(final int count) throws InterruptedException { + waitForRequests(); + assertEquals(count, getMyRequestCount()); } } @@ -356,7 +498,6 @@ public class ConnectivityServiceTest extends AndroidTestCase { } private class WrappedConnectivityService extends ConnectivityService { - private final ConditionVariable mNetworkMonitorCreated = new ConditionVariable(); private WrappedNetworkMonitor mLastCreatedNetworkMonitor; public WrappedConnectivityService(Context context, INetworkManagementService netManager, @@ -365,6 +506,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { } @Override + protected HandlerThread createHandlerThread() { + return new IdleableHandlerThread("WrappedConnectivityService"); + } + + @Override protected int getDefaultTcpRwnd() { // Prevent wrapped ConnectivityService from trying to write to SystemProperties. return 0; @@ -397,7 +543,6 @@ public class ConnectivityServiceTest extends AndroidTestCase { final WrappedNetworkMonitor monitor = new WrappedNetworkMonitor(context, handler, nai, defaultRequest); mLastCreatedNetworkMonitor = monitor; - mNetworkMonitorCreated.open(); return monitor; } @@ -405,10 +550,14 @@ public class ConnectivityServiceTest extends AndroidTestCase { return mLastCreatedNetworkMonitor; } - public ConditionVariable getNetworkMonitorCreatedCV() { - mNetworkMonitorCreated.close(); - return mNetworkMonitorCreated; + public void waitForIdle(int timeoutMs) { + ((IdleableHandlerThread) mHandlerThread).waitForIdle(timeoutMs); } + + public void waitForIdle() { + waitForIdle(TIMEOUT_MS); + } + } private interface Criteria { @@ -431,23 +580,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { } /** - * Wait up to 500ms for {@code conditonVariable} to open. - * Fails if 500ms goes by before {@code conditionVariable} opens. + * Wait up to TIMEOUT_MS for {@code conditionVariable} to open. + * Fails if TIMEOUT_MS goes by before {@code conditionVariable} opens. */ static private void waitFor(ConditionVariable conditionVariable) { - assertTrue(conditionVariable.block(500)); - } - - /** - * This should only be used to verify that nothing happens, in other words that no unexpected - * changes occur. It should never be used to wait for a specific positive signal to occur. - */ - private void shortSleep() { - // TODO: Instead of sleeping, instead wait for all message loops to idle. - try { - Thread.sleep(500); - } catch (InterruptedException e) { - } + assertTrue(conditionVariable.block(TIMEOUT_MS)); } @Override @@ -455,13 +592,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { super.setUp(); mServiceContext = new MockContext(getContext()); + mService = new WrappedConnectivityService(mServiceContext, + mock(INetworkManagementService.class), + mock(INetworkStatsService.class), + mock(INetworkPolicyManager.class)); - mNetManager = mock(INetworkManagementService.class); - mStatsService = mock(INetworkStatsService.class); - mPolicyService = mock(INetworkPolicyManager.class); - - mService = new WrappedConnectivityService( - mServiceContext, mNetManager, mStatsService, mPolicyService); mService.systemReady(); mCm = new ConnectivityManager(getContext(), mService); } @@ -583,11 +718,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { // Test bringing up unvalidated cellular mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); - shortSleep(); + mService.waitForIdle(); verifyActiveNetwork(TRANSPORT_WIFI); // Test cellular disconnect. mCellNetworkAgent.disconnect(); - shortSleep(); + mService.waitForIdle(); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up validated cellular mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); @@ -797,6 +932,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { LOST } + /** + * Utility NetworkCallback for testing. The caller must explicitly test for all the callbacks + * this class receives, by calling expectCallback() exactly once each time a callback is + * received. assertNoCallback may be called at any time. + */ private class TestNetworkCallback extends NetworkCallback { private final ConditionVariable mConditionVariable = new ConditionVariable(); private CallbackState mLastCallback = CallbackState.NONE; @@ -819,14 +959,15 @@ public class ConnectivityServiceTest extends AndroidTestCase { mConditionVariable.open(); } - ConditionVariable getConditionVariable() { + void expectCallback(CallbackState state) { + waitFor(mConditionVariable); + assertEquals(state, mLastCallback); mLastCallback = CallbackState.NONE; mConditionVariable.close(); - return mConditionVariable; } - CallbackState getLastCallback() { - return mLastCallback; + void assertNoCallback() { + assertEquals(CallbackState.NONE, mLastCallback); } } @@ -842,98 +983,68 @@ public class ConnectivityServiceTest extends AndroidTestCase { mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); // Test unvalidated networks - ConditionVariable cellCv = cellNetworkCallback.getConditionVariable(); - ConditionVariable wifiCv = wifiNetworkCallback.getConditionVariable(); ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); - waitFor(cellCv); - assertEquals(CallbackState.AVAILABLE, cellNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); + cellNetworkCallback.expectCallback(CallbackState.AVAILABLE); + wifiNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); waitFor(cv); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); // This should not trigger spurious onAvailable() callbacks, b/21762680. mCellNetworkAgent.adjustScore(-1); - shortSleep(); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback()); + mService.waitForIdle(); + wifiNetworkCallback.assertNoCallback(); + cellNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - waitFor(wifiCv); - assertEquals(CallbackState.AVAILABLE, wifiNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback()); + wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE); + cellNetworkCallback.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); waitFor(cv); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.disconnect(); - waitFor(wifiCv); - assertEquals(CallbackState.LOST, wifiNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback()); + wifiNetworkCallback.expectCallback(CallbackState.LOST); + cellNetworkCallback.assertNoCallback(); waitFor(cv); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.disconnect(); - waitFor(cellCv); - assertEquals(CallbackState.LOST, cellNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); + cellNetworkCallback.expectCallback(CallbackState.LOST); + wifiNetworkCallback.assertNoCallback(); waitFor(cv); // Test validated networks - - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - waitFor(cellCv); - assertEquals(CallbackState.AVAILABLE, cellNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); + cellNetworkCallback.expectCallback(CallbackState.AVAILABLE); + wifiNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); // This should not trigger spurious onAvailable() callbacks, b/21762680. mCellNetworkAgent.adjustScore(-1); - shortSleep(); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback()); + mService.waitForIdle(); + wifiNetworkCallback.assertNoCallback(); + cellNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - waitFor(wifiCv); - assertEquals(CallbackState.AVAILABLE, wifiNetworkCallback.getLastCallback()); - waitFor(cellCv); - assertEquals(CallbackState.LOSING, cellNetworkCallback.getLastCallback()); + wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE); + cellNetworkCallback.expectCallback(CallbackState.LOSING); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); mWiFiNetworkAgent.disconnect(); - waitFor(wifiCv); - assertEquals(CallbackState.LOST, wifiNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback()); + wifiNetworkCallback.expectCallback(CallbackState.LOST); + cellNetworkCallback.assertNoCallback(); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); mCellNetworkAgent.disconnect(); - waitFor(cellCv); - assertEquals(CallbackState.LOST, cellNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); + cellNetworkCallback.expectCallback(CallbackState.LOST); + wifiNetworkCallback.assertNoCallback(); } private void tryNetworkFactoryRequests(int capability) throws Exception { @@ -957,18 +1068,21 @@ public class ConnectivityServiceTest extends AndroidTestCase { mServiceContext, "testFactory", filter); testFactory.setScoreFilter(40); ConditionVariable cv = testFactory.getNetworkStartedCV(); + testFactory.expectAddRequests(1); testFactory.register(); + testFactory.waitForNetworkRequests(1); int expectedRequestCount = 1; NetworkCallback networkCallback = null; // For non-INTERNET capabilities we cannot rely on the default request being present, so // add one. if (capability != NET_CAPABILITY_INTERNET) { - testFactory.waitForNetworkRequests(1); assertFalse(testFactory.getMyStartRequested()); NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build(); networkCallback = new NetworkCallback(); + testFactory.expectAddRequests(1); mCm.requestNetwork(request, networkCallback); expectedRequestCount++; + testFactory.waitForNetworkRequests(expectedRequestCount); } waitFor(cv); assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); @@ -981,13 +1095,20 @@ public class ConnectivityServiceTest extends AndroidTestCase { // unvalidated penalty. testAgent.adjustScore(40); cv = testFactory.getNetworkStoppedCV(); + + // When testAgent connects, ConnectivityService will re-send us all current requests with + // the new score. There are expectedRequestCount such requests, and we must wait for all of + // them. + testFactory.expectAddRequests(expectedRequestCount); testAgent.connect(false); testAgent.addCapability(capability); waitFor(cv); - assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); + testFactory.waitForNetworkRequests(expectedRequestCount); assertFalse(testFactory.getMyStartRequested()); // Bring in a bunch of requests. + testFactory.expectAddRequests(10); + assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); ConnectivityManager.NetworkCallback[] networkCallbacks = new ConnectivityManager.NetworkCallback[10]; for (int i = 0; i< networkCallbacks.length; i++) { @@ -1000,6 +1121,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { assertFalse(testFactory.getMyStartRequested()); // Remove the requests. + testFactory.expectRemoveRequests(10); for (int i = 0; i < networkCallbacks.length; i++) { mCm.unregisterNetworkCallback(networkCallbacks[i]); } @@ -1088,10 +1210,8 @@ public class ConnectivityServiceTest extends AndroidTestCase { // Test bringing up unvalidated cellular with MMS mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); - cv = networkCallback.getConditionVariable(); mCellNetworkAgent.connectWithoutInternet(); - waitFor(cv); - assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback()); + networkCallback.expectCallback(CallbackState.AVAILABLE); verifyActiveNetwork(TRANSPORT_WIFI); // Test releasing NetworkRequest disconnects cellular with MMS cv = mCellNetworkAgent.getDisconnectedCV(); @@ -1114,12 +1234,10 @@ public class ConnectivityServiceTest extends AndroidTestCase { final TestNetworkCallback networkCallback = new TestNetworkCallback(); mCm.requestNetwork(builder.build(), networkCallback); // Test bringing up MMS cellular network - cv = networkCallback.getConditionVariable(); MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS); mmsNetworkAgent.connectWithoutInternet(); - waitFor(cv); - assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback()); + networkCallback.expectCallback(CallbackState.AVAILABLE); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent cv = mmsNetworkAgent.getDisconnectedCV(); @@ -1139,133 +1257,245 @@ public class ConnectivityServiceTest extends AndroidTestCase { final NetworkRequest validatedRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_VALIDATED).build(); mCm.registerNetworkCallback(validatedRequest, validatedCallback); - ConditionVariable validatedCv = validatedCallback.getConditionVariable(); // Bring up a network with a captive portal. // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. - ConditionVariable cv = captivePortalCallback.getConditionVariable(); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connectWithCaptivePortal(); - waitFor(cv); - assertEquals(CallbackState.AVAILABLE, captivePortalCallback.getLastCallback()); + captivePortalCallback.expectCallback(CallbackState.AVAILABLE); // Take down network. // Expect onLost callback. - cv = captivePortalCallback.getConditionVariable(); mWiFiNetworkAgent.disconnect(); - waitFor(cv); - assertEquals(CallbackState.LOST, captivePortalCallback.getLastCallback()); + captivePortalCallback.expectCallback(CallbackState.LOST); // Bring up a network with a captive portal. // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. - cv = captivePortalCallback.getConditionVariable(); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connectWithCaptivePortal(); - waitFor(cv); - assertEquals(CallbackState.AVAILABLE, captivePortalCallback.getLastCallback()); + captivePortalCallback.expectCallback(CallbackState.AVAILABLE); // Make captive portal disappear then revalidate. // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL. - cv = captivePortalCallback.getConditionVariable(); mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204; mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); - waitFor(cv); - assertEquals(CallbackState.LOST, captivePortalCallback.getLastCallback()); + captivePortalCallback.expectCallback(CallbackState.LOST); // Expect NET_CAPABILITY_VALIDATED onAvailable callback. - waitFor(validatedCv); - assertEquals(CallbackState.AVAILABLE, validatedCallback.getLastCallback()); + validatedCallback.expectCallback(CallbackState.AVAILABLE); // Break network connectivity. // Expect NET_CAPABILITY_VALIDATED onLost callback. - validatedCv = validatedCallback.getConditionVariable(); mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 500; mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false); - waitFor(validatedCv); - assertEquals(CallbackState.LOST, validatedCallback.getLastCallback()); + validatedCallback.expectCallback(CallbackState.LOST); + } + + private static class TestKeepaliveCallback extends PacketKeepaliveCallback { + + public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }; + + private class CallbackValue { + public CallbackType callbackType; + public int error; + + public CallbackValue(CallbackType type) { + this.callbackType = type; + this.error = PacketKeepalive.SUCCESS; + assertTrue("onError callback must have error", type != CallbackType.ON_ERROR); + } + + public CallbackValue(CallbackType type, int error) { + this.callbackType = type; + this.error = error; + assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR); + } + + @Override + public boolean equals(Object o) { + return o instanceof CallbackValue && + this.callbackType == ((CallbackValue) o).callbackType && + this.error == ((CallbackValue) o).error; + } + + @Override + public String toString() { + return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, error); + } + } + + private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>(); + + @Override + public void onStarted() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED)); + } + + @Override + public void onStopped() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED)); + } + + @Override + public void onError(int error) { + mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error)); + } + + private void expectCallback(CallbackValue callbackValue) { + try { + assertEquals( + callbackValue, + mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms"); + } + } + + public void expectStarted() { + expectCallback(new CallbackValue(CallbackType.ON_STARTED)); + } + + public void expectStopped() { + expectCallback(new CallbackValue(CallbackType.ON_STOPPED)); + } + + public void expectError(int error) { + expectCallback(new CallbackValue(CallbackType.ON_ERROR, error)); + } } -// @Override -// public void tearDown() throws Exception { -// super.tearDown(); -// } -// -// public void testMobileConnectedAddedRoutes() throws Exception { -// Future<?> nextConnBroadcast; -// -// // bring up mobile network -// mMobile.info.setDetailedState(DetailedState.CONNECTED, null, null); -// mMobile.link.setInterfaceName(MOBILE_IFACE); -// mMobile.link.addRoute(MOBILE_ROUTE_V4); -// mMobile.link.addRoute(MOBILE_ROUTE_V6); -// mMobile.doReturnDefaults(); -// -// cv = waitForConnectivityBroadcasts(1); -// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); -// waitFor(cv); -// -// // verify that both routes were added -// int mobileNetId = mMobile.tracker.getNetwork().netId; -// verify(mNetManager).addRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V4)); -// verify(mNetManager).addRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V6)); -// } -// -// public void testMobileWifiHandoff() throws Exception { -// Future<?> nextConnBroadcast; -// -// // bring up mobile network -// mMobile.info.setDetailedState(DetailedState.CONNECTED, null, null); -// mMobile.link.setInterfaceName(MOBILE_IFACE); -// mMobile.link.addRoute(MOBILE_ROUTE_V4); -// mMobile.link.addRoute(MOBILE_ROUTE_V6); -// mMobile.doReturnDefaults(); -// -// cv = waitForConnectivityBroadcasts(1); -// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); -// waitFor(cv); -// -// reset(mNetManager); -// -// // now bring up wifi network -// mWifi.info.setDetailedState(DetailedState.CONNECTED, null, null); -// mWifi.link.setInterfaceName(WIFI_IFACE); -// mWifi.link.addRoute(WIFI_ROUTE_V4); -// mWifi.link.addRoute(WIFI_ROUTE_V6); -// mWifi.doReturnDefaults(); -// -// // expect that mobile will be torn down -// doReturn(true).when(mMobile.tracker).teardown(); -// -// cv = waitForConnectivityBroadcasts(1); -// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mWifi.info).sendToTarget(); -// waitFor(cv); -// -// // verify that wifi routes added, and teardown requested -// int wifiNetId = mWifi.tracker.getNetwork().netId; -// verify(mNetManager).addRoute(eq(wifiNetId), eq(WIFI_ROUTE_V4)); -// verify(mNetManager).addRoute(eq(wifiNetId), eq(WIFI_ROUTE_V6)); -// verify(mMobile.tracker).teardown(); -// -// int mobileNetId = mMobile.tracker.getNetwork().netId; -// -// reset(mNetManager, mMobile.tracker); -// -// // tear down mobile network, as requested -// mMobile.info.setDetailedState(DetailedState.DISCONNECTED, null, null); -// mMobile.link.clear(); -// mMobile.doReturnDefaults(); -// -// cv = waitForConnectivityBroadcasts(1); -// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); -// waitFor(cv); -// -// verify(mNetManager).removeRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V4)); -// verify(mNetManager).removeRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V6)); -// -// } - - private static InetAddress parse(String addr) { - return InetAddress.parseNumericAddress(addr); + private Network connectKeepaliveNetwork(LinkProperties lp) { + // Ensure the network is disconnected before we do anything. + if (mWiFiNetworkAgent != null) { + assertNull(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork())); + } + + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + ConditionVariable cv = waitForConnectivityBroadcasts(1); + mWiFiNetworkAgent.connect(true); + waitFor(cv); + verifyActiveNetwork(TRANSPORT_WIFI); + mWiFiNetworkAgent.sendLinkProperties(lp); + mService.waitForIdle(); + return mWiFiNetworkAgent.getNetwork(); } + public void testPacketKeepalives() throws Exception { + InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); + InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); + InetAddress myIPv6 = InetAddress.getByName("2001:db8::1"); + InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); + InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888"); + + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("wlan12"); + lp.addLinkAddress(new LinkAddress(myIPv6, 64)); + lp.addLinkAddress(new LinkAddress(myIPv4, 25)); + lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); + lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); + + Network notMyNet = new Network(61234); + Network myNet = connectKeepaliveNetwork(lp); + + TestKeepaliveCallback callback = new TestKeepaliveCallback(); + PacketKeepalive ka; + + // Attempt to start keepalives with invalid parameters and check for errors. + ka = mCm.startNattKeepalive(notMyNet, 25, callback, myIPv4, 1234, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK); + + ka = mCm.startNattKeepalive(myNet, 19, callback, notMyIPv4, 1234, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_INTERVAL); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 1234, dstIPv6); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv6, 1234, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv6, 1234, dstIPv6); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); // NAT-T is IPv4-only. + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 123456, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_PORT); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 123456, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_PORT); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + + // Check that a started keepalive can be stopped. + mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS); + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + mWiFiNetworkAgent.setStopKeepaliveError(PacketKeepalive.SUCCESS); + ka.stop(); + callback.expectStopped(); + + // Check that deleting the IP address stops the keepalive. + LinkProperties bogusLp = new LinkProperties(lp); + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25)); + bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25)); + mWiFiNetworkAgent.sendLinkProperties(bogusLp); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); + mWiFiNetworkAgent.sendLinkProperties(lp); + + // Check that a started keepalive is stopped correctly when the network disconnects. + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + mWiFiNetworkAgent.disconnect(); + callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK); + + // ... and that stopping it after that has no adverse effects. + assertNull(mCm.getNetworkCapabilities(myNet)); + ka.stop(); + + // Reconnect. + myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS); + + // Check things work as expected when the keepalive is stopped and the network disconnects. + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + ka.stop(); + mWiFiNetworkAgent.disconnect(); + mService.waitForIdle(); + callback.expectStopped(); + + // Reconnect. + myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS); + + // Check that keepalive slots start from 1 and increment. The first one gets slot 1. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + + // The second one gets slot 2. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(2); + TestKeepaliveCallback callback2 = new TestKeepaliveCallback(); + PacketKeepalive ka2 = mCm.startNattKeepalive(myNet, 25, callback2, myIPv4, 6789, dstIPv4); + callback2.expectStarted(); + + // Now stop the first one and create a third. This also gets slot 1. + ka.stop(); + callback.expectStopped(); + + mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); + TestKeepaliveCallback callback3 = new TestKeepaliveCallback(); + PacketKeepalive ka3 = mCm.startNattKeepalive(myNet, 25, callback3, myIPv4, 9876, dstIPv4); + callback3.expectStarted(); + + ka2.stop(); + callback2.expectStopped(); + + ka3.stop(); + callback3.expectStopped(); + } } diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 7a2f1a3..29a1809 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -19,15 +19,19 @@ package com.android.server.usb; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbManager; +import android.hardware.usb.UsbPort; +import android.hardware.usb.UsbPortStatus; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; @@ -105,6 +109,7 @@ public class UsbDeviceManager { private static final int MSG_USER_SWITCHED = 5; private static final int MSG_SET_USB_DATA_UNLOCKED = 6; private static final int MSG_UPDATE_USER_RESTRICTIONS = 7; + private static final int MSG_UPDATE_HOST_STATE = 8; private static final int AUDIO_MODE_SOURCE = 1; @@ -175,6 +180,15 @@ public class UsbDeviceManager { } }; + private final BroadcastReceiver mHostReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + UsbPort port = intent.getParcelableExtra(UsbManager.EXTRA_PORT); + UsbPortStatus status = intent.getParcelableExtra(UsbManager.EXTRA_PORT_STATUS); + mHandler.updateHostState(port, status); + } + }; + public UsbDeviceManager(Context context, UsbAlsaManager alsaManager) { mContext = context; mUsbAlsaManager = alsaManager; @@ -197,6 +211,8 @@ public class UsbDeviceManager { if (secureAdbEnabled && !dataEncrypted) { mDebuggingManager = new UsbDebuggingManager(context); } + mContext.registerReceiver(mHostReceiver, + new IntentFilter(UsbManager.ACTION_USB_PORT_CHANGED)); } private UsbSettingsManager getCurrentSettings() { @@ -299,6 +315,7 @@ public class UsbDeviceManager { // current USB state private boolean mConnected; + private boolean mHostConnected; private boolean mConfigured; private boolean mUsbDataUnlocked; private String mCurrentFunctions; @@ -391,6 +408,11 @@ public class UsbDeviceManager { sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0); } + public void updateHostState(UsbPort port, UsbPortStatus status) { + boolean hostConnected = status.getCurrentDataRole() == UsbPort.DATA_ROLE_HOST; + obtainMessage(MSG_UPDATE_HOST_STATE, hostConnected ? 1 :0, 0).sendToTarget(); + } + private boolean waitForState(String state) { // wait for the transition to complete. // give up after 1 second. @@ -408,10 +430,9 @@ public class UsbDeviceManager { private boolean setUsbConfig(String config) { if (DEBUG) Slog.d(TAG, "setUsbConfig(" + config + ")"); // set the new configuration - String oldConfig = SystemProperties.get(USB_CONFIG_PROPERTY); - if (!config.equals(oldConfig)) { - SystemProperties.set(USB_CONFIG_PROPERTY, config); - } + // we always set it due to b/23631400, where adbd was getting killed + // and not restarted due to property timeouts on some devices + SystemProperties.set(USB_CONFIG_PROPERTY, config); return waitForState(config); } @@ -666,6 +687,10 @@ public class UsbDeviceManager { updateUsbFunctions(); } break; + case MSG_UPDATE_HOST_STATE: + mHostConnected = (msg.arg1 == 1); + updateUsbNotification(); + break; case MSG_ENABLE_ADB: setAdbEnabled(msg.arg1 == 1); break; @@ -721,7 +746,7 @@ public class UsbDeviceManager { if (mNotificationManager == null || !mUseUsbNotification) return; int id = 0; Resources r = mContext.getResources(); - if (mConnected) { + if (mConnected || mHostConnected) { if (!mUsbDataUnlocked) { id = com.android.internal.R.string.usb_charging_notification_title; } else if (UsbManager.containsFunction(mCurrentFunctions, diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java index 52abcfe..7f182a4 100644 --- a/services/usb/java/com/android/server/usb/UsbPortManager.java +++ b/services/usb/java/com/android/server/usb/UsbPortManager.java @@ -26,8 +26,13 @@ import android.hardware.usb.UsbPort; import android.hardware.usb.UsbPortStatus; import android.os.Handler; import android.os.Message; +import android.os.SystemClock; +import android.os.SystemProperties; import android.os.UEventObserver; import android.os.UserHandle; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; @@ -89,6 +94,9 @@ public class UsbPortManager { private static final String PORT_DATA_ROLE_HOST = "host"; private static final String PORT_DATA_ROLE_DEVICE = "device"; + private static final String USB_TYPEC_PROP_PREFIX = "sys.usb.typec."; + private static final String USB_TYPEC_STATE = "sys.usb.typec.state"; + // All non-trivial role combinations. private static final int COMBO_SOURCE_HOST = UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_HOST); @@ -621,16 +629,25 @@ public class UsbPortManager { return 0; } + private static boolean fileIsRootWritable(String path) { + try { + // If the file is user writable, then it is root writable. + return (Os.stat(path).st_mode & OsConstants.S_IWUSR) != 0; + } catch (ErrnoException e) { + return false; + } + } + private static boolean canChangeMode(File portDir) { - return new File(portDir, SYSFS_PORT_MODE).canWrite(); + return fileIsRootWritable(new File(portDir, SYSFS_PORT_MODE).getPath()); } private static boolean canChangePowerRole(File portDir) { - return new File(portDir, SYSFS_PORT_POWER_ROLE).canWrite(); + return fileIsRootWritable(new File(portDir, SYSFS_PORT_POWER_ROLE).getPath()); } private static boolean canChangeDataRole(File portDir) { - return new File(portDir, SYSFS_PORT_DATA_ROLE).canWrite(); + return fileIsRootWritable(new File(portDir, SYSFS_PORT_DATA_ROLE).getPath()); } private static String readFile(File dir, String filename) { @@ -642,16 +659,29 @@ public class UsbPortManager { } } - private static boolean writeFile(File dir, String filename, String contents) { - final File file = new File(dir, filename); - try { - try (FileWriter writer = new FileWriter(file)) { - writer.write(contents); - } - return true; - } catch (IOException ex) { - return false; + private static boolean waitForState(String property, String state) { + // wait for the transition to complete. + // give up after 5 seconds. + // 5 seconds is probably too long, but we have seen hardware that takes + // over 3 seconds to change states. + String value = null; + for (int i = 0; i < 100; i++) { + // State transition is done when property is set to the new configuration + value = SystemProperties.get(property); + if (state.equals(value)) return true; + SystemClock.sleep(50); } + Slog.e(TAG, "waitForState(" + state + ") for " + property + " FAILED: got " + value); + return false; + } + + private static String propertyFromFilename(String filename) { + return USB_TYPEC_PROP_PREFIX + filename; + } + + private static boolean writeFile(File dir, String filename, String contents) { + SystemProperties.set(propertyFromFilename(filename), contents); + return waitForState(USB_TYPEC_STATE, contents); } private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) { |