summaryrefslogtreecommitdiffstats
path: root/services/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java')
-rw-r--r--services/core/java/com/android/server/AlarmManagerService.java217
-rw-r--r--services/core/java/com/android/server/AppOpsService.java183
-rw-r--r--services/core/java/com/android/server/BatteryService.java27
-rw-r--r--services/core/java/com/android/server/BluetoothManagerService.java5
-rw-r--r--services/core/java/com/android/server/CommonTimeManagementService.java3
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java1865
-rw-r--r--services/core/java/com/android/server/ConsumerIrService.java20
-rw-r--r--services/core/java/com/android/server/DiskStatsService.java1
-rw-r--r--services/core/java/com/android/server/EntropyMixer.java1
-rw-r--r--services/core/java/com/android/server/EventLogTags.logtags16
-rw-r--r--services/core/java/com/android/server/INativeDaemonConnectorCallbacks.java1
-rw-r--r--services/core/java/com/android/server/IdleMaintenanceService.java603
-rw-r--r--services/core/java/com/android/server/InputMethodManagerService.java328
-rw-r--r--services/core/java/com/android/server/LocationManagerService.java156
-rw-r--r--services/core/java/com/android/server/LockSettingsService.java62
-rw-r--r--services/core/java/com/android/server/MountService.java226
-rw-r--r--services/core/java/com/android/server/MountServiceIdler.java49
-rw-r--r--services/core/java/com/android/server/NativeDaemonConnector.java46
-rw-r--r--services/core/java/com/android/server/NetworkManagementService.java618
-rw-r--r--services/core/java/com/android/server/NetworkScoreService.java199
-rw-r--r--services/core/java/com/android/server/NsdService.java34
-rw-r--r--services/core/java/com/android/server/RecognitionManagerService.java4
-rw-r--r--services/core/java/com/android/server/ServiceWatcher.java1
-rw-r--r--services/core/java/com/android/server/ShutdownActivity.java3
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java72
-rw-r--r--services/core/java/com/android/server/TextServicesManagerService.java1
-rw-r--r--services/core/java/com/android/server/UpdateLockService.java1
-rw-r--r--services/core/java/com/android/server/VibratorService.java80
-rw-r--r--services/core/java/com/android/server/WiredAccessoryManager.java26
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java17
-rwxr-xr-xservices/core/java/com/android/server/am/ActiveServices.java38
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java1916
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityRecord.java45
-rw-r--r--services/core/java/com/android/server/am/ActivityResult.java1
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityStack.java357
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java288
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java226
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.java3
-rw-r--r--services/core/java/com/android/server/am/ContentProviderRecord.java1
-rw-r--r--services/core/java/com/android/server/am/IntentBindRecord.java2
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java2
-rw-r--r--services/core/java/com/android/server/am/PendingThumbnailsRecord.java39
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java1
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java23
-rw-r--r--services/core/java/com/android/server/am/ProcessStatsService.java81
-rw-r--r--services/core/java/com/android/server/am/ReceiverList.java3
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java15
-rw-r--r--services/core/java/com/android/server/am/TaskRecord.java101
-rw-r--r--services/core/java/com/android/server/am/UriPermission.java60
-rw-r--r--services/core/java/com/android/server/am/UriPermissionOwner.java91
-rw-r--r--services/core/java/com/android/server/am/UsageStatsService.java386
-rw-r--r--services/core/java/com/android/server/clipboard/ClipboardService.java23
-rw-r--r--services/core/java/com/android/server/connectivity/Nat464Xlat.java71
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkAgentInfo.java80
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkMonitor.java405
-rw-r--r--services/core/java/com/android/server/connectivity/PacManager.java25
-rw-r--r--services/core/java/com/android/server/connectivity/Tethering.java6
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java5
-rw-r--r--services/core/java/com/android/server/content/ContentService.java300
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java1039
-rw-r--r--services/core/java/com/android/server/content/SyncOperation.java209
-rw-r--r--services/core/java/com/android/server/content/SyncQueue.java131
-rw-r--r--services/core/java/com/android/server/content/SyncStorageEngine.java1319
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java687
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java592
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java35
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java3
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java4
-rw-r--r--services/core/java/com/android/server/firewall/IntentFirewall.java1
-rw-r--r--services/core/java/com/android/server/firewall/SenderPackageFilter.java73
-rw-r--r--services/core/java/com/android/server/hdmi/FeatureAction.java178
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecController.java479
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java212
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java210
-rw-r--r--services/core/java/com/android/server/hdmi/NewDeviceAction.java166
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java49
-rw-r--r--services/core/java/com/android/server/input/PersistentDataStore.java143
-rw-r--r--services/core/java/com/android/server/location/ComprehensiveCountryDetector.java1
-rw-r--r--services/core/java/com/android/server/location/FlpHardwareProvider.java27
-rw-r--r--services/core/java/com/android/server/location/GeocoderProxy.java1
-rw-r--r--services/core/java/com/android/server/location/GeofenceProxy.java2
-rw-r--r--services/core/java/com/android/server/location/GpsLocationProvider.java1
-rw-r--r--services/core/java/com/android/server/location/GpsXtraDownloader.java1
-rw-r--r--services/core/java/com/android/server/location/LocationFudger.java3
-rw-r--r--services/core/java/com/android/server/location/LocationProviderProxy.java1
-rw-r--r--services/core/java/com/android/server/location/LocationRequestStatistics.java205
-rw-r--r--services/core/java/com/android/server/location/MockProvider.java1
-rw-r--r--services/core/java/com/android/server/media/MediaRouteProviderProxy.java419
-rw-r--r--services/core/java/com/android/server/media/MediaRouteProviderWatcher.java239
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java933
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java683
-rw-r--r--services/core/java/com/android/server/media/MediaSessionStack.java291
-rw-r--r--services/core/java/com/android/server/media/RouteConnectionRecord.java118
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java2
-rw-r--r--services/core/java/com/android/server/net/NetworkStatsService.java12
-rw-r--r--services/core/java/com/android/server/notification/ConditionProviders.java546
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java622
-rw-r--r--services/core/java/com/android/server/notification/NotificationComparator.java47
-rw-r--r--services/core/java/com/android/server/notification/NotificationDelegate.java18
-rw-r--r--services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java64
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java1440
-rw-r--r--services/core/java/com/android/server/notification/NotificationSignalExtractor.java41
-rw-r--r--services/core/java/com/android/server/notification/NotificationUsageStats.java595
-rw-r--r--services/core/java/com/android/server/notification/RankingFuture.java118
-rw-r--r--services/core/java/com/android/server/notification/ValidateNotificationPeople.java304
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java345
-rw-r--r--services/core/java/com/android/server/pm/BackgroundDexOptService.java91
-rw-r--r--services/core/java/com/android/server/pm/ForwardingIntentFilter.java114
-rw-r--r--services/core/java/com/android/server/pm/ForwardingIntentResolver.java44
-rw-r--r--services/core/java/com/android/server/pm/KeySetManager.java2
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java405
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java210
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java539
-rw-r--r--services/core/java/com/android/server/pm/PackageKeySetData.java2
-rwxr-xr-xservices/core/java/com/android/server/pm/PackageManagerService.java787
-rw-r--r--services/core/java/com/android/server/pm/PackageSettingBase.java3
-rw-r--r--services/core/java/com/android/server/pm/PersistentPreferredActivity.java96
-rw-r--r--services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java34
-rw-r--r--services/core/java/com/android/server/pm/Settings.java210
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java187
-rw-r--r--services/core/java/com/android/server/power/Notifier.java51
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java152
-rw-r--r--services/core/java/com/android/server/power/ShutdownThread.java3
-rw-r--r--services/core/java/com/android/server/search/SearchManagerService.java7
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java103
-rw-r--r--services/core/java/com/android/server/storage/DeviceStorageMonitorService.java2
-rw-r--r--services/core/java/com/android/server/task/StateChangedListener.java40
-rw-r--r--services/core/java/com/android/server/task/TaskCompletedListener.java38
-rw-r--r--services/core/java/com/android/server/task/TaskManagerService.java206
-rw-r--r--services/core/java/com/android/server/task/TaskServiceContext.java515
-rw-r--r--services/core/java/com/android/server/task/TaskStore.java103
-rw-r--r--services/core/java/com/android/server/task/controllers/ConnectivityController.java124
-rw-r--r--services/core/java/com/android/server/task/controllers/IdleController.java180
-rw-r--r--services/core/java/com/android/server/task/controllers/StateController.java51
-rw-r--r--services/core/java/com/android/server/task/controllers/TaskStatus.java180
-rw-r--r--services/core/java/com/android/server/task/controllers/TimeController.java230
-rw-r--r--services/core/java/com/android/server/trust/TrustAgentWrapper.java168
-rw-r--r--services/core/java/com/android/server/trust/TrustManagerService.java426
-rw-r--r--services/core/java/com/android/server/tv/TvInputManagerService.java1043
-rw-r--r--services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java4
-rw-r--r--services/core/java/com/android/server/updates/TZInfoInstallReceiver.java1
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java29
-rw-r--r--services/core/java/com/android/server/wifi/README.txt19
-rw-r--r--services/core/java/com/android/server/wifi/WifiController.java761
-rw-r--r--services/core/java/com/android/server/wifi/WifiNotificationController.java297
-rw-r--r--services/core/java/com/android/server/wifi/WifiService.java1599
-rw-r--r--services/core/java/com/android/server/wifi/WifiSettingsStore.java197
-rw-r--r--services/core/java/com/android/server/wifi/WifiTrafficPoller.java191
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java1173
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java311
-rw-r--r--services/core/java/com/android/server/wm/DisplayMagnifier.java756
-rw-r--r--services/core/java/com/android/server/wm/DragState.java1
-rw-r--r--services/core/java/com/android/server/wm/FakeWindowImpl.java2
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java2
-rw-r--r--services/core/java/com/android/server/wm/KeyguardDisableHandler.java1
-rw-r--r--services/core/java/com/android/server/wm/ViewServer.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java516
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java24
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java65
160 files changed, 25094 insertions, 9277 deletions
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index c14ed8b..168742f 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -39,8 +39,10 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.WorkSource;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Pair;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.TimeUtils;
import java.io.ByteArrayOutputStream;
@@ -53,9 +55,7 @@ import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
-import java.util.HashMap;
import java.util.LinkedList;
-import java.util.Map;
import java.util.TimeZone;
import static android.app.AlarmManager.RTC_WAKEUP;
@@ -103,6 +103,7 @@ class AlarmManagerService extends SystemService {
private long mNextNonWakeup;
int mBroadcastRefCount = 0;
PowerManager.WakeLock mWakeLock;
+ boolean mLastWakeLockUnimportantForLogging;
ArrayList<InFlight> mInFlight = new ArrayList<InFlight>();
final AlarmHandler mHandler = new AlarmHandler();
ClockReceiver mClockReceiver;
@@ -313,7 +314,22 @@ class AlarmManagerService extends SystemService {
return 0;
}
}
-
+
+ final Comparator<Alarm> mAlarmDispatchComparator = new Comparator<Alarm>() {
+ @Override
+ public int compare(Alarm lhs, Alarm rhs) {
+ if (lhs.wakeup != rhs.wakeup) {
+ return lhs.wakeup ? -1 : 1;
+ }
+ if (lhs.whenElapsed < rhs.whenElapsed) {
+ return -1;
+ } else if (lhs.whenElapsed > rhs.whenElapsed) {
+ return 1;
+ }
+ return 0;
+ }
+ };
+
// minimum recurrence period or alarm futurity for us to be able to fuzz it
static final long MIN_FUZZABLE_INTERVAL = 10000;
static final BatchTimeOrder sBatchOrder = new BatchTimeOrder();
@@ -411,8 +427,10 @@ class AlarmManagerService extends SystemService {
final Pair<String, ComponentName> mTarget;
final BroadcastStats mBroadcastStats;
final FilterStats mFilterStats;
+ final int mAlarmType;
- InFlight(AlarmManagerService service, PendingIntent pendingIntent, WorkSource workSource) {
+ InFlight(AlarmManagerService service, PendingIntent pendingIntent, WorkSource workSource,
+ int alarmType) {
mPendingIntent = pendingIntent;
mWorkSource = workSource;
Intent intent = pendingIntent.getIntent();
@@ -426,6 +444,7 @@ class AlarmManagerService extends SystemService {
mBroadcastStats.filterStats.put(mTarget, fs);
}
mFilterStats = fs;
+ mAlarmType = alarmType;
}
}
@@ -446,6 +465,7 @@ class AlarmManagerService extends SystemService {
}
static final class BroadcastStats {
+ final int mUid;
final String mPackageName;
long aggregateTime;
@@ -453,16 +473,17 @@ class AlarmManagerService extends SystemService {
int numWakeup;
long startTime;
int nesting;
- final HashMap<Pair<String, ComponentName>, FilterStats> filterStats
- = new HashMap<Pair<String, ComponentName>, FilterStats>();
+ final ArrayMap<Pair<String, ComponentName>, FilterStats> filterStats
+ = new ArrayMap<Pair<String, ComponentName>, FilterStats>();
- BroadcastStats(String packageName) {
+ BroadcastStats(int uid, String packageName) {
+ mUid = uid;
mPackageName = packageName;
}
}
- final HashMap<String, BroadcastStats> mBroadcastStats
- = new HashMap<String, BroadcastStats>();
+ final SparseArray<ArrayMap<String, BroadcastStats>> mBroadcastStats
+ = new SparseArray<ArrayMap<String, BroadcastStats>>();
@Override
public void onStart() {
@@ -474,8 +495,8 @@ class AlarmManagerService extends SystemService {
setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY));
PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
-
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*alarm*");
+
mTimeTickSender = PendingIntent.getBroadcastAsUser(getContext(), 0,
new Intent(Intent.ACTION_TIME_TICK).addFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY
@@ -754,24 +775,26 @@ class AlarmManagerService extends SystemService {
}
};
int len = 0;
- for (Map.Entry<String, BroadcastStats> be : mBroadcastStats.entrySet()) {
- BroadcastStats bs = be.getValue();
- for (Map.Entry<Pair<String, ComponentName>, FilterStats> fe
- : bs.filterStats.entrySet()) {
- FilterStats fs = fe.getValue();
- int pos = len > 0
- ? Arrays.binarySearch(topFilters, 0, len, fs, comparator) : 0;
- if (pos < 0) {
- pos = -pos - 1;
- }
- if (pos < topFilters.length) {
- int copylen = topFilters.length - pos - 1;
- if (copylen > 0) {
- System.arraycopy(topFilters, pos, topFilters, pos+1, copylen);
+ for (int iu=0; iu<mBroadcastStats.size(); iu++) {
+ ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu);
+ for (int ip=0; ip<uidStats.size(); ip++) {
+ BroadcastStats bs = uidStats.valueAt(ip);
+ for (int is=0; is<bs.filterStats.size(); is++) {
+ FilterStats fs = bs.filterStats.valueAt(is);
+ int pos = len > 0
+ ? Arrays.binarySearch(topFilters, 0, len, fs, comparator) : 0;
+ if (pos < 0) {
+ pos = -pos - 1;
}
- topFilters[pos] = fs;
- if (len < topFilters.length) {
- len++;
+ if (pos < topFilters.length) {
+ int copylen = topFilters.length - pos - 1;
+ if (copylen > 0) {
+ System.arraycopy(topFilters, pos, topFilters, pos+1, copylen);
+ }
+ topFilters[pos] = fs;
+ if (len < topFilters.length) {
+ len++;
+ }
}
}
}
@@ -785,7 +808,8 @@ class AlarmManagerService extends SystemService {
TimeUtils.formatDuration(fs.aggregateTime, pw);
pw.print(" running, "); pw.print(fs.numWakeup);
pw.print(" wakeups, "); pw.print(fs.count);
- pw.print(" alarms: "); pw.print(fs.mBroadcastStats.mPackageName);
+ pw.print(" alarms: "); UserHandle.formatUid(pw, fs.mBroadcastStats.mUid);
+ pw.print(":"); pw.print(fs.mBroadcastStats.mPackageName);
pw.println();
pw.print(" ");
if (fs.mTarget.first != null) {
@@ -801,35 +825,39 @@ class AlarmManagerService extends SystemService {
pw.println(" ");
pw.println(" Alarm Stats:");
final ArrayList<FilterStats> tmpFilters = new ArrayList<FilterStats>();
- for (Map.Entry<String, BroadcastStats> be : mBroadcastStats.entrySet()) {
- BroadcastStats bs = be.getValue();
- pw.print(" ");
- if (bs.nesting > 0) pw.print("*ACTIVE* ");
- pw.print(be.getKey());
- pw.print(" "); TimeUtils.formatDuration(bs.aggregateTime, pw);
- pw.print(" running, "); pw.print(bs.numWakeup);
- pw.println(" wakeups:");
- tmpFilters.clear();
- for (Map.Entry<Pair<String, ComponentName>, FilterStats> fe
- : bs.filterStats.entrySet()) {
- tmpFilters.add(fe.getValue());
- }
- Collections.sort(tmpFilters, comparator);
- for (int i=0; i<tmpFilters.size(); i++) {
- FilterStats fs = tmpFilters.get(i);
- pw.print(" ");
- if (fs.nesting > 0) pw.print("*ACTIVE* ");
- TimeUtils.formatDuration(fs.aggregateTime, pw);
- pw.print(" "); pw.print(fs.numWakeup);
- pw.print(" wakes " ); pw.print(fs.count);
- pw.print(" alarms:");
- if (fs.mTarget.first != null) {
- pw.print(" act="); pw.print(fs.mTarget.first);
- }
- if (fs.mTarget.second != null) {
- pw.print(" cmp="); pw.print(fs.mTarget.second.toShortString());
- }
- pw.println();
+ for (int iu=0; iu<mBroadcastStats.size(); iu++) {
+ ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu);
+ for (int ip=0; ip<uidStats.size(); ip++) {
+ BroadcastStats bs = uidStats.valueAt(ip);
+ pw.print(" ");
+ if (bs.nesting > 0) pw.print("*ACTIVE* ");
+ UserHandle.formatUid(pw, bs.mUid);
+ pw.print(":");
+ pw.print(bs.mPackageName);
+ pw.print(" "); TimeUtils.formatDuration(bs.aggregateTime, pw);
+ pw.print(" running, "); pw.print(bs.numWakeup);
+ pw.println(" wakeups:");
+ tmpFilters.clear();
+ for (int is=0; is<bs.filterStats.size(); is++) {
+ tmpFilters.add(bs.filterStats.valueAt(is));
+ }
+ Collections.sort(tmpFilters, comparator);
+ for (int i=0; i<tmpFilters.size(); i++) {
+ FilterStats fs = tmpFilters.get(i);
+ pw.print(" ");
+ if (fs.nesting > 0) pw.print("*ACTIVE* ");
+ TimeUtils.formatDuration(fs.aggregateTime, pw);
+ pw.print(" "); pw.print(fs.numWakeup);
+ pw.print(" wakes " ); pw.print(fs.count);
+ pw.print(" alarms:");
+ if (fs.mTarget.first != null) {
+ pw.print(" act="); pw.print(fs.mTarget.first);
+ }
+ if (fs.mTarget.second != null) {
+ pw.print(" cmp="); pw.print(fs.mTarget.second.toShortString());
+ }
+ pw.println();
+ }
}
}
@@ -1049,7 +1077,8 @@ class AlarmManagerService extends SystemService {
private native int setKernelTime(long nativeData, long millis);
private native int setKernelTimezone(long nativeData, int minuteswest);
- void triggerAlarmsLocked(ArrayList<Alarm> triggerList, long nowELAPSED, long nowRTC) {
+ void triggerAlarmsLocked(ArrayList<Alarm> triggerList, final long nowELAPSED,
+ final long nowRTC) {
// batches are temporally sorted, so we need only pull from the
// start of the list until we either empty it or hit a batch
// that is not yet deliverable
@@ -1088,6 +1117,14 @@ class AlarmManagerService extends SystemService {
}
}
+
+ Collections.sort(triggerList, mAlarmDispatchComparator);
+
+ if (localLOGV) {
+ for (int i=0; i<triggerList.size(); i++) {
+ Slog.v(TAG, "Triggering alarm #" + i + ": " + triggerList.get(i));
+ }
+ }
}
/**
@@ -1108,7 +1145,8 @@ class AlarmManagerService extends SystemService {
}
private static class Alarm {
- public int type;
+ public final int type;
+ public final boolean wakeup;
public int count;
public long when;
public long windowLength;
@@ -1121,6 +1159,8 @@ class AlarmManagerService extends SystemService {
public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen,
long _interval, PendingIntent _op, WorkSource _ws) {
type = _type;
+ wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP
+ || _type == AlarmManager.RTC_WAKEUP;
when = _when;
whenElapsed = _whenElapsed;
windowLength = _windowLength;
@@ -1138,6 +1178,8 @@ class AlarmManagerService extends SystemService {
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" type ");
sb.append(type);
+ sb.append(" when ");
+ sb.append(when);
sb.append(" ");
sb.append(operation.getTargetPackage());
sb.append('}');
@@ -1242,11 +1284,12 @@ class AlarmManagerService extends SystemService {
// we have an active broadcast so stay awake.
if (mBroadcastRefCount == 0) {
- setWakelockWorkSource(alarm.operation, alarm.workSource);
+ setWakelockWorkSource(alarm.operation, alarm.workSource,
+ alarm.type, true);
mWakeLock.acquire();
}
final InFlight inflight = new InFlight(AlarmManagerService.this,
- alarm.operation, alarm.workSource);
+ alarm.operation, alarm.workSource, alarm.type);
mInFlight.add(inflight);
mBroadcastRefCount++;
@@ -1270,8 +1313,16 @@ class AlarmManagerService extends SystemService {
|| alarm.type == RTC_WAKEUP) {
bs.numWakeup++;
fs.numWakeup++;
- ActivityManagerNative.noteWakeupAlarm(
- alarm.operation);
+ if (alarm.workSource != null && alarm.workSource.size() > 0) {
+ for (int wi=0; wi<alarm.workSource.size(); wi++) {
+ ActivityManagerNative.noteWakeupAlarm(
+ alarm.operation, alarm.workSource.get(wi),
+ alarm.workSource.getName(wi));
+ }
+ } else {
+ ActivityManagerNative.noteWakeupAlarm(
+ alarm.operation, -1, null);
+ }
}
} catch (PendingIntent.CanceledException e) {
if (alarm.repeatInterval > 0) {
@@ -1293,8 +1344,18 @@ class AlarmManagerService extends SystemService {
* @param pi PendingIntent to attribute blame to if ws is null.
* @param ws WorkSource to attribute blame.
*/
- void setWakelockWorkSource(PendingIntent pi, WorkSource ws) {
+ void setWakelockWorkSource(PendingIntent pi, WorkSource ws, int type, boolean first) {
try {
+ final boolean unimportant = pi == mTimeTickSender;
+ mWakeLock.setUnimportantForLogging(unimportant);
+ if (first || mLastWakeLockUnimportantForLogging) {
+ mWakeLock.setHistoryTag(pi.getTag(
+ type == ELAPSED_REALTIME_WAKEUP || type == RTC_WAKEUP
+ ? "*walarm*:" : "*alarm*:"));
+ } else {
+ mWakeLock.setHistoryTag(null);
+ }
+ mLastWakeLockUnimportantForLogging = unimportant;
if (ws != null) {
mWakeLock.setWorkSource(ws);
return;
@@ -1454,7 +1515,14 @@ class AlarmManagerService extends SystemService {
if (pkgList != null && (pkgList.length > 0)) {
for (String pkg : pkgList) {
removeLocked(pkg);
- mBroadcastStats.remove(pkg);
+ for (int i=mBroadcastStats.size()-1; i>=0; i--) {
+ ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(i);
+ if (uidStats.remove(pkg) != null) {
+ if (uidStats.size() <= 0) {
+ mBroadcastStats.removeAt(i);
+ }
+ }
+ }
}
}
}
@@ -1462,11 +1530,17 @@ class AlarmManagerService extends SystemService {
}
private final BroadcastStats getStatsLocked(PendingIntent pi) {
- String pkg = pi.getTargetPackage();
- BroadcastStats bs = mBroadcastStats.get(pkg);
+ String pkg = pi.getCreatorPackage();
+ int uid = pi.getCreatorUid();
+ ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.get(uid);
+ if (uidStats == null) {
+ uidStats = new ArrayMap<String, BroadcastStats>();
+ mBroadcastStats.put(uid, uidStats);
+ }
+ BroadcastStats bs = uidStats.get(pkg);
if (bs == null) {
- bs = new BroadcastStats(pkg);
- mBroadcastStats.put(pkg, bs);
+ bs = new BroadcastStats(uid, pkg);
+ uidStats.put(pkg, bs);
}
return bs;
}
@@ -1514,7 +1588,8 @@ class AlarmManagerService extends SystemService {
// the next of our alarms is now in flight. reattribute the wakelock.
if (mInFlight.size() > 0) {
InFlight inFlight = mInFlight.get(0);
- setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource);
+ setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource,
+ inFlight.mAlarmType, false);
} else {
// should never happen
mLog.w("Alarm wakelock still held but sent queue empty");
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index e5615c0..be20616 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -33,20 +33,25 @@ import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.AudioService;
import android.os.AsyncTask;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.Xml;
@@ -54,6 +59,7 @@ import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
+import com.google.android.util.AbstractMessageParser.MusicTrack;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -89,6 +95,10 @@ public class AppOpsService extends IAppOpsService.Stub {
final SparseArray<HashMap<String, Ops>> mUidOps
= new SparseArray<HashMap<String, Ops>>();
+ private int mDeviceOwnerUid;
+ private final SparseIntArray mProfileOwnerUids = new SparseIntArray();
+ private final SparseArray<boolean[]> mOpRestrictions = new SparseArray<boolean[]>();
+
public final static class Ops extends SparseArray<Op> {
public final String packageName;
public final int uid;
@@ -123,6 +133,8 @@ public class AppOpsService extends IAppOpsService.Stub {
= new ArrayMap<String, ArrayList<Callback>>();
final ArrayMap<IBinder, Callback> mModeWatchers
= new ArrayMap<IBinder, Callback>();
+ final SparseArray<SparseArray<Restriction>> mAudioRestrictions
+ = new SparseArray<SparseArray<Restriction>>();
public final class Callback implements DeathRecipient {
final IAppOpsCallback mCallback;
@@ -544,6 +556,9 @@ public class AppOpsService extends IAppOpsService.Stub {
verifyIncomingUid(uid);
verifyIncomingOp(code);
synchronized (this) {
+ if (isOpRestricted(uid, code)) {
+ return AppOpsManager.MODE_IGNORED;
+ }
Op op = getOpLocked(AppOpsManager.opToSwitch(code), uid, packageName, false);
if (op == null) {
return AppOpsManager.opToDefaultMode(code);
@@ -553,6 +568,58 @@ public class AppOpsService extends IAppOpsService.Stub {
}
@Override
+ public int checkAudioOperation(int code, int stream, int uid, String packageName) {
+ synchronized (this) {
+ final int mode = checkRestrictionLocked(code, stream, uid, packageName);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ return mode;
+ }
+ }
+ return checkOperation(code, uid, packageName);
+ }
+
+ private int checkRestrictionLocked(int code, int stream, int uid, String packageName) {
+ final SparseArray<Restriction> streamRestrictions = mAudioRestrictions.get(code);
+ if (streamRestrictions != null) {
+ final Restriction r = streamRestrictions.get(stream);
+ if (r != null && !r.exceptionPackages.contains(packageName)) {
+ return r.mode;
+ }
+ }
+ return AppOpsManager.MODE_ALLOWED;
+ }
+
+ @Override
+ public void setAudioRestriction(int code, int stream, int uid, int mode,
+ String[] exceptionPackages) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ synchronized (this) {
+ SparseArray<Restriction> streamRestrictions = mAudioRestrictions.get(code);
+ if (streamRestrictions == null) {
+ streamRestrictions = new SparseArray<Restriction>();
+ mAudioRestrictions.put(code, streamRestrictions);
+ }
+ streamRestrictions.remove(stream);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ final Restriction r = new Restriction();
+ r.mode = mode;
+ if (exceptionPackages != null) {
+ final int N = exceptionPackages.length;
+ r.exceptionPackages = new ArraySet<String>(N);
+ for (int i = 0; i < N; i++) {
+ final String pkg = exceptionPackages[i];
+ if (pkg != null) {
+ r.exceptionPackages.add(pkg.trim());
+ }
+ }
+ }
+ streamRestrictions.put(stream, r);
+ }
+ }
+ }
+
+ @Override
public int checkPackage(int uid, String packageName) {
synchronized (this) {
if (getOpsLocked(uid, packageName, true) != null) {
@@ -575,6 +642,9 @@ public class AppOpsService extends IAppOpsService.Stub {
return AppOpsManager.MODE_ERRORED;
}
Op op = getOpLocked(ops, code, true);
+ if (isOpRestricted(uid, code)) {
+ return AppOpsManager.MODE_IGNORED;
+ }
if (op.duration == -1) {
Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
+ " code " + code + " time=" + op.time + " duration=" + op.duration);
@@ -609,6 +679,9 @@ public class AppOpsService extends IAppOpsService.Stub {
return AppOpsManager.MODE_ERRORED;
}
Op op = getOpLocked(ops, code, true);
+ if (isOpRestricted(uid, code)) {
+ return AppOpsManager.MODE_IGNORED;
+ }
final int switchCode = AppOpsManager.opToSwitch(code);
final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
@@ -774,6 +847,23 @@ public class AppOpsService extends IAppOpsService.Stub {
return op;
}
+ private boolean isOpRestricted(int uid, int code) {
+ int userHandle = UserHandle.getUserId(uid);
+ boolean[] opRestrictions = mOpRestrictions.get(userHandle);
+ if ((opRestrictions != null) && opRestrictions[code]) {
+ if (userHandle == UserHandle.USER_OWNER) {
+ if (uid != mDeviceOwnerUid) {
+ return true;
+ }
+ } else {
+ if (uid != mProfileOwnerUids.get(userHandle, -1)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
void readState() {
synchronized (mFile) {
synchronized (this) {
@@ -1048,6 +1138,31 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
}
+ if (mAudioRestrictions.size() > 0) {
+ boolean printedHeader = false;
+ for (int o=0; o<mAudioRestrictions.size(); o++) {
+ final String op = AppOpsManager.opToName(mAudioRestrictions.keyAt(o));
+ final SparseArray<Restriction> restrictions = mAudioRestrictions.valueAt(o);
+ for (int i=0; i<restrictions.size(); i++) {
+ if (!printedHeader){
+ pw.println(" Audio Restrictions:");
+ printedHeader = true;
+ needSep = true;
+ }
+ final int stream = restrictions.keyAt(i);
+ pw.print(" "); pw.print(op);
+ pw.print(" stream="); pw.print(AudioService.streamToString(stream));
+ Restriction r = restrictions.valueAt(i);
+ pw.print(": mode="); pw.println(r.mode);
+ if (!r.exceptionPackages.isEmpty()) {
+ pw.println(" Exceptions:");
+ for (int j=0; j<r.exceptionPackages.size(); j++) {
+ pw.print(" "); pw.println(r.exceptionPackages.valueAt(j));
+ }
+ }
+ }
+ }
+ }
if (needSep) {
pw.println();
}
@@ -1080,4 +1195,72 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
}
+
+ private static final class Restriction {
+ private static final ArraySet<String> NO_EXCEPTIONS = new ArraySet<String>();
+ int mode;
+ ArraySet<String> exceptionPackages = NO_EXCEPTIONS;
+ }
+
+ @Override
+ public void setDeviceOwner(String packageName) throws RemoteException {
+ checkSystemUid("setDeviceOwner");
+ try {
+ mDeviceOwnerUid = mContext.getPackageManager().getPackageUid(packageName,
+ UserHandle.USER_OWNER);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Could not find Device Owner UID");
+ mDeviceOwnerUid = -1;
+ throw new IllegalArgumentException("Could not find device owner package "
+ + packageName);
+ }
+ }
+
+ @Override
+ public void setProfileOwner(String packageName, int userHandle) throws RemoteException {
+ checkSystemUid("setProfileOwner");
+ try {
+ int uid = mContext.getPackageManager().getPackageUid(packageName,
+ userHandle);
+ mProfileOwnerUids.put(userHandle, uid);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Could not find Profile Owner UID");
+ mProfileOwnerUids.put(userHandle, -1);
+ throw new IllegalArgumentException("Could not find profile owner package "
+ + packageName);
+ }
+ }
+
+ @Override
+ public void setUserRestrictions(Bundle restrictions, int userHandle) throws RemoteException {
+ checkSystemUid("setUserRestrictions");
+ boolean[] opRestrictions = mOpRestrictions.get(userHandle);
+ if (opRestrictions == null) {
+ opRestrictions = new boolean[AppOpsManager._NUM_OP];
+ mOpRestrictions.put(userHandle, opRestrictions);
+ }
+ for (int i = 0; i < opRestrictions.length; ++i) {
+ String restriction = AppOpsManager.opToRestriction(i);
+ if (restriction != null) {
+ opRestrictions[i] = restrictions.getBoolean(restriction, false);
+ } else {
+ opRestrictions[i] = false;
+ }
+ }
+ }
+
+ @Override
+ public void removeUser(int userHandle) throws RemoteException {
+ checkSystemUid("removeUser");
+ mOpRestrictions.remove(userHandle);
+ mProfileOwnerUids.removeAt(mProfileOwnerUids.indexOfKey(userHandle));
+ }
+
+ private void checkSystemUid(String function) {
+ int uid = Binder.getCallingUid();
+ if (uid != Process.SYSTEM_UID) {
+ throw new SecurityException(function + " must by called by the system");
+ }
+ }
+
}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index cc9055d..cb5946a 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -108,6 +108,7 @@ public final class BatteryService extends Binder {
private final Object mLock = new Object();
private BatteryProperties mBatteryProps;
+ private final BatteryProperties mLastBatteryProps = new BatteryProperties();
private boolean mBatteryLevelCritical;
private int mLastBatteryStatus;
private int mLastBatteryHealth;
@@ -157,7 +158,7 @@ public final class BatteryService extends Binder {
"DEVPATH=/devices/virtual/switch/invalid_charger");
}
- IBinder b = ServiceManager.getService("batterypropreg");
+ IBinder b = ServiceManager.getService("batteryproperties");
final IBatteryPropertiesRegistrar batteryPropertiesRegistrar =
IBatteryPropertiesRegistrar.Stub.asInterface(b);
try {
@@ -281,6 +282,8 @@ public final class BatteryService extends Binder {
mBatteryProps = props;
// Process the new values.
processValuesLocked();
+ } else {
+ mLastBatteryProps.set(props);
}
}
}
@@ -311,8 +314,6 @@ public final class BatteryService extends Binder {
+ ", batteryLevel=" + mBatteryProps.batteryLevel
+ ", batteryTechnology=" + mBatteryProps.batteryTechnology
+ ", batteryVoltage=" + mBatteryProps.batteryVoltage
- + ", batteryCurrentNow=" + mBatteryProps.batteryCurrentNow
- + ", batteryChargeCounter=" + mBatteryProps.batteryChargeCounter
+ ", batteryTemperature=" + mBatteryProps.batteryTemperature
+ ", mBatteryLevelCritical=" + mBatteryLevelCritical
+ ", mPlugType=" + mPlugType);
@@ -613,21 +614,15 @@ public final class BatteryService extends Binder {
pw.println(" level: " + mBatteryProps.batteryLevel);
pw.println(" scale: " + BATTERY_SCALE);
pw.println(" voltage: " + mBatteryProps.batteryVoltage);
-
- if (mBatteryProps.batteryCurrentNow != Integer.MIN_VALUE) {
- pw.println(" current now: " + mBatteryProps.batteryCurrentNow);
- }
-
- if (mBatteryProps.batteryChargeCounter != Integer.MIN_VALUE) {
- pw.println(" charge counter: " + mBatteryProps.batteryChargeCounter);
- }
-
pw.println(" temperature: " + mBatteryProps.batteryTemperature);
pw.println(" technology: " + mBatteryProps.batteryTechnology);
} else if (args.length == 3 && "set".equals(args[0])) {
String key = args[1];
String value = args[2];
try {
+ if (!mUpdatesStopped) {
+ mLastBatteryProps.set(mBatteryProps);
+ }
boolean update = true;
if ("ac".equals(key)) {
mBatteryProps.chargerAcOnline = Integer.parseInt(value) != 0;
@@ -660,13 +655,17 @@ public final class BatteryService extends Binder {
} else if (args.length == 1 && "reset".equals(args[0])) {
long ident = Binder.clearCallingIdentity();
try {
- mUpdatesStopped = false;
+ if (mUpdatesStopped) {
+ mUpdatesStopped = false;
+ mBatteryProps.set(mLastBatteryProps);
+ processValuesLocked();
+ }
} finally {
Binder.restoreCallingIdentity(ident);
}
} else {
pw.println("Dump current battery state, or:");
- pw.println(" set ac|usb|wireless|status|level|invalid <value>");
+ pw.println(" set [ac|usb|wireless|status|level|invalid] <value>");
pw.println(" reset");
}
}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 546324a..e2a8ca2 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -212,6 +212,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
registerForAirplaneMode(filter);
+ filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiver(mReceiver, filter);
loadStoredNameAndAddress();
if (isBluetoothPersistedStateOn()) {
@@ -1005,7 +1006,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
mState = BluetoothAdapter.STATE_OFF;
// enable
handleEnable(mQuietEnable);
- } else if (mBinding || mBluetooth != null) {
+ } else if (mBinding || mBluetooth != null) {
Message userMsg = mHandler.obtainMessage(MESSAGE_USER_SWITCHED);
userMsg.arg2 = 1 + msg.arg2;
// if user is switched when service is being binding
@@ -1014,7 +1015,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
if (DBG) {
Log.d(TAG, "delay MESSAGE_USER_SWITCHED " + userMsg.arg2);
}
- }
+ }
break;
}
}
diff --git a/services/core/java/com/android/server/CommonTimeManagementService.java b/services/core/java/com/android/server/CommonTimeManagementService.java
index 710fb9d..60b366a 100644
--- a/services/core/java/com/android/server/CommonTimeManagementService.java
+++ b/services/core/java/com/android/server/CommonTimeManagementService.java
@@ -18,7 +18,6 @@ package com.android.server;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.net.InetAddress;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -26,10 +25,8 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
-import android.net.IConnectivityManager;
import android.net.INetworkManagementEventObserver;
import android.net.InterfaceConfiguration;
-import android.net.NetworkInfo;
import android.os.Binder;
import android.os.CommonTimeConfig;
import android.os.Handler;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 57ee031..cc132be 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -20,15 +20,26 @@ import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.NetworkCallbackListener;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_DUMMY;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
+import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
+import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
+import static android.net.ConnectivityManager.TYPE_MOBILE_IMS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_CBS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_IA;
+import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.ConnectivityManager.TYPE_PROXY;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.ConnectivityServiceProtocol.NetworkFactoryProtocol;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
@@ -65,20 +76,23 @@ import android.net.LinkProperties;
import android.net.LinkProperties.CompareResult;
import android.net.LinkQualityInfo;
import android.net.MobileDataStateTracker;
+import android.net.Network;
+import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
import android.net.NetworkConfig;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkQuotaInfo;
+import android.net.NetworkRequest;
import android.net.NetworkState;
import android.net.NetworkStateTracker;
import android.net.NetworkUtils;
import android.net.Proxy;
import android.net.ProxyDataTracker;
-import android.net.ProxyProperties;
+import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.SamplingDataTracker;
import android.net.Uri;
-import android.net.wifi.WifiStateTracker;
import android.net.wimax.WimaxManagerConstants;
import android.os.AsyncTask;
import android.os.Binder;
@@ -117,11 +131,15 @@ import com.android.internal.net.VpnProfile;
import com.android.internal.telephony.DctConstants;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.AsyncChannel;
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.Nat464Xlat;
+import com.android.server.connectivity.NetworkAgentInfo;
+import com.android.server.connectivity.NetworkMonitor;
import com.android.server.connectivity.PacManager;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
@@ -147,7 +165,6 @@ import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.URL;
-import java.net.URLConnection;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -165,6 +182,8 @@ import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
+import static android.net.ConnectivityManager.INVALID_NET_ID;
+
/**
* @hide
*/
@@ -172,7 +191,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private static final String TAG = "ConnectivityService";
private static final boolean DBG = true;
- private static final boolean VDBG = false;
+ private static final boolean VDBG = true; // STOPSHIP
+
+ // network sampling debugging
+ private static final boolean SAMPLE_DBG = false;
private static final boolean LOGD_RULES = false;
@@ -200,10 +222,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// Set network sampling interval at 12 minutes, this way, even if the timers get
// aggregated, it will fire at around 15 minutes, which should allow us to
// aggregate this timer with other timers (specially the socket keep alive timers)
- private static final int DEFAULT_SAMPLING_INTERVAL_IN_SECONDS = (VDBG ? 30 : 12 * 60);
+ private static final int DEFAULT_SAMPLING_INTERVAL_IN_SECONDS = (SAMPLE_DBG ? 30 : 12 * 60);
// start network sampling a minute after booting ...
- private static final int DEFAULT_START_SAMPLING_INTERVAL_IN_SECONDS = (VDBG ? 30 : 60);
+ private static final int DEFAULT_START_SAMPLING_INTERVAL_IN_SECONDS = (SAMPLE_DBG ? 30 : 60);
AlarmManager mAlarmManager;
@@ -238,6 +260,17 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
private NetworkStateTracker mNetTrackers[];
+ /**
+ * Holds references to all NetworkAgentInfos claiming to support the legacy
+ * NetworkType. We used to have a static set of of NetworkStateTrackers
+ * for each network type. This is the new model.
+ * Supports synchronous inspection of state.
+ * These are built out at startup such that an unsupported network
+ * doesn't get an ArrayList instance, making this a tristate:
+ * unsupported, supported but not active and active.
+ */
+ private ArrayList<NetworkAgentInfo> mNetworkAgentInfoForType[];
+
/* Handles captive portal check on a network */
private CaptivePortalTracker mCaptivePortalTracker;
@@ -299,12 +332,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED = 2;
/**
- * used internally to change our network preference setting
- * arg1 = networkType to prefer
- */
- private static final int EVENT_SET_NETWORK_PREFERENCE = 3;
-
- /**
* used internally to synchronize inet condition reports
* arg1 = networkType
* arg2 = condition (0 bad, 100 good)
@@ -360,7 +387,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private static final int EVENT_ENABLE_FAIL_FAST_MOBILE_DATA = 14;
/**
- * user internally to indicate that data sampling interval is up
+ * used internally to indicate that data sampling interval is up
*/
private static final int EVENT_SAMPLE_INTERVAL_ELAPSED = 15;
@@ -369,10 +396,48 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
private static final int EVENT_PROXY_HAS_CHANGED = 16;
+ /**
+ * used internally when registering NetworkFactories
+ * obj = Messenger
+ */
+ private static final int EVENT_REGISTER_NETWORK_FACTORY = 17;
+
+ /**
+ * used internally when registering NetworkAgents
+ * obj = Messenger
+ */
+ private static final int EVENT_REGISTER_NETWORK_AGENT = 18;
+
+ /**
+ * used to add a network request
+ * includes a NetworkRequestInfo
+ */
+ private static final int EVENT_REGISTER_NETWORK_REQUEST = 19;
+
+ /**
+ * indicates a timeout period is over - check if we had a network yet or not
+ * and if not, call the timeout calback (but leave the request live until they
+ * cancel it.
+ * includes a NetworkRequestInfo
+ */
+ private static final int EVENT_TIMEOUT_NETWORK_REQUEST = 20;
+
+ /**
+ * used to add a network listener - no request
+ * includes a NetworkRequestInfo
+ */
+ private static final int EVENT_REGISTER_NETWORK_LISTENER = 21;
+
+ /**
+ * used to remove a network request, either a listener or a real request
+ * includes a NetworkRequest
+ */
+ private static final int EVENT_RELEASE_NETWORK_REQUEST = 22;
+
/** Handler used for internal events. */
- private InternalHandler mHandler;
+ final private InternalHandler mHandler;
/** Handler used for incoming {@link NetworkStateTracker} events. */
- private NetworkStateTrackerHandler mTrackerHandler;
+ final private NetworkStateTrackerHandler mTrackerHandler;
// list of DeathRecipients used to make sure features are turned off when
// a process dies
@@ -406,12 +471,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private ArrayList mInetLog;
// track the current default http proxy - tell the world if we get a new one (real change)
- private ProxyProperties mDefaultProxy = null;
+ private ProxyInfo mDefaultProxy = null;
private Object mProxyLock = new Object();
private boolean mDefaultProxyDisabled = false;
// track the global proxy.
- private ProxyProperties mGlobalProxy = null;
+ private ProxyInfo mGlobalProxy = null;
private PacManager mPacManager = null;
@@ -442,6 +507,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
TelephonyManager mTelephonyManager;
+ // sequence number for Networks
+ private final static int MIN_NET_ID = 10; // some reserved marks
+ private final static int MAX_NET_ID = 65535;
+ private int mNextNetId = MIN_NET_ID;
+
+ // sequence number of NetworkRequests
+ private int mNextNetworkRequestId = 1;
+
public ConnectivityService(Context context, INetworkManagementService netd,
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
// Currently, omitting a NetworkFactory will create one internally
@@ -454,6 +527,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
NetworkFactory netFactory) {
if (DBG) log("ConnectivityService starting up");
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addNetworkCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ netCap.addNetworkCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+ mDefaultRequest = new NetworkRequest(netCap, true, nextNetworkRequestId());
+ NetworkRequestInfo nri = new NetworkRequestInfo(null, mDefaultRequest, new Binder(),
+ NetworkRequestInfo.REQUEST);
+ mNetworkRequests.put(mDefaultRequest, nri);
+
HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
handlerThread.start();
mHandler = new InternalHandler(handlerThread.getLooper());
@@ -505,6 +586,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mNetTransitionWakeLockTimeout = mContext.getResources().getInteger(
com.android.internal.R.integer.config_networkTransitionTimeout);
+ mNetworkAgentInfoForType = (ArrayList<NetworkAgentInfo>[])
+ new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE + 1];
+
mNetTrackers = new NetworkStateTracker[
ConnectivityManager.MAX_NETWORK_TYPE+1];
mCurrentLinkProperties = new LinkProperties[ConnectivityManager.MAX_NETWORK_TYPE+1];
@@ -559,6 +643,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
"radio " + n.radio + " in network type " + n.type);
continue;
}
+ mNetworkAgentInfoForType[n.type] = new ArrayList<NetworkAgentInfo>();
+
mNetConfigs[n.type] = n;
mNetworksDefined++;
} catch(Exception e) {
@@ -601,21 +687,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
- // Update mNetworkPreference according to user mannually first then overlay config.xml
- mNetworkPreference = getPersistedNetworkPreference();
- if (mNetworkPreference == -1) {
- for (int n : mPriorityList) {
- if (mNetConfigs[n].isDefault() && ConnectivityManager.isNetworkTypeValid(n)) {
- mNetworkPreference = n;
- break;
- }
- }
- if (mNetworkPreference == -1) {
- throw new IllegalStateException(
- "You should set at least one default Network in config.xml!");
- }
- }
-
mNetRequestersPids =
(List<Integer> [])new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE+1];
for (int i : mPriorityList) {
@@ -706,9 +777,23 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
}
+ private synchronized int nextNetworkRequestId() {
+ return mNextNetworkRequestId++;
+ }
+
+ private synchronized int nextNetId() {
+ int netId = mNextNetId;
+ if (++mNextNetId > MAX_NET_ID) mNextNetId = MIN_NET_ID;
+ return netId;
+ }
+
/**
* Factory that creates {@link NetworkStateTracker} instances using given
* {@link NetworkConfig}.
+ *
+ * TODO - this is obsolete and will be deleted. It's replaced by the
+ * registerNetworkFactory call and protocol.
+ * @Deprecated in favor of registerNetworkFactory dynamic bindings
*/
public interface NetworkFactory {
public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config);
@@ -726,10 +811,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
@Override
public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config) {
switch (config.radio) {
- case TYPE_WIFI:
- return new WifiStateTracker(targetNetworkType, config.name);
- case TYPE_MOBILE:
- return new MobileDataStateTracker(targetNetworkType, config.name);
case TYPE_DUMMY:
return new DummyDataStateTracker(targetNetworkType, config.name);
case TYPE_BLUETOOTH:
@@ -830,41 +911,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return wimaxStateTracker;
}
- /**
- * Sets the preferred network.
- * @param preference the new preference
- */
- public void setNetworkPreference(int preference) {
- enforceChangePermission();
-
- mHandler.sendMessage(
- mHandler.obtainMessage(EVENT_SET_NETWORK_PREFERENCE, preference, 0));
- }
-
- public int getNetworkPreference() {
- enforceAccessPermission();
- int preference;
- synchronized(this) {
- preference = mNetworkPreference;
- }
- return preference;
- }
-
- private void handleSetNetworkPreference(int preference) {
- if (ConnectivityManager.isNetworkTypeValid(preference) &&
- mNetConfigs[preference] != null &&
- mNetConfigs[preference].isDefault()) {
- if (mNetworkPreference != preference) {
- final ContentResolver cr = mContext.getContentResolver();
- Settings.Global.putInt(cr, Settings.Global.NETWORK_PREFERENCE, preference);
- synchronized(this) {
- mNetworkPreference = preference;
- }
- enforcePreference();
- }
- }
- }
-
private int getConnectivityChangeDelay() {
final ContentResolver cr = mContext.getContentResolver();
@@ -876,41 +922,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
defaultDelay);
}
- private int getPersistedNetworkPreference() {
- final ContentResolver cr = mContext.getContentResolver();
-
- final int networkPrefSetting = Settings.Global
- .getInt(cr, Settings.Global.NETWORK_PREFERENCE, -1);
-
- return networkPrefSetting;
- }
-
- /**
- * Make the state of network connectivity conform to the preference settings
- * In this method, we only tear down a non-preferred network. Establishing
- * a connection to the preferred network is taken care of when we handle
- * the disconnect event from the non-preferred network
- * (see {@link #handleDisconnect(NetworkInfo)}).
- */
- private void enforcePreference() {
- if (mNetTrackers[mNetworkPreference].getNetworkInfo().isConnected())
- return;
-
- if (!mNetTrackers[mNetworkPreference].isAvailable())
- return;
-
- for (int t=0; t <= ConnectivityManager.MAX_RADIO_TYPE; t++) {
- if (t != mNetworkPreference && mNetTrackers[t] != null &&
- mNetTrackers[t].getNetworkInfo().isConnected()) {
- if (DBG) {
- log("tearing down " + mNetTrackers[t].getNetworkInfo() +
- " in enforcePreference");
- }
- teardown(mNetTrackers[t]);
- }
- }
- }
-
private boolean teardown(NetworkStateTracker netTracker) {
if (netTracker.teardown()) {
netTracker.setTeardownRequested(true);
@@ -924,11 +935,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* Check if UID should be blocked from using the network represented by the
* given {@link NetworkStateTracker}.
*/
- private boolean isNetworkBlocked(NetworkStateTracker tracker, int uid) {
- final String iface = tracker.getLinkProperties().getInterfaceName();
-
+ private boolean isNetworkBlocked(int networkType, int uid) {
final boolean networkCostly;
final int uidRules;
+
+ LinkProperties lp = getLinkPropertiesForType(networkType);
+ final String iface = (lp == null ? "" : lp.getInterfaceName());
synchronized (mRulesLock) {
networkCostly = mMeteredIfaces.contains(iface);
uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
@@ -945,11 +957,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
/**
* Return a filtered {@link NetworkInfo}, potentially marked
* {@link DetailedState#BLOCKED} based on
- * {@link #isNetworkBlocked(NetworkStateTracker, int)}.
+ * {@link #isNetworkBlocked}.
*/
- private NetworkInfo getFilteredNetworkInfo(NetworkStateTracker tracker, int uid) {
- NetworkInfo info = tracker.getNetworkInfo();
- if (isNetworkBlocked(tracker, uid)) {
+ private NetworkInfo getFilteredNetworkInfo(int networkType, int uid) {
+ NetworkInfo info = getNetworkInfoForType(networkType);
+ if (isNetworkBlocked(networkType, uid)) {
// network is blocked; clone and override state
info = new NetworkInfo(info);
info.setDetailedState(DetailedState.BLOCKED, null, null);
@@ -974,6 +986,15 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return getNetworkInfo(mActiveDefaultNetwork, uid);
}
+ // only called when the default request is satisfied
+ private void updateActiveDefaultNetwork(NetworkAgentInfo nai) {
+ if (nai != null) {
+ mActiveDefaultNetwork = nai.networkInfo.getType();
+ } else {
+ mActiveDefaultNetwork = TYPE_NONE;
+ }
+ }
+
/**
* Find the first Provisioning network.
*
@@ -1016,10 +1037,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
public NetworkInfo getActiveNetworkInfoUnfiltered() {
enforceAccessPermission();
if (isNetworkTypeValid(mActiveDefaultNetwork)) {
- final NetworkStateTracker tracker = mNetTrackers[mActiveDefaultNetwork];
- if (tracker != null) {
- return tracker.getNetworkInfo();
- }
+ return getNetworkInfoForType(mActiveDefaultNetwork);
}
return null;
}
@@ -1040,9 +1058,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private NetworkInfo getNetworkInfo(int networkType, int uid) {
NetworkInfo info = null;
if (isNetworkTypeValid(networkType)) {
- final NetworkStateTracker tracker = mNetTrackers[networkType];
- if (tracker != null) {
- info = getFilteredNetworkInfo(tracker, uid);
+ if (getNetworkInfoForType(networkType) != null) {
+ info = getFilteredNetworkInfo(networkType, uid);
}
}
return info;
@@ -1054,9 +1071,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
final int uid = Binder.getCallingUid();
final ArrayList<NetworkInfo> result = Lists.newArrayList();
synchronized (mRulesLock) {
- for (NetworkStateTracker tracker : mNetTrackers) {
- if (tracker != null) {
- result.add(getFilteredNetworkInfo(tracker, uid));
+ for (int networkType = 0; networkType <= ConnectivityManager.MAX_NETWORK_TYPE;
+ networkType++) {
+ if (getNetworkInfoForType(networkType) != null) {
+ result.add(getFilteredNetworkInfo(networkType, uid));
}
}
}
@@ -1066,7 +1084,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
@Override
public boolean isNetworkSupported(int networkType) {
enforceAccessPermission();
- return (isNetworkTypeValid(networkType) && (mNetTrackers[networkType] != null));
+ return (isNetworkTypeValid(networkType) && (getNetworkInfoForType(networkType) != null));
}
/**
@@ -1079,32 +1097,48 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
@Override
public LinkProperties getActiveLinkProperties() {
- return getLinkProperties(mActiveDefaultNetwork);
+ return getLinkPropertiesForType(mActiveDefaultNetwork);
}
@Override
- public LinkProperties getLinkProperties(int networkType) {
+ public LinkProperties getLinkPropertiesForType(int networkType) {
enforceAccessPermission();
if (isNetworkTypeValid(networkType)) {
- final NetworkStateTracker tracker = mNetTrackers[networkType];
- if (tracker != null) {
- return tracker.getLinkProperties();
- }
+ return getLinkPropertiesForTypeInternal(networkType);
}
return null;
}
+ // TODO - this should be ALL networks
+ @Override
+ public LinkProperties getLinkProperties(Network network) {
+ enforceAccessPermission();
+ NetworkAgentInfo nai = mNetworkForNetId.get(network.netId);
+ if (nai != null) return new LinkProperties(nai.linkProperties);
+ return null;
+ }
+
+ @Override
+ public NetworkCapabilities getNetworkCapabilities(Network network) {
+ enforceAccessPermission();
+ NetworkAgentInfo nai = mNetworkForNetId.get(network.netId);
+ if (nai != null) return new NetworkCapabilities(nai.networkCapabilities);
+ return null;
+ }
+
@Override
public NetworkState[] getAllNetworkState() {
enforceAccessPermission();
final int uid = Binder.getCallingUid();
final ArrayList<NetworkState> result = Lists.newArrayList();
synchronized (mRulesLock) {
- for (NetworkStateTracker tracker : mNetTrackers) {
- if (tracker != null) {
- final NetworkInfo info = getFilteredNetworkInfo(tracker, uid);
- result.add(new NetworkState(
- info, tracker.getLinkProperties(), tracker.getLinkCapabilities()));
+ for (int networkType = 0; networkType <= ConnectivityManager.MAX_NETWORK_TYPE;
+ networkType++) {
+ if (getNetworkInfoForType(networkType) != null) {
+ final NetworkInfo info = getFilteredNetworkInfo(networkType, uid);
+ final LinkProperties lp = getLinkPropertiesForTypeInternal(networkType);
+ final NetworkCapabilities netcap = getNetworkCapabilitiesForType(networkType);
+ result.add(new NetworkState(info, lp, netcap));
}
}
}
@@ -1113,10 +1147,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private NetworkState getNetworkStateUnchecked(int networkType) {
if (isNetworkTypeValid(networkType)) {
- final NetworkStateTracker tracker = mNetTrackers[networkType];
- if (tracker != null) {
- return new NetworkState(tracker.getNetworkInfo(), tracker.getLinkProperties(),
- tracker.getLinkCapabilities());
+ NetworkInfo info = getNetworkInfoForType(networkType);
+ if (info != null) {
+ return new NetworkState(info,
+ getLinkPropertiesForTypeInternal(networkType),
+ getNetworkCapabilitiesForType(networkType));
}
}
return null;
@@ -1163,29 +1198,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return false;
}
- public boolean setRadios(boolean turnOn) {
- boolean result = true;
- enforceChangePermission();
- for (NetworkStateTracker t : mNetTrackers) {
- if (t != null) result = t.setRadio(turnOn) && result;
- }
- return result;
- }
-
- public boolean setRadio(int netType, boolean turnOn) {
- enforceChangePermission();
- if (!ConnectivityManager.isNetworkTypeValid(netType)) {
- return false;
- }
- NetworkStateTracker tracker = mNetTrackers[netType];
- return tracker != null && tracker.setRadio(turnOn);
- }
-
private INetworkManagementEventObserver mDataActivityObserver = new BaseNetworkObserver() {
@Override
- public void interfaceClassDataActivityChanged(String label, boolean active) {
+ public void interfaceClassDataActivityChanged(String label, boolean active, long tsNanos) {
int deviceType = Integer.parseInt(label);
- sendDataActivityBroadcast(deviceType, active);
+ sendDataActivityBroadcast(deviceType, active, tsNanos);
}
};
@@ -1662,7 +1679,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
final long token = Binder.clearCallingIdentity();
try {
LinkProperties lp = tracker.getLinkProperties();
- boolean ok = addRouteToAddress(lp, addr, exempt);
+ boolean ok = addRouteToAddress(lp, addr, exempt, tracker.getNetwork().netId);
if (DBG) log("requestRouteToHostAddress ok=" + ok);
return ok;
} finally {
@@ -1671,24 +1688,25 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable,
- boolean exempt) {
- return modifyRoute(p, r, 0, ADD, toDefaultTable, exempt);
+ boolean exempt, int netId) {
+ return modifyRoute(p, r, 0, ADD, toDefaultTable, exempt, netId);
}
- private boolean removeRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) {
- return modifyRoute(p, r, 0, REMOVE, toDefaultTable, UNEXEMPT);
+ private boolean removeRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable, int netId) {
+ return modifyRoute(p, r, 0, REMOVE, toDefaultTable, UNEXEMPT, netId);
}
- private boolean addRouteToAddress(LinkProperties lp, InetAddress addr, boolean exempt) {
- return modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt);
+ private boolean addRouteToAddress(LinkProperties lp, InetAddress addr, boolean exempt,
+ int netId) {
+ return modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt, netId);
}
- private boolean removeRouteToAddress(LinkProperties lp, InetAddress addr) {
- return modifyRouteToAddress(lp, addr, REMOVE, TO_DEFAULT_TABLE, UNEXEMPT);
+ private boolean removeRouteToAddress(LinkProperties lp, InetAddress addr, int netId) {
+ return modifyRouteToAddress(lp, addr, REMOVE, TO_DEFAULT_TABLE, UNEXEMPT, netId);
}
private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd,
- boolean toDefaultTable, boolean exempt) {
+ boolean toDefaultTable, boolean exempt, int netId) {
RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr);
if (bestRoute == null) {
bestRoute = RouteInfo.makeHostRoute(addr, lp.getInterfaceName());
@@ -1703,11 +1721,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface);
}
}
- return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable, exempt);
+ return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable, exempt, netId);
}
private boolean modifyRoute(LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd,
- boolean toDefaultTable, boolean exempt) {
+ boolean toDefaultTable, boolean exempt, int netId) {
if ((lp == null) || (r == null)) {
if (DBG) log("modifyRoute got unexpected null: " + lp + ", " + r);
return false;
@@ -1736,7 +1754,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
bestRoute.getGateway(),
ifaceName);
}
- modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable, exempt);
+ modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable, exempt, netId);
}
}
if (doAdd) {
@@ -1746,7 +1764,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
synchronized (mRoutesLock) {
// only track default table - only one apps can effect
mAddedRoutes.add(r);
- mNetd.addRoute(ifaceName, r);
+ mNetd.addRoute(netId, r);
if (exempt) {
LinkAddress dest = r.getDestination();
if (!mExemptAddresses.contains(dest)) {
@@ -1756,7 +1774,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
} else {
- mNetd.addSecondaryRoute(ifaceName, r);
+ mNetd.addRoute(netId, r);
}
} catch (Exception e) {
// never crash - catch them all
@@ -1772,7 +1790,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (mAddedRoutes.contains(r) == false) {
if (VDBG) log("Removing " + r + " for interface " + ifaceName);
try {
- mNetd.removeRoute(ifaceName, r);
+ mNetd.removeRoute(netId, r);
LinkAddress dest = r.getDestination();
if (mExemptAddresses.contains(dest)) {
mNetd.clearHostExemption(dest);
@@ -1790,7 +1808,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
} else {
if (VDBG) log("Removing " + r + " for interface " + ifaceName);
try {
- mNetd.removeSecondaryRoute(ifaceName, r);
+ mNetd.removeRoute(netId, r);
} catch (Exception e) {
// never crash - catch them all
if (VDBG) loge("Exception trying to remove a route: " + e);
@@ -1899,18 +1917,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
private void handleSetMobileData(boolean enabled) {
- if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) {
- if (VDBG) {
- log(mNetTrackers[ConnectivityManager.TYPE_MOBILE].toString() + enabled);
- }
- mNetTrackers[ConnectivityManager.TYPE_MOBILE].setUserDataEnable(enabled);
- }
- if (mNetTrackers[ConnectivityManager.TYPE_WIMAX] != null) {
- if (VDBG) {
- log(mNetTrackers[ConnectivityManager.TYPE_WIMAX].toString() + enabled);
- }
- mNetTrackers[ConnectivityManager.TYPE_WIMAX].setUserDataEnable(enabled);
- }
+ // TODO - handle this - probably generalize passing in a transport type and send to the
+ // factories?
}
@Override
@@ -1923,12 +1931,13 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
private void handleSetPolicyDataEnable(int networkType, boolean enabled) {
- if (isNetworkTypeValid(networkType)) {
- final NetworkStateTracker tracker = mNetTrackers[networkType];
- if (tracker != null) {
- tracker.setPolicyDataEnable(enabled);
- }
- }
+ // TODO - handle this passing to factories
+// if (isNetworkTypeValid(networkType)) {
+// final NetworkStateTracker tracker = mNetTrackers[networkType];
+// if (tracker != null) {
+// tracker.setPolicyDataEnable(enabled);
+// }
+// }
}
private void enforceAccessPermission() {
@@ -1984,9 +1993,13 @@ public class ConnectivityService extends IConnectivityManager.Stub {
int prevNetType = info.getType();
mNetTrackers[prevNetType].setTeardownRequested(false);
+ int thisNetId = mNetTrackers[prevNetType].getNetwork().netId;
// Remove idletimer previously setup in {@code handleConnect}
- removeDataActivityTracking(prevNetType);
+// Already in place in new function. This is dead code.
+// if (mNetConfigs[prevNetType].isDefault()) {
+// removeDataActivityTracking(prevNetType);
+// }
/*
* If the disconnected network is not the active one, then don't report
@@ -2053,7 +2066,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
// do this before we broadcast the change
- handleConnectivityChange(prevNetType, doReset);
+// Already done in new function. This is dead code.
+// handleConnectivityChange(prevNetType, doReset);
final Intent immediateIntent = new Intent(intent);
immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
@@ -2067,6 +2081,13 @@ public class ConnectivityService extends IConnectivityManager.Stub {
sendConnectedBroadcastDelayed(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(),
getConnectivityChangeDelay());
}
+ try {
+// mNetd.removeNetwork(thisNetId);
+ } catch (Exception e) {
+ loge("Exception removing network: " + e);
+ } finally {
+ mNetTrackers[prevNetType].setNetId(INVALID_NET_ID);
+ }
}
private void tryFailover(int prevNetType) {
@@ -2081,6 +2102,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
log("tryFailover: set mActiveDefaultNetwork=-1, prevNetType=" + prevNetType);
}
mActiveDefaultNetwork = -1;
+ try {
+ mNetd.clearDefaultNetId();
+ } catch (Exception e) {
+ loge("Exception clearing default network :" + e);
+ }
}
// don't signal a reconnect for anything lower or equal priority than our
@@ -2167,10 +2193,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
sendStickyBroadcastDelayed(makeGeneralIntent(info, bcastType), delayMs);
}
- private void sendDataActivityBroadcast(int deviceType, boolean active) {
+ private void sendDataActivityBroadcast(int deviceType, boolean active, long tsNanos) {
Intent intent = new Intent(ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE);
intent.putExtra(ConnectivityManager.EXTRA_DEVICE_TYPE, deviceType);
intent.putExtra(ConnectivityManager.EXTRA_IS_ACTIVE, active);
+ intent.putExtra(ConnectivityManager.EXTRA_REALTIME_NS, tsNanos);
final long ident = Binder.clearCallingIdentity();
try {
mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL,
@@ -2180,67 +2207,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
- /**
- * Called when an attempt to fail over to another network has failed.
- * @param info the {@link NetworkInfo} for the failed network
- */
- private void handleConnectionFailure(NetworkInfo info) {
- mNetTrackers[info.getType()].setTeardownRequested(false);
-
- String reason = info.getReason();
- String extraInfo = info.getExtraInfo();
-
- String reasonText;
- if (reason == null) {
- reasonText = ".";
- } else {
- reasonText = " (" + reason + ").";
- }
- loge("Attempt to connect to " + info.getTypeName() + " failed" + reasonText);
-
- Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
- intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
- intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
- if (getActiveNetworkInfo() == null) {
- intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
- }
- if (reason != null) {
- intent.putExtra(ConnectivityManager.EXTRA_REASON, reason);
- }
- if (extraInfo != null) {
- intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, extraInfo);
- }
- if (info.isFailover()) {
- intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
- info.setFailover(false);
- }
-
- if (mNetConfigs[info.getType()].isDefault()) {
- tryFailover(info.getType());
- if (mActiveDefaultNetwork != -1) {
- NetworkInfo switchTo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
- intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
- } else {
- mDefaultInetConditionPublished = 0;
- intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
- }
- }
-
- intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished);
-
- final Intent immediateIntent = new Intent(intent);
- immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
- sendStickyBroadcast(immediateIntent);
- sendStickyBroadcast(intent);
- /*
- * If the failover network is already connected, then immediately send
- * out a followup broadcast indicating successful failover
- */
- if (mActiveDefaultNetwork != -1) {
- sendConnectedBroadcast(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo());
- }
- }
-
private void sendStickyBroadcast(Intent intent) {
synchronized(this) {
if (!mSystemReady) {
@@ -2318,8 +2284,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private void handleConnect(NetworkInfo info) {
final int newNetType = info.getType();
- setupDataActivityTracking(newNetType);
-
// snapshot isFailover, because sendConnectedBroadcast() resets it
boolean isFailover = info.isFailover();
final NetworkStateTracker thisNet = mNetTrackers[newNetType];
@@ -2335,17 +2299,23 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (mNetConfigs[newNetType].isDefault()) {
if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != newNetType) {
if (isNewNetTypePreferredOverCurrentNetType(newNetType)) {
- // tear down the other
- NetworkStateTracker otherNet =
- mNetTrackers[mActiveDefaultNetwork];
- if (DBG) {
- log("Policy requires " + otherNet.getNetworkInfo().getTypeName() +
- " teardown");
- }
- if (!teardown(otherNet)) {
- loge("Network declined teardown request");
- teardown(thisNet);
- return;
+ String teardownPolicy = SystemProperties.get("net.teardownPolicy");
+ if (TextUtils.equals(teardownPolicy, "keep") == false) {
+ // tear down the other
+ NetworkStateTracker otherNet =
+ mNetTrackers[mActiveDefaultNetwork];
+ if (DBG) {
+ log("Policy requires " + otherNet.getNetworkInfo().getTypeName() +
+ " teardown");
+ }
+ if (!teardown(otherNet)) {
+ loge("Network declined teardown request");
+ teardown(thisNet);
+ return;
+ }
+ } else {
+ //TODO - remove
+ loge("network teardown skipped due to net.teardownPolicy setting");
}
} else {
// don't accept this one
@@ -2357,6 +2327,17 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return;
}
}
+ int thisNetId = nextNetId();
+ thisNet.setNetId(thisNetId);
+ try {
+// mNetd.createNetwork(thisNetId, thisIface);
+ } catch (Exception e) {
+ loge("Exception creating network :" + e);
+ teardown(thisNet);
+ return;
+ }
+// Already in place in new function. This is dead code.
+// setupDataActivityTracking(newNetType);
synchronized (ConnectivityService.this) {
// have a new default network, release the transition wakelock in a second
// if it's held. The second pause is to allow apps to reconnect over the
@@ -2369,6 +2350,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
mActiveDefaultNetwork = newNetType;
+ try {
+ mNetd.setDefaultNetId(thisNetId);
+ } catch (Exception e) {
+ loge("Exception setting default network :" + e);
+ }
// this will cause us to come up initially as unconnected and switching
// to connected after our normal pause unless somebody reports us as reall
// disconnected
@@ -2378,10 +2364,21 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// Don't do this - if we never sign in stay, grey
//reportNetworkCondition(mActiveDefaultNetwork, 100);
updateNetworkSettings(thisNet);
+ } else {
+ int thisNetId = nextNetId();
+ thisNet.setNetId(thisNetId);
+ try {
+// mNetd.createNetwork(thisNetId, thisIface);
+ } catch (Exception e) {
+ loge("Exception creating network :" + e);
+ teardown(thisNet);
+ return;
+ }
}
thisNet.setTeardownRequested(false);
- updateMtuSizeSettings(thisNet);
- handleConnectivityChange(newNetType, false);
+// Already in place in new function. This is dead code.
+// updateMtuSizeSettings(thisNet);
+// handleConnectivityChange(newNetType, false);
sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay());
// notify battery stats service about this network
@@ -2394,75 +2391,49 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
- private void handleCaptivePortalTrackerCheck(NetworkInfo info) {
- if (DBG) log("Captive portal check " + info);
- int type = info.getType();
- final NetworkStateTracker thisNet = mNetTrackers[type];
- if (mNetConfigs[type].isDefault()) {
- if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) {
- if (isNewNetTypePreferredOverCurrentNetType(type)) {
- if (DBG) log("Captive check on " + info.getTypeName());
- mCaptivePortalTracker.detectCaptivePortal(new NetworkInfo(info));
- return;
- } else {
- if (DBG) log("Tear down low priority net " + info.getTypeName());
- teardown(thisNet);
- return;
- }
- }
- }
-
- if (DBG) log("handleCaptivePortalTrackerCheck: call captivePortalCheckComplete ni=" + info);
- thisNet.captivePortalCheckComplete();
- }
-
- /** @hide */
- @Override
- public void captivePortalCheckComplete(NetworkInfo info) {
- enforceConnectivityInternalPermission();
- if (DBG) log("captivePortalCheckComplete: ni=" + info);
- mNetTrackers[info.getType()].captivePortalCheckComplete();
- }
-
/** @hide */
@Override
public void captivePortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
enforceConnectivityInternalPermission();
if (DBG) log("captivePortalCheckCompleted: ni=" + info + " captive=" + isCaptivePortal);
- mNetTrackers[info.getType()].captivePortalCheckCompleted(isCaptivePortal);
+// mNetTrackers[info.getType()].captivePortalCheckCompleted(isCaptivePortal);
}
/**
- * Setup data activity tracking for the given network interface.
+ * Setup data activity tracking for the given network.
*
* Every {@code setupDataActivityTracking} should be paired with a
- * {@link removeDataActivityTracking} for cleanup.
+ * {@link #removeDataActivityTracking} for cleanup.
*/
- private void setupDataActivityTracking(int type) {
- final NetworkStateTracker thisNet = mNetTrackers[type];
- final String iface = thisNet.getLinkProperties().getInterfaceName();
+ private void setupDataActivityTracking(NetworkAgentInfo networkAgent) {
+ final String iface = networkAgent.linkProperties.getInterfaceName();
final int timeout;
+ int type = ConnectivityManager.TYPE_NONE;
- if (ConnectivityManager.isNetworkTypeMobile(type)) {
+ if (networkAgent.networkCapabilities.hasTransport(
+ NetworkCapabilities.TRANSPORT_CELLULAR)) {
timeout = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
- 0);
- // Canonicalize mobile network type
+ 5);
type = ConnectivityManager.TYPE_MOBILE;
- } else if (ConnectivityManager.TYPE_WIFI == type) {
+ } else if (networkAgent.networkCapabilities.hasTransport(
+ NetworkCapabilities.TRANSPORT_WIFI)) {
timeout = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
0);
+ type = ConnectivityManager.TYPE_WIFI;
} else {
// do not track any other networks
timeout = 0;
}
- if (timeout > 0 && iface != null) {
+ if (timeout > 0 && iface != null && type != ConnectivityManager.TYPE_NONE) {
try {
- mNetd.addIdleTimer(iface, timeout, Integer.toString(type));
- } catch (RemoteException e) {
+ mNetd.addIdleTimer(iface, timeout, type);
+ } catch (Exception e) {
+ // You shall not crash!
+ loge("Exception in setupDataActivityTracking " + e);
}
}
}
@@ -2470,16 +2441,17 @@ public class ConnectivityService extends IConnectivityManager.Stub {
/**
* Remove data activity tracking when network disconnects.
*/
- private void removeDataActivityTracking(int type) {
- final NetworkStateTracker net = mNetTrackers[type];
- final String iface = net.getLinkProperties().getInterfaceName();
+ private void removeDataActivityTracking(NetworkAgentInfo networkAgent) {
+ final String iface = networkAgent.linkProperties.getInterfaceName();
+ final NetworkCapabilities caps = networkAgent.networkCapabilities;
- if (iface != null && (ConnectivityManager.isNetworkTypeMobile(type) ||
- ConnectivityManager.TYPE_WIFI == type)) {
+ if (iface != null && (caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
+ caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))) {
try {
// the call fails silently if no idletimer setup for this interface
mNetd.removeIdleTimer(iface);
- } catch (RemoteException e) {
+ } catch (Exception e) {
+ loge("Exception in removeDataActivityTracking " + e);
}
}
}
@@ -2489,8 +2461,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* concerned with making sure that the list of DNS servers is set up
* according to which networks are connected, and ensuring that the
* right routing table entries exist.
+ *
+ * TODO - delete when we're sure all this functionallity is captured.
*/
- private void handleConnectivityChange(int netType, boolean doReset) {
+ private void handleConnectivityChange(int netType, LinkProperties curLp, boolean doReset) {
int resetMask = doReset ? NetworkUtils.RESET_ALL_ADDRESSES : 0;
boolean exempt = ConnectivityManager.isNetworkTypeExempt(netType);
if (VDBG) {
@@ -2504,7 +2478,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
handleDnsConfigurationChange(netType);
- LinkProperties curLp = mCurrentLinkProperties[netType];
LinkProperties newLp = null;
if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
@@ -2534,9 +2507,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
"\n car=" + car);
}
} else {
- if (DBG) {
- log("handleConnectivityChange: address are the same reset per doReset" +
- " linkProperty[" + netType + "]:" +
+ if (VDBG) {
+ log("handleConnectivityChange: addresses are the same reset per" +
+ " doReset linkProperty[" + netType + "]:" +
" resetMask=" + resetMask);
}
}
@@ -2561,7 +2534,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
mCurrentLinkProperties[netType] = newLp;
- boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault(), exempt);
+ boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault(), exempt,
+ mNetTrackers[netType].getNetwork().netId);
if (resetMask != 0 || resetDns) {
if (VDBG) log("handleConnectivityChange: resetting");
@@ -2583,40 +2557,20 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
}
- if (resetDns) {
- flushVmDnsCache();
- if (VDBG) log("resetting DNS cache for " + iface);
- try {
- mNetd.flushInterfaceDnsCache(iface);
- } catch (Exception e) {
- // never crash - catch them all
- if (DBG) loge("Exception resetting dns cache: " + e);
- }
- }
} else {
loge("Can't reset connection for type "+netType);
}
}
- }
- }
-
- // Update 464xlat state.
- NetworkStateTracker tracker = mNetTrackers[netType];
- if (mClat.requiresClat(netType, tracker)) {
-
- // If the connection was previously using clat, but is not using it now, stop the clat
- // daemon. Normally, this happens automatically when the connection disconnects, but if
- // the disconnect is not reported, or if the connection's LinkProperties changed for
- // some other reason (e.g., handoff changes the IP addresses on the link), it would
- // still be running. If it's not running, then stopping it is a no-op.
- if (Nat464Xlat.isRunningClat(curLp) && !Nat464Xlat.isRunningClat(newLp)) {
- mClat.stopClat();
- }
- // If the link requires clat to be running, then start the daemon now.
- if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
- mClat.startClat(tracker);
- } else {
- mClat.stopClat();
+ if (resetDns) {
+ flushVmDnsCache();
+ if (VDBG) log("resetting DNS cache for type " + netType);
+ try {
+ mNetd.flushNetworkDnsCache(mNetTrackers[netType].getNetwork().netId);
+ } catch (Exception e) {
+ // never crash - catch them all
+ if (DBG) loge("Exception resetting dns cache: " + e);
+ }
+ }
}
}
@@ -2640,7 +2594,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* returns a boolean indicating the routes changed
*/
private boolean updateRoutes(LinkProperties newLp, LinkProperties curLp,
- boolean isLinkDefault, boolean exempt) {
+ boolean isLinkDefault, boolean exempt, int netId) {
Collection<RouteInfo> routesToAdd = null;
CompareResult<InetAddress> dnsDiff = new CompareResult<InetAddress>();
CompareResult<RouteInfo> routeDiff = new CompareResult<RouteInfo>();
@@ -2658,45 +2612,20 @@ public class ConnectivityService extends IConnectivityManager.Stub {
for (RouteInfo r : routeDiff.removed) {
if (isLinkDefault || ! r.isDefaultRoute()) {
if (VDBG) log("updateRoutes: default remove route r=" + r);
- removeRoute(curLp, r, TO_DEFAULT_TABLE);
+ removeRoute(curLp, r, TO_DEFAULT_TABLE, netId);
}
if (isLinkDefault == false) {
// remove from a secondary route table
- removeRoute(curLp, r, TO_SECONDARY_TABLE);
- }
- }
-
- if (!isLinkDefault) {
- // handle DNS routes
- if (routesChanged) {
- // routes changed - remove all old dns entries and add new
- if (curLp != null) {
- for (InetAddress oldDns : curLp.getDnses()) {
- removeRouteToAddress(curLp, oldDns);
- }
- }
- if (newLp != null) {
- for (InetAddress newDns : newLp.getDnses()) {
- addRouteToAddress(newLp, newDns, exempt);
- }
- }
- } else {
- // no change in routes, check for change in dns themselves
- for (InetAddress oldDns : dnsDiff.removed) {
- removeRouteToAddress(curLp, oldDns);
- }
- for (InetAddress newDns : dnsDiff.added) {
- addRouteToAddress(newLp, newDns, exempt);
- }
+ removeRoute(curLp, r, TO_SECONDARY_TABLE, netId);
}
}
for (RouteInfo r : routeDiff.added) {
if (isLinkDefault || ! r.isDefaultRoute()) {
- addRoute(newLp, r, TO_DEFAULT_TABLE, exempt);
+ addRoute(newLp, r, TO_DEFAULT_TABLE, exempt, netId);
} else {
// add to a secondary route table
- addRoute(newLp, r, TO_SECONDARY_TABLE, UNEXEMPT);
+ addRoute(newLp, r, TO_SECONDARY_TABLE, UNEXEMPT, netId);
// many radios add a default route even when we don't want one.
// remove the default route unless somebody else has asked for it
@@ -2705,7 +2634,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (!TextUtils.isEmpty(ifaceName) && !mAddedRoutes.contains(r)) {
if (VDBG) log("Removing " + r + " for interface " + ifaceName);
try {
- mNetd.removeRoute(ifaceName, r);
+ mNetd.removeRoute(netId, r);
} catch (Exception e) {
// never crash - catch them all
if (DBG) loge("Exception trying to remove a route: " + e);
@@ -2718,26 +2647,30 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return routesChanged;
}
- /**
+ /**
* Reads the network specific MTU size from reources.
* and set it on it's iface.
*/
- private void updateMtuSizeSettings(NetworkStateTracker nt) {
- final String iface = nt.getLinkProperties().getInterfaceName();
- final int mtu = nt.getLinkProperties().getMtu();
-
- if (mtu < 68 || mtu > 10000) {
- loge("Unexpected mtu value: " + nt);
- return;
- }
-
- try {
- if (VDBG) log("Setting MTU size: " + iface + ", " + mtu);
- mNetd.setMtu(iface, mtu);
- } catch (Exception e) {
- Slog.e(TAG, "exception in setMtu()" + e);
- }
- }
+ private void updateMtu(LinkProperties newLp, LinkProperties oldLp) {
+ final String iface = newLp.getInterfaceName();
+ final int mtu = newLp.getMtu();
+ if (oldLp != null && newLp.isIdenticalMtu(oldLp)) {
+ if (VDBG) log("identical MTU - not setting");
+ return;
+ }
+
+ if (mtu < 68 || mtu > 10000) {
+ loge("Unexpected mtu value: " + mtu + ", " + iface);
+ return;
+ }
+
+ try {
+ if (VDBG) log("Setting MTU size: " + iface + ", " + mtu);
+ mNetd.setMtu(iface, mtu);
+ } catch (Exception e) {
+ Slog.e(TAG, "exception in setMtu()" + e);
+ }
+ }
/**
* Reads the network specific TCP buffer sizes from SystemProperties
@@ -2822,7 +2755,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (p == null) continue;
if (mNetRequestersPids[i].contains(myPid)) {
try {
- mNetd.setDnsInterfaceForPid(p.getInterfaceName(), pid);
+ // TODO: Reimplement this via local variable in bionic.
+ // mNetd.setDnsNetworkForPid(nt.getNetwork().netId, pid);
} catch (Exception e) {
Slog.e(TAG, "exception reasseses pid dns: " + e);
}
@@ -2832,7 +2766,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
// nothing found - delete
try {
- mNetd.clearDnsInterfaceForPid(pid);
+ // TODO: Reimplement this via local variable in bionic.
+ // mNetd.clearDnsNetworkForPid(pid);
} catch (Exception e) {
Slog.e(TAG, "exception clear interface from pid: " + e);
}
@@ -2857,8 +2792,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
// Caller must grab mDnsLock.
- private void updateDnsLocked(String network, String iface,
- Collection<InetAddress> dnses, String domains, boolean defaultDns) {
+ private void updateDnsLocked(String network, int netId,
+ Collection<InetAddress> dnses, String domains) {
int last = 0;
if (dnses.size() == 0 && mDefaultDns != null) {
dnses = new ArrayList();
@@ -2869,10 +2804,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
try {
- mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses), domains);
- if (defaultDns) {
- mNetd.setDefaultInterfaceForDns(iface);
- }
+ mNetd.setDnsServersForNetwork(netId, NetworkUtils.makeStrings(dnses), domains);
for (InetAddress dns : dnses) {
++last;
@@ -2897,14 +2829,15 @@ public class ConnectivityService extends IConnectivityManager.Stub {
LinkProperties p = nt.getLinkProperties();
if (p == null) return;
Collection<InetAddress> dnses = p.getDnses();
+ int netId = nt.getNetwork().netId;
if (mNetConfigs[netType].isDefault()) {
String network = nt.getNetworkInfo().getTypeName();
synchronized (mDnsLock) {
- updateDnsLocked(network, p.getInterfaceName(), dnses, p.getDomains(), true);
+ updateDnsLocked(network, netId, dnses, p.getDomains());
}
} else {
try {
- mNetd.setDnsServersForInterface(p.getInterfaceName(),
+ mNetd.setDnsServersForNetwork(netId,
NetworkUtils.makeStrings(dnses), p.getDomains());
} catch (Exception e) {
if (DBG) loge("exception setting dns servers: " + e);
@@ -2913,7 +2846,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
List<Integer> pids = mNetRequestersPids[netType];
for (Integer pid : pids) {
try {
- mNetd.setDnsInterfaceForPid(p.getInterfaceName(), pid);
+ // TODO: Reimplement this via local variable in bionic.
+ // mNetd.setDnsNetworkForPid(netId, pid);
} catch (Exception e) {
Slog.e(TAG, "exception setting interface for pid: " + e);
}
@@ -2955,41 +2889,39 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return;
}
- // TODO: add locking to get atomic snapshot
- pw.println();
- for (int i = 0; i < mNetTrackers.length; i++) {
- final NetworkStateTracker nst = mNetTrackers[i];
- if (nst != null) {
- pw.println("NetworkStateTracker for " + getNetworkTypeName(i) + ":");
- pw.increaseIndent();
- if (nst.getNetworkInfo().isConnected()) {
- pw.println("Active network: " + nst.getNetworkInfo().
- getTypeName());
- }
- pw.println(nst.getNetworkInfo());
- pw.println(nst.getLinkProperties());
- pw.println(nst);
- pw.println();
- pw.decreaseIndent();
- }
+ NetworkAgentInfo defaultNai = mNetworkForRequestId.get(mDefaultRequest.requestId);
+ pw.print("Active default network: ");
+ if (defaultNai == null) {
+ pw.println("none");
+ } else {
+ pw.println(defaultNai.network.netId);
}
+ pw.println();
- pw.println("Network Requester Pids:");
+ pw.println("Current Networks:");
pw.increaseIndent();
- for (int net : mPriorityList) {
- String pidString = net + ": ";
- for (Integer pid : mNetRequestersPids[net]) {
- pidString = pidString + pid.toString() + ", ";
+ for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
+ pw.println(nai.toString());
+ pw.increaseIndent();
+ pw.println("Requests:");
+ pw.increaseIndent();
+ for (int i = 0; i < nai.networkRequests.size(); i++) {
+ pw.println(nai.networkRequests.valueAt(i).toString());
}
- pw.println(pidString);
+ pw.decreaseIndent();
+ pw.println("Lingered:");
+ pw.increaseIndent();
+ for (NetworkRequest nr : nai.networkLingered) pw.println(nr.toString());
+ pw.decreaseIndent();
+ pw.decreaseIndent();
}
- pw.println();
pw.decreaseIndent();
+ pw.println();
- pw.println("FeatureUsers:");
+ pw.println("Network Requests:");
pw.increaseIndent();
- for (Object requester : mFeatureUsers) {
- pw.println(requester.toString());
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ pw.println(nri.toString());
}
pw.println();
pw.decreaseIndent();
@@ -3024,6 +2956,59 @@ public class ConnectivityService extends IConnectivityManager.Stub {
public void handleMessage(Message msg) {
NetworkInfo info;
switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
+ handleAsyncChannelHalfConnect(msg);
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
+ NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+ if (nai != null) nai.asyncChannel.disconnect();
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+ handleAsyncChannelDisconnected(msg);
+ break;
+ }
+ case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
+ NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+ if (nai == null) {
+ loge("EVENT_NETWORK_CAPABILITIES_CHANGED from unknown NetworkAgent");
+ } else {
+ updateCapabilities(nai, (NetworkCapabilities)msg.obj);
+ }
+ break;
+ }
+ case NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED: {
+ NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+ if (nai == null) {
+ loge("NetworkAgent not found for EVENT_NETWORK_PROPERTIES_CHANGED");
+ } else {
+ LinkProperties oldLp = nai.linkProperties;
+ nai.linkProperties = (LinkProperties)msg.obj;
+ updateLinkProperties(nai, oldLp);
+ }
+ break;
+ }
+ case NetworkAgent.EVENT_NETWORK_INFO_CHANGED: {
+ NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+ if (nai == null) {
+ loge("EVENT_NETWORK_INFO_CHANGED from unknown NetworkAgent");
+ break;
+ }
+ info = (NetworkInfo) msg.obj;
+ updateNetworkInfo(nai, info);
+ break;
+ }
+ case NetworkMonitor.EVENT_NETWORK_VALIDATED: {
+ NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj;
+ handleConnectionValidated(nai);
+ break;
+ }
+ case NetworkMonitor.EVENT_NETWORK_LINGER_COMPLETE: {
+ NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj;
+ handleLingerComplete(nai);
+ break;
+ }
case NetworkStateTracker.EVENT_STATE_CHANGED: {
info = (NetworkInfo) msg.obj;
NetworkInfo.State state = info.getState();
@@ -3056,13 +3041,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
EventLogTags.writeConnectivityStateChanged(
info.getType(), info.getSubtype(), info.getDetailedState().ordinal());
- if (info.getDetailedState() ==
- NetworkInfo.DetailedState.FAILED) {
- handleConnectionFailure(info);
- } else if (info.getDetailedState() ==
- DetailedState.CAPTIVE_PORTAL_CHECK) {
- handleCaptivePortalTrackerCheck(info);
- } else if (info.isConnectedToProvisioningNetwork()) {
+ if (info.isConnectedToProvisioningNetwork()) {
/**
* TODO: Create ConnectivityManager.TYPE_MOBILE_PROVISIONING
* for now its an in between network, its a network that
@@ -3073,7 +3052,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* to the link that may have incorrectly setup by the lower
* levels.
*/
- LinkProperties lp = getLinkProperties(info.getType());
+ LinkProperties lp = getLinkPropertiesForTypeInternal(info.getType());
if (DBG) {
log("EVENT_STATE_CHANGED: connected to provisioning network, lp=" + lp);
}
@@ -3083,21 +3062,13 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// connection will fail until the provisioning network
// is enabled.
for (RouteInfo r : lp.getRoutes()) {
- removeRoute(lp, r, TO_DEFAULT_TABLE);
+ removeRoute(lp, r, TO_DEFAULT_TABLE,
+ mNetTrackers[info.getType()].getNetwork().netId);
}
} else if (state == NetworkInfo.State.DISCONNECTED) {
- handleDisconnect(info);
} else if (state == NetworkInfo.State.SUSPENDED) {
- // TODO: need to think this over.
- // the logic here is, handle SUSPENDED the same as
- // DISCONNECTED. The only difference being we are
- // broadcasting an intent with NetworkInfo that's
- // suspended. This allows the applications an
- // opportunity to handle DISCONNECTED and SUSPENDED
- // differently, or not.
- handleDisconnect(info);
} else if (state == NetworkInfo.State.CONNECTED) {
- handleConnect(info);
+ // handleConnect(info);
}
if (mLockdownTracker != null) {
mLockdownTracker.onNetworkInfoChanged(info);
@@ -3109,7 +3080,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// TODO: Temporary allowing network configuration
// change not resetting sockets.
// @see bug/4455071
- handleConnectivityChange(info.getType(), false);
+ handleConnectivityChange(info.getType(), mCurrentLinkProperties[info.getType()],
+ false);
break;
}
case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: {
@@ -3122,6 +3094,174 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
+ private void handleAsyncChannelHalfConnect(Message msg) {
+ AsyncChannel ac = (AsyncChannel) msg.obj;
+ if (mNetworkFactories.contains(ac)) {
+ if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ if (VDBG) log("NetworkFactory connected");
+ // A network factory has connected. Send it all current NetworkRequests.
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
+ ac.sendMessage(NetworkFactoryProtocol.CMD_REQUEST_NETWORK,
+ (nai != null ? nai.currentScore : 0), 0, nri.request);
+ }
+ } else {
+ loge("Error connecting NetworkFactory");
+ mNetworkFactories.remove(ac);
+ }
+ } else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {
+ if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ if (VDBG) log("NetworkAgent connected");
+ // A network agent has requested a connection. Establish the connection.
+ mNetworkAgentInfos.get(msg.replyTo).asyncChannel.
+ sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+ } else {
+ loge("Error connecting NetworkAgent");
+ NetworkAgentInfo nai = mNetworkAgentInfos.remove(msg.replyTo);
+ try {
+ mNetworkAgentInfoForType[nai.networkInfo.getType()].remove(nai);
+ } catch (NullPointerException e) {}
+ if (nai != null) {
+ mNetworkForNetId.remove(nai.network.netId);
+ }
+ }
+ }
+ }
+ private void handleAsyncChannelDisconnected(Message msg) {
+ NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+ if (nai != null) {
+ if (DBG) {
+ log(nai.name() + " got DISCONNECTED, was satisfying " + nai.networkRequests.size());
+ }
+ // A network agent has disconnected.
+ // Tell netd to clean up the configuration for this network
+ // (routing rules, DNS, etc).
+ try {
+ mNetd.removeNetwork(nai.network.netId);
+ } catch (Exception e) {
+ loge("Exception removing network: " + e);
+ }
+ notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
+ nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
+ mNetworkAgentInfos.remove(msg.replyTo);
+ updateClat(null, nai.linkProperties, nai);
+ try {
+ mNetworkAgentInfoForType[nai.networkInfo.getType()].remove(nai);
+ } catch (NullPointerException e) {}
+
+ mNetworkForNetId.remove(nai.network.netId);
+ // Since we've lost the network, go through all the requests that
+ // it was satisfying and see if any other factory can satisfy them.
+ final ArrayList<NetworkAgentInfo> toActivate = new ArrayList<NetworkAgentInfo>();
+ for (int i = 0; i < nai.networkRequests.size(); i++) {
+ NetworkRequest request = nai.networkRequests.valueAt(i);
+ NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId);
+ if (VDBG) {
+ log(" checking request " + request + ", currentNetwork = " +
+ currentNetwork != null ? currentNetwork.name() : "null");
+ }
+ if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
+ mNetworkForRequestId.remove(request.requestId);
+ sendUpdatedScoreToFactories(request, 0);
+ NetworkAgentInfo alternative = null;
+ for (Map.Entry entry : mNetworkAgentInfos.entrySet()) {
+ NetworkAgentInfo existing = (NetworkAgentInfo)entry.getValue();
+ if (existing.networkInfo.isConnected() &&
+ request.networkCapabilities.satisfiedByNetworkCapabilities(
+ existing.networkCapabilities) &&
+ (alternative == null ||
+ alternative.currentScore < existing.currentScore)) {
+ alternative = existing;
+ }
+ }
+ if (alternative != null && !toActivate.contains(alternative)) {
+ toActivate.add(alternative);
+ }
+ }
+ }
+ if (nai.networkRequests.get(mDefaultRequest.requestId) != null) {
+ removeDataActivityTracking(nai);
+ mActiveDefaultNetwork = ConnectivityManager.TYPE_NONE;
+ }
+ for (NetworkAgentInfo networkToActivate : toActivate) {
+ networkToActivate.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+ }
+ }
+ }
+
+ private void handleRegisterNetworkRequest(Message msg) {
+ final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
+ final NetworkCapabilities newCap = nri.request.networkCapabilities;
+ int score = 0;
+
+ // Check for the best currently alive network that satisfies this request
+ NetworkAgentInfo bestNetwork = null;
+ for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {
+ if (VDBG) log("handleRegisterNetworkRequest checking " + network.name());
+ if (newCap.satisfiedByNetworkCapabilities(network.networkCapabilities)) {
+ if (VDBG) log("apparently satisfied. currentScore=" + network.currentScore);
+ if ((bestNetwork == null) || bestNetwork.currentScore < network.currentScore) {
+ bestNetwork = network;
+ }
+ }
+ }
+ if (bestNetwork != null) {
+ if (VDBG) log("using " + bestNetwork.name());
+ bestNetwork.networkRequests.put(nri.request.requestId, nri.request);
+ notifyNetworkCallback(bestNetwork, nri);
+ score = bestNetwork.currentScore;
+ }
+ mNetworkRequests.put(nri.request, nri);
+ if (msg.what == EVENT_REGISTER_NETWORK_REQUEST) {
+ if (DBG) log("sending new NetworkRequest to factories");
+ for (AsyncChannel ac : mNetworkFactories) {
+ ac.sendMessage(NetworkFactoryProtocol.CMD_REQUEST_NETWORK, score, 0, nri.request);
+ }
+ }
+ }
+
+ private void handleReleaseNetworkRequest(NetworkRequest request) {
+ if (DBG) log("releasing NetworkRequest " + request);
+ NetworkRequestInfo nri = mNetworkRequests.remove(request);
+ if (nri != null) {
+ // tell the network currently servicing this that it's no longer interested
+ NetworkAgentInfo affectedNetwork = mNetworkForRequestId.get(nri.request.requestId);
+ if (affectedNetwork != null) {
+ mNetworkForRequestId.remove(nri.request.requestId);
+ affectedNetwork.networkRequests.remove(nri.request.requestId);
+ if (VDBG) {
+ log(" Removing from current network " + affectedNetwork.name() + ", leaving " +
+ affectedNetwork.networkRequests.size() + " requests.");
+ }
+ }
+
+ if (nri.isRequest) {
+ for (AsyncChannel factory : mNetworkFactories) {
+ factory.sendMessage(NetworkFactoryProtocol.CMD_CANCEL_REQUEST, nri.request);
+ }
+
+ if (affectedNetwork != null) {
+ // check if this network still has live requests - otherwise, tear down
+ // TODO - probably push this to the NF/NA
+ boolean keep = false;
+ for (int i = 0; i < affectedNetwork.networkRequests.size(); i++) {
+ NetworkRequest r = affectedNetwork.networkRequests.valueAt(i);
+ if (mNetworkRequests.get(r).isRequest) {
+ keep = true;
+ break;
+ }
+ }
+ if (keep == false) {
+ if (DBG) log("no live requests for " + affectedNetwork.name() +
+ "; disconnecting");
+ affectedNetwork.asyncChannel.disconnect();
+ }
+ }
+ }
+ callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED);
+ }
+ }
+
private class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
@@ -3162,11 +3302,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
handleInetConditionHoldEnd(netType, sequence);
break;
}
- case EVENT_SET_NETWORK_PREFERENCE: {
- int preference = msg.arg1;
- handleSetNetworkPreference(preference);
- break;
- }
case EVENT_SET_MOBILE_DATA: {
boolean enabled = (msg.arg1 == ENABLED);
handleSetMobileData(enabled);
@@ -3217,7 +3352,24 @@ public class ConnectivityService extends IConnectivityManager.Stub {
break;
}
case EVENT_PROXY_HAS_CHANGED: {
- handleApplyDefaultProxy((ProxyProperties)msg.obj);
+ handleApplyDefaultProxy((ProxyInfo)msg.obj);
+ break;
+ }
+ case EVENT_REGISTER_NETWORK_FACTORY: {
+ handleRegisterNetworkFactory((Messenger)msg.obj);
+ break;
+ }
+ case EVENT_REGISTER_NETWORK_AGENT: {
+ handleRegisterNetworkAgent((NetworkAgentInfo)msg.obj);
+ break;
+ }
+ case EVENT_REGISTER_NETWORK_REQUEST:
+ case EVENT_REGISTER_NETWORK_LISTENER: {
+ handleRegisterNetworkRequest(msg);
+ break;
+ }
+ case EVENT_RELEASE_NETWORK_REQUEST: {
+ handleReleaseNetworkRequest((NetworkRequest) msg.obj);
break;
}
}
@@ -3366,6 +3518,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
EVENT_INET_CONDITION_CHANGE, networkType, percentage));
}
+ public void reportBadNetwork(Network network) {
+ //TODO
+ }
+
private void handleInetConditionChange(int netType, int condition) {
if (mActiveDefaultNetwork == -1) {
if (DBG) log("handleInetConditionChange: no active default network - ignore");
@@ -3425,7 +3581,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// if (DBG) log("no change in condition - aborting");
// return;
//}
- NetworkInfo networkInfo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
+ NetworkInfo networkInfo = getNetworkInfoForType(mActiveDefaultNetwork);
if (networkInfo.isConnected() == false) {
if (DBG) log("handleInetConditionHoldEnd: default network not connected - ignoring");
return;
@@ -3435,19 +3591,19 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return;
}
- public ProxyProperties getProxy() {
+ public ProxyInfo getProxy() {
// this information is already available as a world read/writable jvm property
// so this API change wouldn't have a benifit. It also breaks the passing
// of proxy info to all the JVMs.
// enforceAccessPermission();
synchronized (mProxyLock) {
- ProxyProperties ret = mGlobalProxy;
+ ProxyInfo ret = mGlobalProxy;
if ((ret == null) && !mDefaultProxyDisabled) ret = mDefaultProxy;
return ret;
}
}
- public void setGlobalProxy(ProxyProperties proxyProperties) {
+ public void setGlobalProxy(ProxyInfo proxyProperties) {
enforceConnectivityInternalPermission();
synchronized (mProxyLock) {
@@ -3460,18 +3616,18 @@ public class ConnectivityService extends IConnectivityManager.Stub {
String exclList = "";
String pacFileUrl = "";
if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) ||
- !TextUtils.isEmpty(proxyProperties.getPacFileUrl()))) {
+ (proxyProperties.getPacFileUrl() != null))) {
if (!proxyProperties.isValid()) {
if (DBG)
log("Invalid proxy properties, ignoring: " + proxyProperties.toString());
return;
}
- mGlobalProxy = new ProxyProperties(proxyProperties);
+ mGlobalProxy = new ProxyInfo(proxyProperties);
host = mGlobalProxy.getHost();
port = mGlobalProxy.getPort();
- exclList = mGlobalProxy.getExclusionList();
+ exclList = mGlobalProxy.getExclusionListAsString();
if (proxyProperties.getPacFileUrl() != null) {
- pacFileUrl = proxyProperties.getPacFileUrl();
+ pacFileUrl = proxyProperties.getPacFileUrl().toString();
}
} else {
mGlobalProxy = null;
@@ -3503,11 +3659,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
String pacFileUrl = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC);
if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(pacFileUrl)) {
- ProxyProperties proxyProperties;
+ ProxyInfo proxyProperties;
if (!TextUtils.isEmpty(pacFileUrl)) {
- proxyProperties = new ProxyProperties(pacFileUrl);
+ proxyProperties = new ProxyInfo(pacFileUrl);
} else {
- proxyProperties = new ProxyProperties(host, port, exclList);
+ proxyProperties = new ProxyInfo(host, port, exclList);
}
if (!proxyProperties.isValid()) {
if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString());
@@ -3520,7 +3676,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
- public ProxyProperties getGlobalProxy() {
+ public ProxyInfo getGlobalProxy() {
// this information is already available as a world read/writable jvm property
// so this API change wouldn't have a benifit. It also breaks the passing
// of proxy info to all the JVMs.
@@ -3530,9 +3686,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
- private void handleApplyDefaultProxy(ProxyProperties proxy) {
+ private void handleApplyDefaultProxy(ProxyInfo proxy) {
if (proxy != null && TextUtils.isEmpty(proxy.getHost())
- && TextUtils.isEmpty(proxy.getPacFileUrl())) {
+ && (proxy.getPacFileUrl() == null)) {
proxy = null;
}
synchronized (mProxyLock) {
@@ -3542,6 +3698,18 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (DBG) log("Invalid proxy properties, ignoring: " + proxy.toString());
return;
}
+
+ // This call could be coming from the PacManager, containing the port of the local
+ // proxy. If this new proxy matches the global proxy then copy this proxy to the
+ // global (to get the correct local port), and send a broadcast.
+ // TODO: Switch PacManager to have its own message to send back rather than
+ // reusing EVENT_HAS_CHANGED_PROXY and this call to handleApplyDefaultProxy.
+ if ((mGlobalProxy != null) && (proxy != null) && (proxy.getPacFileUrl() != null)
+ && proxy.getPacFileUrl().equals(mGlobalProxy.getPacFileUrl())) {
+ mGlobalProxy = proxy;
+ sendProxyBroadcast(mGlobalProxy);
+ return;
+ }
mDefaultProxy = proxy;
if (mGlobalProxy != null) return;
@@ -3569,13 +3737,13 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return;
}
}
- ProxyProperties p = new ProxyProperties(data[0], proxyPort, "");
+ ProxyInfo p = new ProxyInfo(data[0], proxyPort, "");
setGlobalProxy(p);
}
}
- private void sendProxyBroadcast(ProxyProperties proxy) {
- if (proxy == null) proxy = new ProxyProperties("", 0, "");
+ private void sendProxyBroadcast(ProxyInfo proxy) {
+ if (proxy == null) proxy = new ProxyInfo("", 0, "");
if (mPacManager.setCurrentProxyScriptUrl(proxy)) return;
if (DBG) log("sending Proxy Broadcast for " + proxy);
Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
@@ -3834,7 +4002,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// Apply DNS changes.
synchronized (mDnsLock) {
- updateDnsLocked("VPN", iface, addresses, domains, false);
+ // TODO: Re-enable this when the netId of the VPN is known.
+ // updateDnsLocked("VPN", netId, addresses, domains);
}
// Temporarily disable the default proxy (not global).
@@ -3902,21 +4071,21 @@ public class ConnectivityService extends IConnectivityManager.Stub {
public void addUidForwarding(String interfaze, int uidStart, int uidEnd,
boolean forwardDns) {
- try {
- mNetd.setUidRangeRoute(interfaze,uidStart, uidEnd);
- if (forwardDns) mNetd.setDnsInterfaceForUidRange(interfaze, uidStart, uidEnd);
- } catch (RemoteException e) {
- }
+ // TODO: Re-enable this when the netId of the VPN is known.
+ // try {
+ // mNetd.setUidRangeRoute(netId, uidStart, uidEnd, forwardDns);
+ // } catch (RemoteException e) {
+ // }
}
public void clearUidForwarding(String interfaze, int uidStart, int uidEnd,
boolean forwardDns) {
- try {
- mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd);
- if (forwardDns) mNetd.clearDnsInterfaceForUidRange(interfaze, uidStart, uidEnd);
- } catch (RemoteException e) {
- }
+ // TODO: Re-enable this when the netId of the VPN is known.
+ // try {
+ // mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd);
+ // } catch (RemoteException e) {
+ // }
}
}
@@ -4177,7 +4346,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
CheckMp.Params params =
new CheckMp.Params(checkMp.getDefaultUrl(), timeOutMs, cb);
if (DBG) log("checkMobileProvisioning: params=" + params);
- checkMp.execute(params);
+ // TODO: Reenable when calls to the now defunct
+ // MobileDataStateTracker.isProvisioningNetwork() are removed.
+ // This code should be moved to the Telephony code.
+ // checkMp.execute(params);
} finally {
Binder.restoreCallingIdentity(token);
if (DBG) log("checkMobileProvisioning: X");
@@ -4420,7 +4592,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
log("isMobileOk: addresses=" + inetAddressesToString(addresses));
// Get the type of addresses supported by this link
- LinkProperties lp = mCs.getLinkProperties(
+ LinkProperties lp = mCs.getLinkPropertiesForTypeInternal(
ConnectivityManager.TYPE_MOBILE_HIPRI);
boolean linkHasIpv4 = lp.hasIPv4Address();
boolean linkHasIpv6 = lp.hasIPv6Address();
@@ -4629,10 +4801,13 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* @param seconds
*/
private static void sleep(int seconds) {
- try {
- Thread.sleep(seconds * 1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
+ long stopTime = System.nanoTime() + (seconds * 1000000000);
+ long sleepTime;
+ while ((sleepTime = stopTime - System.nanoTime()) > 0) {
+ try {
+ Thread.sleep(sleepTime / 1000000);
+ } catch (InterruptedException ignored) {
+ }
}
}
@@ -4662,22 +4837,34 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// otherwise launch browser with the intent directly.
if (mIsProvisioningNetwork.get()) {
if (DBG) log("handleMobileProvisioningAction: on prov network enable then launch");
- mIsStartingProvisioning.set(true);
- MobileDataStateTracker mdst = (MobileDataStateTracker)
- mNetTrackers[ConnectivityManager.TYPE_MOBILE];
- mdst.setEnableFailFastMobileData(DctConstants.ENABLED);
- mdst.enableMobileProvisioning(url);
+// mIsStartingProvisioning.set(true);
+// MobileDataStateTracker mdst = (MobileDataStateTracker)
+// mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+// mdst.setEnableFailFastMobileData(DctConstants.ENABLED);
+// mdst.enableMobileProvisioning(url);
} else {
- if (DBG) log("handleMobileProvisioningAction: not prov network, launch browser directly");
- Intent newIntent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
- Intent.CATEGORY_APP_BROWSER);
- newIntent.setData(Uri.parse(url));
- newIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
- Intent.FLAG_ACTIVITY_NEW_TASK);
- try {
- mContext.startActivity(newIntent);
- } catch (ActivityNotFoundException e) {
- loge("handleMobileProvisioningAction: startActivity failed" + e);
+ if (DBG) log("handleMobileProvisioningAction: not prov network");
+ // Check for apps that can handle provisioning first
+ Intent provisioningIntent = new Intent(TelephonyIntents.ACTION_CARRIER_SETUP);
+ provisioningIntent.addCategory(TelephonyIntents.CATEGORY_MCCMNC_PREFIX
+ + mTelephonyManager.getSimOperator());
+ if (mContext.getPackageManager().resolveActivity(provisioningIntent, 0 /* flags */)
+ != null) {
+ provisioningIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(provisioningIntent);
+ } else {
+ // If no apps exist, use standard URL ACTION_VIEW method
+ Intent newIntent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
+ Intent.CATEGORY_APP_BROWSER);
+ newIntent.setData(Uri.parse(url));
+ newIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ mContext.startActivity(newIntent);
+ } catch (ActivityNotFoundException e) {
+ loge("handleMobileProvisioningAction: startActivity failed" + e);
+ }
}
}
}
@@ -4950,7 +5137,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
@Override
public LinkQualityInfo getLinkQualityInfo(int networkType) {
enforceAccessPermission();
- if (isNetworkTypeValid(networkType)) {
+ if (isNetworkTypeValid(networkType) && mNetTrackers[networkType] != null) {
return mNetTrackers[networkType].getLinkQualityInfo();
} else {
return null;
@@ -4960,7 +5147,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
@Override
public LinkQualityInfo getActiveLinkQualityInfo() {
enforceAccessPermission();
- if (isNetworkTypeValid(mActiveDefaultNetwork)) {
+ if (isNetworkTypeValid(mActiveDefaultNetwork) &&
+ mNetTrackers[mActiveDefaultNetwork] != null) {
return mNetTrackers[mActiveDefaultNetwork].getLinkQualityInfo();
} else {
return null;
@@ -5033,4 +5221,639 @@ public class ConnectivityService extends IConnectivityManager.Stub {
long wakeupTime = SystemClock.elapsedRealtime() + timeoutInMilliseconds;
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeupTime, intent);
}
+
+ private final ArrayList<AsyncChannel> mNetworkFactories = new ArrayList<AsyncChannel>();
+ private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests =
+ new HashMap<NetworkRequest, NetworkRequestInfo>();
+
+
+ private class NetworkRequestInfo implements IBinder.DeathRecipient {
+ static final boolean REQUEST = true;
+ static final boolean LISTEN = false;
+
+ final NetworkRequest request;
+ IBinder mBinder;
+ final int mPid;
+ final int mUid;
+ final Messenger messenger;
+ final boolean isRequest;
+
+ NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder, boolean isRequest) {
+ super();
+ messenger = m;
+ request = r;
+ mBinder = binder;
+ mPid = getCallingPid();
+ mUid = getCallingUid();
+ this.isRequest = isRequest;
+
+ try {
+ mBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ binderDied();
+ }
+ }
+
+ void unlinkDeathRecipient() {
+ mBinder.unlinkToDeath(this, 0);
+ }
+
+ public void binderDied() {
+ log("ConnectivityService NetworkRequestInfo binderDied(" +
+ request + ", " + mBinder + ")");
+ releaseNetworkRequest(request);
+ }
+ }
+
+ @Override
+ public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
+ Messenger messenger, int timeoutSec, IBinder binder) {
+ if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ == false) {
+ enforceConnectivityInternalPermission();
+ } else {
+ enforceChangePermission();
+ }
+
+ if (timeoutSec < 0 || timeoutSec > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_SEC) {
+ throw new IllegalArgumentException("Bad timeout specified");
+ }
+ NetworkRequest networkRequest = new NetworkRequest(new NetworkCapabilities(
+ networkCapabilities), false, nextNetworkRequestId());
+ if (DBG) log("requestNetwork for " + networkRequest);
+ NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder,
+ NetworkRequestInfo.REQUEST);
+
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
+ if (timeoutSec > 0) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NETWORK_REQUEST,
+ nri), timeoutSec * 1000);
+ }
+ return networkRequest;
+ }
+
+ @Override
+ public NetworkRequest pendingRequestForNetwork(NetworkCapabilities networkCapabilities,
+ PendingIntent operation) {
+ // TODO
+ return null;
+ }
+
+ @Override
+ public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities,
+ Messenger messenger, IBinder binder) {
+ enforceAccessPermission();
+
+ NetworkRequest networkRequest = new NetworkRequest(new NetworkCapabilities(
+ networkCapabilities), false, nextNetworkRequestId());
+ if (DBG) log("listenForNetwork for " + networkRequest);
+ NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder,
+ NetworkRequestInfo.LISTEN);
+
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
+ return networkRequest;
+ }
+
+ @Override
+ public void pendingListenForNetwork(NetworkCapabilities networkCapabilities,
+ PendingIntent operation) {
+ }
+
+ @Override
+ public void releaseNetworkRequest(NetworkRequest networkRequest) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST,
+ networkRequest));
+ }
+
+ @Override
+ public void registerNetworkFactory(Messenger messenger) {
+ enforceConnectivityInternalPermission();
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, messenger));
+ }
+
+ private void handleRegisterNetworkFactory(Messenger messenger) {
+ if (VDBG) log("Got NetworkFactory Messenger");
+ AsyncChannel ac = new AsyncChannel();
+ mNetworkFactories.add(ac);
+ ac.connect(mContext, mTrackerHandler, messenger);
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ if (nri.isRequest) {
+ int score = 0;
+ NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
+ if (currentNetwork != null) score = currentNetwork.currentScore;
+ ac.sendMessage(NetworkFactoryProtocol.CMD_REQUEST_NETWORK, score, 0, nri.request);
+ }
+ }
+ }
+
+ /**
+ * NetworkAgentInfo supporting a request by requestId.
+ * These have already been vetted (their Capabilities satisfy the request)
+ * and the are the highest scored network available.
+ * the are keyed off the Requests requestId.
+ */
+ private final SparseArray<NetworkAgentInfo> mNetworkForRequestId =
+ new SparseArray<NetworkAgentInfo>();
+
+ private final SparseArray<NetworkAgentInfo> mNetworkForNetId =
+ new SparseArray<NetworkAgentInfo>();
+
+ // NetworkAgentInfo keyed off its connecting messenger
+ // TODO - eval if we can reduce the number of lists/hashmaps/sparsearrays
+ private final HashMap<Messenger, NetworkAgentInfo> mNetworkAgentInfos =
+ new HashMap<Messenger, NetworkAgentInfo>();
+
+ private final NetworkRequest mDefaultRequest;
+
+ public void registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
+ LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
+ int currentScore) {
+ enforceConnectivityInternalPermission();
+
+ NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), nextNetId(),
+ new NetworkInfo(networkInfo), new LinkProperties(linkProperties),
+ new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler);
+
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
+ }
+
+ private void handleRegisterNetworkAgent(NetworkAgentInfo na) {
+ if (VDBG) log("Got NetworkAgent Messenger");
+ mNetworkAgentInfos.put(na.messenger, na);
+ try {
+ mNetworkAgentInfoForType[na.networkInfo.getType()].add(na);
+ } catch (NullPointerException e) {
+ loge("registered NetworkAgent for unsupported type: " + na);
+ }
+ mNetworkForNetId.put(na.network.netId, na);
+ na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger);
+ NetworkInfo networkInfo = na.networkInfo;
+ na.networkInfo = null;
+ updateNetworkInfo(na, networkInfo);
+ }
+
+ private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties oldLp) {
+ LinkProperties newLp = networkAgent.linkProperties;
+ int netId = networkAgent.network.netId;
+
+ updateInterfaces(newLp, oldLp, netId);
+ updateMtu(newLp, oldLp);
+ // TODO - figure out what to do for clat
+// for (LinkProperties lp : newLp.getStackedLinks()) {
+// updateMtu(lp, null);
+// }
+ updateRoutes(newLp, oldLp, netId);
+ updateDnses(newLp, oldLp, netId);
+ updateClat(newLp, oldLp, networkAgent);
+ }
+
+ private void updateClat(LinkProperties newLp, LinkProperties oldLp, NetworkAgentInfo na) {
+ // Update 464xlat state.
+ if (mClat.requiresClat(na)) {
+
+ // If the connection was previously using clat, but is not using it now, stop the clat
+ // daemon. Normally, this happens automatically when the connection disconnects, but if
+ // the disconnect is not reported, or if the connection's LinkProperties changed for
+ // some other reason (e.g., handoff changes the IP addresses on the link), it would
+ // still be running. If it's not running, then stopping it is a no-op.
+ if (Nat464Xlat.isRunningClat(oldLp) && !Nat464Xlat.isRunningClat(newLp)) {
+ mClat.stopClat();
+ }
+ // If the link requires clat to be running, then start the daemon now.
+ if (newLp != null && na.networkInfo.isConnected()) {
+ mClat.startClat(na);
+ } else {
+ mClat.stopClat();
+ }
+ }
+ }
+
+ private void updateInterfaces(LinkProperties newLp, LinkProperties oldLp, int netId) {
+ CompareResult<String> interfaceDiff = new CompareResult<String>();
+ if (oldLp != null) {
+ interfaceDiff = oldLp.compareAllInterfaceNames(newLp);
+ } else if (newLp != null) {
+ interfaceDiff.added = newLp.getAllInterfaceNames();
+ }
+ for (String iface : interfaceDiff.added) {
+ try {
+ mNetd.addInterfaceToNetwork(iface, netId);
+ } catch (Exception e) {
+ loge("Exception adding interface: " + e);
+ }
+ }
+ for (String iface : interfaceDiff.removed) {
+ try {
+ mNetd.removeInterfaceFromNetwork(iface, netId);
+ } catch (Exception e) {
+ loge("Exception removing interface: " + e);
+ }
+ }
+ }
+
+ private void updateRoutes(LinkProperties newLp, LinkProperties oldLp, int netId) {
+ CompareResult<RouteInfo> routeDiff = new CompareResult<RouteInfo>();
+ if (oldLp != null) {
+ routeDiff = oldLp.compareAllRoutes(newLp);
+ } else if (newLp != null) {
+ routeDiff.added = newLp.getAllRoutes();
+ }
+
+ // add routes before removing old in case it helps with continuous connectivity
+
+ // do this twice, adding non-nexthop routes first, then routes they are dependent on
+ for (RouteInfo route : routeDiff.added) {
+ if (route.hasGateway()) continue;
+ try {
+ mNetd.addRoute(netId, route);
+ } catch (Exception e) {
+ loge("Exception in addRoute for non-gateway: " + e);
+ }
+ }
+ for (RouteInfo route : routeDiff.added) {
+ if (route.hasGateway() == false) continue;
+ try {
+ mNetd.addRoute(netId, route);
+ } catch (Exception e) {
+ loge("Exception in addRoute for gateway: " + e);
+ }
+ }
+
+ for (RouteInfo route : routeDiff.removed) {
+ try {
+ mNetd.removeRoute(netId, route);
+ } catch (Exception e) {
+ loge("Exception in removeRoute: " + e);
+ }
+ }
+ }
+ private void updateDnses(LinkProperties newLp, LinkProperties oldLp, int netId) {
+ if (oldLp == null || (newLp.isIdenticalDnses(oldLp) == false)) {
+ Collection<InetAddress> dnses = newLp.getDnses();
+ if (dnses.size() == 0 && mDefaultDns != null) {
+ dnses = new ArrayList();
+ dnses.add(mDefaultDns);
+ if (DBG) {
+ loge("no dns provided for netId " + netId + ", so using defaults");
+ }
+ }
+ try {
+ mNetd.setDnsServersForNetwork(netId, NetworkUtils.makeStrings(dnses),
+ newLp.getDomains());
+ } catch (Exception e) {
+ loge("Exception in setDnsServersForNetwork: " + e);
+ }
+ NetworkAgentInfo defaultNai = mNetworkForRequestId.get(mDefaultRequest.requestId);
+ if (defaultNai != null && defaultNai.network.netId == netId) {
+ setDefaultDnsSystemProperties(dnses);
+ }
+ }
+ }
+
+ private void setDefaultDnsSystemProperties(Collection<InetAddress> dnses) {
+ int last = 0;
+ for (InetAddress dns : dnses) {
+ ++last;
+ String key = "net.dns" + last;
+ String value = dns.getHostAddress();
+ SystemProperties.set(key, value);
+ }
+ for (int i = last + 1; i <= mNumDnsEntries; ++i) {
+ String key = "net.dns" + i;
+ SystemProperties.set(key, "");
+ }
+ mNumDnsEntries = last;
+ }
+
+
+ private void updateCapabilities(NetworkAgentInfo networkAgent,
+ NetworkCapabilities networkCapabilities) {
+ // TODO - what else here? Verify still satisfies everybody?
+ // Check if satisfies somebody new? call callbacks?
+ networkAgent.networkCapabilities = networkCapabilities;
+ }
+
+ private void sendUpdatedScoreToFactories(NetworkRequest networkRequest, int score) {
+ if (VDBG) log("sending new Min Network Score(" + score + "): " + networkRequest.toString());
+ for (AsyncChannel ac : mNetworkFactories) {
+ ac.sendMessage(NetworkFactoryProtocol.CMD_REQUEST_NETWORK, score, 0, networkRequest);
+ }
+ }
+
+ private void callCallbackForRequest(NetworkRequestInfo nri,
+ NetworkAgentInfo networkAgent, int notificationType) {
+ if (nri.messenger == null) return; // Default request has no msgr
+ Object o;
+ int a1 = 0;
+ int a2 = 0;
+ switch (notificationType) {
+ case ConnectivityManager.CALLBACK_LOSING:
+ a1 = 30; // TODO - read this from NetworkMonitor
+ // fall through
+ case ConnectivityManager.CALLBACK_PRECHECK:
+ case ConnectivityManager.CALLBACK_AVAILABLE:
+ case ConnectivityManager.CALLBACK_LOST:
+ case ConnectivityManager.CALLBACK_CAP_CHANGED:
+ case ConnectivityManager.CALLBACK_IP_CHANGED: {
+ o = new NetworkRequest(nri.request);
+ a2 = networkAgent.network.netId;
+ break;
+ }
+ case ConnectivityManager.CALLBACK_UNAVAIL:
+ case ConnectivityManager.CALLBACK_RELEASED: {
+ o = new NetworkRequest(nri.request);
+ break;
+ }
+ default: {
+ loge("Unknown notificationType " + notificationType);
+ return;
+ }
+ }
+ Message msg = Message.obtain();
+ msg.arg1 = a1;
+ msg.arg2 = a2;
+ msg.obj = o;
+ msg.what = notificationType;
+ try {
+ if (VDBG) log("sending notification " + notificationType + " for " + nri.request);
+ nri.messenger.send(msg);
+ } catch (RemoteException e) {
+ // may occur naturally in the race of binder death.
+ loge("RemoteException caught trying to send a callback msg for " + nri.request);
+ }
+ }
+
+ private void handleLingerComplete(NetworkAgentInfo oldNetwork) {
+ if (oldNetwork == null) {
+ loge("Unknown NetworkAgentInfo in handleLingerComplete");
+ return;
+ }
+ if (DBG) log("handleLingerComplete for " + oldNetwork.name());
+ if (DBG) {
+ if (oldNetwork.networkRequests.size() != 0) {
+ loge("Dead network still had " + oldNetwork.networkRequests.size() + " requests");
+ }
+ }
+ oldNetwork.asyncChannel.disconnect();
+ }
+
+ private void handleConnectionValidated(NetworkAgentInfo newNetwork) {
+ if (newNetwork == null) {
+ loge("Unknown NetworkAgentInfo in handleConnectionValidated");
+ return;
+ }
+ boolean keep = false;
+ boolean isNewDefault = false;
+ if (DBG) log("handleConnectionValidated for "+newNetwork.name());
+ // check if any NetworkRequest wants this NetworkAgent
+ // first check if it satisfies the NetworkCapabilities
+ ArrayList<NetworkAgentInfo> affectedNetworks = new ArrayList<NetworkAgentInfo>();
+ if (VDBG) log(" new Network has: " + newNetwork.networkCapabilities);
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ if (VDBG) log(" checking if request is satisfied: " + nri.request);
+ if (nri.request.networkCapabilities.satisfiedByNetworkCapabilities(
+ newNetwork.networkCapabilities)) {
+ // next check if it's better than any current network we're using for
+ // this request
+ NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
+ if (VDBG) {
+ log("currentScore = " +
+ (currentNetwork != null ? currentNetwork.currentScore : 0) +
+ ", newScore = " + newNetwork.currentScore);
+ }
+ if (currentNetwork == null ||
+ currentNetwork.currentScore < newNetwork.currentScore) {
+ if (currentNetwork != null) {
+ if (VDBG) log(" accepting network in place of " + currentNetwork.name());
+ currentNetwork.networkRequests.remove(nri.request.requestId);
+ currentNetwork.networkLingered.add(nri.request);
+ affectedNetworks.add(currentNetwork);
+ } else {
+ if (VDBG) log(" accepting network in place of null");
+ }
+ mNetworkForRequestId.put(nri.request.requestId, newNetwork);
+ newNetwork.networkRequests.put(nri.request.requestId, nri.request);
+ keep = true;
+ // TODO - this could get expensive if we have alot of requests for this
+ // network. Think about if there is a way to reduce this. Push
+ // netid->request mapping to each factory?
+ sendUpdatedScoreToFactories(nri.request, newNetwork.currentScore);
+ if (mDefaultRequest.requestId == nri.request.requestId) {
+ isNewDefault = true;
+ updateActiveDefaultNetwork(newNetwork);
+ if (newNetwork.linkProperties != null) {
+ setDefaultDnsSystemProperties(newNetwork.linkProperties.getDnses());
+ } else {
+ setDefaultDnsSystemProperties(new ArrayList<InetAddress>());
+ }
+ }
+ }
+ }
+ }
+ for (NetworkAgentInfo nai : affectedNetworks) {
+ boolean teardown = true;
+ for (int i = 0; i < nai.networkRequests.size(); i++) {
+ NetworkRequest nr = nai.networkRequests.valueAt(i);
+ try {
+ if (mNetworkRequests.get(nr).isRequest) {
+ teardown = false;
+ }
+ } catch (Exception e) {
+ loge("Request " + nr + " not found in mNetworkRequests.");
+ loge(" it came from request list of " + nai.name());
+ }
+ }
+ if (teardown) {
+ nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_LINGER);
+ notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING);
+ } else {
+ // not going to linger, so kill the list of linger networks.. only
+ // notify them of linger if it happens as the result of gaining another,
+ // but if they transition and old network stays up, don't tell them of linger
+ // or very delayed loss
+ nai.networkLingered.clear();
+ if (VDBG) log("Lingered for " + nai.name() + " cleared");
+ }
+ }
+ if (keep) {
+ if (isNewDefault) {
+ if (VDBG) log("Switching to new default network: " + newNetwork);
+ setupDataActivityTracking(newNetwork);
+ try {
+ mNetd.setDefaultNetId(newNetwork.network.netId);
+ } catch (Exception e) {
+ loge("Exception setting default network :" + e);
+ }
+ if (newNetwork.equals(mNetworkForRequestId.get(mDefaultRequest.requestId))) {
+ handleApplyDefaultProxy(newNetwork.linkProperties.getHttpProxy());
+ }
+ synchronized (ConnectivityService.this) {
+ // have a new default network, release the transition wakelock in
+ // a second if it's held. The second pause is to allow apps
+ // to reconnect over the new network
+ if (mNetTransitionWakeLock.isHeld()) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+ mNetTransitionWakeLockSerialNumber, 0),
+ 1000);
+ }
+ }
+
+ // this will cause us to come up initially as unconnected and switching
+ // to connected after our normal pause unless somebody reports us as
+ // really disconnected
+ mDefaultInetConditionPublished = 0;
+ mDefaultConnectionSequence++;
+ mInetConditionChangeInFlight = false;
+ // TODO - read the tcp buffer size config string from somewhere
+ // updateNetworkSettings();
+ }
+ // notify battery stats service about this network
+// try {
+ // TODO
+ //BatteryStatsService.getService().noteNetworkInterfaceType(iface, netType);
+// } catch (RemoteException e) { }
+ notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_AVAILABLE);
+ } else {
+ if (DBG && newNetwork.networkRequests.size() != 0) {
+ loge("tearing down network with live requests:");
+ for (int i=0; i < newNetwork.networkRequests.size(); i++) {
+ loge(" " + newNetwork.networkRequests.valueAt(i));
+ }
+ }
+ if (VDBG) log("Validated network turns out to be unwanted. Tear it down.");
+ newNetwork.asyncChannel.disconnect();
+ }
+ }
+
+
+ private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) {
+ NetworkInfo.State state = newInfo.getState();
+ NetworkInfo oldInfo = networkAgent.networkInfo;
+ networkAgent.networkInfo = newInfo;
+
+ if (oldInfo != null && oldInfo.getState() == state) {
+ if (VDBG) log("ignoring duplicate network state non-change");
+ return;
+ }
+ if (DBG) {
+ log(networkAgent.name() + " EVENT_NETWORK_INFO_CHANGED, going from " +
+ (oldInfo == null ? "null" : oldInfo.getState()) +
+ " to " + state);
+ }
+
+ if (state == NetworkInfo.State.CONNECTED) {
+ // TODO - check if we want it (optimization)
+ try {
+ mNetd.createNetwork(networkAgent.network.netId);
+ } catch (Exception e) {
+ loge("Error creating Network " + networkAgent.network.netId);
+ }
+ updateLinkProperties(networkAgent, null);
+ notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
+ networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+ } else if (state == NetworkInfo.State.DISCONNECTED ||
+ state == NetworkInfo.State.SUSPENDED) {
+ networkAgent.asyncChannel.disconnect();
+ }
+ }
+
+ // notify only this one new request of the current state
+ protected void notifyNetworkCallback(NetworkAgentInfo nai, NetworkRequestInfo nri) {
+ int notifyType = ConnectivityManager.CALLBACK_AVAILABLE;
+ // TODO - read state from monitor to decide what to send.
+// if (nai.networkMonitor.isLingering()) {
+// notifyType = NetworkCallbacks.LOSING;
+// } else if (nai.networkMonitor.isEvaluating()) {
+// notifyType = NetworkCallbacks.callCallbackForRequest(request, nai, notifyType);
+// }
+ if (nri.request.needsBroadcasts) {
+ // TODO
+// sendNetworkBroadcast(nai, notifyType);
+ }
+ callCallbackForRequest(nri, nai, notifyType);
+ }
+
+ protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType) {
+ if (VDBG) log("notifyType " + notifyType + " for " + networkAgent.name());
+ boolean needsBroadcasts = false;
+ for (int i = 0; i < networkAgent.networkRequests.size(); i++) {
+ NetworkRequest nr = networkAgent.networkRequests.valueAt(i);
+ NetworkRequestInfo nri = mNetworkRequests.get(nr);
+ if (VDBG) log(" sending notification for " + nr);
+ if (nr.needsBroadcasts) needsBroadcasts = true;
+ callCallbackForRequest(nri, networkAgent, notifyType);
+ }
+ if (needsBroadcasts) {
+ if (notifyType == ConnectivityManager.CALLBACK_AVAILABLE) {
+ sendConnectedBroadcastDelayed(networkAgent.networkInfo,
+ getConnectivityChangeDelay());
+ } else if (notifyType == ConnectivityManager.CALLBACK_LOST) {
+ NetworkInfo info = new NetworkInfo(networkAgent.networkInfo);
+ Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
+ if (info.isFailover()) {
+ intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
+ networkAgent.networkInfo.setFailover(false);
+ }
+ if (info.getReason() != null) {
+ intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason());
+ }
+ if (info.getExtraInfo() != null) {
+ intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo());
+ }
+ NetworkAgentInfo newDefaultAgent = null;
+ if (networkAgent.networkRequests.get(mDefaultRequest.requestId) != null) {
+ newDefaultAgent = mNetworkForRequestId.get(mDefaultRequest.requestId);
+ if (newDefaultAgent != null) {
+ intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO,
+ newDefaultAgent.networkInfo);
+ } else {
+ intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
+ }
+ }
+ intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION,
+ mDefaultInetConditionPublished);
+ final Intent immediateIntent = new Intent(intent);
+ immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
+ sendStickyBroadcast(immediateIntent);
+ sendStickyBroadcastDelayed(intent, getConnectivityChangeDelay());
+ if (newDefaultAgent != null) {
+ sendConnectedBroadcastDelayed(newDefaultAgent.networkInfo,
+ getConnectivityChangeDelay());
+ }
+ }
+ }
+ }
+
+ private LinkProperties getLinkPropertiesForTypeInternal(int networkType) {
+ ArrayList<NetworkAgentInfo> list = mNetworkAgentInfoForType[networkType];
+ if (list == null) return null;
+ try {
+ return new LinkProperties(list.get(0).linkProperties);
+ } catch (IndexOutOfBoundsException e) {
+ return new LinkProperties();
+ }
+ }
+
+ private NetworkInfo getNetworkInfoForType(int networkType) {
+ ArrayList<NetworkAgentInfo> list = mNetworkAgentInfoForType[networkType];
+ if (list == null) return null;
+ try {
+ return new NetworkInfo(list.get(0).networkInfo);
+ } catch (IndexOutOfBoundsException e) {
+ return new NetworkInfo(networkType, 0, "Unknown", "");
+ }
+ }
+
+ private NetworkCapabilities getNetworkCapabilitiesForType(int networkType) {
+ ArrayList<NetworkAgentInfo> list = mNetworkAgentInfoForType[networkType];
+ if (list == null) return null;
+ try {
+ return new NetworkCapabilities(list.get(0).networkCapabilities);
+ } catch (IndexOutOfBoundsException e) {
+ return new NetworkCapabilities();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/ConsumerIrService.java b/services/core/java/com/android/server/ConsumerIrService.java
index 583f1bc..ea6812d 100644
--- a/services/core/java/com/android/server/ConsumerIrService.java
+++ b/services/core/java/com/android/server/ConsumerIrService.java
@@ -16,33 +16,13 @@
package com.android.server;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.database.ContentObserver;
-import android.hardware.input.InputManager;
import android.hardware.IConsumerIrService;
-import android.os.Handler;
import android.os.PowerManager;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.IBinder;
-import android.os.Binder;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.WorkSource;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
import android.util.Slog;
-import android.view.InputDevice;
import java.lang.RuntimeException;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.ListIterator;
public class ConsumerIrService extends IConsumerIrService.Stub {
private static final String TAG = "ConsumerIrService";
diff --git a/services/core/java/com/android/server/DiskStatsService.java b/services/core/java/com/android/server/DiskStatsService.java
index ac25dc5..bc12fc5 100644
--- a/services/core/java/com/android/server/DiskStatsService.java
+++ b/services/core/java/com/android/server/DiskStatsService.java
@@ -19,7 +19,6 @@ package com.android.server;
import android.content.Context;
import android.os.Binder;
import android.os.Environment;
-import android.os.FileUtils;
import android.os.StatFs;
import android.os.SystemClock;
diff --git a/services/core/java/com/android/server/EntropyMixer.java b/services/core/java/com/android/server/EntropyMixer.java
index cfdbf7d..24d8d1e 100644
--- a/services/core/java/com/android/server/EntropyMixer.java
+++ b/services/core/java/com/android/server/EntropyMixer.java
@@ -20,7 +20,6 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
import java.io.PrintWriter;
import android.content.BroadcastReceiver;
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 399e7d1..6fab37c 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -52,13 +52,21 @@ option java_package com.android.server
# NotificationManagerService.java
# ---------------------------
# when a NotificationManager.notify is called
-2750 notification_enqueue (pkg|3),(id|1|5),(tag|3),(userid|1|5),(notification|3)
+2750 notification_enqueue (uid|1|5),(pid|1|5),(pkg|3),(id|1|5),(tag|3),(userid|1|5),(notification|3)
# when someone tries to cancel a notification, the notification manager sometimes
# calls this with flags too
-2751 notification_cancel (pkg|3),(id|1|5),(tag|3),(userid|1|5),(required_flags|1),(forbidden_flags|1)
+2751 notification_cancel (uid|1|5),(pid|1|5),(pkg|3),(id|1|5),(tag|3),(userid|1|5),(required_flags|1),(forbidden_flags|1),(reason|1|5),(listener|3)
# when someone tries to cancel all of the notifications for a particular package
-2752 notification_cancel_all (pkg|3),(userid|1|5),(required_flags|1),(forbidden_flags|1)
-
+2752 notification_cancel_all (uid|1|5),(pid|1|5),(pkg|3),(userid|1|5),(required_flags|1),(forbidden_flags|1),(reason|1|5),(listener|3)
+# when the notification panel is shown
+# Note: New tag range starts here since 2753+ have been used below.
+27500 notification_panel_revealed
+# when the notification panel is hidden
+27501 notification_panel_hidden
+# when notifications are newly displayed on screen, or disappear from screen
+27510 notification_visibility_changed (newlyVisibleKeys|3),(noLongerVisibleKeys|3)
+# when a notification has been clicked
+27520 notification_clicked (key|3)
# ---------------------------
# Watchdog.java
diff --git a/services/core/java/com/android/server/INativeDaemonConnectorCallbacks.java b/services/core/java/com/android/server/INativeDaemonConnectorCallbacks.java
index 6fbf713..0cf9dcd 100644
--- a/services/core/java/com/android/server/INativeDaemonConnectorCallbacks.java
+++ b/services/core/java/com/android/server/INativeDaemonConnectorCallbacks.java
@@ -20,5 +20,6 @@ package com.android.server;
interface INativeDaemonConnectorCallbacks {
void onDaemonConnected();
+ boolean onCheckHoldWakeLock(int code);
boolean onEvent(int code, String raw, String[] cooked);
}
diff --git a/services/core/java/com/android/server/IdleMaintenanceService.java b/services/core/java/com/android/server/IdleMaintenanceService.java
index b0a1aca..acc6abe 100644
--- a/services/core/java/com/android/server/IdleMaintenanceService.java
+++ b/services/core/java/com/android/server/IdleMaintenanceService.java
@@ -16,22 +16,38 @@
package com.android.server;
-import android.app.Activity;
-import android.app.ActivityManagerNative;
import android.app.AlarmManager;
import android.app.PendingIntent;
+import android.app.maintenance.IIdleCallback;
+import android.app.maintenance.IIdleService;
+import android.app.maintenance.IdleService;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.WorkSource;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
/**
* This service observes the device state and when applicable sends
@@ -47,12 +63,15 @@ import android.util.Slog;
*
* The end of a maintenance window is announced only if: a start was
* announced AND the screen turned on or a dream was stopped.
+ *
+ * Method naming note:
+ * Methods whose name ends with "Tm" must only be called from the main thread.
*/
public class IdleMaintenanceService extends BroadcastReceiver {
private static final boolean DEBUG = false;
- private static final String LOG_TAG = IdleMaintenanceService.class.getSimpleName();
+ private static final String TAG = IdleMaintenanceService.class.getSimpleName();
private static final int LAST_USER_ACTIVITY_TIME_INVALID = -1;
@@ -74,36 +93,480 @@ public class IdleMaintenanceService extends BroadcastReceiver {
private static final String ACTION_FORCE_IDLE_MAINTENANCE =
"com.android.server.IdleMaintenanceService.action.FORCE_IDLE_MAINTENANCE";
- private static final Intent sIdleMaintenanceStartIntent;
- static {
- sIdleMaintenanceStartIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_START);
- sIdleMaintenanceStartIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- };
+ static final int MSG_OP_COMPLETE = 1;
+ static final int MSG_IDLE_FINISHED = 2;
+ static final int MSG_TIMEOUT = 3;
- private static final Intent sIdleMaintenanceEndIntent;
- static {
- sIdleMaintenanceEndIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_END);
- sIdleMaintenanceEndIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- }
+ // when a timeout happened, what were we expecting?
+ static final int VERB_BINDING = 1;
+ static final int VERB_IDLING = 2;
+ static final int VERB_ENDING = 3;
- private final AlarmManager mAlarmService;
+ // What are our relevant timeouts / allocated slices?
+ static final long OP_TIMEOUT = 8 * 1000; // 8 seconds to bind or ack the start
+ static final long IDLE_TIMESLICE = 10 * 60 * 1000; // ten minutes for each idler
+ private final AlarmManager mAlarmService;
private final BatteryService mBatteryService;
-
private final PendingIntent mUpdateIdleMaintenanceStatePendingIntent;
-
private final Context mContext;
-
private final WakeLock mWakeLock;
-
- private final Handler mHandler;
+ private final WorkSource mSystemWorkSource = new WorkSource(Process.myUid());
private long mLastIdleMaintenanceStartTimeMillis;
-
private long mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
-
private boolean mIdleMaintenanceStarted;
+ final IdleCallback mCallback;
+ final Handler mHandler;
+
+ final Random mTokenGenerator = new Random();
+
+ int makeToken() {
+ int token;
+ do {
+ token = mTokenGenerator.nextInt(Integer.MAX_VALUE);
+ } while (token == 0);
+ return token;
+ }
+
+ class ActiveTask {
+ public IdleServiceInfo who;
+ public int verb;
+ public int token;
+
+ ActiveTask(IdleServiceInfo target, int action) {
+ who = target;
+ verb = action;
+ token = makeToken();
+ }
+
+ @Override
+ public String toString() {
+ return "ActiveTask{" + Integer.toHexString(this.hashCode())
+ + " : verb=" + verb
+ + " : token=" + token
+ + " : "+ who + "}";
+ }
+ }
+
+ // What operations are in flight?
+ final SparseArray<ActiveTask> mPendingOperations = new SparseArray<ActiveTask>();
+
+ // Idle service queue management
+ class IdleServiceInfo {
+ public final ComponentName componentName;
+ public final int uid;
+ public IIdleService service;
+
+ IdleServiceInfo(ResolveInfo info, ComponentName cname) {
+ componentName = cname; // derived from 'info' but this avoids an extra object
+ uid = info.serviceInfo.applicationInfo.uid;
+ service = null;
+ }
+
+ @Override
+ public int hashCode() {
+ return componentName.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "IdleServiceInfo{" + componentName
+ + " / " + (service == null ? "null" : service.asBinder()) + "}";
+ }
+ }
+
+ final ArrayMap<ComponentName, IdleServiceInfo> mIdleServices =
+ new ArrayMap<ComponentName, IdleServiceInfo>();
+ final LinkedList<IdleServiceInfo> mIdleServiceQueue = new LinkedList<IdleServiceInfo>();
+ IdleServiceInfo mCurrentIdler; // set when we've committed to launching an idler
+ IdleServiceInfo mLastIdler; // end of queue when idling begins
+
+ void reportNoTimeout(int token, boolean result) {
+ final Message msg = mHandler.obtainMessage(MSG_OP_COMPLETE, result ? 1 : 0, token);
+ mHandler.sendMessage(msg);
+ }
+
+ // Binder acknowledgment trampoline
+ class IdleCallback extends IIdleCallback.Stub {
+ @Override
+ public void acknowledgeStart(int token, boolean result) throws RemoteException {
+ reportNoTimeout(token, result);
+ }
+
+ @Override
+ public void acknowledgeStop(int token) throws RemoteException {
+ reportNoTimeout(token, false);
+ }
+
+ @Override
+ public void idleFinished(int token) throws RemoteException {
+ if (DEBUG) {
+ Slog.v(TAG, "idleFinished: " + token);
+ }
+ final Message msg = mHandler.obtainMessage(MSG_IDLE_FINISHED, 0, token);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ // Stuff that we run on a Handler
+ class IdleHandler extends Handler {
+ public IdleHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final int token = msg.arg2;
+
+ switch (msg.what) {
+ case MSG_OP_COMPLETE: {
+ if (DEBUG) {
+ Slog.i(TAG, "MSG_OP_COMPLETE of " + token);
+ }
+ ActiveTask task = mPendingOperations.get(token);
+ if (task != null) {
+ mPendingOperations.remove(token);
+ removeMessages(MSG_TIMEOUT);
+
+ handleOpCompleteTm(task, msg.arg1);
+ } else {
+ // Can happen in a race between timeout and actual
+ // (belated) completion of a "begin idling" or similar
+ // operation. In that state we've already processed the
+ // timeout, so we intentionally no-op here.
+ if (DEBUG) {
+ Slog.w(TAG, "Belated op-complete of " + token);
+ }
+ }
+ break;
+ }
+
+ case MSG_IDLE_FINISHED: {
+ if (DEBUG) {
+ Slog.i(TAG, "MSG_IDLE_FINISHED of " + token);
+ }
+ ActiveTask task = mPendingOperations.get(token);
+ if (task != null) {
+ if (DEBUG) {
+ Slog.i(TAG, "... removing task " + token);
+ }
+ mPendingOperations.remove(token);
+ removeMessages(MSG_TIMEOUT);
+
+ handleIdleFinishedTm(task);
+ } else {
+ // Can happen "legitimately" from an app explicitly calling
+ // idleFinished() after already having been told that its slice
+ // has ended.
+ if (DEBUG) {
+ Slog.w(TAG, "Belated idle-finished of " + token);
+ }
+ }
+ break;
+ }
+
+ case MSG_TIMEOUT: {
+ if (DEBUG) {
+ Slog.i(TAG, "MSG_TIMEOUT of " + token);
+ }
+ ActiveTask task = mPendingOperations.get(token);
+ if (task != null) {
+ mPendingOperations.remove(token);
+ removeMessages(MSG_OP_COMPLETE);
+
+ handleTimeoutTm(task);
+ } else {
+ // This one should not happen; we flushed timeout messages
+ // whenever we entered a state after which we have established
+ // that they are not appropriate.
+ Slog.w(TAG, "Unexpected timeout of " + token);
+ }
+ break;
+ }
+
+ default:
+ Slog.w(TAG, "Unknown message: " + msg.what);
+ }
+ }
+ }
+
+ void handleTimeoutTm(ActiveTask task) {
+ switch (task.verb) {
+ case VERB_BINDING: {
+ // We were trying to bind to this service, but it wedged or otherwise
+ // failed to respond in time. Let it stay in the queue for the next
+ // time around, but just give up on it for now and go on to the next.
+ startNextIdleServiceTm();
+ break;
+ }
+ case VERB_IDLING: {
+ // The service has reached the end of its designated idle timeslice.
+ // This is not considered an error.
+ if (DEBUG) {
+ Slog.i(TAG, "Idler reached end of timeslice: " + task.who);
+ }
+ sendEndIdleTm(task.who);
+ break;
+ }
+ case VERB_ENDING: {
+ if (mCurrentIdler == task.who) {
+ if (DEBUG) {
+ Slog.i(TAG, "Task timed out when ending; unbind needed");
+ }
+ handleIdleFinishedTm(task);
+ } else {
+ if (DEBUG) {
+ Slog.w(TAG, "Ending timeout for non-current idle service!");
+ }
+ }
+ break;
+ }
+ default: {
+ Slog.w(TAG, "Unknown timeout state " + task.verb);
+ break;
+ }
+ }
+ }
+
+ void handleOpCompleteTm(ActiveTask task, int result) {
+ if (DEBUG) {
+ Slog.i(TAG, "handleOpComplete : task=" + task + " result=" + result);
+ }
+ if (task.verb == VERB_IDLING) {
+ // If the service was told to begin idling and responded positively, then
+ // it has begun idling and will eventually either explicitly finish, or
+ // reach the end of its allotted timeslice. It's running free now, so we
+ // just schedule the idle-expiration timeout under the token it's already been
+ // given and let it keep going.
+ if (result != 0) {
+ scheduleOpTimeoutTm(task);
+ } else {
+ // The idle service has indicated that it does not, in fact,
+ // need to run at present, so we immediately indicate that it's
+ // to finish idling, and go on to the next idler.
+ if (DEBUG) {
+ Slog.i(TAG, "Idler declined idling; moving along");
+ }
+ sendEndIdleTm(task.who);
+ }
+ } else {
+ // In the idling case, the task will be cleared either as the result of a timeout
+ // or of an explicit idleFinished(). For all other operations (binding, ending) we
+ // are done with the task as such, so we remove it from our bookkeeping.
+ if (DEBUG) {
+ Slog.i(TAG, "Clearing task " + task);
+ }
+ mPendingOperations.remove(task.token);
+ if (task.verb == VERB_ENDING) {
+ // The last bit of handshaking around idle cessation for this target
+ handleIdleFinishedTm(task);
+ }
+ }
+ }
+
+ void handleIdleFinishedTm(ActiveTask task) {
+ final IdleServiceInfo who = task.who;
+ if (who == mCurrentIdler) {
+ if (DEBUG) {
+ Slog.i(TAG, "Current idler has finished: " + who);
+ Slog.i(TAG, "Attributing wakelock to system work source");
+ }
+ mContext.unbindService(mConnection);
+ startNextIdleServiceTm();
+ } else {
+ Slog.w(TAG, "finish from non-current idle service? " + who);
+ }
+ }
+
+ void updateIdleServiceQueueTm() {
+ if (DEBUG) {
+ Slog.i(TAG, "Updating idle service queue");
+ }
+ PackageManager pm = mContext.getPackageManager();
+ Intent idleIntent = new Intent(IdleService.SERVICE_INTERFACE);
+ List<ResolveInfo> services = pm.queryIntentServices(idleIntent, 0);
+ for (ResolveInfo info : services) {
+ if (info.serviceInfo != null) {
+ if (IdleService.PERMISSION_BIND.equals(info.serviceInfo.permission)) {
+ final ComponentName componentName = new ComponentName(
+ info.serviceInfo.packageName,
+ info.serviceInfo.name);
+ if (DEBUG) {
+ Slog.i(TAG, " - " + componentName);
+ }
+ if (!mIdleServices.containsKey(componentName)) {
+ if (DEBUG) {
+ Slog.i(TAG, " + not known; adding");
+ }
+ IdleServiceInfo serviceInfo = new IdleServiceInfo(info, componentName);
+ mIdleServices.put(componentName, serviceInfo);
+ mIdleServiceQueue.add(serviceInfo);
+ }
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "Idle service " + info.serviceInfo
+ + " does not have required permission; ignoring");
+ }
+ }
+ }
+ }
+ }
+
+ void startNextIdleServiceTm() {
+ mWakeLock.setWorkSource(mSystemWorkSource);
+
+ if (mLastIdler == null) {
+ // we've run the queue; nothing more to do until the next idle interval.
+ if (DEBUG) {
+ Slog.i(TAG, "Queue already drained; nothing more to do");
+ }
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.i(TAG, "startNextIdleService : last=" + mLastIdler + " cur=" + mCurrentIdler);
+ if (mIdleServiceQueue.size() > 0) {
+ int i = 0;
+ Slog.i(TAG, "Queue (" + mIdleServiceQueue.size() + "):");
+ for (IdleServiceInfo info : mIdleServiceQueue) {
+ Slog.i(TAG, " " + i + " : " + info);
+ i++;
+ }
+ }
+ }
+ if (mCurrentIdler != mLastIdler) {
+ if (mIdleServiceQueue.size() > 0) {
+ IdleServiceInfo target = mIdleServiceQueue.pop();
+ if (DEBUG) {
+ Slog.i(TAG, "starting next idle service " + target);
+ }
+ Intent idleIntent = new Intent(IdleService.SERVICE_INTERFACE);
+ idleIntent.setComponent(target.componentName);
+ mCurrentIdler = target;
+ ActiveTask task = new ActiveTask(target, VERB_BINDING);
+ scheduleOpTimeoutTm(task);
+ boolean bindOk = mContext.bindServiceAsUser(idleIntent, mConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY, UserHandle.OWNER);
+ if (!bindOk) {
+ if (DEBUG) {
+ Slog.w(TAG, "bindService() to " + target.componentName
+ + " failed");
+ }
+ } else {
+ mIdleServiceQueue.add(target); // at the end for next time
+ if (DEBUG) { Slog.i(TAG, "Attributing wakelock to target uid " + target.uid); }
+ mWakeLock.setWorkSource(new WorkSource(target.uid));
+ }
+ } else {
+ // Queue is empty but mLastIdler is non-null -- eeep. Clear *everything*
+ // and wind up until the next time around.
+ Slog.e(TAG, "Queue unexpectedly empty; resetting. last="
+ + mLastIdler + " cur=" + mCurrentIdler);
+ mHandler.removeMessages(MSG_TIMEOUT);
+ mPendingOperations.clear();
+ stopIdleMaintenanceTm();
+ }
+ } else {
+ // we've reached the place we started, so mark the queue as drained
+ if (DEBUG) {
+ Slog.i(TAG, "Reached end of queue.");
+ }
+ stopIdleMaintenanceTm();
+ }
+ }
+
+ void sendStartIdleTm(IdleServiceInfo who) {
+ ActiveTask task = new ActiveTask(who, VERB_IDLING);
+ scheduleOpTimeoutTm(task);
+ try {
+ who.service.startIdleMaintenance(mCallback, task.token);
+ } catch (RemoteException e) {
+ // We bound to it, but now we can't reach it. Bail and go on to the
+ // next service.
+ mContext.unbindService(mConnection);
+ if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); }
+ mHandler.removeMessages(MSG_TIMEOUT);
+ startNextIdleServiceTm();
+ }
+ }
+
+ void sendEndIdleTm(IdleServiceInfo who) {
+ ActiveTask task = new ActiveTask(who, VERB_ENDING);
+ scheduleOpTimeoutTm(task);
+ if (DEBUG) {
+ Slog.i(TAG, "Sending end-idle to " + who);
+ }
+ try {
+ who.service.stopIdleMaintenance(mCallback, task.token);
+ } catch (RemoteException e) {
+ // We bound to it, but now we can't reach it. Bail and go on to the
+ // next service.
+ mContext.unbindService(mConnection);
+ if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); }
+ mHandler.removeMessages(MSG_TIMEOUT);
+ startNextIdleServiceTm();
+ }
+ }
+
+ ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) {
+ Slog.i(TAG, "onServiceConnected(" + name + ")");
+ }
+ IdleServiceInfo info = mIdleServices.get(name);
+ if (info != null) {
+ // Bound! Cancel the bind timeout
+ mHandler.removeMessages(MSG_TIMEOUT);
+ // Now tell it to start its idle work
+ info.service = IIdleService.Stub.asInterface(service);
+ sendStartIdleTm(info);
+ } else {
+ // We bound to a service we don't know about. That's ungood.
+ Slog.e(TAG, "Connected to unexpected component " + name);
+ mContext.unbindService(this);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) {
+ Slog.i(TAG, "onServiceDisconnected(" + name + ")");
+ }
+ IdleServiceInfo who = mIdleServices.get(name);
+ if (who == mCurrentIdler) {
+ // Hm, okay; they didn't tell us they were finished but they
+ // went away. Crashed, probably. Oh well. They're gone, so
+ // we can't finish them cleanly; just force things along.
+ Slog.w(TAG, "Idler unexpectedly vanished: " + mCurrentIdler);
+ mContext.unbindService(this);
+ mHandler.removeMessages(MSG_TIMEOUT);
+ startNextIdleServiceTm();
+ } else {
+ // Not the current idler, so we don't interrupt our process...
+ if (DEBUG) {
+ Slog.w(TAG, "Disconnect of abandoned or unexpected service " + name);
+ }
+ }
+ }
+ };
+
+ // Schedules a timeout / end-of-work based on the task verb
+ void scheduleOpTimeoutTm(ActiveTask task) {
+ final long timeoutMillis = (task.verb == VERB_IDLING) ? IDLE_TIMESLICE : OP_TIMEOUT;
+ if (DEBUG) {
+ Slog.i(TAG, "Scheduling timeout (token " + task.token
+ + " : verb " + task.verb + ") for " + task + " in " + timeoutMillis);
+ }
+ mPendingOperations.put(task.token, task);
+ mHandler.removeMessages(MSG_TIMEOUT);
+ final Message msg = mHandler.obtainMessage(MSG_TIMEOUT, 0, task.token);
+ mHandler.sendMessageDelayed(msg, timeoutMillis);
+ }
+
+ // -------------------------------------------------------------------------------
public IdleMaintenanceService(Context context, BatteryService batteryService) {
mContext = context;
mBatteryService = batteryService;
@@ -111,9 +574,10 @@ public class IdleMaintenanceService extends BroadcastReceiver {
mAlarmService = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
+ mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
- mHandler = new Handler(mContext.getMainLooper());
+ mHandler = new IdleHandler(mContext.getMainLooper());
+ mCallback = new IdleCallback();
Intent intent = new Intent(ACTION_UPDATE_IDLE_MAINTENANCE_STATE);
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -159,26 +623,28 @@ public class IdleMaintenanceService extends BroadcastReceiver {
mAlarmService.cancel(mUpdateIdleMaintenanceStatePendingIntent);
}
- private void updateIdleMaintenanceState(boolean noisy) {
+ private void updateIdleMaintenanceStateTm(boolean noisy) {
if (mIdleMaintenanceStarted) {
// Idle maintenance can be interrupted by user activity, or duration
// time out, or low battery.
- if (!lastUserActivityPermitsIdleMaintenanceRunning()
- || !batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
+ final boolean batteryOk
+ = batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning();
+ if (!lastUserActivityPermitsIdleMaintenanceRunning() || !batteryOk) {
unscheduleUpdateIdleMaintenanceState();
mIdleMaintenanceStarted = false;
- EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(),
- mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
- isBatteryCharging() ? 1 : 0);
- sendIdleMaintenanceEndIntent();
// We stopped since we don't have enough battery or timed out but the
// user is not using the device, so we should be able to run maintenance
// in the next maintenance window since the battery may be charged
// without interaction and the min interval between maintenances passed.
- if (!batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
+ if (!batteryOk) {
scheduleUpdateIdleMaintenanceState(
getNextIdleMaintenanceIntervalStartFromNow());
}
+
+ EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(),
+ mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
+ isBatteryCharging() ? 1 : 0);
+ scheduleIdleFinishTm();
}
} else if (deviceStatePermitsIdleMaintenanceStart(noisy)
&& lastUserActivityPermitsIdleMaintenanceStart(noisy)
@@ -191,7 +657,7 @@ public class IdleMaintenanceService extends BroadcastReceiver {
mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
isBatteryCharging() ? 1 : 0);
mLastIdleMaintenanceStartTimeMillis = SystemClock.elapsedRealtime();
- sendIdleMaintenanceStartIntent();
+ startIdleMaintenanceTm();
} else if (lastUserActivityPermitsIdleMaintenanceStart(noisy)) {
if (lastRunPermitsIdleMaintenanceStart(noisy)) {
// The user does not use the device and we did not run maintenance in more
@@ -207,25 +673,55 @@ public class IdleMaintenanceService extends BroadcastReceiver {
}
}
- private long getNextIdleMaintenanceIntervalStartFromNow() {
- return mLastIdleMaintenanceStartTimeMillis + MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS
- - SystemClock.elapsedRealtime();
+ void startIdleMaintenanceTm() {
+ if (DEBUG) {
+ Slog.i(TAG, "*** Starting idle maintenance ***");
+ }
+ if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); }
+ mWakeLock.setWorkSource(mSystemWorkSource);
+ mWakeLock.acquire();
+ updateIdleServiceQueueTm();
+ mCurrentIdler = null;
+ mLastIdler = (mIdleServiceQueue.size() > 0) ? mIdleServiceQueue.peekLast() : null;
+ startNextIdleServiceTm();
}
- private void sendIdleMaintenanceStartIntent() {
- mWakeLock.acquire();
- try {
- ActivityManagerNative.getDefault().performIdleMaintenance();
- } catch (RemoteException e) {
+ // Start a graceful wind-down of the idle maintenance state: end the current idler
+ // and pretend that we've finished running the queue. If there's no current idler,
+ // this is a no-op.
+ void scheduleIdleFinishTm() {
+ if (mCurrentIdler != null) {
+ if (DEBUG) {
+ Slog.i(TAG, "*** Finishing idle maintenance ***");
+ }
+ mLastIdler = mCurrentIdler;
+ sendEndIdleTm(mCurrentIdler);
+ } else {
+ if (DEBUG) {
+ Slog.w(TAG, "Asked to finish idle maintenance but we're done already");
+ }
}
- mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceStartIntent, UserHandle.ALL,
- null, this, mHandler, Activity.RESULT_OK, null, null);
}
- private void sendIdleMaintenanceEndIntent() {
- mWakeLock.acquire();
- mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceEndIntent, UserHandle.ALL,
- null, this, mHandler, Activity.RESULT_OK, null, null);
+ // Actual finalization of the idle maintenance sequence
+ void stopIdleMaintenanceTm() {
+ if (mLastIdler != null) {
+ if (DEBUG) {
+ Slog.i(TAG, "*** Idle maintenance shutdown ***");
+ }
+ mWakeLock.setWorkSource(mSystemWorkSource);
+ mLastIdler = mCurrentIdler = null;
+ updateIdleMaintenanceStateTm(false); // resets 'started' and schedules next window
+ mWakeLock.release();
+ } else {
+ Slog.e(TAG, "ERROR: idle shutdown but invariants not held. last=" + mLastIdler
+ + " cur=" + mCurrentIdler + " size=" + mIdleServiceQueue.size());
+ }
+ }
+
+ private long getNextIdleMaintenanceIntervalStartFromNow() {
+ return mLastIdleMaintenanceStartTimeMillis + MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS
+ - SystemClock.elapsedRealtime();
}
private boolean deviceStatePermitsIdleMaintenanceStart(boolean noisy) {
@@ -281,7 +777,7 @@ public class IdleMaintenanceService extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) {
- Log.i(LOG_TAG, intent.getAction());
+ Log.i(TAG, intent.getAction());
}
String action = intent.getAction();
if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
@@ -292,7 +788,7 @@ public class IdleMaintenanceService extends BroadcastReceiver {
// next release. The only client for this for now is internal an holds
// a wake lock correctly.
if (mIdleMaintenanceStarted) {
- updateIdleMaintenanceState(false);
+ updateIdleMaintenanceStateTm(false);
}
} else if (Intent.ACTION_SCREEN_ON.equals(action)
|| Intent.ACTION_DREAMING_STOPPED.equals(action)) {
@@ -302,7 +798,7 @@ public class IdleMaintenanceService extends BroadcastReceiver {
unscheduleUpdateIdleMaintenanceState();
// If the screen went on/stopped dreaming, we know the user is using the
// device which means that idle maintenance should be stopped if running.
- updateIdleMaintenanceState(false);
+ updateIdleMaintenanceStateTm(false);
} else if (Intent.ACTION_SCREEN_OFF.equals(action)
|| Intent.ACTION_DREAMING_STARTED.equals(action)) {
mLastUserActivityElapsedTimeMillis = SystemClock.elapsedRealtime();
@@ -311,17 +807,12 @@ public class IdleMaintenanceService extends BroadcastReceiver {
// this timeout elapses since the device may go to sleep by then.
scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
} else if (ACTION_UPDATE_IDLE_MAINTENANCE_STATE.equals(action)) {
- updateIdleMaintenanceState(false);
+ updateIdleMaintenanceStateTm(false);
} else if (ACTION_FORCE_IDLE_MAINTENANCE.equals(action)) {
long now = SystemClock.elapsedRealtime() - 1;
mLastUserActivityElapsedTimeMillis = now - MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START;
mLastIdleMaintenanceStartTimeMillis = now - MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS;
- updateIdleMaintenanceState(true);
- } else if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)
- || Intent.ACTION_IDLE_MAINTENANCE_END.equals(action)) {
- // We were holding a wake lock while broadcasting the idle maintenance
- // intents but now that we finished the broadcast release the wake lock.
- mWakeLock.release();
+ updateIdleMaintenanceStateTm(true);
}
}
}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index bbec329..50553ee 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -16,6 +16,8 @@
package com.android.server;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController;
+import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
import com.android.internal.inputmethod.InputMethodUtils;
import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
import com.android.internal.os.HandlerCaller;
@@ -57,6 +59,7 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -76,6 +79,7 @@ import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
@@ -114,12 +118,9 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Locale;
-import java.util.TreeMap;
/**
* This class provides a system service that manages input methods.
@@ -147,10 +148,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
static final int MSG_UNBIND_METHOD = 3000;
static final int MSG_BIND_METHOD = 3010;
static final int MSG_SET_ACTIVE = 3020;
+ static final int MSG_SET_CURSOR_ANCHOR_MONITOR_MODE = 3030;
static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
- static final long TIME_TO_RECONNECT = 10*1000;
+ static final long TIME_TO_RECONNECT = 3 * 1000;
static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
@@ -167,7 +169,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final HandlerCaller mCaller;
final boolean mHasFeature;
private InputMethodFileManager mFileManager;
- private InputMethodAndSubtypeListManager mImListManager;
private final HardKeyboardListener mHardKeyboardListener;
private final WindowManagerService mWindowManagerService;
@@ -179,6 +180,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>();
private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
new LruCache<SuggestionSpan, InputMethodInfo>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
+ private final InputMethodSubtypeSwitchingController mSwitchingController;
// Used to bring IME service up to visible adjustment while it is being shown.
final ServiceConnection mVisibleConnection = new ServiceConnection() {
@@ -442,6 +444,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
hideInputMethodMenu();
// No need to updateActive
return;
+ } else if (Intent.ACTION_USER_ADDED.equals(action)
+ || Intent.ACTION_USER_REMOVED.equals(action)) {
+ updateCurrentProfileIds();
+ return;
} else {
Slog.w(TAG, "Unexpected intent " + intent);
}
@@ -576,7 +582,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
@Override
public void sessionCreated(IInputMethodSession session) {
- mParentIMMS.onSessionCreated(mMethod, session, mChannel);
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mParentIMMS.onSessionCreated(mMethod, session, mChannel);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
}
@@ -644,6 +655,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
broadcastFilter.addAction(Intent.ACTION_SCREEN_ON);
broadcastFilter.addAction(Intent.ACTION_SCREEN_OFF);
broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
+ broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
mNotificationShown = false;
@@ -677,8 +690,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// mSettings should be created before buildInputMethodListLocked
mSettings = new InputMethodSettings(
mRes, context.getContentResolver(), mMethodMap, mMethodList, userId);
+ updateCurrentProfileIds();
mFileManager = new InputMethodFileManager(mMethodMap, userId);
- mImListManager = new InputMethodAndSubtypeListManager(context, this);
+ mSwitchingController = new InputMethodSubtypeSwitchingController(mSettings);
+ mSwitchingController.resetCircularListLocked(context);
// Just checking if defaultImiId is empty or not
final String defaultImiId = mSettings.getSelectedInputMethod();
@@ -758,8 +773,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (DEBUG) {
Slog.i(TAG, "Locale has been changed to " + newLocale);
}
- // InputMethodAndSubtypeListManager should be reset when the locale is changed.
- mImListManager = new InputMethodAndSubtypeListManager(mContext, this);
+ // CircularList should be reset when the locale is changed.
+ mSwitchingController.resetCircularListLocked(mContext);
buildInputMethodListLocked(mMethodList, mMethodMap, resetDefaultEnabledIme);
if (!updateOnlyWhenLocaleChanged) {
final String selectedImiId = mSettings.getSelectedInputMethod();
@@ -791,6 +806,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
private void switchUserLocked(int newUserId) {
mSettings.setCurrentUserId(newUserId);
+ updateCurrentProfileIds();
// InputMethodFileManager should be reset when the user is changed
mFileManager = new InputMethodFileManager(mMethodMap, newUserId);
final String defaultImiId = mSettings.getSelectedInputMethod();
@@ -811,6 +827,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
+ void updateCurrentProfileIds() {
+ List<UserInfo> profiles =
+ UserManager.get(mContext).getProfiles(mSettings.getCurrentUserId());
+ int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
+ for (int i = 0; i < currentProfileIds.length; i++) {
+ currentProfileIds[i] = profiles.get(i).id;
+ }
+ mSettings.setCurrentProfileIds(currentProfileIds);
+ }
+
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -906,7 +932,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
+ mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()
+ InputMethodUtils.getApiCallStack());
}
- if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) {
+ if (uid == Process.SYSTEM_UID || mSettings.isCurrentProfile(userId)) {
return true;
}
@@ -961,17 +987,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- private HashMap<InputMethodInfo, List<InputMethodSubtype>>
- getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked() {
- HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
- new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
- for (InputMethodInfo imi: mSettings.getEnabledInputMethodListLocked()) {
- enabledInputMethodAndSubtypes.put(
- imi, mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true));
- }
- return enabledInputMethodAndSubtypes;
- }
-
/**
* @param imiId if null, returns enabled subtypes for the current imi
* @return enabled subtypes of the specified imi
@@ -1217,7 +1232,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
- | Context.BIND_NOT_VISIBLE | Context.BIND_SHOWING_UI)) {
+ | Context.BIND_NOT_VISIBLE | Context.BIND_NOT_FOREGROUND
+ | Context.BIND_SHOWING_UI)) {
mLastBindTime = SystemClock.uptimeMillis();
mHaveConnection = true;
mCurId = info.getId();
@@ -1448,6 +1464,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
private boolean needsToShowImeSwitchOngoingNotification() {
if (!mShowOngoingImeSwitcherForPhones) return false;
+ if (mSwitchingDialog != null) return false;
if (isScreenLocked()) return false;
synchronized (mMethodMap) {
List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
@@ -1516,14 +1533,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
mImeWindowVis = vis;
mBackDisposition = backDisposition;
- if (mStatusBar != null) {
- mStatusBar.setImeWindowStatus(token, vis, backDisposition);
- }
final boolean iconVisibility = ((vis & (InputMethodService.IME_ACTIVE)) != 0)
&& (mWindowManagerService.isHardKeyboardAvailable()
|| (vis & (InputMethodService.IME_VISIBLE)) != 0);
+ final boolean needsToShowImeSwitcher = iconVisibility
+ && needsToShowImeSwitchOngoingNotification();
+ if (mStatusBar != null) {
+ mStatusBar.setImeWindowStatus(token, vis, backDisposition,
+ needsToShowImeSwitcher);
+ }
final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
- if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) {
+ if (imi != null && needsToShowImeSwitcher) {
// Used to load label
final CharSequence title = mRes.getText(
com.android.internal.R.string.select_input_method);
@@ -1532,7 +1552,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mImeSwitcherNotification.setLatestEventInfo(
mContext, title, summary, mImeSwitchPendingIntent);
- if (mNotificationManager != null) {
+ if ((mNotificationManager != null)
+ && !mWindowManagerService.hasNavigationBar()) {
if (DEBUG) {
Slog.d(TAG, "--- show notification: label = " + summary);
}
@@ -1775,7 +1796,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mInputShown = true;
if (mHaveConnection && !mVisibleBound) {
bindCurrentInputMethodService(
- mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE);
+ mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE
+ | Context.BIND_TREAT_LIKE_ACTIVITY);
mVisibleBound = true;
}
res = true;
@@ -2154,7 +2176,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return false;
}
synchronized (mMethodMap) {
- final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod(
+ final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethod(
onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype);
if (nextSubtype == null) {
return false;
@@ -2170,7 +2192,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return false;
}
synchronized (mMethodMap) {
- final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod(
+ final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethod(
false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype);
if (nextSubtype == null) {
return false;
@@ -2243,6 +2265,43 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return;
}
+ @Override
+ public int getInputMethodWindowVisibleHeight() {
+ return mWindowManagerService.getInputMethodWindowVisibleHeight();
+ }
+
+ @Override
+ public void notifyTextCommitted() {
+ if (DEBUG) {
+ Slog.d(TAG, "Got the notification of commitText");
+ }
+ final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
+ if (imi != null) {
+ mSwitchingController.onCommitText(imi, mCurrentSubtype);
+ }
+ }
+
+ @Override
+ public void setCursorAnchorMonitorMode(IBinder token, int monitorMode) {
+ if (DEBUG) {
+ Slog.d(TAG, "setCursorAnchorMonitorMode: monitorMode=" + monitorMode);
+ }
+ if (!calledFromValidUser()) {
+ return;
+ }
+ synchronized (mMethodMap) {
+ if (token == null || mCurToken != token) {
+ if (DEBUG) {
+ Slog.w(TAG, "Ignoring setCursorAnchorMonitorMode from uid "
+ + Binder.getCallingUid() + " token: " + token);
+ }
+ return;
+ }
+ executeOrSendMessage(mCurMethod, mCaller.obtainMessageIO(
+ MSG_SET_CURSOR_ANCHOR_MONITOR_MODE, monitorMode, mCurClient));
+ }
+ }
+
private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
synchronized (mMethodMap) {
if (token == null) {
@@ -2310,20 +2369,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
void setEnabledSessionInMainThread(SessionState session) {
if (mEnabledSession != session) {
- if (mEnabledSession != null) {
+ if (mEnabledSession != null && mEnabledSession.session != null) {
try {
if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
- mEnabledSession.method.setSessionEnabled(
- mEnabledSession.session, false);
+ mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false);
} catch (RemoteException e) {
}
}
mEnabledSession = session;
- try {
- if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
- session.method.setSessionEnabled(
- session.session, true);
- } catch (RemoteException e) {
+ if (mEnabledSession != null && mEnabledSession.session != null) {
+ try {
+ if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
+ mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true);
+ } catch (RemoteException e) {
+ }
}
}
}
@@ -2474,6 +2533,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
+ ((ClientState)msg.obj).uid);
}
return true;
+ case MSG_SET_CURSOR_ANCHOR_MONITOR_MODE:
+ try {
+ ((ClientState)msg.obj).client.setCursorAnchorMonitorMode(msg.arg1);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Got RemoteException sending setCursorAnchorMonitorMode "
+ + "notification to pid " + ((ClientState)msg.obj).pid
+ + " uid " + ((ClientState)msg.obj).uid);
+ }
+ return true;
// --------------------------------------------------------------
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
@@ -2621,7 +2689,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
synchronized (mMethodMap) {
final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
- getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked();
+ mSettings.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(
+ mContext);
if (immis == null || immis.size() == 0) {
return;
}
@@ -2629,7 +2698,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
hideInputMethodMenuLocked();
final List<ImeSubtypeListItem> imList =
- mImListManager.getSortedInputMethodAndSubtypeList(
+ mSwitchingController.getSortedInputMethodAndSubtypeList(
showSubtypes, mInputShown, isScreenLocked);
if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
@@ -2744,70 +2813,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mSwitchingDialog.getWindow().getAttributes().privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method");
+ updateImeWindowStatusLocked();
mSwitchingDialog.show();
}
}
- private static class ImeSubtypeListItem implements Comparable<ImeSubtypeListItem> {
- public final CharSequence mImeName;
- public final CharSequence mSubtypeName;
- public final InputMethodInfo mImi;
- public final int mSubtypeId;
- private final boolean mIsSystemLocale;
- private final boolean mIsSystemLanguage;
-
- public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName,
- InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) {
- mImeName = imeName;
- mSubtypeName = subtypeName;
- mImi = imi;
- mSubtypeId = subtypeId;
- if (TextUtils.isEmpty(subtypeLocale)) {
- mIsSystemLocale = false;
- mIsSystemLanguage = false;
- } else {
- mIsSystemLocale = subtypeLocale.equals(systemLocale);
- mIsSystemLanguage = mIsSystemLocale
- || subtypeLocale.startsWith(systemLocale.substring(0, 2));
- }
- }
-
- @Override
- public int compareTo(ImeSubtypeListItem other) {
- if (TextUtils.isEmpty(mImeName)) {
- return 1;
- }
- if (TextUtils.isEmpty(other.mImeName)) {
- return -1;
- }
- if (!TextUtils.equals(mImeName, other.mImeName)) {
- return mImeName.toString().compareTo(other.mImeName.toString());
- }
- if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) {
- return 0;
- }
- if (mIsSystemLocale) {
- return -1;
- }
- if (other.mIsSystemLocale) {
- return 1;
- }
- if (mIsSystemLanguage) {
- return -1;
- }
- if (other.mIsSystemLanguage) {
- return 1;
- }
- if (TextUtils.isEmpty(mSubtypeName)) {
- return 1;
- }
- if (TextUtils.isEmpty(other.mSubtypeName)) {
- return -1;
- }
- return mSubtypeName.toString().compareTo(other.mSubtypeName.toString());
- }
- }
-
private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
private final LayoutInflater mInflater;
private final int mTextViewResourceId;
@@ -2861,6 +2871,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mSwitchingDialog = null;
}
+ updateImeWindowStatusLocked();
mDialogBuilder = null;
mIms = null;
}
@@ -3179,123 +3190,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- private static class InputMethodAndSubtypeListManager {
- private final Context mContext;
- // Used to load label
- private final PackageManager mPm;
- private final InputMethodManagerService mImms;
- private final String mSystemLocaleStr;
- public InputMethodAndSubtypeListManager(Context context, InputMethodManagerService imms) {
- mContext = context;
- mPm = context.getPackageManager();
- mImms = imms;
- final Locale locale = context.getResources().getConfiguration().locale;
- mSystemLocaleStr = locale != null ? locale.toString() : "";
- }
-
- private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis =
- new TreeMap<InputMethodInfo, List<InputMethodSubtype>>(
- new Comparator<InputMethodInfo>() {
- @Override
- public int compare(InputMethodInfo imi1, InputMethodInfo imi2) {
- if (imi2 == null) return 0;
- if (imi1 == null) return 1;
- if (mPm == null) {
- return imi1.getId().compareTo(imi2.getId());
- }
- CharSequence imiId1 = imi1.loadLabel(mPm) + "/" + imi1.getId();
- CharSequence imiId2 = imi2.loadLabel(mPm) + "/" + imi2.getId();
- return imiId1.toString().compareTo(imiId2.toString());
- }
- });
-
- public ImeSubtypeListItem getNextInputMethod(
- boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) {
- if (imi == null) {
- return null;
- }
- final List<ImeSubtypeListItem> imList = getSortedInputMethodAndSubtypeList();
- if (imList.size() <= 1) {
- return null;
- }
- final int N = imList.size();
- final int currentSubtypeId = subtype != null
- ? InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode())
- : NOT_A_SUBTYPE_ID;
- for (int i = 0; i < N; ++i) {
- final ImeSubtypeListItem isli = imList.get(i);
- if (isli.mImi.equals(imi) && isli.mSubtypeId == currentSubtypeId) {
- if (!onlyCurrentIme) {
- return imList.get((i + 1) % N);
- }
- for (int j = 0; j < N - 1; ++j) {
- final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N);
- if (candidate.mImi.equals(imi)) {
- return candidate;
- }
- }
- return null;
- }
- }
- return null;
- }
-
- public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList() {
- return getSortedInputMethodAndSubtypeList(true, false, false);
- }
-
- public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(boolean showSubtypes,
- boolean inputShown, boolean isScreenLocked) {
- final ArrayList<ImeSubtypeListItem> imList = new ArrayList<ImeSubtypeListItem>();
- final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
- mImms.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked();
- if (immis == null || immis.size() == 0) {
- return Collections.emptyList();
- }
- mSortedImmis.clear();
- mSortedImmis.putAll(immis);
- for (InputMethodInfo imi : mSortedImmis.keySet()) {
- if (imi == null) continue;
- List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi);
- HashSet<String> enabledSubtypeSet = new HashSet<String>();
- for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) {
- enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
- }
- final CharSequence imeLabel = imi.loadLabel(mPm);
- if (showSubtypes && enabledSubtypeSet.size() > 0) {
- final int subtypeCount = imi.getSubtypeCount();
- if (DEBUG) {
- Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
- }
- for (int j = 0; j < subtypeCount; ++j) {
- final InputMethodSubtype subtype = imi.getSubtypeAt(j);
- final String subtypeHashCode = String.valueOf(subtype.hashCode());
- // We show all enabled IMEs and subtypes when an IME is shown.
- if (enabledSubtypeSet.contains(subtypeHashCode)
- && ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) {
- final CharSequence subtypeLabel =
- subtype.overridesImplicitlyEnabledSubtype() ? null
- : subtype.getDisplayName(mContext, imi.getPackageName(),
- imi.getServiceInfo().applicationInfo);
- imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, imi, j,
- subtype.getLocale(), mSystemLocaleStr));
-
- // Removing this subtype from enabledSubtypeSet because we no longer
- // need to add an entry of this subtype to imList to avoid duplicated
- // entries.
- enabledSubtypeSet.remove(subtypeHashCode);
- }
- }
- } else {
- imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID,
- null, mSystemLocaleStr));
- }
- }
- Collections.sort(imList);
- return imList;
- }
- }
-
// TODO: Cache the state for each user and reset when the cached user is removed.
private static class InputMethodFileManager {
private static final String SYSTEM_PATH = "system";
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index e0b568b..11fc941 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -73,6 +73,9 @@ import com.android.server.location.LocationBlacklist;
import com.android.server.location.LocationFudger;
import com.android.server.location.LocationProviderInterface;
import com.android.server.location.LocationProviderProxy;
+import com.android.server.location.LocationRequestStatistics;
+import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
+import com.android.server.location.LocationRequestStatistics.PackageStatistics;
import com.android.server.location.MockProvider;
import com.android.server.location.PassiveProvider;
@@ -178,6 +181,8 @@ public class LocationManagerService extends ILocationManager.Stub {
private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider =
new HashMap<String, ArrayList<UpdateRecord>>();
+ private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics();
+
// mapping from provider name to last known location
private final HashMap<String, Location> mLastLocation = new HashMap<String, Location>();
@@ -421,29 +426,54 @@ public class LocationManagerService extends ILocationManager.Stub {
Slog.e(TAG, "no geocoder provider found");
}
- // bind to fused provider
- FlpHardwareProvider flpHardwareProvider = FlpHardwareProvider.getInstance(mContext);
- FusedProxy fusedProxy = FusedProxy.createAndBind(
- mContext,
- mLocationHandler,
- flpHardwareProvider.getLocationHardware(),
- com.android.internal.R.bool.config_enableFusedLocationOverlay,
- com.android.internal.R.string.config_fusedLocationProviderPackageName,
- com.android.internal.R.array.config_locationProviderPackageNames);
- if(fusedProxy == null) {
- Slog.e(TAG, "No FusedProvider found.");
+ // bind to fused provider if supported
+ if (FlpHardwareProvider.getInstance(mContext).isSupported()) {
+ FlpHardwareProvider flpHardwareProvider = FlpHardwareProvider.getInstance(mContext);
+ FusedProxy fusedProxy = FusedProxy.createAndBind(
+ mContext,
+ mLocationHandler,
+ flpHardwareProvider.getLocationHardware(),
+ com.android.internal.R.bool.config_enableFusedLocationOverlay,
+ com.android.internal.R.string.config_fusedLocationProviderPackageName,
+ com.android.internal.R.array.config_locationProviderPackageNames);
+ if(fusedProxy == null) {
+ Slog.e(TAG, "Unable to bind FusedProxy.");
+ }
+
+ // bind to geofence provider
+ GeofenceProxy provider = GeofenceProxy.createAndBind(mContext,
+ com.android.internal.R.bool.config_enableGeofenceOverlay,
+ com.android.internal.R.string.config_geofenceProviderPackageName,
+ com.android.internal.R.array.config_locationProviderPackageNames,
+ mLocationHandler,
+ gpsProvider.getGpsGeofenceProxy(),
+ flpHardwareProvider.getGeofenceHardware());
+ if (provider == null) {
+ Slog.e(TAG, "Unable to bind FLP Geofence proxy.");
+ }
+ } else {
+ Slog.e(TAG, "FLP HAL not supported.");
}
- // bind to geofence provider
- GeofenceProxy provider = GeofenceProxy.createAndBind(mContext,
- com.android.internal.R.bool.config_enableGeofenceOverlay,
- com.android.internal.R.string.config_geofenceProviderPackageName,
- com.android.internal.R.array.config_locationProviderPackageNames,
- mLocationHandler,
- gpsProvider.getGpsGeofenceProxy(),
- flpHardwareProvider.getGeofenceHardware());
- if (provider == null) {
- Slog.e(TAG, "no geofence provider found");
+ String[] testProviderStrings = resources.getStringArray(
+ com.android.internal.R.array.config_testLocationProviders);
+ for (String testProviderString : testProviderStrings) {
+ String fragments[] = testProviderString.split(",");
+ String name = fragments[0].trim();
+ if (mProvidersByName.get(name) != null) {
+ throw new IllegalArgumentException("Provider \"" + name + "\" already exists");
+ }
+ ProviderProperties properties = new ProviderProperties(
+ Boolean.parseBoolean(fragments[1]) /* requiresNetwork */,
+ Boolean.parseBoolean(fragments[2]) /* requiresSatellite */,
+ Boolean.parseBoolean(fragments[3]) /* requiresCell */,
+ Boolean.parseBoolean(fragments[4]) /* hasMonetaryCost */,
+ Boolean.parseBoolean(fragments[5]) /* supportsAltitude */,
+ Boolean.parseBoolean(fragments[6]) /* supportsSpeed */,
+ Boolean.parseBoolean(fragments[7]) /* supportsBearing */,
+ Integer.parseInt(fragments[8]) /* powerRequirement */,
+ Integer.parseInt(fragments[9]) /* accuracy */);
+ addTestProviderLocked(name, properties);
}
}
@@ -571,7 +601,7 @@ public class LocationManagerService extends ILocationManager.Stub {
if (isAllowedByCurrentUserSettingsLocked(updateRecord.mProvider)) {
requestingLocation = true;
LocationProviderInterface locationProvider
- = mProvidersByName.get(updateRecord.mProvider);
+ = mProvidersByName.get(updateRecord.mProvider);
ProviderProperties properties = locationProvider != null
? locationProvider.getProperties() : null;
if (properties != null
@@ -816,7 +846,7 @@ public class LocationManagerService extends ILocationManager.Stub {
long identity = Binder.clearCallingIdentity();
receiver.decrementPendingBroadcastsLocked();
Binder.restoreCallingIdentity(identity);
- }
+ }
}
}
}
@@ -1291,13 +1321,18 @@ public class LocationManagerService extends ILocationManager.Stub {
if (!records.contains(this)) {
records.add(this);
}
+
+ // Update statistics for historical location requests by package/provider
+ mRequestStatistics.startRequesting(
+ mReceiver.mPackageName, provider, request.getInterval());
}
/**
- * Method to be called when a record will no longer be used. Calling this multiple times
- * must have the same effect as calling it once.
+ * Method to be called when a record will no longer be used.
*/
void disposeLocked(boolean removeReceiver) {
+ mRequestStatistics.stopRequesting(mReceiver.mPackageName, mProvider);
+
// remove from mRecordsByProvider
ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider);
if (globalRecords != null) {
@@ -1544,6 +1579,7 @@ public class LocationManagerService extends ILocationManager.Stub {
if (oldRecords != null) {
// Call dispose() on the obsolete update records.
for (UpdateRecord record : oldRecords.values()) {
+ // Update statistics for historical location requests by package/provider
record.disposeLocked(false);
}
// Accumulate providers
@@ -1765,7 +1801,7 @@ public class LocationManagerService extends ILocationManager.Stub {
@Override
public ProviderProperties getProviderProperties(String provider) {
if (mProvidersByName.get(provider) == null) {
- return null;
+ return null;
}
checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(),
@@ -1782,10 +1818,6 @@ public class LocationManagerService extends ILocationManager.Stub {
@Override
public boolean isProviderEnabled(String provider) {
- // TODO: remove this check in next release, see b/10696351
- checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(),
- provider);
-
// Fused provider is accessed indirectly via criteria rather than the provider-based APIs,
// so we discourage its use
if (LocationManager.FUSED_PROVIDER.equals(provider)) return false;
@@ -1815,10 +1847,10 @@ public class LocationManagerService extends ILocationManager.Stub {
return true;
}
if (mGeocodeProvider != null) {
- if (doesPackageHaveUid(uid, mGeocodeProvider.getConnectedPackageName())) return true;
+ if (doesUidHavePackage(uid, mGeocodeProvider.getConnectedPackageName())) return true;
}
for (LocationProviderProxy proxy : mProxyProviders) {
- if (doesPackageHaveUid(uid, proxy.getConnectedPackageName())) return true;
+ if (doesUidHavePackage(uid, proxy.getConnectedPackageName())) return true;
}
return false;
}
@@ -1844,19 +1876,23 @@ public class LocationManagerService extends ILocationManager.Stub {
"or UID of a currently bound location provider");
}
- private boolean doesPackageHaveUid(int uid, String packageName) {
+ /**
+ * Returns true if the given package belongs to the given uid.
+ */
+ private boolean doesUidHavePackage(int uid, String packageName) {
if (packageName == null) {
return false;
}
- try {
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName, 0);
- if (appInfo.uid != uid) {
- return false;
- }
- } catch (NameNotFoundException e) {
+ String[] packageNames = mPackageManager.getPackagesForUid(uid);
+ if (packageNames == null) {
return false;
}
- return true;
+ for (String name : packageNames) {
+ if (packageName.equals(name)) {
+ return true;
+ }
+ }
+ return false;
}
@Override
@@ -1968,7 +2004,7 @@ public class LocationManagerService extends ILocationManager.Stub {
// Fetch latest status update time
long newStatusUpdateTime = p.getStatusUpdateTime();
- // Get latest status
+ // Get latest status
Bundle extras = new Bundle();
int status = p.getStatus(extras);
@@ -2182,7 +2218,7 @@ public class LocationManagerService extends ILocationManager.Stub {
}
if (mContext.checkCallingPermission(ACCESS_MOCK_LOCATION) !=
- PackageManager.PERMISSION_GRANTED) {
+ PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires ACCESS_MOCK_LOCATION permission");
}
}
@@ -2197,7 +2233,6 @@ public class LocationManagerService extends ILocationManager.Stub {
long identity = Binder.clearCallingIdentity();
synchronized (mLock) {
- MockProvider provider = new MockProvider(name, this, properties);
// remove the real provider if we are replacing GPS or network provider
if (LocationManager.GPS_PROVIDER.equals(name)
|| LocationManager.NETWORK_PROVIDER.equals(name)
@@ -2207,22 +2242,34 @@ public class LocationManagerService extends ILocationManager.Stub {
removeProviderLocked(p);
}
}
- if (mProvidersByName.get(name) != null) {
- throw new IllegalArgumentException("Provider \"" + name + "\" already exists");
- }
- addProviderLocked(provider);
- mMockProviders.put(name, provider);
- mLastLocation.put(name, null);
- mLastLocationCoarseInterval.put(name, null);
+ addTestProviderLocked(name, properties);
updateProvidersLocked();
}
Binder.restoreCallingIdentity(identity);
}
+ private void addTestProviderLocked(String name, ProviderProperties properties) {
+ if (mProvidersByName.get(name) != null) {
+ throw new IllegalArgumentException("Provider \"" + name + "\" already exists");
+ }
+ MockProvider provider = new MockProvider(name, this, properties);
+ addProviderLocked(provider);
+ mMockProviders.put(name, provider);
+ mLastLocation.put(name, null);
+ mLastLocationCoarseInterval.put(name, null);
+ }
+
@Override
public void removeTestProvider(String provider) {
checkMockPermissionsSafe();
synchronized (mLock) {
+
+ // These methods can't be called after removing the test provider, so first make sure
+ // we don't leave anything dangling.
+ clearTestProviderEnabled(provider);
+ clearTestProviderLocation(provider);
+ clearTestProviderStatus(provider);
+
MockProvider mockProvider = mMockProviders.remove(provider);
if (mockProvider == null) {
throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
@@ -2354,13 +2401,20 @@ public class LocationManagerService extends ILocationManager.Stub {
for (Receiver receiver : mReceivers.values()) {
pw.println(" " + receiver);
}
- pw.println(" Records by Provider:");
+ pw.println(" Active Records by Provider:");
for (Map.Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
pw.println(" " + entry.getKey() + ":");
for (UpdateRecord record : entry.getValue()) {
pw.println(" " + record);
}
}
+ pw.println(" Historical Records by Provider:");
+ for (Map.Entry<PackageProviderKey, PackageStatistics> entry
+ : mRequestStatistics.statistics.entrySet()) {
+ PackageProviderKey key = entry.getKey();
+ PackageStatistics stats = entry.getValue();
+ pw.println(" " + key.packageName + ": " + key.providerName + ": " + stats);
+ }
pw.println(" Last Known Locations:");
for (Map.Entry<String, Location> entry : mLastLocation.entrySet()) {
String provider = entry.getKey();
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 35e7afa..0d2cee8 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -16,7 +16,6 @@
package com.android.server;
-import android.app.ActivityManagerNative;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -29,11 +28,13 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
-import android.media.AudioManager;
-import android.media.AudioService;
import android.os.Binder;
import android.os.Environment;
+import android.os.IBinder;
import android.os.RemoteException;
+import android.os.storage.IMountService;
+import android.os.ServiceManager;
+import android.os.storage.StorageManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -82,6 +83,7 @@ public class LockSettingsService extends ILockSettings.Stub {
private final Context mContext;
private LockPatternUtils mLockPatternUtils;
+ private boolean mFirstCallToVold;
public LockSettingsService(Context context) {
mContext = context;
@@ -89,6 +91,7 @@ public class LockSettingsService extends ILockSettings.Stub {
mOpenHelper = new DatabaseHelper(mContext);
mLockPatternUtils = new LockPatternUtils(context);
+ mFirstCallToVold = true;
}
public void systemReady() {
@@ -350,6 +353,51 @@ public class LockSettingsService extends ILockSettings.Stub {
}
@Override
+ public boolean checkVoldPassword(int userId) throws RemoteException {
+ if (!mFirstCallToVold) {
+ return false;
+ }
+ mFirstCallToVold = false;
+
+ checkPasswordReadPermission(userId);
+
+ // There's no guarantee that this will safely connect, but if it fails
+ // we will simply show the lock screen when we shouldn't, so relatively
+ // benign. There is an outside chance something nasty would happen if
+ // this service restarted before vold stales out the password in this
+ // case. The nastiness is limited to not showing the lock screen when
+ // we should, within the first minute of decrypting the phone if this
+ // service can't connect to vold, it restarts, and then the new instance
+ // does successfully connect.
+ final IMountService service = getMountService();
+ String password = service.getPassword();
+ service.clearPassword();
+ if (password == null) {
+ return false;
+ }
+
+ try {
+ if (mLockPatternUtils.isLockPatternEnabled()) {
+ if (checkPattern(password, userId)) {
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ }
+
+ try {
+ if (mLockPatternUtils.isLockPasswordEnabled()) {
+ if (checkPassword(password, userId)) {
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ }
+
+ return false;
+ }
+
+ @Override
public void removeUser(int userId) {
checkWritePermission(userId);
@@ -527,4 +575,12 @@ public class LockSettingsService extends ILockSettings.Stub {
Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
Secure.LOCK_SCREEN_OWNER_INFO
};
+
+ private IMountService getMountService() {
+ final IBinder service = ServiceManager.getService("mount");
+ if (service != null) {
+ return IMountService.Stub.asInterface(service);
+ }
+ return null;
+ }
}
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 1fa0735..d5f045e 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -52,6 +52,7 @@ import android.os.storage.IMountServiceListener;
import android.os.storage.IMountShutdownObserver;
import android.os.storage.IObbActionListener;
import android.os.storage.OnObbStateChangeListener;
+import android.os.storage.StorageManager;
import android.os.storage.StorageResultCode;
import android.os.storage.StorageVolume;
import android.text.TextUtils;
@@ -73,6 +74,8 @@ import com.android.server.pm.UserManagerService;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.DecoderException;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
@@ -80,6 +83,7 @@ import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
@@ -108,6 +112,9 @@ import javax.crypto.spec.PBEKeySpec;
class MountService extends IMountService.Stub
implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
+ // Static direct instance pointer for the tightly-coupled idle service to use
+ static MountService sSelf = null;
+
// TODO: listen for user creation/deletion
private static final boolean LOCAL_LOGD = false;
@@ -152,6 +159,7 @@ class MountService extends IMountService.Stub
public static final int VolumeListResult = 110;
public static final int AsecListResult = 111;
public static final int StorageUsersListResult = 112;
+ public static final int CryptfsGetfieldResult = 113;
/*
* 200 series - Requestion action has been successfully completed.
@@ -187,8 +195,14 @@ class MountService extends IMountService.Stub
public static final int FstrimCompleted = 700;
}
+ /** List of crypto types.
+ * These must match CRYPT_TYPE_XXX in cryptfs.h AND their
+ * corresponding commands in CommandListener.cpp */
+ public static final String[] CRYPTO_TYPES
+ = { "password", "default", "pattern", "pin" };
+
private final Context mContext;
- private NativeDaemonConnector mConnector;
+ private final NativeDaemonConnector mConnector;
private final Object mVolumesLock = new Object();
@@ -346,6 +360,7 @@ class MountService extends IMountService.Stub
private static final int H_UNMOUNT_PM_DONE = 2;
private static final int H_UNMOUNT_MS = 3;
private static final int H_SYSTEM_READY = 4;
+ private static final int H_FSTRIM = 5;
private static final int RETRY_UNMOUNT_DELAY = 30; // in ms
private static final int MAX_UNMOUNT_RETRIES = 4;
@@ -514,6 +529,24 @@ class MountService extends IMountService.Stub
}
break;
}
+ case H_FSTRIM: {
+ waitForReady();
+ Slog.i(TAG, "Running fstrim idle maintenance");
+ try {
+ // This method must be run on the main (handler) thread,
+ // so it is safe to directly call into vold.
+ mConnector.execute("fstrim", "dotrim");
+ EventLogTags.writeFstrimStart(SystemClock.elapsedRealtime());
+ } catch (NativeDaemonConnectorException ndce) {
+ Slog.e(TAG, "Failed to run fstrim!");
+ }
+ // invoke the completion callback, if any
+ Runnable callback = (Runnable) msg.obj;
+ if (callback != null) {
+ callback.run();
+ }
+ break;
+ }
}
}
};
@@ -543,6 +576,14 @@ class MountService extends IMountService.Stub
}
}
+ private boolean isReady() {
+ try {
+ return mConnectedSignal.await(0, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ return false;
+ }
+ }
+
private void handleSystemReady() {
// Snapshot current volume states since it's not safe to call into vold
// while holding locks.
@@ -628,27 +669,6 @@ class MountService extends IMountService.Stub
}
};
- private final BroadcastReceiver mIdleMaintenanceReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- waitForReady();
- String action = intent.getAction();
- // Since fstrim will be run on a daily basis we do not expect
- // fstrim to be too long, so it is not interruptible. We will
- // implement interruption only in case we see issues.
- if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)) {
- try {
- // This method runs on the handler thread,
- // so it is safe to directly call into vold.
- mConnector.execute("fstrim", "dotrim");
- EventLogTags.writeFstrimStart(SystemClock.elapsedRealtime());
- } catch (NativeDaemonConnectorException ndce) {
- Slog.e(TAG, "Failed to run fstrim!");
- }
- }
- }
- };
-
private final class MountServiceBinderListener implements IBinder.DeathRecipient {
final IMountServiceListener mListener;
@@ -666,6 +686,10 @@ class MountService extends IMountService.Stub
}
}
+ void runIdleMaintenance(Runnable callback) {
+ mHandler.sendMessage(mHandler.obtainMessage(H_FSTRIM, callback));
+ }
+
private void doShareUnshareVolume(String path, String method, boolean enable) {
// TODO: Add support for multiple share methods
if (!method.equals("ums")) {
@@ -803,6 +827,13 @@ class MountService extends IMountService.Stub
/**
* Callback from NativeDaemonConnector
*/
+ public boolean onCheckHoldWakeLock(int code) {
+ return false;
+ }
+
+ /**
+ * Callback from NativeDaemonConnector
+ */
public boolean onEvent(int code, String raw, String[] cooked) {
if (DEBUG_EVENTS) {
StringBuilder builder = new StringBuilder();
@@ -1357,6 +1388,8 @@ class MountService extends IMountService.Stub
* @param context Binder context for this service
*/
public MountService(Context context) {
+ sSelf = this;
+
mContext = context;
synchronized (mVolumesLock) {
@@ -1383,12 +1416,6 @@ class MountService extends IMountService.Stub
mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler);
}
- // Watch for idle maintenance changes
- IntentFilter idleMaintenanceFilter = new IntentFilter();
- idleMaintenanceFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
- mContext.registerReceiverAsUser(mIdleMaintenanceReceiver, UserHandle.ALL,
- idleMaintenanceFilter, null, mHandler);
-
// Add OBB Action Handler to MountService thread.
mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());
@@ -1397,7 +1424,8 @@ class MountService extends IMountService.Stub
* amount of containers we'd ever expect to have. This keeps an
* "asec list" from blocking a thread repeatedly.
*/
- mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25);
+ mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25,
+ null);
Thread thread = new Thread(mConnector, VOLD_TAG);
thread.start();
@@ -2056,6 +2084,27 @@ class MountService extends IMountService.Stub
}
}
+ private String toHex(String password) {
+ if (password == null) {
+ return new String();
+ }
+ byte[] bytes = password.getBytes(StandardCharsets.UTF_8);
+ return new String(Hex.encodeHex(bytes));
+ }
+
+ private String fromHex(String hexPassword) {
+ if (hexPassword == null) {
+ return null;
+ }
+
+ try {
+ byte[] bytes = Hex.decodeHex(hexPassword.toCharArray());
+ return new String(bytes, StandardCharsets.UTF_8);
+ } catch (DecoderException e) {
+ return null;
+ }
+ }
+
@Override
public int decryptStorage(String password) {
if (TextUtils.isEmpty(password)) {
@@ -2073,7 +2122,7 @@ class MountService extends IMountService.Stub
final NativeDaemonEvent event;
try {
- event = mConnector.execute("cryptfs", "checkpw", new SensitiveArg(password));
+ event = mConnector.execute("cryptfs", "checkpw", new SensitiveArg(toHex(password)));
final int code = Integer.parseInt(event.getMessage());
if (code == 0) {
@@ -2097,8 +2146,8 @@ class MountService extends IMountService.Stub
}
}
- public int encryptStorage(String password) {
- if (TextUtils.isEmpty(password)) {
+ public int encryptStorage(int type, String password) {
+ if (TextUtils.isEmpty(password) && type != StorageManager.CRYPT_TYPE_DEFAULT) {
throw new IllegalArgumentException("password cannot be empty");
}
@@ -2112,7 +2161,8 @@ class MountService extends IMountService.Stub
}
try {
- mConnector.execute("cryptfs", "enablecrypto", "inplace", new SensitiveArg(password));
+ mConnector.execute("cryptfs", "enablecrypto", "inplace", CRYPTO_TYPES[type],
+ new SensitiveArg(toHex(password)));
} catch (NativeDaemonConnectorException e) {
// Encryption failed
return e.getCode();
@@ -2121,11 +2171,11 @@ class MountService extends IMountService.Stub
return 0;
}
- public int changeEncryptionPassword(String password) {
- if (TextUtils.isEmpty(password)) {
- throw new IllegalArgumentException("password cannot be empty");
- }
-
+ /** Set the password for encrypting the master key.
+ * @param type One of the CRYPTO_TYPE_XXX consts defined in StorageManager.
+ * @param password The password to set.
+ */
+ public int changeEncryptionPassword(int type, String password) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"no permission to access the crypt keeper");
@@ -2137,7 +2187,8 @@ class MountService extends IMountService.Stub
final NativeDaemonEvent event;
try {
- event = mConnector.execute("cryptfs", "changepw", new SensitiveArg(password));
+ event = mConnector.execute("cryptfs", "changepw", CRYPTO_TYPES[type],
+ new SensitiveArg(toHex(password)));
return Integer.parseInt(event.getMessage());
} catch (NativeDaemonConnectorException e) {
// Encryption failed
@@ -2170,7 +2221,7 @@ class MountService extends IMountService.Stub
final NativeDaemonEvent event;
try {
- event = mConnector.execute("cryptfs", "verifypw", new SensitiveArg(password));
+ event = mConnector.execute("cryptfs", "verifypw", new SensitiveArg(toHex(password)));
Slog.i(TAG, "cryptfs verifypw => " + event.getMessage());
return Integer.parseInt(event.getMessage());
} catch (NativeDaemonConnectorException e) {
@@ -2179,6 +2230,101 @@ class MountService extends IMountService.Stub
}
}
+ /**
+ * Get the type of encryption used to encrypt the master key.
+ * @return The type, one of the CRYPT_TYPE_XXX consts from StorageManager.
+ */
+ @Override
+ public int getPasswordType() throws RemoteException {
+
+ waitForReady();
+
+ final NativeDaemonEvent event;
+ try {
+ event = mConnector.execute("cryptfs", "getpwtype");
+ for (int i = 0; i < CRYPTO_TYPES.length; ++i) {
+ if (CRYPTO_TYPES[i].equals(event.getMessage()))
+ return i;
+ }
+
+ throw new IllegalStateException("unexpected return from cryptfs");
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ /**
+ * Set a field in the crypto header.
+ * @param field field to set
+ * @param contents contents to set in field
+ */
+ @Override
+ public void setField(String field, String contents) throws RemoteException {
+
+ waitForReady();
+
+ final NativeDaemonEvent event;
+ try {
+ event = mConnector.execute("cryptfs", "setfield", field, contents);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ /**
+ * Gets a field from the crypto header.
+ * @param field field to get
+ * @return contents of field
+ */
+ @Override
+ public String getField(String field) throws RemoteException {
+
+ waitForReady();
+
+ final NativeDaemonEvent event;
+ try {
+ final String[] contents = NativeDaemonEvent.filterMessageList(
+ mConnector.executeForList("cryptfs", "getfield", field),
+ VoldResponseCode.CryptfsGetfieldResult);
+ String result = new String();
+ for (String content : contents) {
+ result += content;
+ }
+ return result;
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ @Override
+ public String getPassword() throws RemoteException {
+ if (!isReady()) {
+ return new String();
+ }
+
+ final NativeDaemonEvent event;
+ try {
+ event = mConnector.execute("cryptfs", "getpw");
+ return fromHex(event.getMessage());
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ @Override
+ public void clearPassword() throws RemoteException {
+ if (!isReady()) {
+ return;
+ }
+
+ final NativeDaemonEvent event;
+ try {
+ event = mConnector.execute("cryptfs", "clearpw");
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
@Override
public int mkdirs(String callingPkg, String appPath) {
final int userId = UserHandle.getUserId(Binder.getCallingUid());
diff --git a/services/core/java/com/android/server/MountServiceIdler.java b/services/core/java/com/android/server/MountServiceIdler.java
new file mode 100644
index 0000000..8b19321
--- /dev/null
+++ b/services/core/java/com/android/server/MountServiceIdler.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.maintenance.IdleService;
+import android.util.Slog;
+
+public class MountServiceIdler extends IdleService {
+ private static final String TAG = "MountServiceIdler";
+
+ private Runnable mFinishCallback = new Runnable() {
+ @Override
+ public void run() {
+ Slog.i(TAG, "Got mount service completion callback");
+ finishIdle();
+ }
+ };
+
+ @Override
+ public boolean onIdleStart() {
+ // The mount service will run an fstrim operation asynchronously
+ // on a designated separate thread, so we provide it with a callback
+ // that lets us cleanly end our idle timeslice. It's safe to call
+ // finishIdle() from any thread.
+ MountService ms = MountService.sSelf;
+ if (ms != null) {
+ ms.runIdleMaintenance(mFinishCallback);
+ }
+ return ms != null;
+ }
+
+ @Override
+ public void onIdleStop() {
+ }
+}
diff --git a/services/core/java/com/android/server/NativeDaemonConnector.java b/services/core/java/com/android/server/NativeDaemonConnector.java
index 417d6d8..96f9ab0 100644
--- a/services/core/java/com/android/server/NativeDaemonConnector.java
+++ b/services/core/java/com/android/server/NativeDaemonConnector.java
@@ -20,7 +20,9 @@ import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.Build;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
+import android.os.PowerManager;
import android.os.SystemClock;
import android.util.LocalLog;
import android.util.Slog;
@@ -48,6 +50,8 @@ import java.util.LinkedList;
final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
private static final boolean LOGD = false;
+ private final static boolean VDBG = false;
+
private final String TAG;
private String mSocket;
@@ -56,6 +60,10 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
private final ResponseQueue mResponseQueue;
+ private final PowerManager.WakeLock mWakeLock;
+
+ private final Looper mLooper;
+
private INativeDaemonConnectorCallbacks mCallbacks;
private Handler mCallbackHandler;
@@ -70,10 +78,22 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
private final int BUFFER_SIZE = 4096;
NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
- int responseQueueSize, String logTag, int maxLogSize) {
+ int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) {
+ this(callbacks, socket, responseQueueSize, logTag, maxLogSize, wl,
+ FgThread.get().getLooper());
+ }
+
+ NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
+ int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl,
+ Looper looper) {
mCallbacks = callbacks;
mSocket = socket;
mResponseQueue = new ResponseQueue(responseQueueSize);
+ mWakeLock = wl;
+ if (mWakeLock != null) {
+ mWakeLock.setReferenceCounted(true);
+ }
+ mLooper = looper;
mSequenceNumber = new AtomicInteger(0);
TAG = logTag != null ? logTag : "NativeDaemonConnector";
mLocalLog = new LocalLog(maxLogSize);
@@ -81,7 +101,7 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
@Override
public void run() {
- mCallbackHandler = new Handler(FgThread.get().getLooper(), this);
+ mCallbackHandler = new Handler(mLooper, this);
while (true) {
try {
@@ -102,6 +122,10 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
}
} catch (Exception e) {
loge("Error handling '" + event + "': " + e);
+ } finally {
+ if (mCallbacks.onCheckHoldWakeLock(msg.what) && mWakeLock != null) {
+ mWakeLock.release();
+ }
}
return true;
}
@@ -154,18 +178,30 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
buffer, start, i - start, StandardCharsets.UTF_8);
log("RCV <- {" + rawEvent + "}");
+ boolean releaseWl = false;
try {
final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
rawEvent);
if (event.isClassUnsolicited()) {
// TODO: migrate to sending NativeDaemonEvent instances
- mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
- event.getCode(), event.getRawEvent()));
+ if (mCallbacks.onCheckHoldWakeLock(event.getCode())
+ && mWakeLock != null) {
+ mWakeLock.acquire();
+ releaseWl = true;
+ }
+ if (mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
+ event.getCode(), event.getRawEvent()))) {
+ releaseWl = false;
+ }
} else {
mResponseQueue.add(event.getCmdNumber(), event);
}
} catch (IllegalArgumentException e) {
log("Problem parsing message: " + rawEvent + " - " + e);
+ } finally {
+ if (releaseWl) {
+ mWakeLock.acquire();
+ }
}
start = i + 1;
@@ -375,7 +411,7 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
loge("timed-out waiting for response to " + logCmd);
throw new NativeDaemonFailureException(logCmd, event);
}
- log("RMV <- {" + event + "}");
+ if (VDBG) log("RMV <- {" + event + "}");
events.add(event);
} while (event.isClassContinue());
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index e83d376..cf91782 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -16,10 +16,12 @@
package com.android.server;
+import static android.Manifest.permission.CHANGE_NETWORK_STATE;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.SHUTDOWN;
import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_ALL;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.net.TrafficStats.UID_TETHERING;
@@ -36,6 +38,7 @@ import static com.android.server.NetworkManagementService.NetdResponseCode.TtyLi
import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
import android.content.Context;
+import android.net.ConnectivityManager;
import android.net.INetworkManagementEventObserver;
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
@@ -47,13 +50,17 @@ import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Handler;
+import android.os.INetworkActivityListener;
import android.os.INetworkManagementService;
+import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.PhoneStateListener;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -80,7 +87,6 @@ import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -140,19 +146,25 @@ public class NetworkManagementService extends INetworkManagementService.Stub
public static final int InterfaceDnsServerInfo = 615;
}
+ static final int DAEMON_MSG_MOBILE_CONN_REAL_TIME_INFO = 1;
+
/**
* Binder context for this service
*/
- private Context mContext;
+ private final Context mContext;
/**
* connector object for communicating with netd
*/
- private NativeDaemonConnector mConnector;
+ private final NativeDaemonConnector mConnector;
+
+ private final Handler mFgHandler;
+ private final Handler mDaemonHandler;
+ private final PhoneStateListener mPhoneStateListener;
- private final Handler mMainHandler = new Handler();
+ private IBatteryStats mBatteryStats;
- private Thread mThread;
+ private final Thread mThread;
private CountDownLatch mConnectedSignal = new CountDownLatch(1);
private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
@@ -172,12 +184,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub
/** Set of interfaces with active idle timers. */
private static class IdleTimerParams {
public final int timeout;
- public final String label;
+ public final int type;
public int networkCount;
- IdleTimerParams(int timeout, String label) {
+ IdleTimerParams(int timeout, int type) {
this.timeout = timeout;
- this.label = label;
+ this.type = type;
this.networkCount = 1;
}
}
@@ -186,6 +198,13 @@ public class NetworkManagementService extends INetworkManagementService.Stub
private volatile boolean mBandwidthControlEnabled;
private volatile boolean mFirewallEnabled;
+ private boolean mMobileActivityFromRadio = false;
+ private int mLastPowerStateFromRadio = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+
+ private final RemoteCallbackList<INetworkActivityListener> mNetworkActivityListeners =
+ new RemoteCallbackList<INetworkActivityListener>();
+ private boolean mNetworkActive;
+
/**
* Constructs a new NetworkManagementService instance
*
@@ -194,14 +213,39 @@ public class NetworkManagementService extends INetworkManagementService.Stub
private NetworkManagementService(Context context, String socket) {
mContext = context;
+ // make sure this is on the same looper as our NativeDaemonConnector for sync purposes
+ mFgHandler = new Handler(FgThread.get().getLooper());
+
if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
+ mConnector = null;
+ mThread = null;
+ mDaemonHandler = null;
+ mPhoneStateListener = null;
return;
}
+ // Don't need this wake lock, since we now have a time stamp for when
+ // the network actually went inactive. (It might be nice to still do this,
+ // but I don't want to do it through the power manager because that pollutes the
+ // battery stats history with pointless noise.)
+ //PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ PowerManager.WakeLock wl = null; //pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, NETD_TAG);
+
mConnector = new NativeDaemonConnector(
- new NetdCallbackReceiver(), socket, 10, NETD_TAG, 160);
+ new NetdCallbackReceiver(), socket, 10, NETD_TAG, 160, wl,
+ FgThread.get().getLooper());
mThread = new Thread(mConnector, NETD_TAG);
+ mDaemonHandler = new Handler(FgThread.get().getLooper());
+ mPhoneStateListener = new PhoneStateListener(mDaemonHandler.getLooper()) {
+ public void onDataConnectionRealTimeInfoChanged(
+ DataConnectionRealTimeInfo dcRtInfo) {
+ // Disabled for now, until we are getting good data.
+ //notifyInterfaceClassActivity(ConnectivityManager.TYPE_MOBILE,
+ // dcRtInfo.getDcPowerState(), dcRtInfo.getTime(), true);
+ }
+ };
+
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
}
@@ -227,6 +271,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub
if (DBG) Slog.d(TAG, "Prepared");
}
+ private IBatteryStats getBatteryStats() {
+ synchronized (this) {
+ if (mBatteryStats != null) {
+ return mBatteryStats;
+ }
+ mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+ BatteryStats.SERVICE_NAME));
+ return mBatteryStats;
+ }
+ }
+
@Override
public void registerObserver(INetworkManagementEventObserver observer) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
@@ -244,14 +299,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub
*/
private void notifyInterfaceStatusChanged(String iface, boolean up) {
final int length = mObservers.beginBroadcast();
- for (int i = 0; i < length; i++) {
- try {
- mObservers.getBroadcastItem(i).interfaceStatusChanged(iface, up);
- } catch (RemoteException e) {
- } catch (RuntimeException e) {
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ mObservers.getBroadcastItem(i).interfaceStatusChanged(iface, up);
+ } catch (RemoteException e) {
+ } catch (RuntimeException e) {
+ }
}
+ } finally {
+ mObservers.finishBroadcast();
}
- mObservers.finishBroadcast();
}
/**
@@ -260,14 +318,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub
*/
private void notifyInterfaceLinkStateChanged(String iface, boolean up) {
final int length = mObservers.beginBroadcast();
- for (int i = 0; i < length; i++) {
- try {
- mObservers.getBroadcastItem(i).interfaceLinkStateChanged(iface, up);
- } catch (RemoteException e) {
- } catch (RuntimeException e) {
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ mObservers.getBroadcastItem(i).interfaceLinkStateChanged(iface, up);
+ } catch (RemoteException e) {
+ } catch (RuntimeException e) {
+ }
}
+ } finally {
+ mObservers.finishBroadcast();
}
- mObservers.finishBroadcast();
}
/**
@@ -275,14 +336,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub
*/
private void notifyInterfaceAdded(String iface) {
final int length = mObservers.beginBroadcast();
- for (int i = 0; i < length; i++) {
- try {
- mObservers.getBroadcastItem(i).interfaceAdded(iface);
- } catch (RemoteException e) {
- } catch (RuntimeException e) {
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ mObservers.getBroadcastItem(i).interfaceAdded(iface);
+ } catch (RemoteException e) {
+ } catch (RuntimeException e) {
+ }
}
+ } finally {
+ mObservers.finishBroadcast();
}
- mObservers.finishBroadcast();
}
/**
@@ -295,14 +359,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub
mActiveQuotas.remove(iface);
final int length = mObservers.beginBroadcast();
- for (int i = 0; i < length; i++) {
- try {
- mObservers.getBroadcastItem(i).interfaceRemoved(iface);
- } catch (RemoteException e) {
- } catch (RuntimeException e) {
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ mObservers.getBroadcastItem(i).interfaceRemoved(iface);
+ } catch (RemoteException e) {
+ } catch (RuntimeException e) {
+ }
}
+ } finally {
+ mObservers.finishBroadcast();
}
- mObservers.finishBroadcast();
}
/**
@@ -310,29 +377,83 @@ public class NetworkManagementService extends INetworkManagementService.Stub
*/
private void notifyLimitReached(String limitName, String iface) {
final int length = mObservers.beginBroadcast();
- for (int i = 0; i < length; i++) {
- try {
- mObservers.getBroadcastItem(i).limitReached(limitName, iface);
- } catch (RemoteException e) {
- } catch (RuntimeException e) {
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ mObservers.getBroadcastItem(i).limitReached(limitName, iface);
+ } catch (RemoteException e) {
+ } catch (RuntimeException e) {
+ }
}
+ } finally {
+ mObservers.finishBroadcast();
}
- mObservers.finishBroadcast();
}
/**
* Notify our observers of a change in the data activity state of the interface
*/
- private void notifyInterfaceClassActivity(String label, boolean active) {
- final int length = mObservers.beginBroadcast();
- for (int i = 0; i < length; i++) {
+ private void notifyInterfaceClassActivity(int type, int powerState, long tsNanos,
+ boolean fromRadio) {
+ final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type);
+ if (isMobile) {
+ if (!fromRadio) {
+ if (mMobileActivityFromRadio) {
+ // If this call is not coming from a report from the radio itself, but we
+ // have previously received reports from the radio, then we will take the
+ // power state to just be whatever the radio last reported.
+ powerState = mLastPowerStateFromRadio;
+ }
+ } else {
+ mMobileActivityFromRadio = true;
+ }
+ if (mLastPowerStateFromRadio != powerState) {
+ mLastPowerStateFromRadio = powerState;
+ try {
+ getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ boolean isActive = powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM
+ || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
+
+ if (!isMobile || fromRadio || !mMobileActivityFromRadio) {
+ // Report the change in data activity. We don't do this if this is a change
+ // on the mobile network, that is not coming from the radio itself, and we
+ // have previously seen change reports from the radio. In that case only
+ // the radio is the authority for the current state.
+ final int length = mObservers.beginBroadcast();
try {
- mObservers.getBroadcastItem(i).interfaceClassDataActivityChanged(label, active);
- } catch (RemoteException e) {
- } catch (RuntimeException e) {
+ for (int i = 0; i < length; i++) {
+ try {
+ mObservers.getBroadcastItem(i).interfaceClassDataActivityChanged(
+ Integer.toString(type), isActive, tsNanos);
+ } catch (RemoteException e) {
+ } catch (RuntimeException e) {
+ }
+ }
+ } finally {
+ mObservers.finishBroadcast();
}
}
- mObservers.finishBroadcast();
+
+ boolean report = false;
+ synchronized (mIdleTimerLock) {
+ if (mActiveIdleTimers.isEmpty()) {
+ // If there are no idle timers, we are not monitoring activity, so we
+ // are always considered active.
+ isActive = true;
+ }
+ if (mNetworkActive != isActive) {
+ mNetworkActive = isActive;
+ report = isActive;
+ }
+ }
+ if (report) {
+ reportNetworkActive();
+ }
}
/**
@@ -360,8 +481,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
if (mBandwidthControlEnabled) {
try {
- IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME))
- .noteNetworkStatsEnabled();
+ getBatteryStats().noteNetworkStatsEnabled();
} catch (RemoteException e) {
}
}
@@ -408,14 +528,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub
*/
private void notifyAddressUpdated(String iface, LinkAddress address) {
final int length = mObservers.beginBroadcast();
- for (int i = 0; i < length; i++) {
- try {
- mObservers.getBroadcastItem(i).addressUpdated(iface, address);
- } catch (RemoteException e) {
- } catch (RuntimeException e) {
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ mObservers.getBroadcastItem(i).addressUpdated(iface, address);
+ } catch (RemoteException e) {
+ } catch (RuntimeException e) {
+ }
}
+ } finally {
+ mObservers.finishBroadcast();
}
- mObservers.finishBroadcast();
}
/**
@@ -423,14 +546,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub
*/
private void notifyAddressRemoved(String iface, LinkAddress address) {
final int length = mObservers.beginBroadcast();
- for (int i = 0; i < length; i++) {
- try {
- mObservers.getBroadcastItem(i).addressRemoved(iface, address);
- } catch (RemoteException e) {
- } catch (RuntimeException e) {
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ mObservers.getBroadcastItem(i).addressRemoved(iface, address);
+ } catch (RemoteException e) {
+ } catch (RuntimeException e) {
+ }
}
+ } finally {
+ mObservers.finishBroadcast();
}
- mObservers.finishBroadcast();
}
/**
@@ -438,14 +564,18 @@ public class NetworkManagementService extends INetworkManagementService.Stub
*/
private void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
final int length = mObservers.beginBroadcast();
- for (int i = 0; i < length; i++) {
- try {
- mObservers.getBroadcastItem(i).interfaceDnsServerInfo(iface, lifetime, addresses);
- } catch (RemoteException e) {
- } catch (RuntimeException e) {
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ mObservers.getBroadcastItem(i).interfaceDnsServerInfo(iface, lifetime,
+ addresses);
+ } catch (RemoteException e) {
+ } catch (RuntimeException e) {
+ }
}
+ } finally {
+ mObservers.finishBroadcast();
}
- mObservers.finishBroadcast();
}
//
@@ -461,7 +591,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
mConnectedSignal.countDown();
mConnectedSignal = null;
} else {
- mMainHandler.post(new Runnable() {
+ mFgHandler.post(new Runnable() {
@Override
public void run() {
prepareNativeDaemon();
@@ -471,6 +601,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
@Override
+ public boolean onCheckHoldWakeLock(int code) {
+ return code == NetdResponseCode.InterfaceClassActivity;
+ }
+
+ @Override
public boolean onEvent(int code, String raw, String[] cooked) {
String errorMessage = String.format("Invalid event from daemon (%s)", raw);
switch (code) {
@@ -522,8 +657,18 @@ public class NetworkManagementService extends INetworkManagementService.Stub
if (cooked.length < 4 || !cooked[1].equals("IfaceClass")) {
throw new IllegalStateException(errorMessage);
}
+ long timestampNanos = 0;
+ if (cooked.length == 5) {
+ try {
+ timestampNanos = Long.parseLong(cooked[4]);
+ } catch(NumberFormatException ne) {}
+ } else {
+ timestampNanos = SystemClock.elapsedRealtimeNanos();
+ }
boolean isActive = cooked[2].equals("active");
- notifyInterfaceClassActivity(cooked[3], isActive);
+ notifyInterfaceClassActivity(Integer.parseInt(cooked[3]),
+ isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
+ : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, timestampNanos, false);
return true;
// break;
case NetdResponseCode.InterfaceAddressChange:
@@ -723,46 +868,25 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
@Override
- public void addRoute(String interfaceName, RouteInfo route) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- modifyRoute(interfaceName, ADD, route, DEFAULT);
+ public void addRoute(int netId, RouteInfo route) {
+ modifyRoute(netId, ADD, route);
}
@Override
- public void removeRoute(String interfaceName, RouteInfo route) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- modifyRoute(interfaceName, REMOVE, route, DEFAULT);
+ public void removeRoute(int netId, RouteInfo route) {
+ modifyRoute(netId, REMOVE, route);
}
- @Override
- public void addSecondaryRoute(String interfaceName, RouteInfo route) {
+ private void modifyRoute(int netId, String action, RouteInfo route) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- modifyRoute(interfaceName, ADD, route, SECONDARY);
- }
- @Override
- public void removeSecondaryRoute(String interfaceName, RouteInfo route) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- modifyRoute(interfaceName, REMOVE, route, SECONDARY);
- }
+ final Command cmd = new Command("network", "route", action, netId);
- private void modifyRoute(String interfaceName, String action, RouteInfo route, String type) {
- final Command cmd = new Command("interface", "route", action, interfaceName, type);
-
- // create triplet: dest-ip-addr prefixlength gateway-ip-addr
+ // create triplet: interface dest-ip-addr/prefixlength gateway-ip-addr
final LinkAddress la = route.getDestination();
- cmd.appendArg(la.getAddress().getHostAddress());
- cmd.appendArg(la.getNetworkPrefixLength());
-
- if (route.getGateway() == null) {
- if (la.getAddress() instanceof Inet4Address) {
- cmd.appendArg("0.0.0.0");
- } else {
- cmd.appendArg("::0");
- }
- } else {
- cmd.appendArg(route.getGateway().getHostAddress());
- }
+ cmd.appendArg(route.getInterface());
+ cmd.appendArg(la.getAddress().getHostAddress() + "/" + la.getNetworkPrefixLength());
+ cmd.appendArg(route.getGateway().getHostAddress());
try {
mConnector.execute(cmd);
@@ -1185,7 +1309,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
@Override
- public void addIdleTimer(String iface, int timeout, String label) {
+ public void addIdleTimer(String iface, int timeout, final int type) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
if (DBG) Slog.d(TAG, "Adding idletimer");
@@ -1199,11 +1323,24 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
try {
- mConnector.execute("idletimer", "add", iface, Integer.toString(timeout), label);
+ mConnector.execute("idletimer", "add", iface, Integer.toString(timeout),
+ Integer.toString(type));
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
- mActiveIdleTimers.put(iface, new IdleTimerParams(timeout, label));
+ mActiveIdleTimers.put(iface, new IdleTimerParams(timeout, type));
+
+ // Networks start up.
+ if (ConnectivityManager.isNetworkTypeMobile(type)) {
+ mNetworkActive = false;
+ }
+ mDaemonHandler.post(new Runnable() {
+ @Override public void run() {
+ notifyInterfaceClassActivity(type,
+ DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ SystemClock.elapsedRealtimeNanos(), false);
+ }
+ });
}
}
@@ -1214,18 +1351,25 @@ public class NetworkManagementService extends INetworkManagementService.Stub
if (DBG) Slog.d(TAG, "Removing idletimer");
synchronized (mIdleTimerLock) {
- IdleTimerParams params = mActiveIdleTimers.get(iface);
+ final IdleTimerParams params = mActiveIdleTimers.get(iface);
if (params == null || --(params.networkCount) > 0) {
return;
}
try {
mConnector.execute("idletimer", "remove", iface,
- Integer.toString(params.timeout), params.label);
+ Integer.toString(params.timeout), Integer.toString(params.type));
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
mActiveIdleTimers.remove(iface);
+ mDaemonHandler.post(new Runnable() {
+ @Override public void run() {
+ notifyInterfaceClassActivity(params.type,
+ DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ SystemClock.elapsedRealtimeNanos(), false);
+ }
+ });
}
}
@@ -1253,7 +1397,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
public NetworkStats getNetworkStatsDetail() {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
try {
- return mStatsFactory.readNetworkStatsDetail(UID_ALL);
+ return mStatsFactory.readNetworkStatsDetail(UID_ALL, null, TAG_ALL, null);
} catch (IOException e) {
throw new IllegalStateException(e);
}
@@ -1414,7 +1558,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
public NetworkStats getNetworkStatsUidDetail(int uid) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
try {
- return mStatsFactory.readNetworkStatsDetail(uid);
+ return mStatsFactory.readNetworkStatsDetail(uid, null, TAG_ALL, null);
} catch (IOException e) {
throw new IllegalStateException(e);
}
@@ -1460,20 +1604,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
@Override
- public void setDefaultInterfaceForDns(String iface) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- try {
- mConnector.execute("resolver", "setdefaultif", iface);
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
- @Override
- public void setDnsServersForInterface(String iface, String[] servers, String domains) {
+ public void setDnsServersForNetwork(int netId, String[] servers, String domains) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- final Command cmd = new Command("resolver", "setifdns", iface,
+ final Command cmd = new Command("resolver", "setnetdns", netId,
(domains == null ? "" : domains));
for (String s : servers) {
@@ -1491,11 +1625,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
@Override
- public void setUidRangeRoute(String iface, int uid_start, int uid_end) {
+ public void setUidRangeRoute(String iface, int uid_start, int uid_end, boolean forward_dns) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
try {
mConnector.execute("interface", "fwmark",
- "uid", "add", iface, uid_start, uid_end);
+ "uid", "add", iface, uid_start, uid_end, forward_dns ? 1 : 0);
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
@@ -1506,7 +1640,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
try {
mConnector.execute("interface", "fwmark",
- "uid", "remove", iface, uid_start, uid_end);
+ "uid", "remove", iface, uid_start, uid_end, 0);
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
@@ -1603,51 +1737,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
@Override
- public void setDnsInterfaceForUidRange(String iface, int uid_start, int uid_end) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- try {
- mConnector.execute("resolver", "setifaceforuidrange", iface, uid_start, uid_end);
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
- @Override
- public void clearDnsInterfaceForUidRange(String iface, int uid_start, int uid_end) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- try {
- mConnector.execute("resolver", "clearifaceforuidrange", iface, uid_start, uid_end);
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
- @Override
- public void clearDnsInterfaceMaps() {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- try {
- mConnector.execute("resolver", "clearifacemapping");
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
-
- @Override
- public void flushDefaultDnsCache() {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- try {
- mConnector.execute("resolver", "flushdefaultif");
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
- @Override
- public void flushInterfaceDnsCache(String iface) {
+ public void flushNetworkDnsCache(int netId) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
try {
- mConnector.execute("resolver", "flushif", iface);
+ mConnector.execute("resolver", "flushnet", netId);
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
@@ -1726,28 +1819,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
@Override
- public void setDnsInterfaceForPid(String iface, int pid) throws IllegalStateException {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- try {
- mConnector.execute("resolver", "setifaceforpid", iface, pid);
- } catch (NativeDaemonConnectorException e) {
- throw new IllegalStateException(
- "Error communicating with native deamon to set interface for pid" + iface, e);
- }
- }
-
- @Override
- public void clearDnsInterfaceForPid(int pid) throws IllegalStateException {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- try {
- mConnector.execute("resolver", "clearifaceforpid", pid);
- } catch (NativeDaemonConnectorException e) {
- throw new IllegalStateException(
- "Error communicating with native deamon to clear interface for pid " + pid, e);
- }
- }
-
- @Override
public void startClatd(String interfaceName) throws IllegalStateException {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
@@ -1784,6 +1855,38 @@ public class NetworkManagementService extends INetworkManagementService.Stub
return event.getMessage().endsWith("started");
}
+ @Override
+ public void registerNetworkActivityListener(INetworkActivityListener listener) {
+ mNetworkActivityListeners.register(listener);
+ }
+
+ @Override
+ public void unregisterNetworkActivityListener(INetworkActivityListener listener) {
+ mNetworkActivityListeners.unregister(listener);
+ }
+
+ @Override
+ public boolean isNetworkActive() {
+ synchronized (mNetworkActivityListeners) {
+ return mNetworkActive || mActiveIdleTimers.isEmpty();
+ }
+ }
+
+ private void reportNetworkActive() {
+ final int length = mNetworkActivityListeners.beginBroadcast();
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ mNetworkActivityListeners.getBroadcastItem(i).onNetworkActive();
+ } catch (RemoteException e) {
+ } catch (RuntimeException e) {
+ }
+ }
+ } finally {
+ mNetworkActivityListeners.finishBroadcast();
+ }
+ }
+
/** {@inheritDoc} */
@Override
public void monitor() {
@@ -1801,6 +1904,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub
pw.println();
pw.print("Bandwidth control enabled: "); pw.println(mBandwidthControlEnabled);
+ pw.print("mMobileActivityFromRadio="); pw.print(mMobileActivityFromRadio);
+ pw.print(" mLastPowerStateFromRadio="); pw.println(mLastPowerStateFromRadio);
+ pw.print("mNetworkActive="); pw.println(mNetworkActive);
synchronized (mQuotaLock) {
pw.print("Active quota ifaces: "); pw.println(mActiveQuotas.toString());
@@ -1817,6 +1923,146 @@ public class NetworkManagementService extends INetworkManagementService.Stub
pw.println("]");
}
+ synchronized (mIdleTimerLock) {
+ pw.println("Idle timers:");
+ for (HashMap.Entry<String, IdleTimerParams> ent : mActiveIdleTimers.entrySet()) {
+ pw.print(" "); pw.print(ent.getKey()); pw.println(":");
+ IdleTimerParams params = ent.getValue();
+ pw.print(" timeout="); pw.print(params.timeout);
+ pw.print(" type="); pw.print(params.type);
+ pw.print(" networkCount="); pw.println(params.networkCount);
+ }
+ }
+
pw.print("Firewall enabled: "); pw.println(mFirewallEnabled);
}
+
+ @Override
+ public void createNetwork(int netId) {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ try {
+ mConnector.execute("network", "create", netId);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ @Override
+ public void removeNetwork(int netId) {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ try {
+ mConnector.execute("network", "destroy", netId);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ @Override
+ public void addInterfaceToNetwork(String iface, int netId) {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ try {
+ mConnector.execute("network", "addiface", netId, iface);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ @Override
+ public void removeInterfaceFromNetwork(String iface, int netId) {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ try {
+ mConnector.execute("network", "removeiface", netId, iface);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ @Override
+ public void addLegacyRouteForNetId(int netId, RouteInfo routeInfo, int uid) {
+ modifyLegacyRouteForNetId(netId, routeInfo, uid, ADD);
+ }
+
+ @Override
+ public void removeLegacyRouteForNetId(int netId, RouteInfo routeInfo, int uid) {
+ modifyLegacyRouteForNetId(netId, routeInfo, uid, REMOVE);
+ }
+
+ private void modifyLegacyRouteForNetId(int netId, RouteInfo routeInfo, int uid, String action) {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ final Command cmd = new Command("network", "legacy", uid, "route", action, netId);
+
+ // create quadlet: dest-ip-addr prefixlength gateway-ip-addr iface
+ final LinkAddress la = routeInfo.getDestination();
+ cmd.appendArg(la.getAddress().getHostAddress());
+ cmd.appendArg(la.getNetworkPrefixLength());
+ cmd.appendArg(routeInfo.getGateway().getHostAddress());
+ cmd.appendArg(routeInfo.getInterface());
+
+ try {
+ mConnector.execute(cmd);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ @Override
+ public void setDefaultNetId(int netId) {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ try {
+ mConnector.execute("network", "default", "set", netId);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ @Override
+ public void clearDefaultNetId() {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ try {
+ mConnector.execute("network", "default", "clear");
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ @Override
+ public void setPermission(boolean internal, boolean changeNetState, int[] uids) {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ final Command cmd = new Command("network", "permission", "user", "set");
+ if (internal) cmd.appendArg(CONNECTIVITY_INTERNAL);
+ if (changeNetState) cmd.appendArg(CHANGE_NETWORK_STATE);
+ for (int i=0; i<uids.length; i++) {
+ cmd.appendArg(uids[i]);
+ }
+
+ try {
+ mConnector.execute(cmd);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ @Override
+ public void clearPermission(int[] uids) {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ final Command cmd = new Command("network", "permission", "user", "clear");
+ for (int i=0; i<uids.length; i++) {
+ cmd.appendArg(uids[i]);
+ }
+
+ try {
+ mConnector.execute(cmd);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
new file mode 100644
index 0000000..512ebc6
--- /dev/null
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import android.Manifest.permission;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.net.INetworkScoreCache;
+import android.net.INetworkScoreService;
+import android.net.NetworkScorerAppManager;
+import android.net.ScoredNetwork;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Backing service for {@link android.net.NetworkScoreManager}.
+ * @hide
+ */
+public class NetworkScoreService extends INetworkScoreService.Stub {
+ private static final String TAG = "NetworkScoreService";
+
+ /** SharedPreference bit set to true after the service is first initialized. */
+ private static final String PREF_SCORING_PROVISIONED = "is_provisioned";
+
+ private final Context mContext;
+
+ private final Map<Integer, INetworkScoreCache> mScoreCaches;
+
+ public NetworkScoreService(Context context) {
+ mContext = context;
+ mScoreCaches = new HashMap<>();
+ }
+
+ /** Called when the system is ready to run third-party code but before it actually does so. */
+ void systemReady() {
+ SharedPreferences prefs = mContext.getSharedPreferences(TAG, Context.MODE_PRIVATE);
+ if (!prefs.getBoolean(PREF_SCORING_PROVISIONED, false)) {
+ // On first run, we try to initialize the scorer to the one configured at build time.
+ // This will be a no-op if the scorer isn't actually valid.
+ String defaultPackage = mContext.getResources().getString(
+ R.string.config_defaultNetworkScorerPackageName);
+ if (!TextUtils.isEmpty(defaultPackage)) {
+ NetworkScorerAppManager.setActiveScorer(mContext, defaultPackage);
+ }
+ prefs.edit().putBoolean(PREF_SCORING_PROVISIONED, true).apply();
+ }
+ }
+
+ @Override
+ public boolean updateScores(ScoredNetwork[] networks) {
+ if (!NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid())) {
+ throw new SecurityException("Caller with UID " + getCallingUid() +
+ " is not the active scorer.");
+ }
+
+ // Separate networks by type.
+ Map<Integer, List<ScoredNetwork>> networksByType = new HashMap<>();
+ for (ScoredNetwork network : networks) {
+ List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
+ if (networkList == null) {
+ networkList = new ArrayList<>();
+ networksByType.put(network.networkKey.type, networkList);
+ }
+ networkList.add(network);
+ }
+
+ // Pass the scores of each type down to the appropriate network scorer.
+ for (Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
+ INetworkScoreCache scoreCache = mScoreCaches.get(entry.getKey());
+ if (scoreCache != null) {
+ try {
+ scoreCache.updateScores(entry.getValue());
+ } catch (RemoteException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
+ }
+ }
+ } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding");
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean clearScores() {
+ // Only the active scorer or the system (who can broadcast BROADCAST_SCORE_NETWORKS) should
+ // be allowed to flush all scores.
+ if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) ||
+ mContext.checkCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS) ==
+ PackageManager.PERMISSION_GRANTED) {
+ clearInternal();
+ return true;
+ } else {
+ throw new SecurityException(
+ "Caller is neither the active scorer nor the scorer manager.");
+ }
+ }
+
+ @Override
+ public boolean setActiveScorer(String packageName) {
+ mContext.enforceCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS, TAG);
+ // Preemptively clear scores even though the set operation could fail. We do this for safety
+ // as scores should never be compared across apps; in practice, Settings should only be
+ // allowing valid apps to be set as scorers, so failure here should be rare.
+ clearInternal();
+ return NetworkScorerAppManager.setActiveScorer(mContext, packageName);
+ }
+
+ /** Clear scores. Callers are responsible for checking permissions as appropriate. */
+ private void clearInternal() {
+ Set<INetworkScoreCache> cachesToClear = getScoreCaches();
+
+ for (INetworkScoreCache scoreCache : cachesToClear) {
+ try {
+ scoreCache.clearScores();
+ } catch (RemoteException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to clear scores", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
+ mContext.enforceCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS, TAG);
+ synchronized (mScoreCaches) {
+ if (mScoreCaches.containsKey(networkType)) {
+ throw new IllegalArgumentException(
+ "Score cache already registered for type " + networkType);
+ }
+ mScoreCaches.put(networkType, scoreCache);
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
+ String currentScorer = NetworkScorerAppManager.getActiveScorer(mContext);
+ if (currentScorer == null) {
+ writer.println("Scoring is disabled.");
+ return;
+ }
+ writer.println("Current scorer: " + currentScorer);
+ writer.flush();
+
+ for (INetworkScoreCache scoreCache : getScoreCaches()) {
+ try {
+ scoreCache.asBinder().dump(fd, args);
+ } catch (RemoteException e) {
+ writer.println("Unable to dump score cache");
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to dump score cache", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a set of all score caches that are currently active.
+ *
+ * <p>May be used to perform an action on all score caches without potentially strange behavior
+ * if a new scorer is registered during that action's execution.
+ */
+ private Set<INetworkScoreCache> getScoreCaches() {
+ synchronized (mScoreCaches) {
+ return new HashSet<>(mScoreCaches.values());
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index 8df93f1..fe97c71 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -26,11 +26,8 @@ import android.net.nsd.DnsSdTxtRecord;
import android.net.nsd.INsdManager;
import android.net.nsd.NsdManager;
import android.os.Binder;
-import android.os.Handler;
-import android.os.HandlerThread;
import android.os.Message;
import android.os.Messenger;
-import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
@@ -40,22 +37,17 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import com.android.server.am.BatteryStatsService;
import com.android.server.NativeDaemonConnector.Command;
-import com.android.internal.R;
/**
* Network Service Discovery Service handles remote service discovery operation requests by
@@ -429,11 +421,8 @@ public class NsdService extends INsdManager.Stub {
}
/* This goes in response as msg.arg2 */
- int clientId = -1;
- int keyId = clientInfo.mClientIds.indexOfValue(id);
- if (keyId != -1) {
- clientId = clientInfo.mClientIds.keyAt(keyId);
- } else {
+ int clientId = clientInfo.getClientId(id);
+ if (clientId < 0) {
// This can happen because of race conditions. For example,
// SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY,
// and we may get in this situation.
@@ -560,7 +549,7 @@ public class NsdService extends INsdManager.Stub {
mContentResolver = context.getContentResolver();
mNativeConnector = new NativeDaemonConnector(new NativeCallbackReceiver(), "mdns", 10,
- MDNS_TAG, 25);
+ MDNS_TAG, 25, null);
mNsdStateMachine = new NsdStateMachine(TAG);
mNsdStateMachine.start();
@@ -648,6 +637,10 @@ public class NsdService extends INsdManager.Stub {
mNativeDaemonConnected.countDown();
}
+ public boolean onCheckHoldWakeLock(int code) {
+ return false;
+ }
+
public boolean onEvent(int code, String raw, String[] cooked) {
// TODO: NDC translates a message to a callback, we could enhance NDC to
// directly interact with a state machine through messages
@@ -908,5 +901,18 @@ public class NsdService extends INsdManager.Stub {
mClientRequests.clear();
}
+ // mClientIds is a sparse array of listener id -> mDnsClient id. For a given mDnsClient id,
+ // return the corresponding listener id. mDnsClient id is also called a global id.
+ private int getClientId(final int globalId) {
+ // This doesn't use mClientIds.indexOfValue because indexOfValue uses == (not .equals)
+ // while also coercing the int primitives to Integer objects.
+ for (int i = 0, nSize = mClientIds.size(); i < nSize; i++) {
+ int mDnsId = mClientIds.valueAt(i);
+ if (globalId == mDnsId) {
+ return mClientIds.keyAt(i);
+ }
+ }
+ return -1;
+ }
}
}
diff --git a/services/core/java/com/android/server/RecognitionManagerService.java b/services/core/java/com/android/server/RecognitionManagerService.java
index c2e749d..60d38ae 100644
--- a/services/core/java/com/android/server/RecognitionManagerService.java
+++ b/services/core/java/com/android/server/RecognitionManagerService.java
@@ -78,8 +78,10 @@ public class RecognitionManagerService extends Binder {
mMonitor = new MyPackageMonitor();
mMonitor.register(context, null, UserHandle.ALL, true);
mIPm = AppGlobals.getPackageManager();
+ IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
+ filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
- new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
+ filter, null, null);
}
public void systemReady() {
diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
index 5c7bfab..9274295 100644
--- a/services/core/java/com/android/server/ServiceWatcher.java
+++ b/services/core/java/com/android/server/ServiceWatcher.java
@@ -37,7 +37,6 @@ import com.android.internal.content.PackageMonitor;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
diff --git a/services/core/java/com/android/server/ShutdownActivity.java b/services/core/java/com/android/server/ShutdownActivity.java
index be65141..56172ed 100644
--- a/services/core/java/com/android/server/ShutdownActivity.java
+++ b/services/core/java/com/android/server/ShutdownActivity.java
@@ -20,14 +20,11 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
-import android.os.Handler;
import android.os.IPowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Slog;
-import com.android.server.power.ShutdownThread;
-
public class ShutdownActivity extends Activity {
private static final String TAG = "ShutdownActivity";
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 77f5182..cfaf016 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -22,8 +22,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.net.LinkCapabilities;
import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -32,6 +32,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.telephony.CellLocation;
+import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
@@ -119,7 +120,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
private LinkProperties mDataConnectionLinkProperties;
- private LinkCapabilities mDataConnectionLinkCapabilities;
+ private NetworkCapabilities mDataConnectionNetworkCapabilities;
private Bundle mCellLocation = new Bundle();
@@ -129,6 +130,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
private List<CellInfo> mCellInfo = null;
+ private DataConnectionRealTimeInfo mDcRtInfo = new DataConnectionRealTimeInfo();
+
private int mRingingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
private int mForegroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
@@ -324,6 +327,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
remove(r.binder);
}
}
+ if ((events & PhoneStateListener.LISTEN_DATA_CONNECTION_REAL_TIME_INFO) != 0) {
+ try {
+ r.callback.onDataConnectionRealTimeInfoChanged(mDcRtInfo);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
if ((events & PhoneStateListener.LISTEN_PRECISE_CALL_STATE) != 0) {
try {
r.callback.onPreciseCallStateChanged(mPreciseCallState);
@@ -383,6 +393,14 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (!checkNotifyPermission("notifyServiceState()")){
return;
}
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mBatteryStats.notePhoneState(state.getState());
+ } catch (RemoteException re) {
+ // Can't do much
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
synchronized (mRecords) {
mServiceState = state;
for (Record r : mRecords) {
@@ -451,6 +469,31 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
+ public void notifyDataConnectionRealTimeInfo(DataConnectionRealTimeInfo dcRtInfo) {
+ if (!checkNotifyPermission("notifyDataConnectionRealTimeInfo()")) {
+ return;
+ }
+
+ synchronized (mRecords) {
+ mDcRtInfo = dcRtInfo;
+ for (Record r : mRecords) {
+ if (validateEventsAndUserLocked(r,
+ PhoneStateListener.LISTEN_DATA_CONNECTION_REAL_TIME_INFO)) {
+ try {
+ if (DBG_LOC) {
+ Slog.d(TAG, "notifyDataConnectionRealTimeInfo: mDcRtInfo="
+ + mDcRtInfo + " r=" + r);
+ }
+ r.callback.onDataConnectionRealTimeInfoChanged(mDcRtInfo);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
public void notifyMessageWaitingChanged(boolean mwi) {
if (!checkNotifyPermission("notifyMessageWaitingChanged()")) {
return;
@@ -510,7 +553,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
public void notifyDataConnection(int state, boolean isDataConnectivityPossible,
String reason, String apn, String apnType, LinkProperties linkProperties,
- LinkCapabilities linkCapabilities, int networkType, boolean roaming) {
+ NetworkCapabilities networkCapabilities, int networkType, boolean roaming) {
if (!checkNotifyPermission("notifyDataConnection()" )) {
return;
}
@@ -544,7 +587,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
mDataConnectionPossible = isDataConnectivityPossible;
mDataConnectionReason = reason;
mDataConnectionLinkProperties = linkProperties;
- mDataConnectionLinkCapabilities = linkCapabilities;
+ mDataConnectionNetworkCapabilities = networkCapabilities;
if (mDataConnectionNetworkType != networkType) {
mDataConnectionNetworkType = networkType;
// need to tell registered listeners about the new network type
@@ -581,7 +624,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
handleRemoveListLocked();
}
broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn,
- apnType, linkProperties, linkCapabilities, roaming);
+ apnType, linkProperties, networkCapabilities, roaming);
broadcastPreciseDataConnectionStateChanged(state, networkType, apnType, apn, reason,
linkProperties, "");
}
@@ -751,9 +794,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
pw.println(" mDataConnectionReason=" + mDataConnectionReason);
pw.println(" mDataConnectionApn=" + mDataConnectionApn);
pw.println(" mDataConnectionLinkProperties=" + mDataConnectionLinkProperties);
- pw.println(" mDataConnectionLinkCapabilities=" + mDataConnectionLinkCapabilities);
+ pw.println(" mDataConnectionNetworkCapabilities=" +
+ mDataConnectionNetworkCapabilities);
pw.println(" mCellLocation=" + mCellLocation);
pw.println(" mCellInfo=" + mCellInfo);
+ pw.println(" mDcRtInfo=" + mDcRtInfo);
pw.println("registrations: count=" + recordCount);
for (Record r : mRecords) {
pw.println(" " + r.pkgForDebug + " 0x" + Integer.toHexString(r.events));
@@ -766,15 +811,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
//
private void broadcastServiceStateChanged(ServiceState state) {
- long ident = Binder.clearCallingIdentity();
- try {
- mBatteryStats.notePhoneState(state.getState());
- } catch (RemoteException re) {
- // Can't do much
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
-
Intent intent = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
Bundle data = new Bundle();
state.fillInNotifierBundle(data);
@@ -827,7 +863,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
private void broadcastDataConnectionStateChanged(int state,
boolean isDataConnectivityPossible,
String reason, String apn, String apnType, LinkProperties linkProperties,
- LinkCapabilities linkCapabilities, boolean roaming) {
+ NetworkCapabilities networkCapabilities, boolean roaming) {
// Note: not reporting to the battery stats service here, because the
// status bar takes care of that after taking into account all of the
// required info.
@@ -847,8 +883,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
intent.putExtra(PhoneConstants.DATA_IFACE_NAME_KEY, iface);
}
}
- if (linkCapabilities != null) {
- intent.putExtra(PhoneConstants.DATA_LINK_CAPABILITIES_KEY, linkCapabilities);
+ if (networkCapabilities != null) {
+ intent.putExtra(PhoneConstants.DATA_NETWORK_CAPABILITIES_KEY, networkCapabilities);
}
if (roaming) intent.putExtra(PhoneConstants.DATA_NETWORK_ROAMING_KEY, true);
diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java
index 0964767..d4c436f 100644
--- a/services/core/java/com/android/server/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/TextServicesManagerService.java
@@ -33,7 +33,6 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
diff --git a/services/core/java/com/android/server/UpdateLockService.java b/services/core/java/com/android/server/UpdateLockService.java
index 0f778cd..7f33973 100644
--- a/services/core/java/com/android/server/UpdateLockService.java
+++ b/services/core/java/com/android/server/UpdateLockService.java
@@ -24,7 +24,6 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.IUpdateLock;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.TokenWatcher;
import android.os.UpdateLock;
import android.os.UserHandle;
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 28eb948..82c13e0 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -41,6 +41,7 @@ import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.Slog;
import android.view.InputDevice;
+import android.media.AudioManager;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
@@ -73,6 +74,8 @@ public class VibratorService extends IVibratorService.Stub
private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
private int mCurVibUid = -1;
+ private boolean mLowPowerMode;
+ private SettingsObserver mSettingObserver;
native static boolean vibratorExists();
native static void vibratorOn(long milliseconds);
@@ -84,26 +87,29 @@ public class VibratorService extends IVibratorService.Stub
private final long mStartTime;
private final long[] mPattern;
private final int mRepeat;
+ private final int mStreamHint;
private final int mUid;
- private final String mPackageName;
+ private final String mOpPkg;
- Vibration(IBinder token, long millis, int uid, String packageName) {
- this(token, millis, null, 0, uid, packageName);
+ Vibration(IBinder token, long millis, int streamHint, int uid, String opPkg) {
+ this(token, millis, null, 0, streamHint, uid, opPkg);
}
- Vibration(IBinder token, long[] pattern, int repeat, int uid, String packageName) {
- this(token, 0, pattern, repeat, uid, packageName);
+ Vibration(IBinder token, long[] pattern, int repeat, int streamHint, int uid,
+ String opPkg) {
+ this(token, 0, pattern, repeat, streamHint, uid, opPkg);
}
private Vibration(IBinder token, long millis, long[] pattern,
- int repeat, int uid, String packageName) {
+ int repeat, int streamHint, int uid, String opPkg) {
mToken = token;
mTimeout = millis;
mStartTime = SystemClock.uptimeMillis();
mPattern = pattern;
mRepeat = repeat;
+ mStreamHint = streamHint;
mUid = uid;
- mPackageName = packageName;
+ mOpPkg = opPkg;
}
public void binderDied() {
@@ -156,15 +162,15 @@ public class VibratorService extends IVibratorService.Stub
public void systemReady() {
mIm = (InputManager)mContext.getSystemService(Context.INPUT_SERVICE);
+ mSettingObserver = new SettingsObserver(mH);
mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES), true,
- new ContentObserver(mH) {
- @Override
- public void onChange(boolean selfChange) {
- updateInputDeviceVibrators();
- }
- }, UserHandle.USER_ALL);
+ Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES),
+ true, mSettingObserver, UserHandle.USER_ALL);
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE), false,
+ mSettingObserver, UserHandle.USER_ALL);
mContext.registerReceiver(new BroadcastReceiver() {
@Override
@@ -176,6 +182,17 @@ public class VibratorService extends IVibratorService.Stub
updateInputDeviceVibrators();
}
+ private final class SettingsObserver extends ContentObserver {
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean SelfChange) {
+ updateInputDeviceVibrators();
+ }
+ }
+
public boolean hasVibrator() {
return doVibratorExists();
}
@@ -191,7 +208,8 @@ public class VibratorService extends IVibratorService.Stub
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
- public void vibrate(int uid, String packageName, long milliseconds, IBinder token) {
+ public void vibrate(int uid, String opPkg, long milliseconds, int streamHint,
+ IBinder token) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires VIBRATE permission");
@@ -207,7 +225,7 @@ public class VibratorService extends IVibratorService.Stub
return;
}
- Vibration vib = new Vibration(token, milliseconds, uid, packageName);
+ Vibration vib = new Vibration(token, milliseconds, streamHint, uid, opPkg);
final long ident = Binder.clearCallingIdentity();
try {
@@ -233,7 +251,7 @@ public class VibratorService extends IVibratorService.Stub
}
public void vibratePattern(int uid, String packageName, long[] pattern, int repeat,
- IBinder token) {
+ int streamHint, IBinder token) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires VIBRATE permission");
@@ -258,7 +276,7 @@ public class VibratorService extends IVibratorService.Stub
return;
}
- Vibration vib = new Vibration(token, pattern, repeat, uid, packageName);
+ Vibration vib = new Vibration(token, pattern, repeat, streamHint, uid, packageName);
try {
token.linkToDeath(vib, 0);
} catch (RemoteException e) {
@@ -342,8 +360,16 @@ public class VibratorService extends IVibratorService.Stub
// Lock held on mVibrations
private void startVibrationLocked(final Vibration vib) {
try {
- int mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
- AppOpsManager.OP_VIBRATE, vib.mUid, vib.mPackageName);
+ if (mLowPowerMode && vib.mStreamHint != AudioManager.STREAM_RING) {
+ return;
+ }
+
+ int mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
+ vib.mStreamHint, vib.mUid, vib.mOpPkg);
+ if (mode == AppOpsManager.MODE_ALLOWED) {
+ mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+ AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
+ }
if (mode != AppOpsManager.MODE_ALLOWED) {
if (mode == AppOpsManager.MODE_ERRORED) {
Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
@@ -354,7 +380,7 @@ public class VibratorService extends IVibratorService.Stub
} catch (RemoteException e) {
}
if (vib.mTimeout != 0) {
- doVibratorOn(vib.mTimeout, vib.mUid);
+ doVibratorOn(vib.mTimeout, vib.mUid, vib.mStreamHint);
mH.postDelayed(mVibrationRunnable, vib.mTimeout);
} else {
// mThread better be null here. doCancelVibrate should always be
@@ -369,7 +395,7 @@ public class VibratorService extends IVibratorService.Stub
try {
mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
- mCurrentVibration.mPackageName);
+ mCurrentVibration.mOpPkg);
} catch (RemoteException e) {
}
mCurrentVibration = null;
@@ -417,6 +443,9 @@ public class VibratorService extends IVibratorService.Stub
} catch (SettingNotFoundException snfe) {
}
+ mLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.LOW_POWER_MODE, 0) != 0;
+
if (mVibrateInputDevicesSetting) {
if (!mInputDeviceListenerRegistered) {
mInputDeviceListenerRegistered = true;
@@ -473,7 +502,7 @@ public class VibratorService extends IVibratorService.Stub
return vibratorExists();
}
- private void doVibratorOn(long millis, int uid) {
+ private void doVibratorOn(long millis, int uid, int streamHint) {
synchronized (mInputDeviceVibrators) {
try {
mBatteryStatsService.noteVibratorOn(uid, millis);
@@ -483,7 +512,7 @@ public class VibratorService extends IVibratorService.Stub
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
for (int i = 0; i < vibratorCount; i++) {
- mInputDeviceVibrators.get(i).vibrate(millis);
+ mInputDeviceVibrators.get(i).vibrate(millis, streamHint);
}
} else {
vibratorOn(millis);
@@ -546,6 +575,7 @@ public class VibratorService extends IVibratorService.Stub
final int len = pattern.length;
final int repeat = mVibration.mRepeat;
final int uid = mVibration.mUid;
+ final int streamHint = mVibration.mStreamHint;
int index = 0;
long duration = 0;
@@ -566,7 +596,7 @@ public class VibratorService extends IVibratorService.Stub
// duration is saved for delay() at top of loop
duration = pattern[index++];
if (duration > 0) {
- VibratorService.this.doVibratorOn(duration, uid);
+ VibratorService.this.doVibratorOn(duration, uid, streamHint);
}
} else {
if (repeat < 0) {
diff --git a/services/core/java/com/android/server/WiredAccessoryManager.java b/services/core/java/com/android/server/WiredAccessoryManager.java
index 415fcc1..c32beda 100644
--- a/services/core/java/com/android/server/WiredAccessoryManager.java
+++ b/services/core/java/com/android/server/WiredAccessoryManager.java
@@ -70,6 +70,7 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks {
private static final String NAME_HDMI = "hdmi";
private static final int MSG_NEW_DEVICE_STATE = 1;
+ private static final int MSG_SYSTEM_READY = 2;
private final Object mLock = new Object();
@@ -96,17 +97,9 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks {
context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
mObserver = new WiredAccessoryObserver();
-
- context.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context ctx, Intent intent) {
- bootCompleted();
- }
- },
- new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
}
- private void bootCompleted() {
+ private void onSystemReady() {
if (mUseDevInputEventForAudioJack) {
int switchValues = 0;
if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_HEADPHONE_INSERT) == 1) {
@@ -157,6 +150,16 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks {
}
}
+ @Override
+ public void systemReady() {
+ synchronized (mLock) {
+ mWakeLock.acquire();
+
+ Message msg = mHandler.obtainMessage(MSG_SYSTEM_READY, 0, 0, null);
+ mHandler.sendMessage(msg);
+ }
+ }
+
/**
* Compare the existing headset state with the new state and pass along accordingly. Note
* that this only supports a single headset at a time. Inserting both a usb and jacked headset
@@ -218,6 +221,11 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks {
case MSG_NEW_DEVICE_STATE:
setDevicesState(msg.arg1, msg.arg2, (String)msg.obj);
mWakeLock.release();
+ break;
+ case MSG_SYSTEM_READY:
+ onSystemReady();
+ mWakeLock.release();
+ break;
}
}
};
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index aa9849e..b2aaf74 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -34,6 +34,7 @@ import android.app.AppGlobals;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentValues;
@@ -857,12 +858,13 @@ public class AccountManagerService
checkManageAccountsPermission();
UserHandle user = Binder.getCallingUserHandle();
UserAccounts accounts = getUserAccountsForCaller();
- if (!canUserModifyAccounts(Binder.getCallingUid())) {
+ if (!canUserModifyAccounts(Binder.getCallingUid(), account.type)) {
try {
response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
"User cannot modify accounts");
} catch (RemoteException re) {
}
+ return;
}
long identityToken = clearCallingIdentity();
@@ -1511,7 +1513,7 @@ public class AccountManagerService
checkManageAccountsPermission();
// Is user disallowed from modifying accounts?
- if (!canUserModifyAccounts(Binder.getCallingUid())) {
+ if (!canUserModifyAccounts(Binder.getCallingUid(), accountType)) {
try {
response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
"User is not allowed to add an account!");
@@ -2757,7 +2759,7 @@ public class AccountManagerService
Manifest.permission.USE_CREDENTIALS);
}
- private boolean canUserModifyAccounts(int callingUid) {
+ private boolean canUserModifyAccounts(int callingUid, String accountType) {
if (callingUid != Process.myUid()) {
if (getUserManager().getUserRestrictions(
new UserHandle(UserHandle.getUserId(callingUid)))
@@ -2765,6 +2767,15 @@ public class AccountManagerService
return false;
}
}
+
+ DevicePolicyManager dpm = (DevicePolicyManager) mContext
+ .getSystemService(Context.DEVICE_POLICY_SERVICE);
+ String[] typesArray = dpm.getAccountTypesWithManagementDisabled();
+ for (String forbiddenType : typesArray) {
+ if (forbiddenType.equals(accountType)) {
+ return false;
+ }
+ }
return true;
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d66c5a7..033b967 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -28,6 +28,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.SystemProperties;
import android.util.ArrayMap;
+import com.android.internal.app.ProcessMap;
import com.android.internal.app.ProcessStats;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.TransferPipe;
@@ -596,12 +597,7 @@ public final class ActiveServices {
break;
}
}
- if (anyForeground != proc.foregroundServices) {
- proc.foregroundServices = anyForeground;
- if (oomAdj) {
- mAm.updateOomAdjLocked();
- }
- }
+ mAm.updateProcessForegroundLocked(proc, anyForeground, oomAdj);
}
private boolean updateServiceClientActivitiesLocked(ProcessRecord proc,
@@ -671,8 +667,7 @@ public final class ActiveServices {
// what they are, so we can report this elsewhere for
// others to know why certain services are running.
try {
- clientIntent = (PendingIntent)service.getParcelableExtra(
- Intent.EXTRA_CLIENT_INTENT);
+ clientIntent = service.getParcelableExtra(Intent.EXTRA_CLIENT_INTENT);
} catch (RuntimeException e) {
}
if (clientIntent != null) {
@@ -686,6 +681,11 @@ public final class ActiveServices {
}
}
+ if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
+ mAm.enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+ "BIND_TREAT_LIKE_ACTIVITY");
+ }
+
final boolean callerFg = callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE;
ServiceLookupResult res =
@@ -759,8 +759,12 @@ public final class ActiveServices {
}
if (s.app != null) {
+ if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
+ s.app.treatLikeActivity = true;
+ }
// This could have made the service more important.
- mAm.updateLruProcessLocked(s.app, s.app.hasClientActivities, b.client);
+ mAm.updateLruProcessLocked(s.app, s.app.hasClientActivities
+ || s.app.treatLikeActivity, b.client);
mAm.updateOomAdjLocked(s.app);
}
@@ -862,6 +866,12 @@ public final class ActiveServices {
if (r.binding.service.app != null) {
// This could have made the service less important.
+ if ((r.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
+ r.binding.service.app.treatLikeActivity = true;
+ mAm.updateLruProcessLocked(r.binding.service.app,
+ r.binding.service.app.hasClientActivities
+ || r.binding.service.app.treatLikeActivity, null);
+ }
mAm.updateOomAdjLocked(r.binding.service.app);
}
}
@@ -2077,10 +2087,12 @@ public final class ActiveServices {
// Sanity check: if the service listed for the app is not one
// we actually are maintaining, just let it drop.
- if (smap.mServicesByName.get(sr.name) != sr) {
- ServiceRecord cur = smap.mServicesByName.get(sr.name);
- Slog.wtf(TAG, "Service " + sr + " in process " + app
- + " not same as in map: " + cur);
+ final ServiceRecord curRec = smap.mServicesByName.get(sr.name);
+ if (curRec != sr) {
+ if (curRec != null) {
+ Slog.wtf(TAG, "Service " + sr + " in process " + app
+ + " not same as in map: " + curRec);
+ }
continue;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0cca44c..7abc75f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17,8 +17,10 @@
package com.android.server.am;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
@@ -26,18 +28,25 @@ import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
+import android.Manifest;
import android.app.AppOpsManager;
import android.app.IActivityContainer;
import android.app.IActivityContainerCallback;
+import android.app.IAppTask;
import android.appwidget.AppWidgetManager;
import android.graphics.Rect;
+import android.os.BatteryStats;
+import android.os.PersistableBundle;
+import android.service.voice.IVoiceInteractionSession;
import android.util.ArrayMap;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ProcessMap;
import com.android.internal.app.ProcessStats;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.ProcessCpuTracker;
@@ -53,6 +62,7 @@ import com.android.server.IntentResolver;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
+import com.android.server.SystemServiceManager;
import com.android.server.Watchdog;
import com.android.server.am.ActivityStack.ActivityState;
import com.android.server.firewall.IntentFirewall;
@@ -87,7 +97,6 @@ import android.app.INotificationManager;
import android.app.IProcessObserver;
import android.app.IServiceConnection;
import android.app.IStopUserCallback;
-import android.app.IThumbnailReceiver;
import android.app.IUiAutomationConnection;
import android.app.IUserSwitchObserver;
import android.app.Instrumentation;
@@ -129,7 +138,7 @@ import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.net.Proxy;
-import android.net.ProxyProperties;
+import android.net.ProxyInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -330,6 +339,9 @@ public final class ActivityManagerService extends ActivityManagerNative
// How many bytes to write into the dropbox log before truncating
static final int DROPBOX_MAX_SIZE = 256 * 1024;
+ /** All system services */
+ SystemServiceManager mSystemServiceManager;
+
/** Run all ActivityStacks through this */
ActivityStackSupervisor mStackSupervisor;
@@ -396,7 +408,7 @@ public final class ActivityManagerService extends ActivityManagerNative
/**
* List of intents that were used to start the most recent tasks.
*/
- private final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>();
+ final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>();
public class PendingAssistExtras extends Binder implements Runnable {
public final ActivityRecord activity;
@@ -692,13 +704,6 @@ public final class ActivityManagerService extends ActivityManagerNative
String mBackupAppName = null;
BackupRecord mBackupTarget = null;
- /**
- * List of PendingThumbnailsRecord objects of clients who are still
- * waiting to receive all of the thumbnails for a task.
- */
- final ArrayList<PendingThumbnailsRecord> mPendingThumbnails =
- new ArrayList<PendingThumbnailsRecord>();
-
final ProviderMap mProviderMap;
/**
@@ -718,11 +723,14 @@ public final class ActivityManagerService extends ActivityManagerNative
private static final String TAG_URI_GRANTS = "uri-grants";
private static final String TAG_URI_GRANT = "uri-grant";
private static final String ATTR_USER_HANDLE = "userHandle";
+ private static final String ATTR_SOURCE_USER_ID = "sourceUserId";
+ private static final String ATTR_TARGET_USER_ID = "targetUserId";
private static final String ATTR_SOURCE_PKG = "sourcePkg";
private static final String ATTR_TARGET_PKG = "targetPkg";
private static final String ATTR_URI = "uri";
private static final String ATTR_MODE_FLAGS = "modeFlags";
private static final String ATTR_CREATED_TIME = "createdTime";
+ private static final String ATTR_PREFIX = "prefix";
/**
* Global set of specific {@link Uri} permissions that have been granted.
@@ -730,8 +738,53 @@ public final class ActivityManagerService extends ActivityManagerNative
* to {@link UriPermission#uri} to {@link UriPermission}.
*/
@GuardedBy("this")
- private final SparseArray<ArrayMap<Uri, UriPermission>>
- mGrantedUriPermissions = new SparseArray<ArrayMap<Uri, UriPermission>>();
+ private final SparseArray<ArrayMap<GrantUri, UriPermission>>
+ mGrantedUriPermissions = new SparseArray<ArrayMap<GrantUri, UriPermission>>();
+
+ public static class GrantUri {
+ public final int sourceUserId;
+ public final Uri uri;
+ public boolean prefix;
+
+ public GrantUri(int sourceUserId, Uri uri, boolean prefix) {
+ this.sourceUserId = sourceUserId;
+ this.uri = uri;
+ this.prefix = prefix;
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof GrantUri) {
+ GrantUri other = (GrantUri) o;
+ return uri.equals(other.uri) && (sourceUserId == other.sourceUserId)
+ && prefix == other.prefix;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ String result = Integer.toString(sourceUserId) + " @ " + uri.toString();
+ if (prefix) result += " [prefix]";
+ return result;
+ }
+
+ public String toSafeString() {
+ String result = Integer.toString(sourceUserId) + " @ " + uri.toSafeString();
+ if (prefix) result += " [prefix]";
+ return result;
+ }
+
+ public static GrantUri resolve(int defaultSourceUserHandle, Uri uri) {
+ return new GrantUri(ContentProvider.getUserIdFromUri(uri, defaultSourceUserHandle),
+ ContentProvider.getUriWithoutUserId(uri), false);
+ }
+ }
CoreSettingsObserver mCoreSettingsObserver;
@@ -842,17 +895,23 @@ public final class ActivityManagerService extends ActivityManagerNative
* Set while we are wanting to sleep, to prevent any
* activities from being started/resumed.
*/
- boolean mSleeping = false;
+ private boolean mSleeping = false;
+
+ /**
+ * Set while we are running a voice interaction. This overrides
+ * sleeping while it is active.
+ */
+ private boolean mRunningVoice = false;
/**
* State of external calls telling us if the device is asleep.
*/
- boolean mWentToSleep = false;
+ private boolean mWentToSleep = false;
/**
* State of external call telling us if the lock screen is shown.
*/
- boolean mLockScreenShown = false;
+ private boolean mLockScreenShown = false;
/**
* Set if we are shutting down the system, similar to sleeping.
@@ -920,11 +979,25 @@ public final class ActivityManagerService extends ActivityManagerNative
long mLowRamTimeSinceLastIdle = 0;
/**
- * If RAM is currently low, when that horrible situatin started.
+ * If RAM is currently low, when that horrible situation started.
*/
long mLowRamStartTime = 0;
/**
+ * For reporting to battery stats the current top application.
+ */
+ private String mCurResumedPackage = null;
+ private int mCurResumedUid = -1;
+
+ /**
+ * For reporting to battery stats the apps currently running foreground
+ * service. The ProcessMap is package/uid tuples; each of these contain
+ * an array of the currently foreground processes.
+ */
+ final ProcessMap<ArrayList<ProcessRecord>> mForegroundPackages
+ = new ProcessMap<ArrayList<ProcessRecord>>();
+
+ /**
* This is set if we had to do a delayed dexopt of an app before launching
* it, to increase the ANR timeouts in that case.
*/
@@ -952,11 +1025,11 @@ public final class ActivityManagerService extends ActivityManagerNative
static class ProcessChangeItem {
static final int CHANGE_ACTIVITIES = 1<<0;
- static final int CHANGE_IMPORTANCE= 1<<1;
+ static final int CHANGE_PROCESS_STATE = 1<<1;
int changes;
int uid;
int pid;
- int importance;
+ int processState;
boolean foregroundActivities;
}
@@ -1005,6 +1078,7 @@ public final class ActivityManagerService extends ActivityManagerNative
final ActivityThread mSystemThread;
int mCurrentUserId = 0;
+ int[] mCurrentProfileIds = new int[] {UserHandle.USER_OWNER}; // Accessed by ActivityStack
private UserManagerService mUserManager;
private final class AppDeathRecipient implements IBinder.DeathRecipient {
@@ -1063,7 +1137,10 @@ public final class ActivityManagerService extends ActivityManagerNative
static final int IMMERSIVE_MODE_LOCK_MSG = 37;
static final int PERSIST_URI_GRANTS_MSG = 38;
static final int REQUEST_ALL_PSS_MSG = 39;
- static final int UPDATE_TIME = 40;
+ static final int START_PROFILES_MSG = 40;
+ static final int UPDATE_TIME = 41;
+ static final int SYSTEM_USER_START_MSG = 42;
+ static final int SYSTEM_USER_CURRENT_MSG = 43;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1262,15 +1339,15 @@ public final class ActivityManagerService extends ActivityManagerNative
}
} break;
case UPDATE_HTTP_PROXY_MSG: {
- ProxyProperties proxy = (ProxyProperties)msg.obj;
+ ProxyInfo proxy = (ProxyInfo)msg.obj;
String host = "";
String port = "";
String exclList = "";
- String pacFileUrl = null;
+ Uri pacFileUrl = Uri.EMPTY;
if (proxy != null) {
host = proxy.getHost();
port = Integer.toString(proxy.getPort());
- exclList = proxy.getExclusionList();
+ exclList = proxy.getExclusionListAsString();
pacFileUrl = proxy.getPacFileUrl();
}
synchronized (ActivityManagerService.this) {
@@ -1337,7 +1414,7 @@ public final class ActivityManagerService extends ActivityManagerNative
String pkg = bundle.getString("pkg");
String reason = bundle.getString("reason");
forceStopPackageLocked(pkg, appid, restart, false, true, false,
- UserHandle.USER_ALL, reason);
+ false, UserHandle.USER_ALL, reason);
}
} break;
case FINALIZE_PENDING_INTENT_MSG: {
@@ -1643,15 +1720,15 @@ public final class ActivityManagerService extends ActivityManagerNative
break;
}
case REPORT_USER_SWITCH_MSG: {
- dispatchUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
+ dispatchUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2);
break;
}
case CONTINUE_USER_SWITCH_MSG: {
- continueUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
+ continueUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2);
break;
}
case USER_SWITCH_TIMEOUT_MSG: {
- timeoutUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
+ timeoutUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2);
break;
}
case IMMERSIVE_MODE_LOCK_MSG: {
@@ -1677,6 +1754,12 @@ public final class ActivityManagerService extends ActivityManagerNative
requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
break;
}
+ case START_PROFILES_MSG: {
+ synchronized (ActivityManagerService.this) {
+ startProfilesLocked();
+ }
+ break;
+ }
case UPDATE_TIME: {
synchronized (ActivityManagerService.this) {
for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
@@ -1690,7 +1773,14 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
-
+ break;
+ }
+ case SYSTEM_USER_START_MSG: {
+ mSystemServiceManager.startUser(msg.arg1);
+ break;
+ }
+ case SYSTEM_USER_CURRENT_MSG: {
+ mSystemServiceManager.switchUser(msg.arg1);
break;
}
}
@@ -1755,6 +1845,87 @@ public final class ActivityManagerService extends ActivityManagerNative
}
};
+ /**
+ * Monitor for package changes and update our internal state.
+ */
+ private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ // Remove all tasks with activities in the specified package from the list of recent tasks
+ synchronized (ActivityManagerService.this) {
+ for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
+ TaskRecord tr = mRecentTasks.get(i);
+ ComponentName cn = tr.intent.getComponent();
+ if (cn != null && cn.getPackageName().equals(packageName)) {
+ // If the package name matches, remove the task and kill the process
+ removeTaskByIdLocked(tr.taskId, ActivityManager.REMOVE_TASK_KILL_PROCESS);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ onPackageModified(packageName);
+ return true;
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ final PackageManager pm = mContext.getPackageManager();
+ final ArrayList<Pair<Intent, Integer>> recentTaskIntents =
+ new ArrayList<Pair<Intent, Integer>>();
+ final ArrayList<Integer> tasksToRemove = new ArrayList<Integer>();
+ // Copy the list of recent tasks so that we don't hold onto the lock on
+ // ActivityManagerService for long periods while checking if components exist.
+ synchronized (ActivityManagerService.this) {
+ for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
+ TaskRecord tr = mRecentTasks.get(i);
+ recentTaskIntents.add(new Pair<Intent, Integer>(tr.intent, tr.taskId));
+ }
+ }
+ // Check the recent tasks and filter out all tasks with components that no longer exist.
+ Intent tmpI = new Intent();
+ for (int i = recentTaskIntents.size() - 1; i >= 0; i--) {
+ Pair<Intent, Integer> p = recentTaskIntents.get(i);
+ ComponentName cn = p.first.getComponent();
+ if (cn != null && cn.getPackageName().equals(packageName)) {
+ try {
+ // Add the task to the list to remove if the component no longer exists
+ tmpI.setComponent(cn);
+ if (pm.queryIntentActivities(tmpI, PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
+ tasksToRemove.add(p.second);
+ }
+ } catch (Exception e) {}
+ }
+ }
+ // Prune all the tasks with removed components from the list of recent tasks
+ synchronized (ActivityManagerService.this) {
+ for (int i = tasksToRemove.size() - 1; i >= 0; i--) {
+ // Remove the task but don't kill the process (since other components in that
+ // package may still be running and in the background)
+ removeTaskByIdLocked(tasksToRemove.get(i), 0);
+ }
+ }
+ }
+
+ @Override
+ public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
+ // Force stop the specified packages
+ if (packages != null) {
+ for (String pkg : packages) {
+ synchronized (ActivityManagerService.this) {
+ if (forceStopPackageLocked(pkg, -1, false, false, false, false, false, 0,
+ "finished booting")) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+ };
+
public void setSystemProcess() {
try {
ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);
@@ -2001,6 +2172,10 @@ public final class ActivityManagerService extends ActivityManagerNative
Watchdog.getInstance().addThread(mHandler);
}
+ public void setSystemServiceManager(SystemServiceManager mgr) {
+ mSystemServiceManager = mgr;
+ }
+
private void start() {
mProcessCpuThread.start();
@@ -2123,29 +2298,24 @@ public final class ActivityManagerService extends ActivityManagerNative
totalUTime += otherUTime;
totalSTime += otherSTime;
if (pr != null) {
- BatteryStatsImpl.Uid.Proc ps = bstats.getProcessStatsLocked(
- st.name, st.pid);
+ BatteryStatsImpl.Uid.Proc ps = pr.curProcBatteryStats;
+ if (ps == null || !ps.isActive()) {
+ pr.curProcBatteryStats = ps = bstats.getProcessStatsLocked(
+ pr.info.uid, pr.processName);
+ }
ps.addCpuTimeLocked(st.rel_utime-otherUTime,
st.rel_stime-otherSTime);
ps.addSpeedStepTimes(cpuSpeedTimes);
pr.curCpuTime += (st.rel_utime+st.rel_stime) * 10;
- } else if (st.uid >= Process.FIRST_APPLICATION_UID) {
+ } else {
BatteryStatsImpl.Uid.Proc ps = st.batteryStats;
- if (ps == null) {
- st.batteryStats = ps = bstats.getProcessStatsLocked(st.uid,
- "(Unknown)");
+ if (ps == null || !ps.isActive()) {
+ st.batteryStats = ps = bstats.getProcessStatsLocked(
+ bstats.mapUid(st.uid), st.name);
}
ps.addCpuTimeLocked(st.rel_utime-otherUTime,
st.rel_stime-otherSTime);
ps.addSpeedStepTimes(cpuSpeedTimes);
- } else {
- BatteryStatsImpl.Uid.Proc ps =
- bstats.getProcessStatsLocked(st.name, st.pid);
- if (ps != null) {
- ps.addCpuTimeLocked(st.rel_utime-otherUTime,
- st.rel_stime-otherSTime);
- ps.addSpeedStepTimes(cpuSpeedTimes);
- }
}
}
bstats.finishAddingCpuLocked(perc, totalUTime,
@@ -2201,6 +2371,11 @@ public final class ActivityManagerService extends ActivityManagerNative
if (mFocusedActivity != r) {
if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedActivityLocked: r=" + r);
mFocusedActivity = r;
+ if (r.task != null && r.task.voiceInteractor != null) {
+ startRunningVoiceLocked();
+ } else {
+ finishRunningVoiceLocked();
+ }
mStackSupervisor.setFocusedStack(r);
if (r != null) {
mWindowManager.setFocusedApp(r.appToken, true);
@@ -2209,6 +2384,12 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ final void clearFocusedActivity(ActivityRecord r) {
+ if (mFocusedActivity == r) {
+ mFocusedActivity = null;
+ }
+ }
+
@Override
public void setFocusedStack(int stackId) {
if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedStack: stackId=" + stackId);
@@ -2303,11 +2484,12 @@ public final class ActivityManagerService extends ActivityManagerNative
final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
ProcessRecord client) {
- final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities;
+ final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities
+ || app.treatLikeActivity;
final boolean hasService = false; // not impl yet. app.services.size() > 0;
if (!activityChange && hasActivity) {
- // The process has activties, so we are only going to allow activity-based
- // adjustments move it. It should be kept in the front of the list with other
+ // The process has activities, so we are only allowing activity-based adjustments
+ // to move it. It should be kept in the front of the list with other
// processes that have activities, and we don't want those to change their
// order except due to activity operations.
return;
@@ -2799,6 +2981,11 @@ public final class ActivityManagerService extends ActivityManagerNative
mHandler.sendMessageDelayed(msg, startResult.usingWrapper
? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
}
+ mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_PROC_START,
+ app.processName, app.info.uid);
+ if (app.isolated) {
+ mBatteryStatsService.addIsolatedUid(app.uid, app.info.uid);
+ }
} catch (RuntimeException e) {
// XXX do better error recovery.
app.setPid(0);
@@ -2931,7 +3118,7 @@ public final class ActivityManagerService extends ActivityManagerNative
intent.setComponent(new ComponentName(
ri.activityInfo.packageName, ri.activityInfo.name));
mStackSupervisor.startActivityLocked(null, intent, null, ri.activityInfo,
- null, null, 0, 0, 0, null, 0, null, false, null, null);
+ null, null, null, null, 0, 0, 0, null, 0, null, false, null, null);
}
}
}
@@ -3026,11 +3213,10 @@ public final class ActivityManagerService extends ActivityManagerNative
observer.onForegroundActivitiesChanged(item.pid, item.uid,
item.foregroundActivities);
}
- if ((item.changes&ProcessChangeItem.CHANGE_IMPORTANCE) != 0) {
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "IMPORTANCE CHANGED pid="
- + item.pid + " uid=" + item.uid + ": " + item.importance);
- observer.onImportanceChanged(item.pid, item.uid,
- item.importance);
+ if ((item.changes&ProcessChangeItem.CHANGE_PROCESS_STATE) != 0) {
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "PROCSTATE CHANGED pid="
+ + item.pid + " uid=" + item.uid + ": " + item.processState);
+ observer.onProcessStateChanged(item.pid, item.uid, item.processState);
}
}
} catch (RemoteException e) {
@@ -3062,7 +3248,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
for (int i=0; i<N; i++) {
PendingActivityLaunch pal = mPendingActivityLaunches.get(i);
- mStackSupervisor.startActivityUncheckedLocked(pal.r, pal.sourceRecord, pal.startFlags,
+ mStackSupervisor.startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags,
doResume && i == (N-1), null);
}
mPendingActivityLaunches.clear();
@@ -3088,7 +3274,7 @@ public final class ActivityManagerService extends ActivityManagerNative
false, true, "startActivity", null);
// TODO: Switch to user app stacks here.
return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
- resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
+ null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
null, null, options, userId, null);
}
@@ -3103,7 +3289,7 @@ public final class ActivityManagerService extends ActivityManagerNative
WaitResult res = new WaitResult();
// TODO: Switch to user app stacks here.
mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
- resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
+ null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
res, null, options, UserHandle.getCallingUserId(), null);
return res;
}
@@ -3118,7 +3304,7 @@ public final class ActivityManagerService extends ActivityManagerNative
false, true, "startActivityWithConfig", null);
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
- resolvedType, resultTo, resultWho, requestCode, startFlags,
+ resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
null, null, null, config, options, userId, null);
return ret;
}
@@ -3156,6 +3342,31 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
+ public int startVoiceActivity(String callingPackage, int callingPid, int callingUid,
+ Intent intent, String resolvedType, IVoiceInteractionSession session,
+ IVoiceInteractor interactor, int startFlags, String profileFile,
+ ParcelFileDescriptor profileFd, Bundle options, int userId) {
+ if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: startVoiceActivity() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + android.Manifest.permission.BIND_VOICE_INTERACTION;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ if (session == null || interactor == null) {
+ throw new NullPointerException("null session or interactor");
+ }
+ userId = handleIncomingUser(callingPid, callingUid, userId,
+ false, true, "startVoiceActivity", null);
+ // TODO: Switch to user app stacks here.
+ return mStackSupervisor.startActivityMayWait(null, callingUid, callingPackage, intent,
+ resolvedType, session, interactor, null, null, 0, startFlags,
+ profileFile, profileFd, null, null, options, userId, null);
+ }
+
+ @Override
public boolean startNextMatchingActivity(IBinder callingActivity,
Intent intent, Bundle options) {
// Refuse possible leaked file descriptors
@@ -3248,7 +3459,7 @@ public final class ActivityManagerService extends ActivityManagerNative
final long origId = Binder.clearCallingIdentity();
int res = mStackSupervisor.startActivityLocked(r.app.thread, intent,
- r.resolvedType, aInfo, resultTo != null ? resultTo.appToken : null,
+ r.resolvedType, aInfo, null, null, resultTo != null ? resultTo.appToken : null,
resultWho, requestCode, -1, r.launchedFromUid, r.launchedFromPackage, 0,
options, false, null, null);
Binder.restoreCallingIdentity(origId);
@@ -3271,7 +3482,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent, resolvedType,
- resultTo, resultWho, requestCode, startFlags,
+ null, null, resultTo, resultWho, requestCode, startFlags,
null, null, null, null, options, userId, container);
return ret;
}
@@ -3307,22 +3518,41 @@ public final class ActivityManagerService extends ActivityManagerNative
if (N > 0 && mRecentTasks.get(0) == task) {
return;
}
+ // Another quick case: never add voice sessions.
+ if (task.voiceSession != null) {
+ return;
+ }
// Remove any existing entries that are the same kind of task.
+ final Intent intent = task.intent;
+ final boolean document = intent != null && intent.isDocument();
for (int i=0; i<N; i++) {
TaskRecord tr = mRecentTasks.get(i);
- if (task.userId == tr.userId
- && ((task.affinity != null && task.affinity.equals(tr.affinity))
- || (task.intent != null && task.intent.filterEquals(tr.intent)))) {
- tr.disposeThumbnail();
- mRecentTasks.remove(i);
- i--;
- N--;
- if (task.intent == null) {
- // If the new recent task we are adding is not fully
- // specified, then replace it with the existing recent task.
- task = tr;
+ if (task != tr) {
+ if (task.userId != tr.userId) {
+ continue;
+ }
+ final Intent trIntent = tr.intent;
+ if ((task.affinity == null || !task.affinity.equals(tr.affinity)) &&
+ (intent == null || !intent.filterEquals(trIntent))) {
+ continue;
+ }
+ if (document || trIntent != null && trIntent.isDocument()) {
+ // Document tasks do not match other tasks.
+ continue;
}
}
+
+ // Either task and tr are the same or, their affinities match or their intents match
+ // and neither of them is a document.
+ tr.disposeThumbnail();
+ mRecentTasks.remove(i);
+ i--;
+ N--;
+ if (task.intent == null) {
+ // If the new recent task we are adding is not fully
+ // specified, then replace it with the existing recent task.
+ task = tr;
+ }
}
if (N >= MAX_RECENT_TASKS) {
mRecentTasks.remove(N-1).disposeThumbnail();
@@ -3379,11 +3609,14 @@ public final class ActivityManagerService extends ActivityManagerNative
* @param token The Binder token referencing the Activity we want to finish.
* @param resultCode Result code, if any, from this Activity.
* @param resultData Result data (Intent), if any, from this Activity.
+ * @param finishTask Whether to finish the task associated with this Activity. Only applies to
+ * the root Activity in the task.
*
* @return Returns true if the activity successfully finished, or false if it is still running.
*/
@Override
- public final boolean finishActivity(IBinder token, int resultCode, Intent resultData) {
+ public final boolean finishActivity(IBinder token, int resultCode, Intent resultData,
+ boolean finishTask) {
// Refuse possible leaked file descriptors
if (resultData != null && resultData.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
@@ -3394,6 +3627,9 @@ public final class ActivityManagerService extends ActivityManagerNative
if (r == null) {
return true;
}
+ // Keep track of the root activity of the task before we finish it
+ TaskRecord tr = r.task;
+ ActivityRecord rootR = tr.getRootActivity();
if (mController != null) {
// Find the first activity that is not finishing.
ActivityRecord next = r.task.stack.topRunningActivityLocked(token, 0);
@@ -3413,10 +3649,21 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
final long origId = Binder.clearCallingIdentity();
- boolean res = r.task.stack.requestFinishActivityLocked(token, resultCode,
- resultData, "app-request", true);
- Binder.restoreCallingIdentity(origId);
- return res;
+ try {
+ boolean res;
+ if (finishTask && r == rootR) {
+ // If requested, remove the task that is associated to this activity only if it
+ // was the root activity in the task. The result code and data is ignored because
+ // we don't support returning them across task boundaries.
+ res = removeTaskByIdLocked(tr.taskId, 0);
+ } else {
+ res = tr.stack.requestFinishActivityLocked(token, resultCode,
+ resultData, "app-request", true);
+ }
+ return res;
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
}
}
@@ -4471,7 +4718,7 @@ public final class ActivityManagerService extends ActivityManagerNative
private void forceStopPackageLocked(final String packageName, int uid, String reason) {
forceStopPackageLocked(packageName, UserHandle.getAppId(uid), false,
- false, true, false, UserHandle.getUserId(uid), reason);
+ false, true, false, false, UserHandle.getUserId(uid), reason);
Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,
Uri.fromParts("package", packageName, null));
if (!mProcessesReady) {
@@ -4487,7 +4734,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
private void forceStopUserLocked(int userId, String reason) {
- forceStopPackageLocked(null, -1, false, false, true, false, userId, reason);
+ forceStopPackageLocked(null, -1, false, false, true, false, false, userId, reason);
Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
@@ -4572,7 +4819,7 @@ public final class ActivityManagerService extends ActivityManagerNative
private final boolean forceStopPackageLocked(String name, int appId,
boolean callerWillRestart, boolean purgeCache, boolean doit,
- boolean evenPersistent, int userId, String reason) {
+ boolean evenPersistent, boolean uninstalling, int userId, String reason) {
int i;
int N;
@@ -4664,7 +4911,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// Remove transient permissions granted from/to this package/user
removeUriPermissionsForPackageLocked(name, userId, false);
- if (name == null) {
+ if (name == null || uninstalling) {
// Remove pending intents. For now we only do this when force
// stopping users, because we have some problems when doing this
// for packages -- app widgets are not currently cleaned up for
@@ -4754,6 +5001,11 @@ public final class ActivityManagerService extends ActivityManagerNative
mPidsSelfLocked.remove(pid);
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
}
+ mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_PROC_FINISH,
+ app.processName, app.info.uid);
+ if (app.isolated) {
+ mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
+ }
killUnneededProcessLocked(app, reason);
handleAppDiedLocked(app, true, allowRestart);
removeLruProcessLocked(app);
@@ -4794,6 +5046,11 @@ public final class ActivityManagerService extends ActivityManagerNative
mHeavyWeightProcess.userId, 0));
mHeavyWeightProcess = null;
}
+ mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_PROC_FINISH,
+ app.processName, app.info.uid);
+ if (app.isolated) {
+ mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
+ }
// Take care of any launching providers waiting for this process.
checkAppInLaunchingProvidersLocked(app, true);
// Take care of any services that are waiting for the process.
@@ -4878,7 +5135,7 @@ public final class ActivityManagerService extends ActivityManagerNative
app.curAdj = app.setAdj = -100;
app.curSchedGroup = app.setSchedGroup = Process.THREAD_GROUP_DEFAULT;
app.forcingToForeground = null;
- app.foregroundServices = false;
+ updateProcessForegroundLocked(app, false, false);
app.hasShownUi = false;
app.debugging = false;
app.cached = false;
@@ -5099,26 +5356,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
final void finishBooting() {
- IntentFilter pkgFilter = new IntentFilter();
- pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
- pkgFilter.addDataScheme("package");
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String[] pkgs = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
- if (pkgs != null) {
- for (String pkg : pkgs) {
- synchronized (ActivityManagerService.this) {
- if (forceStopPackageLocked(pkg, -1, false, false, false, false, 0,
- "finished booting")) {
- setResultCode(Activity.RESULT_OK);
- return;
- }
- }
- }
- }
- }
- }, pkgFilter);
+ // Register receivers to handle package update events
+ mPackageMonitor.register(mContext, Looper.getMainLooper(), false);
synchronized (this) {
// Ensure that any processes we had put on hold are now started
@@ -5167,10 +5406,11 @@ public final class ActivityManagerService extends ActivityManagerNative
userId);
}
}
+ scheduleStartProfilesLocked();
}
}
}
-
+
final void ensureBootCompleted() {
boolean booting;
boolean enableScreen;
@@ -5180,7 +5420,7 @@ public final class ActivityManagerService extends ActivityManagerNative
enableScreen = !mBooted;
mBooted = true;
}
-
+
if (booting) {
finishBooting();
}
@@ -5203,43 +5443,36 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
- public final void activityPaused(IBinder token) {
+ public final void activityPaused(IBinder token, PersistableBundle persistentState) {
final long origId = Binder.clearCallingIdentity();
synchronized(this) {
ActivityStack stack = ActivityRecord.getStackLocked(token);
if (stack != null) {
- stack.activityPausedLocked(token, false);
+ stack.activityPausedLocked(token, false, persistentState);
}
}
Binder.restoreCallingIdentity(origId);
}
@Override
- public final void activityStopped(IBinder token, Bundle icicle, Bitmap thumbnail,
- CharSequence description) {
- if (localLOGV) Slog.v(
- TAG, "Activity stopped: token=" + token);
+ public final void activityStopped(IBinder token, Bundle icicle,
+ PersistableBundle persistentState, CharSequence description) {
+ if (localLOGV) Slog.v(TAG, "Activity stopped: token=" + token);
// Refuse possible leaked file descriptors
if (icicle != null && icicle.hasFileDescriptors()) {
throw new IllegalArgumentException("File descriptors passed in Bundle");
}
- ActivityRecord r = null;
-
final long origId = Binder.clearCallingIdentity();
synchronized (this) {
- r = ActivityRecord.isInStackLocked(token);
+ ActivityRecord r = ActivityRecord.isInStackLocked(token);
if (r != null) {
- r.task.stack.activityStoppedLocked(r, icicle, thumbnail, description);
+ r.task.stack.activityStoppedLocked(r, icicle, persistentState, description);
}
}
- if (r != null) {
- sendPendingThumbnail(r, null, null, null, false);
- }
-
trimApplications();
Binder.restoreCallingIdentity(origId);
@@ -5550,6 +5783,38 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
+ public String getTagForIntentSender(IIntentSender pendingResult, String prefix) {
+ if (!(pendingResult instanceof PendingIntentRecord)) {
+ return null;
+ }
+ try {
+ PendingIntentRecord res = (PendingIntentRecord)pendingResult;
+ Intent intent = res.key.requestIntent;
+ if (intent != null) {
+ if (res.lastTag != null && res.lastTagPrefix == prefix && (res.lastTagPrefix == null
+ || res.lastTagPrefix.equals(prefix))) {
+ return res.lastTag;
+ }
+ res.lastTagPrefix = prefix;
+ StringBuilder sb = new StringBuilder(128);
+ if (prefix != null) {
+ sb.append(prefix);
+ }
+ if (intent.getAction() != null) {
+ sb.append(intent.getAction());
+ } else if (intent.getComponent() != null) {
+ intent.getComponent().appendShortString(sb);
+ } else {
+ sb.append("?");
+ }
+ return res.lastTag = sb.toString();
+ }
+ } catch (ClassCastException e) {
+ }
+ return null;
+ }
+
+ @Override
public void setProcessLimit(int max) {
enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT,
"setProcessLimit()");
@@ -5581,7 +5846,7 @@ public final class ActivityManagerService extends ActivityManagerNative
return;
}
pr.forcingToForeground = null;
- pr.foregroundServices = false;
+ updateProcessForegroundLocked(pr, false, false);
}
updateOomAdjLocked();
}
@@ -5741,9 +6006,9 @@ public final class ActivityManagerService extends ActivityManagerNative
* in {@link ContentProvider}.
*/
private final boolean checkHoldingPermissionsLocked(
- IPackageManager pm, ProviderInfo pi, Uri uri, int uid, int modeFlags) {
+ IPackageManager pm, ProviderInfo pi, GrantUri grantUri, int uid, final int modeFlags) {
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "checkHoldingPermissionsLocked: uri=" + uri + " uid=" + uid);
+ "checkHoldingPermissionsLocked: uri=" + grantUri + " uid=" + uid);
if (pi.applicationInfo.uid == uid) {
return true;
@@ -5772,7 +6037,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// check if target holds any <path-permission> that match uri
final PathPermission[] pps = pi.pathPermissions;
if (pps != null) {
- final String path = uri.getPath();
+ final String path = grantUri.uri.getPath();
int i = pps.length;
while (i > 0 && (!readMet || !writeMet)) {
i--;
@@ -5837,47 +6102,67 @@ public final class ActivityManagerService extends ActivityManagerNative
return pi;
}
- private UriPermission findUriPermissionLocked(int targetUid, Uri uri) {
- ArrayMap<Uri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
+ private UriPermission findUriPermissionLocked(int targetUid, GrantUri grantUri) {
+ final ArrayMap<GrantUri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
if (targetUris != null) {
- return targetUris.get(uri);
- } else {
- return null;
+ return targetUris.get(grantUri);
}
+ return null;
}
- private UriPermission findOrCreateUriPermissionLocked(
- String sourcePkg, String targetPkg, int targetUid, Uri uri) {
- ArrayMap<Uri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
+ private UriPermission findOrCreateUriPermissionLocked(String sourcePkg,
+ String targetPkg, int targetUid, GrantUri grantUri) {
+ ArrayMap<GrantUri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
if (targetUris == null) {
targetUris = Maps.newArrayMap();
mGrantedUriPermissions.put(targetUid, targetUris);
}
- UriPermission perm = targetUris.get(uri);
+ UriPermission perm = targetUris.get(grantUri);
if (perm == null) {
- perm = new UriPermission(sourcePkg, targetPkg, targetUid, uri);
- targetUris.put(uri, perm);
+ perm = new UriPermission(sourcePkg, targetPkg, targetUid, grantUri);
+ targetUris.put(grantUri, perm);
}
return perm;
}
- private final boolean checkUriPermissionLocked(
- Uri uri, int uid, int modeFlags, int minStrength) {
+ private final boolean checkUriPermissionLocked(GrantUri grantUri, int uid,
+ final int modeFlags) {
+ final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
+ final int minStrength = persistable ? UriPermission.STRENGTH_PERSISTABLE
+ : UriPermission.STRENGTH_OWNED;
+
// Root gets to do everything.
if (uid == 0) {
return true;
}
- ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(uid);
+
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(uid);
if (perms == null) return false;
- UriPermission perm = perms.get(uri);
- if (perm == null) return false;
- return perm.getStrength(modeFlags) >= minStrength;
+
+ // First look for exact match
+ final UriPermission exactPerm = perms.get(grantUri);
+ if (exactPerm != null && exactPerm.getStrength(modeFlags) >= minStrength) {
+ return true;
+ }
+
+ // No exact match, look for prefixes
+ final int N = perms.size();
+ for (int i = 0; i < N; i++) {
+ final UriPermission perm = perms.valueAt(i);
+ if (perm.uri.prefix && grantUri.uri.isPathPrefixMatch(perm.uri.uri)
+ && perm.getStrength(modeFlags) >= minStrength) {
+ return true;
+ }
+ }
+
+ return false;
}
@Override
- public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
+ public int checkUriPermission(Uri uri, int pid, int uid,
+ final int modeFlags, int userId) {
enforceNotIsolatedCaller("checkUriPermission");
// Another redirected-binder-call permissions check as in
@@ -5892,8 +6177,8 @@ public final class ActivityManagerService extends ActivityManagerNative
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
- synchronized(this) {
- return checkUriPermissionLocked(uri, uid, modeFlags, UriPermission.STRENGTH_OWNED)
+ synchronized (this) {
+ return checkUriPermissionLocked(new GrantUri(userId, uri, false), uid, modeFlags)
? PackageManager.PERMISSION_GRANTED
: PackageManager.PERMISSION_DENIED;
}
@@ -5908,33 +6193,31 @@ public final class ActivityManagerService extends ActivityManagerNative
* If you already know the uid of the target, you can supply it in
* lastTargetUid else set that to -1.
*/
- int checkGrantUriPermissionLocked(int callingUid, String targetPkg,
- Uri uri, int modeFlags, int lastTargetUid) {
- final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
- modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- if (modeFlags == 0) {
+ int checkGrantUriPermissionLocked(int callingUid, String targetPkg, GrantUri grantUri,
+ final int modeFlags, int lastTargetUid) {
+ if (!Intent.isAccessUriMode(modeFlags)) {
return -1;
}
if (targetPkg != null) {
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Checking grant " + targetPkg + " permission to " + uri);
+ "Checking grant " + targetPkg + " permission to " + grantUri);
}
final IPackageManager pm = AppGlobals.getPackageManager();
// If this is not a content: uri, we can't do anything with it.
- if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+ if (!ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) {
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Can't grant URI permission for non-content URI: " + uri);
+ "Can't grant URI permission for non-content URI: " + grantUri);
return -1;
}
- final String authority = uri.getAuthority();
- final ProviderInfo pi = getProviderInfoLocked(authority, UserHandle.getUserId(callingUid));
+ final String authority = grantUri.uri.getAuthority();
+ final ProviderInfo pi = getProviderInfoLocked(authority, grantUri.sourceUserId);
if (pi == null) {
- Slog.w(TAG, "No content provider found for permission check: " + uri.toSafeString());
+ Slog.w(TAG, "No content provider found for permission check: " +
+ grantUri.uri.toSafeString());
return -1;
}
@@ -5954,10 +6237,10 @@ public final class ActivityManagerService extends ActivityManagerNative
if (targetUid >= 0) {
// First... does the target actually need this permission?
- if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) {
+ if (checkHoldingPermissionsLocked(pm, pi, grantUri, targetUid, modeFlags)) {
// No need to grant the target this permission.
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Target " + targetPkg + " already has full permission to " + uri);
+ "Target " + targetPkg + " already has full permission to " + grantUri);
return -1;
}
} else {
@@ -5983,14 +6266,14 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new SecurityException("Provider " + pi.packageName
+ "/" + pi.name
+ " does not allow granting of Uri permissions (uri "
- + uri + ")");
+ + grantUri + ")");
}
if (pi.uriPermissionPatterns != null) {
final int N = pi.uriPermissionPatterns.length;
boolean allowed = false;
for (int i=0; i<N; i++) {
if (pi.uriPermissionPatterns[i] != null
- && pi.uriPermissionPatterns[i].match(uri.getPath())) {
+ && pi.uriPermissionPatterns[i].match(grantUri.uri.getPath())) {
allowed = true;
break;
}
@@ -5999,42 +6282,37 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new SecurityException("Provider " + pi.packageName
+ "/" + pi.name
+ " does not allow granting of permission to path of Uri "
- + uri);
+ + grantUri);
}
}
// Third... does the caller itself have permission to access
// this uri?
- if (callingUid != Process.myUid()) {
- if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
+ if (UserHandle.getAppId(callingUid) != Process.SYSTEM_UID) {
+ if (!checkHoldingPermissionsLocked(pm, pi, grantUri, callingUid, modeFlags)) {
// Require they hold a strong enough Uri permission
- final int minStrength = persistable ? UriPermission.STRENGTH_PERSISTABLE
- : UriPermission.STRENGTH_OWNED;
- if (!checkUriPermissionLocked(uri, callingUid, modeFlags, minStrength)) {
+ if (!checkUriPermissionLocked(grantUri, callingUid, modeFlags)) {
throw new SecurityException("Uid " + callingUid
- + " does not have permission to uri " + uri);
+ + " does not have permission to uri " + grantUri);
}
}
}
-
return targetUid;
}
@Override
- public int checkGrantUriPermission(int callingUid, String targetPkg,
- Uri uri, int modeFlags) {
+ public int checkGrantUriPermission(int callingUid, String targetPkg, Uri uri,
+ final int modeFlags, int userId) {
enforceNotIsolatedCaller("checkGrantUriPermission");
synchronized(this) {
- return checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags, -1);
+ return checkGrantUriPermissionLocked(callingUid, targetPkg,
+ new GrantUri(userId, uri, false), modeFlags, -1);
}
}
- void grantUriPermissionUncheckedLocked(
- int targetUid, String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) {
- final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
- modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- if (modeFlags == 0) {
+ void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg, GrantUri grantUri,
+ final int modeFlags, UriPermissionOwner owner) {
+ if (!Intent.isAccessUriMode(modeFlags)) {
return;
}
@@ -6043,35 +6321,40 @@ public final class ActivityManagerService extends ActivityManagerNative
// the target.
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Granting " + targetPkg + "/" + targetUid + " permission to " + uri);
+ "Granting " + targetPkg + "/" + targetUid + " permission to " + grantUri);
- final String authority = uri.getAuthority();
- final ProviderInfo pi = getProviderInfoLocked(authority, UserHandle.getUserId(targetUid));
+ final String authority = grantUri.uri.getAuthority();
+ final ProviderInfo pi = getProviderInfoLocked(authority, grantUri.sourceUserId);
if (pi == null) {
- Slog.w(TAG, "No content provider found for grant: " + uri.toSafeString());
+ Slog.w(TAG, "No content provider found for grant: " + grantUri.toSafeString());
return;
}
+ if ((modeFlags & Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) != 0) {
+ grantUri.prefix = true;
+ }
final UriPermission perm = findOrCreateUriPermissionLocked(
- pi.packageName, targetPkg, targetUid, uri);
- perm.grantModes(modeFlags, persistable, owner);
+ pi.packageName, targetPkg, targetUid, grantUri);
+ perm.grantModes(modeFlags, owner);
}
- void grantUriPermissionLocked(int callingUid, String targetPkg, Uri uri,
- int modeFlags, UriPermissionOwner owner) {
+ void grantUriPermissionLocked(int callingUid, String targetPkg, GrantUri grantUri,
+ final int modeFlags, UriPermissionOwner owner) {
if (targetPkg == null) {
throw new NullPointerException("targetPkg");
}
- int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags, -1);
+ int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, grantUri, modeFlags,
+ -1);
if (targetUid < 0) {
return;
}
- grantUriPermissionUncheckedLocked(targetUid, targetPkg, uri, modeFlags, owner);
+ grantUriPermissionUncheckedLocked(targetUid, targetPkg, grantUri, modeFlags,
+ owner);
}
- static class NeededUriGrants extends ArrayList<Uri> {
+ static class NeededUriGrants extends ArrayList<GrantUri> {
final String targetPkg;
final int targetUid;
final int flags;
@@ -6108,13 +6391,14 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (data != null) {
- int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, data,
- mode, needed != null ? needed.targetUid : -1);
+ GrantUri grantUri = GrantUri.resolve(UserHandle.getUserId(callingUid), data);
+ int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, grantUri, mode,
+ needed != null ? needed.targetUid : -1);
if (targetUid > 0) {
if (needed == null) {
needed = new NeededUriGrants(targetPkg, targetUid, mode);
}
- needed.add(data);
+ needed.add(grantUri);
}
}
if (clip != null) {
@@ -6122,13 +6406,14 @@ public final class ActivityManagerService extends ActivityManagerNative
Uri uri = clip.getItemAt(i).getUri();
if (uri != null) {
int targetUid = -1;
- targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri,
- mode, needed != null ? needed.targetUid : -1);
+ GrantUri grantUri = GrantUri.resolve(UserHandle.getUserId(callingUid), uri);
+ targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, grantUri, mode,
+ needed != null ? needed.targetUid : -1);
if (targetUid > 0) {
if (needed == null) {
needed = new NeededUriGrants(targetPkg, targetUid, mode);
}
- needed.add(uri);
+ needed.add(grantUri);
}
} else {
Intent clipIntent = clip.getItemAt(i).getIntent();
@@ -6153,8 +6438,9 @@ public final class ActivityManagerService extends ActivityManagerNative
UriPermissionOwner owner) {
if (needed != null) {
for (int i=0; i<needed.size(); i++) {
+ GrantUri grantUri = needed.get(i);
grantUriPermissionUncheckedLocked(needed.targetUid, needed.targetPkg,
- needed.get(i), needed.flags, owner);
+ grantUri, needed.flags, owner);
}
}
}
@@ -6171,112 +6457,98 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
- public void grantUriPermission(IApplicationThread caller, String targetPkg,
- Uri uri, int modeFlags) {
+ public void grantUriPermission(IApplicationThread caller, String targetPkg, Uri uri,
+ final int modeFlags, int userId) {
enforceNotIsolatedCaller("grantUriPermission");
+ GrantUri grantUri = new GrantUri(userId, uri, false);
synchronized(this) {
final ProcessRecord r = getRecordForAppLocked(caller);
if (r == null) {
throw new SecurityException("Unable to find app for caller "
+ caller
- + " when granting permission to uri " + uri);
+ + " when granting permission to uri " + grantUri);
}
if (targetPkg == null) {
throw new IllegalArgumentException("null target");
}
- if (uri == null) {
+ if (grantUri == null) {
throw new IllegalArgumentException("null uri");
}
- // Persistable only supported through Intents
- Preconditions.checkFlagsArgument(modeFlags,
- Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ Preconditions.checkFlagsArgument(modeFlags, Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
- grantUriPermissionLocked(r.uid, targetPkg, uri, modeFlags,
- null);
+ grantUriPermissionLocked(r.uid, targetPkg, grantUri, modeFlags, null);
}
}
void removeUriPermissionIfNeededLocked(UriPermission perm) {
- if ((perm.modeFlags&(Intent.FLAG_GRANT_READ_URI_PERMISSION
- |Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) == 0) {
- ArrayMap<Uri, UriPermission> perms
- = mGrantedUriPermissions.get(perm.targetUid);
+ if (perm.modeFlags == 0) {
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(
+ perm.targetUid);
if (perms != null) {
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
"Removing " + perm.targetUid + " permission to " + perm.uri);
+
perms.remove(perm.uri);
- if (perms.size() == 0) {
+ if (perms.isEmpty()) {
mGrantedUriPermissions.remove(perm.targetUid);
}
}
}
}
- private void revokeUriPermissionLocked(int callingUid, Uri uri, int modeFlags) {
- if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Revoking all granted permissions to " + uri);
+ private void revokeUriPermissionLocked(int callingUid, GrantUri grantUri, final int modeFlags) {
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Revoking all granted permissions to " + grantUri);
final IPackageManager pm = AppGlobals.getPackageManager();
- final String authority = uri.getAuthority();
- final ProviderInfo pi = getProviderInfoLocked(authority, UserHandle.getUserId(callingUid));
+ final String authority = grantUri.uri.getAuthority();
+ final ProviderInfo pi = getProviderInfoLocked(authority, grantUri.sourceUserId);
if (pi == null) {
- Slog.w(TAG, "No content provider found for permission revoke: " + uri.toSafeString());
+ Slog.w(TAG, "No content provider found for permission revoke: "
+ + grantUri.toSafeString());
return;
}
// Does the caller have this permission on the URI?
- if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
+ if (!checkHoldingPermissionsLocked(pm, pi, grantUri, callingUid, modeFlags)) {
// Right now, if you are not the original owner of the permission,
// you are not allowed to revoke it.
//if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
throw new SecurityException("Uid " + callingUid
- + " does not have permission to uri " + uri);
+ + " does not have permission to uri " + grantUri);
//}
}
boolean persistChanged = false;
// Go through all of the permissions and remove any that match.
- final List<String> SEGMENTS = uri.getPathSegments();
- if (SEGMENTS != null) {
- final int NS = SEGMENTS.size();
- int N = mGrantedUriPermissions.size();
- for (int i=0; i<N; i++) {
- ArrayMap<Uri, UriPermission> perms
- = mGrantedUriPermissions.valueAt(i);
- Iterator<UriPermission> it = perms.values().iterator();
- toploop:
- while (it.hasNext()) {
- UriPermission perm = it.next();
- Uri targetUri = perm.uri;
- if (!authority.equals(targetUri.getAuthority())) {
- continue;
- }
- List<String> targetSegments = targetUri.getPathSegments();
- if (targetSegments == null) {
- continue;
- }
- if (targetSegments.size() < NS) {
- continue;
- }
- for (int j=0; j<NS; j++) {
- if (!SEGMENTS.get(j).equals(targetSegments.get(j))) {
- continue toploop;
- }
- }
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Revoking " + perm.targetUid + " permission to " + perm.uri);
- persistChanged |= perm.clearModes(modeFlags, true);
+ int N = mGrantedUriPermissions.size();
+ for (int i = 0; i < N; i++) {
+ final int targetUid = mGrantedUriPermissions.keyAt(i);
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
+
+ for (Iterator<UriPermission> it = perms.values().iterator(); it.hasNext();) {
+ final UriPermission perm = it.next();
+ if (perm.uri.sourceUserId == grantUri.sourceUserId
+ && perm.uri.uri.isPathPrefixMatch(grantUri.uri)) {
+ if (DEBUG_URI_PERMISSION)
+ Slog.v(TAG,
+ "Revoking " + perm.targetUid + " permission to " + perm.uri);
+ persistChanged |= perm.revokeModes(
+ modeFlags | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
if (perm.modeFlags == 0) {
it.remove();
}
}
- if (perms.size() == 0) {
- mGrantedUriPermissions.remove(
- mGrantedUriPermissions.keyAt(i));
- N--;
- i--;
- }
+ }
+
+ if (perms.isEmpty()) {
+ mGrantedUriPermissions.remove(targetUid);
+ N--;
+ i--;
}
}
@@ -6286,8 +6558,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
- public void revokeUriPermission(IApplicationThread caller, Uri uri,
- int modeFlags) {
+ public void revokeUriPermission(IApplicationThread caller, Uri uri, final int modeFlags,
+ int userId) {
enforceNotIsolatedCaller("revokeUriPermission");
synchronized(this) {
final ProcessRecord r = getRecordForAppLocked(caller);
@@ -6301,22 +6573,20 @@ public final class ActivityManagerService extends ActivityManagerNative
return;
}
- modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- if (modeFlags == 0) {
+ if (!Intent.isAccessUriMode(modeFlags)) {
return;
}
final IPackageManager pm = AppGlobals.getPackageManager();
final String authority = uri.getAuthority();
- final ProviderInfo pi = getProviderInfoLocked(authority, r.userId);
+ final ProviderInfo pi = getProviderInfoLocked(authority, userId);
if (pi == null) {
Slog.w(TAG, "No content provider found for permission revoke: "
+ uri.toSafeString());
return;
}
- revokeUriPermissionLocked(r.uid, uri, modeFlags);
+ revokeUriPermissionLocked(r.uid, new GrantUri(userId, uri, false), modeFlags);
}
}
@@ -6338,20 +6608,22 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean persistChanged = false;
- final int size = mGrantedUriPermissions.size();
- for (int i = 0; i < size; i++) {
+ int N = mGrantedUriPermissions.size();
+ for (int i = 0; i < N; i++) {
+ final int targetUid = mGrantedUriPermissions.keyAt(i);
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
+
// Only inspect grants matching user
if (userHandle == UserHandle.USER_ALL
- || userHandle == UserHandle.getUserId(mGrantedUriPermissions.keyAt(i))) {
- final Iterator<UriPermission> it = mGrantedUriPermissions.valueAt(i)
- .values().iterator();
- while (it.hasNext()) {
+ || userHandle == UserHandle.getUserId(targetUid)) {
+ for (Iterator<UriPermission> it = perms.values().iterator(); it.hasNext();) {
final UriPermission perm = it.next();
// Only inspect grants matching package
if (packageName == null || perm.sourcePkg.equals(packageName)
|| perm.targetPkg.equals(packageName)) {
- persistChanged |= perm.clearModes(~0, persistable);
+ persistChanged |= perm.revokeModes(
+ persistable ? ~0 : ~Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
// Only remove when no modes remain; any persisted grants
// will keep this alive.
@@ -6360,6 +6632,12 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
+
+ if (perms.isEmpty()) {
+ mGrantedUriPermissions.remove(targetUid);
+ N--;
+ i--;
+ }
}
}
@@ -6378,8 +6656,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
- public void grantUriPermissionFromOwner(IBinder token, int fromUid, String targetPkg,
- Uri uri, int modeFlags) {
+ public void grantUriPermissionFromOwner(IBinder token, int fromUid, String targetPkg, Uri uri,
+ final int modeFlags, int userId) {
synchronized(this) {
UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
if (owner == null) {
@@ -6399,12 +6677,13 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new IllegalArgumentException("null uri");
}
- grantUriPermissionLocked(fromUid, targetPkg, uri, modeFlags, owner);
+ grantUriPermissionLocked(fromUid, targetPkg, new GrantUri(userId, uri, false),
+ modeFlags, owner);
}
}
@Override
- public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode) {
+ public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId) {
synchronized(this) {
UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
if (owner == null) {
@@ -6414,7 +6693,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (uri == null) {
owner.removeUriPermissionsLocked(mode);
} else {
- owner.removeUriPermissionLocked(uri, mode);
+ owner.removeUriPermissionLocked(new GrantUri(userId, uri, false), mode);
}
}
}
@@ -6433,8 +6712,9 @@ public final class ActivityManagerService extends ActivityManagerNative
ArrayList<UriPermission.Snapshot> persist = Lists.newArrayList();
synchronized (this) {
final int size = mGrantedUriPermissions.size();
- for (int i = 0 ; i < size; i++) {
- for (UriPermission perm : mGrantedUriPermissions.valueAt(i).values()) {
+ for (int i = 0; i < size; i++) {
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
+ for (UriPermission perm : perms.values()) {
if (perm.persistedModeFlags != 0) {
persist.add(perm.snapshot());
}
@@ -6452,10 +6732,12 @@ public final class ActivityManagerService extends ActivityManagerNative
out.startTag(null, TAG_URI_GRANTS);
for (UriPermission.Snapshot perm : persist) {
out.startTag(null, TAG_URI_GRANT);
- writeIntAttribute(out, ATTR_USER_HANDLE, perm.userHandle);
+ writeIntAttribute(out, ATTR_SOURCE_USER_ID, perm.uri.sourceUserId);
+ writeIntAttribute(out, ATTR_TARGET_USER_ID, perm.targetUserId);
out.attribute(null, ATTR_SOURCE_PKG, perm.sourcePkg);
out.attribute(null, ATTR_TARGET_PKG, perm.targetPkg);
- out.attribute(null, ATTR_URI, String.valueOf(perm.uri));
+ out.attribute(null, ATTR_URI, String.valueOf(perm.uri.uri));
+ writeBooleanAttribute(out, ATTR_PREFIX, perm.uri.prefix);
writeIntAttribute(out, ATTR_MODE_FLAGS, perm.persistedModeFlags);
writeLongAttribute(out, ATTR_CREATED_TIME, perm.persistedCreateTime);
out.endTag(null, TAG_URI_GRANT);
@@ -6487,26 +6769,39 @@ public final class ActivityManagerService extends ActivityManagerNative
final String tag = in.getName();
if (type == START_TAG) {
if (TAG_URI_GRANT.equals(tag)) {
- final int userHandle = readIntAttribute(in, ATTR_USER_HANDLE);
+ final int sourceUserId;
+ final int targetUserId;
+ final int userHandle = readIntAttribute(in,
+ ATTR_USER_HANDLE, UserHandle.USER_NULL);
+ if (userHandle != UserHandle.USER_NULL) {
+ // For backwards compatibility.
+ sourceUserId = userHandle;
+ targetUserId = userHandle;
+ } else {
+ sourceUserId = readIntAttribute(in, ATTR_SOURCE_USER_ID);
+ targetUserId = readIntAttribute(in, ATTR_TARGET_USER_ID);
+ }
final String sourcePkg = in.getAttributeValue(null, ATTR_SOURCE_PKG);
final String targetPkg = in.getAttributeValue(null, ATTR_TARGET_PKG);
final Uri uri = Uri.parse(in.getAttributeValue(null, ATTR_URI));
+ final boolean prefix = readBooleanAttribute(in, ATTR_PREFIX);
final int modeFlags = readIntAttribute(in, ATTR_MODE_FLAGS);
final long createdTime = readLongAttribute(in, ATTR_CREATED_TIME, now);
// Sanity check that provider still belongs to source package
final ProviderInfo pi = getProviderInfoLocked(
- uri.getAuthority(), userHandle);
+ uri.getAuthority(), sourceUserId);
if (pi != null && sourcePkg.equals(pi.packageName)) {
int targetUid = -1;
try {
targetUid = AppGlobals.getPackageManager()
- .getPackageUid(targetPkg, userHandle);
+ .getPackageUid(targetPkg, targetUserId);
} catch (RemoteException e) {
}
if (targetUid != -1) {
final UriPermission perm = findOrCreateUriPermissionLocked(
- sourcePkg, targetPkg, targetUid, uri);
+ sourcePkg, targetPkg, targetUid,
+ new GrantUri(sourceUserId, uri, prefix));
perm.initPersistedModes(modeFlags, createdTime);
}
} else {
@@ -6528,7 +6823,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
- public void takePersistableUriPermission(Uri uri, int modeFlags) {
+ public void takePersistableUriPermission(Uri uri, final int modeFlags, int userId) {
enforceNotIsolatedCaller("takePersistableUriPermission");
Preconditions.checkFlagsArgument(modeFlags,
@@ -6536,13 +6831,31 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized (this) {
final int callingUid = Binder.getCallingUid();
- final UriPermission perm = findUriPermissionLocked(callingUid, uri);
- if (perm == null) {
- throw new SecurityException("No permission grant found for UID " + callingUid
- + " and Uri " + uri.toSafeString());
+ boolean persistChanged = false;
+ GrantUri grantUri = new GrantUri(userId, uri, false);
+
+ UriPermission exactPerm = findUriPermissionLocked(callingUid,
+ new GrantUri(userId, uri, false));
+ UriPermission prefixPerm = findUriPermissionLocked(callingUid,
+ new GrantUri(userId, uri, true));
+
+ final boolean exactValid = (exactPerm != null)
+ && ((modeFlags & exactPerm.persistableModeFlags) == modeFlags);
+ final boolean prefixValid = (prefixPerm != null)
+ && ((modeFlags & prefixPerm.persistableModeFlags) == modeFlags);
+
+ if (!(exactValid || prefixValid)) {
+ throw new SecurityException("No persistable permission grants found for UID "
+ + callingUid + " and Uri " + grantUri.toSafeString());
+ }
+
+ if (exactValid) {
+ persistChanged |= exactPerm.takePersistableModes(modeFlags);
+ }
+ if (prefixValid) {
+ persistChanged |= prefixPerm.takePersistableModes(modeFlags);
}
- boolean persistChanged = perm.takePersistableModes(modeFlags);
persistChanged |= maybePrunePersistedUriGrantsLocked(callingUid);
if (persistChanged) {
@@ -6552,7 +6865,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
- public void releasePersistableUriPermission(Uri uri, int modeFlags) {
+ public void releasePersistableUriPermission(Uri uri, final int modeFlags, int userId) {
enforceNotIsolatedCaller("releasePersistableUriPermission");
Preconditions.checkFlagsArgument(modeFlags,
@@ -6560,16 +6873,26 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized (this) {
final int callingUid = Binder.getCallingUid();
+ boolean persistChanged = false;
+
+ UriPermission exactPerm = findUriPermissionLocked(callingUid,
+ new GrantUri(userId, uri, false));
+ UriPermission prefixPerm = findUriPermissionLocked(callingUid,
+ new GrantUri(userId, uri, true));
+ if (exactPerm == null && prefixPerm == null) {
+ throw new SecurityException("No permission grants found for UID " + callingUid
+ + " and Uri " + uri.toSafeString());
+ }
- final UriPermission perm = findUriPermissionLocked(callingUid, uri);
- if (perm == null) {
- Slog.w(TAG, "No permission grant found for UID " + callingUid + " and Uri "
- + uri.toSafeString());
- return;
+ if (exactPerm != null) {
+ persistChanged |= exactPerm.releasePersistableModes(modeFlags);
+ removeUriPermissionIfNeededLocked(exactPerm);
+ }
+ if (prefixPerm != null) {
+ persistChanged |= prefixPerm.releasePersistableModes(modeFlags);
+ removeUriPermissionIfNeededLocked(prefixPerm);
}
- final boolean persistChanged = perm.releasePersistableModes(modeFlags);
- removeUriPermissionIfNeededLocked(perm);
if (persistChanged) {
schedulePersistUriGrants();
}
@@ -6583,7 +6906,7 @@ public final class ActivityManagerService extends ActivityManagerNative
* @return if any mutations occured that require persisting.
*/
private boolean maybePrunePersistedUriGrantsLocked(int uid) {
- final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(uid);
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(uid);
if (perms == null) return false;
if (perms.size() < MAX_PERSISTED_URI_GRANTS) return false;
@@ -6633,13 +6956,12 @@ public final class ActivityManagerService extends ActivityManagerNative
final ArrayList<android.content.UriPermission> result = Lists.newArrayList();
synchronized (this) {
if (incoming) {
- final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(
+ callingUid);
if (perms == null) {
Slog.w(TAG, "No permission grants found for " + packageName);
} else {
- final int size = perms.size();
- for (int i = 0; i < size; i++) {
- final UriPermission perm = perms.valueAt(i);
+ for (UriPermission perm : perms.values()) {
if (packageName.equals(perm.targetPkg) && perm.persistedModeFlags != 0) {
result.add(perm.buildPersistedPublicApiObject());
}
@@ -6648,10 +6970,9 @@ public final class ActivityManagerService extends ActivityManagerNative
} else {
final int size = mGrantedUriPermissions.size();
for (int i = 0; i < size; i++) {
- final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
- final int permsSize = perms.size();
- for (int j = 0; j < permsSize; j++) {
- final UriPermission perm = perms.valueAt(j);
+ final ArrayMap<GrantUri, UriPermission> perms =
+ mGrantedUriPermissions.valueAt(i);
+ for (UriPermission perm : perms.values()) {
if (packageName.equals(perm.sourcePkg) && perm.persistedModeFlags != 0) {
result.add(perm.buildPersistedPublicApiObject());
}
@@ -6699,66 +7020,51 @@ public final class ActivityManagerService extends ActivityManagerNative
// =========================================================
@Override
- public List<RunningTaskInfo> getTasks(int maxNum, int flags,
- IThumbnailReceiver receiver) {
- ArrayList<RunningTaskInfo> list = new ArrayList<RunningTaskInfo>();
-
- PendingThumbnailsRecord pending = new PendingThumbnailsRecord(receiver);
- ActivityRecord topRecord = null;
-
+ public List<IAppTask> getAppTasks() {
+ int callingUid = Binder.getCallingUid();
+ long ident = Binder.clearCallingIdentity();
synchronized(this) {
- if (localLOGV) Slog.v(
- TAG, "getTasks: max=" + maxNum + ", flags=" + flags
- + ", receiver=" + receiver);
-
- if (checkCallingPermission(android.Manifest.permission.GET_TASKS)
- != PackageManager.PERMISSION_GRANTED) {
- if (receiver != null) {
- // If the caller wants to wait for pending thumbnails,
- // it ain't gonna get them.
- try {
- receiver.finished();
- } catch (RemoteException ex) {
+ ArrayList<IAppTask> list = new ArrayList<IAppTask>();
+ try {
+ if (localLOGV) Slog.v(TAG, "getAppTasks");
+
+ final int N = mRecentTasks.size();
+ for (int i = 0; i < N; i++) {
+ TaskRecord tr = mRecentTasks.get(i);
+ // Skip tasks that are not created by the caller
+ if (tr.creatorUid == callingUid) {
+ ActivityManager.RecentTaskInfo taskInfo =
+ createRecentTaskInfoFromTaskRecord(tr);
+ AppTaskImpl taskImpl = new AppTaskImpl(taskInfo.persistentId, callingUid);
+ list.add(taskImpl);
}
}
- String msg = "Permission Denial: getTasks() from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + android.Manifest.permission.GET_TASKS;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
-
- // TODO: Improve with MRU list from all ActivityStacks.
- topRecord = mStackSupervisor.getTasksLocked(maxNum, receiver, pending, list);
-
- if (!pending.pendingRecords.isEmpty()) {
- mPendingThumbnails.add(pending);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
+ return list;
}
+ }
- if (localLOGV) Slog.v(TAG, "We have pending thumbnails: " + pending);
+ @Override
+ public List<RunningTaskInfo> getTasks(int maxNum, int flags) {
+ final int callingUid = Binder.getCallingUid();
+ ArrayList<RunningTaskInfo> list = new ArrayList<RunningTaskInfo>();
- if (topRecord != null) {
- if (localLOGV) Slog.v(TAG, "Requesting top thumbnail");
- try {
- IApplicationThread topThumbnail = topRecord.app.thread;
- topThumbnail.requestThumbnail(topRecord.appToken);
- } catch (Exception e) {
- Slog.w(TAG, "Exception thrown when requesting thumbnail", e);
- sendPendingThumbnail(null, topRecord.appToken, null, null, true);
- }
- }
+ synchronized(this) {
+ if (localLOGV) Slog.v(
+ TAG, "getTasks: max=" + maxNum + ", flags=" + flags);
- if (pending == null && receiver != null) {
- // In this case all thumbnails were available and the client
- // is being asked to be told when the remaining ones come in...
- // which is unusually, since the top-most currently running
- // activity should never have a canned thumbnail! Oh well.
- try {
- receiver.finished();
- } catch (RemoteException ex) {
+ final boolean allowed = checkCallingPermission(
+ android.Manifest.permission.GET_TASKS)
+ == PackageManager.PERMISSION_GRANTED;
+ if (!allowed) {
+ Slog.w(TAG, "getTasks: caller " + callingUid
+ + " does not hold GET_TASKS; limiting output");
}
+
+ // TODO: Improve with MRU list from all ActivityStacks.
+ mStackSupervisor.getTasksLocked(maxNum, list, callingUid, allowed);
}
return list;
@@ -6768,15 +7074,38 @@ public final class ActivityManagerService extends ActivityManagerNative
return mRecentTasks.get(0);
}
+ /**
+ * Creates a new RecentTaskInfo from a TaskRecord.
+ */
+ private ActivityManager.RecentTaskInfo createRecentTaskInfoFromTaskRecord(TaskRecord tr) {
+ ActivityManager.RecentTaskInfo rti
+ = new ActivityManager.RecentTaskInfo();
+ rti.id = tr.numActivities > 0 ? tr.taskId : -1;
+ rti.persistentId = tr.taskId;
+ rti.baseIntent = new Intent(tr.getBaseIntent());
+ rti.origActivity = tr.origActivity;
+ rti.description = tr.lastDescription;
+ rti.stackId = tr.stack.mStackId;
+ rti.userId = tr.userId;
+ rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription);
+ return rti;
+ }
+
@Override
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
int flags, int userId) {
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
+ final int callingUid = Binder.getCallingUid();
+ userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
false, true, "getRecentTasks", null);
synchronized (this) {
- enforceCallingPermission(android.Manifest.permission.GET_TASKS,
- "getRecentTasks()");
+ final boolean allowed = checkCallingPermission(
+ android.Manifest.permission.GET_TASKS)
+ == PackageManager.PERMISSION_GRANTED;
+ if (!allowed) {
+ Slog.w(TAG, "getRecentTasks: caller " + callingUid
+ + " does not hold GET_TASKS; limiting output");
+ }
final boolean detailed = checkCallingPermission(
android.Manifest.permission.GET_DETAILED_TASKS)
== PackageManager.PERMISSION_GRANTED;
@@ -6787,10 +7116,19 @@ public final class ActivityManagerService extends ActivityManagerNative
ArrayList<ActivityManager.RecentTaskInfo> res
= new ArrayList<ActivityManager.RecentTaskInfo>(
maxNum < N ? maxNum : N);
+
+ final Set<Integer> includedUsers;
+ if ((flags & ActivityManager.RECENT_INCLUDE_PROFILES) != 0) {
+ includedUsers = getProfileIdsLocked(userId);
+ } else {
+ includedUsers = new HashSet<Integer>();
+ }
+ includedUsers.add(Integer.valueOf(userId));
for (int i=0; i<N && maxNum > 0; i++) {
TaskRecord tr = mRecentTasks.get(i);
- // Only add calling user's recent tasks
- if (tr.userId != userId) continue;
+ // Only add calling user or related users recent tasks
+ if (!includedUsers.contains(Integer.valueOf(tr.userId))) continue;
+
// Return the entry if desired by the caller. We always return
// the first entry, because callers always expect this to be the
// foreground app. We may filter others if the caller has
@@ -6802,18 +7140,18 @@ public final class ActivityManagerService extends ActivityManagerNative
|| (tr.intent == null)
|| ((tr.intent.getFlags()
&Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0)) {
- ActivityManager.RecentTaskInfo rti
- = new ActivityManager.RecentTaskInfo();
- rti.id = tr.numActivities > 0 ? tr.taskId : -1;
- rti.persistentId = tr.taskId;
- rti.baseIntent = new Intent(
- tr.intent != null ? tr.intent : tr.affinityIntent);
+ if (!allowed) {
+ // If the caller doesn't have the GET_TASKS permission, then only
+ // allow them to see a small subset of tasks -- their own and home.
+ if (!tr.isHomeTask() && tr.creatorUid != callingUid) {
+ continue;
+ }
+ }
+
+ ActivityManager.RecentTaskInfo rti = createRecentTaskInfoFromTaskRecord(tr);
if (!detailed) {
rti.baseIntent.replaceExtras((Bundle)null);
}
- rti.origActivity = tr.origActivity;
- rti.description = tr.lastDescription;
- rti.stackId = tr.stack.mStackId;
if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0) {
// Check whether this activity is currently available.
@@ -6833,7 +7171,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// Will never happen.
}
}
-
+
res.add(rti);
maxNum--;
}
@@ -6880,6 +7218,17 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
+ public void setTaskDescription(IBinder token, ActivityManager.TaskDescription td) {
+ synchronized (this) {
+ ActivityRecord r = ActivityRecord.isInStackLocked(token);
+ if (r != null) {
+ r.taskDescription = td;
+ r.task.updateTaskDescription();
+ }
+ }
+ }
+
+ @Override
public boolean removeSubTask(int taskId, int subTaskIndex) {
synchronized (this) {
enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS,
@@ -6957,6 +7306,24 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ /**
+ * Removes the task with the specified task id.
+ *
+ * @param taskId Identifier of the task to be removed.
+ * @param flags Additional operational flags. May be 0 or
+ * {@link ActivityManager#REMOVE_TASK_KILL_PROCESS}.
+ * @return Returns true if the given task was found and removed.
+ */
+ private boolean removeTaskByIdLocked(int taskId, int flags) {
+ TaskRecord tr = recentTaskForIdLocked(taskId);
+ if (tr != null) {
+ tr.removeTaskActivitiesLocked(-1, false);
+ cleanUpRemovedTaskLocked(tr, flags);
+ return true;
+ }
+ return false;
+ }
+
@Override
public boolean removeTask(int taskId, int flags) {
synchronized (this) {
@@ -6964,40 +7331,22 @@ public final class ActivityManagerService extends ActivityManagerNative
"removeTask()");
long ident = Binder.clearCallingIdentity();
try {
- TaskRecord tr = recentTaskForIdLocked(taskId);
- if (tr != null) {
- ActivityRecord r = tr.removeTaskActivitiesLocked(-1, false);
- if (r != null) {
- cleanUpRemovedTaskLocked(tr, flags);
- return true;
- }
- if (tr.mActivities.size() == 0) {
- // Caller is just removing a recent task that is
- // not actively running. That is easy!
- cleanUpRemovedTaskLocked(tr, flags);
- return true;
- }
- Slog.w(TAG, "removeTask: task " + taskId
- + " does not have activities to remove, "
- + " but numActivities=" + tr.numActivities
- + ": " + tr);
- }
+ return removeTaskByIdLocked(taskId, flags);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
- return false;
}
/**
* TODO: Add mController hook
*/
@Override
- public void moveTaskToFront(int task, int flags, Bundle options) {
+ public void moveTaskToFront(int taskId, int flags, Bundle options) {
enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,
"moveTaskToFront()");
- if (DEBUG_STACK) Slog.d(TAG, "moveTaskToFront: moving task=" + task);
+ if (DEBUG_STACK) Slog.d(TAG, "moveTaskToFront: moving taskId=" + taskId);
synchronized(this) {
if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
Binder.getCallingUid(), "Task to front")) {
@@ -7006,6 +7355,14 @@ public final class ActivityManagerService extends ActivityManagerNative
}
final long origId = Binder.clearCallingIdentity();
try {
+ final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+ if (task == null) {
+ return;
+ }
+ if (mStackSupervisor.isLockTaskModeViolation(task)) {
+ Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode");
+ return;
+ }
mStackSupervisor.findTaskToMoveToFrontLocked(task, flags, options);
} finally {
Binder.restoreCallingIdentity(origId);
@@ -7194,85 +7551,106 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
+ public boolean isInHomeStack(int taskId) {
+ enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+ "getStackInfo()");
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ TaskRecord tr = recentTaskForIdLocked(taskId);
+ if (tr != null) {
+ return tr.stack.isHomeStack();
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return false;
+ }
+
+ @Override
public int getTaskForActivity(IBinder token, boolean onlyRoot) {
synchronized(this) {
return ActivityRecord.getTaskForActivityLocked(token, onlyRoot);
}
}
- // =========================================================
- // THUMBNAILS
- // =========================================================
-
- public void reportThumbnail(IBinder token,
- Bitmap thumbnail, CharSequence description) {
- //System.out.println("Report thumbnail for " + token + ": " + thumbnail);
- final long origId = Binder.clearCallingIdentity();
- sendPendingThumbnail(null, token, thumbnail, description, true);
- Binder.restoreCallingIdentity(origId);
+ private boolean isLockTaskAuthorized(ComponentName name) {
+// enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,
+// "startLockTaskMode()");
+// DevicePolicyManager dpm = (DevicePolicyManager)
+// mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+// return dpm != null && dpm.isLockTaskPermitted(name);
+ return true;
}
- final void sendPendingThumbnail(ActivityRecord r, IBinder token,
- Bitmap thumbnail, CharSequence description, boolean always) {
- TaskRecord task;
- ArrayList<PendingThumbnailsRecord> receivers = null;
+ private void startLockTaskMode(TaskRecord task) {
+ if (!isLockTaskAuthorized(task.intent.getComponent())) {
+ return;
+ }
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ // Since we lost lock on task, make sure it is still there.
+ task = mStackSupervisor.anyTaskForIdLocked(task.taskId);
+ if (task != null) {
+ mStackSupervisor.setLockTaskModeLocked(task);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
- //System.out.println("Send pending thumbnail: " + r);
+ @Override
+ public void startLockTaskMode(int taskId) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ final TaskRecord task;
+ synchronized (this) {
+ task = mStackSupervisor.anyTaskForIdLocked(taskId);
+ }
+ if (task != null) {
+ startLockTaskMode(task);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
- synchronized(this) {
- if (r == null) {
- r = ActivityRecord.isInStackLocked(token);
+ @Override
+ public void startLockTaskMode(IBinder token) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ final TaskRecord task;
+ synchronized (this) {
+ final ActivityRecord r = ActivityRecord.forToken(token);
if (r == null) {
return;
}
+ task = r.task;
}
- if (thumbnail == null && r.thumbHolder != null) {
- thumbnail = r.thumbHolder.lastThumbnail;
- description = r.thumbHolder.lastDescription;
+ if (task != null) {
+ startLockTaskMode(task);
}
- if (thumbnail == null && !always) {
- // If there is no thumbnail, and this entry is not actually
- // going away, then abort for now and pick up the next
- // thumbnail we get.
- return;
- }
- task = r.task;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
- int N = mPendingThumbnails.size();
- int i=0;
- while (i<N) {
- PendingThumbnailsRecord pr = mPendingThumbnails.get(i);
- //System.out.println("Looking in " + pr.pendingRecords);
- if (pr.pendingRecords.remove(r)) {
- if (receivers == null) {
- receivers = new ArrayList<PendingThumbnailsRecord>();
- }
- receivers.add(pr);
- if (pr.pendingRecords.size() == 0) {
- pr.finished = true;
- mPendingThumbnails.remove(i);
- N--;
- continue;
- }
- }
- i++;
- }
+ @Override
+ public void stopLockTaskMode() {
+// enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,
+// "stopLockTaskMode()");
+ synchronized (this) {
+ mStackSupervisor.setLockTaskModeLocked(null);
}
+ }
- if (receivers != null) {
- final int N = receivers.size();
- for (int i=0; i<N; i++) {
- try {
- PendingThumbnailsRecord pr = receivers.get(i);
- pr.receiver.newThumbnail(
- task != null ? task.taskId : -1, thumbnail, description);
- if (pr.finished) {
- pr.receiver.finished();
- }
- } catch (Exception e) {
- Slog.w(TAG, "Exception thrown when sending thumbnail", e);
- }
- }
+ @Override
+ public boolean isInLockTaskMode() {
+ synchronized (this) {
+ return mStackSupervisor.isInLockTaskMode();
}
}
@@ -7338,9 +7716,25 @@ public final class ActivityManagerService extends ActivityManagerNative
* in {@link ContentProvider}.
*/
private final String checkContentProviderPermissionLocked(
- ProviderInfo cpi, ProcessRecord r) {
+ ProviderInfo cpi, ProcessRecord r, int userId) {
final int callingPid = (r != null) ? r.pid : Binder.getCallingPid();
final int callingUid = (r != null) ? r.uid : Binder.getCallingUid();
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
+ // Looking for cross-user grants before to enforce the typical cross-users permissions
+ if (userId != UserHandle.getUserId(callingUid)) {
+ if (perms != null) {
+ for (GrantUri grantUri : perms.keySet()) {
+ if (grantUri.sourceUserId == userId) {
+ String authority = grantUri.uri.getAuthority();
+ if (authority.equals(cpi.authority)) {
+ return null;
+ }
+ }
+ }
+ }
+ }
+ userId = handleIncomingUser(callingPid, callingUid, userId,
+ false, true, "checkContentProviderPermissionLocked", null);
if (checkComponentPermission(cpi.readPermission, callingPid, callingUid,
cpi.applicationInfo.uid, cpi.exported)
== PackageManager.PERMISSION_GRANTED) {
@@ -7370,11 +7764,10 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
-
- ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
+
if (perms != null) {
- for (Map.Entry<Uri, UriPermission> uri : perms.entrySet()) {
- if (uri.getKey().getAuthority().equals(cpi.authority)) {
+ for (GrantUri grantUri : perms.keySet()) {
+ if (grantUri.uri.getAuthority().equals(cpi.authority)) {
return null;
}
}
@@ -7482,7 +7875,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (providerRunning) {
cpi = cpr.info;
String msg;
- if ((msg=checkContentProviderPermissionLocked(cpi, r)) != null) {
+ if ((msg=checkContentProviderPermissionLocked(cpi, r, userId)) != null) {
throw new SecurityException(msg);
}
@@ -7570,7 +7963,7 @@ public final class ActivityManagerService extends ActivityManagerNative
cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);
String msg;
- if ((msg=checkContentProviderPermissionLocked(cpi, r)) != null) {
+ if ((msg=checkContentProviderPermissionLocked(cpi, r, userId)) != null) {
throw new SecurityException(msg);
}
@@ -7736,6 +8129,7 @@ public final class ActivityManagerService extends ActivityManagerNative
return cpr != null ? cpr.newHolder(conn) : null;
}
+ @Override
public final ContentProviderHolder getContentProvider(
IApplicationThread caller, String name, int userId, boolean stable) {
enforceNotIsolatedCaller("getContentProvider");
@@ -7745,9 +8139,8 @@ public final class ActivityManagerService extends ActivityManagerNative
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
-
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, true, "getContentProvider", null);
+ // The incoming user check is now handled in checkContentProviderPermissionLocked() to deal
+ // with cross-user grant.
return getContentProviderImpl(caller, name, null, stable, userId);
}
@@ -7770,22 +8163,27 @@ public final class ActivityManagerService extends ActivityManagerNative
*/
public void removeContentProvider(IBinder connection, boolean stable) {
enforceNotIsolatedCaller("removeContentProvider");
- synchronized (this) {
- ContentProviderConnection conn;
- try {
- conn = (ContentProviderConnection)connection;
- } catch (ClassCastException e) {
- String msg ="removeContentProvider: " + connection
- + " not a ContentProviderConnection";
- Slog.w(TAG, msg);
- throw new IllegalArgumentException(msg);
- }
- if (conn == null) {
- throw new NullPointerException("connection is null");
- }
- if (decProviderCountLocked(conn, null, null, stable)) {
- updateOomAdjLocked();
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ ContentProviderConnection conn;
+ try {
+ conn = (ContentProviderConnection)connection;
+ } catch (ClassCastException e) {
+ String msg ="removeContentProvider: " + connection
+ + " not a ContentProviderConnection";
+ Slog.w(TAG, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (conn == null) {
+ throw new NullPointerException("connection is null");
+ }
+ if (decProviderCountLocked(conn, null, null, stable)) {
+ updateOomAdjLocked();
+ }
}
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -8195,11 +8593,27 @@ public final class ActivityManagerService extends ActivityManagerNative
return mSleeping || mShuttingDown;
}
+ public boolean isSleeping() {
+ return mSleeping;
+ }
+
void goingToSleep() {
synchronized(this) {
mWentToSleep = true;
updateEventDispatchingLocked();
+ goToSleepIfNeededLocked();
+ }
+ }
+
+ void finishRunningVoiceLocked() {
+ if (mRunningVoice) {
+ mRunningVoice = false;
+ goToSleepIfNeededLocked();
+ }
+ }
+ void goToSleepIfNeededLocked() {
+ if (mWentToSleep && !mRunningVoice) {
if (!mSleeping) {
mSleeping = true;
mStackSupervisor.goingToSleepLocked();
@@ -8262,7 +8676,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
private void comeOutOfSleepIfNeededLocked() {
- if (!mWentToSleep && !mLockScreenShown) {
+ if ((!mWentToSleep && !mLockScreenShown) || mRunningVoice) {
if (mSleeping) {
mSleeping = false;
mStackSupervisor.comeOutOfSleepIfNeededLocked();
@@ -8278,6 +8692,13 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ void startRunningVoiceLocked() {
+ if (!mRunningVoice) {
+ mRunningVoice = true;
+ comeOutOfSleepIfNeededLocked();
+ }
+ }
+
private void updateEventDispatchingLocked() {
mWindowManager.setEventDispatching(mBooted && !mWentToSleep && !mShuttingDown);
}
@@ -8380,7 +8801,7 @@ public final class ActivityManagerService extends ActivityManagerNative
mDebugTransient = !persistent;
if (packageName != null) {
forceStopPackageLocked(packageName, -1, false, false, true, true,
- UserHandle.USER_ALL, "set debug app");
+ false, UserHandle.USER_ALL, "set debug app");
}
}
} finally {
@@ -8660,7 +9081,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
- public boolean convertToTranslucent(IBinder token) {
+ public boolean convertToTranslucent(IBinder token, ActivityOptions options) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (this) {
@@ -8669,7 +9090,7 @@ public final class ActivityManagerService extends ActivityManagerNative
return false;
}
if (r.changeWindowTranslucency(false)) {
- r.task.stack.convertToTranslucent(r);
+ r.task.stack.convertToTranslucent(r, options);
mWindowManager.setAppFullscreen(token, false);
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
return true;
@@ -8682,6 +9103,24 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
+ public ActivityOptions getActivityOptions(IBinder token) {
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+ if (r != null) {
+ final ActivityOptions activityOptions = r.pendingOptions;
+ r.pendingOptions = null;
+ return activityOptions;
+ }
+ return null;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ @Override
public void setImmersive(IBinder token, boolean immersive) {
synchronized(this) {
final ActivityRecord r = ActivityRecord.isInStackLocked(token);
@@ -8750,7 +9189,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Context.WINDOW_SERVICE)).addView(v, lp);
}
- public void noteWakeupAlarm(IIntentSender sender) {
+ public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg) {
if (!(sender instanceof PendingIntentRecord)) {
return;
}
@@ -8762,7 +9201,8 @@ public final class ActivityManagerService extends ActivityManagerNative
int MY_UID = Binder.getCallingUid();
int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
BatteryStatsImpl.Uid.Pkg pkg =
- stats.getPackageStatsLocked(uid, rec.key.packageName);
+ stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid,
+ sourcePkg != null ? sourcePkg : rec.key.packageName);
pkg.incWakeupsLocked();
}
}
@@ -8983,7 +9423,7 @@ public final class ActivityManagerService extends ActivityManagerNative
proc.notCachedSinceIdle = true;
proc.initialIdlePss = 0;
proc.nextPssTime = ProcessList.computeNextPssTime(proc.curProcState, true,
- mSleeping, now);
+ isSleeping(), now);
}
}
@@ -9203,6 +9643,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
mAppOpsService.systemReady();
+ mUsageStatsService.systemReady();
mSystemReady = true;
}
@@ -9282,6 +9723,8 @@ public final class ActivityManagerService extends ActivityManagerNative
if (goingCallback != null) goingCallback.run();
+ mSystemServiceManager.startUser(mCurrentUserId);
+
synchronized (this) {
if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
try {
@@ -9338,6 +9781,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}, 0, null, null,
android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
+ } catch (Throwable t) {
+ Slog.wtf(TAG, "Failed sending first user broadcasts", t);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -10167,6 +10612,7 @@ public final class ActivityManagerService extends ActivityManagerNative
int adj = app.curAdj;
outInfo.importance = oomAdjToImportance(adj, outInfo);
outInfo.importanceReasonCode = app.adjTypeCode;
+ outInfo.processState = app.curProcState;
}
public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses() {
@@ -10837,8 +11283,8 @@ public final class ActivityManagerService extends ActivityManagerNative
pw.println(" mSleeping=" + mSleeping + " mWentToSleep=" + mWentToSleep
+ " mLockScreenShown " + mLockScreenShown);
}
- if (mShuttingDown) {
- pw.println(" mShuttingDown=" + mShuttingDown);
+ if (mShuttingDown || mRunningVoice) {
+ pw.print(" mShuttingDown=" + mShuttingDown + " mRunningVoice=" + mRunningVoice);
}
}
if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
@@ -11335,8 +11781,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (dumpUid >= -1 && UserHandle.getAppId(uid) != dumpUid) {
continue;
}
- ArrayMap<Uri, UriPermission> perms
- = mGrantedUriPermissions.valueAt(i);
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
if (!printed) {
if (needSep) pw.println();
needSep = true;
@@ -11344,8 +11789,7 @@ public final class ActivityManagerService extends ActivityManagerNative
printed = true;
printedAnything = true;
}
- pw.print(" * UID "); pw.print(uid);
- pw.println(" holds:");
+ pw.print(" * UID "); pw.print(uid); pw.println(" holds:");
for (UriPermission perm : perms.values()) {
pw.print(" "); pw.println(perm);
if (dumpAll) {
@@ -12115,7 +12559,25 @@ public final class ActivityManagerService extends ActivityManagerNative
if (!brief) {
if (!isCompact) {
pw.print("Total RAM: "); pw.print(memInfo.getTotalSizeKb());
- pw.println(" kB");
+ pw.print(" kB (status ");
+ switch (mLastMemoryLevel) {
+ case ProcessStats.ADJ_MEM_FACTOR_NORMAL:
+ pw.println("normal)");
+ break;
+ case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
+ pw.println("moderate)");
+ break;
+ case ProcessStats.ADJ_MEM_FACTOR_LOW:
+ pw.println("low)");
+ break;
+ case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
+ pw.println("critical)");
+ break;
+ default:
+ pw.print(mLastMemoryLevel);
+ pw.println(")");
+ break;
+ }
pw.print(" Free RAM: "); pw.print(cachedPss + memInfo.getCachedSizeKb()
+ memInfo.getFreeSizeKb()); pw.print(" kB (");
pw.print(cachedPss); pw.print(" cached pss + ");
@@ -12330,9 +12792,10 @@ public final class ActivityManagerService extends ActivityManagerNative
app.unlinkDeathRecipient();
app.makeInactive(mProcessStats);
app.forcingToForeground = null;
- app.foregroundServices = false;
+ updateProcessForegroundLocked(app, false, false);
app.foregroundActivities = false;
app.hasShownUi = false;
+ app.treatLikeActivity = false;
app.hasAboveClient = false;
app.hasClientActivities = false;
@@ -12460,10 +12923,16 @@ public final class ActivityManagerService extends ActivityManagerNative
startProcessLocked(app, "restart", app.processName);
} else if (app.pid > 0 && app.pid != MY_PID) {
// Goodbye!
+ boolean removed;
synchronized (mPidsSelfLocked) {
mPidsSelfLocked.remove(app.pid);
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
}
+ mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_PROC_FINISH,
+ app.processName, app.info.uid);
+ if (app.isolated) {
+ mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
+ }
app.setPid(0);
}
}
@@ -13290,7 +13759,7 @@ public final class ActivityManagerService extends ActivityManagerNative
String list[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
if (list != null && (list.length > 0)) {
for (String pkg : list) {
- forceStopPackageLocked(pkg, -1, false, true, true, false, userId,
+ forceStopPackageLocked(pkg, -1, false, true, true, false, false, userId,
"storage unmount");
}
sendPackageBroadcastLocked(
@@ -13302,10 +13771,13 @@ public final class ActivityManagerService extends ActivityManagerNative
if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(
intent.getAction());
+ boolean fullUninstall = removed &&
+ !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
if (!intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false)) {
forceStopPackageLocked(ssp, UserHandle.getAppId(
intent.getIntExtra(Intent.EXTRA_UID, -1)), false, true, true,
- false, userId, removed ? "pkg removed" : "pkg changed");
+ false, fullUninstall, userId,
+ removed ? "pkg removed" : "pkg changed");
}
if (removed) {
sendPackageBroadcastLocked(IApplicationThread.PACKAGE_REMOVED,
@@ -13365,7 +13837,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) {
- ProxyProperties proxy = intent.getParcelableExtra("proxy");
+ ProxyInfo proxy = intent.getParcelableExtra("proxy");
mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy));
}
@@ -13792,7 +14264,7 @@ public final class ActivityManagerService extends ActivityManagerNative
final long origId = Binder.clearCallingIdentity();
// Instrumentation can kill and relaunch even persistent processes
- forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, userId,
+ forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false, userId,
"start instr");
ProcessRecord app = addAppLocked(ai, false);
app.instrumentationClass = className;
@@ -13860,7 +14332,7 @@ public final class ActivityManagerService extends ActivityManagerNative
app.instrumentationProfileFile = null;
app.instrumentationArguments = null;
- forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, app.userId,
+ forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, false, app.userId,
"finished inst");
}
@@ -13993,6 +14465,7 @@ public final class ActivityManagerService extends ActivityManagerNative
newConfig.seq = mConfigurationSeq;
mConfiguration = newConfig;
Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
+ mUsageStatsService.noteStartConfig(newConfig);
final Configuration configCopy = new Configuration(mConfiguration);
@@ -14196,7 +14669,7 @@ public final class ActivityManagerService extends ActivityManagerNative
app.keeping = true;
app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;
app.curProcState = ActivityManager.PROCESS_STATE_PERSISTENT;
- // System process can do UI, and when they do we want to have
+ // System processes can do UI, and when they do we want to have
// them trim their memory after the user leaves the UI. To
// facilitate this, here we need to determine whether or not it
// is currently showing UI.
@@ -14616,6 +15089,9 @@ public final class ActivityManagerService extends ActivityManagerNative
app.adjTarget = s.name;
}
}
+ if ((cr.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
+ app.treatLikeActivity = true;
+ }
final ActivityRecord a = cr.activity;
if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ &&
@@ -14748,10 +15224,17 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY && app.hasClientActivities) {
- // This is a cached process, but with client activities. Mark it so.
- procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
- app.adjType = "cch-client-act";
+ if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
+ if (app.hasClientActivities) {
+ // This is a cached process, but with client activities. Mark it so.
+ procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
+ app.adjType = "cch-client-act";
+ } else if (app.treatLikeActivity) {
+ // This is a cached process, but somebody wants us to treat it like it has
+ // an activity, okay!
+ procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
+ app.adjType = "cch-as-act";
+ }
}
if (adj == ProcessList.SERVICE_ADJ) {
@@ -14801,89 +15284,10 @@ public final class ActivityManagerService extends ActivityManagerNative
// it when computing the final cached adj later. Note that we don't need to
// worry about this for max adj above, since max adj will always be used to
// keep it out of the cached vaues.
- adj = app.modifyRawOomAdj(adj);
-
+ app.curAdj = app.modifyRawOomAdj(adj);
+ app.curSchedGroup = schedGroup;
app.curProcState = procState;
-
- int importance = app.memImportance;
- if (importance == 0 || adj != app.curAdj || schedGroup != app.curSchedGroup) {
- app.curAdj = adj;
- app.curSchedGroup = schedGroup;
- if (!interesting) {
- // For this reporting, if there is not something explicitly
- // interesting in this process then we will push it to the
- // background importance.
- importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
- } else if (adj >= ProcessList.CACHED_APP_MIN_ADJ) {
- importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
- } else if (adj >= ProcessList.SERVICE_B_ADJ) {
- importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE;
- } else if (adj >= ProcessList.HOME_APP_ADJ) {
- importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
- } else if (adj >= ProcessList.SERVICE_ADJ) {
- importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE;
- } else if (adj >= ProcessList.HEAVY_WEIGHT_APP_ADJ) {
- importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE;
- } else if (adj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
- importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE;
- } else if (adj >= ProcessList.VISIBLE_APP_ADJ) {
- importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
- } else if (adj >= ProcessList.FOREGROUND_APP_ADJ) {
- importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
- } else {
- importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERSISTENT;
- }
- }
-
- int changes = importance != app.memImportance ? ProcessChangeItem.CHANGE_IMPORTANCE : 0;
- if (foregroundActivities != app.foregroundActivities) {
- changes |= ProcessChangeItem.CHANGE_ACTIVITIES;
- }
- if (changes != 0) {
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Changes in " + app + ": " + changes);
- app.memImportance = importance;
- app.foregroundActivities = foregroundActivities;
- int i = mPendingProcessChanges.size()-1;
- ProcessChangeItem item = null;
- while (i >= 0) {
- item = mPendingProcessChanges.get(i);
- if (item.pid == app.pid) {
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Re-using existing item: " + item);
- break;
- }
- i--;
- }
- if (i < 0) {
- // No existing item in pending changes; need a new one.
- final int NA = mAvailProcessChanges.size();
- if (NA > 0) {
- item = mAvailProcessChanges.remove(NA-1);
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Retreiving available item: " + item);
- } else {
- item = new ProcessChangeItem();
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Allocating new item: " + item);
- }
- item.changes = 0;
- item.pid = app.pid;
- item.uid = app.info.uid;
- if (mPendingProcessChanges.size() == 0) {
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG,
- "*** Enqueueing dispatch processes changed!");
- mHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED).sendToTarget();
- }
- mPendingProcessChanges.add(item);
- }
- item.changes |= changes;
- item.importance = importance;
- item.foregroundActivities = foregroundActivities;
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Item "
- + Integer.toHexString(System.identityHashCode(item))
- + " " + app.toShortString() + ": changes=" + item.changes
- + " importance=" + item.importance
- + " foreground=" + item.foregroundActivities
- + " type=" + app.adjType + " source=" + app.adjSource
- + " target=" + app.adjTarget);
- }
+ app.foregroundActivities = foregroundActivities;
return app.curRawAdj;
}
@@ -14922,7 +15326,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) {
app.pssProcState = app.setProcState;
app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true,
- mSleeping, now);
+ isSleeping(), now);
mPendingPssProcesses.add(app);
}
}
@@ -14959,7 +15363,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
return !processingBroadcasts
- && (mSleeping || mStackSupervisor.allResumedActivitiesIdle());
+ && (isSleeping() || mStackSupervisor.allResumedActivitiesIdle());
}
/**
@@ -15156,7 +15560,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
private final boolean applyOomAdjLocked(ProcessRecord app, boolean wasKeeping,
- ProcessRecord TOP_APP, boolean doingAll, boolean reportingProcessState, long now) {
+ ProcessRecord TOP_APP, boolean doingAll, long now) {
boolean success = true;
if (app.curRawAdj != app.setRawAdj) {
@@ -15175,6 +15579,8 @@ public final class ActivityManagerService extends ActivityManagerNative
app.setRawAdj = app.curRawAdj;
}
+ int changes = 0;
+
if (app.curAdj != app.setAdj) {
ProcessList.setOomAdj(app.pid, app.curAdj);
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(
@@ -15216,9 +15622,14 @@ public final class ActivityManagerService extends ActivityManagerNative
app.curSchedGroup <= Process.THREAD_GROUP_BG_NONINTERACTIVE);
}
}
+ if (app.repForegroundActivities != app.foregroundActivities) {
+ app.repForegroundActivities = app.foregroundActivities;
+ changes |= ProcessChangeItem.CHANGE_ACTIVITIES;
+ }
if (app.repProcState != app.curProcState) {
app.repProcState = app.curProcState;
- if (!reportingProcessState && app.thread != null) {
+ changes |= ProcessChangeItem.CHANGE_PROCESS_STATE;
+ if (app.thread != null) {
try {
if (false) {
//RuntimeException h = new RuntimeException("here");
@@ -15234,7 +15645,7 @@ public final class ActivityManagerService extends ActivityManagerNative
app.setProcState)) {
app.lastStateTime = now;
app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true,
- mSleeping, now);
+ isSleeping(), now);
if (DEBUG_PSS) Slog.d(TAG, "Process state change from "
+ ProcessList.makeProcStateString(app.setProcState) + " to "
+ ProcessList.makeProcStateString(app.curProcState) + " next pss in "
@@ -15244,7 +15655,7 @@ public final class ActivityManagerService extends ActivityManagerNative
&& now > (app.lastStateTime+ProcessList.PSS_MIN_TIME_FROM_STATE_CHANGE))) {
requestPssLocked(app, app.setProcState);
app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, false,
- mSleeping, now);
+ isSleeping(), now);
} else if (false && DEBUG_PSS) {
Slog.d(TAG, "Not requesting PSS of " + app + ": next=" + (app.nextPssTime-now));
}
@@ -15263,6 +15674,51 @@ public final class ActivityManagerService extends ActivityManagerNative
app.procStateChanged = true;
}
}
+
+ if (changes != 0) {
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Changes in " + app + ": " + changes);
+ int i = mPendingProcessChanges.size()-1;
+ ProcessChangeItem item = null;
+ while (i >= 0) {
+ item = mPendingProcessChanges.get(i);
+ if (item.pid == app.pid) {
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Re-using existing item: " + item);
+ break;
+ }
+ i--;
+ }
+ if (i < 0) {
+ // No existing item in pending changes; need a new one.
+ final int NA = mAvailProcessChanges.size();
+ if (NA > 0) {
+ item = mAvailProcessChanges.remove(NA-1);
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Retreiving available item: " + item);
+ } else {
+ item = new ProcessChangeItem();
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Allocating new item: " + item);
+ }
+ item.changes = 0;
+ item.pid = app.pid;
+ item.uid = app.info.uid;
+ if (mPendingProcessChanges.size() == 0) {
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG,
+ "*** Enqueueing dispatch processes changed!");
+ mHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED).sendToTarget();
+ }
+ mPendingProcessChanges.add(item);
+ }
+ item.changes |= changes;
+ item.processState = app.repProcState;
+ item.foregroundActivities = app.repForegroundActivities;
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Item "
+ + Integer.toHexString(System.identityHashCode(item))
+ + " " + app.toShortString() + ": changes=" + item.changes
+ + " procState=" + item.processState
+ + " foreground=" + item.foregroundActivities
+ + " type=" + app.adjType + " source=" + app.adjSource
+ + " target=" + app.adjTarget);
+ }
+
return success;
}
@@ -15273,7 +15729,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
- ProcessRecord TOP_APP, boolean doingAll, boolean reportingProcessState, long now) {
+ ProcessRecord TOP_APP, boolean doingAll, long now) {
if (app.thread == null) {
return false;
}
@@ -15282,19 +15738,72 @@ public final class ActivityManagerService extends ActivityManagerNative
computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);
- return applyOomAdjLocked(app, wasKeeping, TOP_APP, doingAll,
- reportingProcessState, now);
+ return applyOomAdjLocked(app, wasKeeping, TOP_APP, doingAll, now);
}
- private final ActivityRecord resumedAppLocked() {
- return mStackSupervisor.resumedAppLocked();
+ final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
+ boolean oomAdj) {
+ if (isForeground != proc.foregroundServices) {
+ proc.foregroundServices = isForeground;
+ ArrayList<ProcessRecord> curProcs = mForegroundPackages.get(proc.info.packageName,
+ proc.info.uid);
+ if (isForeground) {
+ if (curProcs == null) {
+ curProcs = new ArrayList<ProcessRecord>();
+ mForegroundPackages.put(proc.info.packageName, proc.info.uid, curProcs);
+ }
+ if (!curProcs.contains(proc)) {
+ curProcs.add(proc);
+ mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_FOREGROUND_START,
+ proc.info.packageName, proc.info.uid);
+ }
+ } else {
+ if (curProcs != null) {
+ if (curProcs.remove(proc)) {
+ mBatteryStatsService.noteEvent(
+ BatteryStats.HistoryItem.EVENT_FOREGROUND_FINISH,
+ proc.info.packageName, proc.info.uid);
+ if (curProcs.size() <= 0) {
+ mForegroundPackages.remove(proc.info.packageName, proc.info.uid);
+ }
+ }
+ }
+ }
+ if (oomAdj) {
+ updateOomAdjLocked();
+ }
+ }
}
- final boolean updateOomAdjLocked(ProcessRecord app) {
- return updateOomAdjLocked(app, false);
+ private final ActivityRecord resumedAppLocked() {
+ ActivityRecord act = mStackSupervisor.resumedAppLocked();
+ String pkg;
+ int uid;
+ if (act != null && !act.sleeping) {
+ pkg = act.packageName;
+ uid = act.info.applicationInfo.uid;
+ } else {
+ pkg = null;
+ uid = -1;
+ }
+ // Has the UID or resumed package name changed?
+ if (uid != mCurResumedUid || (pkg != mCurResumedPackage
+ && (pkg == null || !pkg.equals(mCurResumedPackage)))) {
+ if (mCurResumedPackage != null) {
+ mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_TOP_FINISH,
+ mCurResumedPackage, mCurResumedUid);
+ }
+ mCurResumedPackage = pkg;
+ mCurResumedUid = uid;
+ if (mCurResumedPackage != null) {
+ mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_TOP_START,
+ mCurResumedPackage, mCurResumedUid);
+ }
+ }
+ return act;
}
- final boolean updateOomAdjLocked(ProcessRecord app, boolean doingProcessState) {
+ final boolean updateOomAdjLocked(ProcessRecord app) {
final ActivityRecord TOP_ACT = resumedAppLocked();
final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;
final boolean wasCached = app.cached;
@@ -15307,7 +15816,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// need to do a complete oom adj.
final int cachedAdj = app.curRawAdj >= ProcessList.CACHED_APP_MIN_ADJ
? app.curRawAdj : ProcessList.UNKNOWN_ADJ;
- boolean success = updateOomAdjLocked(app, cachedAdj, TOP_APP, false, doingProcessState,
+ boolean success = updateOomAdjLocked(app, cachedAdj, TOP_APP, false,
SystemClock.uptimeMillis());
if (wasCached != app.cached || app.curRawAdj == ProcessList.UNKNOWN_ADJ) {
// Changed to/from cached state, so apps after it in the LRU
@@ -15440,7 +15949,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- applyOomAdjLocked(app, wasKeeping, TOP_APP, true, false, now);
+ applyOomAdjLocked(app, wasKeeping, TOP_APP, true, now);
// Count the number of process types.
switch (app.curProcState) {
@@ -15523,7 +16032,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
mLastMemoryLevel = memFactor;
mLastNumProcesses = mLruProcesses.size();
- boolean allChanged = mProcessStats.setMemFactorLocked(memFactor, !mSleeping, now);
+ boolean allChanged = mProcessStats.setMemFactorLocked(memFactor, !isSleeping(), now);
final int trackerMemFactor = mProcessStats.getMemFactorLocked();
if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) {
if (mLowRamStartTime == 0) {
@@ -15946,8 +16455,45 @@ public final class ActivityManagerService extends ActivityManagerNative
// Multi-user methods
+ /**
+ * Start user, if its not already running, but don't bring it to foreground.
+ */
+ @Override
+ public boolean startUserInBackground(final int userId) {
+ return startUser(userId, /* foreground */ false);
+ }
+
+ /**
+ * Refreshes the list of users related to the current user when either a
+ * user switch happens or when a new related user is started in the
+ * background.
+ */
+ private void updateCurrentProfileIdsLocked() {
+ final List<UserInfo> profiles = getUserManagerLocked().getProfiles(
+ mCurrentUserId, false /* enabledOnly */);
+ int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
+ for (int i = 0; i < currentProfileIds.length; i++) {
+ currentProfileIds[i] = profiles.get(i).id;
+ }
+ mCurrentProfileIds = currentProfileIds;
+ }
+
+ private Set getProfileIdsLocked(int userId) {
+ Set userIds = new HashSet<Integer>();
+ final List<UserInfo> profiles = getUserManagerLocked().getProfiles(
+ userId, false /* enabledOnly */);
+ for (UserInfo user : profiles) {
+ userIds.add(Integer.valueOf(user.id));
+ }
+ return userIds;
+ }
+
@Override
public boolean switchUser(final int userId) {
+ return startUser(userId, /* foregound */ true);
+ }
+
+ private boolean startUser(final int userId, boolean foreground) {
if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
!= PackageManager.PERMISSION_GRANTED) {
String msg = "Permission Denial: switchUser() from pid="
@@ -15958,6 +16504,8 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new SecurityException(msg);
}
+ if (DEBUG_MU) Slog.i(TAG_MU, "starting userid:" + userId + " fore:" + foreground);
+
final long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
@@ -15966,14 +16514,18 @@ public final class ActivityManagerService extends ActivityManagerNative
return true;
}
+ mStackSupervisor.setLockTaskModeLocked(null);
+
final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
if (userInfo == null) {
Slog.w(TAG, "No user info for user #" + userId);
return false;
}
- mWindowManager.startFreezingScreen(R.anim.screen_user_exit,
- R.anim.screen_user_enter);
+ if (foreground) {
+ mWindowManager.startFreezingScreen(R.anim.screen_user_exit,
+ R.anim.screen_user_enter);
+ }
boolean needStart = false;
@@ -15985,16 +16537,24 @@ public final class ActivityManagerService extends ActivityManagerNative
needStart = true;
}
- mCurrentUserId = userId;
final Integer userIdInt = Integer.valueOf(userId);
mUserLru.remove(userIdInt);
mUserLru.add(userIdInt);
- mWindowManager.setCurrentUser(userId);
-
- // Once the internal notion of the active user has switched, we lock the device
- // with the option to show the user switcher on the keyguard.
- mWindowManager.lockNow(null);
+ if (foreground) {
+ mCurrentUserId = userId;
+ updateCurrentProfileIdsLocked();
+ mWindowManager.setCurrentUser(userId, mCurrentProfileIds);
+ // Once the internal notion of the active user has switched, we lock the device
+ // with the option to show the user switcher on the keyguard.
+ mWindowManager.lockNow(null);
+ } else {
+ final Integer currentUserIdInt = Integer.valueOf(mCurrentUserId);
+ updateCurrentProfileIdsLocked();
+ mWindowManager.setCurrentProfileIds(mCurrentProfileIds);
+ mUserLru.remove(currentUserIdInt);
+ mUserLru.add(currentUserIdInt);
+ }
final UserStartedState uss = mStartedUsers.get(userId);
@@ -16015,12 +16575,23 @@ public final class ActivityManagerService extends ActivityManagerNative
needStart = true;
}
- mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
- mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
- oldUserId, userId, uss));
- mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
- oldUserId, userId, uss), USER_SWITCH_TIMEOUT);
+ if (uss.mState == UserStartedState.STATE_BOOTING) {
+ // Booting up a new user, need to tell system services about it.
+ // Note that this is on the same handler as scheduling of broadcasts,
+ // which is important because it needs to go first.
+ mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId));
+ }
+
+ if (foreground) {
+ mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId));
+ mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
+ mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+ mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
+ oldUserId, userId, uss));
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
+ oldUserId, userId, uss), USER_SWITCH_TIMEOUT);
+ }
+
if (needStart) {
Intent intent = new Intent(Intent.ACTION_USER_STARTED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
@@ -16032,7 +16603,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) {
- if (userId != 0) {
+ if (userId != UserHandle.USER_OWNER) {
Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntentLocked(null, null, intent, null,
@@ -16051,16 +16622,20 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- boolean homeInFront = mStackSupervisor.switchUserLocked(userId, uss);
- if (homeInFront) {
- startHomeActivityLocked(userId);
+ if (foreground) {
+ boolean homeInFront = mStackSupervisor.switchUserLocked(userId, uss);
+ if (homeInFront) {
+ startHomeActivityLocked(userId);
+ } else {
+ mStackSupervisor.resumeTopActivitiesLocked();
+ }
+ EventLogTags.writeAmSwitchUser(userId);
+ getUserManagerLocked().userForeground(userId);
+ sendUserSwitchBroadcastsLocked(oldUserId, userId);
} else {
- mStackSupervisor.resumeTopActivitiesLocked();
+ mStackSupervisor.startBackgroundUserLocked(userId, uss);
}
- EventLogTags.writeAmSwitchUser(userId);
- getUserManagerLocked().userForeground(userId);
- sendUserSwitchBroadcastsLocked(oldUserId, userId);
if (needStart) {
Intent intent = new Intent(Intent.ACTION_USER_STARTING);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -16206,7 +16781,35 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- void finishUserSwitch(UserStartedState uss) {
+ void scheduleStartProfilesLocked() {
+ if (!mHandler.hasMessages(START_PROFILES_MSG)) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
+ DateUtils.SECOND_IN_MILLIS);
+ }
+ }
+
+ void startProfilesLocked() {
+ if (DEBUG_MU) Slog.i(TAG_MU, "startProfilesLocked");
+ List<UserInfo> profiles = getUserManagerLocked().getProfiles(
+ mCurrentUserId, false /* enabledOnly */);
+ List<UserInfo> toStart = new ArrayList<UserInfo>(profiles.size());
+ for (UserInfo user : profiles) {
+ if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
+ && user.id != mCurrentUserId) {
+ toStart.add(user);
+ }
+ }
+ final int n = toStart.size();
+ int i = 0;
+ for (; i < n && i < (MAX_RUNNING_USERS - 1); ++i) {
+ startUserInBackground(toStart.get(i).id);
+ }
+ if (i < n) {
+ Slog.w(TAG_MU, "More profiles than MAX_RUNNING_USERS");
+ }
+ }
+
+ void finishUserBoot(UserStartedState uss) {
synchronized (this) {
if (uss.mState == UserStartedState.STATE_BOOTING
&& mStartedUsers.get(uss.mHandle.getIdentifier()) == uss) {
@@ -16220,6 +16823,15 @@ public final class ActivityManagerService extends ActivityManagerNative
android.Manifest.permission.RECEIVE_BOOT_COMPLETED, AppOpsManager.OP_NONE,
true, false, MY_PID, Process.SYSTEM_UID, userId);
}
+ }
+ }
+
+ void finishUserSwitch(UserStartedState uss) {
+ synchronized (this) {
+ finishUserBoot(uss);
+
+ startProfilesLocked();
+
int num = mUserLru.size();
int i = 0;
while (num > MAX_RUNNING_USERS && i < mUserLru.size()) {
@@ -16271,6 +16883,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
private int stopUserLocked(final int userId, final IStopUserCallback callback) {
+ if (DEBUG_MU) Slog.i(TAG_MU, "stopUserLocked userId=" + userId);
if (mCurrentUserId == userId) {
return ActivityManager.USER_OP_IS_CURRENT;
}
@@ -16333,6 +16946,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
uss.mState = UserStartedState.STATE_SHUTDOWN;
}
+ mSystemServiceManager.stopUser(userId);
broadcastIntentLocked(null, null, shutdownIntent,
null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
true, false, MY_PID, Process.SYSTEM_UID, userId);
@@ -16382,7 +16996,12 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- mStackSupervisor.removeUserLocked(userId);
+ if (stopped) {
+ mSystemServiceManager.cleanupUser(userId);
+ synchronized (this) {
+ mStackSupervisor.removeUserLocked(userId);
+ }
+ }
}
@Override
@@ -16549,4 +17168,69 @@ public final class ActivityManagerService extends ActivityManagerNative
ActivityManagerService.this.wakingUp();
}
}
+
+ /**
+ * An implementation of IAppTask, that allows an app to manage its own tasks via
+ * {@link android.app.ActivityManager#AppTask}. We keep track of the callingUid to ensure that
+ * only the process that calls getAppTasks() can call the AppTask methods.
+ */
+ class AppTaskImpl extends IAppTask.Stub {
+ private int mTaskId;
+ private int mCallingUid;
+
+ public AppTaskImpl(int taskId, int callingUid) {
+ mTaskId = taskId;
+ mCallingUid = callingUid;
+ }
+
+ @Override
+ public void finishAndRemoveTask() {
+ // Ensure that we are called from the same process that created this AppTask
+ if (mCallingUid != Binder.getCallingUid()) {
+ Slog.w(TAG, "finishAndRemoveTask: caller " + mCallingUid
+ + " does not match caller of getAppTasks(): " + Binder.getCallingUid());
+ return;
+ }
+
+ synchronized (ActivityManagerService.this) {
+ long origId = Binder.clearCallingIdentity();
+ try {
+ TaskRecord tr = recentTaskForIdLocked(mTaskId);
+ if (tr != null) {
+ // Only kill the process if we are not a new document
+ int flags = tr.getBaseIntent().getFlags();
+ boolean isDocument = (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) ==
+ Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+ removeTaskByIdLocked(mTaskId,
+ !isDocument ? ActivityManager.REMOVE_TASK_KILL_PROCESS : 0);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+
+ @Override
+ public ActivityManager.RecentTaskInfo getTaskInfo() {
+ // Ensure that we are called from the same process that created this AppTask
+ if (mCallingUid != Binder.getCallingUid()) {
+ Slog.w(TAG, "finishAndRemoveTask: caller " + mCallingUid
+ + " does not match caller of getAppTasks(): " + Binder.getCallingUid());
+ return null;
+ }
+
+ synchronized (ActivityManagerService.this) {
+ long origId = Binder.clearCallingIdentity();
+ try {
+ TaskRecord tr = recentTaskForIdLocked(mTaskId);
+ if (tr != null) {
+ return createRecentTaskInfoFromTaskRecord(tr);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ return null;
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 37ead27..dbe2ca1 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -16,13 +16,14 @@
package com.android.server.am;
+import android.os.PersistableBundle;
import android.os.Trace;
-import com.android.internal.R.styleable;
import com.android.internal.app.ResolverActivity;
import com.android.server.AttributeCache;
import com.android.server.am.ActivityStack.ActivityState;
import com.android.server.am.ActivityStackSupervisor.ActivityContainer;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ResultInfo;
import android.content.ComponentName;
@@ -117,6 +118,7 @@ final class ActivityRecord {
ProcessRecord app; // if non-null, hosting application
ActivityState state; // current state we are in
Bundle icicle; // last saved activity state
+ PersistableBundle persistentState; // last persistently saved activity state
boolean frontOfTask; // is this the root activity of its task?
boolean launchFailed; // set if a launched failed, to abort on 2nd try
boolean haveState; // have we gotten the last activity state?
@@ -131,7 +133,6 @@ final class ActivityRecord {
boolean sleeping; // have we told the activity to sleep?
boolean waitingVisible; // true if waiting for a new act to become vis
boolean nowVisible; // is this activity's window visible?
- boolean thumbnailNeeded;// has someone requested a thumbnail?
boolean idle; // has the activity gone idle?
boolean hasBeenLaunched;// has this activity ever been launched?
boolean frozenBeforeDestroy;// has been frozen but not yet destroyed.
@@ -145,8 +146,11 @@ final class ActivityRecord {
private boolean inHistory; // are we in the history stack?
final ActivityStackSupervisor mStackSupervisor;
+ boolean mStartingWindowShown = false;
ActivityContainer mInitialActivityContainer;
+ ActivityManager.TaskDescription taskDescription; // the recents information for this activity
+
void dump(PrintWriter pw, String prefix) {
final long now = SystemClock.uptimeMillis();
pw.print(prefix); pw.print("packageName="); pw.print(packageName);
@@ -211,14 +215,7 @@ final class ActivityRecord {
pw.print(prefix); pw.print("pendingOptions="); pw.println(pendingOptions);
}
if (uriPermissions != null) {
- if (uriPermissions.readUriPermissions != null) {
- pw.print(prefix); pw.print("readUriPermissions=");
- pw.println(uriPermissions.readUriPermissions);
- }
- if (uriPermissions.writeUriPermissions != null) {
- pw.print(prefix); pw.print("writeUriPermissions=");
- pw.println(uriPermissions.writeUriPermissions);
- }
+ uriPermissions.dump(pw, prefix);
}
pw.print(prefix); pw.print("launchFailed="); pw.print(launchFailed);
pw.print(" launchCount="); pw.print(launchCount);
@@ -242,7 +239,6 @@ final class ActivityRecord {
pw.print(" immersive="); pw.print(immersive);
pw.print(" launchMode="); pw.println(launchMode);
pw.print(prefix); pw.print("frozenBeforeDestroy="); pw.print(frozenBeforeDestroy);
- pw.print(" thumbnailNeeded="); pw.print(thumbnailNeeded);
pw.print(" forceNewConfig="); pw.println(forceNewConfig);
pw.print(prefix); pw.print("mActivityType=");
pw.println(activityTypeToString(mActivityType));
@@ -351,7 +347,7 @@ final class ActivityRecord {
ActivityInfo aInfo, Configuration _configuration,
ActivityRecord _resultTo, String _resultWho, int _reqCode,
boolean _componentSpecified, ActivityStackSupervisor supervisor,
- ActivityContainer container) {
+ ActivityContainer container, Bundle options) {
service = _service;
appToken = new Token(this);
info = aInfo;
@@ -378,11 +374,13 @@ final class ActivityRecord {
visible = true;
waitingVisible = false;
nowVisible = false;
- thumbnailNeeded = false;
idle = false;
hasBeenLaunched = false;
mStackSupervisor = supervisor;
mInitialActivityContainer = container;
+ if (options != null) {
+ pendingOptions = new ActivityOptions(options);
+ }
// This starts out true, since the initial state of an activity
// is that we have everything, and we shouldn't never consider it
@@ -518,13 +516,6 @@ final class ActivityRecord {
if (fullscreen == toOpaque) {
return false;
}
- AttributeCache.Entry ent =
- AttributeCache.instance().get(packageName, realTheme, styleable.Window, userId);
- if (ent == null
- || !ent.array.getBoolean(styleable.Window_windowIsTranslucent, false)
- || ent.array.getBoolean(styleable.Window_windowIsFloating, false)) {
- return false;
- }
// Keep track of the number of fullscreen activities in this task.
task.numFullscreen += toOpaque ? +1 : -1;
@@ -597,7 +588,7 @@ final class ActivityRecord {
int requestCode, int resultCode,
Intent resultData) {
ActivityResult r = new ActivityResult(from, resultWho,
- requestCode, resultCode, resultData);
+ requestCode, resultCode, resultData);
if (results == null) {
results = new ArrayList<ResultInfo>();
}
@@ -643,7 +634,7 @@ final class ActivityRecord {
// case we will deliver it if this is the current top activity on its
// stack.
boolean unsent = true;
- if ((state == ActivityState.RESUMED || (service.mSleeping
+ if ((state == ActivityState.RESUMED || (service.isSleeping()
&& task.stack.topRunningActivityLocked(null) == this))
&& app != null && app.thread != null) {
try {
@@ -684,7 +675,8 @@ final class ActivityRecord {
}
void applyOptionsLocked() {
- if (pendingOptions != null) {
+ if (pendingOptions != null
+ && pendingOptions.getAnimationType() != ActivityOptions.ANIM_SCENE_TRANSITION) {
final int animationType = pendingOptions.getAnimationType();
switch (animationType) {
case ActivityOptions.ANIM_CUSTOM:
@@ -722,11 +714,18 @@ final class ActivityRecord {
+ pendingOptions.getThumbnail().getHeight()));
}
break;
+ default:
+ Slog.e(TAG, "applyOptionsLocked: Unknown animationType=" + animationType);
+ break;
}
pendingOptions = null;
}
}
+ ActivityOptions getOptionsForTargetActivityLocked() {
+ return pendingOptions != null ? pendingOptions.forTargetActivity() : null;
+ }
+
void clearOptionsLocked() {
if (pendingOptions != null) {
pendingOptions.abort();
diff --git a/services/core/java/com/android/server/am/ActivityResult.java b/services/core/java/com/android/server/am/ActivityResult.java
index 6d5bdeb..395918e 100644
--- a/services/core/java/com/android/server/am/ActivityResult.java
+++ b/services/core/java/com/android/server/am/ActivityResult.java
@@ -18,7 +18,6 @@ package com.android.server.am;
import android.app.ResultInfo;
import android.content.Intent;
-import android.os.Bundle;
/**
* Pending result information to send back to an activity.
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index c6cd312..33e59a7 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -36,8 +36,8 @@ import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE;
import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES;
import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
-import static com.android.server.am.ActivityStackSupervisor.ActivityContainer.CONTAINER_STATE_HAS_SURFACE;
-
+import android.service.voice.IVoiceInteractionSession;
+import com.android.internal.app.IVoiceInteractor;
import com.android.internal.os.BatteryStatsImpl;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService.ItemMatcher;
@@ -51,7 +51,6 @@ import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IActivityController;
-import android.app.IThumbnailReceiver;
import android.app.ResultInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.ComponentName;
@@ -69,6 +68,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -123,8 +123,7 @@ final class ActivityStack {
// convertToTranslucent().
static final long TRANSLUCENT_CONVERSION_TIMEOUT = 2000;
- static final boolean SCREENSHOT_FORCE_565 = ActivityManager
- .isLowRamDeviceStatic() ? true : false;
+ static final boolean SCREENSHOT_FORCE_565 = ActivityManager.isLowRamDeviceStatic();
enum ActivityState {
INITIALIZING,
@@ -206,6 +205,9 @@ final class ActivityStack {
ActivityRecord mTranslucentActivityWaiting = null;
ArrayList<ActivityRecord> mUndrawnActivitiesBelowTopTranslucent =
new ArrayList<ActivityRecord>();
+ // Options passed from the caller of the convertToTranslucent to the activity that will
+ // appear below it.
+ ActivityOptions mReturningActivityOptions = null;
/**
* Set when we know we are going to be calling updateConfiguration()
@@ -278,7 +280,7 @@ final class ActivityStack {
if (r.app != null) {
mService.logAppTooSlow(r.app, r.pauseTime, "pausing " + r);
}
- activityPausedLocked(r.appToken, true);
+ activityPausedLocked(r.appToken, true, r.persistentState);
}
} break;
case LAUNCH_TICK_MSG: {
@@ -342,8 +344,19 @@ final class ActivityStack {
mCurrentUser = mService.mCurrentUserId;
}
- boolean okToShow(ActivityRecord r) {
- return r.userId == mCurrentUser
+ /**
+ * Checks whether the userid is a profile of the current user.
+ */
+ private boolean isCurrentProfileLocked(int userId) {
+ if (userId == mCurrentUser) return true;
+ for (int i = 0; i < mService.mCurrentProfileIds.length; i++) {
+ if (mService.mCurrentProfileIds[i] == userId) return true;
+ }
+ return false;
+ }
+
+ boolean okToShowLocked(ActivityRecord r) {
+ return isCurrentProfileLocked(r.userId)
|| (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0;
}
@@ -363,7 +376,7 @@ final class ActivityStack {
final ArrayList<ActivityRecord> activities = task.mActivities;
for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
ActivityRecord r = activities.get(activityNdx);
- if (!r.finishing && !r.delayedResume && r != notTop && okToShow(r)) {
+ if (!r.finishing && !r.delayedResume && r != notTop && okToShowLocked(r)) {
return r;
}
}
@@ -390,7 +403,7 @@ final class ActivityStack {
for (int i = activities.size() - 1; i >= 0; --i) {
final ActivityRecord r = activities.get(i);
// Note: the taskId check depends on real taskId fields being non-zero
- if (!r.finishing && (token != r.appToken) && okToShow(r)) {
+ if (!r.finishing && (token != r.appToken) && okToShowLocked(r)) {
return r;
}
}
@@ -403,8 +416,9 @@ final class ActivityStack {
// be simplified once we stop storing tasks with empty mActivities lists.
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- return activities.get(activityNdx);
+ final int topActivityNdx = activities.size() - 1;
+ if (topActivityNdx >= 0) {
+ return activities.get(topActivityNdx);
}
}
return null;
@@ -482,10 +496,18 @@ final class ActivityStack {
cls = new ComponentName(info.packageName, info.targetActivity);
}
final int userId = UserHandle.getUserId(info.applicationInfo.uid);
+ boolean isDocument = intent != null & intent.isDocument();
+ // If documentData is non-null then it must match the existing task data.
+ Uri documentData = isDocument ? intent.getData() : null;
if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + target + " in " + this);
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
+ if (task.voiceSession != null) {
+ // We never match voice sessions; those always run independently.
+ if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": voice session");
+ continue;
+ }
if (task.userId != userId) {
// Looking for a different task.
if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": different user");
@@ -498,23 +520,39 @@ final class ActivityStack {
continue;
}
+ final Intent taskIntent = task.intent;
+ final Intent affinityIntent = task.affinityIntent;
+ final boolean taskIsDocument;
+ final Uri taskDocumentData;
+ if (taskIntent != null && taskIntent.isDocument()) {
+ taskIsDocument = true;
+ taskDocumentData = taskIntent.getData();
+ } else if (affinityIntent != null && affinityIntent.isDocument()) {
+ taskIsDocument = true;
+ taskDocumentData = affinityIntent.getData();
+ } else {
+ taskIsDocument = false;
+ taskDocumentData = null;
+ }
+
if (DEBUG_TASKS) Slog.d(TAG, "Comparing existing cls="
- + r.task.intent.getComponent().flattenToShortString()
+ + taskIntent.getComponent().flattenToShortString()
+ "/aff=" + r.task.affinity + " to new cls="
+ intent.getComponent().flattenToShortString() + "/aff=" + info.taskAffinity);
- if (task.affinity != null) {
- if (task.affinity.equals(info.taskAffinity)) {
+ if (!isDocument && !taskIsDocument && task.affinity != null) {
+ if (task.affinity.equals(target.taskAffinity)) {
if (DEBUG_TASKS) Slog.d(TAG, "Found matching affinity!");
return r;
}
- } else if (task.intent != null && task.intent.getComponent().equals(cls)) {
+ } else if (taskIntent != null && taskIntent.getComponent().equals(cls) &&
+ Objects.equals(documentData, taskDocumentData)) {
if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!");
//dump();
if (DEBUG_TASKS) Slog.d(TAG, "For Intent " + intent + " bringing to top: "
+ r.intent);
return r;
- } else if (task.affinityIntent != null
- && task.affinityIntent.getComponent().equals(cls)) {
+ } else if (affinityIntent != null && affinityIntent.getComponent().equals(cls) &&
+ Objects.equals(documentData, taskDocumentData)) {
if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!");
//dump();
if (DEBUG_TASKS) Slog.d(TAG, "For Intent " + intent + " bringing to top: "
@@ -542,7 +580,7 @@ final class ActivityStack {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
TaskRecord task = mTaskHistory.get(taskNdx);
- if (task.userId != mCurrentUser) {
+ if (!isCurrentProfileLocked(task.userId)) {
return null;
}
final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -573,7 +611,7 @@ final class ActivityStack {
int index = mTaskHistory.size();
for (int i = 0; i < index; ) {
TaskRecord task = mTaskHistory.get(i);
- if (task.userId == userId) {
+ if (isCurrentProfileLocked(task.userId)) {
if (DEBUG_TASKS) Slog.d(TAG, "switchUserLocked: stack=" + getStackId() +
" moving " + task + " to top");
mTaskHistory.remove(i);
@@ -705,9 +743,9 @@ final class ActivityStack {
int h = mThumbnailHeight;
if (w < 0) {
mThumbnailWidth = w =
- res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
+ res.getDimensionPixelSize(com.android.internal.R.dimen.recents_thumbnail_width);
mThumbnailHeight = h =
- res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
+ res.getDimensionPixelSize(com.android.internal.R.dimen.recents_thumbnail_height);
}
if (w > 0) {
@@ -755,7 +793,7 @@ final class ActivityStack {
prev.task.touchActiveTime();
clearLaunchTime(prev);
final ActivityRecord next = mStackSupervisor.topRunningActivityLocked();
- if (next == null || next.task != prev.task) {
+ if (next == null || next.noDisplay || next.task != prev.task) {
prev.updateThumbnail(screenshotActivities(prev), null);
}
stopFullyDrawnTraceIfNeeded();
@@ -817,13 +855,15 @@ final class ActivityStack {
}
}
- final void activityPausedLocked(IBinder token, boolean timeout) {
+ final void activityPausedLocked(IBinder token, boolean timeout,
+ PersistableBundle persistentState) {
if (DEBUG_PAUSE) Slog.v(
TAG, "Activity paused: token=" + token + ", timeout=" + timeout);
final ActivityRecord r = isInStackLocked(token);
if (r != null) {
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
+ r.persistentState = persistentState;
if (mPausingActivity == r) {
if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSED: " + r
+ (timeout ? " (due to timeout)" : " (pause complete)"));
@@ -838,13 +878,14 @@ final class ActivityStack {
}
}
- final void activityStoppedLocked(ActivityRecord r, Bundle icicle, Bitmap thumbnail,
- CharSequence description) {
+ final void activityStoppedLocked(ActivityRecord r, Bundle icicle,
+ PersistableBundle persistentState, CharSequence description) {
if (r.state != ActivityState.STOPPING) {
Slog.i(TAG, "Activity reported stop, but no longer stopping: " + r);
mHandler.removeMessages(STOP_TIMEOUT_MSG, r);
return;
}
+ r.persistentState = persistentState;
if (DEBUG_SAVED_STATE) Slog.i(TAG, "Saving icicle of " + r + ": " + icicle);
if (icicle != null) {
// If icicle is null, this is happening due to a timeout, so we
@@ -852,7 +893,7 @@ final class ActivityStack {
r.icicle = icicle;
r.haveState = true;
r.launchCount = 0;
- r.updateThumbnail(thumbnail, description);
+ r.updateThumbnail(null, description);
}
if (!r.stopped) {
if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r + " (stop complete)");
@@ -1037,26 +1078,59 @@ final class ActivityStack {
}
}
- /**
- * Version of ensureActivitiesVisible that can easily be called anywhere.
- */
- final boolean ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) {
- return ensureActivitiesVisibleLocked(starting, configChanges, false);
+ // Checks if any of the stacks above this one has a fullscreen activity behind it.
+ // If so, this stack is hidden, otherwise it is visible.
+ private boolean isStackVisible() {
+ if (!isAttached()) {
+ return false;
+ }
+
+ if (mStackSupervisor.isFrontStack(this)) {
+ return true;
+ }
+
+ /**
+ * Start at the task above this one and go up, looking for a visible
+ * fullscreen activity, or a translucent activity that requested the
+ * wallpaper to be shown behind it.
+ */
+ for (int i = mStacks.indexOf(this) + 1; i < mStacks.size(); i++) {
+ final ArrayList<TaskRecord> tasks = mStacks.get(i).getAllTasks();
+ for (int taskNdx = 0; taskNdx < tasks.size(); taskNdx++) {
+ final ArrayList<ActivityRecord> activities = tasks.get(taskNdx).mActivities;
+ for (int activityNdx = 0; activityNdx < activities.size(); activityNdx++) {
+ final ActivityRecord r = activities.get(activityNdx);
+
+ // Conditions for an activity to obscure the stack we're
+ // examining:
+ // 1. Not Finishing AND Visible AND:
+ // 2. Either:
+ // - Full Screen Activity OR
+ // - On top of Home and our stack is NOT home
+ if (!r.finishing && r.visible && (r.fullscreen ||
+ (!isHomeStack() && r.frontOfTask && tasks.get(taskNdx).mOnTopOfHome))) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
}
- final boolean ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges,
- boolean forceHomeShown) {
+ final void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) {
ActivityRecord r = topRunningActivityLocked(null);
- return r != null &&
- ensureActivitiesVisibleLocked(r, starting, null, configChanges, forceHomeShown);
+ if (r != null) {
+ ensureActivitiesVisibleLocked(r, starting, null, configChanges);
+ }
}
/**
* Make sure that all activities that need to be visible (that is, they
* currently can be seen by the user) actually are.
*/
- final boolean ensureActivitiesVisibleLocked(ActivityRecord top, ActivityRecord starting,
- String onlyThisProcess, int configChanges, boolean forceHomeShown) {
+ final void ensureActivitiesVisibleLocked(ActivityRecord top, ActivityRecord starting,
+ String onlyThisProcess, int configChanges) {
if (DEBUG_VISBILITY) Slog.v(
TAG, "ensureActivitiesVisible behind " + top
+ " configChanges=0x" + Integer.toHexString(configChanges));
@@ -1074,9 +1148,8 @@ final class ActivityStack {
// If the top activity is not fullscreen, then we need to
// make sure any activities under it are now visible.
boolean aboveTop = true;
- boolean showHomeBehindStack = false;
- boolean behindFullscreen = !mStackSupervisor.isFrontStack(this) &&
- !(forceHomeShown && isHomeStack());
+ boolean behindFullscreen = !isStackVisible();
+
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -1139,6 +1212,7 @@ final class ActivityStack {
TAG, "Making visible and scheduling visibility: " + r);
try {
if (mTranslucentActivityWaiting != null) {
+ r.updateOptionsLocked(mReturningActivityOptions);
mUndrawnActivitiesBelowTopTranslucent.add(r);
}
setVisibile(r, true);
@@ -1162,11 +1236,9 @@ final class ActivityStack {
// At this point, nothing else needs to be shown
if (DEBUG_VISBILITY) Slog.v(TAG, "Fullscreen: at " + r);
behindFullscreen = true;
- showHomeBehindStack = false;
- } else if (isActivityOverHome(r)) {
+ } else if (!isHomeStack() && r.frontOfTask && task.mOnTopOfHome) {
if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r);
- showHomeBehindStack = true;
- behindFullscreen = !isHomeStack() && r.frontOfTask && task.mOnTopOfHome;
+ behindFullscreen = true;
}
} else {
if (DEBUG_VISBILITY) Slog.v(
@@ -1216,12 +1288,12 @@ final class ActivityStack {
}
}
}
- return showHomeBehindStack;
}
- void convertToTranslucent(ActivityRecord r) {
+ void convertToTranslucent(ActivityRecord r, ActivityOptions options) {
mTranslucentActivityWaiting = r;
mUndrawnActivitiesBelowTopTranslucent.clear();
+ mReturningActivityOptions = options;
mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT);
}
@@ -1258,6 +1330,32 @@ final class ActivityStack {
}
}
+ /** If any activities below the top running one are in the INITIALIZING state and they have a
+ * starting window displayed then remove that starting window. It is possible that the activity
+ * in this state will never resumed in which case that starting window will be orphaned. */
+ void cancelInitializingActivities() {
+ final ActivityRecord topActivity = topRunningActivityLocked(null);
+ boolean aboveTop = true;
+ for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+ final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
+ for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = activities.get(activityNdx);
+ if (aboveTop) {
+ if (r == topActivity) {
+ aboveTop = false;
+ }
+ continue;
+ }
+
+ if (r.state == ActivityState.INITIALIZING && r.mStartingWindowShown) {
+ if (DEBUG_VISBILITY) Slog.w(TAG, "Found orphaned starting window " + r);
+ r.mStartingWindowShown = false;
+ mWindowManager.removeAppStartingWindow(r.appToken);
+ }
+ }
+ }
+ }
+
/**
* Ensure that the top activity in the stack is resumed.
*
@@ -1282,6 +1380,8 @@ final class ActivityStack {
return false;
}
+ cancelInitializingActivities();
+
// Find the first activity that is not finishing.
ActivityRecord next = topRunningActivityLocked(null);
@@ -1317,7 +1417,8 @@ final class ActivityStack {
final TaskRecord nextTask = next.task;
final TaskRecord prevTask = prev != null ? prev.task : null;
- if (prevTask != null && prevTask.mOnTopOfHome && prev.finishing && prev.frontOfTask) {
+ if (prevTask != null && prevTask.stack == this &&
+ prevTask.mOnTopOfHome && prev.finishing && prev.frontOfTask) {
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
if (prevTask == nextTask) {
prevTask.setFrontOfTask();
@@ -1366,8 +1467,6 @@ final class ActivityStack {
next.sleeping = false;
mStackSupervisor.mWaitingVisibleActivities.remove(next);
- next.updateOptionsLocked(options);
-
if (DEBUG_SWITCH) Slog.v(TAG, "Resuming " + next);
// If we are currently pausing an activity, then don't do anything
@@ -1435,7 +1534,7 @@ final class ActivityStack {
// If the most recent activity was noHistory but was only stopped rather
// than stopped+finished because the device went to sleep, we need to make
// sure to finish it as we're making a new activity topmost.
- if (mService.mSleeping && mLastNoHistoryActivity != null &&
+ if (mService.isSleeping() && mLastNoHistoryActivity != null &&
!mLastNoHistoryActivity.finishing) {
if (DEBUG_STATES) Slog.d(TAG, "no-history finish of " + mLastNoHistoryActivity +
" on new resume");
@@ -1527,7 +1626,13 @@ final class ActivityStack {
mWindowManager.prepareAppTransition(AppTransition.TRANSIT_ACTIVITY_OPEN, false);
}
}
+
+ Bundle resumeAnimOptions = null;
if (anim) {
+ ActivityOptions opts = next.getOptionsForTargetActivityLocked();
+ if (opts != null) {
+ resumeAnimOptions = opts.toBundle();
+ }
next.applyOptionsLocked();
} else {
next.clearOptionsLocked();
@@ -1619,8 +1724,9 @@ final class ActivityStack {
mService.showAskCompatModeDialogLocked(next);
next.app.pendingUiClean = true;
next.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
+ next.clearOptionsLocked();
next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
- mService.isNextTransitionForward());
+ mService.isNextTransitionForward(), resumeAnimOptions);
mStackSupervisor.checkReadyForSleepLocked();
@@ -1705,10 +1811,10 @@ final class ActivityStack {
mTaskHistory.remove(task);
// Now put task at top.
int stackNdx = mTaskHistory.size();
- if (task.userId != mCurrentUser) {
+ if (!isCurrentProfileLocked(task.userId)) {
// Put non-current user tasks below current user tasks.
while (--stackNdx >= 0) {
- if (mTaskHistory.get(stackNdx).userId != mCurrentUser) {
+ if (!isCurrentProfileLocked(mTaskHistory.get(stackNdx).userId)) {
break;
}
}
@@ -1804,7 +1910,6 @@ final class ActivityStack {
: AppTransition.TRANSIT_ACTIVITY_OPEN, keepCurTransition);
mNoAnimActivities.remove(r);
}
- r.updateOptionsLocked(options);
mWindowManager.addAppToken(task.mActivities.indexOf(r),
r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
(r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
@@ -1817,7 +1922,7 @@ final class ActivityStack {
// If the caller has requested that the target task be
// reset, then do so.
if ((r.intent.getFlags()
- &Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
+ & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
resetTaskIfNeededLocked(r, r);
doShow = topRunningNonDelayedActivityLocked(null) == r;
}
@@ -1845,6 +1950,7 @@ final class ActivityStack {
r.info.applicationInfo), r.nonLocalizedLabel,
r.labelRes, r.icon, r.logo, r.windowFlags,
prev != null ? prev.appToken : null, showStartingIcon);
+ r.mStartingWindowShown = true;
}
} else {
// If this is the first activity, don't do any fancy animations,
@@ -1854,13 +1960,14 @@ final class ActivityStack {
(r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
r.info.configChanges);
ActivityOptions.abort(options);
+ options = null;
}
if (VALIDATE_TOKENS) {
validateAppTokensLocked();
}
if (doResume) {
- mStackSupervisor.resumeTopActivitiesLocked();
+ mStackSupervisor.resumeTopActivitiesLocked(this, r, options);
}
}
@@ -1958,21 +2065,14 @@ final class ActivityStack {
+ " out to bottom task " + bottom.task);
} else {
targetTask = createTaskRecord(mStackSupervisor.getNextTaskId(), target.info,
- null, false);
+ null, null, null, false);
newThumbHolder = targetTask;
targetTask.affinityIntent = target.intent;
if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
+ " out to new task " + target.task);
}
- if (clearWhenTaskReset) {
- // This is the start of a new sub-task.
- if (target.thumbHolder == null) {
- target.thumbHolder = new ThumbnailHolder();
- }
- } else {
- target.thumbHolder = newThumbHolder;
- }
+ target.thumbHolder = newThumbHolder;
final int targetTaskId = targetTask.taskId;
mWindowManager.setAppGroupId(target.appToken, targetTaskId);
@@ -2253,7 +2353,10 @@ final class ActivityStack {
mStackSupervisor.moveHomeToTop();
}
}
- mService.setFocusedActivityLocked(mStackSupervisor.topRunningActivityLocked());
+ ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
+ if (top != null) {
+ mService.setFocusedActivityLocked(top);
+ }
}
}
@@ -2262,7 +2365,7 @@ final class ActivityStack {
if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
|| (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) {
if (!r.finishing) {
- if (!mService.mSleeping) {
+ if (!mService.isSleeping()) {
if (DEBUG_STATES) {
Slog.d(TAG, "no-history finish of " + r);
}
@@ -2435,14 +2538,15 @@ final class ActivityStack {
}
r.makeFinishing();
+ final TaskRecord task = r.task;
EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY,
r.userId, System.identityHashCode(r),
- r.task.taskId, r.shortComponentName, reason);
- final ArrayList<ActivityRecord> activities = r.task.mActivities;
+ task.taskId, r.shortComponentName, reason);
+ final ArrayList<ActivityRecord> activities = task.mActivities;
final int index = activities.indexOf(r);
if (index < (activities.size() - 1)) {
- r.task.setFrontOfTask();
- if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
+ task.setFrontOfTask();
+ if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
// If the caller asked that this activity (and all above it)
// be cleared when the task is reset, don't lose that information,
// but propagate it up to the next activity.
@@ -2457,13 +2561,6 @@ final class ActivityStack {
finishActivityResultsLocked(r, resultCode, resultData);
- if (!mService.mPendingThumbnails.isEmpty()) {
- // There are clients waiting to receive thumbnails so, in case
- // this is an activity that someone is waiting for, add it
- // to the pending list so we can correctly update the clients.
- mStackSupervisor.mCancelledThumbnails.add(r);
- }
-
if (mResumedActivity == r) {
boolean endTask = index <= 0;
if (DEBUG_VISBILITY || DEBUG_TRANSITION) Slog.v(TAG,
@@ -2481,6 +2578,9 @@ final class ActivityStack {
startPausingLocked(false, false);
}
+ if (endTask) {
+ mStackSupervisor.endLockTaskModeIfTaskEnding(task);
+ }
} else if (r.state != ActivityState.PAUSING) {
// If the activity is PAUSING, we will complete the finish once
// it is done pausing; else we can just directly finish it here.
@@ -2637,7 +2737,7 @@ final class ActivityStack {
ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
destIntent.getComponent(), 0, srec.userId);
int res = mStackSupervisor.startActivityLocked(srec.app.thread, destIntent,
- null, aInfo, parent.appToken, null,
+ null, aInfo, null, null, parent.appToken, null,
0, -1, parent.launchedFromUid, parent.launchedFromPackage,
0, null, true, null, null);
foundParentInTask = res == ActivityManager.START_SUCCESS;
@@ -2666,9 +2766,7 @@ final class ActivityStack {
if (mPausingActivity == r) {
mPausingActivity = null;
}
- if (mService.mFocusedActivity == r) {
- mService.mFocusedActivity = null;
- }
+ mService.clearFocusedActivity(r);
r.configDestroy = false;
r.frozenBeforeDestroy = false;
@@ -2701,13 +2799,6 @@ final class ActivityStack {
cleanUpActivityServicesLocked(r);
}
- if (!mService.mPendingThumbnails.isEmpty()) {
- // There are clients waiting to receive thumbnails so, in case
- // this is an activity that someone is waiting for, add it
- // to the pending list so we can correctly update the clients.
- mStackSupervisor.mCancelledThumbnails.add(r);
- }
-
// Get rid of any pending idle timeouts.
removeTimeoutsForActivityLocked(r);
}
@@ -3061,23 +3152,6 @@ final class ActivityStack {
}
}
- final boolean findTaskToMoveToFrontLocked(int taskId, int flags, Bundle options) {
- final TaskRecord task = taskForIdLocked(taskId);
- if (task != null) {
- if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) {
- mStackSupervisor.mUserLeaving = true;
- }
- if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
- // Caller wants the home activity moved with it. To accomplish this,
- // we'll just indicate that this task returns to the home task.
- task.mOnTopOfHome = true;
- }
- moveTaskToFrontLocked(task, null, options);
- return true;
- }
- return false;
- }
-
final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason, Bundle options) {
if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr);
@@ -3135,7 +3209,15 @@ final class ActivityStack {
* @return Returns true if the move completed, false if not.
*/
final boolean moveTaskToBackLocked(int taskId, ActivityRecord reason) {
- Slog.i(TAG, "moveTaskToBack: " + taskId);
+ final TaskRecord tr = taskForIdLocked(taskId);
+ if (tr == null) {
+ Slog.i(TAG, "moveTaskToBack: bad taskId=" + taskId);
+ return false;
+ }
+
+ Slog.i(TAG, "moveTaskToBack: " + tr);
+
+ mStackSupervisor.endLockTaskModeIfTaskEnding(tr);
// If we have a watcher, preflight the move before committing to it. First check
// for *other* available tasks, but if none are available, then try again allowing the
@@ -3163,11 +3245,6 @@ final class ActivityStack {
if (DEBUG_TRANSITION) Slog.v(TAG,
"Prepare to back transition: task=" + taskId);
- final TaskRecord tr = taskForIdLocked(taskId);
- if (tr == null) {
- return false;
- }
-
mTaskHistory.remove(tr);
mTaskHistory.add(0, tr);
@@ -3468,9 +3545,7 @@ final class ActivityStack {
return didSomething;
}
- ActivityRecord getTasksLocked(IThumbnailReceiver receiver,
- PendingThumbnailsRecord pending, List<RunningTaskInfo> list) {
- ActivityRecord topRecord = null;
+ void getTasksLocked(List<RunningTaskInfo> list, int callingUid, boolean allowed) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
ActivityRecord r = null;
@@ -3481,6 +3556,9 @@ final class ActivityStack {
if (activities.isEmpty()) {
continue;
}
+ if (!allowed && !task.isHomeTask() && task.creatorUid != callingUid) {
+ continue;
+ }
for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
r = activities.get(activityNdx);
@@ -3514,23 +3592,8 @@ final class ActivityStack {
ci.numRunning = numRunning;
//System.out.println(
// "#" + maxNum + ": " + " descr=" + ci.description);
- if (receiver != null) {
- if (localLOGV) Slog.v(
- TAG, "State=" + top.state + "Idle=" + top.idle
- + " app=" + top.app
- + " thr=" + (top.app != null ? top.app.thread : null));
- if (top.state == ActivityState.RESUMED || top.state == ActivityState.PAUSING) {
- if (top.idle && top.app != null && top.app.thread != null) {
- topRecord = top;
- } else {
- top.thumbnailNeeded = true;
- }
- }
- pending.pendingRecords.add(top);
- }
list.add(ci);
}
- return topRecord;
}
public void unhandledBackLocked() {
@@ -3651,6 +3714,7 @@ final class ActivityStack {
}
void removeTask(TaskRecord task) {
+ mStackSupervisor.endLockTaskModeIfTaskEnding(task);
mWindowManager.removeTask(task.taskId);
final ActivityRecord r = mResumedActivity;
if (r != null && r.task == task) {
@@ -3664,6 +3728,21 @@ final class ActivityStack {
}
mTaskHistory.remove(task);
+ if (task.mActivities.isEmpty()) {
+ final boolean isVoiceSession = task.voiceSession != null;
+ if (isVoiceSession) {
+ try {
+ task.voiceSession.taskFinished(task.intent, task.taskId);
+ } catch (RemoteException e) {
+ }
+ }
+ if (task.autoRemoveFromRecents() || isVoiceSession) {
+ // Task creator asked to remove this when done, or this task was a voice
+ // interaction, so it should not remain on the recent tasks list.
+ mService.mRecentTasks.remove(task);
+ }
+ }
+
if (mTaskHistory.isEmpty()) {
if (DEBUG_STACK) Slog.i(TAG, "removeTask: moving to back stack=" + this);
if (isOnHomeDisplay()) {
@@ -3676,9 +3755,11 @@ final class ActivityStack {
}
}
- TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, boolean toTop) {
- TaskRecord task = new TaskRecord(taskId, info, intent);
- addTask(task, toTop);
+ TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ boolean toTop) {
+ TaskRecord task = new TaskRecord(taskId, info, intent, voiceSession, voiceInteractor);
+ addTask(task, toTop, false);
return task;
}
@@ -3686,13 +3767,19 @@ final class ActivityStack {
return new ArrayList<TaskRecord>(mTaskHistory);
}
- void addTask(final TaskRecord task, final boolean toTop) {
+ void addTask(final TaskRecord task, final boolean toTop, boolean moving) {
task.stack = this;
if (toTop) {
insertTaskAtTop(task);
} else {
mTaskHistory.add(0, task);
}
+ if (!moving && task.voiceSession != null) {
+ try {
+ task.voiceSession.taskStarted(task.intent, task.taskId);
+ } catch (RemoteException e) {
+ }
+ }
}
public int getStackId() {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index e942afb..ef9c711 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -41,7 +41,6 @@ import android.app.IActivityContainer;
import android.app.IActivityContainerCallback;
import android.app.IActivityManager;
import android.app.IApplicationThread;
-import android.app.IThumbnailReceiver;
import android.app.PendingIntent;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.IActivityManager.WaitResult;
@@ -76,6 +75,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.service.voice.IVoiceInteractionSession;
import android.util.EventLog;
import android.util.Slog;
import android.util.SparseArray;
@@ -86,6 +86,7 @@ import android.view.DisplayInfo;
import android.view.InputEvent;
import android.view.Surface;
import com.android.internal.app.HeavyWeightSwitcherActivity;
+import com.android.internal.app.IVoiceInteractor;
import com.android.internal.os.TransferPipe;
import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService.PendingActivityLaunch;
@@ -189,13 +190,12 @@ public final class ActivityStackSupervisor implements DisplayListener {
/** List of activities that are in the process of going to sleep. */
final ArrayList<ActivityRecord> mGoingToSleepActivities = new ArrayList<ActivityRecord>();
- /** List of ActivityRecord objects that have been finished and must still report back to a
- * pending thumbnail receiver. */
- final ArrayList<ActivityRecord> mCancelledThumbnails = new ArrayList<ActivityRecord>();
-
/** Used on user changes */
final ArrayList<UserStartedState> mStartingUsers = new ArrayList<UserStartedState>();
+ /** Used to queue up any background users being started */
+ final ArrayList<UserStartedState> mStartingBackgroundUsers = new ArrayList<UserStartedState>();
+
/** Set to indicate whether to issue an onUserLeaving callback when a newly launched activity
* is being brought in front of us. */
boolean mUserLeaving = false;
@@ -231,6 +231,10 @@ public final class ActivityStackSupervisor implements DisplayListener {
InputManagerInternal mInputManagerInternal;
+ /** If non-null then the task specified remains in front and no other tasks may be started
+ * until the task exits or #stopLockTaskMode() is called. */
+ private TaskRecord mLockTaskModeTask;
+
public ActivityStackSupervisor(ActivityManagerService service) {
mService = service;
PowerManager pm = (PowerManager)mService.mContext.getSystemService(Context.POWER_SERVICE);
@@ -523,7 +527,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
void pauseChildStacks(ActivityRecord parent, boolean userLeaving, boolean uiSleeping) {
- // TODO: Put all stacks in supervisor and iterate through them instead.
+ // TODO: Put all stacks in supervisor and iterate through them instead.
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
@@ -585,10 +589,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
return null;
}
- ActivityRecord getTasksLocked(int maxNum, IThumbnailReceiver receiver,
- PendingThumbnailsRecord pending, List<RunningTaskInfo> list) {
- ActivityRecord r = null;
-
+ void getTasksLocked(int maxNum, List<RunningTaskInfo> list, int callingUid, boolean allowed) {
// Gather all of the running tasks for each stack into runningTaskLists.
ArrayList<ArrayList<RunningTaskInfo>> runningTaskLists =
new ArrayList<ArrayList<RunningTaskInfo>>();
@@ -599,10 +600,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
final ActivityStack stack = stacks.get(stackNdx);
ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<RunningTaskInfo>();
runningTaskLists.add(stackTaskList);
- final ActivityRecord ar = stack.getTasksLocked(receiver, pending, stackTaskList);
- if (r == null && isFrontStack(stack)) {
- r = ar;
- }
+ stack.getTasksLocked(stackTaskList, callingUid, allowed);
}
}
@@ -629,8 +627,6 @@ public final class ActivityStackSupervisor implements DisplayListener {
break;
}
}
-
- return r;
}
ActivityInfo resolveActivity(Intent intent, String resolvedType, int startFlags,
@@ -682,13 +678,14 @@ public final class ActivityStackSupervisor implements DisplayListener {
void startHomeActivity(Intent intent, ActivityInfo aInfo) {
moveHomeToTop();
- startActivityLocked(null, intent, null, aInfo, null, null, 0, 0, 0, null, 0,
+ startActivityLocked(null, intent, null, aInfo, null, null, null, null, 0, 0, 0, null, 0,
null, false, null, null);
}
final int startActivityMayWait(IApplicationThread caller, int callingUid,
- String callingPackage, Intent intent, String resolvedType, IBinder resultTo,
- String resultWho, int requestCode, int startFlags, String profileFile,
+ String callingPackage, Intent intent, String resolvedType,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile,
ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config,
Bundle options, int userId, IActivityContainer iContainer) {
// Refuse possible leaked file descriptors
@@ -797,7 +794,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
- int res = startActivityLocked(caller, intent, resolvedType, aInfo, resultTo, resultWho,
+ int res = startActivityLocked(caller, intent, resolvedType, aInfo,
+ voiceSession, voiceInteractor, resultTo, resultWho,
requestCode, callingPid, callingUid, callingPackage, startFlags, options,
componentSpecified, null, container);
@@ -913,7 +911,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
theseOptions = null;
}
int res = startActivityLocked(caller, intent, resolvedTypes[i],
- aInfo, resultTo, null, -1, callingPid, callingUid, callingPackage,
+ aInfo, null, null, resultTo, null, -1, callingPid, callingUid, callingPackage,
0, theseOptions, componentSpecified, outActivity, null);
if (res < 0) {
return res;
@@ -1022,13 +1020,14 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
}
+
app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info,
- new Configuration(mService.mConfiguration), r.compat,
- app.repProcState, r.icicle, results, newIntents, !andResume,
- mService.isNextTransitionForward(), profileFile, profileFd,
- profileAutoStop);
+ new Configuration(mService.mConfiguration), r.compat, r.task.voiceInteractor,
+ app.repProcState, r.icicle, r.persistentState, results, newIntents, !andResume,
+ mService.isNextTransitionForward(), profileFile, profileFd, profileAutoStop
+ );
if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
// This may be a heavy-weight process! Note that the package
@@ -1135,8 +1134,9 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
final int startActivityLocked(IApplicationThread caller,
- Intent intent, String resolvedType, ActivityInfo aInfo, IBinder resultTo,
- String resultWho, int requestCode,
+ Intent intent, String resolvedType, ActivityInfo aInfo,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ IBinder resultTo, String resultWho, int requestCode,
int callingPid, int callingUid, String callingPackage, int startFlags, Bundle options,
boolean componentSpecified, ActivityRecord[] outActivity, ActivityContainer container) {
int err = ActivityManager.START_SUCCESS;
@@ -1179,7 +1179,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;
- int launchFlags = intent.getFlags();
+ final int launchFlags = intent.getFlags();
if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0
&& sourceRecord != null) {
@@ -1224,6 +1224,38 @@ public final class ActivityStackSupervisor implements DisplayListener {
err = ActivityManager.START_CLASS_NOT_FOUND;
}
+ if (err == ActivityManager.START_SUCCESS && sourceRecord != null
+ && sourceRecord.task.voiceSession != null) {
+ // If this activity is being launched as part of a voice session, we need
+ // to ensure that it is safe to do so. If the upcoming activity will also
+ // be part of the voice session, we can only launch it if it has explicitly
+ // said it supports the VOICE category, or it is a part of the calling app.
+ if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
+ && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
+ try {
+ if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(),
+ intent, resolvedType)) {
+ err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+ }
+ } catch (RemoteException e) {
+ err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+ }
+ }
+ }
+
+ if (err == ActivityManager.START_SUCCESS && voiceSession != null) {
+ // If the caller is starting a new voice session, just make sure the target
+ // is actually allowing it to run this way.
+ try {
+ if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(),
+ intent, resolvedType)) {
+ err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+ }
+ } catch (RemoteException e) {
+ err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+ }
+ }
+
if (err != ActivityManager.START_SUCCESS) {
if (resultRecord != null) {
resultStack.sendActivityResultLocked(-1,
@@ -1291,14 +1323,14 @@ public final class ActivityStackSupervisor implements DisplayListener {
ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
- requestCode, componentSpecified, this, container);
+ requestCode, componentSpecified, this, container, options);
if (outActivity != null) {
outActivity[0] = r;
}
final ActivityStack stack = getFocusedStack();
- if (stack.mResumedActivity == null
- || stack.mResumedActivity.info.applicationInfo.uid != callingUid) {
+ if (voiceSession == null && (stack.mResumedActivity == null
+ || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) {
if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) {
PendingActivityLaunch pal =
new PendingActivityLaunch(r, sourceRecord, startFlags, stack);
@@ -1322,7 +1354,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
mService.doPendingActivityLaunchesLocked(false);
- err = startActivityUncheckedLocked(r, sourceRecord, startFlags, true, options);
+ err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor,
+ startFlags, true, options);
if (allPausedActivitiesComplete()) {
// If someone asked to have the keyguard dismissed on the next
@@ -1402,8 +1435,9 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
final int startActivityUncheckedLocked(ActivityRecord r,
- ActivityRecord sourceRecord, int startFlags, boolean doResume,
- Bundle options) {
+ ActivityRecord sourceRecord,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags,
+ boolean doResume, Bundle options) {
final Intent intent = r.intent;
final int callingUid = r.launchedFromUid;
@@ -1438,14 +1472,31 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
+ switch (r.info.documentLaunchMode) {
+ case ActivityInfo.DOCUMENT_LAUNCH_NONE:
+ break;
+ case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS:
+ intent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ break;
+ case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING:
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+ break;
+ }
+ final boolean newDocument = intent.isDocument();
if (sourceRecord == null) {
// This activity is not being started from another... in this
// case we -always- start a new task.
- if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
+ if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
"Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
}
+ } else if (newDocument) {
+ if (r.launchMode != ActivityInfo.LAUNCH_MULTIPLE) {
+ Slog.w(TAG, "FLAG_ACTIVITY_NEW_DOCUMENT and launchMode != \"standard\"");
+ r.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+ }
} else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
// The original activity who is starting us is running as a single
// instance... this new activity it is starting must go on its
@@ -1468,7 +1519,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
// so we don't want to blindly throw it in to that task. Instead we will take
// the NEW_TASK flow and try to find a task for it. But save the task information
// so it can be used when creating the new task.
- if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
+ if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
Slog.w(TAG, "startActivity called from finishing " + sourceRecord
+ "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -1484,7 +1535,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
sourceStack = null;
}
- if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+ if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
// For whatever reason this activity is being launched into a new
// task... yet the caller has requested a result back. Well, that
// is pretty messed up, so instead immediately send back a cancel
@@ -1501,8 +1552,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
boolean movedHome = false;
TaskRecord reuseTask = null;
ActivityStack targetStack;
- if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
- (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
+ if (((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
+ (launchFlags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
// If bring to front is requested, and no result is requested, and
@@ -1517,6 +1568,10 @@ public final class ActivityStackSupervisor implements DisplayListener {
? findTaskLocked(r)
: findActivityLocked(intent, r.info);
if (intentActivity != null) {
+ if (isLockTaskModeViolation(intentActivity.task)) {
+ Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode");
+ return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
+ }
if (r.task == null) {
r.task = intentActivity.task;
}
@@ -1687,7 +1742,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
if (top != null && r.resultTo == null) {
if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) {
if (top.app != null && top.app.thread != null) {
- if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
+ if ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top,
@@ -1727,13 +1782,17 @@ public final class ActivityStackSupervisor implements DisplayListener {
// Should this be considered a new task?
if (r.resultTo == null && !addingToTask
&& (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+ if (isLockTaskModeViolation(reuseTask)) {
+ Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
+ return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
+ }
targetStack = adjustStackFocus(r);
targetStack.moveToFront();
if (reuseTask == null) {
r.setTask(targetStack.createTaskRecord(getNextTaskId(),
newTaskInfo != null ? newTaskInfo : r.info,
newTaskIntent != null ? newTaskIntent : intent,
- true), null, true);
+ voiceSession, voiceInteractor, true), null, true);
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " +
r.task);
} else {
@@ -1751,9 +1810,13 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
} else if (sourceRecord != null) {
TaskRecord sourceTask = sourceRecord.task;
+ if (isLockTaskModeViolation(sourceTask)) {
+ Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
+ return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
+ }
targetStack = sourceTask.stack;
targetStack.moveToFront();
- mWindowManager.moveTaskToTop(sourceTask.taskId);
+ mWindowManager.moveTaskToTop(targetStack.topTask().taskId);
if (!addingToTask &&
(launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
// In this case, we are adding the activity to an existing
@@ -1807,7 +1870,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
targetStack.moveToFront();
ActivityRecord prev = targetStack.topActivity();
r.setTask(prev != null ? prev.task
- : targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true),
+ : targetStack.createTaskRecord(getNextTaskId(), r.info, intent, null, null, true),
null, true);
mWindowManager.moveTaskToTop(r.task.taskId);
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
@@ -1848,7 +1911,6 @@ public final class ActivityStackSupervisor implements DisplayListener {
ArrayList<UserStartedState> startingUsers = null;
int NS = 0;
int NF = 0;
- IApplicationThread sendThumbnail = null;
boolean booting = false;
boolean enableScreen = false;
boolean activityRemoved = false;
@@ -1876,11 +1938,6 @@ public final class ActivityStackSupervisor implements DisplayListener {
// us, we can now deliver.
r.idle = true;
- if (r.thumbnailNeeded && r.app != null && r.app.thread != null) {
- sendThumbnail = r.app.thread;
- r.thumbnailNeeded = false;
- }
-
//Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout);
if (!mService.mBooted && isFrontStack(r.task.stack)) {
mService.mBooted = true;
@@ -1912,15 +1969,6 @@ public final class ActivityStackSupervisor implements DisplayListener {
mFinishingActivities.clear();
}
- final ArrayList<ActivityRecord> thumbnails;
- final int NT = mCancelledThumbnails.size();
- if (NT > 0) {
- thumbnails = new ArrayList<ActivityRecord>(mCancelledThumbnails);
- mCancelledThumbnails.clear();
- } else {
- thumbnails = null;
- }
-
if (isFrontStack(mHomeStack)) {
booting = mService.mBooting;
mService.mBooting = false;
@@ -1931,28 +1979,6 @@ public final class ActivityStackSupervisor implements DisplayListener {
mStartingUsers.clear();
}
- // Perform the following actions from unsynchronized state.
- final IApplicationThread thumbnailThread = sendThumbnail;
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- if (thumbnailThread != null) {
- try {
- thumbnailThread.requestThumbnail(token);
- } catch (Exception e) {
- Slog.w(TAG, "Exception thrown when requesting thumbnail", e);
- mService.sendPendingThumbnail(null, token, null, null, true);
- }
- }
-
- // Report back to any thumbnail receivers.
- for (int i = 0; i < NT; i++) {
- ActivityRecord r = thumbnails.get(i);
- mService.sendPendingThumbnail(r, null, null, null, true);
- }
- }
- });
-
// Stop any activities that are scheduled to do so but have been
// waiting for the next one to start.
for (int i = 0; i < NS; i++) {
@@ -1974,9 +2000,20 @@ public final class ActivityStackSupervisor implements DisplayListener {
if (booting) {
mService.finishBooting();
- } else if (startingUsers != null) {
- for (int i = 0; i < startingUsers.size(); i++) {
- mService.finishUserSwitch(startingUsers.get(i));
+ } else {
+ // Complete user switch
+ if (startingUsers != null) {
+ for (int i = 0; i < startingUsers.size(); i++) {
+ mService.finishUserSwitch(startingUsers.get(i));
+ }
+ }
+ // Complete starting up of background users
+ if (mStartingBackgroundUsers.size() > 0) {
+ startingUsers = new ArrayList<UserStartedState>(mStartingBackgroundUsers);
+ mStartingBackgroundUsers.clear();
+ for (int i = 0; i < startingUsers.size(); i++) {
+ mService.finishUserBoot(startingUsers.get(i));
+ }
}
}
@@ -2112,17 +2149,18 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
- void findTaskToMoveToFrontLocked(int taskId, int flags, Bundle options) {
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- if (stacks.get(stackNdx).findTaskToMoveToFrontLocked(taskId, flags, options)) {
- if (DEBUG_STACK) Slog.d(TAG, "findTaskToMoveToFront: moved to front of stack="
- + stacks.get(stackNdx));
- return;
- }
- }
+ void findTaskToMoveToFrontLocked(TaskRecord task, int flags, Bundle options) {
+ if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) {
+ mUserLeaving = true;
}
+ if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
+ // Caller wants the home activity moved with it. To accomplish this,
+ // we'll just indicate that this task returns to the home task.
+ task.mOnTopOfHome = true;
+ }
+ task.stack.moveTaskToFrontLocked(task, null, options);
+ if (DEBUG_STACK) Slog.d(TAG, "findTaskToMoveToFront: moved to front of stack="
+ + task.stack);
}
ActivityStack getStack(int stackId) {
@@ -2223,7 +2261,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
return;
}
task.stack.removeTask(task);
- stack.addTask(task, toTop);
+ stack.addTask(task, toTop, true);
mWindowManager.addTask(taskId, stackId, toTop);
resumeTopActivitiesLocked();
}
@@ -2274,6 +2312,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
checkReadyForSleepLocked();
+ setLockTaskModeLocked(null);
}
boolean shutdownLocked(int timeout) {
@@ -2414,21 +2453,12 @@ public final class ActivityStackSupervisor implements DisplayListener {
void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) {
// First the front stacks. In case any are not fullscreen and are in front of home.
- boolean showHomeBehindStack = false;
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
final int topStackNdx = stacks.size() - 1;
for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
- if (stackNdx == topStackNdx) {
- // Top stack.
- showHomeBehindStack =
- stack.ensureActivitiesVisibleLocked(starting, configChanges);
- } else {
- // Back stack.
- stack.ensureActivitiesVisibleLocked(starting, configChanges,
- showHomeBehindStack);
- }
+ stack.ensureActivitiesVisibleLocked(starting, configChanges);
}
}
}
@@ -2455,7 +2485,10 @@ public final class ActivityStackSupervisor implements DisplayListener {
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
stack.switchUserLocked(userId);
- mWindowManager.moveTaskToTop(stack.topTask().taskId);
+ TaskRecord task = stack.topTask();
+ if (task != null) {
+ mWindowManager.moveTaskToTop(task.taskId);
+ }
}
}
@@ -2466,7 +2499,10 @@ public final class ActivityStackSupervisor implements DisplayListener {
final boolean homeInFront = stack.isHomeStack();
if (stack.isOnHomeDisplay()) {
moveHomeStack(homeInFront);
- mWindowManager.moveTaskToTop(stack.topTask().taskId);
+ TaskRecord task = stack.topTask();
+ if (task != null) {
+ mWindowManager.moveTaskToTop(task.taskId);
+ }
} else {
// Stack was moved to another display while user was swapped out.
resumeHomeActivity(null);
@@ -2474,6 +2510,15 @@ public final class ActivityStackSupervisor implements DisplayListener {
return homeInFront;
}
+ /**
+ * Add background users to send boot completed events to.
+ * @param userId The user being started in the background
+ * @param uss The state object for the user.
+ */
+ public void startBackgroundUserLocked(int userId, UserStartedState uss) {
+ mStartingBackgroundUsers.add(uss);
+ }
+
final ArrayList<ActivityRecord> processStoppingActivitiesLocked(boolean remove) {
int N = mStoppingActivities.size();
if (N <= 0) return null;
@@ -2852,6 +2897,35 @@ public final class ActivityStackSupervisor implements DisplayListener {
return list;
}
+ void setLockTaskModeLocked(TaskRecord task) {
+ if (task == null) {
+ // Take out of lock task mode.
+ mLockTaskModeTask = null;
+ return;
+ }
+ if (isLockTaskModeViolation(task)) {
+ Slog.e(TAG, "setLockTaskMode: Attempt to start a second Lock Task Mode task.");
+ return;
+ }
+ mLockTaskModeTask = task;
+ findTaskToMoveToFrontLocked(task, 0, null);
+ resumeTopActivitiesLocked();
+ }
+
+ boolean isLockTaskModeViolation(TaskRecord task) {
+ return mLockTaskModeTask != null && mLockTaskModeTask != task;
+ }
+
+ void endLockTaskModeIfTaskEnding(TaskRecord task) {
+ if (mLockTaskModeTask != null && mLockTaskModeTask == task) {
+ mLockTaskModeTask = null;
+ }
+ }
+
+ boolean isInLockTaskMode() {
+ return mLockTaskModeTask != null;
+ }
+
private final class ActivityStackSupervisorHandler extends Handler {
public ActivityStackSupervisorHandler(Looper looper) {
@@ -3041,7 +3115,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
&& "content".equals(intent.getData().getScheme())) {
mimeType = mService.getProviderMimeType(intent.getData(), userId);
}
- return startActivityMayWait(null, -1, null, intent, mimeType, null, null, 0, 0, null,
+ return startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, null, 0, 0, null,
null, null, null, null, userId, this);
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 89e96fd..249422b 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -27,10 +27,13 @@ import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcel;
+import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.util.Slog;
@@ -38,6 +41,7 @@ import android.util.Slog;
import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.PowerProfile;
+import com.android.server.LocalServices;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -47,13 +51,17 @@ import java.util.List;
* All information we are collecting about things that can happen that impact
* battery life.
*/
-public final class BatteryStatsService extends IBatteryStats.Stub {
+public final class BatteryStatsService extends IBatteryStats.Stub
+ implements PowerManagerInternal.LowPowerModeListener {
+ static final String TAG = "BatteryStatsService";
+
static IBatteryStats sService;
final BatteryStatsImpl mStats;
Context mContext;
private boolean mBluetoothPendingStats;
private BluetoothHeadset mBluetoothHeadset;
+ PowerManagerInternal mPowerManagerInternal;
BatteryStatsService(String filename, Handler handler) {
mStats = new BatteryStatsImpl(filename, handler);
@@ -66,7 +74,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
mStats.setRadioScanningTimeout(mContext.getResources().getInteger(
com.android.internal.R.integer.config_radioScanningTimeout)
* 1000L);
- }
+ mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+ mPowerManagerInternal.registerLowPowerModeObserver(this);
+ mStats.noteLowPowerMode(mPowerManagerInternal.getLowPowerModeEnabled());
+ (new WakeupReasonThread()).start();
+ }
public void shutdown() {
Slog.w("BatteryStats", "Writing battery stats before shutdown...");
@@ -83,7 +95,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
sService = asInterface(b);
return sService;
}
-
+
+ @Override
+ public void onLowPowerModeChanged(boolean enabled) {
+ synchronized (mStats) {
+ mStats.noteLowPowerMode(enabled);
+ }
+ }
+
/**
* @return the current statistics object, which may be modified
* to reflect events that affect battery usage. You must lock the
@@ -105,31 +124,82 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
return data;
}
- public void noteStartWakelock(int uid, int pid, String name, int type) {
+ public long computeBatteryTimeRemaining() {
+ synchronized (mStats) {
+ long time = mStats.computeBatteryTimeRemaining(SystemClock.elapsedRealtime());
+ return time >= 0 ? (time/1000) : time;
+ }
+ }
+
+ public long computeChargeTimeRemaining() {
+ synchronized (mStats) {
+ long time = mStats.computeChargeTimeRemaining(SystemClock.elapsedRealtime());
+ return time >= 0 ? (time/1000) : time;
+ }
+ }
+
+ public void addIsolatedUid(int isolatedUid, int appUid) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.addIsolatedUidLocked(isolatedUid, appUid);
+ }
+ }
+
+ public void removeIsolatedUid(int isolatedUid, int appUid) {
enforceCallingPermission();
synchronized (mStats) {
- mStats.noteStartWakeLocked(uid, pid, name, type);
+ mStats.removeIsolatedUidLocked(isolatedUid, appUid);
}
}
- public void noteStopWakelock(int uid, int pid, String name, int type) {
+ public void noteEvent(int code, String name, int uid) {
enforceCallingPermission();
synchronized (mStats) {
- mStats.noteStopWakeLocked(uid, pid, name, type);
+ mStats.noteEventLocked(code, name, uid);
}
}
- public void noteStartWakelockFromSource(WorkSource ws, int pid, String name, int type) {
+ public void noteStartWakelock(int uid, int pid, String name, String historyName, int type,
+ boolean unimportantForLogging) {
enforceCallingPermission();
synchronized (mStats) {
- mStats.noteStartWakeFromSourceLocked(ws, pid, name, type);
+ mStats.noteStartWakeLocked(uid, pid, name, historyName, type, unimportantForLogging,
+ SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
}
}
- public void noteStopWakelockFromSource(WorkSource ws, int pid, String name, int type) {
+ public void noteStopWakelock(int uid, int pid, String name, String historyName, int type) {
enforceCallingPermission();
synchronized (mStats) {
- mStats.noteStopWakeFromSourceLocked(ws, pid, name, type);
+ mStats.noteStopWakeLocked(uid, pid, name, historyName, type,
+ SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
+ }
+ }
+
+ public void noteStartWakelockFromSource(WorkSource ws, int pid, String name,
+ String historyName, int type, boolean unimportantForLogging) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteStartWakeFromSourceLocked(ws, pid, name, historyName,
+ type, unimportantForLogging);
+ }
+ }
+
+ public void noteChangeWakelockFromSource(WorkSource ws, int pid, String name,
+ String historyName, int type, WorkSource newWs, int newPid, String newName,
+ String newHistoryName, int newType, boolean newUnimportantForLogging) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteChangeWakelockFromSourceLocked(ws, pid, name, historyName, type,
+ newWs, newPid, newName, newHistoryName, newType, newUnimportantForLogging);
+ }
+ }
+
+ public void noteStopWakelockFromSource(WorkSource ws, int pid, String name, String historyName,
+ int type) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteStopWakeFromSourceLocked(ws, pid, name, historyName, type);
}
}
@@ -202,7 +272,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
mStats.noteInteractiveLocked(interactive);
}
}
-
+
+ public void noteMobileRadioPowerState(int powerState, long timestampNs) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteMobileRadioPowerState(powerState, timestampNs);
+ }
+ }
+
public void notePhoneOn() {
enforceCallingPermission();
synchronized (mStats) {
@@ -302,6 +379,13 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
}
}
+ public void noteWifiState(int wifiState, String accessPoint) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteWifiStateLocked(wifiState, accessPoint);
+ }
+ }
+
public void noteBluetoothOn() {
enforceCallingPermission();
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -345,6 +429,13 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
}
}
+ public void noteBluetoothState(int bluetoothState) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteBluetoothStateLocked(bluetoothState);
+ }
+ }
+
public void noteFullWifiLockAcquired(int uid) {
enforceCallingPermission();
synchronized (mStats) {
@@ -488,18 +579,79 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
-
+
+ final class WakeupReasonThread extends Thread {
+ final int[] mIrqs = new int[32];
+ final String[] mReasons = new String[32];
+
+ WakeupReasonThread() {
+ super("BatteryStats_wakeupReason");
+ }
+
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
+
+ try {
+ int num;
+ while ((num=nativeWaitWakeup(mIrqs, mReasons)) >= 0) {
+ synchronized (mStats) {
+ if (num > 0) {
+ for (int i=0; i<num; i++) {
+ mStats.noteWakeupReasonLocked(mReasons[i]);
+ }
+ } else {
+ mStats.noteWakeupReasonLocked("unknown");
+ }
+ }
+ }
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "Failure reading wakeup reasons", e);
+ }
+ }
+ }
+
+ private static native int nativeWaitWakeup(int[] outIrqs, String[] outReasons);
+
private void dumpHelp(PrintWriter pw) {
pw.println("Battery stats (batterystats) dump options:");
- pw.println(" [--checkin] [-c] [--unplugged] [--reset] [--write] [-h] [<package.name>]");
+ pw.println(" [--checkin] [--history] [--history-start] [--unplugged] [--charged] [-c]");
+ pw.println(" [--reset] [--write] [-h] [<package.name>]");
pw.println(" --checkin: format output for a checkin report.");
+ pw.println(" --history: show only history data.");
+ pw.println(" --history-start <num>: show only history data starting at given time offset.");
pw.println(" --unplugged: only output data since last unplugged.");
+ pw.println(" --charged: only output data since last charged.");
pw.println(" --reset: reset the stats, clearing all current data.");
pw.println(" --write: force write current collected stats to disk.");
+ pw.println(" --enable: enable an option: full-wake-history, no-auto-reset.");
+ pw.println(" --disable: disable an option: full-wake-history, no-auto-reset.");
pw.println(" -h: print this help text.");
pw.println(" <package.name>: optional name of package to filter output by.");
}
+ private int doEnableOrDisable(PrintWriter pw, int i, String[] args, boolean enable) {
+ i++;
+ if (i >= args.length) {
+ pw.println("Missing option argument for " + (enable ? "--enable" : "--disable"));
+ dumpHelp(pw);
+ return -1;
+ }
+ if ("full-wake-history".equals(args[i])) {
+ synchronized (mStats) {
+ mStats.setRecordAllWakeLocksLocked(enable);
+ }
+ } else if ("no-auto-reset".equals(args[i])) {
+ synchronized (mStats) {
+ mStats.setNoAutoReset(enable);
+ }
+ } else {
+ pw.println("Unknown enable/disable option: " + args[i]);
+ dumpHelp(pw);
+ return -1;
+ }
+ return i;
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -510,23 +662,37 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
return;
}
+ int flags = 0;
boolean isCheckin = false;
- boolean includeHistory = false;
- boolean isUnpluggedOnly = false;
boolean noOutput = false;
+ long historyStart = -1;
int reqUid = -1;
if (args != null) {
- for (String arg : args) {
+ for (int i=0; i<args.length; i++) {
+ String arg = args[i];
if ("--checkin".equals(arg)) {
isCheckin = true;
+ } else if ("--history".equals(arg)) {
+ flags |= BatteryStats.DUMP_HISTORY_ONLY;
+ } else if ("--history-start".equals(arg)) {
+ flags |= BatteryStats.DUMP_HISTORY_ONLY;
+ i++;
+ if (i >= args.length) {
+ pw.println("Missing time argument for --history-since");
+ dumpHelp(pw);
+ return;
+ }
+ historyStart = Long.parseLong(args[i]);
} else if ("-c".equals(arg)) {
isCheckin = true;
- includeHistory = true;
+ flags |= BatteryStats.DUMP_INCLUDE_HISTORY;
} else if ("--unplugged".equals(arg)) {
- isUnpluggedOnly = true;
+ flags |= BatteryStats.DUMP_UNPLUGGED_ONLY;
+ } else if ("--charged".equals(arg)) {
+ flags |= BatteryStats.DUMP_CHARGED_ONLY;
} else if ("--reset".equals(arg)) {
synchronized (mStats) {
- mStats.resetAllStatsLocked();
+ mStats.resetAllStatsCmdLocked();
pw.println("Battery stats reset.");
noOutput = true;
}
@@ -536,11 +702,25 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
pw.println("Battery stats written.");
noOutput = true;
}
+ } else if ("--enable".equals(arg)) {
+ i = doEnableOrDisable(pw, i, args, true);
+ if (i < 0) {
+ return;
+ }
+ pw.println("Enabled: " + args[i]);
+ return;
+ } else if ("--disable".equals(arg)) {
+ i = doEnableOrDisable(pw, i, args, false);
+ if (i < 0) {
+ return;
+ }
+ pw.println("Disabled: " + args[i]);
+ return;
} else if ("-h".equals(arg)) {
dumpHelp(pw);
return;
} else if ("-a".equals(arg)) {
- // fall through
+ flags |= BatteryStats.DUMP_VERBOSE;
} else if (arg.length() > 0 && arg.charAt(0) == '-'){
pw.println("Unknown option: " + arg);
dumpHelp(pw);
@@ -564,11 +744,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
if (isCheckin) {
List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0);
synchronized (mStats) {
- mStats.dumpCheckinLocked(pw, apps, isUnpluggedOnly, includeHistory);
+ mStats.dumpCheckinLocked(mContext, pw, apps, flags, historyStart);
}
} else {
synchronized (mStats) {
- mStats.dumpLocked(pw, isUnpluggedOnly, reqUid);
+ mStats.dumpLocked(mContext, pw, flags, reqUid, historyStart);
}
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index aef9e5c..9d6481a 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -27,7 +27,6 @@ import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
@@ -504,7 +503,7 @@ public final class BroadcastQueue {
// are already core system stuff so don't matter for this.
r.curApp = filter.receiverList.app;
filter.receiverList.app.curReceiver = r;
- mService.updateOomAdjLocked(r.curApp, true);
+ mService.updateOomAdjLocked(r.curApp);
}
}
try {
diff --git a/services/core/java/com/android/server/am/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java
index 646b7d2..ff22764 100644
--- a/services/core/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/core/java/com/android/server/am/ContentProviderRecord.java
@@ -31,7 +31,6 @@ import android.util.Slog;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.HashSet;
final class ContentProviderRecord {
final ActivityManagerService service;
diff --git a/services/core/java/com/android/server/am/IntentBindRecord.java b/services/core/java/com/android/server/am/IntentBindRecord.java
index 21cf266..ba6010a 100644
--- a/services/core/java/com/android/server/am/IntentBindRecord.java
+++ b/services/core/java/com/android/server/am/IntentBindRecord.java
@@ -22,8 +22,6 @@ import android.os.IBinder;
import android.util.ArrayMap;
import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.Iterator;
/**
* A particular Intent that has been bound to a Service.
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 00fa216..98999e9 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -41,6 +41,8 @@ final class PendingIntentRecord extends IIntentSender.Stub {
boolean canceled = false;
String stringName;
+ String lastTagPrefix;
+ String lastTag;
final static class Key {
final int type;
diff --git a/services/core/java/com/android/server/am/PendingThumbnailsRecord.java b/services/core/java/com/android/server/am/PendingThumbnailsRecord.java
deleted file mode 100644
index e4eb4d0..0000000
--- a/services/core/java/com/android/server/am/PendingThumbnailsRecord.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.am;
-
-import android.app.IThumbnailReceiver;
-
-import java.util.HashSet;
-
-/**
- * This class keeps track of calls to getTasks() that are still
- * waiting for thumbnail images.
- */
-final class PendingThumbnailsRecord
-{
- final IThumbnailReceiver receiver; // who is waiting.
- final HashSet<ActivityRecord> pendingRecords; // HistoryRecord objects we still wait for.
- boolean finished; // Is pendingRecords empty?
-
- PendingThumbnailsRecord(IThumbnailReceiver _receiver)
- {
- receiver = _receiver;
- pendingRecords = new HashSet<ActivityRecord>();
- finished = false;
- }
-}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index f5920c8..755a237 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -16,7 +16,6 @@
package com.android.server.am;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 217a8d6..8d7d300 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -59,6 +59,7 @@ final class ProcessRecord {
// 'persistent' is true (in which case we
// are in the process of launching the app)
ProcessStats.ProcessState baseProcessTracker;
+ BatteryStatsImpl.Uid.Proc curProcBatteryStats;
int pid; // The process of this application; 0 if none
boolean starting; // True if the process is being started
long lastActivityTime; // For managing the LRU list
@@ -76,7 +77,6 @@ final class ProcessRecord {
int curSchedGroup; // Currently desired scheduling class
int setSchedGroup; // Last set to background scheduling class
int trimMemoryLevel; // Last selected memory trimming level
- int memImportance; // Importance constant computed from curAdj
int curProcState = -1; // Currently computed process state: ActivityManager.PROCESS_STATE_*
int repProcState = -1; // Last reported process state
int setProcState = -1; // Last set process state in process tracker
@@ -90,10 +90,12 @@ final class ProcessRecord {
boolean hasStartedServices; // Are there any started services running in this process?
boolean foregroundServices; // Running any services that are foreground?
boolean foregroundActivities; // Running any activities that are foreground?
+ boolean repForegroundActivities; // Last reported foreground activities.
boolean systemNoUi; // This is a system process, but not currently showing UI.
boolean hasShownUi; // Has UI been shown in this process since it was started?
boolean pendingUiClean; // Want to clean up resources from showing UI?
boolean hasAboveClient; // Bound using BIND_ABOVE_CLIENT, so want to be lower
+ boolean treatLikeActivity; // Bound using BIND_TREAT_LIKE_ACTIVITY
boolean bad; // True if disabled in the bad process list
boolean killedByAm; // True when proc has been killed by activity manager, not for RAM
boolean procStateChanged; // Keep track of whether we changed 'setAdj'.
@@ -250,10 +252,11 @@ final class ProcessRecord {
pw.print(" lastStateTime=");
TimeUtils.formatDuration(lastStateTime, now, pw);
pw.println();
- if (hasShownUi || pendingUiClean || hasAboveClient) {
+ if (hasShownUi || pendingUiClean || hasAboveClient || treatLikeActivity) {
pw.print(prefix); pw.print("hasShownUi="); pw.print(hasShownUi);
pw.print(" pendingUiClean="); pw.print(pendingUiClean);
- pw.print(" hasAboveClient="); pw.println(hasAboveClient);
+ pw.print(" hasAboveClient="); pw.print(hasAboveClient);
+ pw.print(" treatLikeActivity="); pw.println(treatLikeActivity);
}
if (setIsForeground || foregroundServices || forcingToForeground != null) {
pw.print(prefix); pw.print("setIsForeground="); pw.print(setIsForeground);
@@ -264,9 +267,10 @@ final class ProcessRecord {
pw.print(prefix); pw.print("persistent="); pw.print(persistent);
pw.print(" removed="); pw.println(removed);
}
- if (hasClientActivities || foregroundActivities) {
+ if (hasClientActivities || foregroundActivities || repForegroundActivities) {
pw.print(prefix); pw.print("hasClientActivities="); pw.print(hasClientActivities);
- pw.print(" foregroundActivities="); pw.println(foregroundActivities);
+ pw.print(" foregroundActivities="); pw.print(foregroundActivities);
+ pw.print(" (rep="); pw.print(repForegroundActivities); pw.println(")");
}
if (hasStartedServices) {
pw.print(prefix); pw.print("hasStartedServices="); pw.println(hasStartedServices);
@@ -391,14 +395,15 @@ final class ProcessRecord {
origBase.makeInactive();
}
baseProcessTracker = tracker.getProcessStateLocked(info.packageName, info.uid,
- processName);
+ info.versionCode, processName);
baseProcessTracker.makeActive();
for (int i=0; i<pkgList.size(); i++) {
ProcessStats.ProcessState ps = pkgList.valueAt(i);
if (ps != null && ps != origBase) {
ps.makeInactive();
}
- ps = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid, processName);
+ ps = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid,
+ info.versionCode, processName);
if (ps != baseProcessTracker) {
ps.makeActive();
}
@@ -571,7 +576,7 @@ final class ProcessRecord {
if (!pkgList.containsKey(pkg)) {
if (baseProcessTracker != null) {
ProcessStats.ProcessState state = tracker.getProcessStateLocked(
- pkg, info.uid, processName);
+ pkg, info.uid, info.versionCode, processName);
pkgList.put(pkg, state);
if (state != baseProcessTracker) {
state.makeActive();
@@ -618,7 +623,7 @@ final class ProcessRecord {
}
pkgList.clear();
ProcessStats.ProcessState ps = tracker.getProcessStateLocked(
- info.packageName, info.uid, processName);
+ info.packageName, info.uid, info.versionCode, processName);
pkgList.put(info.packageName, ps);
if (ps != baseProcessTracker) {
ps.makeActive();
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index e05fcda..14f3ef9 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -16,8 +16,6 @@
package com.android.server.am;
-import android.app.AppGlobals;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Parcel;
@@ -25,7 +23,6 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Slog;
@@ -111,13 +108,14 @@ public final class ProcessStatsService extends IProcessStats.Stub {
}
public ProcessStats.ProcessState getProcessStateLocked(String packageName,
- int uid, String processName) {
- return mProcessStats.getProcessStateLocked(packageName, uid, processName);
+ int uid, int versionCode, String processName) {
+ return mProcessStats.getProcessStateLocked(packageName, uid, versionCode, processName);
}
public ProcessStats.ServiceState getServiceStateLocked(String packageName, int uid,
- String processName, String className) {
- return mProcessStats.getServiceStateLocked(packageName, uid, processName, className);
+ int versionCode, String processName, String className) {
+ return mProcessStats.getServiceStateLocked(packageName, uid, versionCode, processName,
+ className);
}
public boolean isMemFactorLowered() {
@@ -137,25 +135,29 @@ public final class ProcessStatsService extends IProcessStats.Stub {
}
mProcessStats.mMemFactor = memFactor;
mProcessStats.mStartTime = now;
- ArrayMap<String, SparseArray<ProcessStats.PackageState>> pmap
+ final ArrayMap<String, SparseArray<SparseArray<ProcessStats.PackageState>>> pmap
= mProcessStats.mPackages.getMap();
- for (int i=0; i<pmap.size(); i++) {
- SparseArray<ProcessStats.PackageState> uids = pmap.valueAt(i);
- for (int j=0; j<uids.size(); j++) {
- ProcessStats.PackageState pkg = uids.valueAt(j);
- ArrayMap<String, ProcessStats.ServiceState> services = pkg.mServices;
- for (int k=0; k<services.size(); k++) {
- ProcessStats.ServiceState service = services.valueAt(k);
- if (service.isInUse()) {
- if (service.mStartedState != ProcessStats.STATE_NOTHING) {
- service.setStarted(true, memFactor, now);
- }
- if (service.mBoundState != ProcessStats.STATE_NOTHING) {
- service.setBound(true, memFactor, now);
- }
- if (service.mExecState != ProcessStats.STATE_NOTHING) {
- service.setExecuting(true, memFactor, now);
+ for (int ipkg=pmap.size()-1; ipkg>=0; ipkg--) {
+ final SparseArray<SparseArray<ProcessStats.PackageState>> uids = pmap.valueAt(ipkg);
+ for (int iuid=uids.size()-1; iuid>=0; iuid--) {
+ final SparseArray<ProcessStats.PackageState> vers = uids.valueAt(iuid);
+ for (int iver=vers.size()-1; iver>=0; iver--) {
+ final ProcessStats.PackageState pkg = vers.valueAt(iver);
+ final ArrayMap<String, ProcessStats.ServiceState> services = pkg.mServices;
+ for (int isvc=services.size()-1; isvc>=0; isvc--) {
+ final ProcessStats.ServiceState service = services.valueAt(isvc);
+ if (service.isInUse()) {
+ if (service.mStartedState != ProcessStats.STATE_NOTHING) {
+ service.setStarted(true, memFactor, now);
+ }
+ if (service.mBoundState != ProcessStats.STATE_NOTHING) {
+ service.setBound(true, memFactor, now);
+ }
+ if (service.mExecState != ProcessStats.STATE_NOTHING) {
+ service.setExecuting(true, memFactor, now);
+ }
}
+
}
}
}
@@ -294,25 +296,32 @@ public final class ProcessStatsService extends IProcessStats.Stub {
Slog.w(TAG, " Uid " + uids.keyAt(iu) + ": " + uids.valueAt(iu));
}
}
- ArrayMap<String, SparseArray<ProcessStats.PackageState>> pkgMap
+ ArrayMap<String, SparseArray<SparseArray<ProcessStats.PackageState>>> pkgMap
= stats.mPackages.getMap();
final int NPKG = pkgMap.size();
for (int ip=0; ip<NPKG; ip++) {
Slog.w(TAG, "Package: " + pkgMap.keyAt(ip));
- SparseArray<ProcessStats.PackageState> uids = pkgMap.valueAt(ip);
+ SparseArray<SparseArray<ProcessStats.PackageState>> uids
+ = pkgMap.valueAt(ip);
final int NUID = uids.size();
for (int iu=0; iu<NUID; iu++) {
Slog.w(TAG, " Uid: " + uids.keyAt(iu));
- ProcessStats.PackageState pkgState = uids.valueAt(iu);
- final int NPROCS = pkgState.mProcesses.size();
- for (int iproc=0; iproc<NPROCS; iproc++) {
- Slog.w(TAG, " Process " + pkgState.mProcesses.keyAt(iproc)
- + ": " + pkgState.mProcesses.valueAt(iproc));
- }
- final int NSRVS = pkgState.mServices.size();
- for (int isvc=0; isvc<NSRVS; isvc++) {
- Slog.w(TAG, " Service " + pkgState.mServices.keyAt(isvc)
- + ": " + pkgState.mServices.valueAt(isvc));
+ SparseArray<ProcessStats.PackageState> vers = uids.valueAt(iu);
+ final int NVERS = vers.size();
+ for (int iv=0; iv<NVERS; iv++) {
+ Slog.w(TAG, " Vers: " + vers.keyAt(iv));
+ ProcessStats.PackageState pkgState = vers.valueAt(iv);
+ final int NPROCS = pkgState.mProcesses.size();
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ Slog.w(TAG, " Process " + pkgState.mProcesses.keyAt(iproc)
+ + ": " + pkgState.mProcesses.valueAt(iproc));
+ }
+ final int NSRVS = pkgState.mServices.size();
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ Slog.w(TAG, " Service " + pkgState.mServices.keyAt(isvc)
+ + ": " + pkgState.mServices.valueAt(isvc));
+
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/ReceiverList.java b/services/core/java/com/android/server/am/ReceiverList.java
index fa8c1df..6ade736 100644
--- a/services/core/java/com/android/server/am/ReceiverList.java
+++ b/services/core/java/com/android/server/am/ReceiverList.java
@@ -17,11 +17,8 @@
package com.android.server.am;
import android.content.IIntentReceiver;
-import android.content.Intent;
import android.os.Binder;
-import android.os.Bundle;
import android.os.IBinder;
-import android.os.RemoteException;
import android.util.PrintWriterPrinter;
import android.util.Printer;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index cb04835..e54c95e 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -193,14 +193,7 @@ final class ServiceRecord extends Binder {
pw.println(si.neededGrants);
}
if (si.uriPermissions != null) {
- if (si.uriPermissions.readUriPermissions != null) {
- pw.print(prefix); pw.print(" readUriPermissions=");
- pw.println(si.uriPermissions.readUriPermissions);
- }
- if (si.uriPermissions.writeUriPermissions != null) {
- pw.print(prefix); pw.print(" writeUriPermissions=");
- pw.println(si.uriPermissions.writeUriPermissions);
- }
+ si.uriPermissions.dump(pw, prefix);
}
}
}
@@ -329,7 +322,8 @@ final class ServiceRecord extends Binder {
}
if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) {
tracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName,
- serviceInfo.applicationInfo.uid, serviceInfo.processName, serviceInfo.name);
+ serviceInfo.applicationInfo.uid, serviceInfo.applicationInfo.versionCode,
+ serviceInfo.processName, serviceInfo.name);
tracker.applyNewOwner(this);
}
return tracker;
@@ -346,7 +340,8 @@ final class ServiceRecord extends Binder {
if (restartTracker == null) {
if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) {
restartTracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName,
- serviceInfo.applicationInfo.uid, serviceInfo.processName, serviceInfo.name);
+ serviceInfo.applicationInfo.uid, serviceInfo.applicationInfo.versionCode,
+ serviceInfo.processName, serviceInfo.name);
}
if (restartTracker == null) {
return;
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 9740812..6d66b29 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -28,7 +28,9 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.os.UserHandle;
+import android.service.voice.IVoiceInteractionSession;
import android.util.Slog;
+import com.android.internal.app.IVoiceInteractor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -36,6 +38,8 @@ import java.util.ArrayList;
final class TaskRecord extends ThumbnailHolder {
final int taskId; // Unique identifier for this task.
final String affinity; // The affinity name for this task, or null.
+ final IVoiceInteractionSession voiceSession; // Voice interaction session driving task
+ final IVoiceInteractor voiceInteractor; // Associated interactor to provide to app
Intent intent; // The original intent that started the task.
Intent affinityIntent; // Intent of affinity-moved activity that started this task.
ComponentName origActivity; // The non-alias activity component of the intent.
@@ -48,9 +52,15 @@ final class TaskRecord extends ThumbnailHolder {
String stringName; // caching of toString() result.
int userId; // user for which this task was created
+ int creatorUid; // The app uid that originally created the task
int numFullscreen; // Number of fullscreen activities.
+ // This represents the last resolved activity values for this task
+ // NOTE: This value needs to be persisted with each task
+ ActivityManager.TaskDescription lastTaskDescription =
+ new ActivityManager.TaskDescription();
+
/** List of all activities in the task arranged in history order */
final ArrayList<ActivityRecord> mActivities = new ArrayList<ActivityRecord>();
@@ -64,9 +74,12 @@ final class TaskRecord extends ThumbnailHolder {
* Display.DEFAULT_DISPLAY. */
boolean mOnTopOfHome = false;
- TaskRecord(int _taskId, ActivityInfo info, Intent _intent) {
+ TaskRecord(int _taskId, ActivityInfo info, Intent _intent,
+ IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
taskId = _taskId;
affinity = info.taskAffinity;
+ voiceSession = _voiceSession;
+ voiceInteractor = _voiceInteractor;
setIntent(_intent, info);
}
@@ -124,8 +137,10 @@ final class TaskRecord extends ThumbnailHolder {
rootWasReset = true;
}
- if (info.applicationInfo != null) {
- userId = UserHandle.getUserId(info.applicationInfo.uid);
+ userId = UserHandle.getUserId(info.applicationInfo.uid);
+ creatorUid = info.applicationInfo.uid;
+ if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS);
}
}
@@ -139,6 +154,23 @@ final class TaskRecord extends ThumbnailHolder {
}
}
+ /** Returns the intent for the root activity for this task */
+ Intent getBaseIntent() {
+ return intent != null ? intent : affinityIntent;
+ }
+
+ /** Returns the first non-finishing activity from the root. */
+ ActivityRecord getRootActivity() {
+ for (int i = 0; i < mActivities.size(); i++) {
+ final ActivityRecord r = mActivities.get(i);
+ if (r.finishing) {
+ continue;
+ }
+ return r;
+ }
+ return null;
+ }
+
ActivityRecord getTopActivity() {
for (int i = mActivities.size() - 1; i >= 0; --i) {
final ActivityRecord r = mActivities.get(i);
@@ -153,7 +185,7 @@ final class TaskRecord extends ThumbnailHolder {
ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
ActivityRecord r = mActivities.get(activityNdx);
- if (!r.finishing && r != notTop && stack.okToShow(r)) {
+ if (!r.finishing && r != notTop && stack.okToShowLocked(r)) {
return r;
}
}
@@ -222,6 +254,11 @@ final class TaskRecord extends ThumbnailHolder {
return mActivities.size() == 0;
}
+ boolean autoRemoveFromRecents() {
+ return intent != null &&
+ (intent.getFlags() & Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS) != 0;
+ }
+
/**
* Completely remove all activities associated with an existing
* task starting at a specified index.
@@ -305,7 +342,7 @@ final class TaskRecord extends ThumbnailHolder {
}
public ActivityManager.TaskThumbnails getTaskThumbnailsLocked() {
- TaskAccessInfo info = getTaskAccessInfoLocked(true);
+ TaskAccessInfo info = getTaskAccessInfoLocked();
final ActivityRecord resumedActivity = stack.mResumedActivity;
if (resumedActivity != null && resumedActivity.thumbHolder == this) {
info.mainThumbnail = stack.screenshotActivities(resumedActivity);
@@ -325,7 +362,7 @@ final class TaskRecord extends ThumbnailHolder {
}
// Return the information about the task, to figure out the top
// thumbnail to return.
- TaskAccessInfo info = getTaskAccessInfoLocked(true);
+ TaskAccessInfo info = getTaskAccessInfoLocked();
if (info.numSubThumbbails <= 0) {
return info.mainThumbnail != null ? info.mainThumbnail : lastThumbnail;
}
@@ -334,7 +371,7 @@ final class TaskRecord extends ThumbnailHolder {
public ActivityRecord removeTaskActivitiesLocked(int subTaskIndex,
boolean taskRequired) {
- TaskAccessInfo info = getTaskAccessInfoLocked(false);
+ TaskAccessInfo info = getTaskAccessInfoLocked();
if (info.root == null) {
if (taskRequired) {
Slog.w(TAG, "removeTaskLocked: unknown taskId " + taskId);
@@ -369,7 +406,7 @@ final class TaskRecord extends ThumbnailHolder {
return mTaskType == ActivityRecord.APPLICATION_ACTIVITY_TYPE;
}
- public TaskAccessInfo getTaskAccessInfoLocked(boolean inclThumbs) {
+ public TaskAccessInfo getTaskAccessInfoLocked() {
final TaskAccessInfo thumbs = new TaskAccessInfo();
// How many different sub-thumbnails?
final int NA = mActivities.size();
@@ -449,6 +486,48 @@ final class TaskRecord extends ThumbnailHolder {
return null;
}
+ /** Updates the last task description values. */
+ void updateTaskDescription() {
+ // Traverse upwards looking for any break between main task activities and
+ // utility activities.
+ int activityNdx;
+ final int numActivities = mActivities.size();
+ for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
+ ++activityNdx) {
+ final ActivityRecord r = mActivities.get(activityNdx);
+ if (r.intent != null &&
+ (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
+ != 0) {
+ break;
+ }
+ }
+ if (activityNdx > 0) {
+ // Traverse downwards starting below break looking for set label, icon.
+ // Note that if there are activities in the task but none of them set the
+ // recent activity values, then we do not fall back to the last set
+ // values in the TaskRecord.
+ String label = null;
+ Bitmap icon = null;
+ int colorPrimary = 0;
+ for (--activityNdx; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = mActivities.get(activityNdx);
+ if (r.taskDescription != null) {
+ if (label == null) {
+ label = r.taskDescription.getLabel();
+ }
+ if (icon == null) {
+ icon = r.taskDescription.getIcon();
+ }
+ if (colorPrimary == 0) {
+ colorPrimary = r.taskDescription.getPrimaryColor();
+
+ }
+ }
+ }
+ lastTaskDescription = new ActivityManager.TaskDescription(label, icon, colorPrimary);
+ }
+ }
+
void dump(PrintWriter pw, String prefix) {
if (numActivities != 0 || rootWasReset || userId != 0 || numFullscreen != 0) {
pw.print(prefix); pw.print("numActivities="); pw.print(numActivities);
@@ -461,6 +540,12 @@ final class TaskRecord extends ThumbnailHolder {
if (affinity != null) {
pw.print(prefix); pw.print("affinity="); pw.println(affinity);
}
+ if (voiceSession != null || voiceInteractor != null) {
+ pw.print(prefix); pw.print("VOICE: session=0x");
+ pw.print(Integer.toHexString(System.identityHashCode(voiceSession)));
+ pw.print(" interactor=0x");
+ pw.println(Integer.toHexString(System.identityHashCode(voiceInteractor)));
+ }
if (intent != null) {
StringBuilder sb = new StringBuilder(128);
sb.append(prefix); sb.append("intent={");
diff --git a/services/core/java/com/android/server/am/UriPermission.java b/services/core/java/com/android/server/am/UriPermission.java
index 1f12b74..284086d 100644
--- a/services/core/java/com/android/server/am/UriPermission.java
+++ b/services/core/java/com/android/server/am/UriPermission.java
@@ -17,15 +17,16 @@
package com.android.server.am;
import android.content.Intent;
-import android.net.Uri;
import android.os.UserHandle;
+import android.util.ArraySet;
import android.util.Log;
+import android.util.Slog;
+import com.android.server.am.ActivityManagerService.GrantUri;
import com.google.android.collect.Sets;
import java.io.PrintWriter;
import java.util.Comparator;
-import java.util.HashSet;
/**
* Description of a permission granted to an app to access a particular URI.
@@ -43,14 +44,14 @@ final class UriPermission {
public static final int STRENGTH_GLOBAL = 2;
public static final int STRENGTH_PERSISTABLE = 3;
- final int userHandle;
+ final int targetUserId;
final String sourcePkg;
final String targetPkg;
/** Cached UID of {@link #targetPkg}; should not be persisted */
final int targetUid;
- final Uri uri;
+ final GrantUri uri;
/**
* Allowed modes. All permission enforcement should use this field. Must
@@ -61,12 +62,13 @@ final class UriPermission {
*/
int modeFlags = 0;
- /** Allowed modes with explicit owner. */
+ /** Allowed modes with active owner. */
int ownedModeFlags = 0;
/** Allowed modes without explicit owner. */
int globalModeFlags = 0;
/** Allowed modes that have been offered for possible persisting. */
int persistableModeFlags = 0;
+
/** Allowed modes that should be persisted across device boots. */
int persistedModeFlags = 0;
@@ -78,13 +80,13 @@ final class UriPermission {
private static final long INVALID_TIME = Long.MIN_VALUE;
- private HashSet<UriPermissionOwner> mReadOwners;
- private HashSet<UriPermissionOwner> mWriteOwners;
+ private ArraySet<UriPermissionOwner> mReadOwners;
+ private ArraySet<UriPermissionOwner> mWriteOwners;
private String stringName;
- UriPermission(String sourcePkg, String targetPkg, int targetUid, Uri uri) {
- this.userHandle = UserHandle.getUserId(targetUid);
+ UriPermission(String sourcePkg, String targetPkg, int targetUid, GrantUri uri) {
+ this.targetUserId = UserHandle.getUserId(targetUid);
this.sourcePkg = sourcePkg;
this.targetPkg = targetPkg;
this.targetUid = targetUid;
@@ -100,6 +102,9 @@ final class UriPermission {
* global or owner grants.
*/
void initPersistedModes(int modeFlags, long createdTime) {
+ modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
persistableModeFlags = modeFlags;
persistedModeFlags = modeFlags;
persistedCreateTime = createdTime;
@@ -107,7 +112,11 @@ final class UriPermission {
updateModeFlags();
}
- void grantModes(int modeFlags, boolean persistable, UriPermissionOwner owner) {
+ void grantModes(int modeFlags, UriPermissionOwner owner) {
+ final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
+ modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
if (persistable) {
persistableModeFlags |= modeFlags;
}
@@ -130,10 +139,14 @@ final class UriPermission {
* @return if mode changes should trigger persisting.
*/
boolean takePersistableModes(int modeFlags) {
+ modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
if ((modeFlags & persistableModeFlags) != modeFlags) {
- throw new SecurityException("Requested flags 0x"
+ Slog.w(TAG, "Requested flags 0x"
+ Integer.toHexString(modeFlags) + ", but only 0x"
+ Integer.toHexString(persistableModeFlags) + " are allowed");
+ return false;
}
final int before = persistedModeFlags;
@@ -148,6 +161,9 @@ final class UriPermission {
}
boolean releasePersistableModes(int modeFlags) {
+ modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
final int before = persistedModeFlags;
persistableModeFlags &= ~modeFlags;
@@ -164,7 +180,11 @@ final class UriPermission {
/**
* @return if mode changes should trigger persisting.
*/
- boolean clearModes(int modeFlags, boolean persistable) {
+ boolean revokeModes(int modeFlags) {
+ final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
+ modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
final int before = persistedModeFlags;
if ((modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
@@ -208,6 +228,8 @@ final class UriPermission {
* Return strength of this permission grant for the given flags.
*/
public int getStrength(int modeFlags) {
+ modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
if ((persistableModeFlags & modeFlags) == modeFlags) {
return STRENGTH_PERSISTABLE;
} else if ((globalModeFlags & modeFlags) == modeFlags) {
@@ -221,7 +243,7 @@ final class UriPermission {
private void addReadOwner(UriPermissionOwner owner) {
if (mReadOwners == null) {
- mReadOwners = Sets.newHashSet();
+ mReadOwners = Sets.newArraySet();
ownedModeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
updateModeFlags();
}
@@ -246,7 +268,7 @@ final class UriPermission {
private void addWriteOwner(UriPermissionOwner owner) {
if (mWriteOwners == null) {
- mWriteOwners = Sets.newHashSet();
+ mWriteOwners = Sets.newArraySet();
ownedModeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
updateModeFlags();
}
@@ -285,7 +307,7 @@ final class UriPermission {
void dump(PrintWriter pw, String prefix) {
pw.print(prefix);
- pw.print("userHandle=" + userHandle);
+ pw.print("targetUserId=" + targetUserId);
pw.print(" sourcePkg=" + sourcePkg);
pw.println(" targetPkg=" + targetPkg);
@@ -330,15 +352,15 @@ final class UriPermission {
* {@link UriPermission#persistedModeFlags} state.
*/
public static class Snapshot {
- final int userHandle;
+ final int targetUserId;
final String sourcePkg;
final String targetPkg;
- final Uri uri;
+ final GrantUri uri;
final int persistedModeFlags;
final long persistedCreateTime;
private Snapshot(UriPermission perm) {
- this.userHandle = perm.userHandle;
+ this.targetUserId = perm.targetUserId;
this.sourcePkg = perm.sourcePkg;
this.targetPkg = perm.targetPkg;
this.uri = perm.uri;
@@ -352,6 +374,6 @@ final class UriPermission {
}
public android.content.UriPermission buildPersistedPublicApiObject() {
- return new android.content.UriPermission(uri, persistedModeFlags, persistedCreateTime);
+ return new android.content.UriPermission(uri.uri, persistedModeFlags, persistedCreateTime);
}
}
diff --git a/services/core/java/com/android/server/am/UriPermissionOwner.java b/services/core/java/com/android/server/am/UriPermissionOwner.java
index 7bbd3bc..ae83940 100644
--- a/services/core/java/com/android/server/am/UriPermissionOwner.java
+++ b/services/core/java/com/android/server/am/UriPermissionOwner.java
@@ -20,8 +20,11 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
+import android.util.ArraySet;
-import java.util.HashSet;
+import com.google.android.collect.Sets;
+
+import java.io.PrintWriter;
import java.util.Iterator;
final class UriPermissionOwner {
@@ -30,8 +33,8 @@ final class UriPermissionOwner {
Binder externalToken;
- HashSet<UriPermission> readUriPermissions; // special access to reading uris.
- HashSet<UriPermission> writeUriPermissions; // special access to writing uris.
+ private ArraySet<UriPermission> mReadPerms;
+ private ArraySet<UriPermission> mWritePerms;
class ExternalToken extends Binder {
UriPermissionOwner getOwner() {
@@ -39,9 +42,9 @@ final class UriPermissionOwner {
}
}
- UriPermissionOwner(ActivityManagerService _service, Object _owner) {
- service = _service;
- owner = _owner;
+ UriPermissionOwner(ActivityManagerService service, Object owner) {
+ this.service = service;
+ this.owner = owner;
}
Binder getExternalTokenLocked() {
@@ -64,82 +67,76 @@ final class UriPermissionOwner {
}
void removeUriPermissionsLocked(int mode) {
- if ((mode&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0
- && readUriPermissions != null) {
- for (UriPermission perm : readUriPermissions) {
- perm.removeReadOwner(this);
- service.removeUriPermissionIfNeededLocked(perm);
- }
- readUriPermissions = null;
- }
- if ((mode&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0
- && writeUriPermissions != null) {
- for (UriPermission perm : writeUriPermissions) {
- perm.removeWriteOwner(this);
- service.removeUriPermissionIfNeededLocked(perm);
- }
- writeUriPermissions = null;
- }
+ removeUriPermissionLocked(null, mode);
}
- void removeUriPermissionLocked(Uri uri, int mode) {
- if ((mode&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0
- && readUriPermissions != null) {
- Iterator<UriPermission> it = readUriPermissions.iterator();
+ void removeUriPermissionLocked(ActivityManagerService.GrantUri grantUri, int mode) {
+ if ((mode & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0
+ && mReadPerms != null) {
+ Iterator<UriPermission> it = mReadPerms.iterator();
while (it.hasNext()) {
UriPermission perm = it.next();
- if (uri.equals(perm.uri)) {
+ if (grantUri == null || grantUri.equals(perm.uri)) {
perm.removeReadOwner(this);
service.removeUriPermissionIfNeededLocked(perm);
it.remove();
}
}
- if (readUriPermissions.size() == 0) {
- readUriPermissions = null;
+ if (mReadPerms.isEmpty()) {
+ mReadPerms = null;
}
}
- if ((mode&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0
- && writeUriPermissions != null) {
- Iterator<UriPermission> it = writeUriPermissions.iterator();
+ if ((mode & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0
+ && mWritePerms != null) {
+ Iterator<UriPermission> it = mWritePerms.iterator();
while (it.hasNext()) {
UriPermission perm = it.next();
- if (uri.equals(perm.uri)) {
+ if (grantUri == null || grantUri.equals(perm.uri)) {
perm.removeWriteOwner(this);
service.removeUriPermissionIfNeededLocked(perm);
it.remove();
}
}
- if (writeUriPermissions.size() == 0) {
- writeUriPermissions = null;
+ if (mWritePerms.isEmpty()) {
+ mWritePerms = null;
}
}
}
public void addReadPermission(UriPermission perm) {
- if (readUriPermissions == null) {
- readUriPermissions = new HashSet<UriPermission>();
+ if (mReadPerms == null) {
+ mReadPerms = Sets.newArraySet();
}
- readUriPermissions.add(perm);
+ mReadPerms.add(perm);
}
public void addWritePermission(UriPermission perm) {
- if (writeUriPermissions == null) {
- writeUriPermissions = new HashSet<UriPermission>();
+ if (mWritePerms == null) {
+ mWritePerms = Sets.newArraySet();
}
- writeUriPermissions.add(perm);
+ mWritePerms.add(perm);
}
public void removeReadPermission(UriPermission perm) {
- readUriPermissions.remove(perm);
- if (readUriPermissions.size() == 0) {
- readUriPermissions = null;
+ mReadPerms.remove(perm);
+ if (mReadPerms.isEmpty()) {
+ mReadPerms = null;
}
}
public void removeWritePermission(UriPermission perm) {
- writeUriPermissions.remove(perm);
- if (writeUriPermissions.size() == 0) {
- writeUriPermissions = null;
+ mWritePerms.remove(perm);
+ if (mWritePerms.isEmpty()) {
+ mWritePerms = null;
+ }
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ if (mReadPerms != null) {
+ pw.print(prefix); pw.print("readUriPermissions="); pw.println(mReadPerms);
+ }
+ if (mWritePerms != null) {
+ pw.print(prefix); pw.print("writeUriPermissions="); pw.println(mWritePerms);
}
}
diff --git a/services/core/java/com/android/server/am/UsageStatsService.java b/services/core/java/com/android/server/am/UsageStatsService.java
index 5305c8f..4a5a554 100644
--- a/services/core/java/com/android/server/am/UsageStatsService.java
+++ b/services/core/java/com/android/server/am/UsageStatsService.java
@@ -17,27 +17,31 @@
package com.android.server.am;
import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.UsageStats;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.os.Binder;
import android.os.IBinder;
import android.os.FileUtils;
import android.os.Parcel;
+import android.os.ParcelableParcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Slog;
+import android.util.TimeUtils;
import android.util.Xml;
import com.android.internal.app.IUsageStats;
import com.android.internal.content.PackageMonitor;
-import com.android.internal.os.PkgUsageStats;
import com.android.internal.util.FastXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
@@ -47,18 +51,14 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -77,7 +77,7 @@ public final class UsageStatsService extends IUsageStats.Stub {
private static final String TAG = "UsageStats";
// Current on-disk Parcel version
- private static final int VERSION = 1008;
+ private static final int VERSION = 1010;
private static final int CHECKIN_VERSION = 4;
@@ -96,13 +96,10 @@ public final class UsageStatsService extends IUsageStats.Stub {
static IUsageStats sService;
private Context mContext;
- // structure used to maintain statistics since the last checkin.
- final private ArrayMap<String, PkgUsageStatsExtended> mStats
- = new ArrayMap<String, PkgUsageStatsExtended>();
+ private AppOpsManager mAppOps;
- // Maintains the last time any component was resumed, for all time.
- final private ArrayMap<String, ArrayMap<String, Long>> mLastResumeTimes
- = new ArrayMap<String, ArrayMap<String, Long>>();
+ // structure used to maintain statistics since the last checkin.
+ private LocalUsageStats mStats = new LocalUsageStats();
// To remove last-resume time stats when a pacakge is removed.
private PackageMonitor mPackageMonitor;
@@ -117,6 +114,7 @@ public final class UsageStatsService extends IUsageStats.Stub {
private String mLastResumedPkg;
private String mLastResumedComp;
private boolean mIsResumed;
+ private ConfigUsageStatsExtended mCurrentConfigStats;
private File mFile;
private AtomicFile mHistoryFile;
private String mFileLeaf;
@@ -129,6 +127,30 @@ public final class UsageStatsService extends IUsageStats.Stub {
private final AtomicLong mLastWriteElapsedTime = new AtomicLong(0);
private final AtomicBoolean mUnforcedDiskWriteRunning = new AtomicBoolean(false);
+ static class LocalUsageStats extends UsageStats {
+ public LocalUsageStats() {
+ }
+ public LocalUsageStats(Parcel in, boolean extended) {
+ super(in, extended);
+ }
+ @Override
+ public PackageStats onNewPackageStats(String pkgName) {
+ return new PkgUsageStatsExtended(pkgName);
+ }
+ @Override
+ public PackageStats onNewPackageStats(Parcel in) {
+ return new PkgUsageStatsExtended(in);
+ }
+ @Override
+ public ConfigurationStats onNewConfigurationStats(Configuration config) {
+ return new ConfigUsageStatsExtended(config);
+ }
+ @Override
+ public ConfigurationStats onNewConfigurationStats(Parcel source) {
+ return new ConfigUsageStatsExtended(source);
+ }
+ }
+
static class TimeStats {
int mCount;
final int[] mTimes = new int[NUM_LAUNCH_TIME_BINS];
@@ -168,27 +190,18 @@ public final class UsageStatsService extends IUsageStats.Stub {
}
}
- static class PkgUsageStatsExtended {
+ static class PkgUsageStatsExtended extends UsageStats.PackageStats {
final ArrayMap<String, TimeStats> mLaunchTimes
= new ArrayMap<String, TimeStats>();
final ArrayMap<String, TimeStats> mFullyDrawnTimes
= new ArrayMap<String, TimeStats>();
- int mLaunchCount;
- long mUsageTime;
- long mPausedTime;
- long mResumedTime;
- PkgUsageStatsExtended() {
- mLaunchCount = 0;
- mUsageTime = 0;
+ PkgUsageStatsExtended(String pkgName) {
+ super(pkgName);
}
PkgUsageStatsExtended(Parcel in) {
- mLaunchCount = in.readInt();
- mUsageTime = in.readLong();
- if (localLOGV) Slog.v(TAG, "Launch count: " + mLaunchCount
- + ", Usage time:" + mUsageTime);
-
+ super(in);
final int numLaunchTimeStats = in.readInt();
if (localLOGV) Slog.v(TAG, "Reading launch times: " + numLaunchTimeStats);
mLaunchTimes.ensureCapacity(numLaunchTimeStats);
@@ -210,18 +223,6 @@ public final class UsageStatsService extends IUsageStats.Stub {
}
}
- void updateResume(String comp, boolean launched) {
- if (launched) {
- mLaunchCount++;
- }
- mResumedTime = SystemClock.elapsedRealtime();
- }
-
- void updatePause() {
- mPausedTime = SystemClock.elapsedRealtime();
- mUsageTime += (mPausedTime - mResumedTime);
- }
-
void addLaunchCount(String comp) {
TimeStats times = mLaunchTimes.get(comp);
if (times == null) {
@@ -249,9 +250,7 @@ public final class UsageStatsService extends IUsageStats.Stub {
times.add(millis);
}
- void writeToParcel(Parcel out) {
- out.writeInt(mLaunchCount);
- out.writeLong(mUsageTime);
+ public void writeExtendedToParcel(Parcel out, int parcelableFlags) {
final int numLaunchTimeStats = mLaunchTimes.size();
out.writeInt(numLaunchTimeStats);
for (int i=0; i<numLaunchTimeStats; i++) {
@@ -266,11 +265,21 @@ public final class UsageStatsService extends IUsageStats.Stub {
}
}
- void clear() {
+ @Override
+ public boolean clearUsageTimes() {
mLaunchTimes.clear();
mFullyDrawnTimes.clear();
- mLaunchCount = 0;
- mUsageTime = 0;
+ return super.clearUsageTimes();
+ }
+ }
+
+ static class ConfigUsageStatsExtended extends UsageStats.ConfigurationStats {
+ ConfigUsageStatsExtended(Configuration config) {
+ super(config);
+ }
+
+ ConfigUsageStatsExtended(Parcel in) {
+ super(in);
}
}
@@ -366,18 +375,9 @@ public final class UsageStatsService extends IUsageStats.Stub {
+ VERSION + "; dropping");
return;
}
- int N = in.readInt();
- while (N > 0) {
- N--;
- String pkgName = in.readString();
- if (pkgName == null) {
- break;
- }
- if (localLOGV) Slog.v(TAG, "Reading package #" + N + ": " + pkgName);
- PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
- synchronized (mStatsLock) {
- mStats.put(pkgName, pus);
- }
+ LocalUsageStats stats = new LocalUsageStats(in, true);
+ synchronized (mStatsLock) {
+ mStats = stats;
}
}
@@ -421,12 +421,9 @@ public final class UsageStatsService extends IUsageStats.Stub {
try {
long lastResumeTime = Long.parseLong(lastResumeTimeStr);
synchronized (mStatsLock) {
- ArrayMap<String, Long> lrt = mLastResumeTimes.get(pkg);
- if (lrt == null) {
- lrt = new ArrayMap<String, Long>();
- mLastResumeTimes.put(pkg, lrt);
- }
- lrt.put(comp, lastResumeTime);
+ PkgUsageStatsExtended pus = (PkgUsageStatsExtended)
+ mStats.getOrCreatePackageStats(pkg);
+ pus.componentResumeTimes.put(comp, lastResumeTime);
}
} catch (NumberFormatException e) {
}
@@ -545,6 +542,15 @@ public final class UsageStatsService extends IUsageStats.Stub {
return;
}
+ Parcel out = Parcel.obtain();
+ synchronized (mStatsLock) {
+ out.writeInt(VERSION);
+ mStats.writeExtendedToParcel(out, 0);
+ if (dayChanged) {
+ mStats.clearUsageTimes();
+ }
+ }
+
synchronized (mFileLock) {
// Get the most recent file
mFileLeaf = getCurrentDateStr(FILE_PREFIX);
@@ -555,6 +561,7 @@ public final class UsageStatsService extends IUsageStats.Stub {
if (!backupFile.exists()) {
if (!mFile.renameTo(backupFile)) {
Slog.w(TAG, "Failed to persist new stats");
+ out.recycle();
return;
}
} else {
@@ -564,14 +571,10 @@ public final class UsageStatsService extends IUsageStats.Stub {
try {
// Write mStats to file
- writeStatsFLOCK(mFile);
+ writeStatsFLOCK(mFile, out);
mLastWriteElapsedTime.set(currElapsedTime);
if (dayChanged) {
mLastWriteDay.set(curDay);
- // clear stats
- synchronized (mStats) {
- mStats.clear();
- }
mFile = new File(mDir, mFileLeaf);
checkFileLimitFLOCK();
}
@@ -592,17 +595,15 @@ public final class UsageStatsService extends IUsageStats.Stub {
backupFile.renameTo(mFile);
}
}
+ out.recycle();
}
if (localLOGV) Slog.d(TAG, "Dumped usage stats.");
}
- private void writeStatsFLOCK(File file) throws IOException {
+ private void writeStatsFLOCK(File file, Parcel parcel) throws IOException {
FileOutputStream stream = new FileOutputStream(file);
try {
- Parcel out = Parcel.obtain();
- writeStatsToParcelFLOCK(out);
- stream.write(out.marshall());
- out.recycle();
+ stream.write(parcel.marshall());
stream.flush();
} finally {
FileUtils.sync(stream);
@@ -610,29 +611,14 @@ public final class UsageStatsService extends IUsageStats.Stub {
}
}
- private void writeStatsToParcelFLOCK(Parcel out) {
- synchronized (mStatsLock) {
- out.writeInt(VERSION);
- Set<String> keys = mStats.keySet();
- out.writeInt(keys.size());
- for (String key : keys) {
- PkgUsageStatsExtended pus = mStats.get(key);
- out.writeString(key);
- pus.writeToParcel(out);
- }
- }
- }
-
/** Filter out stats for any packages which aren't present anymore. */
private void filterHistoryStats() {
synchronized (mStatsLock) {
IPackageManager pm = AppGlobals.getPackageManager();
- for (int i=0; i<mLastResumeTimes.size(); i++) {
- String pkg = mLastResumeTimes.keyAt(i);
+ for (int i=mStats.mPackages.size()-1; i>=0; i--) {
try {
- if (pm.getPackageUid(pkg, 0) < 0) {
- mLastResumeTimes.removeAt(i);
- i--;
+ if (pm.getPackageUid(mStats.mPackages.valueAt(i).getPackageName(), 0) < 0) {
+ mStats.mPackages.removeAt(i);
}
} catch (RemoteException e) {
}
@@ -650,10 +636,12 @@ public final class UsageStatsService extends IUsageStats.Stub {
out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
out.startTag(null, "usage-history");
synchronized (mStatsLock) {
- for (int i=0; i<mLastResumeTimes.size(); i++) {
+ int NP = mStats.mPackages.size();
+ for (int i=0; i<NP; i++) {
+ UsageStats.PackageStats ps = mStats.mPackages.valueAt(i);
out.startTag(null, "pkg");
- out.attribute(null, "name", mLastResumeTimes.keyAt(i));
- ArrayMap<String, Long> comp = mLastResumeTimes.valueAt(i);
+ out.attribute(null, "name", ps.getPackageName());
+ ArrayMap<String, Long> comp = ps.componentResumeTimes;
for (int j=0; j<comp.size(); j++) {
out.startTag(null, "comp");
out.attribute(null, "name", comp.keyAt(j));
@@ -680,6 +668,10 @@ public final class UsageStatsService extends IUsageStats.Stub {
ServiceManager.addService(SERVICE_NAME, asBinder());
}
+ public void systemReady() {
+ mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
+ }
+
/**
* Start watching packages to remove stats when a package is uninstalled.
* May only be called when the package manager is ready.
@@ -689,7 +681,7 @@ public final class UsageStatsService extends IUsageStats.Stub {
@Override
public void onPackageRemovedAllUsers(String packageName, int uid) {
synchronized (mStatsLock) {
- mLastResumeTimes.remove(packageName);
+ mStats.mPackages.remove(packageName);
}
}
};
@@ -731,9 +723,10 @@ public final class UsageStatsService extends IUsageStats.Stub {
// to recover.
if (REPORT_UNEXPECTED) Slog.i(TAG, "Unexpected resume of " + pkgName
+ " while already resumed in " + mLastResumedPkg);
- PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg);
+ PkgUsageStatsExtended pus = (PkgUsageStatsExtended)mStats.getPackageStats(
+ mLastResumedPkg);
if (pus != null) {
- pus.updatePause();
+ pus.pause();
}
}
}
@@ -746,22 +739,13 @@ public final class UsageStatsService extends IUsageStats.Stub {
mLastResumedComp = componentName.getClassName();
if (localLOGV) Slog.i(TAG, "started component:" + pkgName);
- PkgUsageStatsExtended pus = mStats.get(pkgName);
- if (pus == null) {
- pus = new PkgUsageStatsExtended();
- mStats.put(pkgName, pus);
- }
- pus.updateResume(mLastResumedComp, !samePackage);
+ PkgUsageStatsExtended pus = (PkgUsageStatsExtended)
+ mStats.getOrCreatePackageStats(pkgName);
+ pus.resume(!samePackage);
if (!sameComp) {
pus.addLaunchCount(mLastResumedComp);
}
-
- ArrayMap<String, Long> componentResumeTimes = mLastResumeTimes.get(pkgName);
- if (componentResumeTimes == null) {
- componentResumeTimes = new ArrayMap<String, Long>();
- mLastResumeTimes.put(pkgName, componentResumeTimes);
- }
- componentResumeTimes.put(mLastResumedComp, System.currentTimeMillis());
+ pus.componentResumeTimes.put(mLastResumedComp, System.currentTimeMillis());
}
}
@@ -784,13 +768,13 @@ public final class UsageStatsService extends IUsageStats.Stub {
if (localLOGV) Slog.i(TAG, "paused component:"+pkgName);
- PkgUsageStatsExtended pus = mStats.get(pkgName);
+ PkgUsageStatsExtended pus = (PkgUsageStatsExtended)mStats.getPackageStats(pkgName);
if (pus == null) {
// Weird some error here
Slog.i(TAG, "No package stats for pkg:"+pkgName);
return;
}
- pus.updatePause();
+ pus.pause();
}
// Persist current data to file if needed.
@@ -810,7 +794,7 @@ public final class UsageStatsService extends IUsageStats.Stub {
writeStatsToFile(false, false);
synchronized (mStatsLock) {
- PkgUsageStatsExtended pus = mStats.get(pkgName);
+ PkgUsageStatsExtended pus = (PkgUsageStatsExtended)mStats.getPackageStats(pkgName);
if (pus != null) {
pus.addLaunchTime(componentName.getClassName(), millis);
}
@@ -829,13 +813,29 @@ public final class UsageStatsService extends IUsageStats.Stub {
writeStatsToFile(false, false);
synchronized (mStatsLock) {
- PkgUsageStatsExtended pus = mStats.get(pkgName);
+ PkgUsageStatsExtended pus = (PkgUsageStatsExtended)mStats.getPackageStats(pkgName);
if (pus != null) {
pus.addFullyDrawnTime(componentName.getClassName(), millis);
}
}
}
+ public void noteStartConfig(Configuration config) {
+ enforceCallingPermission();
+ synchronized (mStatsLock) {
+ config = new Configuration(config);
+ ConfigUsageStatsExtended cus = (ConfigUsageStatsExtended)
+ mStats.getOrCreateConfigurationStats(config);
+ if (cus != mCurrentConfigStats) {
+ if (mCurrentConfigStats != null) {
+ mCurrentConfigStats.stop();
+ }
+ cus.start();
+ mCurrentConfigStats = cus;
+ }
+ }
+ }
+
public void enforceCallingPermission() {
if (Binder.getCallingPid() == Process.myPid()) {
return;
@@ -845,53 +845,71 @@ public final class UsageStatsService extends IUsageStats.Stub {
}
@Override
- public PkgUsageStats getPkgUsageStats(ComponentName componentName) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.PACKAGE_USAGE_STATS, null);
+ public UsageStats.PackageStats getPkgUsageStats(String callingPkg,
+ ComponentName componentName) {
+ checkCallerPermission(callingPkg, "getPkgUsageStats");
String pkgName;
if ((componentName == null) ||
((pkgName = componentName.getPackageName()) == null)) {
return null;
}
synchronized (mStatsLock) {
- PkgUsageStatsExtended pus = mStats.get(pkgName);
- Map<String, Long> lastResumeTimes = mLastResumeTimes.get(pkgName);
- if (pus == null && lastResumeTimes == null) {
+ PkgUsageStatsExtended pus = (PkgUsageStatsExtended)mStats.getPackageStats(pkgName);
+ if (pus == null) {
return null;
}
- int launchCount = pus != null ? pus.mLaunchCount : 0;
- long usageTime = pus != null ? pus.mUsageTime : 0;
- return new PkgUsageStats(pkgName, launchCount, usageTime, lastResumeTimes);
+ return new UsageStats.PackageStats(pus);
}
}
@Override
- public PkgUsageStats[] getAllPkgUsageStats() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.PACKAGE_USAGE_STATS, null);
+ public UsageStats.PackageStats[] getAllPkgUsageStats(String callingPkg) {
+ checkCallerPermission(callingPkg, "getAllPkgUsageStats");
synchronized (mStatsLock) {
- int size = mLastResumeTimes.size();
- if (size <= 0) {
+ int NP = mStats.mPackages.size();
+ if (NP <= 0) {
return null;
}
- PkgUsageStats retArr[] = new PkgUsageStats[size];
- for (int i=0; i<size; i++) {
- String pkg = mLastResumeTimes.keyAt(i);
- long usageTime = 0;
- int launchCount = 0;
-
- PkgUsageStatsExtended pus = mStats.get(pkg);
- if (pus != null) {
- usageTime = pus.mUsageTime;
- launchCount = pus.mLaunchCount;
- }
- retArr[i] = new PkgUsageStats(pkg, launchCount, usageTime,
- mLastResumeTimes.valueAt(i));
+ UsageStats.PackageStats retArr[] = new UsageStats.PackageStats[NP];
+ for (int p=0; p<NP; p++) {
+ UsageStats.PackageStats ps = mStats.mPackages.valueAt(p);
+ retArr[p] = new UsageStats.PackageStats(ps);
}
return retArr;
}
}
+ @Override
+ public ParcelableParcel getCurrentStats(String callingPkg) {
+ checkCallerPermission(callingPkg, "getCurrentStats");
+ synchronized (mStatsLock) {
+ ParcelableParcel out = new ParcelableParcel(null);
+ mStats.writeToParcel(out.getParcel(), 0);
+ return out;
+ }
+ }
+
+ private void checkCallerPermission(String callingPkg, String callingOp) {
+ // Because the permission for this is system-only, its use with
+ // app ops is a little different: the op is disabled by default,
+ // and enabling it allows apps to get access even if they don't
+ // hold the permission.
+ int mode = mAppOps.noteOpNoThrow(AppOpsManager.OP_GET_USAGE_STATS, Binder.getCallingUid(),
+ callingPkg);
+ if (mode == AppOpsManager.MODE_ALLOWED) {
+ return;
+ } else if (mode != AppOpsManager.MODE_IGNORED) {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.PACKAGE_USAGE_STATS)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+
+ String msg = "Package " + callingPkg + " not allowed to call " + callingOp;
+ throw new SecurityException(msg);
+ }
+
static byte[] readFully(FileInputStream stream) throws IOException {
int pos = 0;
int avail = stream.available();
@@ -965,31 +983,28 @@ public final class UsageStatsService extends IUsageStats.Stub {
return;
}
- pw.println(sb.toString());
- int N = in.readInt();
+ final LocalUsageStats stats = new LocalUsageStats(in, true);
+ final long time = SystemClock.elapsedRealtime();
- while (N > 0) {
- N--;
- String pkgName = in.readString();
- if (pkgName == null) {
- break;
- }
+ pw.println(sb.toString());
+ int NP = stats.mPackages.size();
+ for (int p=0; p<NP; p++) {
+ PkgUsageStatsExtended pus = (PkgUsageStatsExtended)stats.mPackages.valueAt(p);
sb.setLength(0);
- PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
- if (packages != null && !packages.contains(pkgName)) {
+ if (packages != null && !packages.contains(pus.getPackageName())) {
// This package has not been requested -- don't print
// anything for it.
} else if (isCompactOutput) {
sb.append("P:");
- sb.append(pkgName);
+ sb.append(pus.getPackageName());
sb.append(',');
- sb.append(pus.mLaunchCount);
+ sb.append(pus.getLaunchCount());
sb.append(',');
- sb.append(pus.mUsageTime);
+ sb.append(pus.getUsageTime(time));
sb.append('\n');
final int NLT = pus.mLaunchTimes.size();
for (int i=0; i<NLT; i++) {
- sb.append("A:");
+ sb.append("L:");
String activity = pus.mLaunchTimes.keyAt(i);
sb.append(activity);
TimeStats times = pus.mLaunchTimes.valueAt(i);
@@ -1003,7 +1018,7 @@ public final class UsageStatsService extends IUsageStats.Stub {
}
final int NFDT = pus.mFullyDrawnTimes.size();
for (int i=0; i<NFDT; i++) {
- sb.append("A:");
+ sb.append("D:");
String activity = pus.mFullyDrawnTimes.keyAt(i);
sb.append(activity);
TimeStats times = pus.mFullyDrawnTimes.valueAt(i);
@@ -1013,15 +1028,23 @@ public final class UsageStatsService extends IUsageStats.Stub {
}
sb.append('\n');
}
+ final int NC = pus.componentResumeTimes.size();
+ for (int c=0; c<NC; c++) {
+ pw.print("R:"); pw.print(pus.componentResumeTimes.keyAt(c)); pw.print(",");
+ pw.println(pus.componentResumeTimes.valueAt(c));
+ }
} else {
sb.append(" ");
- sb.append(pkgName);
- sb.append(": ");
- sb.append(pus.mLaunchCount);
- sb.append(" times, ");
- sb.append(pus.mUsageTime);
- sb.append(" ms");
+ sb.append(pus.getPackageName());
+ if (pus.getLaunchCount() != 0 || pus.getUsageTime(time) != 0) {
+ sb.append(": ");
+ sb.append(pus.getLaunchCount());
+ sb.append(" times, ");
+ TimeUtils.formatDuration(pus.getUsageTime(time), sb);
+ } else {
+ sb.append(":");
+ }
sb.append('\n');
final int NLT = pus.mLaunchTimes.size();
for (int i=0; i<NLT; i++) {
@@ -1086,10 +1109,50 @@ public final class UsageStatsService extends IUsageStats.Stub {
}
sb.append('\n');
}
+ final int NC = pus.componentResumeTimes.size();
+ for (int c=0; c<NC; c++) {
+ sb.append(" ");
+ sb.append(pus.componentResumeTimes.keyAt(c));
+ sb.append(" last resumed ");
+ sb.append(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
+ pus.componentResumeTimes.valueAt(c)).toString());
+ sb.append('\n');
+ }
}
pw.write(sb.toString());
}
+ if (packages == null) {
+ int NC = stats.mConfigurations.size();
+ for (int c=0; c<NC; c++) {
+ ConfigUsageStatsExtended cus
+ = (ConfigUsageStatsExtended)stats.mConfigurations.valueAt(c);
+ sb.setLength(0);
+ if (isCompactOutput) {
+ sb.append("C:"); sb.append(cus.getConfiguration().toString());
+ sb.append(","); sb.append(cus.getUsageCount()); sb.append(",");
+ sb.append(cus.getUsageTime(time));
+ } else {
+ sb.append(" ");
+ sb.append(cus.getConfiguration().toString());
+ sb.append(":\n");
+ if (cus.getUsageCount() != 0 || cus.getUsageTime(time) != 0) {
+ sb.append(" Used ");
+ sb.append(cus.getUsageCount());
+ sb.append(" times, ");
+ TimeUtils.formatDuration(cus.getUsageTime(time), sb);
+ sb.append("\n");
+ }
+ if (cus.getLastUsedTime() > 0) {
+ sb.append(" Last used: ");
+ sb.append(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
+ cus.getLastUsedTime()).toString());
+ sb.append("\n");
+ }
+ }
+ pw.write(sb.toString());
+ }
+ }
}
/**
@@ -1176,5 +1239,4 @@ public final class UsageStatsService extends IUsageStats.Stub {
collectDumpInfoFLOCK(pw, isCompactOutput, deleteAfterPrint, packages);
}
}
-
}
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 6aa596d..15e3e89 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -23,6 +23,7 @@ import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ClipDescription;
+import android.content.ContentProvider;
import android.content.IClipboard;
import android.content.IOnPrimaryClipChangedListener;
import android.content.Context;
@@ -31,7 +32,6 @@ import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
@@ -40,7 +40,6 @@ import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -257,7 +256,8 @@ public class ClipboardService extends IClipboard.Stub {
long ident = Binder.clearCallingIdentity();
try {
// This will throw SecurityException for us.
- mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ mAm.checkGrantUriPermission(uid, null, ContentProvider.getUriWithoutUserId(uri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION, resolveUserId(uri, uid));
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(ident);
@@ -284,8 +284,10 @@ public class ClipboardService extends IClipboard.Stub {
private final void grantUriLocked(Uri uri, String pkg) {
long ident = Binder.clearCallingIdentity();
try {
- mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg,
+ ContentProvider.getUriWithoutUserId(uri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ resolveUserId(uri, Process.myUid()));
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(ident);
@@ -333,9 +335,10 @@ public class ClipboardService extends IClipboard.Stub {
private final void revokeUriLocked(Uri uri) {
long ident = Binder.clearCallingIdentity();
try {
- mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
- Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ mAm.revokeUriPermissionFromOwner(mPermissionOwner,
+ ContentProvider.getUriWithoutUserId(uri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ resolveUserId(uri, Process.myUid()));
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(ident);
@@ -363,4 +366,8 @@ public class ClipboardService extends IClipboard.Stub {
revokeItemLocked(clipboard.primaryClip.getItemAt(i));
}
}
+
+ private final int resolveUserId(Uri uri, int uid) {
+ return ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(uid));
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index a15d678..096ab66 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -25,11 +25,12 @@ import android.net.IConnectivityManager;
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
import android.net.LinkProperties;
-import android.net.NetworkStateTracker;
+import android.net.NetworkAgent;
import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.os.Handler;
import android.os.Message;
+import android.os.Messenger;
import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.util.Slog;
@@ -45,15 +46,18 @@ public class Nat464Xlat extends BaseNetworkObserver {
private Context mContext;
private INetworkManagementService mNMService;
private IConnectivityManager mConnService;
- private NetworkStateTracker mTracker;
- private Handler mHandler;
-
// Whether we started clatd and expect it to be running.
private boolean mIsStarted;
// Whether the clatd interface exists (i.e., clatd is running).
private boolean mIsRunning;
// The LinkProperties of the clat interface.
private LinkProperties mLP;
+ // Current LinkProperties of the network. Includes mLP as a stacked link when clat is active.
+ private LinkProperties mBaseLP;
+ // ConnectivityService Handler for LinkProperties updates.
+ private Handler mHandler;
+ // Marker to connote which network we're augmenting.
+ private Messenger mNetworkMessenger;
// This must match the interface name in clatd.conf.
private static final String CLAT_INTERFACE_NAME = "clat4";
@@ -70,17 +74,24 @@ public class Nat464Xlat extends BaseNetworkObserver {
mIsStarted = false;
mIsRunning = false;
mLP = new LinkProperties();
+
+ // If this is a runtime restart, it's possible that clatd is already
+ // running, but we don't know about it. If so, stop it.
+ try {
+ if (mNMService.isClatdStarted()) {
+ mNMService.stopClatd();
+ }
+ } catch(RemoteException e) {} // Well, we tried.
}
/**
- * Determines whether an interface requires clat.
- * @param netType the network type (one of the
- * android.net.ConnectivityManager.TYPE_* constants)
- * @param tracker the NetworkStateTracker corresponding to the network type.
- * @return true if the interface requires clat, false otherwise.
+ * Determines whether a network requires clat.
+ * @param network the NetworkAgentInfo corresponding to the network.
+ * @return true if the network requires clat, false otherwise.
*/
- public boolean requiresClat(int netType, NetworkStateTracker tracker) {
- LinkProperties lp = tracker.getLinkProperties();
+ public boolean requiresClat(NetworkAgentInfo network) {
+ int netType = network.networkInfo.getType();
+ LinkProperties lp = network.linkProperties;
// Only support clat on mobile for now.
Slog.d(TAG, "requiresClat: netType=" + netType + ", hasIPv4Address=" +
lp.hasIPv4Address());
@@ -95,13 +106,18 @@ public class Nat464Xlat extends BaseNetworkObserver {
* Starts the clat daemon.
* @param lp The link properties of the interface to start clatd on.
*/
- public void startClat(NetworkStateTracker tracker) {
+ public void startClat(NetworkAgentInfo network) {
+ if (mNetworkMessenger != null && mNetworkMessenger != network.messenger) {
+ Slog.e(TAG, "startClat: too many networks requesting clat");
+ return;
+ }
+ mNetworkMessenger = network.messenger;
+ LinkProperties lp = network.linkProperties;
+ mBaseLP = new LinkProperties(lp);
if (mIsStarted) {
Slog.e(TAG, "startClat: already started");
return;
}
- mTracker = tracker;
- LinkProperties lp = mTracker.getLinkProperties();
String iface = lp.getInterfaceName();
Slog.i(TAG, "Starting clatd on " + iface + ", lp=" + lp);
try {
@@ -125,7 +141,8 @@ public class Nat464Xlat extends BaseNetworkObserver {
}
mIsStarted = false;
mIsRunning = false;
- mTracker = null;
+ mNetworkMessenger = null;
+ mBaseLP = null;
mLP.clear();
} else {
Slog.e(TAG, "stopClat: already stopped");
@@ -140,6 +157,14 @@ public class Nat464Xlat extends BaseNetworkObserver {
return mIsRunning;
}
+ private void updateConnectivityService() {
+ Message msg = mHandler.obtainMessage(
+ NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED, mBaseLP);
+ msg.replyTo = mNetworkMessenger;
+ Slog.i(TAG, "sending message to ConnectivityService: " + msg);
+ msg.sendToTarget();
+ }
+
@Override
public void interfaceAdded(String iface) {
if (iface.equals(CLAT_INTERFACE_NAME)) {
@@ -165,19 +190,12 @@ public class Nat464Xlat extends BaseNetworkObserver {
clatAddress.getAddress(), iface);
mLP.addRoute(ipv4Default);
mLP.addLinkAddress(clatAddress);
- mTracker.addStackedLink(mLP);
- Slog.i(TAG, "Adding stacked link. tracker LP: " +
- mTracker.getLinkProperties());
+ mBaseLP.addStackedLink(mLP);
+ Slog.i(TAG, "Adding stacked link. tracker LP: " + mBaseLP);
+ updateConnectivityService();
} catch(RemoteException e) {
Slog.e(TAG, "Error getting link properties: " + e);
}
-
- // Inform ConnectivityService that things have changed.
- Message msg = mHandler.obtainMessage(
- NetworkStateTracker.EVENT_CONFIGURATION_CHANGED,
- mTracker.getNetworkInfo());
- Slog.i(TAG, "sending message to ConnectivityService: " + msg);
- msg.sendToTarget();
}
}
@@ -188,11 +206,12 @@ public class Nat464Xlat extends BaseNetworkObserver {
NetworkUtils.resetConnections(
CLAT_INTERFACE_NAME,
NetworkUtils.RESET_IPV4_ADDRESSES);
+ mBaseLP.removeStackedLink(mLP);
+ updateConnectivityService();
}
Slog.i(TAG, "interface " + CLAT_INTERFACE_NAME +
" removed, mIsRunning = " + mIsRunning + " -> false");
mIsRunning = false;
- mTracker.removeStackedLink(mLP);
mLP.clear();
Slog.i(TAG, "mLP = " + mLP);
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
new file mode 100644
index 0000000..8102591
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.content.Context;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.os.Handler;
+import android.os.Messenger;
+import android.util.SparseArray;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.server.connectivity.NetworkMonitor;
+
+import java.util.ArrayList;
+
+/**
+ * A bag class used by ConnectivityService for holding a collection of most recent
+ * information published by a particular NetworkAgent as well as the
+ * AsyncChannel/messenger for reaching that NetworkAgent and lists of NetworkRequests
+ * interested in using it.
+ */
+public class NetworkAgentInfo {
+ public NetworkInfo networkInfo;
+ public final Network network;
+ public LinkProperties linkProperties;
+ public NetworkCapabilities networkCapabilities;
+ public int currentScore;
+ public final NetworkMonitor networkMonitor;
+
+
+ // The list of NetworkRequests being satisfied by this Network.
+ public final SparseArray<NetworkRequest> networkRequests = new SparseArray<NetworkRequest>();
+ public final ArrayList<NetworkRequest> networkLingered = new ArrayList<NetworkRequest>();
+
+ public final Messenger messenger;
+ public final AsyncChannel asyncChannel;
+
+ public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, int netId, NetworkInfo info,
+ LinkProperties lp, NetworkCapabilities nc, int score, Context context,
+ Handler handler) {
+ this.messenger = messenger;
+ asyncChannel = ac;
+ network = new Network(netId);
+ networkInfo = info;
+ linkProperties = lp;
+ networkCapabilities = nc;
+ currentScore = score;
+ networkMonitor = new NetworkMonitor(context, handler, this);
+ }
+
+ public String toString() {
+ return "NetworkAgentInfo{ ni{" + networkInfo + "} network{" +
+ network + "} lp{" +
+ linkProperties + "} nc{" +
+ networkCapabilities + "} Score{" + currentScore + "} }";
+ }
+
+ public String name() {
+ return "NetworkAgentInfo [" + networkInfo.getTypeName() + " (" +
+ networkInfo.getSubtypeName() + ")]";
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
new file mode 100644
index 0000000..47789b1
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.provider.Settings;
+
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.connectivity.NetworkAgentInfo;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.URL;
+
+/**
+ * {@hide}
+ */
+public class NetworkMonitor extends StateMachine {
+ private static final boolean DBG = true;
+ private static final String TAG = "NetworkMonitor";
+ private static final String DEFAULT_SERVER = "clients3.google.com";
+ private static final int SOCKET_TIMEOUT_MS = 10000;
+
+ private static final int BASE = Protocol.BASE_NETWORK_MONITOR;
+
+ /**
+ * Inform NetworkMonitor that their network is connected.
+ * Initiates Network Validation.
+ */
+ public static final int CMD_NETWORK_CONNECTED = BASE + 1;
+
+ /**
+ * Inform ConnectivityService that the network is validated.
+ * obj = NetworkAgentInfo
+ */
+ public static final int EVENT_NETWORK_VALIDATED = BASE + 2;
+
+ /**
+ * Inform NetworkMonitor to linger a network. The Monitor should
+ * start a timer and/or start watching for zero live connections while
+ * moving towards LINGER_COMPLETE. After the Linger period expires
+ * (or other events mark the end of the linger state) the LINGER_COMPLETE
+ * event should be sent and the network will be shut down. If a
+ * CMD_NETWORK_CONNECTED happens before the LINGER completes
+ * it indicates further desire to keep the network alive and so
+ * the LINGER is aborted.
+ */
+ public static final int CMD_NETWORK_LINGER = BASE + 3;
+
+ /**
+ * Message to self indicating linger delay has expired.
+ * arg1 = Token to ignore old messages.
+ */
+ private static final int CMD_LINGER_EXPIRED = BASE + 4;
+
+ /**
+ * Inform ConnectivityService that the network LINGER period has
+ * expired.
+ * obj = NetworkAgentInfo
+ */
+ public static final int EVENT_NETWORK_LINGER_COMPLETE = BASE + 5;
+
+ /**
+ * Message to self indicating it's time to check for a captive portal again.
+ * TODO - Remove this once broadcast intents are used to communicate with
+ * apps to log into captive portals.
+ * arg1 = Token to ignore old messages.
+ */
+ private static final int CMD_CAPTIVE_PORTAL_REEVALUATE = BASE + 6;
+
+ /**
+ * Message to self indicating it's time to evaluate a network's connectivity.
+ * arg1 = Token to ignore old messages.
+ */
+ private static final int CMD_REEVALUATE = BASE + 7;
+
+ /**
+ * Message to self indicating network evaluation is complete.
+ * arg1 = Token to ignore old messages.
+ * arg2 = HTTP response code of network evaluation.
+ */
+ private static final int EVENT_REEVALUATION_COMPLETE = BASE + 8;
+
+ /**
+ * Inform NetworkMonitor that the network has disconnected.
+ */
+ public static final int CMD_NETWORK_DISCONNECTED = BASE + 9;
+
+ /**
+ * Force evaluation even if it has succeeded in the past.
+ */
+ public static final int CMD_FORCE_REEVALUATION = BASE + 10;
+
+ private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
+ // Default to 30s linger time-out.
+ private static final int DEFAULT_LINGER_DELAY_MS = 30000;
+ private final int mLingerDelayMs;
+ private int mLingerToken = 0;
+
+ private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 5000;
+ private int mCaptivePortalReevaluateToken = 0;
+
+ // Negative values disable reevaluation.
+ private static final String REEVALUATE_DELAY_PROPERTY = "persist.netmon.reeval_delay";
+ // Default to 5s reevaluation delay.
+ private static final int DEFAULT_REEVALUATE_DELAY_MS = 5000;
+ private final int mReevaluateDelayMs;
+ private int mReevaluateToken = 0;
+
+ private final Context mContext;
+ private final Handler mConnectivityServiceHandler;
+ private final NetworkAgentInfo mNetworkAgentInfo;
+
+ private String mServer;
+ private boolean mIsCaptivePortalCheckEnabled = false;
+
+ private State mDefaultState = new DefaultState();
+ private State mOfflineState = new OfflineState();
+ private State mValidatedState = new ValidatedState();
+ private State mEvaluatingState = new EvaluatingState();
+ private State mCaptivePortalState = new CaptivePortalState();
+ private State mLingeringState = new LingeringState();
+
+ public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo) {
+ // Add suffix indicating which NetworkMonitor we're talking about.
+ super(TAG + networkAgentInfo.name());
+
+ mContext = context;
+ mConnectivityServiceHandler = handler;
+ mNetworkAgentInfo = networkAgentInfo;
+
+ addState(mDefaultState);
+ addState(mOfflineState, mDefaultState);
+ addState(mValidatedState, mDefaultState);
+ addState(mEvaluatingState, mDefaultState);
+ addState(mCaptivePortalState, mDefaultState);
+ addState(mLingeringState, mDefaultState);
+ setInitialState(mOfflineState);
+
+ mServer = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.CAPTIVE_PORTAL_SERVER);
+ if (mServer == null) mServer = DEFAULT_SERVER;
+
+ mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
+ mReevaluateDelayMs = SystemProperties.getInt(REEVALUATE_DELAY_PROPERTY,
+ DEFAULT_REEVALUATE_DELAY_MS);
+
+ // TODO: Enable this when we're ready.
+ // mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+ // Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
+
+ start();
+ }
+
+ private class DefaultState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) log(getName() + message.toString());
+ switch (message.what) {
+ case CMD_NETWORK_LINGER:
+ if (DBG) log("Lingering");
+ transitionTo(mLingeringState);
+ break;
+ case CMD_NETWORK_CONNECTED:
+ if (DBG) log("Connected");
+ transitionTo(mEvaluatingState);
+ break;
+ case CMD_NETWORK_DISCONNECTED:
+ if (DBG) log("Disconnected");
+ transitionTo(mOfflineState);
+ break;
+ case CMD_FORCE_REEVALUATION:
+ if (DBG) log("Forcing reevaluation");
+ transitionTo(mEvaluatingState);
+ break;
+ default:
+ break;
+ }
+ return HANDLED;
+ }
+ }
+
+ private class OfflineState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) log(getName() + message.toString());
+ return NOT_HANDLED;
+ }
+ }
+
+ private class ValidatedState extends State {
+ @Override
+ public void enter() {
+ if (DBG) log("Validated");
+ mConnectivityServiceHandler.sendMessage(
+ obtainMessage(EVENT_NETWORK_VALIDATED, mNetworkAgentInfo));
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) log(getName() + message.toString());
+ switch (message.what) {
+ case CMD_NETWORK_CONNECTED:
+ transitionTo(mValidatedState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ private class EvaluatingState extends State {
+ private class EvaluateInternetConnectivity extends Thread {
+ private int mToken;
+ EvaluateInternetConnectivity(int token) {
+ mToken = token;
+ }
+ public void run() {
+ sendMessage(EVENT_REEVALUATION_COMPLETE, mToken, isCaptivePortal());
+ }
+ }
+
+ @Override
+ public void enter() {
+ sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) log(getName() + message.toString());
+ switch (message.what) {
+ case CMD_REEVALUATE:
+ if (message.arg1 != mReevaluateToken)
+ break;
+ // If network provides no internet connectivity adjust evaluation.
+ if (mNetworkAgentInfo.networkCapabilities.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+ // TODO: Try to verify something works. Do all gateways respond to pings?
+ transitionTo(mValidatedState);
+ }
+ // Kick off a thread to perform internet connectivity evaluation.
+ Thread thread = new EvaluateInternetConnectivity(mReevaluateToken);
+ thread.run();
+ break;
+ case EVENT_REEVALUATION_COMPLETE:
+ if (message.arg1 != mReevaluateToken)
+ break;
+ int httpResponseCode = message.arg2;
+ if (httpResponseCode == 204) {
+ transitionTo(mValidatedState);
+ } else if (httpResponseCode >= 200 && httpResponseCode <= 399) {
+ transitionTo(mCaptivePortalState);
+ } else {
+ if (mReevaluateDelayMs >= 0) {
+ Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
+ sendMessageDelayed(msg, mReevaluateDelayMs);
+ }
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ // TODO: Until we add an intent from the app handling captive portal
+ // login we'll just re-evaluate after a delay.
+ private class CaptivePortalState extends State {
+ @Override
+ public void enter() {
+ Message message = obtainMessage(CMD_CAPTIVE_PORTAL_REEVALUATE,
+ ++mCaptivePortalReevaluateToken, 0);
+ sendMessageDelayed(message, CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) log(getName() + message.toString());
+ switch (message.what) {
+ case CMD_CAPTIVE_PORTAL_REEVALUATE:
+ if (message.arg1 != mCaptivePortalReevaluateToken)
+ break;
+ transitionTo(mEvaluatingState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ private class LingeringState extends State {
+ @Override
+ public void enter() {
+ Message message = obtainMessage(CMD_LINGER_EXPIRED, ++mLingerToken, 0);
+ sendMessageDelayed(message, mLingerDelayMs);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) log(getName() + message.toString());
+ switch (message.what) {
+ case CMD_NETWORK_CONNECTED:
+ // Go straight to active as we've already evaluated.
+ transitionTo(mValidatedState);
+ break;
+ case CMD_LINGER_EXPIRED:
+ if (message.arg1 != mLingerToken)
+ break;
+ mConnectivityServiceHandler.sendMessage(
+ obtainMessage(EVENT_NETWORK_LINGER_COMPLETE, mNetworkAgentInfo));
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ /**
+ * Do a URL fetch on a known server to see if we get the data we expect.
+ * Returns HTTP response code.
+ */
+ private int isCaptivePortal() {
+ if (!mIsCaptivePortalCheckEnabled) return 204;
+
+ String urlString = "http://" + mServer + "/generate_204";
+ if (DBG) log("Checking " + urlString);
+ HttpURLConnection urlConnection = null;
+ Socket socket = null;
+ int httpResponseCode = 500;
+ try {
+ URL url = new URL(urlString);
+ if (false) {
+ // TODO: Need to add URLConnection.setNetwork() before we can enable.
+ urlConnection = (HttpURLConnection) url.openConnection();
+ urlConnection.setInstanceFollowRedirects(false);
+ urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
+ urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
+ urlConnection.setUseCaches(false);
+ urlConnection.getInputStream();
+ httpResponseCode = urlConnection.getResponseCode();
+ } else {
+ socket = new Socket();
+ // TODO: setNetworkForSocket(socket, mNetworkAgentInfo.network.netId);
+ InetSocketAddress address = new InetSocketAddress(url.getHost(), 80);
+ // TODO: address = new InetSocketAddress(
+ // getByNameOnNetwork(mNetworkAgentInfo.network, url.getHost()), 80);
+ socket.connect(address);
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(socket.getInputStream()));
+ OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream());
+ writer.write("GET " + url.getFile() + " HTTP/1.1\r\n\n");
+ writer.flush();
+ String response = reader.readLine();
+ if (response.startsWith("HTTP/1.1 ")) {
+ httpResponseCode = Integer.parseInt(response.substring(9, 12));
+ }
+ }
+ if (DBG) log("isCaptivePortal: ret=" + httpResponseCode);
+ } catch (IOException e) {
+ if (DBG) log("Probably not a portal: exception " + e);
+ } finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
+ }
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ return httpResponseCode;
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java
index 7786fe6..63178eb 100644
--- a/services/core/java/com/android/server/connectivity/PacManager.java
+++ b/services/core/java/com/android/server/connectivity/PacManager.java
@@ -24,18 +24,15 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
-import android.net.Proxy;
-import android.net.ProxyProperties;
-import android.os.Binder;
+import android.net.ProxyInfo;
+import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.provider.Settings;
-import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -74,7 +71,7 @@ public class PacManager {
public static final String KEY_PROXY = "keyProxy";
private String mCurrentPac;
@GuardedBy("mProxyLock")
- private String mPacUrl;
+ private Uri mPacUrl;
private AlarmManager mAlarmManager;
@GuardedBy("mProxyLock")
@@ -103,7 +100,7 @@ public class PacManager {
public void run() {
String file;
synchronized (mProxyLock) {
- if (mPacUrl == null) return;
+ if (Uri.EMPTY.equals(mPacUrl)) return;
try {
file = get(mPacUrl);
} catch (IOException ioe) {
@@ -160,8 +157,8 @@ public class PacManager {
* @param proxy Proxy information that is about to be broadcast.
* @return Returns true when the broadcast should not be sent
*/
- public synchronized boolean setCurrentProxyScriptUrl(ProxyProperties proxy) {
- if (!TextUtils.isEmpty(proxy.getPacFileUrl())) {
+ public synchronized boolean setCurrentProxyScriptUrl(ProxyInfo proxy) {
+ if (!Uri.EMPTY.equals(proxy.getPacFileUrl())) {
if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) {
// Allow to send broadcast, nothing to do.
return false;
@@ -199,8 +196,8 @@ public class PacManager {
*
* @throws IOException
*/
- private static String get(String urlString) throws IOException {
- URL url = new URL(urlString);
+ private static String get(Uri pacUri) throws IOException {
+ URL url = new URL(pacUri.toString());
URLConnection urlConnection = url.openConnection(java.net.Proxy.NO_PROXY);
return new String(Streams.readFully(urlConnection.getInputStream()));
}
@@ -271,7 +268,7 @@ public class PacManager {
// Already bound no need to bind again.
if ((mProxyConnection != null) && (mConnection != null)) {
if (mLastPort != -1) {
- sendPacBroadcast(new ProxyProperties(mPacUrl, mLastPort));
+ sendPacBroadcast(new ProxyInfo(mPacUrl, mLastPort));
} else {
Log.e(TAG, "Received invalid port from Local Proxy,"
+ " PAC will not be operational");
@@ -365,7 +362,7 @@ public class PacManager {
mLastPort = -1;
}
- private void sendPacBroadcast(ProxyProperties proxy) {
+ private void sendPacBroadcast(ProxyInfo proxy) {
mConnectivityHandler.sendMessage(mConnectivityHandler.obtainMessage(mProxyMessage, proxy));
}
@@ -374,7 +371,7 @@ public class PacManager {
return;
}
if (!mHasSentBroadcast) {
- sendPacBroadcast(new ProxyProperties(mPacUrl, mLastPort));
+ sendPacBroadcast(new ProxyInfo(mPacUrl, mLastPort));
mHasSentBroadcast = true;
}
}
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index adf1dfc..92b5f52 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -28,7 +28,6 @@ import android.content.res.Resources;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
-import android.net.INetworkManagementEventObserver;
import android.net.INetworkStatsService;
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
@@ -52,7 +51,6 @@ import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.server.IoThread;
import com.android.server.net.BaseNetworkObserver;
-import com.google.android.collect.Lists;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -472,7 +470,9 @@ public class Tethering extends BaseNetworkObserver {
mTetheredNotification.defaults &= ~Notification.DEFAULT_SOUND;
mTetheredNotification.flags = Notification.FLAG_ONGOING_EVENT;
mTetheredNotification.tickerText = title;
+ mTetheredNotification.visibility = Notification.VISIBILITY_PUBLIC;
mTetheredNotification.setLatestEventInfo(mContext, title, message, pi);
+ mTetheredNotification.category = Notification.CATEGORY_STATUS;
notificationManager.notifyAsUser(null, mTetheredNotification.icon,
mTetheredNotification, UserHandle.ALL);
@@ -1325,7 +1325,7 @@ public class Tethering extends BaseNetworkObserver {
} else {
LinkProperties linkProperties = null;
try {
- linkProperties = mConnService.getLinkProperties(upType);
+ linkProperties = mConnService.getLinkPropertiesForType(upType);
} catch (RemoteException e) { }
if (linkProperties != null) {
// Find the interface with the default IPv4 route. It may be the
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 376414b..cb1dfe4 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -29,7 +29,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
@@ -64,14 +63,12 @@ import android.security.Credentials;
import android.security.KeyStore;
import android.util.Log;
import android.util.SparseBooleanArray;
-import android.widget.Toast;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.R;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
-import com.android.internal.util.Preconditions;
import com.android.server.ConnectivityService.VpnCallback;
import com.android.server.net.BaseNetworkObserver;
@@ -80,10 +77,8 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Inet4Address;
-import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
-import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 023bf2b..c7d2871 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -19,11 +19,13 @@ package com.android.server.content;
import android.Manifest;
import android.accounts.Account;
import android.app.ActivityManager;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IContentService;
import android.content.ISyncStatusObserver;
import android.content.PeriodicSync;
+import android.content.pm.PackageManager;
import android.content.SyncAdapterType;
import android.content.SyncInfo;
import android.content.SyncRequest;
@@ -41,7 +43,6 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseIntArray;
@@ -315,7 +316,6 @@ public final class ContentService extends IContentService.Stub {
}
}
- @Override
public void requestSync(Account account, String authority, Bundle extras) {
ContentResolver.validateSyncExtrasBundle(extras);
int userId = UserHandle.getCallingUserId();
@@ -345,46 +345,56 @@ public final class ContentService extends IContentService.Stub {
* Depending on the request, we enqueue to suit in the SyncManager.
* @param request The request object. Validation of this object is done by its builder.
*/
- @Override
public void sync(SyncRequest request) {
- Bundle extras = request.getBundle();
- long flextime = request.getSyncFlexTime();
- long runAtTime = request.getSyncRunTime();
int userId = UserHandle.getCallingUserId();
- int uId = Binder.getCallingUid();
-
+ int callerUid = Binder.getCallingUid();
// This makes it so that future permission checks will be in the context of this
// process rather than the caller's process. We will restore this before returning.
long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
+ if (syncManager == null) {
+ return;
+ }
+
+ Bundle extras = request.getBundle();
+ long flextime = request.getSyncFlexTime();
+ long runAtTime = request.getSyncRunTime();
+ if (request.isPeriodic()) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.WRITE_SYNC_SETTINGS,
+ "no permission to write the sync settings");
+ SyncStorageEngine.EndPoint info;
+ if (!request.hasAuthority()) {
+ // Extra permissions checking for sync service.
+ verifySignatureForPackage(callerUid,
+ request.getService().getPackageName(), "sync");
+ info = new SyncStorageEngine.EndPoint(request.getService(), userId);
+ } else {
+ info = new SyncStorageEngine.EndPoint(
+ request.getAccount(), request.getProvider(), userId);
+ }
+ if (runAtTime < 60) {
+ Slog.w(TAG, "Requested poll frequency of " + runAtTime
+ + " seconds being rounded up to 60 seconds.");
+ runAtTime = 60;
+ }
+ // Schedule periodic sync.
+ getSyncManager().getSyncStorageEngine()
+ .updateOrAddPeriodicSync(info, runAtTime, flextime, extras);
+ } else {
+ long beforeRuntimeMillis = (flextime) * 1000;
+ long runtimeMillis = runAtTime * 1000;
if (request.hasAuthority()) {
- // Sync Adapter registered with the system - old API.
- final Account account = request.getAccount();
- final String provider = request.getProvider();
- if (request.isPeriodic()) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.WRITE_SYNC_SETTINGS,
- "no permission to write the sync settings");
- if (runAtTime < 60) {
- Slog.w(TAG, "Requested poll frequency of " + runAtTime
- + " seconds being rounded up to 60 seconds.");
- runAtTime = 60;
- }
- PeriodicSync syncToAdd =
- new PeriodicSync(account, provider, extras, runAtTime, flextime);
- getSyncManager().getSyncStorageEngine().addPeriodicSync(syncToAdd, userId);
- } else {
- long beforeRuntimeMillis = (flextime) * 1000;
- long runtimeMillis = runAtTime * 1000;
- syncManager.scheduleSync(
- account, userId, uId, provider, extras,
- beforeRuntimeMillis, runtimeMillis,
- false /* onlyThoseWithUnknownSyncableState */);
- }
+ syncManager.scheduleSync(
+ request.getAccount(), userId, callerUid, request.getProvider(), extras,
+ beforeRuntimeMillis, runtimeMillis,
+ false /* onlyThoseWithUnknownSyncableState */);
} else {
- Log.w(TAG, "Unrecognised sync parameters, doing nothing.");
+ syncManager.scheduleSync(
+ request.getService(), userId, callerUid, extras,
+ beforeRuntimeMillis,
+ runtimeMillis); // Empty function.
}
}
} finally {
@@ -395,11 +405,14 @@ public final class ContentService extends IContentService.Stub {
/**
* Clear all scheduled sync operations that match the uri and cancel the active sync
* if they match the authority and account, if they are present.
- * @param account filter the pending and active syncs to cancel using this account
- * @param authority filter the pending and active syncs to cancel using this authority
+ *
+ * @param account filter the pending and active syncs to cancel using this account, or null.
+ * @param authority filter the pending and active syncs to cancel using this authority, or
+ * null.
+ * @param cname cancel syncs running on this service, or null for provider/account.
*/
@Override
- public void cancelSync(Account account, String authority) {
+ public void cancelSync(Account account, String authority, ComponentName cname) {
if (authority != null && authority.length() == 0) {
throw new IllegalArgumentException("Authority must be non-empty");
}
@@ -411,9 +424,49 @@ public final class ContentService extends IContentService.Stub {
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
- syncManager.clearScheduledSyncOperations(account, userId, authority);
- syncManager.cancelActiveSync(account, userId, authority);
+ SyncStorageEngine.EndPoint info;
+ if (cname == null) {
+ info = new SyncStorageEngine.EndPoint(account, authority, userId);
+ } else {
+ info = new SyncStorageEngine.EndPoint(cname, userId);
+ }
+ syncManager.clearScheduledSyncOperations(info);
+ syncManager.cancelActiveSync(info, null /* all syncs for this adapter */);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public void cancelRequest(SyncRequest request) {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager == null) return;
+ int userId = UserHandle.getCallingUserId();
+ int callerUid = Binder.getCallingUid();
+
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncStorageEngine.EndPoint info;
+ Bundle extras = new Bundle(request.getBundle());
+ if (request.hasAuthority()) {
+ Account account = request.getAccount();
+ String provider = request.getProvider();
+ info = new SyncStorageEngine.EndPoint(account, provider, userId);
+ } else {
+ // Only allowed to manipulate syncs for a service which you own.
+ ComponentName service = request.getService();
+ verifySignatureForPackage(callerUid, service.getPackageName(), "cancel");
+ info = new SyncStorageEngine.EndPoint(service, userId);
}
+ if (request.isPeriodic()) {
+ // Remove periodic sync.
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+ "no permission to write the sync settings");
+ getSyncManager().getSyncStorageEngine().removePeriodicSync(info, extras);
+ }
+ // Cancel active syncs and clear pending syncs from the queue.
+ syncManager.cancelScheduledSyncOperation(info, extras);
+ syncManager.cancelActiveSync(info, extras);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -447,8 +500,8 @@ public final class ContentService extends IContentService.Stub {
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
- return syncManager.getSyncStorageEngine().getSyncAutomatically(
- account, userId, providerName);
+ return syncManager.getSyncStorageEngine()
+ .getSyncAutomatically(account, userId, providerName);
}
} finally {
restoreCallingIdentity(identityToken);
@@ -469,8 +522,8 @@ public final class ContentService extends IContentService.Stub {
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
- syncManager.getSyncStorageEngine().setSyncAutomatically(
- account, userId, providerName, sync);
+ syncManager.getSyncStorageEngine()
+ .setSyncAutomatically(account, userId, providerName, sync);
}
} finally {
restoreCallingIdentity(identityToken);
@@ -496,21 +549,22 @@ public final class ContentService extends IContentService.Stub {
+ " seconds being rounded up to 60 seconds.");
pollFrequency = 60;
}
+ long defaultFlex = SyncStorageEngine.calculateDefaultFlexTime(pollFrequency);
long identityToken = clearCallingIdentity();
try {
- // Add default flex time to this sync.
- PeriodicSync syncToAdd =
- new PeriodicSync(account, authority, extras,
- pollFrequency,
- SyncStorageEngine.calculateDefaultFlexTime(pollFrequency));
- getSyncManager().getSyncStorageEngine().addPeriodicSync(syncToAdd, userId);
+ SyncStorageEngine.EndPoint info =
+ new SyncStorageEngine.EndPoint(account, authority, userId);
+ getSyncManager().getSyncStorageEngine()
+ .updateOrAddPeriodicSync(info,
+ pollFrequency,
+ defaultFlex,
+ extras);
} finally {
restoreCallingIdentity(identityToken);
}
}
- @Override
public void removePeriodicSync(Account account, String authority, Bundle extras) {
if (account == null) {
throw new IllegalArgumentException("Account must not be null");
@@ -524,24 +578,18 @@ public final class ContentService extends IContentService.Stub {
int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
- PeriodicSync syncToRemove = new PeriodicSync(account, authority, extras,
- 0 /* Not read for removal */, 0 /* Not read for removal */);
- getSyncManager().getSyncStorageEngine().removePeriodicSync(syncToRemove, userId);
+ getSyncManager().getSyncStorageEngine()
+ .removePeriodicSync(
+ new SyncStorageEngine.EndPoint(account, authority, userId),
+ extras);
} finally {
restoreCallingIdentity(identityToken);
}
}
- /**
- * TODO: Implement.
- * @param request Sync to remove.
- */
- public void removeSync(SyncRequest request) {
- }
-
- @Override
- public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
+ public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName,
+ ComponentName cname) {
if (account == null) {
throw new IllegalArgumentException("Account must not be null");
}
@@ -551,11 +599,20 @@ public final class ContentService extends IContentService.Stub {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
+ int callerUid = Binder.getCallingUid();
int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
- return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
- account, userId, providerName);
+ if (cname == null) {
+ return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
+ new SyncStorageEngine.EndPoint(account, providerName, userId));
+ } else if (account == null && providerName == null) {
+ verifySignatureForPackage(callerUid, cname.getPackageName(), "getPeriodicSyncs");
+ return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
+ new SyncStorageEngine.EndPoint(cname, userId));
+ } else {
+ throw new IllegalArgumentException("Invalid authority specified");
+ }
} finally {
restoreCallingIdentity(identityToken);
}
@@ -579,7 +636,6 @@ public final class ContentService extends IContentService.Stub {
return -1;
}
- @Override
public void setIsSyncable(Account account, String providerName, int syncable) {
if (TextUtils.isEmpty(providerName)) {
throw new IllegalArgumentException("Authority must not be empty");
@@ -600,6 +656,45 @@ public final class ContentService extends IContentService.Stub {
}
}
+ public void setServiceActive(ComponentName cname, boolean active) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+ "no permission to write the sync settings");
+ verifySignatureForPackage(Binder.getCallingUid(), cname.getPackageName(),
+ "setServiceActive");
+
+ int userId = UserHandle.getCallingUserId();
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ syncManager.getSyncStorageEngine().setIsTargetServiceActive(
+ cname, userId, active);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public boolean isServiceActive(ComponentName cname) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+ "no permission to read the sync settings");
+ verifySignatureForPackage(Binder.getCallingUid(), cname.getPackageName(),
+ "isServiceActive");
+
+ int userId = UserHandle.getCallingUserId();
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ return syncManager.getSyncStorageEngine()
+ .getIsTargetServiceActive(cname, userId);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ return false;
+ }
+
@Override
public boolean getMasterSyncAutomatically() {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
@@ -635,17 +730,24 @@ public final class ContentService extends IContentService.Stub {
}
}
- public boolean isSyncActive(Account account, String authority) {
+ public boolean isSyncActive(Account account, String authority, ComponentName cname) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
"no permission to read the sync stats");
int userId = UserHandle.getCallingUserId();
-
+ int callingUid = Binder.getCallingUid();
long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
+ if (syncManager == null) {
+ return false;
+ }
+ if (cname == null) {
return syncManager.getSyncStorageEngine().isSyncActive(
- account, userId, authority);
+ new SyncStorageEngine.EndPoint(account, authority, userId));
+ } else if (account == null && authority == null) {
+ verifySignatureForPackage(callingUid, cname.getPackageName(), "isSyncActive");
+ return syncManager.getSyncStorageEngine().isSyncActive(
+ new SyncStorageEngine.EndPoint(cname, userId));
}
} finally {
restoreCallingIdentity(identityToken);
@@ -666,7 +768,7 @@ public final class ContentService extends IContentService.Stub {
}
}
- public SyncStatusInfo getSyncStatus(Account account, String authority) {
+ public SyncStatusInfo getSyncStatus(Account account, String authority, ComponentName cname) {
if (TextUtils.isEmpty(authority)) {
throw new IllegalArgumentException("Authority must not be empty");
}
@@ -674,34 +776,52 @@ public final class ContentService extends IContentService.Stub {
"no permission to read the sync stats");
int userId = UserHandle.getCallingUserId();
+ int callerUid = Binder.getCallingUid();
long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- return syncManager.getSyncStorageEngine().getStatusByAccountAndAuthority(
- account, userId, authority);
+ if (syncManager == null) {
+ return null;
}
+ SyncStorageEngine.EndPoint info;
+ if (cname == null) {
+ info = new SyncStorageEngine.EndPoint(account, authority, userId);
+ } else if (account == null && authority == null) {
+ verifySignatureForPackage(callerUid, cname.getPackageName(), "getSyncStatus");
+ info = new SyncStorageEngine.EndPoint(cname, userId);
+ } else {
+ throw new IllegalArgumentException("Must call sync status with valid authority");
+ }
+ return syncManager.getSyncStorageEngine().getStatusByAuthority(info);
} finally {
restoreCallingIdentity(identityToken);
}
- return null;
}
- public boolean isSyncPending(Account account, String authority) {
+ public boolean isSyncPending(Account account, String authority, ComponentName cname) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
"no permission to read the sync stats");
+ int callerUid = Binder.getCallingUid();
int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
+ SyncManager syncManager = getSyncManager();
+ if (syncManager == null) return false;
+
try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- return syncManager.getSyncStorageEngine().isSyncPending(account, userId, authority);
+ SyncStorageEngine.EndPoint info;
+ if (cname == null) {
+ info = new SyncStorageEngine.EndPoint(account, authority, userId);
+ } else if (account == null && authority == null) {
+ verifySignatureForPackage(callerUid, cname.getPackageName(), "isSyncPending");
+ info = new SyncStorageEngine.EndPoint(cname, userId);
+ } else {
+ throw new IllegalArgumentException("Invalid authority specified");
}
+ return syncManager.getSyncStorageEngine().isSyncPending(info);
} finally {
restoreCallingIdentity(identityToken);
}
- return false;
}
public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
@@ -735,6 +855,30 @@ public final class ContentService extends IContentService.Stub {
}
/**
+ * Helper to verify that the provided package name shares the same cert as the caller.
+ * @param callerUid uid of the calling process.
+ * @param packageName package to verify against package of calling application.
+ * @param tag a tag to use when throwing an exception if the signatures don't
+ * match. Cannot be null.
+ * @return true if the calling application and the provided package are signed with the same
+ * certificate.
+ */
+ private boolean verifySignatureForPackage(int callerUid, String packageName, String tag) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ int serviceUid = pm.getApplicationInfo(packageName, 0).uid;
+ if (pm.checkSignatures(callerUid, serviceUid) == PackageManager.SIGNATURE_MATCH) {
+ return true;
+ } else {
+ throw new SecurityException(tag + ": Caller certificate does not match that for - "
+ + packageName);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalArgumentException(tag + ": " + packageName + " package not found.");
+ }
+ }
+
+ /**
* Hide this class since it is not part of api,
* but current unittest framework requires it to be public
* @hide
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 9e3dad6..1b40cdf 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -31,6 +31,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.ISyncAdapter;
import android.content.ISyncContext;
+import android.content.ISyncServiceAdapter;
import android.content.ISyncStatusObserver;
import android.content.Intent;
import android.content.IntentFilter;
@@ -52,6 +53,7 @@ import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
+import android.os.BatteryStats;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -59,6 +61,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -74,6 +77,7 @@ import android.util.Pair;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.accounts.AccountManagerService;
@@ -85,10 +89,8 @@ import com.google.android.collect.Sets;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -99,7 +101,6 @@ import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
-import java.util.concurrent.CountDownLatch;
/**
* @hide
@@ -153,7 +154,7 @@ public class SyncManager {
private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
- private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*";
+ private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*/";
private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
@@ -174,6 +175,7 @@ public class SyncManager {
private final NotificationManager mNotificationMgr;
private AlarmManager mAlarmService = null;
+ private final IBatteryStats mBatteryStats;
private SyncStorageEngine mSyncStorageEngine;
@@ -202,8 +204,9 @@ public class SyncManager {
Log.v(TAG, "Internal storage is low.");
}
mStorageIsLow = true;
- cancelActiveSync(null /* any account */, UserHandle.USER_ALL,
- null /* any authority */);
+ cancelActiveSync(
+ SyncStorageEngine.EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL,
+ null /* any sync */);
} else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Internal storage is ok.");
@@ -221,19 +224,6 @@ public class SyncManager {
}
};
- private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (getConnectivityManager().getBackgroundDataSetting()) {
- scheduleSync(null /* account */, UserHandle.USER_ALL,
- SyncOperation.REASON_BACKGROUND_DATA_SETTINGS_CHANGED,
- null /* authority */,
- new Bundle(), 0 /* delay */, 0 /* delay */,
- false /* onlyThoseWithUnknownSyncableState */);
- }
- }
- };
-
private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -278,16 +268,16 @@ public class SyncManager {
doDatabaseCleanup();
}
+ AccountAndUser[] accounts = mRunningAccounts;
for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
- if (!containsAccountAndUser(mRunningAccounts,
- currentSyncContext.mSyncOperation.account,
- currentSyncContext.mSyncOperation.userId)) {
+ if (!containsAccountAndUser(accounts,
+ currentSyncContext.mSyncOperation.target.account,
+ currentSyncContext.mSyncOperation.target.userId)) {
Log.d(TAG, "canceling sync since the account is no longer running");
sendSyncFinishedOrCanceledMessage(currentSyncContext,
null /* no result since this is a cancel */);
}
}
-
// we must do this since we don't bother scheduling alarms when
// the accounts are not set yet
sendCheckAlarmsMessage();
@@ -316,9 +306,7 @@ public class SyncManager {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Reconnection detected: clearing all backoffs");
}
- synchronized(mSyncQueue) {
- mSyncStorageEngine.clearAllBackoffsLocked(mSyncQueue);
- }
+ mSyncStorageEngine.clearAllBackoffs(mSyncQueue);
}
sendCheckAlarmsMessage();
}
@@ -384,12 +372,17 @@ public class SyncManager {
mSyncStorageEngine = SyncStorageEngine.getSingleton();
mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() {
@Override
- public void onSyncRequest(Account account, int userId, int reason, String authority,
- Bundle extras) {
- scheduleSync(account, userId, reason, authority, extras,
- 0 /* no delay */,
- 0 /* no delay */,
- false);
+ public void onSyncRequest(SyncStorageEngine.EndPoint info, int reason, Bundle extras) {
+ if (info.target_provider) {
+ scheduleSync(info.account, info.userId, reason, info.provider, extras,
+ 0 /* no flex */,
+ 0 /* run immediately */,
+ false);
+ } else if (info.target_service) {
+ scheduleSync(info.service, info.userId, reason, extras,
+ 0 /* no flex */,
+ 0 /* run immediately */);
+ }
}
});
@@ -418,12 +411,10 @@ public class SyncManager {
if (!factoryTest) {
intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
+ intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
context.registerReceiver(mBootCompletedReceiver, intentFilter);
}
- intentFilter = new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
- context.registerReceiver(mBackgroundDataSettingChanged, intentFilter);
-
intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
context.registerReceiver(mStorageIntentReceiver, intentFilter);
@@ -449,6 +440,8 @@ public class SyncManager {
}
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+ BatteryStats.SERVICE_NAME));
// This WakeLock is used to ensure that we stay awake between the time that we receive
// a sync alarm notification and when we finish processing it. We need to do this
@@ -538,8 +531,75 @@ public class SyncManager {
private void ensureAlarmService() {
if (mAlarmService == null) {
- mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+ mAlarmService = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ }
+ }
+
+ /**
+ * Initiate a sync using the new anonymous service API.
+ * @param cname SyncService component bound to in order to perform the sync.
+ * @param userId the id of the user whose accounts are to be synced. If userId is USER_ALL,
+ * then all users' accounts are considered.
+ * @param uid Linux uid of the application that is performing the sync.
+ * @param extras a Map of SyncAdapter-specific information to control
+ * syncs of a specific provider. Cannot be null.
+ * @param beforeRunTimeMillis milliseconds before <code>runtimeMillis</code> that this sync may
+ * be run.
+ * @param runtimeMillis milliseconds from now by which this sync must be run.
+ */
+ public void scheduleSync(ComponentName cname, int userId, int uid, Bundle extras,
+ long beforeRunTimeMillis, long runtimeMillis) {
+ boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+ if (isLoggable) {
+ Log.d(TAG, "one off sync for: " + cname + " " + extras.toString());
+ }
+
+ Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
+ if (expedited) {
+ runtimeMillis = -1; // this means schedule at the front of the queue
+ }
+
+ final boolean ignoreSettings =
+ extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
+ int source = SyncStorageEngine.SOURCE_SERVICE;
+ boolean isEnabled = mSyncStorageEngine.getIsTargetServiceActive(cname, userId);
+ // Only schedule this sync if
+ // - we've explicitly been told to ignore settings.
+ // - global sync is enabled for this user.
+ boolean syncAllowed =
+ ignoreSettings
+ || mSyncStorageEngine.getMasterSyncAutomatically(userId);
+ if (!syncAllowed) {
+ if (isLoggable) {
+ Log.d(TAG, "scheduleSync: sync of " + cname + " not allowed, dropping request.");
+ }
+ return;
+ }
+ if (!isEnabled) {
+ if (isLoggable) {
+ Log.d(TAG, "scheduleSync: " + cname + " is not enabled, dropping request");
+ }
+ return;
}
+ SyncStorageEngine.EndPoint info = new SyncStorageEngine.EndPoint(cname, userId);
+ Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(info);
+ long delayUntil = mSyncStorageEngine.getDelayUntilTime(info);
+ final long backoffTime = backoff != null ? backoff.first : 0;
+ if (isLoggable) {
+ Log.v(TAG, "schedule Sync:"
+ + ", delay until " + delayUntil
+ + ", run by " + runtimeMillis
+ + ", flex " + beforeRunTimeMillis
+ + ", source " + source
+ + ", sync service " + cname
+ + ", extras " + extras);
+ }
+ scheduleSyncOperation(
+ new SyncOperation(cname, userId, uid, source, extras,
+ runtimeMillis /* runtime */,
+ beforeRunTimeMillis /* flextime */,
+ backoffTime,
+ delayUntil));
}
/**
@@ -588,9 +648,6 @@ public class SyncManager {
long runtimeMillis, boolean onlyThoseWithUnkownSyncableState) {
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
- final boolean backgroundDataUsageAllowed = !mBootCompleted ||
- getConnectivityManager().getBackgroundDataSetting();
-
if (extras == null) {
extras = new Bundle();
}
@@ -607,8 +664,6 @@ public class SyncManager {
if (requestedAccount != null && userId != UserHandle.USER_ALL) {
accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) };
} else {
- // if the accounts aren't configured yet then we can't support an account-less
- // sync request
accounts = mRunningAccounts;
if (accounts.length == 0) {
if (isLoggable) {
@@ -683,12 +738,10 @@ public class SyncManager {
continue;
}
- // always allow if the isSyncable state is unknown
boolean syncAllowed =
- (isSyncable < 0)
+ (isSyncable < 0) // always allow if the isSyncable state is unknown
|| ignoreSettings
- || (backgroundDataUsageAllowed
- && mSyncStorageEngine.getMasterSyncAutomatically(account.userId)
+ || (mSyncStorageEngine.getMasterSyncAutomatically(account.userId)
&& mSyncStorageEngine.getSyncAutomatically(account.account,
account.userId, authority));
if (!syncAllowed) {
@@ -698,11 +751,12 @@ public class SyncManager {
}
continue;
}
-
- Pair<Long, Long> backoff = mSyncStorageEngine
- .getBackoff(account.account, account.userId, authority);
- long delayUntil = mSyncStorageEngine.getDelayUntilTime(account.account,
- account.userId, authority);
+ SyncStorageEngine.EndPoint info =
+ new SyncStorageEngine.EndPoint(
+ account.account, authority, account.userId);
+ Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(info);
+ long delayUntil =
+ mSyncStorageEngine.getDelayUntilTime(info);
final long backoffTime = backoff != null ? backoff.first : 0;
if (isSyncable < 0) {
// Initialisation sync.
@@ -712,6 +766,7 @@ public class SyncManager {
Log.v(TAG, "schedule initialisation Sync:"
+ ", delay until " + delayUntil
+ ", run by " + 0
+ + ", flex " + 0
+ ", source " + source
+ ", account " + account
+ ", authority " + authority
@@ -787,13 +842,12 @@ public class SyncManager {
mSyncHandler.sendMessage(msg);
}
- private void sendCancelSyncsMessage(final Account account, final int userId,
- final String authority) {
+ private void sendCancelSyncsMessage(final SyncStorageEngine.EndPoint info, Bundle extras) {
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CANCEL");
Message msg = mSyncHandler.obtainMessage();
msg.what = SyncHandler.MESSAGE_CANCEL;
- msg.obj = Pair.create(account, authority);
- msg.arg1 = userId;
+ msg.setData(extras);
+ msg.obj = info;
mSyncHandler.sendMessage(msg);
}
@@ -816,10 +870,11 @@ public class SyncManager {
}
private void clearBackoffSetting(SyncOperation op) {
- mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority,
- SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
+ mSyncStorageEngine.setBackoff(op.target,
+ SyncStorageEngine.NOT_IN_BACKOFF_MODE,
+ SyncStorageEngine.NOT_IN_BACKOFF_MODE);
synchronized (mSyncQueue) {
- mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, 0);
+ mSyncQueue.onBackoffChanged(op.target, 0);
}
}
@@ -829,7 +884,7 @@ public class SyncManager {
final long now = SystemClock.elapsedRealtime();
final Pair<Long, Long> previousSettings =
- mSyncStorageEngine.getBackoff(op.account, op.userId, op.authority);
+ mSyncStorageEngine.getBackoff(op.target);
long newDelayInMs = -1;
if (previousSettings != null) {
// don't increase backoff before current backoff is expired. This will happen for op's
@@ -860,14 +915,12 @@ public class SyncManager {
final long backoff = now + newDelayInMs;
- mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority,
- backoff, newDelayInMs);
-
+ mSyncStorageEngine.setBackoff(op.target, backoff, newDelayInMs);
op.backoff = backoff;
op.updateEffectiveRunTime();
synchronized (mSyncQueue) {
- mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, backoff);
+ mSyncQueue.onBackoffChanged(op.target, backoff);
}
}
@@ -880,20 +933,20 @@ public class SyncManager {
} else {
newDelayUntilTime = 0;
}
- mSyncStorageEngine
- .setDelayUntilTime(op.account, op.userId, op.authority, newDelayUntilTime);
+ mSyncStorageEngine.setDelayUntilTime(op.target, newDelayUntilTime);
synchronized (mSyncQueue) {
- mSyncQueue.onDelayUntilTimeChanged(op.account, op.authority, newDelayUntilTime);
+ mSyncQueue.onDelayUntilTimeChanged(op.target, newDelayUntilTime);
}
}
/**
- * Cancel the active sync if it matches the authority and account.
- * @param account limit the cancelations to syncs with this account, if non-null
- * @param authority limit the cancelations to syncs with this authority, if non-null
+ * Cancel the active sync if it matches the target.
+ * @param info object containing info about which syncs to cancel. The target can
+ * have null account/provider info to specify all accounts/providers.
+ * @param extras if non-null, specifies the exact sync to remove.
*/
- public void cancelActiveSync(Account account, int userId, String authority) {
- sendCancelSyncsMessage(account, userId, authority);
+ public void cancelActiveSync(SyncStorageEngine.EndPoint info, Bundle extras) {
+ sendCancelSyncsMessage(info, extras);
}
/**
@@ -922,24 +975,40 @@ public class SyncManager {
/**
* Remove scheduled sync operations.
- * @param account limit the removals to operations with this account, if non-null
- * @param authority limit the removals to operations with this authority, if non-null
+ * @param info limit the removals to operations that match this target. The target can
+ * have null account/provider info to specify all accounts/providers.
*/
- public void clearScheduledSyncOperations(Account account, int userId, String authority) {
+ public void clearScheduledSyncOperations(SyncStorageEngine.EndPoint info) {
synchronized (mSyncQueue) {
- mSyncQueue.remove(account, userId, authority);
+ mSyncQueue.remove(info, null /* all operations */);
}
- mSyncStorageEngine.setBackoff(account, userId, authority,
+ mSyncStorageEngine.setBackoff(info,
SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
}
+ /**
+ * Remove a specified sync, if it exists.
+ * @param info Authority for which the sync is to be removed.
+ * @param extras extras bundle to uniquely identify sync.
+ */
+ public void cancelScheduledSyncOperation(SyncStorageEngine.EndPoint info, Bundle extras) {
+ synchronized (mSyncQueue) {
+ mSyncQueue.remove(info, extras);
+ }
+ // Reset the back-off if there are no more syncs pending.
+ if (!mSyncStorageEngine.isSyncPending(info)) {
+ mSyncStorageEngine.setBackoff(info,
+ SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
+ }
+ }
+
void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
if (isLoggable) {
Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation);
}
- operation = new SyncOperation(operation);
+ operation = new SyncOperation(operation, 0L /* newRunTimeFromNow */);
// The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given
// request. Retries of the request will always honor the backoff, so clear the
@@ -948,25 +1017,29 @@ public class SyncManager {
operation.extras.remove(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
}
- // If this sync aborted because the internal sync loop retried too many times then
- // don't reschedule. Otherwise we risk getting into a retry loop.
- // If the operation succeeded to some extent then retry immediately.
- // If this was a two-way sync then retry soft errors with an exponential backoff.
- // If this was an upward sync then schedule a two-way sync immediately.
- // Otherwise do not reschedule.
if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) {
- Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified "
- + operation);
+ if (isLoggable) {
+ Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified "
+ + operation);
+ }
} else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)
&& !syncResult.syncAlreadyInProgress) {
+ // If this was an upward sync then schedule a two-way sync immediately.
operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
- Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
- + "encountered an error: " + operation);
+ if (isLoggable) {
+ Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
+ + "encountered an error: " + operation);
+ }
scheduleSyncOperation(operation);
} else if (syncResult.tooManyRetries) {
- Log.d(TAG, "not retrying sync operation because it retried too many times: "
- + operation);
+ // If this sync aborted because the internal sync loop retried too many times then
+ // don't reschedule. Otherwise we risk getting into a retry loop.
+ if (isLoggable) {
+ Log.d(TAG, "not retrying sync operation because it retried too many times: "
+ + operation);
+ }
} else if (syncResult.madeSomeProgress()) {
+ // If the operation succeeded to some extent then retry immediately.
if (isLoggable) {
Log.d(TAG, "retrying sync operation because even though it had an error "
+ "it achieved some success");
@@ -979,19 +1052,18 @@ public class SyncManager {
}
scheduleSyncOperation(
new SyncOperation(
- operation.account, operation.userId,
- operation.reason,
- operation.syncSource,
- operation.authority, operation.extras,
- DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000, operation.flexTime,
- operation.backoff, operation.delayUntil, operation.allowParallelSyncs));
+ operation,
+ DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000 /* newRunTimeFromNow */)
+ );
} else if (syncResult.hasSoftError()) {
+ // If this was a two-way sync then retry soft errors with an exponential backoff.
if (isLoggable) {
Log.d(TAG, "retrying sync operation because it encountered a soft error: "
+ operation);
}
scheduleSyncOperation(operation);
} else {
+ // Otherwise do not reschedule.
Log.d(TAG, "not retrying sync operation because the error is a hard error: "
+ operation);
}
@@ -1024,9 +1096,12 @@ public class SyncManager {
updateRunningAccounts();
cancelActiveSync(
- null /* any account */,
- userId,
- null /* any authority */);
+ new SyncStorageEngine.EndPoint(
+ null /* any account */,
+ null /* any authority */,
+ userId),
+ null /* any sync. */
+ );
}
private void onUserRemoved(int userId) {
@@ -1035,7 +1110,7 @@ public class SyncManager {
// Clean up the storage engine database
mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId);
synchronized (mSyncQueue) {
- mSyncQueue.removeUser(userId);
+ mSyncQueue.removeUserLocked(userId);
}
}
@@ -1047,6 +1122,7 @@ public class SyncManager {
final SyncOperation mSyncOperation;
final long mHistoryRowId;
ISyncAdapter mSyncAdapter;
+ ISyncServiceAdapter mSyncServiceAdapter;
final long mStartTime;
long mTimeoutStartTime;
boolean mBound;
@@ -1054,6 +1130,7 @@ public class SyncManager {
final int mSyncAdapterUid;
SyncInfo mSyncInfo;
boolean mIsLinkedToDeath = false;
+ String mEventName;
/**
* Create an ActiveSyncContext for an impending sync and grab the wakelock for that
@@ -1072,10 +1149,10 @@ public class SyncManager {
mSyncOperation = syncOperation;
mHistoryRowId = historyRowId;
mSyncAdapter = null;
+ mSyncServiceAdapter = null;
mStartTime = SystemClock.elapsedRealtime();
mTimeoutStartTime = mStartTime;
- mSyncWakeLock = mSyncHandler.getSyncWakeLock(
- mSyncOperation.account, mSyncOperation.authority);
+ mSyncWakeLock = mSyncHandler.getSyncWakeLock(mSyncOperation);
mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterUid));
mSyncWakeLock.acquire();
}
@@ -1102,7 +1179,7 @@ public class SyncManager {
public void onServiceConnected(ComponentName name, IBinder service) {
Message msg = mSyncHandler.obtainMessage();
msg.what = SyncHandler.MESSAGE_SERVICE_CONNECTED;
- msg.obj = new ServiceConnectionData(this, ISyncAdapter.Stub.asInterface(service));
+ msg.obj = new ServiceConnectionData(this, service);
mSyncHandler.sendMessage(msg);
}
@@ -1113,13 +1190,13 @@ public class SyncManager {
mSyncHandler.sendMessage(msg);
}
- boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info, int userId) {
+ boolean bindToSyncAdapter(ComponentName serviceComponent, int userId) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "bindToSyncAdapter: " + info.componentName + ", connection " + this);
+ Log.d(TAG, "bindToSyncAdapter: " + serviceComponent + ", connection " + this);
}
Intent intent = new Intent();
intent.setAction("android.content.SyncAdapter");
- intent.setComponent(info.componentName);
+ intent.setComponent(serviceComponent);
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.sync_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
@@ -1129,9 +1206,16 @@ public class SyncManager {
final boolean bindResult = mContext.bindServiceAsUser(intent, this,
Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
| Context.BIND_ALLOW_OOM_MANAGEMENT,
- new UserHandle(mSyncOperation.userId));
+ new UserHandle(mSyncOperation.target.userId));
if (!bindResult) {
mBound = false;
+ } else {
+ try {
+ mEventName = mSyncOperation.wakeLockName();
+ mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_SYNC_START,
+ mEventName, mSyncAdapterUid);
+ } catch (RemoteException e) {
+ }
}
return bindResult;
}
@@ -1147,12 +1231,16 @@ public class SyncManager {
if (mBound) {
mBound = false;
mContext.unbindService(this);
+ try {
+ mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_SYNC_FINISH,
+ mEventName, mSyncAdapterUid);
+ } catch (RemoteException e) {
+ }
}
mSyncWakeLock.release();
mSyncWakeLock.setWorkSource(null);
}
- @Override
public String toString() {
StringBuilder sb = new StringBuilder();
toString(sb);
@@ -1291,11 +1379,13 @@ public class SyncManager {
int row = table.getNumRows();
Pair<AuthorityInfo, SyncStatusInfo> syncAuthoritySyncStatus =
mSyncStorageEngine.getCopyOfAuthorityWithSyncStatus(
- account.account, account.userId, syncAdapterType.type.authority);
+ new SyncStorageEngine.EndPoint(
+ account.account,
+ syncAdapterType.type.authority,
+ account.userId));
SyncStorageEngine.AuthorityInfo settings = syncAuthoritySyncStatus.first;
SyncStatusInfo status = syncAuthoritySyncStatus.second;
-
- String authority = settings.authority;
+ String authority = settings.target.provider;
if (authority.length() > 50) {
authority = authority.substring(authority.length() - 50);
}
@@ -1416,14 +1506,25 @@ public class SyncManager {
int maxAuthority = 0;
int maxAccount = 0;
for (SyncStorageEngine.SyncHistoryItem item : items) {
- SyncStorageEngine.AuthorityInfo authority
+ SyncStorageEngine.AuthorityInfo authorityInfo
= mSyncStorageEngine.getAuthority(item.authorityId);
final String authorityName;
final String accountKey;
- if (authority != null) {
- authorityName = authority.authority;
- accountKey = authority.account.name + "/" + authority.account.type
- + " u" + authority.userId;
+ if (authorityInfo != null) {
+ if (authorityInfo.target.target_provider) {
+ authorityName = authorityInfo.target.provider;
+ accountKey = authorityInfo.target.account.name + "/"
+ + authorityInfo.target.account.type
+ + " u" + authorityInfo.target.userId;
+ } else if (authorityInfo.target.target_service) {
+ authorityName = authorityInfo.target.service.getPackageName() + "/"
+ + authorityInfo.target.service.getClassName()
+ + " u" + authorityInfo.target.userId;
+ accountKey = "no account";
+ } else {
+ authorityName = "Unknown";
+ accountKey = "Unknown";
+ }
} else {
authorityName = "Unknown";
accountKey = "Unknown";
@@ -1544,14 +1645,25 @@ public class SyncManager {
final PackageManager pm = mContext.getPackageManager();
for (int i = 0; i < N; i++) {
SyncStorageEngine.SyncHistoryItem item = items.get(i);
- SyncStorageEngine.AuthorityInfo authority
+ SyncStorageEngine.AuthorityInfo authorityInfo
= mSyncStorageEngine.getAuthority(item.authorityId);
final String authorityName;
final String accountKey;
- if (authority != null) {
- authorityName = authority.authority;
- accountKey = authority.account.name + "/" + authority.account.type
- + " u" + authority.userId;
+ if (authorityInfo != null) {
+ if (authorityInfo.target.target_provider) {
+ authorityName = authorityInfo.target.provider;
+ accountKey = authorityInfo.target.account.name + "/"
+ + authorityInfo.target.account.type
+ + " u" + authorityInfo.target.userId;
+ } else if (authorityInfo.target.target_service) {
+ authorityName = authorityInfo.target.service.getPackageName() + "/"
+ + authorityInfo.target.service.getClassName()
+ + " u" + authorityInfo.target.userId;
+ accountKey = "none";
+ } else {
+ authorityName = "Unknown";
+ accountKey = "Unknown";
+ }
} else {
authorityName = "Unknown";
accountKey = "Unknown";
@@ -1610,14 +1722,25 @@ public class SyncManager {
if (extras == null || extras.size() == 0) {
continue;
}
- final SyncStorageEngine.AuthorityInfo authority
+ final SyncStorageEngine.AuthorityInfo authorityInfo
= mSyncStorageEngine.getAuthority(item.authorityId);
final String authorityName;
final String accountKey;
- if (authority != null) {
- authorityName = authority.authority;
- accountKey = authority.account.name + "/" + authority.account.type
- + " u" + authority.userId;
+ if (authorityInfo != null) {
+ if (authorityInfo.target.target_provider) {
+ authorityName = authorityInfo.target.provider;
+ accountKey = authorityInfo.target.account.name + "/"
+ + authorityInfo.target.account.type
+ + " u" + authorityInfo.target.userId;
+ } else if (authorityInfo.target.target_service) {
+ authorityName = authorityInfo.target.service.getPackageName() + "/"
+ + authorityInfo.target.service.getClassName()
+ + " u" + authorityInfo.target.userId;
+ accountKey = "none";
+ } else {
+ authorityName = "Unknown";
+ accountKey = "Unknown";
+ }
} else {
authorityName = "Unknown";
accountKey = "Unknown";
@@ -1761,10 +1884,11 @@ public class SyncManager {
class ServiceConnectionData {
public final ActiveSyncContext activeSyncContext;
- public final ISyncAdapter syncAdapter;
- ServiceConnectionData(ActiveSyncContext activeSyncContext, ISyncAdapter syncAdapter) {
+ public final IBinder adapter;
+
+ ServiceConnectionData(ActiveSyncContext activeSyncContext, IBinder adapter) {
this.activeSyncContext = activeSyncContext;
- this.syncAdapter = syncAdapter;
+ this.adapter = adapter;
}
}
@@ -1784,11 +1908,11 @@ public class SyncManager {
public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
private Long mAlarmScheduleTime = null;
public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
- private final HashMap<Pair<Account, String>, PowerManager.WakeLock> mWakeLocks =
- Maps.newHashMap();
+ private final HashMap<String, PowerManager.WakeLock> mWakeLocks = Maps.newHashMap();
+
private List<Message> mBootQueue = new ArrayList<Message>();
- public void onBootCompleted() {
+ public void onBootCompleted() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Boot completed, clearing boot queue.");
}
@@ -1803,12 +1927,11 @@ public class SyncManager {
}
}
- private PowerManager.WakeLock getSyncWakeLock(Account account, String authority) {
- final Pair<Account, String> wakeLockKey = Pair.create(account, authority);
+ private PowerManager.WakeLock getSyncWakeLock(SyncOperation operation) {
+ final String wakeLockKey = operation.wakeLockName();
PowerManager.WakeLock wakeLock = mWakeLocks.get(wakeLockKey);
if (wakeLock == null) {
- final String name = SYNC_WAKE_LOCK_PREFIX + "/" + authority + "/" + account.type
- + "/" + account.name;
+ final String name = SYNC_WAKE_LOCK_PREFIX + wakeLockKey;
wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
wakeLock.setReferenceCounted(false);
mWakeLocks.put(wakeLockKey, wakeLock);
@@ -1861,7 +1984,6 @@ public class SyncManager {
super(looper);
}
- @Override
public void handleMessage(Message msg) {
if (tryEnqueueMessageUntilReadyToRun(msg)) {
return;
@@ -1881,12 +2003,13 @@ public class SyncManager {
earliestFuturePollTime = scheduleReadyPeriodicSyncs();
switch (msg.what) {
case SyncHandler.MESSAGE_CANCEL: {
- Pair<Account, String> payload = (Pair<Account, String>) msg.obj;
+ SyncStorageEngine.EndPoint payload = (SyncStorageEngine.EndPoint) msg.obj;
+ Bundle extras = msg.peekData();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: "
- + payload.first + ", " + payload.second);
+ + payload + " bundle: " + extras);
}
- cancelActiveSyncLocked(payload.first, msg.arg1, payload.second);
+ cancelActiveSyncLocked(payload, extras);
nextPendingSyncTime = maybeStartNextSyncLocked();
break;
}
@@ -1895,35 +2018,38 @@ public class SyncManager {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED");
}
- SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
+ SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload) msg.obj;
if (!isSyncStillActive(payload.activeSyncContext)) {
Log.d(TAG, "handleSyncHandlerMessage: dropping since the "
+ "sync is no longer active: "
+ payload.activeSyncContext);
break;
}
- runSyncFinishedOrCanceledLocked(payload.syncResult, payload.activeSyncContext);
+ runSyncFinishedOrCanceledLocked(payload.syncResult,
+ payload.activeSyncContext);
// since a sync just finished check if it is time to start a new sync
nextPendingSyncTime = maybeStartNextSyncLocked();
break;
case SyncHandler.MESSAGE_SERVICE_CONNECTED: {
- ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
+ ServiceConnectionData msgData = (ServiceConnectionData) msg.obj;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: "
+ msgData.activeSyncContext);
}
// check that this isn't an old message
if (isSyncStillActive(msgData.activeSyncContext)) {
- runBoundToSyncAdapter(msgData.activeSyncContext, msgData.syncAdapter);
+ runBoundToAdapter(
+ msgData.activeSyncContext,
+ msgData.adapter);
}
break;
}
case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: {
final ActiveSyncContext currentSyncContext =
- ((ServiceConnectionData)msg.obj).activeSyncContext;
+ ((ServiceConnectionData) msg.obj).activeSyncContext;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: "
+ currentSyncContext);
@@ -1932,12 +2058,15 @@ public class SyncManager {
if (isSyncStillActive(currentSyncContext)) {
// cancel the sync if we have a syncadapter, which means one is
// outstanding
- if (currentSyncContext.mSyncAdapter != null) {
- try {
+ try {
+ if (currentSyncContext.mSyncAdapter != null) {
currentSyncContext.mSyncAdapter.cancelSync(currentSyncContext);
- } catch (RemoteException e) {
- // we don't need to retry this in this case
+ } else if (currentSyncContext.mSyncServiceAdapter != null) {
+ currentSyncContext.mSyncServiceAdapter
+ .cancelSync(currentSyncContext);
}
+ } catch (RemoteException e) {
+ // We don't need to retry this in this case.
}
// pretend that the sync failed with an IOException,
@@ -1982,9 +2111,46 @@ public class SyncManager {
}
}
+ private boolean isDispatchable(SyncStorageEngine.EndPoint target) {
+ final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+ if (target.target_provider) {
+ // skip the sync if the account of this operation no longer exists
+ AccountAndUser[] accounts = mRunningAccounts;
+ if (!containsAccountAndUser(
+ accounts, target.account, target.userId)) {
+ return false;
+ }
+ if (!mSyncStorageEngine.getMasterSyncAutomatically(target.userId)
+ || !mSyncStorageEngine.getSyncAutomatically(
+ target.account,
+ target.userId,
+ target.provider)) {
+ if (isLoggable) {
+ Log.v(TAG, " Not scheduling periodic operation: sync turned off.");
+ }
+ return false;
+ }
+ if (getIsSyncable(target.account, target.userId, target.provider)
+ == 0) {
+ if (isLoggable) {
+ Log.v(TAG, " Not scheduling periodic operation: isSyncable == 0.");
+ }
+ return false;
+ }
+ } else if (target.target_service) {
+ if (mSyncStorageEngine.getIsTargetServiceActive(target.service, target.userId)) {
+ if (isLoggable) {
+ Log.v(TAG, " Not scheduling periodic operation: isEnabled == 0.");
+ }
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* Turn any periodic sync operations that are ready to run into pending sync operations.
- * @return the desired start time of the earliest future periodic sync operation,
+ * @return the desired start time of the earliest future periodic sync operation,
* in milliseconds since boot
*/
private long scheduleReadyPeriodicSyncs() {
@@ -1992,14 +2158,7 @@ public class SyncManager {
if (isLoggable) {
Log.v(TAG, "scheduleReadyPeriodicSyncs");
}
- final boolean backgroundDataUsageAllowed =
- getConnectivityManager().getBackgroundDataSetting();
long earliestFuturePollTime = Long.MAX_VALUE;
- if (!backgroundDataUsageAllowed) {
- return earliestFuturePollTime;
- }
-
- AccountAndUser[] accounts = mRunningAccounts;
final long nowAbsolute = System.currentTimeMillis();
final long shiftedNowAbsolute = (0 < nowAbsolute - mSyncRandomOffsetMillis)
@@ -2010,40 +2169,31 @@ public class SyncManager {
for (Pair<AuthorityInfo, SyncStatusInfo> info : infos) {
final AuthorityInfo authorityInfo = info.first;
final SyncStatusInfo status = info.second;
- if (TextUtils.isEmpty(authorityInfo.authority)) {
- Log.e(TAG, "Got an empty provider string. Skipping: " + authorityInfo);
- continue;
- }
- // skip the sync if the account of this operation no longer exists
- if (!containsAccountAndUser(
- accounts, authorityInfo.account, authorityInfo.userId)) {
- continue;
- }
- if (!mSyncStorageEngine.getMasterSyncAutomatically(authorityInfo.userId)
- || !mSyncStorageEngine.getSyncAutomatically(
- authorityInfo.account, authorityInfo.userId,
- authorityInfo.authority)) {
+ if (TextUtils.isEmpty(authorityInfo.target.provider)) {
+ Log.e(TAG, "Got an empty provider string. Skipping: "
+ + authorityInfo.target.provider);
continue;
}
- if (getIsSyncable(
- authorityInfo.account, authorityInfo.userId, authorityInfo.authority)
- == 0) {
+ if (!isDispatchable(authorityInfo.target)) {
continue;
}
for (int i = 0, N = authorityInfo.periodicSyncs.size(); i < N; i++) {
final PeriodicSync sync = authorityInfo.periodicSyncs.get(i);
final Bundle extras = sync.extras;
- final long periodInMillis = sync.period * 1000;
- final long flexInMillis = sync.flexTime * 1000;
+ final Long periodInMillis = sync.period * 1000;
+ final Long flexInMillis = sync.flexTime * 1000;
// Skip if the period is invalid.
if (periodInMillis <= 0) {
continue;
}
// Find when this periodic sync was last scheduled to run.
final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i);
+ final long shiftedLastPollTimeAbsolute =
+ (0 < lastPollTimeAbsolute - mSyncRandomOffsetMillis) ?
+ (lastPollTimeAbsolute - mSyncRandomOffsetMillis) : 0;
long remainingMillis
= periodInMillis - (shiftedNowAbsolute % periodInMillis);
long timeSinceLastRunMillis
@@ -2054,12 +2204,13 @@ public class SyncManager {
boolean runEarly = remainingMillis <= flexInMillis
&& timeSinceLastRunMillis > periodInMillis - flexInMillis;
if (isLoggable) {
- Log.v(TAG, "sync: " + i + " for " + authorityInfo.authority + "."
+ Log.v(TAG, "sync: " + i + " for " + authorityInfo.target + "."
+ " period: " + (periodInMillis)
+ " flex: " + (flexInMillis)
+ " remaining: " + (remainingMillis)
+ " time_since_last: " + timeSinceLastRunMillis
+ " last poll absol: " + lastPollTimeAbsolute
+ + " last poll shifed: " + shiftedLastPollTimeAbsolute
+ " shifted now: " + shiftedNowAbsolute
+ " run_early: " + runEarly);
}
@@ -2073,41 +2224,49 @@ public class SyncManager {
* future, sync now and reinitialize. This can happen for
* example if the user changed the time, synced and changed
* back.
- * Case 3: If we failed to sync at the last scheduled
- * time.
+ * Case 3: If we failed to sync at the last scheduled time.
* Case 4: This sync is close enough to the time that we can schedule it.
*/
- if (runEarly // Case 4
- || remainingMillis == periodInMillis // Case 1
+ if (remainingMillis == periodInMillis // Case 1
|| lastPollTimeAbsolute > nowAbsolute // Case 2
- || timeSinceLastRunMillis >= periodInMillis) { // Case 3
+ || timeSinceLastRunMillis >= periodInMillis // Case 3
+ || runEarly) { // Case 4
// Sync now
-
- final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
- authorityInfo.account, authorityInfo.userId,
- authorityInfo.authority);
- final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
- syncAdapterInfo = mSyncAdapters.getServiceInfo(
- SyncAdapterType.newKey(
- authorityInfo.authority, authorityInfo.account.type),
- authorityInfo.userId);
- if (syncAdapterInfo == null) {
- continue;
- }
+ SyncStorageEngine.EndPoint target = authorityInfo.target;
+ final Pair<Long, Long> backoff =
+ mSyncStorageEngine.getBackoff(target);
mSyncStorageEngine.setPeriodicSyncTime(authorityInfo.ident,
authorityInfo.periodicSyncs.get(i), nowAbsolute);
- scheduleSyncOperation(
- new SyncOperation(authorityInfo.account, authorityInfo.userId,
- SyncOperation.REASON_PERIODIC,
- SyncStorageEngine.SOURCE_PERIODIC,
- authorityInfo.authority, extras,
- 0 /* runtime */, 0 /* flex */,
- backoff != null ? backoff.first : 0,
- mSyncStorageEngine.getDelayUntilTime(
- authorityInfo.account, authorityInfo.userId,
- authorityInfo.authority),
- syncAdapterInfo.type.allowParallelSyncs()));
-
+
+ if (target.target_provider) {
+ final RegisteredServicesCache.ServiceInfo<SyncAdapterType>
+ syncAdapterInfo = mSyncAdapters.getServiceInfo(
+ SyncAdapterType.newKey(
+ target.provider, target.account.type),
+ target.userId);
+ if (syncAdapterInfo == null) {
+ continue;
+ }
+ scheduleSyncOperation(
+ new SyncOperation(target.account, target.userId,
+ SyncOperation.REASON_PERIODIC,
+ SyncStorageEngine.SOURCE_PERIODIC,
+ target.provider, extras,
+ 0 /* runtime */, 0 /* flex */,
+ backoff != null ? backoff.first : 0,
+ mSyncStorageEngine.getDelayUntilTime(target),
+ syncAdapterInfo.type.allowParallelSyncs()));
+ } else if (target.target_service) {
+ scheduleSyncOperation(
+ new SyncOperation(target.service, target.userId,
+ SyncOperation.REASON_PERIODIC,
+ SyncStorageEngine.SOURCE_PERIODIC,
+ extras,
+ 0 /* runtime */,
+ 0 /* flex */,
+ backoff != null ? backoff.first : 0,
+ mSyncStorageEngine.getDelayUntilTime(target)));
+ }
}
// Compute when this periodic sync should next run.
long nextPollTimeAbsolute;
@@ -2154,8 +2313,7 @@ public class SyncManager {
// If the accounts aren't known yet then we aren't ready to run. We will be kicked
// when the account lookup request does complete.
- AccountAndUser[] accounts = mRunningAccounts;
- if (accounts == INITIAL_ACCOUNTS_ARRAY) {
+ if (mRunningAccounts == INITIAL_ACCOUNTS_ARRAY) {
if (isLoggable) {
Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
}
@@ -2165,9 +2323,6 @@ public class SyncManager {
// Otherwise consume SyncOperations from the head of the SyncQueue until one is
// found that is runnable (not disabled, etc). If that one is ready to run then
// start it, otherwise just get out.
- final boolean backgroundDataUsageAllowed =
- getConnectivityManager().getBackgroundDataSetting();
-
final long now = SystemClock.elapsedRealtime();
// will be set to the next time that a sync should be considered for running
@@ -2189,40 +2344,23 @@ public class SyncManager {
while (operationIterator.hasNext()) {
final SyncOperation op = operationIterator.next();
- // Drop the sync if the account of this operation no longer exists.
- if (!containsAccountAndUser(accounts, op.account, op.userId)) {
- operationIterator.remove();
- mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+ // If the user is not running, skip the request.
+ if (!activityManager.isUserRunning(op.target.userId)) {
+ final UserInfo userInfo = mUserManager.getUserInfo(op.target.userId);
+ if (userInfo == null) {
+ removedUsers.add(op.target.userId);
+ }
if (isLoggable) {
- Log.v(TAG, " Dropping sync operation: account doesn't exist.");
+ Log.v(TAG, " Dropping all sync operations for + "
+ + op.target.userId + ": user not running.");
}
continue;
}
-
- // Drop this sync request if it isn't syncable.
- int syncableState = getIsSyncable(
- op.account, op.userId, op.authority);
- if (syncableState == 0) {
+ if (!isOperationValidLocked(op)) {
operationIterator.remove();
mSyncStorageEngine.deleteFromPending(op.pendingOperation);
- if (isLoggable) {
- Log.v(TAG, " Dropping sync operation: isSyncable == 0.");
- }
continue;
}
-
- // If the user is not running, drop the request.
- if (!activityManager.isUserRunning(op.userId)) {
- final UserInfo userInfo = mUserManager.getUserInfo(op.userId);
- if (userInfo == null) {
- removedUsers.add(op.userId);
- }
- if (isLoggable) {
- Log.v(TAG, " Dropping sync operation: user not running.");
- }
- continue;
- }
-
// If the next run time is in the future, even given the flexible scheduling,
// return the time.
if (op.effectiveRunTime - op.flexTime > now) {
@@ -2230,51 +2368,16 @@ public class SyncManager {
nextReadyToRunTime = op.effectiveRunTime;
}
if (isLoggable) {
- Log.v(TAG, " Dropping sync operation: Sync too far in future.");
+ Log.v(TAG, " Not running sync operation: Sync too far in future."
+ + "effective: " + op.effectiveRunTime + " flex: " + op.flexTime
+ + " now: " + now);
}
continue;
}
-
- // If the op isn't allowed on metered networks and we're on one, drop it.
- if (getConnectivityManager().isActiveNetworkMetered()
- && op.isMeteredDisallowed()) {
- operationIterator.remove();
- mSyncStorageEngine.deleteFromPending(op.pendingOperation);
- continue;
- }
-
- // TODO: change this behaviour for non-registered syncs.
- final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
- syncAdapterInfo = mSyncAdapters.getServiceInfo(
- SyncAdapterType.newKey(op.authority, op.account.type), op.userId);
-
- // only proceed if network is connected for requesting UID
- final boolean uidNetworkConnected;
- if (syncAdapterInfo != null) {
- final NetworkInfo networkInfo = getConnectivityManager()
- .getActiveNetworkInfoForUid(syncAdapterInfo.uid);
- uidNetworkConnected = networkInfo != null && networkInfo.isConnected();
- } else {
- uidNetworkConnected = false;
- }
-
- // skip the sync if it isn't manual, and auto sync or
- // background data usage is disabled or network is
- // disconnected for the target UID.
- if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
- && (syncableState > 0)
- && (!mSyncStorageEngine.getMasterSyncAutomatically(op.userId)
- || !backgroundDataUsageAllowed
- || !uidNetworkConnected
- || !mSyncStorageEngine.getSyncAutomatically(
- op.account, op.userId, op.authority))) {
- operationIterator.remove();
- mSyncStorageEngine.deleteFromPending(op.pendingOperation);
- continue;
- }
-
+ // Add this sync to be run.
operations.add(op);
}
+
for (Integer user : removedUsers) {
// if it's still removed
if (mUserManager.getUserInfo(user) == null) {
@@ -2316,13 +2419,9 @@ public class SyncManager {
}
}
}
- if (activeOp.account.type.equals(candidate.account.type)
- && activeOp.authority.equals(candidate.authority)
- && activeOp.userId == candidate.userId
- && (!activeOp.allowParallelSyncs
- || activeOp.account.name.equals(candidate.account.name))) {
+ if (activeOp.isConflict(candidate)) {
conflict = activeSyncContext;
- // don't break out since we want to do a full count of the varieties
+ // don't break out since we want to do a full count of the varieties.
} else {
if (candidateIsInitialization == activeOp.isInitialization()
&& activeSyncContext.mStartTime + MAX_TIME_PER_SYNC < now) {
@@ -2372,8 +2471,8 @@ public class SyncManager {
// is null. Reschedule the active sync and start the candidate.
toReschedule = oldestNonExpeditedRegular;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "canceling and rescheduling sync since an expedited is ready to run, "
- + oldestNonExpeditedRegular);
+ Log.v(TAG, "canceling and rescheduling sync since an expedited is ready to"
+ + " run, " + oldestNonExpeditedRegular);
}
} else if (longRunning != null
&& (candidateIsInitialization
@@ -2402,7 +2501,114 @@ public class SyncManager {
}
return nextReadyToRunTime;
- }
+ }
+
+ /**
+ * Determine if a sync is no longer valid and should be dropped from the sync queue and its
+ * pending op deleted.
+ * @param op operation for which the sync is to be scheduled.
+ */
+ private boolean isOperationValidLocked(SyncOperation op) {
+ final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+ int targetUid;
+ int state;
+ final SyncStorageEngine.EndPoint target = op.target;
+ boolean syncEnabled = mSyncStorageEngine.getMasterSyncAutomatically(target.userId);
+ if (target.target_provider) {
+ // Drop the sync if the account of this operation no longer exists.
+ AccountAndUser[] accounts = mRunningAccounts;
+ if (!containsAccountAndUser(accounts, target.account, target.userId)) {
+ if (isLoggable) {
+ Log.v(TAG, " Dropping sync operation: account doesn't exist.");
+ }
+ return false;
+ }
+ // Drop this sync request if it isn't syncable.
+ state = getIsSyncable(target.account, target.userId, target.provider);
+ if (state == 0) {
+ if (isLoggable) {
+ Log.v(TAG, " Dropping sync operation: isSyncable == 0.");
+ }
+ return false;
+ }
+ syncEnabled = syncEnabled && mSyncStorageEngine.getSyncAutomatically(
+ target.account, target.userId, target.provider);
+
+ final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
+ syncAdapterInfo = mSyncAdapters.getServiceInfo(
+ SyncAdapterType.newKey(
+ target.provider, target.account.type), target.userId);
+ if (syncAdapterInfo != null) {
+ targetUid = syncAdapterInfo.uid;
+ } else {
+ if (isLoggable) {
+ Log.v(TAG, " Dropping sync operation: No sync adapter registered"
+ + "for: " + target);
+ }
+ return false;
+ }
+ } else if (target.target_service) {
+ state = mSyncStorageEngine.getIsTargetServiceActive(target.service, target.userId)
+ ? 1 : 0;
+ if (state == 0) {
+ // TODO: Change this to not drop disabled syncs - keep them in the pending queue.
+ if (isLoggable) {
+ Log.v(TAG, " Dropping sync operation: isActive == 0.");
+ }
+ return false;
+ }
+ try {
+ targetUid = mContext.getPackageManager()
+ .getServiceInfo(target.service, 0)
+ .applicationInfo
+ .uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ if (isLoggable) {
+ Log.v(TAG, " Dropping sync operation: No service registered for: "
+ + target.service);
+ }
+ return false;
+ }
+ } else {
+ Log.e(TAG, "Unknown target for Sync Op: " + target);
+ return false;
+ }
+
+ // We ignore system settings that specify the sync is invalid if:
+ // 1) It's manual - we try it anyway. When/if it fails it will be rescheduled.
+ // or
+ // 2) it's an initialisation sync - we just need to connect to it.
+ final boolean ignoreSystemConfiguration =
+ op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
+ || (state < 0);
+
+ // Sync not enabled.
+ if (!syncEnabled && !ignoreSystemConfiguration) {
+ if (isLoggable) {
+ Log.v(TAG, " Dropping sync operation: disallowed by settings/network.");
+ }
+ return false;
+ }
+ // Network down.
+ final NetworkInfo networkInfo = getConnectivityManager()
+ .getActiveNetworkInfoForUid(targetUid);
+ final boolean uidNetworkConnected = networkInfo != null && networkInfo.isConnected();
+ if (!uidNetworkConnected && !ignoreSystemConfiguration) {
+ if (isLoggable) {
+ Log.v(TAG, " Dropping sync operation: disallowed by settings/network.");
+ }
+ return false;
+ }
+ // Metered network.
+ if (op.isNotAllowedOnMetered() && getConnectivityManager().isActiveNetworkMetered()
+ && !ignoreSystemConfiguration) {
+ if (isLoggable) {
+ Log.v(TAG, " Dropping sync operation: not allowed on metered network.");
+ }
+ return false;
+ }
+ return true;
+ }
private boolean dispatchSyncOperation(SyncOperation op) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -2412,27 +2618,48 @@ public class SyncManager {
Log.v(TAG, syncContext.toString());
}
}
-
- // connect to the sync adapter
- SyncAdapterType syncAdapterType = SyncAdapterType.newKey(op.authority, op.account.type);
- final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
- syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType, op.userId);
- if (syncAdapterInfo == null) {
- Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
- + ", removing settings for it");
- mSyncStorageEngine.removeAuthority(op.account, op.userId, op.authority);
- return false;
+ // Connect to the sync adapter.
+ int targetUid;
+ ComponentName targetComponent;
+ final SyncStorageEngine.EndPoint info = op.target;
+ if (info.target_provider) {
+ SyncAdapterType syncAdapterType =
+ SyncAdapterType.newKey(info.provider, info.account.type);
+ final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
+ syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType, info.userId);
+ if (syncAdapterInfo == null) {
+ Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
+ + ", removing settings for it");
+ mSyncStorageEngine.removeAuthority(info);
+ return false;
+ }
+ targetUid = syncAdapterInfo.uid;
+ targetComponent = syncAdapterInfo.componentName;
+ } else {
+ // TODO: Store the uid of the service as part of the authority info in order to
+ // avoid this call?
+ try {
+ targetUid = mContext.getPackageManager()
+ .getServiceInfo(info.service, 0)
+ .applicationInfo
+ .uid;
+ targetComponent = info.service;
+ } catch(PackageManager.NameNotFoundException e) {
+ Log.d(TAG, "Can't find a service for " + info.service
+ + ", removing settings for it");
+ mSyncStorageEngine.removeAuthority(info);
+ return false;
+ }
}
-
ActiveSyncContext activeSyncContext =
- new ActiveSyncContext(op, insertStartSyncEvent(op), syncAdapterInfo.uid);
+ new ActiveSyncContext(op, insertStartSyncEvent(op), targetUid);
activeSyncContext.mSyncInfo = mSyncStorageEngine.addActiveSync(activeSyncContext);
mActiveSyncContexts.add(activeSyncContext);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext);
}
- if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo, op.userId)) {
- Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
+ if (!activeSyncContext.bindToSyncAdapter(targetComponent, info.userId)) {
+ Log.e(TAG, "Bind attempt failed - target: " + targetComponent);
closeActiveSyncContext(activeSyncContext);
return false;
}
@@ -2440,47 +2667,54 @@ public class SyncManager {
return true;
}
- private void runBoundToSyncAdapter(final ActiveSyncContext activeSyncContext,
- ISyncAdapter syncAdapter) {
- activeSyncContext.mSyncAdapter = syncAdapter;
+ private void runBoundToAdapter(final ActiveSyncContext activeSyncContext,
+ IBinder syncAdapter) {
final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
try {
activeSyncContext.mIsLinkedToDeath = true;
- syncAdapter.asBinder().linkToDeath(activeSyncContext, 0);
-
- syncAdapter.startSync(activeSyncContext, syncOperation.authority,
- syncOperation.account, syncOperation.extras);
+ syncAdapter.linkToDeath(activeSyncContext, 0);
+
+ if (syncOperation.target.target_provider) {
+ activeSyncContext.mSyncAdapter = ISyncAdapter.Stub.asInterface(syncAdapter);
+ activeSyncContext.mSyncAdapter
+ .startSync(activeSyncContext, syncOperation.target.provider,
+ syncOperation.target.account, syncOperation.extras);
+ } else if (syncOperation.target.target_service) {
+ activeSyncContext.mSyncServiceAdapter =
+ ISyncServiceAdapter.Stub.asInterface(syncAdapter);
+ activeSyncContext.mSyncServiceAdapter
+ .startSync(activeSyncContext, syncOperation.extras);
+ }
} catch (RemoteException remoteExc) {
Log.d(TAG, "maybeStartNextSync: caught a RemoteException, rescheduling", remoteExc);
closeActiveSyncContext(activeSyncContext);
increaseBackoffSetting(syncOperation);
- scheduleSyncOperation(new SyncOperation(syncOperation));
+ scheduleSyncOperation(
+ new SyncOperation(syncOperation, 0L /* newRunTimeFromNow */));
} catch (RuntimeException exc) {
closeActiveSyncContext(activeSyncContext);
Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
}
}
- private void cancelActiveSyncLocked(Account account, int userId, String authority) {
+ /**
+ * Cancel the sync for the provided target that matches the given bundle.
+ * @param info can have null fields to indicate all the active syncs for that field.
+ */
+ private void cancelActiveSyncLocked(SyncStorageEngine.EndPoint info, Bundle extras) {
ArrayList<ActiveSyncContext> activeSyncs =
new ArrayList<ActiveSyncContext>(mActiveSyncContexts);
for (ActiveSyncContext activeSyncContext : activeSyncs) {
if (activeSyncContext != null) {
- // if an account was specified then only cancel the sync if it matches
- if (account != null) {
- if (!account.equals(activeSyncContext.mSyncOperation.account)) {
- continue;
- }
- }
- // if an authority was specified then only cancel the sync if it matches
- if (authority != null) {
- if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
- continue;
- }
+ final SyncStorageEngine.EndPoint opInfo =
+ activeSyncContext.mSyncOperation.target;
+ if (!opInfo.matchesSpec(info)) {
+ continue;
}
- // check if the userid matches
- if (userId != UserHandle.USER_ALL
- && userId != activeSyncContext.mSyncOperation.userId) {
+ if (extras != null &&
+ !syncExtrasEquals(activeSyncContext.mSyncOperation.extras,
+ extras,
+ false /* no config settings */)) {
continue;
}
runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */,
@@ -2493,16 +2727,20 @@ public class SyncManager {
ActiveSyncContext activeSyncContext) {
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+ final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
+ final SyncStorageEngine.EndPoint info = syncOperation.target;
+
if (activeSyncContext.mIsLinkedToDeath) {
- activeSyncContext.mSyncAdapter.asBinder().unlinkToDeath(activeSyncContext, 0);
+ if (info.target_provider) {
+ activeSyncContext.mSyncAdapter.asBinder().unlinkToDeath(activeSyncContext, 0);
+ } else {
+ activeSyncContext.mSyncServiceAdapter.asBinder()
+ .unlinkToDeath(activeSyncContext, 0);
+ }
activeSyncContext.mIsLinkedToDeath = false;
}
closeActiveSyncContext(activeSyncContext);
-
- final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
-
final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime;
-
String historyMessage;
int downstreamActivity;
int upstreamActivity;
@@ -2544,6 +2782,12 @@ public class SyncManager {
} catch (RemoteException e) {
// we don't need to retry this in this case
}
+ } else if (activeSyncContext.mSyncServiceAdapter != null) {
+ try {
+ activeSyncContext.mSyncServiceAdapter.cancelSync(activeSyncContext);
+ } catch (RemoteException e) {
+ // we don't need to retry this in this case
+ }
}
historyMessage = SyncStorageEngine.MESG_CANCELED;
downstreamActivity = 0;
@@ -2553,24 +2797,35 @@ public class SyncManager {
stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
upstreamActivity, downstreamActivity, elapsedTime);
- if (syncResult != null && syncResult.tooManyDeletions) {
- installHandleTooManyDeletesNotification(syncOperation.account,
- syncOperation.authority, syncResult.stats.numDeletes,
- syncOperation.userId);
+ // Check for full-resync and schedule it after closing off the last sync.
+ if (info.target_provider) {
+ if (syncResult != null && syncResult.tooManyDeletions) {
+ installHandleTooManyDeletesNotification(info.account,
+ info.provider, syncResult.stats.numDeletes,
+ info.userId);
+ } else {
+ mNotificationMgr.cancelAsUser(null,
+ info.account.hashCode() ^ info.provider.hashCode(),
+ new UserHandle(info.userId));
+ }
+ if (syncResult != null && syncResult.fullSyncRequested) {
+ scheduleSyncOperation(
+ new SyncOperation(info.account, info.userId,
+ syncOperation.reason,
+ syncOperation.syncSource, info.provider, new Bundle(),
+ 0 /* delay */, 0 /* flex */,
+ syncOperation.backoff, syncOperation.delayUntil,
+ syncOperation.allowParallelSyncs));
+ }
} else {
- mNotificationMgr.cancelAsUser(null,
- syncOperation.account.hashCode() ^ syncOperation.authority.hashCode(),
- new UserHandle(syncOperation.userId));
- }
-
- if (syncResult != null && syncResult.fullSyncRequested) {
- scheduleSyncOperation(
- new SyncOperation(syncOperation.account, syncOperation.userId,
- syncOperation.reason,
- syncOperation.syncSource, syncOperation.authority, new Bundle(),
- 0 /* delay */, 0 /* flex */,
- syncOperation.backoff, syncOperation.delayUntil,
- syncOperation.allowParallelSyncs));
+ if (syncResult != null && syncResult.fullSyncRequested) {
+ scheduleSyncOperation(
+ new SyncOperation(info.service, info.userId,
+ syncOperation.reason,
+ syncOperation.syncSource, new Bundle(),
+ 0 /* delay */, 0 /* flex */,
+ syncOperation.backoff, syncOperation.delayUntil));
+ }
}
// no need to schedule an alarm, as that will be done by our caller.
}
@@ -2579,7 +2834,7 @@ public class SyncManager {
activeSyncContext.close();
mActiveSyncContexts.remove(activeSyncContext);
mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo,
- activeSyncContext.mSyncOperation.userId);
+ activeSyncContext.mSyncOperation.target.userId);
}
/**
@@ -2842,26 +3097,16 @@ public class SyncManager {
}
public long insertStartSyncEvent(SyncOperation syncOperation) {
- final int source = syncOperation.syncSource;
final long now = System.currentTimeMillis();
-
- EventLog.writeEvent(2720, syncOperation.authority,
- SyncStorageEngine.EVENT_START, source,
- syncOperation.account.name.hashCode());
-
- return mSyncStorageEngine.insertStartSyncEvent(
- syncOperation.account, syncOperation.userId, syncOperation.reason,
- syncOperation.authority,
- now, source, syncOperation.isInitialization(), syncOperation.extras
- );
+ EventLog.writeEvent(2720,
+ syncOperation.toEventLog(SyncStorageEngine.EVENT_START));
+ return mSyncStorageEngine.insertStartSyncEvent(syncOperation, now);
}
public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage,
int upstreamActivity, int downstreamActivity, long elapsedTime) {
- EventLog.writeEvent(2720, syncOperation.authority,
- SyncStorageEngine.EVENT_STOP, syncOperation.syncSource,
- syncOperation.account.name.hashCode());
-
+ EventLog.writeEvent(2720,
+ syncOperation.toEventLog(SyncStorageEngine.EVENT_STOP));
mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime,
resultMessage, downstreamActivity, upstreamActivity);
}
@@ -2876,6 +3121,83 @@ public class SyncManager {
return false;
}
+ /**
+ * Sync extra comparison function.
+ * @param b1 bundle to compare
+ * @param b2 other bundle to compare
+ * @param includeSyncSettings if false, ignore system settings in bundle.
+ */
+ public static boolean syncExtrasEquals(Bundle b1, Bundle b2, boolean includeSyncSettings) {
+ if (b1 == b2) {
+ return true;
+ }
+ // Exit early if we can.
+ if (includeSyncSettings && b1.size() != b2.size()) {
+ return false;
+ }
+ Bundle bigger = b1.size() > b2.size() ? b1 : b2;
+ Bundle smaller = b1.size() > b2.size() ? b2 : b1;
+ for (String key : bigger.keySet()) {
+ if (!includeSyncSettings && isSyncSetting(key)) {
+ continue;
+ }
+ if (!smaller.containsKey(key)) {
+ return false;
+ }
+ if (!bigger.get(key).equals(smaller.get(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * TODO: Get rid of this when we separate sync settings extras from dev specified extras.
+ * @return true if the provided key is used by the SyncManager in scheduling the sync.
+ */
+ private static boolean isSyncSetting(String key) {
+ if (key.equals(ContentResolver.SYNC_EXTRAS_EXPEDITED)) {
+ return true;
+ }
+ if (key.equals(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS)) {
+ return true;
+ }
+ if (key.equals(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF)) {
+ return true;
+ }
+ if (key.equals(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY)) {
+ return true;
+ }
+ if (key.equals(ContentResolver.SYNC_EXTRAS_MANUAL)) {
+ return true;
+ }
+ if (key.equals(ContentResolver.SYNC_EXTRAS_UPLOAD)) {
+ return true;
+ }
+ if (key.equals(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS)) {
+ return true;
+ }
+ if (key.equals(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS)) {
+ return true;
+ }
+ if (key.equals(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD)) {
+ return true;
+ }
+ if (key.equals(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD)) {
+ return true;
+ }
+ if (key.equals(ContentResolver.SYNC_EXTRAS_PRIORITY)) {
+ return true;
+ }
+ if (key.equals(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED)) {
+ return true;
+ }
+ if (key.equals(ContentResolver.SYNC_EXTRAS_INITIALIZE)) {
+ return true;
+ }
+ return false;
+ }
+
static class PrintTable {
private ArrayList<Object[]> mTable = Lists.newArrayList();
private final int mCols;
@@ -2913,6 +3235,7 @@ public class SyncManager {
totalLength += maxLength;
formats[col] = String.format("%%-%ds", maxLength);
}
+ formats[mCols - 1] = "%s";
printRow(out, formats, mTable.get(0));
totalLength += (mCols - 1) * 2;
for (int i = 0; i < totalLength; ++i) {
diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java
index 67e3b09..9a4abce 100644
--- a/services/core/java/com/android/server/content/SyncOperation.java
+++ b/services/core/java/com/android/server/content/SyncOperation.java
@@ -20,10 +20,9 @@ import android.accounts.Account;
import android.content.pm.PackageManager;
import android.content.ComponentName;
import android.content.ContentResolver;
-import android.content.SyncRequest;
import android.os.Bundle;
import android.os.SystemClock;
-import android.util.Pair;
+import android.util.Log;
/**
* Value type that represents a sync operation.
@@ -32,10 +31,13 @@ import android.util.Pair;
* {@hide}
*/
public class SyncOperation implements Comparable {
+ public static final String TAG = "SyncManager";
+
public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1;
public static final int REASON_ACCOUNTS_UPDATED = -2;
public static final int REASON_SERVICE_CHANGED = -3;
public static final int REASON_PERIODIC = -4;
+ /** Sync started because it has just been set to isSyncable. */
public static final int REASON_IS_SYNCABLE = -5;
/** Sync started because it has just been set to sync automatically. */
public static final int REASON_SYNC_AUTO = -6;
@@ -54,25 +56,27 @@ public class SyncOperation implements Comparable {
"UserStart",
};
- /** Account info to identify a SyncAdapter registered with the system. */
- public final Account account;
- /** Authority info to identify a SyncAdapter registered with the system. */
- public final String authority;
- /** Service to which this operation will bind to perform the sync. */
- public final ComponentName service;
- public final int userId;
+ public static final int SYNC_TARGET_UNKNOWN = 0;
+ public static final int SYNC_TARGET_ADAPTER = 1;
+ public static final int SYNC_TARGET_SERVICE = 2;
+
+ /** Identifying info for the target for this operation. */
+ public final SyncStorageEngine.EndPoint target;
+ /** Why this sync was kicked off. {@link #REASON_NAMES} */
public final int reason;
- public int syncSource;
+ /** Where this sync was initiated. */
+ public final int syncSource;
public final boolean allowParallelSyncs;
- public Bundle extras;
public final String key;
/** Internal boolean to avoid reading a bundle everytime we want to compare operations. */
private final boolean expedited;
+ public Bundle extras;
+ /** Bare-bones version of this operation that is persisted across reboots. */
public SyncStorageEngine.PendingOperation pendingOperation;
/** Elapsed real time in millis at which to run this sync. */
public long latestRunTime;
/** Set by the SyncManager in order to delay retries. */
- public Long backoff;
+ public long backoff;
/** Specified by the adapter to delay subsequent sync operations. */
public long delayUntil;
/**
@@ -83,56 +87,68 @@ public class SyncOperation implements Comparable {
/** Amount of time before {@link #effectiveRunTime} from which this sync can run. */
public long flexTime;
- public SyncOperation(Account account, int userId, int reason, int source, String authority,
+ /** Descriptive string key for this operation */
+ public String wakeLockName;
+
+ public SyncOperation(Account account, int userId, int reason, int source, String provider,
Bundle extras, long runTimeFromNow, long flexTime, long backoff,
long delayUntil, boolean allowParallelSyncs) {
- this.service = null;
- this.account = account;
- this.authority = authority;
- this.userId = userId;
+ this(new SyncStorageEngine.EndPoint(account, provider, userId),
+ reason, source, extras, runTimeFromNow, flexTime, backoff, delayUntil,
+ allowParallelSyncs);
+ }
+
+ public SyncOperation(ComponentName service, int userId, int reason, int source,
+ Bundle extras, long runTimeFromNow, long flexTime, long backoff,
+ long delayUntil) {
+ this(new SyncStorageEngine.EndPoint(service, userId), reason, source, extras,
+ runTimeFromNow, flexTime, backoff, delayUntil, true /* allowParallelSyncs */);
+ }
+
+ private SyncOperation(SyncStorageEngine.EndPoint info, int reason, int source, Bundle extras,
+ long runTimeFromNow, long flexTime, long backoff, long delayUntil,
+ boolean allowParallelSyncs) {
+ this.target = info;
this.reason = reason;
this.syncSource = source;
- this.allowParallelSyncs = allowParallelSyncs;
this.extras = new Bundle(extras);
cleanBundle(this.extras);
this.delayUntil = delayUntil;
this.backoff = backoff;
+ this.allowParallelSyncs = allowParallelSyncs;
final long now = SystemClock.elapsedRealtime();
- // Checks the extras bundle. Must occur after we set the internal bundle.
+ // Set expedited based on runTimeFromNow. The SyncManager specifies whether the op is
+ // expedited (Not done solely based on bundle).
if (runTimeFromNow < 0) {
+ this.expedited = true;
// Sanity check: Will always be true.
if (!this.extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) {
this.extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
}
- this.expedited = true;
this.latestRunTime = now;
this.flexTime = 0;
} else {
- this.extras.remove(ContentResolver.SYNC_EXTRAS_EXPEDITED);
this.expedited = false;
+ this.extras.remove(ContentResolver.SYNC_EXTRAS_EXPEDITED);
this.latestRunTime = now + runTimeFromNow;
this.flexTime = flexTime;
}
updateEffectiveRunTime();
- this.key = toKey();
+ this.key = toKey(info, this.extras);
+ }
+
+ /** Used to reschedule a sync at a new point in time. */
+ public SyncOperation(SyncOperation other, long newRunTimeFromNow) {
+ this(other.target, other.reason, other.syncSource, new Bundle(other.extras),
+ newRunTimeFromNow,
+ 0L /* In back-off so no flex */,
+ other.backoff,
+ other.delayUntil,
+ other.allowParallelSyncs);
}
- /** Only used to immediately reschedule a sync. */
- SyncOperation(SyncOperation other) {
- this.service = other.service;
- this.account = other.account;
- this.authority = other.authority;
- this.userId = other.userId;
- this.reason = other.reason;
- this.syncSource = other.syncSource;
- this.extras = new Bundle(other.extras);
- this.expedited = other.expedited;
- this.latestRunTime = SystemClock.elapsedRealtime();
- this.flexTime = 0L;
- this.backoff = other.backoff;
- this.allowParallelSyncs = other.allowParallelSyncs;
- this.updateEffectiveRunTime();
- this.key = toKey();
+ public boolean matchesAuthority(SyncOperation other) {
+ return this.target.matchesSpec(other.target);
}
/**
@@ -150,10 +166,6 @@ public class SyncOperation implements Comparable {
removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_EXPEDITED);
removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISALLOW_METERED);
-
- // Remove Config data.
- bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD);
- bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD);
}
private void removeFalseExtra(Bundle bundle, String extraName) {
@@ -162,24 +174,52 @@ public class SyncOperation implements Comparable {
}
}
+ /**
+ * Determine whether if this sync operation is running, the provided operation would conflict
+ * with it.
+ * Parallel syncs allow multiple accounts to be synced at the same time.
+ */
+ public boolean isConflict(SyncOperation toRun) {
+ final SyncStorageEngine.EndPoint other = toRun.target;
+ if (target.target_provider) {
+ return target.account.type.equals(other.account.type)
+ && target.provider.equals(other.provider)
+ && target.userId == other.userId
+ && (!allowParallelSyncs
+ || target.account.name.equals(other.account.name));
+ } else {
+ // Ops that target a service default to allow parallel syncs, which is handled by the
+ // service returning SYNC_IN_PROGRESS if they don't.
+ return target.service.equals(other.service) && !allowParallelSyncs;
+ }
+ }
+
@Override
public String toString() {
return dump(null, true);
}
public String dump(PackageManager pm, boolean useOneLine) {
- StringBuilder sb = new StringBuilder()
- .append(account.name)
+ StringBuilder sb = new StringBuilder();
+ if (target.target_provider) {
+ sb.append(target.account.name)
.append(" u")
- .append(userId).append(" (")
- .append(account.type)
+ .append(target.userId).append(" (")
+ .append(target.account.type)
.append(")")
.append(", ")
- .append(authority)
- .append(", ")
- .append(SyncStorageEngine.SOURCES[syncSource])
- .append(", latestRunTime ")
- .append(latestRunTime);
+ .append(target.provider)
+ .append(", ");
+ } else if (target.target_service) {
+ sb.append(target.service.getPackageName())
+ .append(" u")
+ .append(target.userId).append(" (")
+ .append(target.service.getClassName()).append(")")
+ .append(", ");
+ }
+ sb.append(SyncStorageEngine.SOURCES[syncSource])
+ .append(", currentRunTime ")
+ .append(effectiveRunTime);
if (expedited) {
sb.append(", EXPEDITED");
}
@@ -217,10 +257,6 @@ public class SyncOperation implements Comparable {
}
}
- public boolean isMeteredDisallowed() {
- return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false);
- }
-
public boolean isInitialization() {
return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
}
@@ -233,28 +269,39 @@ public class SyncOperation implements Comparable {
return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
}
+ public boolean isNotAllowedOnMetered() {
+ return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false);
+ }
+
/** Changed in V3. */
- private String toKey() {
+ public static String toKey(SyncStorageEngine.EndPoint info, Bundle extras) {
StringBuilder sb = new StringBuilder();
- if (service == null) {
- sb.append("authority: ").append(authority);
- sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type
+ if (info.target_provider) {
+ sb.append("provider: ").append(info.provider);
+ sb.append(" account {name=" + info.account.name
+ + ", user="
+ + info.userId
+ + ", type="
+ + info.account.type
+ "}");
- } else {
+ } else if (info.target_service) {
sb.append("service {package=" )
- .append(service.getPackageName())
+ .append(info.service.getPackageName())
.append(" user=")
- .append(userId)
+ .append(info.userId)
.append(", class=")
- .append(service.getClassName())
+ .append(info.service.getClassName())
.append("}");
+ } else {
+ Log.v(TAG, "Converting SyncOperaton to key, invalid target: " + info.toString());
+ return "";
}
sb.append(" extras: ");
extrasToStringBuilder(extras, sb);
return sb.toString();
}
- public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
+ private static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
sb.append("[");
for (String key : bundle.keySet()) {
sb.append(key).append("=").append(bundle.get(key)).append(" ");
@@ -262,6 +309,23 @@ public class SyncOperation implements Comparable {
sb.append("]");
}
+ public String wakeLockName() {
+ if (wakeLockName != null) {
+ return wakeLockName;
+ }
+ if (target.target_provider) {
+ return (wakeLockName = target.provider
+ + "/" + target.account.type
+ + "/" + target.account.name);
+ } else if (target.target_service) {
+ return (wakeLockName = target.service.getPackageName()
+ + "/" + target.service.getClassName());
+ } else {
+ Log.wtf(TAG, "Invalid target getting wakelock name for operation - " + key);
+ return null;
+ }
+ }
+
/**
* Update the effective run time of this Operation based on latestRunTime (specified at
* creation time of sync), delayUntil (specified by SyncAdapter), or backoff (specified by
@@ -297,4 +361,21 @@ public class SyncOperation implements Comparable {
return 0;
}
}
+
+ // TODO: Test this to make sure that casting to object doesn't lose the type info for EventLog.
+ public Object[] toEventLog(int event) {
+ Object[] logArray = new Object[4];
+ logArray[1] = event;
+ logArray[2] = syncSource;
+ if (target.target_provider) {
+ logArray[0] = target.provider;
+ logArray[3] = target.account.name.hashCode();
+ } else if (target.target_service) {
+ logArray[0] = target.service.getPackageName();
+ logArray[3] = target.service.hashCode();
+ } else {
+ Log.wtf(TAG, "sync op with invalid target: " + key);
+ }
+ return logArray;
+ }
}
diff --git a/services/core/java/com/android/server/content/SyncQueue.java b/services/core/java/com/android/server/content/SyncQueue.java
index 22fa2de..587de1c 100644
--- a/services/core/java/com/android/server/content/SyncQueue.java
+++ b/services/core/java/com/android/server/content/SyncQueue.java
@@ -16,12 +16,11 @@
package com.android.server.content;
-import android.accounts.Account;
import android.content.pm.PackageManager;
-import android.content.pm.RegisteredServicesCache;
import android.content.SyncAdapterType;
import android.content.SyncAdaptersCache;
import android.content.pm.RegisteredServicesCache.ServiceInfo;
+import android.os.Bundle;
import android.os.SystemClock;
import android.text.format.DateUtils;
import android.util.Log;
@@ -60,24 +59,49 @@ public class SyncQueue {
public void addPendingOperations(int userId) {
for (SyncStorageEngine.PendingOperation op : mSyncStorageEngine.getPendingOperations()) {
- if (op.userId != userId) continue;
-
- final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
- op.account, op.userId, op.authority);
- final ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo(
- SyncAdapterType.newKey(op.authority, op.account.type), op.userId);
- if (syncAdapterInfo == null) {
- Log.w(TAG, "Missing sync adapter info for authority " + op.authority + ", userId "
- + op.userId);
- continue;
+ final SyncStorageEngine.EndPoint info = op.target;
+ if (info.userId != userId) continue;
+
+ final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(info);
+ SyncOperation operationToAdd;
+ if (info.target_provider) {
+ final ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo(
+ SyncAdapterType.newKey(info.provider, info.account.type), info.userId);
+ if (syncAdapterInfo == null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Missing sync adapter info for authority " + op.target);
+ }
+ continue;
+ }
+ operationToAdd = new SyncOperation(
+ info.account, info.userId, op.reason, op.syncSource, info.provider,
+ op.extras,
+ op.expedited ? -1 : 0 /* delay */,
+ 0 /* flex */,
+ backoff != null ? backoff.first : 0L,
+ mSyncStorageEngine.getDelayUntilTime(info),
+ syncAdapterInfo.type.allowParallelSyncs());
+ operationToAdd.pendingOperation = op;
+ add(operationToAdd, op);
+ } else if (info.target_service) {
+ try {
+ mPackageManager.getServiceInfo(info.service, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.w(TAG, "Missing sync service for authority " + op.target);
+ }
+ continue;
+ }
+ operationToAdd = new SyncOperation(
+ info.service, info.userId, op.reason, op.syncSource,
+ op.extras,
+ op.expedited ? -1 : 0 /* delay */,
+ 0 /* flex */,
+ backoff != null ? backoff.first : 0,
+ mSyncStorageEngine.getDelayUntilTime(info));
+ operationToAdd.pendingOperation = op;
+ add(operationToAdd, op);
}
- SyncOperation syncOperation = new SyncOperation(
- op.account, op.userId, op.reason, op.syncSource, op.authority, op.extras,
- op.expedited ? -1: 0 /* delay */, 0 /* flex */, backoff != null ? backoff.first : 0,
- mSyncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority),
- syncAdapterInfo.type.allowParallelSyncs());
- syncOperation.pendingOperation = op;
- add(syncOperation, op);
}
}
@@ -117,12 +141,8 @@ public class SyncQueue {
operation.pendingOperation = pop;
// Don't update the PendingOp if one already exists. This really is just a placeholder,
// no actual scheduling info is placed here.
- // TODO: Change this to support service components.
if (operation.pendingOperation == null) {
- pop = new SyncStorageEngine.PendingOperation(
- operation.account, operation.userId, operation.reason, operation.syncSource,
- operation.authority, operation.extras, operation.isExpedited());
- pop = mSyncStorageEngine.insertIntoPending(pop);
+ pop = mSyncStorageEngine.insertIntoPending(operation);
if (pop == null) {
throw new IllegalStateException("error adding pending sync operation "
+ operation);
@@ -134,17 +154,16 @@ public class SyncQueue {
return true;
}
- public void removeUser(int userId) {
+ public void removeUserLocked(int userId) {
ArrayList<SyncOperation> opsToRemove = new ArrayList<SyncOperation>();
for (SyncOperation op : mOperationsMap.values()) {
- if (op.userId == userId) {
+ if (op.target.userId == userId) {
opsToRemove.add(op);
}
}
-
- for (SyncOperation op : opsToRemove) {
- remove(op);
- }
+ for (SyncOperation op : opsToRemove) {
+ remove(op);
+ }
}
/**
@@ -152,8 +171,15 @@ public class SyncQueue {
* @param operation the operation to remove
*/
public void remove(SyncOperation operation) {
+ boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
SyncOperation operationToRemove = mOperationsMap.remove(operation.key);
+ if (isLoggable) {
+ Log.v(TAG, "Attempting to remove: " + operation.key);
+ }
if (operationToRemove == null) {
+ if (isLoggable) {
+ Log.v(TAG, "Could not find: " + operation.key);
+ }
return;
}
if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
@@ -162,41 +188,58 @@ public class SyncQueue {
}
}
- public void onBackoffChanged(Account account, int userId, String providerName, long backoff) {
- // for each op that matches the account and provider update its
+ /** Reset backoffs for all operations in the queue. */
+ public void clearBackoffs() {
+ for (SyncOperation op : mOperationsMap.values()) {
+ op.backoff = 0L;
+ op.updateEffectiveRunTime();
+ }
+ }
+
+ public void onBackoffChanged(SyncStorageEngine.EndPoint target, long backoff) {
+ // For each op that matches the target of the changed op, update its
// backoff and effectiveStartTime
for (SyncOperation op : mOperationsMap.values()) {
- if (op.account.equals(account) && op.authority.equals(providerName)
- && op.userId == userId) {
+ if (op.target.matchesSpec(target)) {
op.backoff = backoff;
op.updateEffectiveRunTime();
}
}
}
- public void onDelayUntilTimeChanged(Account account, String providerName, long delayUntil) {
- // for each op that matches the account and provider update its
- // delayUntilTime and effectiveStartTime
+ public void onDelayUntilTimeChanged(SyncStorageEngine.EndPoint target, long delayUntil) {
+ // for each op that matches the target info of the provided op, change the delay time.
for (SyncOperation op : mOperationsMap.values()) {
- if (op.account.equals(account) && op.authority.equals(providerName)) {
+ if (op.target.matchesSpec(target)) {
op.delayUntil = delayUntil;
op.updateEffectiveRunTime();
}
}
}
- public void remove(Account account, int userId, String authority) {
+ /**
+ * Remove all of the SyncOperations associated with a given target.
+ *
+ * @param info target object provided here can have null Account/provider. This is the case
+ * where you want to remove all ops associated with a provider (null Account) or all ops
+ * associated with an account (null provider).
+ * @param extras option bundle to include to further specify which operation to remove. If this
+ * bundle contains sync settings flags, they are ignored.
+ */
+ public void remove(final SyncStorageEngine.EndPoint info, Bundle extras) {
Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<String, SyncOperation> entry = entries.next();
SyncOperation syncOperation = entry.getValue();
- if (account != null && !syncOperation.account.equals(account)) {
- continue;
- }
- if (authority != null && !syncOperation.authority.equals(authority)) {
+ final SyncStorageEngine.EndPoint opInfo = syncOperation.target;
+ if (!opInfo.matchesSpec(info)) {
continue;
}
- if (userId != syncOperation.userId) {
+ if (extras != null
+ && !SyncManager.syncExtrasEquals(
+ syncOperation.extras,
+ extras,
+ false /* no config flags*/)) {
continue;
}
entries.remove();
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index 178c372..35c494d 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.ISyncStatusObserver;
import android.content.PeriodicSync;
import android.content.SyncInfo;
+import android.content.SyncRequest;
import android.content.SyncStatusInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
@@ -36,10 +37,12 @@ import android.os.Message;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
+import android.util.ArrayMap;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
@@ -71,7 +74,6 @@ import java.util.TimeZone;
public class SyncStorageEngine extends Handler {
private static final String TAG = "SyncManager";
- private static final boolean DEBUG = false;
private static final String TAG_FILE = "SyncManagerFile";
private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId";
@@ -108,10 +110,7 @@ public class SyncStorageEngine extends Handler {
/** Enum value for a local-initiated sync. */
public static final int SOURCE_LOCAL = 1;
- /**
- * Enum value for a poll-based sync (e.g., upon connection to
- * network)
- */
+ /** Enum value for a poll-based sync (e.g., upon connection to network) */
public static final int SOURCE_POLL = 2;
/** Enum value for a user-initiated sync. */
@@ -119,6 +118,9 @@ public class SyncStorageEngine extends Handler {
/** Enum value for a periodic sync. */
public static final int SOURCE_PERIODIC = 4;
+
+ /** Enum value for a sync started for a service. */
+ public static final int SOURCE_SERVICE = 5;
public static final long NOT_IN_BACKOFF_MODE = -1;
@@ -128,7 +130,8 @@ public class SyncStorageEngine extends Handler {
"LOCAL",
"POLL",
"USER",
- "PERIODIC" };
+ "PERIODIC",
+ "SERVICE"};
// The MESG column will contain one of these or one of the Error types.
public static final String MESG_SUCCESS = "success";
@@ -156,41 +159,54 @@ public class SyncStorageEngine extends Handler {
}
public static class PendingOperation {
- final Account account;
- final int userId;
+ final EndPoint target;
final int reason;
final int syncSource;
- final String authority;
final Bundle extras; // note: read-only.
- final ComponentName serviceName;
final boolean expedited;
- int authorityId;
+ final int authorityId;
+ // No longer used.
+ // Keep around for sake up updating from pending.bin to pending.xml
byte[] flatExtras;
- PendingOperation(Account account, int userId, int reason, int source,
- String authority, Bundle extras, boolean expedited) {
- this.account = account;
- this.userId = userId;
+ PendingOperation(AuthorityInfo authority, int reason, int source,
+ Bundle extras, boolean expedited) {
+ this.target = authority.target;
this.syncSource = source;
this.reason = reason;
- this.authority = authority;
this.extras = extras != null ? new Bundle(extras) : extras;
this.expedited = expedited;
- this.authorityId = -1;
- this.serviceName = null;
+ this.authorityId = authority.ident;
}
PendingOperation(PendingOperation other) {
- this.account = other.account;
- this.userId = other.userId;
this.reason = other.reason;
this.syncSource = other.syncSource;
- this.authority = other.authority;
+ this.target = other.target;
this.extras = other.extras;
this.authorityId = other.authorityId;
this.expedited = other.expedited;
- this.serviceName = other.serviceName;
+ }
+
+ /**
+ * Considered equal if they target the same sync adapter (A
+ * {@link android.content.SyncService}
+ * is considered an adapter), for the same userId.
+ * @param other PendingOperation to compare.
+ * @return true if the two pending ops are the same.
+ */
+ public boolean equals(PendingOperation other) {
+ return target.matchesSpec(other.target);
+ }
+
+ public String toString() {
+ return "service=" + target.service
+ + " user=" + target.userId
+ + " auth=" + target
+ + " account=" + target.account
+ + " src=" + syncSource
+ + " extras=" + extras;
}
}
@@ -204,17 +220,96 @@ public class SyncStorageEngine extends Handler {
}
}
- public static class AuthorityInfo {
+ /** Bare bones representation of a sync target. */
+ public static class EndPoint {
+ public final static EndPoint USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL =
+ new EndPoint(null, null, UserHandle.USER_ALL);
final ComponentName service;
final Account account;
final int userId;
- final String authority;
+ final String provider;
+ final boolean target_service;
+ final boolean target_provider;
+
+ public EndPoint(ComponentName service, int userId) {
+ this.service = service;
+ this.userId = userId;
+ this.account = null;
+ this.provider = null;
+ this.target_service = true;
+ this.target_provider = false;
+ }
+
+ public EndPoint(Account account, String provider, int userId) {
+ this.account = account;
+ this.provider = provider;
+ this.userId = userId;
+ this.service = null;
+ this.target_service = false;
+ this.target_provider = true;
+ }
+
+ /**
+ * An Endpoint for a sync matches if it targets the same sync adapter for the same user.
+ *
+ * @param spec the Endpoint to match. If the spec has null fields, they indicate a wildcard
+ * and match any.
+ */
+ public boolean matchesSpec(EndPoint spec) {
+ if (userId != spec.userId
+ && userId != UserHandle.USER_ALL
+ && spec.userId != UserHandle.USER_ALL) {
+ return false;
+ }
+ if (target_service && spec.target_service) {
+ return service.equals(spec.service);
+ } else if (target_provider && spec.target_provider) {
+ boolean accountsMatch;
+ if (spec.account == null) {
+ accountsMatch = true;
+ } else {
+ accountsMatch = account.equals(spec.account);
+ }
+ boolean providersMatch;
+ if (spec.provider == null) {
+ providersMatch = true;
+ } else {
+ providersMatch = provider.equals(spec.provider);
+ }
+ return accountsMatch && providersMatch;
+ }
+ return false;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (target_provider) {
+ sb.append(account == null ? "ALL ACCS" : account.name)
+ .append("/")
+ .append(provider == null ? "ALL PDRS" : provider);
+ } else if (target_service) {
+ sb.append(service.getPackageName() + "/")
+ .append(service.getClassName());
+ } else {
+ sb.append("invalid target");
+ }
+ sb.append(":u" + userId);
+ return sb.toString();
+ }
+ }
+
+ public static class AuthorityInfo {
+ final EndPoint target;
final int ident;
boolean enabled;
int syncable;
+ /** Time at which this sync will run, taking into account backoff. */
long backoffTime;
+ /** Amount of delay due to backoff. */
long backoffDelay;
+ /** Time offset to add to any requests coming to this target. */
long delayUntil;
+
final ArrayList<PeriodicSync> periodicSyncs;
/**
@@ -224,10 +319,7 @@ public class SyncStorageEngine extends Handler {
* @param toCopy AuthorityInfo to be copied.
*/
AuthorityInfo(AuthorityInfo toCopy) {
- account = toCopy.account;
- userId = toCopy.userId;
- authority = toCopy.authority;
- service = toCopy.service;
+ target = toCopy.target;
ident = toCopy.ident;
enabled = toCopy.enabled;
syncable = toCopy.syncable;
@@ -241,56 +333,40 @@ public class SyncStorageEngine extends Handler {
}
}
- /**
- * Create an authority with one periodic sync scheduled with an empty bundle and syncing
- * every day. An empty bundle is considered equal to any other bundle see
- * {@link PeriodicSync.syncExtrasEquals}.
- * @param account Account that this authority syncs.
- * @param userId which user this sync is registered for.
- * @param userId user for which this authority is registered.
- * @param ident id of this authority.
- */
- AuthorityInfo(Account account, int userId, String authority, int ident) {
- this.account = account;
- this.userId = userId;
- this.authority = authority;
- this.service = null;
- this.ident = ident;
- enabled = SYNC_ENABLED_DEFAULT;
- syncable = -1; // default to "unknown"
- backoffTime = -1; // if < 0 then we aren't in backoff mode
- backoffDelay = -1; // if < 0 then we aren't in backoff mode
+ AuthorityInfo(EndPoint info, int id) {
+ target = info;
+ ident = id;
+ enabled = info.target_provider ?
+ SYNC_ENABLED_DEFAULT : true;
+ // Service is active by default,
+ if (info.target_service) {
+ this.syncable = 1;
+ }
periodicSyncs = new ArrayList<PeriodicSync>();
- // Old version adds one periodic sync a day.
- periodicSyncs.add(new PeriodicSync(account, authority,
- new Bundle(),
- DEFAULT_POLL_FREQUENCY_SECONDS,
- calculateDefaultFlexTime(DEFAULT_POLL_FREQUENCY_SECONDS)));
+ defaultInitialisation();
}
- /**
- * Create an authority with one periodic sync scheduled with an empty bundle and syncing
- * every day using a sync service.
- * @param cname sync service identifier.
- * @param userId user for which this authority is registered.
- * @param ident id of this authority.
- */
- AuthorityInfo(ComponentName cname, int userId, int ident) {
- this.account = null;
- this.userId = userId;
- this.authority = null;
- this.service = cname;
- this.ident = ident;
- // Sync service is always enabled.
- enabled = true;
+ private void defaultInitialisation() {
syncable = -1; // default to "unknown"
backoffTime = -1; // if < 0 then we aren't in backoff mode
backoffDelay = -1; // if < 0 then we aren't in backoff mode
- periodicSyncs = new ArrayList<PeriodicSync>();
- periodicSyncs.add(new PeriodicSync(account, authority,
- new Bundle(),
- DEFAULT_POLL_FREQUENCY_SECONDS,
- calculateDefaultFlexTime(DEFAULT_POLL_FREQUENCY_SECONDS)));
+ PeriodicSync defaultSync;
+ // Old version is one sync a day. Empty bundle gets replaced by any addPeriodicSync()
+ // call.
+ if (target.target_provider) {
+ defaultSync =
+ new PeriodicSync(target.account, target.provider,
+ new Bundle(),
+ DEFAULT_POLL_FREQUENCY_SECONDS,
+ calculateDefaultFlexTime(DEFAULT_POLL_FREQUENCY_SECONDS));
+ periodicSyncs.add(defaultSync);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return target + ", enabled=" + enabled + ", syncable=" + syncable + ", backoff="
+ + backoffTime + ", delay=" + delayUntil;
}
}
@@ -322,16 +398,9 @@ public class SyncStorageEngine extends Handler {
}
interface OnSyncRequestListener {
- /**
- * Called when a sync is needed on an account(s) due to some change in state.
- * @param account
- * @param userId
- * @param reason
- * @param authority
- * @param extras
- */
- public void onSyncRequest(Account account, int userId, int reason, String authority,
- Bundle extras);
+
+ /** Called when a sync is needed on an account(s) due to some change in state. */
+ public void onSyncRequest(EndPoint info, int reason, Bundle extras);
}
// Primary list of all syncable authorities. Also our global lock.
@@ -356,9 +425,9 @@ public class SyncStorageEngine extends Handler {
private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners
= new RemoteCallbackList<ISyncStatusObserver>();
- /** Reverse mapping for component name -> <userid -> authority id>. */
- private final HashMap<ComponentName, SparseArray<AuthorityInfo>> mServices =
- new HashMap<ComponentName, SparseArray<AuthorityInfo>>();
+ /** Reverse mapping for component name -> <userid -> target id>. */
+ private final ArrayMap<ComponentName, SparseArray<AuthorityInfo>> mServices =
+ new ArrayMap<ComponentName, SparseArray<AuthorityInfo>>();
private int mNextAuthorityId = 0;
@@ -535,7 +604,7 @@ public class SyncStorageEngine extends Handler {
mChangeListeners.finishBroadcast();
}
- if (DEBUG) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "reportChange " + which + " to: " + reports);
}
@@ -555,7 +624,8 @@ public class SyncStorageEngine extends Handler {
public boolean getSyncAutomatically(Account account, int userId, String providerName) {
synchronized (mAuthorities) {
if (account != null) {
- AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
+ AuthorityInfo authority = getAuthorityLocked(
+ new EndPoint(account, providerName, userId),
"getSyncAutomatically");
return authority != null && authority.enabled;
}
@@ -563,10 +633,9 @@ public class SyncStorageEngine extends Handler {
int i = mAuthorities.size();
while (i > 0) {
i--;
- AuthorityInfo authority = mAuthorities.valueAt(i);
- if (authority.authority.equals(providerName)
- && authority.userId == userId
- && authority.enabled) {
+ AuthorityInfo authorityInfo = mAuthorities.valueAt(i);
+ if (authorityInfo.target.matchesSpec(new EndPoint(account, providerName, userId))
+ && authorityInfo.enabled) {
return true;
}
}
@@ -576,15 +645,18 @@ public class SyncStorageEngine extends Handler {
public void setSyncAutomatically(Account account, int userId, String providerName,
boolean sync) {
- if (DEBUG) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "setSyncAutomatically: " + /* account + */" provider " + providerName
+ ", user " + userId + " -> " + sync);
}
synchronized (mAuthorities) {
- AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1,
- false);
+ AuthorityInfo authority =
+ getOrCreateAuthorityLocked(
+ new EndPoint(account, providerName, userId),
+ -1 /* ident */,
+ false);
if (authority.enabled == sync) {
- if (DEBUG) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "setSyncAutomatically: already set to " + sync + ", doing nothing");
}
return;
@@ -603,8 +675,9 @@ public class SyncStorageEngine extends Handler {
public int getIsSyncable(Account account, int userId, String providerName) {
synchronized (mAuthorities) {
if (account != null) {
- AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
- "getIsSyncable");
+ AuthorityInfo authority = getAuthorityLocked(
+ new EndPoint(account, providerName, userId),
+ "get authority syncable");
if (authority == null) {
return -1;
}
@@ -614,9 +687,10 @@ public class SyncStorageEngine extends Handler {
int i = mAuthorities.size();
while (i > 0) {
i--;
- AuthorityInfo authority = mAuthorities.valueAt(i);
- if (authority.authority.equals(providerName)) {
- return authority.syncable;
+ AuthorityInfo authorityInfo = mAuthorities.valueAt(i);
+ if (authorityInfo.target != null
+ && authorityInfo.target.provider.equals(providerName)) {
+ return authorityInfo.syncable;
}
}
return -1;
@@ -624,119 +698,178 @@ public class SyncStorageEngine extends Handler {
}
public void setIsSyncable(Account account, int userId, String providerName, int syncable) {
- if (syncable > 1) {
- syncable = 1;
- } else if (syncable < -1) {
- syncable = -1;
- }
- if (DEBUG) {
- Log.d(TAG, "setIsSyncable: " + account + ", provider " + providerName
- + ", user " + userId + " -> " + syncable);
+ setSyncableStateForEndPoint(new EndPoint(account, providerName, userId), syncable);
+ }
+
+ public boolean getIsTargetServiceActive(ComponentName cname, int userId) {
+ synchronized (mAuthorities) {
+ if (cname != null) {
+ AuthorityInfo authority = getAuthorityLocked(
+ new EndPoint(cname, userId),
+ "get service active");
+ if (authority == null) {
+ return false;
+ }
+ return (authority.syncable == 1);
+ }
+ return false;
}
+ }
+
+ public void setIsTargetServiceActive(ComponentName cname, int userId, boolean active) {
+ setSyncableStateForEndPoint(new EndPoint(cname, userId), active ? 1 : 0);
+ }
+
+ /**
+ * An enabled sync service and a syncable provider's adapter both get resolved to the same
+ * persisted variable - namely the "syncable" attribute for an AuthorityInfo in accounts.xml.
+ * @param target target to set value for.
+ * @param syncable 0 indicates unsyncable, <0 unknown, >0 is active/syncable.
+ */
+ private void setSyncableStateForEndPoint(EndPoint target, int syncable) {
+ AuthorityInfo aInfo;
synchronized (mAuthorities) {
- AuthorityInfo authority =
- getOrCreateAuthorityLocked(account, userId, providerName, -1, false);
- if (authority.syncable == syncable) {
- if (DEBUG) {
+ aInfo = getOrCreateAuthorityLocked(target, -1, false);
+ if (syncable > 1) {
+ syncable = 1;
+ } else if (syncable < -1) {
+ syncable = -1;
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "setIsSyncable: " + aInfo.toString() + " -> " + syncable);
+ }
+ if (aInfo.syncable == syncable) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing");
}
return;
}
- authority.syncable = syncable;
+ aInfo.syncable = syncable;
writeAccountInfoLocked();
}
-
if (syncable > 0) {
- requestSync(account, userId, SyncOperation.REASON_IS_SYNCABLE, providerName,
- new Bundle());
+ requestSync(aInfo, SyncOperation.REASON_IS_SYNCABLE, new Bundle());
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
}
- public Pair<Long, Long> getBackoff(Account account, int userId, String providerName) {
+ public Pair<Long, Long> getBackoff(EndPoint info) {
synchronized (mAuthorities) {
- AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
- "getBackoff");
- if (authority == null || authority.backoffTime < 0) {
- return null;
+ AuthorityInfo authority = getAuthorityLocked(info, "getBackoff");
+ if (authority != null) {
+ return Pair.create(authority.backoffTime, authority.backoffDelay);
}
- return Pair.create(authority.backoffTime, authority.backoffDelay);
+ return null;
}
}
- public void setBackoff(Account account, int userId, String providerName,
- long nextSyncTime, long nextDelay) {
- if (DEBUG) {
- Log.v(TAG, "setBackoff: " + account + ", provider " + providerName
- + ", user " + userId
+ /**
+ * Update the backoff for the given endpoint. The endpoint may be for a provider/account and
+ * the account or provider info be null, which signifies all accounts or providers.
+ */
+ public void setBackoff(EndPoint info, long nextSyncTime, long nextDelay) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "setBackoff: " + info
+ " -> nextSyncTime " + nextSyncTime + ", nextDelay " + nextDelay);
}
- boolean changed = false;
+ boolean changed;
synchronized (mAuthorities) {
- if (account == null || providerName == null) {
- for (AccountInfo accountInfo : mAccounts.values()) {
- if (account != null && !account.equals(accountInfo.accountAndUser.account)
- && userId != accountInfo.accountAndUser.userId) {
- continue;
- }
- for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
- if (providerName != null
- && !providerName.equals(authorityInfo.authority)) {
- continue;
- }
- if (authorityInfo.backoffTime != nextSyncTime
- || authorityInfo.backoffDelay != nextDelay) {
- authorityInfo.backoffTime = nextSyncTime;
- authorityInfo.backoffDelay = nextDelay;
- changed = true;
- }
- }
- }
+ if (info.target_provider
+ && (info.account == null || info.provider == null)) {
+ // Do more work for a provider sync if the provided info has specified all
+ // accounts/providers.
+ changed = setBackoffLocked(
+ info.account /* may be null */,
+ info.userId,
+ info.provider /* may be null */,
+ nextSyncTime, nextDelay);
} else {
- AuthorityInfo authority =
- getOrCreateAuthorityLocked(account, userId, providerName, -1 /* ident */,
- true);
- if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) {
- return;
+ AuthorityInfo authorityInfo =
+ getOrCreateAuthorityLocked(info, -1 /* ident */, true);
+ if (authorityInfo.backoffTime == nextSyncTime
+ && authorityInfo.backoffDelay == nextDelay) {
+ changed = false;
+ } else {
+ authorityInfo.backoffTime = nextSyncTime;
+ authorityInfo.backoffDelay = nextDelay;
+ changed = true;
}
- authority.backoffTime = nextSyncTime;
- authority.backoffDelay = nextDelay;
- changed = true;
}
}
-
if (changed) {
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
}
}
/**
- * Callers of this function need to hold a lock for syncQueue object passed in. Bear in mind
- * this function grabs the lock for {@link #mAuthorities}
- * @param syncQueue queue containing pending sync operations.
+ * Either set backoff for a specific authority, or set backoff for all the
+ * accounts on a specific adapter/all adapters.
+ *
+ * @param account account for which to set backoff. Null to specify all accounts.
+ * @param userId id of the user making this request.
+ * @param providerName provider for which to set backoff. Null to specify all providers.
+ * @return true if a change occured.
*/
- public void clearAllBackoffsLocked(SyncQueue syncQueue) {
+ private boolean setBackoffLocked(Account account, int userId, String providerName,
+ long nextSyncTime, long nextDelay) {
+ boolean changed = false;
+ for (AccountInfo accountInfo : mAccounts.values()) {
+ if (account != null && !account.equals(accountInfo.accountAndUser.account)
+ && userId != accountInfo.accountAndUser.userId) {
+ continue;
+ }
+ for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
+ if (providerName != null
+ && !providerName.equals(authorityInfo.target.provider)) {
+ continue;
+ }
+ if (authorityInfo.backoffTime != nextSyncTime
+ || authorityInfo.backoffDelay != nextDelay) {
+ authorityInfo.backoffTime = nextSyncTime;
+ authorityInfo.backoffDelay = nextDelay;
+ changed = true;
+ }
+ }
+ }
+ return changed;
+ }
+
+ public void clearAllBackoffs(SyncQueue syncQueue) {
boolean changed = false;
synchronized (mAuthorities) {
- for (AccountInfo accountInfo : mAccounts.values()) {
- for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
- if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE
- || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) {
- if (DEBUG) {
- Log.v(TAG, "clearAllBackoffs:"
- + " authority:" + authorityInfo.authority
- + " account:" + accountInfo.accountAndUser.account.name
- + " user:" + accountInfo.accountAndUser.userId
- + " backoffTime was: " + authorityInfo.backoffTime
- + " backoffDelay was: " + authorityInfo.backoffDelay);
+ synchronized (syncQueue) {
+ // Clear backoff for all sync adapters.
+ for (AccountInfo accountInfo : mAccounts.values()) {
+ for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
+ if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE
+ || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "clearAllBackoffs:"
+ + " authority:" + authorityInfo.target
+ + " account:" + accountInfo.accountAndUser.account.name
+ + " user:" + accountInfo.accountAndUser.userId
+ + " backoffTime was: " + authorityInfo.backoffTime
+ + " backoffDelay was: " + authorityInfo.backoffDelay);
+ }
+ authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE;
+ authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE;
+ changed = true;
+ }
+ }
+ }
+ // Clear backoff for all sync services.
+ for (ComponentName service : mServices.keySet()) {
+ SparseArray<AuthorityInfo> aInfos = mServices.get(service);
+ for (int i = 0; i < aInfos.size(); i++) {
+ AuthorityInfo authorityInfo = aInfos.valueAt(i);
+ if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE
+ || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) {
+ authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE;
+ authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE;
}
- authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE;
- authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE;
- syncQueue.onBackoffChanged(accountInfo.accountAndUser.account,
- accountInfo.accountAndUser.userId, authorityInfo.authority, 0);
- changed = true;
}
}
+ syncQueue.clearBackoffs();
}
}
@@ -745,142 +878,157 @@ public class SyncStorageEngine extends Handler {
}
}
- public void setDelayUntilTime(Account account, int userId, String providerName,
- long delayUntil) {
- if (DEBUG) {
- Log.v(TAG, "setDelayUntil: " + account + ", provider " + providerName
- + ", user " + userId + " -> delayUntil " + delayUntil);
- }
+ public long getDelayUntilTime(EndPoint info) {
synchronized (mAuthorities) {
- AuthorityInfo authority = getOrCreateAuthorityLocked(
- account, userId, providerName, -1 /* ident */, true);
- if (authority.delayUntil == delayUntil) {
- return;
+ AuthorityInfo authority = getAuthorityLocked(info, "getDelayUntil");
+ if (authority == null) {
+ return 0;
}
- authority.delayUntil = delayUntil;
+ return authority.delayUntil;
}
-
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
}
- public long getDelayUntilTime(Account account, int userId, String providerName) {
+ public void setDelayUntilTime(EndPoint info, long delayUntil) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "setDelayUntil: " + info
+ + " -> delayUntil " + delayUntil);
+ }
synchronized (mAuthorities) {
- AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
- "getDelayUntil");
- if (authority == null) {
- return 0;
+ AuthorityInfo authority = getOrCreateAuthorityLocked(info, -1, true);
+ if (authority.delayUntil == delayUntil) {
+ return;
}
- return authority.delayUntil;
+ authority.delayUntil = delayUntil;
}
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
}
- private void updateOrRemovePeriodicSync(PeriodicSync toUpdate, int userId, boolean add) {
- if (DEBUG) {
- Log.v(TAG, "addOrRemovePeriodicSync: " + toUpdate.account + ", user " + userId
- + ", provider " + toUpdate.authority
- + " -> period " + toUpdate.period + ", extras " + toUpdate.extras);
+ public void updateOrAddPeriodicSync(EndPoint info, long period, long flextime, Bundle extras) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "addPeriodicSync: " + info
+ + " -> period " + period + ", flex " + flextime + ", extras "
+ + extras.toString());
}
synchronized (mAuthorities) {
- if (toUpdate.period <= 0 && add) {
- Log.e(TAG, "period < 0, should never happen in updateOrRemovePeriodicSync: add-"
- + add);
+ if (period <= 0) {
+ Log.e(TAG, "period < 0, should never happen in updateOrAddPeriodicSync");
}
- if (toUpdate.extras == null) {
- Log.e(TAG, "null extras, should never happen in updateOrRemovePeriodicSync: add-"
- + add);
+ if (extras == null) {
+ Log.e(TAG, "null extras, should never happen in updateOrAddPeriodicSync:");
}
try {
+ PeriodicSync toUpdate;
+ if (info.target_provider) {
+ toUpdate = new PeriodicSync(info.account,
+ info.provider,
+ extras,
+ period,
+ flextime);
+ } else {
+ toUpdate = new PeriodicSync(info.service,
+ extras,
+ period,
+ flextime);
+ }
AuthorityInfo authority =
- getOrCreateAuthorityLocked(toUpdate.account, userId, toUpdate.authority,
- -1, false);
- if (add) {
- // add this periodic sync if an equivalent periodic doesn't already exist.
- boolean alreadyPresent = false;
- for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
- PeriodicSync syncInfo = authority.periodicSyncs.get(i);
- if (PeriodicSync.syncExtrasEquals(
- toUpdate.extras,
- syncInfo.extras)) {
- if (toUpdate.period == syncInfo.period &&
- toUpdate.flexTime == syncInfo.flexTime) {
- // Absolutely the same.
- return;
- }
- authority.periodicSyncs.set(i, new PeriodicSync(toUpdate));
- alreadyPresent = true;
- break;
+ getOrCreateAuthorityLocked(info, -1, false);
+ // add this periodic sync if an equivalent periodic doesn't already exist.
+ boolean alreadyPresent = false;
+ for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
+ PeriodicSync syncInfo = authority.periodicSyncs.get(i);
+ if (SyncManager.syncExtrasEquals(syncInfo.extras,
+ extras,
+ true /* includeSyncSettings*/)) {
+ if (period == syncInfo.period &&
+ flextime == syncInfo.flexTime) {
+ // Absolutely the same.
+ return;
}
+ authority.periodicSyncs.set(i, toUpdate);
+ alreadyPresent = true;
+ break;
}
- // If we added an entry to the periodicSyncs array also add an entry to
- // the periodic syncs status to correspond to it.
- if (!alreadyPresent) {
- authority.periodicSyncs.add(new PeriodicSync(toUpdate));
- SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
- status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0L);
- }
- } else {
- // Remove any periodic syncs that match the authority and extras.
- SyncStatusInfo status = mSyncStatus.get(authority.ident);
- boolean changed = false;
- Iterator<PeriodicSync> iterator = authority.periodicSyncs.iterator();
- int i = 0;
- while (iterator.hasNext()) {
- PeriodicSync syncInfo = iterator.next();
- if (PeriodicSync.syncExtrasEquals(syncInfo.extras, toUpdate.extras)) {
- iterator.remove();
- changed = true;
- // If we removed an entry from the periodicSyncs array also
- // remove the corresponding entry from the status
- if (status != null) {
- status.removePeriodicSyncTime(i);
- } else {
- Log.e(TAG, "Tried removing sync status on remove periodic sync but"
- + "did not find it.");
- }
+ }
+ // If we added an entry to the periodicSyncs array also add an entry to
+ // the periodic syncs status to correspond to it.
+ if (!alreadyPresent) {
+ authority.periodicSyncs.add(toUpdate);
+ SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+ // A new periodic sync is initialised as already having been run.
+ status.setPeriodicSyncTime(
+ authority.periodicSyncs.size() - 1,
+ System.currentTimeMillis());
+ }
+ } finally {
+ writeAccountInfoLocked();
+ writeStatusLocked();
+ }
+ }
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ }
+
+ public void removePeriodicSync(EndPoint info, Bundle extras) {
+ synchronized(mAuthorities) {
+ try {
+ AuthorityInfo authority =
+ getOrCreateAuthorityLocked(info, -1, false);
+ // Remove any periodic syncs that match the target and extras.
+ SyncStatusInfo status = mSyncStatus.get(authority.ident);
+ boolean changed = false;
+ Iterator<PeriodicSync> iterator = authority.periodicSyncs.iterator();
+ int i = 0;
+ while (iterator.hasNext()) {
+ PeriodicSync syncInfo = iterator.next();
+ if (SyncManager.syncExtrasEquals(syncInfo.extras,
+ extras,
+ true /* includeSyncSettings */)) {
+ iterator.remove();
+ changed = true;
+ // If we removed an entry from the periodicSyncs array also
+ // remove the corresponding entry from the status
+ if (status != null) {
+ status.removePeriodicSyncTime(i);
} else {
- i++;
+ Log.e(TAG, "Tried removing sync status on remove periodic sync but"
+ + " did not find it.");
}
- }
- if (!changed) {
- return;
+ } else {
+ i++;
}
}
+ if (!changed) {
+ return;
+ }
} finally {
writeAccountInfoLocked();
writeStatusLocked();
}
}
-
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
}
- public void addPeriodicSync(PeriodicSync toAdd, int userId) {
- updateOrRemovePeriodicSync(toAdd, userId, true /* add */);
- }
-
- public void removePeriodicSync(PeriodicSync toRemove, int userId) {
- updateOrRemovePeriodicSync(toRemove, userId, false /* remove */);
- }
-
- public List<PeriodicSync> getPeriodicSyncs(Account account, int userId, String providerName) {
- ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
+ /**
+ * @return list of periodic syncs for a target. Never null. If no such syncs exist, returns an
+ * empty list.
+ */
+ public List<PeriodicSync> getPeriodicSyncs(EndPoint info) {
synchronized (mAuthorities) {
- AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
- "getPeriodicSyncs");
- if (authority != null) {
- for (PeriodicSync item : authority.periodicSyncs) {
+ AuthorityInfo authorityInfo = getAuthorityLocked(info, "getPeriodicSyncs");
+ ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
+ if (authorityInfo != null) {
+ for (PeriodicSync item : authorityInfo.periodicSyncs) {
// Copy and send out. Necessary for thread-safety although it's parceled.
syncs.add(new PeriodicSync(item));
}
}
+ return syncs;
}
- return syncs;
}
public void setMasterSyncAutomatically(boolean flag, int userId) {
synchronized (mAuthorities) {
Boolean auto = mMasterSyncAutomatically.get(userId);
- if (auto != null && (boolean) auto == flag) {
+ if (auto != null && auto.equals(flag)) {
return;
}
mMasterSyncAutomatically.put(userId, flag);
@@ -901,12 +1049,6 @@ public class SyncStorageEngine extends Handler {
}
}
- public void removeAuthority(Account account, int userId, String authority) {
- synchronized (mAuthorities) {
- removeAuthorityLocked(account, userId, authority, true /* doWrite */);
- }
- }
-
public AuthorityInfo getAuthority(int authorityId) {
synchronized (mAuthorities) {
return mAuthorities.get(authorityId);
@@ -914,72 +1056,60 @@ public class SyncStorageEngine extends Handler {
}
/**
- * Returns true if there is currently a sync operation for the given
- * account or authority actively being processed.
+ * Returns true if there is currently a sync operation being actively processed for the given
+ * target.
*/
- public boolean isSyncActive(Account account, int userId, String authority) {
+ public boolean isSyncActive(EndPoint info) {
synchronized (mAuthorities) {
- for (SyncInfo syncInfo : getCurrentSyncs(userId)) {
+ for (SyncInfo syncInfo : getCurrentSyncs(info.userId)) {
AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
- if (ainfo != null && ainfo.account.equals(account)
- && ainfo.authority.equals(authority)
- && ainfo.userId == userId) {
+ if (ainfo != null && ainfo.target.matchesSpec(info)) {
return true;
}
}
}
-
return false;
}
- public PendingOperation insertIntoPending(PendingOperation op) {
+ public PendingOperation insertIntoPending(SyncOperation op) {
+ PendingOperation pop;
synchronized (mAuthorities) {
- if (DEBUG) {
- Log.v(TAG, "insertIntoPending: account=" + op.account
- + " user=" + op.userId
- + " auth=" + op.authority
- + " src=" + op.syncSource
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "insertIntoPending: authority=" + op.target
+ " extras=" + op.extras);
}
-
- AuthorityInfo authority = getOrCreateAuthorityLocked(op.account, op.userId,
- op.authority,
- -1 /* desired identifier */,
- true /* write accounts to storage */);
+ final EndPoint info = op.target;
+ AuthorityInfo authority =
+ getOrCreateAuthorityLocked(info,
+ -1 /* desired identifier */,
+ true /* write accounts to storage */);
if (authority == null) {
return null;
}
- op = new PendingOperation(op);
- op.authorityId = authority.ident;
- mPendingOperations.add(op);
- appendPendingOperationLocked(op);
+ pop = new PendingOperation(authority, op.reason, op.syncSource, op.extras,
+ op.isExpedited());
+ mPendingOperations.add(pop);
+ appendPendingOperationLocked(pop);
SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
status.pending = true;
}
-
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
- return op;
+ return pop;
}
/**
* Remove from list of pending operations. If successful, search through list for matching
- * authorities. If there are no more pending syncs for the same authority/account/userid,
- * update the SyncStatusInfo for that authority(authority here is the internal representation
- * of a 'sync operation'.
- * @param op
- * @return
+ * authorities. If there are no more pending syncs for the same target,
+ * update the SyncStatusInfo for that target.
+ * @param op Pending op to delete.
*/
public boolean deleteFromPending(PendingOperation op) {
boolean res = false;
synchronized (mAuthorities) {
- if (DEBUG) {
- Log.v(TAG, "deleteFromPending: account=" + op.account
- + " user=" + op.userId
- + " auth=" + op.authority
- + " src=" + op.syncSource
- + " extras=" + op.extras);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "deleteFromPending: account=" + op.toString());
}
if (mPendingOperations.remove(op)) {
if (mPendingOperations.size() == 0
@@ -989,30 +1119,27 @@ public class SyncStorageEngine extends Handler {
} else {
mNumPendingFinished++;
}
-
- AuthorityInfo authority = getAuthorityLocked(op.account, op.userId, op.authority,
- "deleteFromPending");
+ AuthorityInfo authority = getAuthorityLocked(op.target, "deleteFromPending");
if (authority != null) {
- if (DEBUG) Log.v(TAG, "removing - " + authority.toString());
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "removing - " + authority.toString());
+ }
final int N = mPendingOperations.size();
boolean morePending = false;
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
PendingOperation cur = mPendingOperations.get(i);
- if (cur.account.equals(op.account)
- && cur.authority.equals(op.authority)
- && cur.userId == op.userId) {
+ if (cur.equals(op)) {
morePending = true;
break;
}
}
if (!morePending) {
- if (DEBUG) Log.v(TAG, "no more pending!");
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "no more pending!");
SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
status.pending = false;
}
}
-
res = true;
}
}
@@ -1047,7 +1174,9 @@ public class SyncStorageEngine extends Handler {
*/
public void doDatabaseCleanup(Account[] accounts, int userId) {
synchronized (mAuthorities) {
- if (DEBUG) Log.v(TAG, "Updating for new accounts...");
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Updating for new accounts...");
+ }
SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
Iterator<AccountInfo> accIt = mAccounts.values().iterator();
while (accIt.hasNext()) {
@@ -1055,7 +1184,7 @@ public class SyncStorageEngine extends Handler {
if (!ArrayUtils.contains(accounts, acc.accountAndUser.account)
&& acc.accountAndUser.userId == userId) {
// This account no longer exists...
- if (DEBUG) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Account removed: " + acc.accountAndUser);
}
for (AuthorityInfo auth : acc.authorities.values()) {
@@ -1102,25 +1231,25 @@ public class SyncStorageEngine extends Handler {
public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
final SyncInfo syncInfo;
synchronized (mAuthorities) {
- if (DEBUG) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "setActiveSync: account="
- + activeSyncContext.mSyncOperation.account
- + " auth=" + activeSyncContext.mSyncOperation.authority
+ + " auth=" + activeSyncContext.mSyncOperation.target
+ " src=" + activeSyncContext.mSyncOperation.syncSource
+ " extras=" + activeSyncContext.mSyncOperation.extras);
}
- AuthorityInfo authority = getOrCreateAuthorityLocked(
- activeSyncContext.mSyncOperation.account,
- activeSyncContext.mSyncOperation.userId,
- activeSyncContext.mSyncOperation.authority,
- -1 /* assign a new identifier if creating a new authority */,
+ final EndPoint info = activeSyncContext.mSyncOperation.target;
+ AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(
+ info,
+ -1 /* assign a new identifier if creating a new target */,
true /* write to storage if this results in a change */);
- syncInfo = new SyncInfo(authority.ident,
- authority.account, authority.authority,
+ syncInfo = new SyncInfo(
+ authorityInfo.ident,
+ authorityInfo.target.account,
+ authorityInfo.target.provider,
+ authorityInfo.target.service,
activeSyncContext.mStartTime);
- getCurrentSyncs(authority.userId).add(syncInfo);
+ getCurrentSyncs(authorityInfo.target.userId).add(syncInfo);
}
-
reportActiveChange();
return syncInfo;
}
@@ -1130,10 +1259,11 @@ public class SyncStorageEngine extends Handler {
*/
public void removeActiveSync(SyncInfo syncInfo, int userId) {
synchronized (mAuthorities) {
- if (DEBUG) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "removeActiveSync: account=" + syncInfo.account
+ " user=" + userId
- + " auth=" + syncInfo.authority);
+ + " auth=" + syncInfo.authority
+ + " service=" + syncInfo.service);
}
getCurrentSyncs(userId).remove(syncInfo);
}
@@ -1149,37 +1279,34 @@ public class SyncStorageEngine extends Handler {
}
/**
- * Note that sync has started for the given account and authority.
+ * Note that sync has started for the given operation.
*/
- public long insertStartSyncEvent(Account accountName, int userId, int reason,
- String authorityName, long now, int source, boolean initialization, Bundle extras) {
+ public long insertStartSyncEvent(SyncOperation op, long now) {
long id;
synchronized (mAuthorities) {
- if (DEBUG) {
- Log.v(TAG, "insertStartSyncEvent: account=" + accountName + "user=" + userId
- + " auth=" + authorityName + " source=" + source);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "insertStartSyncEvent: " + op);
}
- AuthorityInfo authority = getAuthorityLocked(accountName, userId, authorityName,
- "insertStartSyncEvent");
+ AuthorityInfo authority = getAuthorityLocked(op.target, "insertStartSyncEvent");
if (authority == null) {
return -1;
}
SyncHistoryItem item = new SyncHistoryItem();
- item.initialization = initialization;
+ item.initialization = op.isInitialization();
item.authorityId = authority.ident;
item.historyId = mNextHistoryId++;
if (mNextHistoryId < 0) mNextHistoryId = 0;
item.eventTime = now;
- item.source = source;
- item.reason = reason;
- item.extras = extras;
+ item.source = op.syncSource;
+ item.reason = op.reason;
+ item.extras = op.extras;
item.event = EVENT_START;
mSyncHistory.add(0, item);
while (mSyncHistory.size() > MAX_HISTORY) {
mSyncHistory.remove(mSyncHistory.size()-1);
}
id = item.historyId;
- if (DEBUG) Log.v(TAG, "returning historyId " + id);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "returning historyId " + id);
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
@@ -1189,7 +1316,7 @@ public class SyncStorageEngine extends Handler {
public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
long downstreamActivity, long upstreamActivity) {
synchronized (mAuthorities) {
- if (DEBUG) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
}
SyncHistoryItem item = null;
@@ -1346,13 +1473,12 @@ public class SyncStorageEngine extends Handler {
}
/**
- * Return a copy of the specified authority with the corresponding sync status
+ * Return a copy of the specified target with the corresponding sync status
*/
- public Pair<AuthorityInfo, SyncStatusInfo> getCopyOfAuthorityWithSyncStatus(
- Account account, int userId, String authority) {
+ public Pair<AuthorityInfo, SyncStatusInfo> getCopyOfAuthorityWithSyncStatus(EndPoint info) {
synchronized (mAuthorities) {
- AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(account, userId, authority,
- -1 /* assign a new identifier if creating a new authority */,
+ AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(info,
+ -1 /* assign a new identifier if creating a new target */,
true /* write to storage if this results in a change */);
return createCopyPairOfAuthorityWithSyncStatusLocked(authorityInfo);
}
@@ -1373,26 +1499,24 @@ public class SyncStorageEngine extends Handler {
}
/**
- * Returns the status that matches the authority and account.
+ * Returns the status that matches the target.
*
- * @param account the account we want to check
- * @param authority the authority whose row should be selected
- * @return the SyncStatusInfo for the authority or null if none found.
+ * @param info the endpoint target we are querying status info for.
+ * @return the SyncStatusInfo for the endpoint.
*/
- public SyncStatusInfo getStatusByAccountAndAuthority(Account account, int userId,
- String authority) {
- if (account == null || authority == null) {
- return null;
+ public SyncStatusInfo getStatusByAuthority(EndPoint info) {
+ if (info.target_provider && (info.account == null || info.provider == null)) {
+ return null;
+ } else if (info.target_service && info.service == null) {
+ return null;
}
synchronized (mAuthorities) {
final int N = mSyncStatus.size();
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
SyncStatusInfo cur = mSyncStatus.valueAt(i);
AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
-
- if (ainfo != null && ainfo.authority.equals(authority)
- && ainfo.userId == userId
- && account.equals(ainfo.account)) {
+ if (ainfo != null
+ && ainfo.target.matchesSpec(info)) {
return cur;
}
}
@@ -1400,25 +1524,20 @@ public class SyncStorageEngine extends Handler {
}
}
- /**
- * Return true if the pending status is true of any matching authorities.
- */
- public boolean isSyncPending(Account account, int userId, String authority) {
+ /** Return true if the pending status is true of any matching authorities. */
+ public boolean isSyncPending(EndPoint info) {
synchronized (mAuthorities) {
final int N = mSyncStatus.size();
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
SyncStatusInfo cur = mSyncStatus.valueAt(i);
AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
if (ainfo == null) {
continue;
}
- if (userId != ainfo.userId) {
+ if (!ainfo.target.matchesSpec(info)) {
continue;
}
- if (account != null && !ainfo.account.equals(account)) {
- continue;
- }
- if (ainfo.authority.equals(authority) && cur.pending) {
+ if (cur.pending) {
return true;
}
}
@@ -1474,128 +1593,133 @@ public class SyncStorageEngine extends Handler {
}
/**
- * Retrieve an authority, returning null if one does not exist.
+ * Retrieve a target's full info, returning null if one does not exist.
*
- * @param accountName The name of the account for the authority.
- * @param authorityName The name of the authority itself.
+ * @param info info of the target to look up.
* @param tag If non-null, this will be used in a log message if the
- * requested authority does not exist.
+ * requested target does not exist.
*/
- private AuthorityInfo getAuthorityLocked(Account accountName, int userId, String authorityName,
- String tag) {
- AccountAndUser au = new AccountAndUser(accountName, userId);
- AccountInfo accountInfo = mAccounts.get(au);
- if (accountInfo == null) {
- if (tag != null) {
- if (DEBUG) {
- Log.v(TAG, tag + ": unknown account " + au);
+ private AuthorityInfo getAuthorityLocked(EndPoint info, String tag) {
+ if (info.target_service) {
+ SparseArray<AuthorityInfo> aInfo = mServices.get(info.service);
+ AuthorityInfo authority = null;
+ if (aInfo != null) {
+ authority = aInfo.get(info.userId);
+ }
+ if (authority == null) {
+ if (tag != null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, tag + " No authority info found for " + info.service + " for"
+ + " user " + info.userId);
+ }
}
+ return null;
}
- return null;
- }
- AuthorityInfo authority = accountInfo.authorities.get(authorityName);
- if (authority == null) {
- if (tag != null) {
- if (DEBUG) {
- Log.v(TAG, tag + ": unknown authority " + authorityName);
+ return authority;
+ } else if (info.target_provider){
+ AccountAndUser au = new AccountAndUser(info.account, info.userId);
+ AccountInfo accountInfo = mAccounts.get(au);
+ if (accountInfo == null) {
+ if (tag != null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, tag + ": unknown account " + au);
+ }
}
+ return null;
}
- return null;
- }
-
- return authority;
- }
-
- /**
- * Retrieve an authority, returning null if one does not exist.
- *
- * @param service The service name used for this sync.
- * @param userId The user for whom this sync is scheduled.
- * @param tag If non-null, this will be used in a log message if the
- * requested authority does not exist.
- */
- private AuthorityInfo getAuthorityLocked(ComponentName service, int userId, String tag) {
- AuthorityInfo authority = mServices.get(service).get(userId);
- if (authority == null) {
- if (tag != null) {
- if (DEBUG) {
- Log.v(TAG, tag + " No authority info found for " + service + " for user "
- + userId);
+ AuthorityInfo authority = accountInfo.authorities.get(info.provider);
+ if (authority == null) {
+ if (tag != null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, tag + ": unknown provider " + info.provider);
+ }
}
+ return null;
}
+ return authority;
+ } else {
+ Log.e(TAG, tag + " Authority : " + info + ", invalid target");
return null;
}
- return authority;
}
/**
- * @param cname identifier for the service.
- * @param userId for the syncs corresponding to this authority.
- * @param ident unique identifier for authority. -1 for none.
+ * @param info info identifying target.
+ * @param ident unique identifier for target. -1 for none.
* @param doWrite if true, update the accounts.xml file on the disk.
- * @return the authority that corresponds to the provided sync service, creating it if none
+ * @return the authority that corresponds to the provided sync target, creating it if none
* exists.
*/
- private AuthorityInfo getOrCreateAuthorityLocked(ComponentName cname, int userId, int ident,
- boolean doWrite) {
- SparseArray<AuthorityInfo> aInfo = mServices.get(cname);
- if (aInfo == null) {
- aInfo = new SparseArray<AuthorityInfo>();
- mServices.put(cname, aInfo);
- }
- AuthorityInfo authority = aInfo.get(userId);
- if (authority == null) {
- if (ident < 0) {
- ident = mNextAuthorityId;
- mNextAuthorityId++;
- doWrite = true;
- }
- if (DEBUG) {
- Log.v(TAG, "created a new AuthorityInfo for " + cname.getPackageName()
- + ", " + cname.getClassName()
- + ", user: " + userId);
- }
- authority = new AuthorityInfo(cname, userId, ident);
- aInfo.put(userId, authority);
- mAuthorities.put(ident, authority);
- if (doWrite) {
- writeAccountInfoLocked();
+ private AuthorityInfo getOrCreateAuthorityLocked(EndPoint info, int ident, boolean doWrite) {
+ AuthorityInfo authority = null;
+ if (info.target_service) {
+ SparseArray<AuthorityInfo> aInfo = mServices.get(info.service);
+ if (aInfo == null) {
+ aInfo = new SparseArray<AuthorityInfo>();
+ mServices.put(info.service, aInfo);
+ }
+ authority = aInfo.get(info.userId);
+ if (authority == null) {
+ authority = createAuthorityLocked(info, ident, doWrite);
+ aInfo.put(info.userId, authority);
+ }
+ } else if (info.target_provider) {
+ AccountAndUser au = new AccountAndUser(info.account, info.userId);
+ AccountInfo account = mAccounts.get(au);
+ if (account == null) {
+ account = new AccountInfo(au);
+ mAccounts.put(au, account);
+ }
+ authority = account.authorities.get(info.provider);
+ if (authority == null) {
+ authority = createAuthorityLocked(info, ident, doWrite);
+ account.authorities.put(info.provider, authority);
}
}
return authority;
}
- private AuthorityInfo getOrCreateAuthorityLocked(Account accountName, int userId,
- String authorityName, int ident, boolean doWrite) {
- AccountAndUser au = new AccountAndUser(accountName, userId);
- AccountInfo account = mAccounts.get(au);
- if (account == null) {
- account = new AccountInfo(au);
- mAccounts.put(au, account);
- }
- AuthorityInfo authority = account.authorities.get(authorityName);
- if (authority == null) {
- if (ident < 0) {
- ident = mNextAuthorityId;
- mNextAuthorityId++;
- doWrite = true;
- }
- if (DEBUG) {
- Log.v(TAG, "created a new AuthorityInfo for " + accountName
- + ", user " + userId
- + ", provider " + authorityName);
- }
- authority = new AuthorityInfo(accountName, userId, authorityName, ident);
- account.authorities.put(authorityName, authority);
- mAuthorities.put(ident, authority);
- if (doWrite) {
- writeAccountInfoLocked();
- }
+ private AuthorityInfo createAuthorityLocked(EndPoint info, int ident, boolean doWrite) {
+ AuthorityInfo authority;
+ if (ident < 0) {
+ ident = mNextAuthorityId;
+ mNextAuthorityId++;
+ doWrite = true;
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "created a new AuthorityInfo for " + info);
+ }
+ authority = new AuthorityInfo(info, ident);
+ mAuthorities.put(ident, authority);
+ if (doWrite) {
+ writeAccountInfoLocked();
}
-
return authority;
}
+ public void removeAuthority(EndPoint info) {
+ synchronized (mAuthorities) {
+ if (info.target_provider) {
+ removeAuthorityLocked(info.account, info.userId, info.provider, true /* doWrite */);
+ } else {
+ SparseArray<AuthorityInfo> aInfos = mServices.get(info.service);
+ if (aInfos != null) {
+ AuthorityInfo authorityInfo = aInfos.get(info.userId);
+ if (authorityInfo != null) {
+ mAuthorities.remove(authorityInfo.ident);
+ aInfos.delete(info.userId);
+ writeAccountInfoLocked();
+ }
+ }
+
+ }
+ }
+ }
+
+ /**
+ * Remove an authority associated with a provider. Needs to be a standalone function for
+ * backward compatibility.
+ */
private void removeAuthorityLocked(Account account, int userId, String authorityName,
boolean doWrite) {
AccountInfo accountInfo = mAccounts.get(new AccountAndUser(account, userId));
@@ -1612,10 +1736,9 @@ public class SyncStorageEngine extends Handler {
/**
* Updates (in a synchronized way) the periodic sync time of the specified
- * authority id and target periodic sync
+ * target id and target periodic sync
*/
- public void setPeriodicSyncTime(
- int authorityId, PeriodicSync targetPeriodicSync, long when) {
+ public void setPeriodicSyncTime(int authorityId, PeriodicSync targetPeriodicSync, long when) {
boolean found = false;
final AuthorityInfo authorityInfo;
synchronized (mAuthorities) {
@@ -1631,7 +1754,7 @@ public class SyncStorageEngine extends Handler {
}
if (!found) {
Log.w(TAG, "Ignoring setPeriodicSyncTime request for a sync that does not exist. " +
- "Authority: " + authorityInfo.authority);
+ "Authority: " + authorityInfo.target);
}
}
@@ -1692,7 +1815,7 @@ public class SyncStorageEngine extends Handler {
try {
fis = mAccountInfoFile.openRead();
if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
- Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile());
+ Log.v(TAG_FILE, "Reading " + mAccountInfoFile.getBaseFile());
}
XmlPullParser parser = Xml.newPullParser();
parser.setInput(fis, null);
@@ -1806,10 +1929,14 @@ public class SyncStorageEngine extends Handler {
ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>();
final int N = mAuthorities.size();
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
AuthorityInfo authority = mAuthorities.valueAt(i);
+ // skip this authority if it doesn't target a provider
+ if (authority.target.target_service) {
+ continue;
+ }
// skip this authority if it isn't one of the renamed ones
- final String newAuthorityName = sAuthorityRenames.get(authority.authority);
+ final String newAuthorityName = sAuthorityRenames.get(authority.target.provider);
if (newAuthorityName == null) {
continue;
}
@@ -1825,20 +1952,26 @@ public class SyncStorageEngine extends Handler {
}
// if we already have a record of this new authority then don't copy over the settings
- if (getAuthorityLocked(authority.account, authority.userId, newAuthorityName, "cleanup")
- != null) {
+ EndPoint newInfo =
+ new EndPoint(authority.target.account,
+ newAuthorityName,
+ authority.target.userId);
+ if (getAuthorityLocked(newInfo, "cleanup") != null) {
continue;
}
- AuthorityInfo newAuthority = getOrCreateAuthorityLocked(authority.account,
- authority.userId, newAuthorityName, -1 /* ident */, false /* doWrite */);
+ AuthorityInfo newAuthority =
+ getOrCreateAuthorityLocked(newInfo, -1 /* ident */, false /* doWrite */);
newAuthority.enabled = true;
writeNeeded = true;
}
for (AuthorityInfo authorityInfo : authoritiesToRemove) {
- removeAuthorityLocked(authorityInfo.account, authorityInfo.userId,
- authorityInfo.authority, false /* doWrite */);
+ removeAuthorityLocked(
+ authorityInfo.target.account,
+ authorityInfo.target.userId,
+ authorityInfo.target.provider,
+ false /* doWrite */);
writeNeeded = true;
}
@@ -1880,30 +2013,37 @@ public class SyncStorageEngine extends Handler {
String packageName = parser.getAttributeValue(null, "package");
String className = parser.getAttributeValue(null, "class");
int userId = user == null ? 0 : Integer.parseInt(user);
- if (accountType == null) {
+ if (accountType == null && packageName == null) {
accountType = "com.google";
syncable = "unknown";
}
authority = mAuthorities.get(id);
if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
- Log.v(TAG, "Adding authority: account="
- + accountName + " auth=" + authorityName
+ Log.v(TAG_FILE, "Adding authority:"
+ + " account=" + accountName
+ + " accountType=" + accountType
+ + " auth=" + authorityName
+ + " package=" + packageName
+ + " class=" + className
+ " user=" + userId
+ " enabled=" + enabled
+ " syncable=" + syncable);
}
if (authority == null) {
if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
- Log.v(TAG, "Creating entry");
+ Log.v(TAG_FILE, "Creating authority entry");
}
- if (accountName != null && accountType != null) {
- authority = getOrCreateAuthorityLocked(
- new Account(accountName, accountType), userId, authorityName, id,
- false);
+ EndPoint info;
+ if (accountName != null && authorityName != null) {
+ info = new EndPoint(
+ new Account(accountName, accountType),
+ authorityName, userId);
} else {
- authority = getOrCreateAuthorityLocked(
- new ComponentName(packageName, className), userId, id, false);
+ info = new EndPoint(
+ new ComponentName(packageName, className),
+ userId);
}
+ authority = getOrCreateAuthorityLocked(info, id, false);
// If the version is 0 then we are upgrading from a file format that did not
// know about periodic syncs. In that case don't clear the list since we
// want the default, which is a daily periodic sync.
@@ -1934,7 +2074,7 @@ public class SyncStorageEngine extends Handler {
/**
* Parse a periodic sync from accounts.xml. Sets the bundle to be empty.
*/
- private PeriodicSync parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) {
+ private PeriodicSync parsePeriodicSync(XmlPullParser parser, AuthorityInfo authorityInfo) {
Bundle extras = new Bundle(); // Gets filled in later.
String periodValue = parser.getAttributeValue(null, "period");
String flexValue = parser.getAttributeValue(null, "flex");
@@ -1952,17 +2092,31 @@ public class SyncStorageEngine extends Handler {
try {
flextime = Long.parseLong(flexValue);
} catch (NumberFormatException e) {
- Log.e(TAG, "Error formatting value parsed for periodic sync flex: " + flexValue);
flextime = calculateDefaultFlexTime(period);
+ Log.e(TAG, "Error formatting value parsed for periodic sync flex: " + flexValue
+ + ", using default: "
+ + flextime);
} catch (NullPointerException expected) {
flextime = calculateDefaultFlexTime(period);
Log.d(TAG, "No flex time specified for this sync, using a default. period: "
+ period + " flex: " + flextime);
}
- final PeriodicSync periodicSync =
- new PeriodicSync(authority.account, authority.authority, extras,
+ PeriodicSync periodicSync;
+ if (authorityInfo.target.target_provider) {
+ periodicSync =
+ new PeriodicSync(authorityInfo.target.account,
+ authorityInfo.target.provider,
+ extras,
period, flextime);
- authority.periodicSyncs.add(periodicSync);
+ } else {
+ periodicSync =
+ new PeriodicSync(
+ authorityInfo.target.service,
+ extras,
+ period,
+ flextime);
+ }
+ authorityInfo.periodicSyncs.add(periodicSync);
return periodicSync;
}
@@ -2000,7 +2154,7 @@ public class SyncStorageEngine extends Handler {
*/
private void writeAccountInfoLocked() {
if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
- Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile());
+ Log.v(TAG_FILE, "Writing new " + mAccountInfoFile.getBaseFile());
}
FileOutputStream fos = null;
@@ -2030,17 +2184,18 @@ public class SyncStorageEngine extends Handler {
final int N = mAuthorities.size();
for (int i = 0; i < N; i++) {
AuthorityInfo authority = mAuthorities.valueAt(i);
+ EndPoint info = authority.target;
out.startTag(null, "authority");
out.attribute(null, "id", Integer.toString(authority.ident));
- out.attribute(null, XML_ATTR_USER, Integer.toString(authority.userId));
+ out.attribute(null, XML_ATTR_USER, Integer.toString(info.userId));
out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled));
- if (authority.service == null) {
- out.attribute(null, "account", authority.account.name);
- out.attribute(null, "type", authority.account.type);
- out.attribute(null, "authority", authority.authority);
+ if (info.service == null) {
+ out.attribute(null, "account", info.account.name);
+ out.attribute(null, "type", info.account.type);
+ out.attribute(null, "authority", info.provider);
} else {
- out.attribute(null, "package", authority.service.getPackageName());
- out.attribute(null, "class", authority.service.getClassName());
+ out.attribute(null, "package", info.service.getPackageName());
+ out.attribute(null, "class", info.service.getClassName());
}
if (authority.syncable < 0) {
out.attribute(null, "syncable", "unknown");
@@ -2100,7 +2255,7 @@ public class SyncStorageEngine extends Handler {
// Copy in all of the status information, as well as accounts.
if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
- Log.v(TAG, "Reading legacy sync accounts db");
+ Log.v(TAG_FILE, "Reading legacy sync accounts db");
}
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables("stats, status");
@@ -2134,9 +2289,13 @@ public class SyncStorageEngine extends Handler {
accountType = "com.google";
}
String authorityName = c.getString(c.getColumnIndex("authority"));
- AuthorityInfo authority = this.getOrCreateAuthorityLocked(
- new Account(accountName, accountType), 0 /* legacy is single-user */,
- authorityName, -1, false);
+ AuthorityInfo authority =
+ this.getOrCreateAuthorityLocked(
+ new EndPoint(new Account(accountName, accountType),
+ authorityName,
+ 0 /* legacy is single-user */)
+ , -1,
+ false);
if (authority != null) {
int i = mSyncStatus.size();
boolean found = false;
@@ -2188,7 +2347,7 @@ public class SyncStorageEngine extends Handler {
while (i > 0) {
i--;
AuthorityInfo authority = mAuthorities.valueAt(i);
- if (authority.authority.equals(provider)) {
+ if (authority.target.provider.equals(provider)) {
authority.enabled = value == null || Boolean.parseBoolean(value);
authority.syncable = 1;
}
@@ -2212,7 +2371,7 @@ public class SyncStorageEngine extends Handler {
*/
private void readStatusLocked() {
if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
- Log.v(TAG, "Reading " + mStatusFile.getBaseFile());
+ Log.v(TAG_FILE, "Reading " + mStatusFile.getBaseFile());
}
try {
byte[] data = mStatusFile.readFully();
@@ -2226,8 +2385,7 @@ public class SyncStorageEngine extends Handler {
if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
status.pending = false;
if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
- Log.v(TAG, "Adding status for id "
- + status.authorityId);
+ Log.v(TAG_FILE, "Adding status for id " + status.authorityId);
}
mSyncStatus.put(status.authorityId, status);
}
@@ -2247,7 +2405,7 @@ public class SyncStorageEngine extends Handler {
*/
private void writeStatusLocked() {
if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
- Log.v(TAG, "Writing new " + mStatusFile.getBaseFile());
+ Log.v(TAG_FILE, "Writing new " + mStatusFile.getBaseFile());
}
// The file is being written, so we don't need to have a scheduled
@@ -2285,11 +2443,14 @@ public class SyncStorageEngine extends Handler {
if (!mPendingFile.getBaseFile().exists()) {
if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
Log.v(TAG_FILE, "No pending operation file.");
- return;
}
+ return;
}
try {
fis = mPendingFile.openRead();
+ if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+ Log.v(TAG_FILE, "Reading " + mPendingFile.getBaseFile());
+ }
XmlPullParser parser;
parser = Xml.newPullParser();
parser.setInput(fis, null);
@@ -2301,12 +2462,11 @@ public class SyncStorageEngine extends Handler {
}
if (eventType == XmlPullParser.END_DOCUMENT) return; // Nothing to read.
- String tagName = parser.getName();
do {
PendingOperation pop = null;
if (eventType == XmlPullParser.START_TAG) {
try {
- tagName = parser.getName();
+ String tagName = parser.getName();
if (parser.getDepth() == 1 && "op".equals(tagName)) {
// Verify version.
String versionString =
@@ -2331,18 +2491,16 @@ public class SyncStorageEngine extends Handler {
}
if (authority != null) {
pop = new PendingOperation(
- authority.account, authority.userId, reason,
- syncSource, authority.authority, new Bundle(),
- expedited);
+ authority, reason, syncSource, new Bundle(), expedited);
pop.flatExtras = null; // No longer used.
mPendingOperations.add(pop);
if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
Log.v(TAG_FILE, "Adding pending op: "
- + pop.authority
+ + pop.target
+ " src=" + pop.syncSource
+ " reason=" + pop.reason
+ " expedited=" + pop.expedited);
- }
+ }
} else {
// Skip non-existent authority.
pop = null;
@@ -2377,11 +2535,40 @@ public class SyncStorageEngine extends Handler {
}
}
+ static private byte[] flattenBundle(Bundle bundle) {
+ byte[] flatData = null;
+ Parcel parcel = Parcel.obtain();
+ try {
+ bundle.writeToParcel(parcel, 0);
+ flatData = parcel.marshall();
+ } finally {
+ parcel.recycle();
+ }
+ return flatData;
+ }
+
+ static private Bundle unflattenBundle(byte[] flatData) {
+ Bundle bundle;
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.unmarshall(flatData, 0, flatData.length);
+ parcel.setDataPosition(0);
+ bundle = parcel.readBundle();
+ } catch (RuntimeException e) {
+ // A RuntimeException is thrown if we were unable to parse the parcel.
+ // Create an empty parcel in this case.
+ bundle = new Bundle();
+ } finally {
+ parcel.recycle();
+ }
+ return bundle;
+ }
+
+ private static final String XML_ATTR_VERSION = "version";
private static final String XML_ATTR_AUTHORITYID = "authority_id";
private static final String XML_ATTR_SOURCE = "source";
private static final String XML_ATTR_EXPEDITED = "expedited";
private static final String XML_ATTR_REASON = "reason";
- private static final String XML_ATTR_VERSION = "version";
/**
* Write all currently pending ops to the pending ops file.
@@ -2391,14 +2578,14 @@ public class SyncStorageEngine extends Handler {
FileOutputStream fos = null;
try {
if (N == 0) {
- if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
- Log.v(TAG_FILE, "Truncating " + mPendingFile.getBaseFile());
+ if (Log.isLoggable(TAG_FILE, Log.VERBOSE)){
+ Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
}
mPendingFile.truncate();
return;
}
if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
- Log.v(TAG_FILE, "Writing new " + mPendingFile.getBaseFile());
+ Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
}
fos = mPendingFile.startWrite();
XmlSerializer out = new FastXmlSerializer();
@@ -2407,9 +2594,9 @@ public class SyncStorageEngine extends Handler {
for (int i = 0; i < N; i++) {
PendingOperation pop = mPendingOperations.get(i);
writePendingOperationLocked(pop, out);
- }
- out.endDocument();
- mPendingFile.finishWrite(fos);
+ }
+ out.endDocument();
+ mPendingFile.finishWrite(fos);
} catch (java.io.IOException e1) {
Log.w(TAG, "Error writing pending operations", e1);
if (fos != null) {
@@ -2469,35 +2656,6 @@ public class SyncStorageEngine extends Handler {
}
}
- static private byte[] flattenBundle(Bundle bundle) {
- byte[] flatData = null;
- Parcel parcel = Parcel.obtain();
- try {
- bundle.writeToParcel(parcel, 0);
- flatData = parcel.marshall();
- } finally {
- parcel.recycle();
- }
- return flatData;
- }
-
- static private Bundle unflattenBundle(byte[] flatData) {
- Bundle bundle;
- Parcel parcel = Parcel.obtain();
- try {
- parcel.unmarshall(flatData, 0, flatData.length);
- parcel.setDataPosition(0);
- bundle = parcel.readBundle();
- } catch (RuntimeException e) {
- // A RuntimeException is thrown if we were unable to parse the parcel.
- // Create an empty parcel in this case.
- bundle = new Bundle();
- } finally {
- parcel.recycle();
- }
- return bundle;
- }
-
private void extrasToXml(XmlSerializer out, Bundle extras) throws java.io.IOException {
for (String key : extras.keySet()) {
out.startTag(null, "extra");
@@ -2530,6 +2688,24 @@ public class SyncStorageEngine extends Handler {
}
}
+ private void requestSync(AuthorityInfo authorityInfo, int reason, Bundle extras) {
+ if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
+ && mSyncRequestListener != null) {
+ mSyncRequestListener.onSyncRequest(authorityInfo.target, reason, extras);
+ } else {
+ SyncRequest.Builder req =
+ new SyncRequest.Builder()
+ .syncOnce()
+ .setExtras(extras);
+ if (authorityInfo.target.target_provider) {
+ req.setSyncAdapter(authorityInfo.target.account, authorityInfo.target.provider);
+ } else {
+ req.setSyncAdapter(authorityInfo.target.service);
+ }
+ ContentResolver.requestSync(req.build());
+ }
+ }
+
private void requestSync(Account account, int userId, int reason, String authority,
Bundle extras) {
// If this is happening in the system process, then call the syncrequest listener
@@ -2538,7 +2714,10 @@ public class SyncStorageEngine extends Handler {
// which will know which userId to apply based on the Binder id.
if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
&& mSyncRequestListener != null) {
- mSyncRequestListener.onSyncRequest(account, userId, reason, authority, extras);
+ mSyncRequestListener.onSyncRequest(
+ new EndPoint(account, authority, userId),
+ reason,
+ extras);
} else {
ContentResolver.requestSync(account, authority, extras);
}
@@ -2634,10 +2813,8 @@ public class SyncStorageEngine extends Handler {
public void dumpPendingOperations(StringBuilder sb) {
sb.append("Pending Ops: ").append(mPendingOperations.size()).append(" operation(s)\n");
for (PendingOperation pop : mPendingOperations) {
- sb.append("(" + pop.account)
- .append(", u" + pop.userId)
- .append(", " + pop.authority)
- .append(", " + pop.extras)
+ sb.append("(info: " + pop.target.toString())
+ .append(", extras: " + pop.extras)
.append(")\n");
}
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
new file mode 100644
index 0000000..4740cae
--- /dev/null
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -0,0 +1,687 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import com.android.server.LocalServices;
+import com.android.server.twilight.TwilightListener;
+import com.android.server.twilight.TwilightManager;
+import com.android.server.twilight.TwilightState;
+
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+import android.util.MathUtils;
+import android.util.Spline;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+class AutomaticBrightnessController {
+ private static final String TAG = "AutomaticBrightnessController";
+
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false;
+
+ // If true, enables the use of the screen auto-brightness adjustment setting.
+ private static final boolean USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT = true;
+
+ // The maximum range of gamma adjustment possible using the screen
+ // auto-brightness adjustment setting.
+ private static final float SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA = 3.0f;
+
+ // Light sensor event rate in milliseconds.
+ private static final int LIGHT_SENSOR_RATE_MILLIS = 1000;
+
+ // Period of time in which to consider light samples in milliseconds.
+ private static final int AMBIENT_LIGHT_HORIZON = 10000;
+
+ // Stability requirements in milliseconds for accepting a new brightness level. This is used
+ // for debouncing the light sensor. Different constants are used to debounce the light sensor
+ // when adapting to brighter or darker environments. This parameter controls how quickly
+ // brightness changes occur in response to an observed change in light level that exceeds the
+ // hysteresis threshold.
+ private static final long BRIGHTENING_LIGHT_DEBOUNCE = 4000;
+ private static final long DARKENING_LIGHT_DEBOUNCE = 8000;
+
+ // Hysteresis constraints for brightening or darkening.
+ // The recent lux must have changed by at least this fraction relative to the
+ // current ambient lux before a change will be considered.
+ private static final float BRIGHTENING_LIGHT_HYSTERESIS = 0.10f;
+ private static final float DARKENING_LIGHT_HYSTERESIS = 0.20f;
+
+ // The intercept used for the weighting calculation. This is used in order to keep all possible
+ // weighting values positive.
+ private static final int WEIGHTING_INTERCEPT = AMBIENT_LIGHT_HORIZON;
+
+ // How long the current sensor reading is assumed to be valid beyond the current time.
+ // This provides a bit of prediction, as well as ensures that the weight for the last sample is
+ // non-zero, which in turn ensures that the total weight is non-zero.
+ private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100;
+
+ // If true, enables the use of the current time as an auto-brightness adjustment.
+ // The basic idea here is to expand the dynamic range of auto-brightness
+ // when it is especially dark outside. The light sensor tends to perform
+ // poorly at low light levels so we compensate for it by making an
+ // assumption about the environment.
+ private static final boolean USE_TWILIGHT_ADJUSTMENT =
+ PowerManager.useTwilightAdjustmentFeature();
+
+ // Specifies the maximum magnitude of the time of day adjustment.
+ private static final float TWILIGHT_ADJUSTMENT_MAX_GAMMA = 1.5f;
+
+ // The amount of time after or before sunrise over which to start adjusting
+ // the gamma. We want the change to happen gradually so that it is below the
+ // threshold of perceptibility and so that the adjustment has maximum effect
+ // well after dusk.
+ private static final long TWILIGHT_ADJUSTMENT_TIME = DateUtils.HOUR_IN_MILLIS * 2;
+
+ private static final int MSG_UPDATE_AMBIENT_LUX = 1;
+
+ // Callbacks for requesting updates to the the display's power state
+ private final Callbacks mCallbacks;
+
+ // The sensor manager.
+ private final SensorManager mSensorManager;
+
+ // The light sensor, or null if not available or needed.
+ private final Sensor mLightSensor;
+
+ // The twilight service.
+ private final TwilightManager mTwilight;
+
+ // The auto-brightness spline adjustment.
+ // The brightness values have been scaled to a range of 0..1.
+ private final Spline mScreenAutoBrightnessSpline;
+
+ // The minimum and maximum screen brightnesses.
+ private final int mScreenBrightnessRangeMinimum;
+ private final int mScreenBrightnessRangeMaximum;
+
+ // Amount of time to delay auto-brightness after screen on while waiting for
+ // the light sensor to warm-up in milliseconds.
+ // May be 0 if no warm-up is required.
+ private int mLightSensorWarmUpTimeConfig;
+
+ // Set to true if the light sensor is enabled.
+ private boolean mLightSensorEnabled;
+
+ // The time when the light sensor was enabled.
+ private long mLightSensorEnableTime;
+
+ // The currently accepted nominal ambient light level.
+ private float mAmbientLux;
+
+ // True if mAmbientLux holds a valid value.
+ private boolean mAmbientLuxValid;
+
+ // The ambient light level threshold at which to brighten or darken the screen.
+ private float mBrighteningLuxThreshold;
+ private float mDarkeningLuxThreshold;
+
+ // The most recent light sample.
+ private float mLastObservedLux;
+
+ // The time of the most light recent sample.
+ private long mLastObservedLuxTime;
+
+ // The number of light samples collected since the light sensor was enabled.
+ private int mRecentLightSamples;
+
+ // A ring buffer containing all of the recent ambient light sensor readings.
+ private AmbientLightRingBuffer mAmbientLightRingBuffer;
+
+ // The handler
+ private AutomaticBrightnessHandler mHandler;
+
+ // The screen brightness level that has been chosen by the auto-brightness
+ // algorithm. The actual brightness should ramp towards this value.
+ // We preserve this value even when we stop using the light sensor so
+ // that we can quickly revert to the previous auto-brightness level
+ // while the light sensor warms up.
+ // Use -1 if there is no current auto-brightness value available.
+ private int mScreenAutoBrightness = -1;
+
+ // The screen auto-brightness adjustment factor in the range -1 (dimmer) to 1 (brighter)
+ private float mScreenAutoBrightnessAdjustment = 0.0f;
+
+ // The last screen auto-brightness gamma. (For printing in dump() only.)
+ private float mLastScreenAutoBrightnessGamma = 1.0f;
+
+ public AutomaticBrightnessController(Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Spline autoBrightnessSpline,
+ int lightSensorWarmUpTime, int brightnessMin, int brightnessMax) {
+ mCallbacks = callbacks;
+ mTwilight = LocalServices.getService(TwilightManager.class);
+ mSensorManager = sensorManager;
+ mScreenAutoBrightnessSpline = autoBrightnessSpline;
+ mScreenBrightnessRangeMinimum = brightnessMin;
+ mScreenBrightnessRangeMaximum = brightnessMax;
+ mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime;
+
+ mHandler = new AutomaticBrightnessHandler(looper);
+ mAmbientLightRingBuffer = new AmbientLightRingBuffer();
+
+ if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) {
+ mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
+ }
+
+ if (USE_TWILIGHT_ADJUSTMENT) {
+ mTwilight.registerListener(mTwilightListener, mHandler);
+ }
+ }
+
+ public int getAutomaticScreenBrightness() {
+ return mScreenAutoBrightness;
+ }
+
+ public void updatePowerState(DisplayManagerInternal.DisplayPowerRequest request) {
+ if (setScreenAutoBrightnessAdjustment(request.screenAutoBrightnessAdjustment)
+ | setLightSensorEnabled(request.wantLightSensorEnabled())) {
+ updateAutoBrightness(false /*sendUpdate*/);
+ }
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println();
+ pw.println("Automatic Brightness Controller Configuration:");
+ pw.println(" mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline);
+ pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum);
+ pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum);
+ pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
+
+ pw.println();
+ pw.println("Automatic Brightness Controller State:");
+ pw.println(" mLightSensor=" + mLightSensor);
+ pw.println(" mTwilight.getCurrentState()=" + mTwilight.getCurrentState());
+ pw.println(" mLightSensorEnabled=" + mLightSensorEnabled);
+ pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
+ pw.println(" mAmbientLux=" + mAmbientLux);
+ pw.println(" mBrighteningLuxThreshold=" + mBrighteningLuxThreshold);
+ pw.println(" mDarkeningLuxThreshold=" + mDarkeningLuxThreshold);
+ pw.println(" mLastObservedLux=" + mLastObservedLux);
+ pw.println(" mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime));
+ pw.println(" mRecentLightSamples=" + mRecentLightSamples);
+ pw.println(" mAmbientLightRingBuffer=" + mAmbientLightRingBuffer);
+ pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness);
+ pw.println(" mScreenAutoBrightnessAdjustment=" + mScreenAutoBrightnessAdjustment);
+ pw.println(" mLastScreenAutoBrightnessGamma=" + mLastScreenAutoBrightnessGamma);
+ }
+
+ private boolean setLightSensorEnabled(boolean enable) {
+ if (enable) {
+ if (!mLightSensorEnabled) {
+ mLightSensorEnabled = true;
+ mLightSensorEnableTime = SystemClock.uptimeMillis();
+ mSensorManager.registerListener(mLightSensorListener, mLightSensor,
+ LIGHT_SENSOR_RATE_MILLIS * 1000, mHandler);
+ return true;
+ }
+ } else {
+ if (mLightSensorEnabled) {
+ mLightSensorEnabled = false;
+ mAmbientLuxValid = false;
+ mRecentLightSamples = 0;
+ mAmbientLightRingBuffer.clear();
+ mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
+ mSensorManager.unregisterListener(mLightSensorListener);
+ }
+ }
+ return false;
+ }
+
+ private void handleLightSensorEvent(long time, float lux) {
+ mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
+
+ applyLightSensorMeasurement(time, lux);
+ updateAmbientLux(time);
+ }
+
+ private void applyLightSensorMeasurement(long time, float lux) {
+ mRecentLightSamples++;
+ mAmbientLightRingBuffer.prune(time - AMBIENT_LIGHT_HORIZON);
+ mAmbientLightRingBuffer.push(time, lux);
+
+ // Remember this sample value.
+ mLastObservedLux = lux;
+ mLastObservedLuxTime = time;
+ }
+
+ private boolean setScreenAutoBrightnessAdjustment(float adjustment) {
+ if (adjustment != mScreenAutoBrightnessAdjustment) {
+ mScreenAutoBrightnessAdjustment = adjustment;
+ return true;
+ }
+ return false;
+ }
+
+ private void setAmbientLux(float lux) {
+ mAmbientLux = lux;
+ mBrighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS);
+ mDarkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS);
+ }
+
+ private float calculateAmbientLux(long now) {
+ final int N = mAmbientLightRingBuffer.size();
+ if (N == 0) {
+ Slog.e(TAG, "calculateAmbientLux: No ambient light readings available");
+ return -1;
+ }
+ float sum = 0;
+ float totalWeight = 0;
+ long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS;
+ for (int i = N - 1; i >= 0; i--) {
+ long startTime = (mAmbientLightRingBuffer.getTime(i) - now);
+ float weight = calculateWeight(startTime, endTime);
+ float lux = mAmbientLightRingBuffer.getLux(i);
+ if (DEBUG) {
+ Slog.d(TAG, "calculateAmbientLux: [" +
+ (startTime) + ", " +
+ (endTime) + "]: lux=" + lux + ", weight=" + weight);
+ }
+ totalWeight += weight;
+ sum += mAmbientLightRingBuffer.getLux(i) * weight;
+ endTime = startTime;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "calculateAmbientLux: totalWeight=" + totalWeight +
+ ", newAmbientLux=" + (sum / totalWeight));
+ }
+ return sum / totalWeight;
+ }
+
+ private static float calculateWeight(long startDelta, long endDelta) {
+ return weightIntegral(endDelta) - weightIntegral(startDelta);
+ }
+
+ // Evaluates the integral of y = x + WEIGHTING_INTERCEPT. This is always positive for the
+ // horizon we're looking at and provides a non-linear weighting for light samples.
+ private static float weightIntegral(long x) {
+ return x * (x * 0.5f + WEIGHTING_INTERCEPT);
+ }
+
+ private long nextAmbientLightBrighteningTransition(long time) {
+ final int N = mAmbientLightRingBuffer.size();
+ long earliestValidTime = time;
+ for (int i = N - 1; i >= 0; i--) {
+ if (mAmbientLightRingBuffer.getLux(i) <= mBrighteningLuxThreshold) {
+ break;
+ }
+ earliestValidTime = mAmbientLightRingBuffer.getTime(i);
+ }
+ return earliestValidTime + BRIGHTENING_LIGHT_DEBOUNCE;
+ }
+
+ private long nextAmbientLightDarkeningTransition(long time) {
+ final int N = mAmbientLightRingBuffer.size();
+ long earliestValidTime = time;
+ for (int i = N - 1; i >= 0; i--) {
+ if (mAmbientLightRingBuffer.getLux(i) >= mDarkeningLuxThreshold) {
+ break;
+ }
+ earliestValidTime = mAmbientLightRingBuffer.getTime(i);
+ }
+ return earliestValidTime + DARKENING_LIGHT_DEBOUNCE;
+ }
+
+ private void updateAmbientLux() {
+ long time = SystemClock.uptimeMillis();
+ mAmbientLightRingBuffer.prune(time - AMBIENT_LIGHT_HORIZON);
+ updateAmbientLux(time);
+ }
+
+ private void updateAmbientLux(long time) {
+ // If the light sensor was just turned on then immediately update our initial
+ // estimate of the current ambient light level.
+ if (!mAmbientLuxValid) {
+ final long timeWhenSensorWarmedUp =
+ mLightSensorWarmUpTimeConfig + mLightSensorEnableTime;
+ if (time < timeWhenSensorWarmedUp) {
+ if (DEBUG) {
+ Slog.d(TAG, "updateAmbientLux: Sensor not ready yet: "
+ + "time=" + time
+ + ", timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp);
+ }
+ mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX,
+ timeWhenSensorWarmedUp);
+ return;
+ }
+ setAmbientLux(calculateAmbientLux(time));
+ mAmbientLuxValid = true;
+ if (DEBUG) {
+ Slog.d(TAG, "updateAmbientLux: Initializing: "
+ + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer
+ + ", mAmbientLux=" + mAmbientLux);
+ }
+ updateAutoBrightness(true);
+ }
+
+ long nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
+ long nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
+ float ambientLux = calculateAmbientLux(time);
+
+ if (ambientLux >= mBrighteningLuxThreshold && nextBrightenTransition <= time
+ || ambientLux <= mDarkeningLuxThreshold && nextDarkenTransition <= time) {
+ setAmbientLux(ambientLux);
+ if (DEBUG) {
+ Slog.d(TAG, "updateAmbientLux: "
+ + ((ambientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
+ + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold
+ + ", mAmbientLightRingBuffer=" + mAmbientLightRingBuffer
+ + ", mAmbientLux=" + mAmbientLux);
+ }
+ updateAutoBrightness(true);
+ nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
+ nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
+ }
+ long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition);
+ // If one of the transitions is ready to occur, but the total weighted ambient lux doesn't
+ // exceed the necessary threshold, then it's possible we'll get a transition time prior to
+ // now. Rather than continually checking to see whether the weighted lux exceeds the
+ // threshold, schedule an update for when we'd normally expect another light sample, which
+ // should be enough time to decide whether we should actually transition to the new
+ // weighted ambient lux or not.
+ nextTransitionTime =
+ nextTransitionTime > time ? nextTransitionTime : time + LIGHT_SENSOR_RATE_MILLIS;
+ if (DEBUG) {
+ Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for "
+ + nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime));
+ }
+ mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, nextTransitionTime);
+ }
+
+ private void updateAutoBrightness(boolean sendUpdate) {
+ if (!mAmbientLuxValid) {
+ return;
+ }
+
+ float value = mScreenAutoBrightnessSpline.interpolate(mAmbientLux);
+ float gamma = 1.0f;
+
+ if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT
+ && mScreenAutoBrightnessAdjustment != 0.0f) {
+ final float adjGamma = MathUtils.pow(SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA,
+ Math.min(1.0f, Math.max(-1.0f, -mScreenAutoBrightnessAdjustment)));
+ gamma *= adjGamma;
+ if (DEBUG) {
+ Slog.d(TAG, "updateAutoBrightness: adjGamma=" + adjGamma);
+ }
+ }
+
+ if (USE_TWILIGHT_ADJUSTMENT) {
+ TwilightState state = mTwilight.getCurrentState();
+ if (state != null && state.isNight()) {
+ final long now = System.currentTimeMillis();
+ final float earlyGamma =
+ getTwilightGamma(now, state.getYesterdaySunset(), state.getTodaySunrise());
+ final float lateGamma =
+ getTwilightGamma(now, state.getTodaySunset(), state.getTomorrowSunrise());
+ gamma *= earlyGamma * lateGamma;
+ if (DEBUG) {
+ Slog.d(TAG, "updateAutoBrightness: earlyGamma=" + earlyGamma
+ + ", lateGamma=" + lateGamma);
+ }
+ }
+ }
+
+ if (gamma != 1.0f) {
+ final float in = value;
+ value = MathUtils.pow(value, gamma);
+ if (DEBUG) {
+ Slog.d(TAG, "updateAutoBrightness: gamma=" + gamma
+ + ", in=" + in + ", out=" + value);
+ }
+ }
+
+ int newScreenAutoBrightness =
+ clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON));
+ if (mScreenAutoBrightness != newScreenAutoBrightness) {
+ if (DEBUG) {
+ Slog.d(TAG, "updateAutoBrightness: mScreenAutoBrightness="
+ + mScreenAutoBrightness + ", newScreenAutoBrightness="
+ + newScreenAutoBrightness);
+ }
+
+ mScreenAutoBrightness = newScreenAutoBrightness;
+ mLastScreenAutoBrightnessGamma = gamma;
+ if (sendUpdate) {
+ mCallbacks.updateBrightness();
+ }
+ }
+ }
+
+ private int clampScreenBrightness(int value) {
+ return MathUtils.constrain(value,
+ mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum);
+ }
+
+ private static float getTwilightGamma(long now, long lastSunset, long nextSunrise) {
+ if (lastSunset < 0 || nextSunrise < 0
+ || now < lastSunset || now > nextSunrise) {
+ return 1.0f;
+ }
+
+ if (now < lastSunset + TWILIGHT_ADJUSTMENT_TIME) {
+ return MathUtils.lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA,
+ (float)(now - lastSunset) / TWILIGHT_ADJUSTMENT_TIME);
+ }
+
+ if (now > nextSunrise - TWILIGHT_ADJUSTMENT_TIME) {
+ return MathUtils.lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA,
+ (float)(nextSunrise - now) / TWILIGHT_ADJUSTMENT_TIME);
+ }
+
+ return TWILIGHT_ADJUSTMENT_MAX_GAMMA;
+ }
+
+ private final class AutomaticBrightnessHandler extends Handler {
+ public AutomaticBrightnessHandler(Looper looper) {
+ super(looper, null, true /*async*/);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE_AMBIENT_LUX:
+ updateAmbientLux();
+ break;
+ }
+ }
+ }
+
+ private final SensorEventListener mLightSensorListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (mLightSensorEnabled) {
+ final long time = SystemClock.uptimeMillis();
+ final float lux = event.values[0];
+ handleLightSensorEvent(time, lux);
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // Not used.
+ }
+ };
+
+ private final TwilightListener mTwilightListener = new TwilightListener() {
+ @Override
+ public void onTwilightStateChanged() {
+ updateAutoBrightness(true /*sendUpdate*/);
+ }
+ };
+
+ /** Callbacks to request updates to the display's power state. */
+ interface Callbacks {
+ void updateBrightness();
+ }
+
+ private static final class AmbientLightRingBuffer{
+ // Proportional extra capacity of the buffer beyond the expected number of light samples
+ // in the horizon
+ private static final float BUFFER_SLACK = 1.5f;
+ private static final int DEFAULT_CAPACITY =
+ (int) Math.ceil(AMBIENT_LIGHT_HORIZON * BUFFER_SLACK / LIGHT_SENSOR_RATE_MILLIS);
+ private float[] mRingLux;
+ private long[] mRingTime;
+ private int mCapacity;
+
+ // The first valid element and the next open slot.
+ // Note that if mCount is zero then there are no valid elements.
+ private int mStart;
+ private int mEnd;
+ private int mCount;
+
+ public AmbientLightRingBuffer() {
+ this(DEFAULT_CAPACITY);
+ }
+
+ public AmbientLightRingBuffer(int initialCapacity) {
+ mCapacity = initialCapacity;
+ mRingLux = new float[mCapacity];
+ mRingTime = new long[mCapacity];
+ }
+
+ public float getLux(int index) {
+ return mRingLux[offsetOf(index)];
+ }
+
+ public long getTime(int index) {
+ return mRingTime[offsetOf(index)];
+ }
+
+ public void push(long time, float lux) {
+ int next = mEnd;
+ if (mCount == mCapacity) {
+ int newSize = mCapacity * 2;
+
+ float[] newRingLux = new float[newSize];
+ long[] newRingTime = new long[newSize];
+ int length = mCapacity - mStart;
+ System.arraycopy(mRingLux, mStart, newRingLux, 0, length);
+ System.arraycopy(mRingTime, mStart, newRingTime, 0, length);
+ if (mStart != 0) {
+ System.arraycopy(mRingLux, 0, newRingLux, length, mStart);
+ System.arraycopy(mRingTime, 0, newRingTime, length, mStart);
+ }
+ mRingLux = newRingLux;
+ mRingTime = newRingTime;
+
+ next = mCapacity;
+ mCapacity = newSize;
+ mStart = 0;
+ }
+ mRingTime[next] = time;
+ mRingLux[next] = lux;
+ mEnd = next + 1;
+ if (mEnd == mCapacity) {
+ mEnd = 0;
+ }
+ mCount++;
+ }
+
+ public void prune(long horizon) {
+ if (mCount == 0) {
+ return;
+ }
+
+ while (mCount > 1) {
+ int next = mStart + 1;
+ if (next >= mCapacity) {
+ next -= mCapacity;
+ }
+ if (mRingTime[next] > horizon) {
+ // Some light sensors only produce data upon a change in the ambient light
+ // levels, so we need to consider the previous measurement as the ambient light
+ // level for all points in time up until we receive a new measurement. Thus, we
+ // always want to keep the youngest element that would be removed from the
+ // buffer and just set its measurement time to the horizon time since at that
+ // point it is the ambient light level, and to remove it would be to drop a
+ // valid data point within our horizon.
+ break;
+ }
+ mStart = next;
+ mCount -= 1;
+ }
+
+ if (mRingTime[mStart] < horizon) {
+ mRingTime[mStart] = horizon;
+ }
+ }
+
+ public int size() {
+ return mCount;
+ }
+
+ public boolean isEmpty() {
+ return mCount == 0;
+ }
+
+ public void clear() {
+ mStart = 0;
+ mEnd = 0;
+ mCount = 0;
+ }
+
+ @Override
+ public String toString() {
+ final int length = mCapacity - mStart;
+ float[] lux = new float[mCount];
+ long[] time = new long[mCount];
+
+ if (mCount <= length) {
+ System.arraycopy(mRingLux, mStart, lux, 0, mCount);
+ System.arraycopy(mRingTime, mStart, time, 0, mCount);
+ } else {
+ System.arraycopy(mRingLux, mStart, lux, 0, length);
+ System.arraycopy(mRingLux, 0, lux, length, mCount - length);
+
+ System.arraycopy(mRingTime, mStart, time, 0, length);
+ System.arraycopy(mRingTime, 0, time, length, mCount - length);
+ }
+ return "AmbientLightRingBuffer{mCapacity=" + mCapacity
+ + ", mStart=" + mStart
+ + ", mEnd=" + mEnd
+ + ", mCount=" + mCount
+ + ", mRingLux=" + Arrays.toString(lux)
+ + ", mRingTime=" + Arrays.toString(time)
+ + "}";
+ }
+
+ private int offsetOf(int index) {
+ if (index >= mCount || index < 0) {
+ throw new ArrayIndexOutOfBoundsException(index);
+ }
+ index += mStart;
+ if (index >= mCapacity) {
+ index -= mCapacity;
+ }
+ return index;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 279a9cc..3d5fb57 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -20,9 +20,6 @@ import com.android.internal.app.IBatteryStats;
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
import com.android.server.lights.LightsManager;
-import com.android.server.twilight.TwilightListener;
-import com.android.server.twilight.TwilightManager;
-import com.android.server.twilight.TwilightState;
import android.animation.Animator;
import android.animation.ObjectAnimator;
@@ -41,7 +38,7 @@ import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.text.format.DateUtils;
-import android.util.FloatMath;
+import android.util.MathUtils;
import android.util.Slog;
import android.util.Spline;
import android.util.TimeUtils;
@@ -71,12 +68,11 @@ import java.io.PrintWriter;
* For debugging, you can make the electron beam and brightness animations run
* slower by changing the "animator duration scale" option in Development Settings.
*/
-final class DisplayPowerController {
+final class DisplayPowerController implements AutomaticBrightnessController.Callbacks {
private static final String TAG = "DisplayPowerController";
private static boolean DEBUG = false;
private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
- private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false;
// If true, uses the electron beam on animation.
// We might want to turn this off if we cannot get a guarantee that the screen
@@ -84,40 +80,15 @@ final class DisplayPowerController {
// screen state returns. Playing the animation can also be somewhat slow.
private static final boolean USE_ELECTRON_BEAM_ON_ANIMATION = false;
- // If true, enables the use of the screen auto-brightness adjustment setting.
- private static final boolean USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT =
- PowerManager.useScreenAutoBrightnessAdjustmentFeature();
-
- // The maximum range of gamma adjustment possible using the screen
- // auto-brightness adjustment setting.
- private static final float SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA = 3.0f;
// The minimum reduction in brightness when dimmed.
private static final int SCREEN_DIM_MINIMUM_REDUCTION = 10;
- // If true, enables the use of the current time as an auto-brightness adjustment.
- // The basic idea here is to expand the dynamic range of auto-brightness
- // when it is especially dark outside. The light sensor tends to perform
- // poorly at low light levels so we compensate for it by making an
- // assumption about the environment.
- private static final boolean USE_TWILIGHT_ADJUSTMENT =
- PowerManager.useTwilightAdjustmentFeature();
-
- // Specifies the maximum magnitude of the time of day adjustment.
- private static final float TWILIGHT_ADJUSTMENT_MAX_GAMMA = 1.5f;
-
- // The amount of time after or before sunrise over which to start adjusting
- // the gamma. We want the change to happen gradually so that it is below the
- // threshold of perceptibility and so that the adjustment has maximum effect
- // well after dusk.
- private static final long TWILIGHT_ADJUSTMENT_TIME = DateUtils.HOUR_IN_MILLIS * 2;
-
private static final int ELECTRON_BEAM_ON_ANIMATION_DURATION_MILLIS = 250;
private static final int ELECTRON_BEAM_OFF_ANIMATION_DURATION_MILLIS = 400;
private static final int MSG_UPDATE_POWER_STATE = 1;
private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2;
- private static final int MSG_LIGHT_SENSOR_DEBOUNCED = 3;
private static final int PROXIMITY_UNKNOWN = -1;
private static final int PROXIMITY_NEGATIVE = 0;
@@ -130,41 +101,10 @@ final class DisplayPowerController {
// Trigger proximity if distance is less than 5 cm.
private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f;
- // Light sensor event rate in milliseconds.
- private static final int LIGHT_SENSOR_RATE_MILLIS = 1000;
-
- // A rate for generating synthetic light sensor events in the case where the light
- // sensor hasn't reported any new data in a while and we need it to update the
- // debounce filter. We only synthesize light sensor measurements when needed.
- private static final int SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS =
- LIGHT_SENSOR_RATE_MILLIS * 2;
-
// Brightness animation ramp rate in brightness units per second.
private static final int BRIGHTNESS_RAMP_RATE_FAST = 200;
private static final int BRIGHTNESS_RAMP_RATE_SLOW = 40;
- // IIR filter time constants in milliseconds for computing two moving averages of
- // the light samples. One is a long-term average and the other is a short-term average.
- // We can use these filters to assess trends in ambient brightness.
- // The short term average gives us a filtered but relatively low latency measurement.
- // The long term average informs us about the overall trend.
- private static final long SHORT_TERM_AVERAGE_LIGHT_TIME_CONSTANT = 1000;
- private static final long LONG_TERM_AVERAGE_LIGHT_TIME_CONSTANT = 5000;
-
- // Stability requirements in milliseconds for accepting a new brightness
- // level. This is used for debouncing the light sensor. Different constants
- // are used to debounce the light sensor when adapting to brighter or darker environments.
- // This parameter controls how quickly brightness changes occur in response to
- // an observed change in light level that exceeds the hysteresis threshold.
- private static final long BRIGHTENING_LIGHT_DEBOUNCE = 4000;
- private static final long DARKENING_LIGHT_DEBOUNCE = 8000;
-
- // Hysteresis constraints for brightening or darkening.
- // The recent lux must have changed by at least this fraction relative to the
- // current ambient lux before a change will be considered.
- private static final float BRIGHTENING_LIGHT_HYSTERESIS = 0.10f;
- private static final float DARKENING_LIGHT_HYSTERESIS = 0.20f;
-
private final Object mLock = new Object();
// Our handler.
@@ -180,9 +120,6 @@ final class DisplayPowerController {
// The lights service.
private final LightsManager mLights;
- // The twilight service.
- private final TwilightManager mTwilight;
-
// The sensor manager.
private final SensorManager mSensorManager;
@@ -192,9 +129,6 @@ final class DisplayPowerController {
// The proximity sensor, or null if not available or needed.
private Sensor mProximitySensor;
- // The light sensor, or null if not available or needed.
- private Sensor mLightSensor;
-
// The doze screen brightness.
private final int mScreenBrightnessDozeConfig;
@@ -210,15 +144,6 @@ final class DisplayPowerController {
// True if auto-brightness should be used.
private boolean mUseSoftwareAutoBrightnessConfig;
- // The auto-brightness spline adjustment.
- // The brightness values have been scaled to a range of 0..1.
- private Spline mScreenAutoBrightnessSpline;
-
- // Amount of time to delay auto-brightness after screen on while waiting for
- // the light sensor to warm-up in milliseconds.
- // May be 0 if no warm-up is required.
- private int mLightSensorWarmUpTimeConfig;
-
// True if we should fade the screen while turning it off, false if we should play
// a stylish electron beam animation instead.
private boolean mElectronBeamFadesConfig;
@@ -288,67 +213,18 @@ final class DisplayPowerController {
// The elapsed real time when the screen on was blocked.
private long mScreenOnBlockStartRealTime;
- // Set to true if the light sensor is enabled.
- private boolean mLightSensorEnabled;
-
- // The time when the light sensor was enabled.
- private long mLightSensorEnableTime;
-
- // The currently accepted nominal ambient light level.
- private float mAmbientLux;
-
- // True if mAmbientLux holds a valid value.
- private boolean mAmbientLuxValid;
-
- // The ambient light level threshold at which to brighten or darken the screen.
- private float mBrighteningLuxThreshold;
- private float mDarkeningLuxThreshold;
-
- // The most recent light sample.
- private float mLastObservedLux;
-
- // The time of the most light recent sample.
- private long mLastObservedLuxTime;
-
- // The number of light samples collected since the light sensor was enabled.
- private int mRecentLightSamples;
-
- // The long-term and short-term filtered light measurements.
- private float mRecentShortTermAverageLux;
- private float mRecentLongTermAverageLux;
-
- // The direction in which the average lux is moving relative to the current ambient lux.
- // 0 if not changing or within hysteresis threshold.
- // 1 if brightening beyond hysteresis threshold.
- // -1 if darkening beyond hysteresis threshold.
- private int mDebounceLuxDirection;
-
- // The time when the average lux last changed direction.
- private long mDebounceLuxTime;
-
- // The screen brightness level that has been chosen by the auto-brightness
- // algorithm. The actual brightness should ramp towards this value.
- // We preserve this value even when we stop using the light sensor so
- // that we can quickly revert to the previous auto-brightness level
- // while the light sensor warms up.
- // Use -1 if there is no current auto-brightness value available.
- private int mScreenAutoBrightness = -1;
-
- // The last screen auto-brightness gamma. (For printing in dump() only.)
- private float mLastScreenAutoBrightnessGamma = 1.0f;
-
// True if the screen auto-brightness value is actually being used to
// set the display brightness.
private boolean mUsingScreenAutoBrightness;
+ // The controller for the automatic brightness level.
+ private AutomaticBrightnessController mAutomaticBrightnessController;
+
// Animators.
private ObjectAnimator mElectronBeamOnAnimator;
private ObjectAnimator mElectronBeamOffAnimator;
private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
- // Twilight changed. We might recalculate auto-brightness values.
- private boolean mTwilightChanged;
-
/**
* Creates the display power controller.
*/
@@ -360,7 +236,6 @@ final class DisplayPowerController {
mBatteryStats = BatteryStatsService.getService();
mLights = LocalServices.getService(LightsManager.class);
- mTwilight = LocalServices.getService(TwilightManager.class);
mSensorManager = sensorManager;
mBlanker = blanker;
@@ -372,9 +247,11 @@ final class DisplayPowerController {
mScreenBrightnessDimConfig = clampAbsoluteBrightness(resources.getInteger(
com.android.internal.R.integer.config_screenBrightnessDim));
- int screenBrightnessMinimum = Math.min(resources.getInteger(
+ int screenBrightnessRangeMinimum = clampAbsoluteBrightness(Math.min(resources.getInteger(
com.android.internal.R.integer.config_screenBrightnessSettingMinimum),
- mScreenBrightnessDimConfig);
+ mScreenBrightnessDimConfig));
+
+ mScreenBrightnessRangeMaximum = PowerManager.BRIGHTNESS_ON;
mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
com.android.internal.R.bool.config_automatic_brightness_available);
@@ -383,9 +260,11 @@ final class DisplayPowerController {
com.android.internal.R.array.config_autoBrightnessLevels);
int[] screenBrightness = resources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
+ int lightSensorWarmUpTimeConfig = resources.getInteger(
+ com.android.internal.R.integer.config_lightSensorWarmupTime);
- mScreenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness);
- if (mScreenAutoBrightnessSpline == null) {
+ Spline screenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness);
+ if (screenAutoBrightnessSpline == null) {
Slog.e(TAG, "Error in config.xml. config_autoBrightnessLcdBacklightValues "
+ "(size " + screenBrightness.length + ") "
+ "must be monotic and have exactly one more entry than "
@@ -394,17 +273,17 @@ final class DisplayPowerController {
+ "Auto-brightness will be disabled.");
mUseSoftwareAutoBrightnessConfig = false;
} else {
- if (screenBrightness[0] < screenBrightnessMinimum) {
- screenBrightnessMinimum = screenBrightness[0];
+ if (screenBrightness[0] < screenBrightnessRangeMinimum) {
+ screenBrightnessRangeMinimum = clampAbsoluteBrightness(screenBrightness[0]);
}
+ mAutomaticBrightnessController = new AutomaticBrightnessController(this,
+ handler.getLooper(), sensorManager, screenAutoBrightnessSpline,
+ lightSensorWarmUpTimeConfig, screenBrightnessRangeMinimum,
+ mScreenBrightnessRangeMaximum);
}
-
- mLightSensorWarmUpTimeConfig = resources.getInteger(
- com.android.internal.R.integer.config_lightSensorWarmupTime);
}
- mScreenBrightnessRangeMinimum = clampAbsoluteBrightness(screenBrightnessMinimum);
- mScreenBrightnessRangeMaximum = PowerManager.BRIGHTNESS_ON;
+ mScreenBrightnessRangeMinimum = screenBrightnessRangeMinimum;
mElectronBeamFadesConfig = resources.getBoolean(
com.android.internal.R.bool.config_animateScreenLights);
@@ -417,39 +296,6 @@ final class DisplayPowerController {
}
}
- if (mUseSoftwareAutoBrightnessConfig
- && !DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) {
- mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
- }
-
- if (mUseSoftwareAutoBrightnessConfig && USE_TWILIGHT_ADJUSTMENT) {
- mTwilight.registerListener(mTwilightListener, mHandler);
- }
- }
-
- private static Spline createAutoBrightnessSpline(int[] lux, int[] brightness) {
- try {
- final int n = brightness.length;
- float[] x = new float[n];
- float[] y = new float[n];
- y[0] = normalizeAbsoluteBrightness(brightness[0]);
- for (int i = 1; i < n; i++) {
- x[i] = lux[i - 1];
- y[i] = normalizeAbsoluteBrightness(brightness[i]);
- }
-
- Spline spline = Spline.createMonotoneCubicSpline(x, y);
- if (DEBUG) {
- Slog.d(TAG, "Auto-brightness spline: " + spline);
- for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) {
- Slog.d(TAG, String.format(" %7.1f: %7.1f", v, spline.interpolate(v)));
- }
- }
- return spline;
- } catch (IllegalArgumentException ex) {
- Slog.e(TAG, "Could not create auto-brightness spline.", ex);
- return null;
- }
}
/**
@@ -574,9 +420,7 @@ final class DisplayPowerController {
// Update the power state request.
final boolean mustNotify;
boolean mustInitialize = false;
- boolean updateAutoBrightness = mTwilightChanged;
boolean wasDimOrDoze = false;
- mTwilightChanged = false;
synchronized (mLock) {
mPendingUpdatePowerStateLocked = false;
@@ -591,10 +435,6 @@ final class DisplayPowerController {
mPendingRequestChangedLocked = false;
mustInitialize = true;
} else if (mPendingRequestChangedLocked) {
- if (mPowerRequest.screenAutoBrightnessAdjustment
- != mPendingRequestLocked.screenAutoBrightnessAdjustment) {
- updateAutoBrightness = true;
- }
wasDimOrDoze = (mPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_DIM
|| mPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_DOZE);
mPowerRequest.copyFrom(mPendingRequestLocked);
@@ -641,18 +481,19 @@ final class DisplayPowerController {
}
// Turn on the light sensor if needed.
- if (mLightSensor != null) {
- setLightSensorEnabled(mPowerRequest.wantLightSensorEnabled(),
- updateAutoBrightness);
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.updatePowerState(mPowerRequest);
}
// Set the screen brightness.
if (mPowerRequest.wantScreenOnAny()) {
int target;
boolean slow;
- if (mScreenAutoBrightness >= 0 && mLightSensorEnabled) {
+ int screenAutoBrightness = mAutomaticBrightnessController != null ?
+ mAutomaticBrightnessController.getAutomaticScreenBrightness() : -1;
+ if (screenAutoBrightness >= 0 && mPowerRequest.useAutoBrightness) {
// Use current auto-brightness value.
- target = mScreenAutoBrightness;
+ target = screenAutoBrightness;
slow = mUsingScreenAutoBrightness;
mUsingScreenAutoBrightness = true;
} else {
@@ -677,6 +518,11 @@ final class DisplayPowerController {
// Brighten quickly.
slow = false;
}
+ // If low power mode is enabled, brightness level
+ // would be scaled down to half
+ if (mPowerRequest.lowPowerMode) {
+ target = target/2;
+ }
animateScreenBrightness(clampScreenBrightness(target),
slow ? BRIGHTNESS_RAMP_RATE_SLOW : BRIGHTNESS_RAMP_RATE_FAST);
} else {
@@ -767,6 +613,11 @@ final class DisplayPowerController {
}
}
+ @Override
+ public void updateBrightness() {
+ sendUpdatePowerState();
+ }
+
private void blockScreenOn() {
if (!mScreenOnWasBlocked) {
mScreenOnWasBlocked = true;
@@ -799,25 +650,8 @@ final class DisplayPowerController {
}
private int clampScreenBrightness(int value) {
- return clamp(value, mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum);
- }
-
- private static int clampAbsoluteBrightness(int value) {
- return clamp(value, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON);
- }
-
- private static int clamp(int value, int min, int max) {
- if (value <= min) {
- return min;
- }
- if (value >= max) {
- return max;
- }
- return value;
- }
-
- private static float normalizeAbsoluteBrightness(int value) {
- return (float)clampAbsoluteBrightness(value) / PowerManager.BRIGHTNESS_ON;
+ return MathUtils.constrain(
+ value, mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum);
}
private void animateScreenBrightness(int target, int rate) {
@@ -922,270 +756,6 @@ final class DisplayPowerController {
mPendingProximityDebounceTime = debounceTime;
}
- private void setLightSensorEnabled(boolean enable, boolean updateAutoBrightness) {
- if (enable) {
- if (!mLightSensorEnabled) {
- updateAutoBrightness = true;
- mLightSensorEnabled = true;
- mLightSensorEnableTime = SystemClock.uptimeMillis();
- mSensorManager.registerListener(mLightSensorListener, mLightSensor,
- LIGHT_SENSOR_RATE_MILLIS * 1000, mHandler);
- }
- } else {
- if (mLightSensorEnabled) {
- mLightSensorEnabled = false;
- mAmbientLuxValid = false;
- mRecentLightSamples = 0;
- mHandler.removeMessages(MSG_LIGHT_SENSOR_DEBOUNCED);
- mSensorManager.unregisterListener(mLightSensorListener);
- }
- }
- if (updateAutoBrightness) {
- updateAutoBrightness(false);
- }
- }
-
- private void handleLightSensorEvent(long time, float lux) {
- mHandler.removeMessages(MSG_LIGHT_SENSOR_DEBOUNCED);
-
- applyLightSensorMeasurement(time, lux);
- updateAmbientLux(time);
- }
-
- private void applyLightSensorMeasurement(long time, float lux) {
- // Update our filters.
- mRecentLightSamples += 1;
- if (mRecentLightSamples == 1) {
- mRecentShortTermAverageLux = lux;
- mRecentLongTermAverageLux = lux;
- } else {
- final long timeDelta = time - mLastObservedLuxTime;
- mRecentShortTermAverageLux += (lux - mRecentShortTermAverageLux)
- * timeDelta / (SHORT_TERM_AVERAGE_LIGHT_TIME_CONSTANT + timeDelta);
- mRecentLongTermAverageLux += (lux - mRecentLongTermAverageLux)
- * timeDelta / (LONG_TERM_AVERAGE_LIGHT_TIME_CONSTANT + timeDelta);
- }
-
- // Remember this sample value.
- mLastObservedLux = lux;
- mLastObservedLuxTime = time;
- }
-
- private void setAmbientLux(float lux) {
- mAmbientLux = lux;
- mBrighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS);
- mDarkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS);
- }
-
- private void updateAmbientLux(long time) {
- // If the light sensor was just turned on then immediately update our initial
- // estimate of the current ambient light level.
- if (!mAmbientLuxValid) {
- final long timeWhenSensorWarmedUp =
- mLightSensorWarmUpTimeConfig + mLightSensorEnableTime;
- if (time < timeWhenSensorWarmedUp) {
- mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED,
- timeWhenSensorWarmedUp);
- return;
- }
- setAmbientLux(mRecentShortTermAverageLux);
- mAmbientLuxValid = true;
- mDebounceLuxDirection = 0;
- mDebounceLuxTime = time;
- if (DEBUG) {
- Slog.d(TAG, "updateAmbientLux: Initializing: "
- + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
- + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
- + ", mAmbientLux=" + mAmbientLux);
- }
- updateAutoBrightness(true);
- } else if (mRecentShortTermAverageLux > mBrighteningLuxThreshold
- && mRecentLongTermAverageLux > mBrighteningLuxThreshold) {
- // The ambient environment appears to be brightening.
- if (mDebounceLuxDirection <= 0) {
- mDebounceLuxDirection = 1;
- mDebounceLuxTime = time;
- if (DEBUG) {
- Slog.d(TAG, "updateAmbientLux: Possibly brightened, waiting for "
- + BRIGHTENING_LIGHT_DEBOUNCE + " ms: "
- + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold
- + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
- + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
- + ", mAmbientLux=" + mAmbientLux);
- }
- }
- long debounceTime = mDebounceLuxTime + BRIGHTENING_LIGHT_DEBOUNCE;
- if (time < debounceTime) {
- mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime);
- return;
- }
- setAmbientLux(mRecentShortTermAverageLux);
- if (DEBUG) {
- Slog.d(TAG, "updateAmbientLux: Brightened: "
- + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold
- + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
- + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
- + ", mAmbientLux=" + mAmbientLux);
- }
- updateAutoBrightness(true);
- } else if (mRecentShortTermAverageLux < mDarkeningLuxThreshold
- && mRecentLongTermAverageLux < mDarkeningLuxThreshold) {
- // The ambient environment appears to be darkening.
- if (mDebounceLuxDirection >= 0) {
- mDebounceLuxDirection = -1;
- mDebounceLuxTime = time;
- if (DEBUG) {
- Slog.d(TAG, "updateAmbientLux: Possibly darkened, waiting for "
- + DARKENING_LIGHT_DEBOUNCE + " ms: "
- + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold
- + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
- + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
- + ", mAmbientLux=" + mAmbientLux);
- }
- }
- long debounceTime = mDebounceLuxTime + DARKENING_LIGHT_DEBOUNCE;
- if (time < debounceTime) {
- mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime);
- return;
- }
- // Be conservative about reducing the brightness, only reduce it a little bit
- // at a time to avoid having to bump it up again soon.
- setAmbientLux(Math.max(mRecentShortTermAverageLux, mRecentLongTermAverageLux));
- if (DEBUG) {
- Slog.d(TAG, "updateAmbientLux: Darkened: "
- + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold
- + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
- + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
- + ", mAmbientLux=" + mAmbientLux);
- }
- updateAutoBrightness(true);
- } else if (mDebounceLuxDirection != 0) {
- // No change or change is within the hysteresis thresholds.
- mDebounceLuxDirection = 0;
- mDebounceLuxTime = time;
- if (DEBUG) {
- Slog.d(TAG, "updateAmbientLux: Canceled debounce: "
- + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold
- + ", mDarkeningLuxThreshold=" + mDarkeningLuxThreshold
- + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
- + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
- + ", mAmbientLux=" + mAmbientLux);
- }
- }
-
- // Now that we've done all of that, we haven't yet posted a debounce
- // message. So consider the case where current lux is beyond the
- // threshold. It's possible that the light sensor may not report values
- // if the light level does not change, so we need to occasionally
- // synthesize sensor readings in order to make sure the brightness is
- // adjusted accordingly. Note these thresholds may have changed since
- // we entered the function because we called setAmbientLux and
- // updateAutoBrightness along the way.
- if (mLastObservedLux > mBrighteningLuxThreshold
- || mLastObservedLux < mDarkeningLuxThreshold) {
- mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED,
- time + SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS);
- }
- }
-
- private void debounceLightSensor() {
- if (mLightSensorEnabled) {
- long time = SystemClock.uptimeMillis();
- if (time >= mLastObservedLuxTime + SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS) {
- if (DEBUG) {
- Slog.d(TAG, "debounceLightSensor: Synthesizing light sensor measurement "
- + "after " + (time - mLastObservedLuxTime) + " ms.");
- }
- applyLightSensorMeasurement(time, mLastObservedLux);
- }
- updateAmbientLux(time);
- }
- }
-
- private void updateAutoBrightness(boolean sendUpdate) {
- if (!mAmbientLuxValid) {
- return;
- }
-
- float value = mScreenAutoBrightnessSpline.interpolate(mAmbientLux);
- float gamma = 1.0f;
-
- if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT
- && mPowerRequest.screenAutoBrightnessAdjustment != 0.0f) {
- final float adjGamma = FloatMath.pow(SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA,
- Math.min(1.0f, Math.max(-1.0f,
- -mPowerRequest.screenAutoBrightnessAdjustment)));
- gamma *= adjGamma;
- if (DEBUG) {
- Slog.d(TAG, "updateAutoBrightness: adjGamma=" + adjGamma);
- }
- }
-
- if (USE_TWILIGHT_ADJUSTMENT) {
- TwilightState state = mTwilight.getCurrentState();
- if (state != null && state.isNight()) {
- final long now = System.currentTimeMillis();
- final float earlyGamma =
- getTwilightGamma(now, state.getYesterdaySunset(), state.getTodaySunrise());
- final float lateGamma =
- getTwilightGamma(now, state.getTodaySunset(), state.getTomorrowSunrise());
- gamma *= earlyGamma * lateGamma;
- if (DEBUG) {
- Slog.d(TAG, "updateAutoBrightness: earlyGamma=" + earlyGamma
- + ", lateGamma=" + lateGamma);
- }
- }
- }
-
- if (gamma != 1.0f) {
- final float in = value;
- value = FloatMath.pow(value, gamma);
- if (DEBUG) {
- Slog.d(TAG, "updateAutoBrightness: gamma=" + gamma
- + ", in=" + in + ", out=" + value);
- }
- }
-
- int newScreenAutoBrightness = clampScreenBrightness(
- Math.round(value * PowerManager.BRIGHTNESS_ON));
- if (mScreenAutoBrightness != newScreenAutoBrightness) {
- if (DEBUG) {
- Slog.d(TAG, "updateAutoBrightness: mScreenAutoBrightness="
- + mScreenAutoBrightness + ", newScreenAutoBrightness="
- + newScreenAutoBrightness);
- }
-
- mScreenAutoBrightness = newScreenAutoBrightness;
- mLastScreenAutoBrightnessGamma = gamma;
- if (sendUpdate) {
- sendUpdatePowerState();
- }
- }
- }
-
- private static float getTwilightGamma(long now, long lastSunset, long nextSunrise) {
- if (lastSunset < 0 || nextSunrise < 0
- || now < lastSunset || now > nextSunrise) {
- return 1.0f;
- }
-
- if (now < lastSunset + TWILIGHT_ADJUSTMENT_TIME) {
- return lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA,
- (float)(now - lastSunset) / TWILIGHT_ADJUSTMENT_TIME);
- }
-
- if (now > nextSunrise - TWILIGHT_ADJUSTMENT_TIME) {
- return lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA,
- (float)(nextSunrise - now) / TWILIGHT_ADJUSTMENT_TIME);
- }
-
- return TWILIGHT_ADJUSTMENT_MAX_GAMMA;
- }
-
- private static float lerp(float x, float y, float alpha) {
- return x + (y - x) * alpha;
- }
-
private void sendOnStateChangedWithWakelock() {
mCallbacks.acquireSuspendBlocker();
mHandler.post(mOnStateChangedRunnable);
@@ -1245,8 +815,6 @@ final class DisplayPowerController {
pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum);
pw.println(" mUseSoftwareAutoBrightnessConfig="
+ mUseSoftwareAutoBrightnessConfig);
- pw.println(" mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline);
- pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
mHandler.runWithScissors(new Runnable() {
@Override
@@ -1270,25 +838,7 @@ final class DisplayPowerController {
pw.println(" mPendingProximityDebounceTime="
+ TimeUtils.formatUptime(mPendingProximityDebounceTime));
pw.println(" mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity);
-
- pw.println(" mLightSensor=" + mLightSensor);
- pw.println(" mLightSensorEnabled=" + mLightSensorEnabled);
- pw.println(" mLightSensorEnableTime="
- + TimeUtils.formatUptime(mLightSensorEnableTime));
- pw.println(" mAmbientLux=" + mAmbientLux);
- pw.println(" mAmbientLuxValid=" + mAmbientLuxValid);
- pw.println(" mLastObservedLux=" + mLastObservedLux);
- pw.println(" mLastObservedLuxTime="
- + TimeUtils.formatUptime(mLastObservedLuxTime));
- pw.println(" mRecentLightSamples=" + mRecentLightSamples);
- pw.println(" mRecentShortTermAverageLux=" + mRecentShortTermAverageLux);
- pw.println(" mRecentLongTermAverageLux=" + mRecentLongTermAverageLux);
- pw.println(" mDebounceLuxDirection=" + mDebounceLuxDirection);
- pw.println(" mDebounceLuxTime=" + TimeUtils.formatUptime(mDebounceLuxTime));
- pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness);
pw.println(" mUsingScreenAutoBrightness=" + mUsingScreenAutoBrightness);
- pw.println(" mLastScreenAutoBrightnessGamma=" + mLastScreenAutoBrightnessGamma);
- pw.println(" mTwilight.getCurrentState()=" + mTwilight.getCurrentState());
if (mElectronBeamOnAnimator != null) {
pw.println(" mElectronBeamOnAnimator.isStarted()=" +
@@ -1302,6 +852,11 @@ final class DisplayPowerController {
if (mPowerState != null) {
mPowerState.dump(pw);
}
+
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.dump(pw);
+ }
+
}
private static String proximityToString(int state) {
@@ -1317,6 +872,39 @@ final class DisplayPowerController {
}
}
+ private static Spline createAutoBrightnessSpline(int[] lux, int[] brightness) {
+ try {
+ final int n = brightness.length;
+ float[] x = new float[n];
+ float[] y = new float[n];
+ y[0] = normalizeAbsoluteBrightness(brightness[0]);
+ for (int i = 1; i < n; i++) {
+ x[i] = lux[i - 1];
+ y[i] = normalizeAbsoluteBrightness(brightness[i]);
+ }
+
+ Spline spline = Spline.createMonotoneCubicSpline(x, y);
+ if (DEBUG) {
+ Slog.d(TAG, "Auto-brightness spline: " + spline);
+ for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) {
+ Slog.d(TAG, String.format(" %7.1f: %7.1f", v, spline.interpolate(v)));
+ }
+ }
+ return spline;
+ } catch (IllegalArgumentException ex) {
+ Slog.e(TAG, "Could not create auto-brightness spline.", ex);
+ return null;
+ }
+ }
+
+ private static float normalizeAbsoluteBrightness(int value) {
+ return (float)clampAbsoluteBrightness(value) / PowerManager.BRIGHTNESS_ON;
+ }
+
+ private static int clampAbsoluteBrightness(int value) {
+ return MathUtils.constrain(value, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON);
+ }
+
private final class DisplayControllerHandler extends Handler {
public DisplayControllerHandler(Looper looper) {
super(looper, null, true /*async*/);
@@ -1332,10 +920,6 @@ final class DisplayPowerController {
case MSG_PROXIMITY_SENSOR_DEBOUNCED:
debounceProximitySensor();
break;
-
- case MSG_LIGHT_SENSOR_DEBOUNCED:
- debounceLightSensor();
- break;
}
}
}
@@ -1356,28 +940,4 @@ final class DisplayPowerController {
// Not used.
}
};
-
- private final SensorEventListener mLightSensorListener = new SensorEventListener() {
- @Override
- public void onSensorChanged(SensorEvent event) {
- if (mLightSensorEnabled) {
- final long time = SystemClock.uptimeMillis();
- final float lux = event.values[0];
- handleLightSensorEvent(time, lux);
- }
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- // Not used.
- }
- };
-
- private final TwilightListener mTwilightListener = new TwilightListener() {
- @Override
- public void onTwilightStateChanged() {
- mTwilightChanged = true;
- updatePowerState();
- }
- };
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 096f263..7f43e43 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -21,12 +21,12 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.SystemProperties;
+import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayEventReceiver;
import android.view.Surface;
import android.view.SurfaceControl;
-import android.view.SurfaceControl.PhysicalDisplayInfo;
import java.io.PrintWriter;
@@ -48,8 +48,6 @@ final class LocalDisplayAdapter extends DisplayAdapter {
new SparseArray<LocalDisplayDevice>();
private HotplugDisplayEventReceiver mHotplugReceiver;
- private final SurfaceControl.PhysicalDisplayInfo mTempPhys = new SurfaceControl.PhysicalDisplayInfo();
-
// Called with SyncRoot lock held.
public LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
Context context, Handler handler, Listener listener) {
@@ -69,14 +67,31 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private void tryConnectDisplayLocked(int builtInDisplayId) {
IBinder displayToken = SurfaceControl.getBuiltInDisplay(builtInDisplayId);
- if (displayToken != null && SurfaceControl.getDisplayInfo(displayToken, mTempPhys)) {
+ if (displayToken != null) {
+ SurfaceControl.PhysicalDisplayInfo[] configs =
+ SurfaceControl.getDisplayConfigs(displayToken);
+ if (configs == null) {
+ // There are no valid configs for this device, so we can't use it
+ Slog.w(TAG, "No valid configs found for display device " +
+ builtInDisplayId);
+ return;
+ }
+ int activeConfig = SurfaceControl.getActiveConfig(displayToken);
+ if (activeConfig < 0) {
+ // There is no active config, and for now we don't have the
+ // policy to set one.
+ Slog.w(TAG, "No active config found for display device " +
+ builtInDisplayId);
+ return;
+ }
LocalDisplayDevice device = mDevices.get(builtInDisplayId);
if (device == null) {
// Display was added.
- device = new LocalDisplayDevice(displayToken, builtInDisplayId, mTempPhys);
+ device = new LocalDisplayDevice(displayToken, builtInDisplayId,
+ configs[activeConfig]);
mDevices.put(builtInDisplayId, device);
sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
- } else if (device.updatePhysicalDisplayInfoLocked(mTempPhys)) {
+ } else if (device.updatePhysicalDisplayInfoLocked(configs[activeConfig])) {
// Display properties changed.
sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
}
@@ -175,6 +190,12 @@ final class LocalDisplayAdapter extends DisplayAdapter {
if ("portrait".equals(SystemProperties.get("persist.demo.hdmirotation"))) {
mInfo.rotation = Surface.ROTATION_270;
}
+
+ // For demonstration purposes, allow rotation of the external display
+ // to follow the built-in display.
+ if (SystemProperties.getBoolean("persist.demo.hdmirotates", false)) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+ }
}
}
return mInfo;
@@ -223,4 +244,4 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
}
}
-} \ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 5499af6..d61a35b 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -260,8 +260,7 @@ final class LogicalDisplay {
// The orientation specifies how the physical coordinate system of the display
// is rotated when the contents of the logical display are rendered.
int orientation = Surface.ROTATION_0;
- if (device == mPrimaryDisplayDevice
- && (displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0) {
+ if ((displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0) {
orientation = displayInfo.rotation;
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index a165f26..14ef5a9 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -146,9 +146,7 @@ final class VirtualDisplayAdapter extends DisplayAdapter {
@Override
public void performTraversalInTransactionLocked() {
- if (mSurface != null) {
- setSurfaceInTransactionLocked(mSurface);
- }
+ setSurfaceInTransactionLocked(mSurface);
}
public void setSurfaceLocked(Surface surface) {
diff --git a/services/core/java/com/android/server/firewall/IntentFirewall.java b/services/core/java/com/android/server/firewall/IntentFirewall.java
index eb7a383..62114cd 100644
--- a/services/core/java/com/android/server/firewall/IntentFirewall.java
+++ b/services/core/java/com/android/server/firewall/IntentFirewall.java
@@ -95,6 +95,7 @@ public class IntentFirewall {
CategoryFilter.FACTORY,
SenderFilter.FACTORY,
+ SenderPackageFilter.FACTORY,
SenderPermissionFilter.FACTORY,
PortFilter.FACTORY
};
diff --git a/services/core/java/com/android/server/firewall/SenderPackageFilter.java b/services/core/java/com/android/server/firewall/SenderPackageFilter.java
new file mode 100644
index 0000000..ec9b5de
--- /dev/null
+++ b/services/core/java/com/android/server/firewall/SenderPackageFilter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.firewall;
+
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+public class SenderPackageFilter implements Filter {
+ private static final String ATTR_NAME = "name";
+
+ public final String mPackageName;
+
+ public SenderPackageFilter(String packageName) {
+ mPackageName = packageName;
+ }
+
+ @Override
+ public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent,
+ int callerUid, int callerPid, String resolvedType, int receivingUid) {
+ IPackageManager pm = AppGlobals.getPackageManager();
+
+ int packageUid = -1;
+ try {
+ packageUid = pm.getPackageUid(mPackageName, UserHandle.USER_OWNER);
+ } catch (RemoteException ex) {
+ // handled below
+ }
+
+ if (packageUid == -1) {
+ return false;
+ }
+
+ return UserHandle.isSameApp(packageUid, callerUid);
+ }
+
+ public static final FilterFactory FACTORY = new FilterFactory("sender-package") {
+ @Override
+ public Filter newFilter(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ String packageName = parser.getAttributeValue(null, ATTR_NAME);
+
+ if (packageName == null) {
+ throw new XmlPullParserException(
+ "A package name must be specified.", parser, null);
+ }
+
+ return new SenderPackageFilter(packageName);
+ }
+ };
+}
diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java
new file mode 100644
index 0000000..d747cd0
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/FeatureAction.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCecMessage;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Encapsulates a sequence of CEC/MHL command exchange for a certain feature.
+ *
+ * <p>Many CEC/MHL features are accomplished by CEC devices on the bus exchanging
+ * more than one command. {@link FeatureAction} represents the life cycle of the communication,
+ * manages the state as the process progresses, and if necessary, returns the result
+ * to the caller which initiates the action, through the callback given at the creation
+ * of the object. All the actual action classes inherit FeatureAction.
+ *
+ * <p>More than one FeatureAction objects can be up and running simultaneously,
+ * maintained by {@link HdmiControlService}. Each action is passed a new command
+ * arriving from the bus, and either consumes it if the command is what the action expects,
+ * or yields it to other action.
+ *
+ * Declared as package private, accessed by {@link HdmiControlService} only.
+ */
+abstract class FeatureAction {
+
+ private static final String TAG = "FeatureAction";
+
+ // Timer handler message used for timeout event
+ protected static final int MSG_TIMEOUT = 100;
+
+ // Default timeout for the incoming command to arrive in response to a request
+ protected static final int TIMEOUT_MS = 1000;
+
+ // Default state used in common by all the feature actions.
+ protected static final int STATE_NONE = 0;
+
+ // Internal state indicating the progress of action.
+ protected int mState = STATE_NONE;
+
+ protected final HdmiControlService mService;
+
+ // Logical address of the device for which the feature action is taken. The commands
+ // generated in an action all use this field as source address.
+ protected final int mSourceAddress;
+
+ // Timer that manages timeout events.
+ protected ActionTimer mActionTimer;
+
+ FeatureAction(HdmiControlService service, int sourceAddress) {
+ mService = service;
+ mSourceAddress = sourceAddress;
+ mActionTimer = createActionTimer(service.getServiceLooper());
+ }
+
+ @VisibleForTesting
+ void setActionTimer(ActionTimer actionTimer) {
+ mActionTimer = actionTimer;
+ }
+
+ /**
+ * Called right after the action is created. Initialization or first step to take
+ * for the action can be done in this method.
+ *
+ * @return true if the operation is successful; otherwise false.
+ */
+ abstract boolean start();
+
+ /**
+ * Process the command. Called whenever a new command arrives.
+ *
+ * @param cmd command to process
+ * @return true if the command was consumed in the process; Otherwise false, which
+ * indicates that the command shall be handled by other actions.
+ */
+ abstract boolean processCommand(HdmiCecMessage cmd);
+
+ /**
+ * Called when the action should handle the timer event it created before.
+ *
+ * <p>CEC standard mandates each command transmission should be responded within
+ * certain period of time. The method is called when the timer it created as it transmitted
+ * a command gets expired. Inner logic should take an appropriate action.
+ *
+ * @param state the state associated with the time when the timer was created
+ */
+ abstract void handleTimerEvent(int state);
+
+ /**
+ * Timer handler interface used for FeatureAction classes.
+ */
+ interface ActionTimer {
+ /**
+ * Send a timer message.
+ *
+ * Also carries the state of the action when the timer is created. Later this state is
+ * compared to the one the action is in when it receives the timer to let the action tell
+ * the right timer to handle.
+ *
+ * @param state state of the action is in
+ * @param delayMillis amount of delay for the timer
+ */
+ void sendTimerMessage(int state, long delayMillis);
+ }
+
+ private class ActionTimerHandler extends Handler implements ActionTimer {
+
+ public ActionTimerHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void sendTimerMessage(int state, long delayMillis) {
+ sendMessageDelayed(obtainMessage(MSG_TIMEOUT, state), delayMillis);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_TIMEOUT:
+ handleTimerEvent(msg.arg1);
+ break;
+ default:
+ Slog.w(TAG, "Unsupported message:" + msg.what);
+ break;
+ }
+ }
+ }
+
+ private ActionTimer createActionTimer(Looper looper) {
+ return new ActionTimerHandler(looper);
+ }
+
+ // Add a new timer. The timer event will come to mActionTimer.handleMessage() in
+ // delayMillis.
+ protected void addTimer(int state, int delayMillis) {
+ mActionTimer.sendTimerMessage(state, delayMillis);
+ }
+
+ protected final void sendCommand(HdmiCecMessage cmd) {
+ mService.sendCecCommand(cmd);
+ }
+
+ /**
+ * Finish up the action. Reset the state, and remove itself from the action queue.
+ */
+ protected void finish() {
+ mState = STATE_NONE;
+ removeAction(this);
+ }
+
+ /**
+ * Remove the action from the action queue. This is called after the action finishes
+ * its role.
+ *
+ * @param action
+ */
+ private void removeAction(FeatureAction action) {
+ mService.removeAction(action);
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
new file mode 100644
index 0000000..986cb9b
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import libcore.util.EmptyArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command
+ * and pass it to CEC HAL so that it sends message to other device. For incoming
+ * message it translates the message and delegates it to proper module.
+ *
+ * <p>It can be created only by {@link HdmiCecController#create}
+ *
+ * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
+ */
+final class HdmiCecController {
+ private static final String TAG = "HdmiCecController";
+
+ private static final byte[] EMPTY_BODY = EmptyArray.BYTE;
+
+ // A message to pass cec send command to IO looper.
+ private static final int MSG_SEND_CEC_COMMAND = 1;
+ // A message to delegate logical allocation to IO looper.
+ private static final int MSG_ALLOCATE_LOGICAL_ADDRESS = 2;
+
+ // Message types to handle incoming message in main service looper.
+ private final static int MSG_RECEIVE_CEC_COMMAND = 1;
+ // A message to report allocated logical address to main control looper.
+ private final static int MSG_REPORT_LOGICAL_ADDRESS = 2;
+
+ private static final int NUM_LOGICAL_ADDRESS = 16;
+
+ // TODO: define other constants for errors.
+ private static final int ERROR_SUCCESS = 0;
+
+ // Handler instance to process synchronous I/O (mainly send) message.
+ private Handler mIoHandler;
+
+ // Handler instance to process various messages coming from other CEC
+ // device or issued by internal state change.
+ private Handler mControlHandler;
+
+ // Stores the pointer to the native implementation of the service that
+ // interacts with HAL.
+ private long mNativePtr;
+
+ private HdmiControlService mService;
+
+ // Map-like container of all cec devices. A logical address of device is
+ // used as key of container.
+ private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos =
+ new SparseArray<HdmiCecDeviceInfo>();
+ // Set-like container for all local devices' logical address.
+ // Key and value are same.
+ private final SparseIntArray mLocalAddresses = new SparseIntArray();
+
+ // Private constructor. Use HdmiCecController.create().
+ private HdmiCecController() {
+ // TODO: Consider restoring the local device addresses from persistent storage
+ // to allocate the same addresses again if possible.
+ }
+
+ /**
+ * A factory method to get {@link HdmiCecController}. If it fails to initialize
+ * inner device or has no device it will return {@code null}.
+ *
+ * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
+ * @param service {@link HdmiControlService} instance used to create internal handler
+ * and to pass callback for incoming message or event.
+ * @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
+ * returns {@code null}.
+ */
+ static HdmiCecController create(HdmiControlService service) {
+ HdmiCecController handler = new HdmiCecController();
+ long nativePtr = nativeInit(handler);
+ if (nativePtr == 0L) {
+ handler = null;
+ return null;
+ }
+
+ handler.init(service, nativePtr);
+ return handler;
+ }
+
+ /**
+ * Initialize {@link #mLocalAddresses} by allocating logical addresses for each hosted type.
+ *
+ * @param deviceTypes local device types
+ */
+ void initializeLocalDevices(int[] deviceTypes) {
+ for (int deviceType : deviceTypes) {
+ int preferred = getPreferredAddress(deviceType);
+ allocateLogicalAddress(deviceType, preferred, new AllocateLogicalAddressCallback() {
+ @Override
+ public void onAllocated(int deviceType, int logicalAddress) {
+ addLogicalAddress(logicalAddress);
+ }
+ });
+ }
+ }
+
+ /**
+ * Get the preferred address for a given type.
+ *
+ * @param deviceType logical device type to get the address for
+ * @return preferred address; {@link HdmiCec#ADDR_UNREGISTERED} if not available.
+ */
+ private int getPreferredAddress(int deviceType) {
+ // Uses the data restored from persistent memory at boot up if they are available.
+ // Otherwise we return UNREGISTERED indicating there is no preferred address.
+ // Note that for address SPECIFIC_USE(14), HdmiCec.getTypeFromAddress() returns DEVICE_TV,
+ // meaning that we do not support device type video processor yet.
+ for (int i = 0; i < mLocalAddresses.size(); ++i) {
+ int address = mLocalAddresses.keyAt(i);
+ int type = HdmiCec.getTypeFromAddress(address);
+ if (type == deviceType) {
+ return address;
+ }
+ }
+ return HdmiCec.ADDR_UNREGISTERED;
+ }
+
+ /**
+ * Interface to report allocated logical address.
+ */
+ interface AllocateLogicalAddressCallback {
+ /**
+ * Called when a new logical address is allocated.
+ *
+ * @param deviceType requested device type to allocate logical address
+ * @param logicalAddress allocated logical address. If it is
+ * {@link HdmiCec#ADDR_UNREGISTERED}, it means that
+ * it failed to allocate logical address for the given device type
+ */
+ void onAllocated(int deviceType, int logicalAddress);
+ }
+
+ /**
+ * Allocate a new logical address of the given device type. Allocated
+ * address will be reported through {@link AllocateLogicalAddressCallback}.
+ *
+ * <p> Declared as package-private, accessed by {@link HdmiControlService} only.
+ *
+ * @param deviceType type of device to used to determine logical address
+ * @param preferredAddress a logical address preferred to be allocated.
+ * If sets {@link HdmiCec#ADDR_UNREGISTERED}, scans
+ * the smallest logical address matched with the given device type.
+ * Otherwise, scan address will start from {@code preferredAddress}
+ * @param callback callback interface to report allocated logical address to caller
+ */
+ void allocateLogicalAddress(int deviceType, int preferredAddress,
+ AllocateLogicalAddressCallback callback) {
+ Message msg = mIoHandler.obtainMessage(MSG_ALLOCATE_LOGICAL_ADDRESS);
+ msg.arg1 = deviceType;
+ msg.arg2 = preferredAddress;
+ msg.obj = callback;
+ mIoHandler.sendMessage(msg);
+ }
+
+ private static byte[] buildBody(int opcode, byte[] params) {
+ byte[] body = new byte[params.length + 1];
+ body[0] = (byte) opcode;
+ System.arraycopy(params, 0, body, 1, params.length);
+ return body;
+ }
+
+ private final class IoHandler extends Handler {
+ private IoHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SEND_CEC_COMMAND:
+ HdmiCecMessage cecMessage = (HdmiCecMessage) msg.obj;
+ byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
+ nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
+ cecMessage.getDestination(), body);
+ break;
+ case MSG_ALLOCATE_LOGICAL_ADDRESS:
+ int deviceType = msg.arg1;
+ int preferredAddress = msg.arg2;
+ AllocateLogicalAddressCallback callback =
+ (AllocateLogicalAddressCallback) msg.obj;
+ handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
+ break;
+ default:
+ Slog.w(TAG, "Unsupported CEC Io request:" + msg.what);
+ break;
+ }
+ }
+
+ private void handleAllocateLogicalAddress(int deviceType, int preferredAddress,
+ AllocateLogicalAddressCallback callback) {
+ int startAddress = preferredAddress;
+ // If preferred address is "unregistered", start_index will be the smallest
+ // address matched with the given device type.
+ if (preferredAddress == HdmiCec.ADDR_UNREGISTERED) {
+ for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
+ if (deviceType == HdmiCec.getTypeFromAddress(i)) {
+ startAddress = i;
+ break;
+ }
+ }
+ }
+
+ int logcialAddress = HdmiCec.ADDR_UNREGISTERED;
+ // Iterates all possible addresses which has the same device type.
+ for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
+ int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
+ if (curAddress != HdmiCec.ADDR_UNREGISTERED
+ && deviceType == HdmiCec.getTypeFromAddress(i)) {
+ // <Polling Message> is a message which has empty body and
+ // uses same address for both source and destination address.
+ // If sending <Polling Message> failed (NAK), it becomes
+ // new logical address for the device because no device uses
+ // it as logical address of the device.
+ int error = nativeSendCecCommand(mNativePtr, curAddress, curAddress,
+ EMPTY_BODY);
+ if (error != ERROR_SUCCESS) {
+ logcialAddress = curAddress;
+ break;
+ }
+ }
+ }
+
+ Message msg = mControlHandler.obtainMessage(MSG_REPORT_LOGICAL_ADDRESS);
+ msg.arg1 = deviceType;
+ msg.arg2 = logcialAddress;
+ msg.obj = callback;
+ mControlHandler.sendMessage(msg);
+ }
+ }
+
+ private final class ControlHandler extends Handler {
+ private ControlHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_RECEIVE_CEC_COMMAND:
+ // TODO: delegate it to HdmiControl service.
+ onReceiveCommand((HdmiCecMessage) msg.obj);
+ break;
+ case MSG_REPORT_LOGICAL_ADDRESS:
+ int deviceType = msg.arg1;
+ int logicalAddress = msg.arg2;
+ AllocateLogicalAddressCallback callback =
+ (AllocateLogicalAddressCallback) msg.obj;
+ callback.onAllocated(deviceType, logicalAddress);
+ break;
+ default:
+ Slog.i(TAG, "Unsupported message type:" + msg.what);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same
+ * logical address as new device info's.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ *
+ * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added.
+ * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo}
+ * that has the same logical address as new one has.
+ */
+ HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
+ HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress());
+ if (oldDeviceInfo != null) {
+ removeDeviceInfo(deviceInfo.getLogicalAddress());
+ }
+ mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo);
+ return oldDeviceInfo;
+ }
+
+ /**
+ * Remove a device info corresponding to the given {@code logicalAddress}.
+ * It returns removed {@link HdmiCecDeviceInfo} if exists.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ *
+ * @param logicalAddress logical address of device to be removed
+ * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null}
+ */
+ HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) {
+ HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress);
+ if (deviceInfo != null) {
+ mDeviceInfos.remove(logicalAddress);
+ }
+ return deviceInfo;
+ }
+
+ /**
+ * Return a list of all {@HdmiCecDeviceInfo}.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ */
+ List<HdmiCecDeviceInfo> getDeviceInfoList() {
+ List<HdmiCecDeviceInfo> deviceInfoList = new ArrayList<HdmiCecDeviceInfo>(
+ mDeviceInfos.size());
+ for (int i = 0; i < mDeviceInfos.size(); ++i) {
+ deviceInfoList.add(mDeviceInfos.valueAt(i));
+ }
+ return deviceInfoList;
+ }
+
+ /**
+ * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ *
+ * @param logicalAddress logical address to be retrieved
+ * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}.
+ * Returns null if no logical address matched
+ */
+ HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
+ return mDeviceInfos.get(logicalAddress);
+ }
+
+ /**
+ * Add a new logical address to the device. Device's HW should be notified
+ * when a new logical address is assigned to a device, so that it can accept
+ * a command having available destinations.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ *
+ * @param newLogicalAddress a logical address to be added
+ * @return 0 on success. Otherwise, returns negative value
+ */
+ int addLogicalAddress(int newLogicalAddress) {
+ if (HdmiCec.isValidAddress(newLogicalAddress)) {
+ mLocalAddresses.put(newLogicalAddress, newLogicalAddress);
+ return nativeAddLogicalAddress(mNativePtr, newLogicalAddress);
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Clear all logical addresses registered in the device.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ */
+ void clearLogicalAddress() {
+ // TODO: consider to backup logical address so that new logical address
+ // allocation can use it as preferred address.
+ mLocalAddresses.clear();
+ nativeClearLogicalAddress(mNativePtr);
+ }
+
+ /**
+ * Return the physical address of the device.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ *
+ * @return CEC physical address of the device. The range of success address
+ * is between 0x0000 and 0xFFFF. If failed it returns -1
+ */
+ int getPhysicalAddress() {
+ return nativeGetPhysicalAddress(mNativePtr);
+ }
+
+ /**
+ * Return CEC version of the device.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ */
+ int getVersion() {
+ return nativeGetVersion(mNativePtr);
+ }
+
+ /**
+ * Return vendor id of the device.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ */
+ int getVendorId() {
+ return nativeGetVendorId(mNativePtr);
+ }
+
+ private void init(HdmiControlService service, long nativePtr) {
+ mService = service;
+ mIoHandler = new IoHandler(service.getServiceLooper());
+ mControlHandler = new ControlHandler(service.getServiceLooper());
+ mNativePtr = nativePtr;
+ }
+
+ private boolean isAcceptableAddress(int address) {
+ // Can access command targeting devices available in local device or
+ // broadcast command.
+ return address == HdmiCec.ADDR_BROADCAST
+ || mLocalAddresses.indexOfKey(address) < 0;
+ }
+
+ private void onReceiveCommand(HdmiCecMessage message) {
+ if (isAcceptableAddress(message.getDestination()) &&
+ mService.handleCecCommand(message)) {
+ return;
+ }
+
+ // TODO: Use device's source address for broadcast message.
+ int sourceAddress = message.getDestination() != HdmiCec.ADDR_BROADCAST ?
+ message.getDestination() : 0;
+ // Reply <Feature Abort> to initiator (source) for all requests.
+ HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand
+ (sourceAddress, message.getSource(), message.getOpcode(),
+ HdmiCecMessageBuilder.ABORT_REFUSED);
+ sendCommand(cecMessage);
+
+ }
+
+ void sendCommand(HdmiCecMessage cecMessage) {
+ Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage);
+ mIoHandler.sendMessage(message);
+ }
+
+ /**
+ * Called by native when incoming CEC message arrived.
+ */
+ private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
+ byte opcode = body[0];
+ byte params[] = Arrays.copyOfRange(body, 1, body.length);
+ HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params);
+
+ // Delegate message to main handler so that it handles in main thread.
+ Message message = mControlHandler.obtainMessage(
+ MSG_RECEIVE_CEC_COMMAND, cecMessage);
+ mControlHandler.sendMessage(message);
+ }
+
+ /**
+ * Called by native when a hotplug event issues.
+ */
+ private void handleHotplug(boolean connected) {
+ // TODO: Delegate event to main message handler.
+ }
+
+ private static native long nativeInit(HdmiCecController handler);
+ private static native int nativeSendCecCommand(long controllerPtr, int srcAddress,
+ int dstAddress, byte[] body);
+ private static native int nativeAddLogicalAddress(long controllerPtr, int logicalAddress);
+ private static native void nativeClearLogicalAddress(long controllerPtr);
+ private static native int nativeGetPhysicalAddress(long controllerPtr);
+ private static native int nativeGetVersion(long controllerPtr);
+ private static native int nativeGetVendorId(long controllerPtr);
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
new file mode 100644
index 0000000..6d2b83b
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * A helper class to build {@link HdmiCecMessage} from various cec commands.
+ */
+public class HdmiCecMessageBuilder {
+ // TODO: move these values to HdmiCec.java once make it internal constant class.
+ // CEC's ABORT reason values.
+ static final int ABORT_UNRECOGNIZED_MODE = 0;
+ static final int ABORT_NOT_IN_CORRECT_MODE = 1;
+ static final int ABORT_CANNOT_PROVIDE_SOURCE = 2;
+ static final int ABORT_INVALID_OPERAND = 3;
+ static final int ABORT_REFUSED = 4;
+ static final int ABORT_UNABLE_TO_DETERMINE = 5;
+
+ private static final int OSD_NAME_MAX_LENGTH = 13;
+
+ private HdmiCecMessageBuilder() {}
+
+ /**
+ * Build &lt;Feature Abort&gt; command. &lt;Feature Abort&gt; consists of
+ * 1 byte original opcode and 1 byte reason fields with basic fields.
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @param originalOpcode original opcode causing feature abort
+ * @param reason reason of feature abort
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildFeatureAbortCommand(int src, int dest, int originalOpcode,
+ int reason) {
+ byte[] params = new byte[] {
+ (byte) originalOpcode,
+ (byte) reason,
+ };
+ return buildCommand(src, dest, HdmiCec.MESSAGE_FEATURE_ABORT, params);
+ }
+
+ /**
+ * Build &lt;Give Osd Name&gt; command.
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildGiveOsdNameCommand(int src, int dest) {
+ return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_OSD_NAME);
+ }
+
+ /**
+ * Build &lt;Give Vendor Id Command&gt; command.
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildGiveDeviceVendorIdCommand(int src, int dest) {
+ return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID);
+ }
+
+ /**
+ * Build &lt;Set Menu Language &gt; command.
+ *
+ * <p>This is a broadcast message sent to all devices on the bus.
+ *
+ * @param src source address of command
+ * @param language 3-letter ISO639-2 based language code
+ * @return newly created {@link HdmiCecMessage} if language is valid.
+ * Otherwise, return null
+ */
+ static HdmiCecMessage buildSetMenuLanguageCommand(int src, String language) {
+ if (language.length() != 3) {
+ return null;
+ }
+ // Hdmi CEC uses lower-cased ISO 639-2 (3 letters code).
+ String normalized = language.toLowerCase();
+ byte[] params = new byte[] {
+ (byte) normalized.charAt(0),
+ (byte) normalized.charAt(1),
+ (byte) normalized.charAt(2),
+ };
+ // <Set Menu Language> is broadcast message.
+ return buildCommand(src, HdmiCec.ADDR_BROADCAST, HdmiCec.MESSAGE_SET_MENU_LANGUAGE,
+ params);
+ }
+
+ /**
+ * Build &lt;Set Osd Name &gt; command.
+ *
+ * @param src source address of command
+ * @param name display (OSD) name of device
+ * @return newly created {@link HdmiCecMessage} if valid name. Otherwise,
+ * return null
+ */
+ static HdmiCecMessage buildSetOsdNameCommand(int src, int dest, String name) {
+ int length = Math.min(name.length(), OSD_NAME_MAX_LENGTH);
+ byte[] params;
+ try {
+ params = name.substring(0, length).getBytes("US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ return null;
+ }
+ return buildCommand(src, dest, HdmiCec.MESSAGE_SET_OSD_NAME, params);
+ }
+
+ /**
+ * Build &lt;Report Physical Address&gt; command. It has two bytes physical
+ * address and one byte device type as parameter.
+ *
+ * <p>This is a broadcast message sent to all devices on the bus.
+ *
+ * @param src source address of command
+ * @param address physical address of device
+ * @param deviceType type of device
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildReportPhysicalAddressCommand(int src, int address, int deviceType) {
+ byte[] params = new byte[] {
+ // Two bytes for physical address
+ (byte) ((address >> 8) & 0xFF),
+ (byte) (address & 0xFF),
+ // One byte device type
+ (byte) deviceType
+ };
+ // <Report Physical Address> is broadcast message.
+ return buildCommand(src, HdmiCec.ADDR_BROADCAST, HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS,
+ params);
+ }
+
+ /**
+ * Build &lt;Device Vendor Id&gt; command. It has three bytes vendor id as
+ * parameter.
+ *
+ * <p>This is a broadcast message sent to all devices on the bus.
+ *
+ * @param src source address of command
+ * @param vendorId device's vendor id
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildDeviceVendorIdCommand(int src, int vendorId) {
+ byte[] params = new byte[] {
+ (byte) ((vendorId >> 16) & 0xFF),
+ (byte) ((vendorId >> 8) & 0xFF),
+ (byte) (vendorId & 0xFF)
+ };
+ // <Device Vendor Id> is broadcast message.
+ return buildCommand(src, HdmiCec.ADDR_BROADCAST, HdmiCec.MESSAGE_DEVICE_VENDOR_ID,
+ params);
+ }
+
+ /**
+ * Build &lt;Device Vendor Id&gt; command. It has one byte cec version as parameter.
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @param version version of cec. Use 0x04 for "Version 1.3a" and 0x05 for
+ * "Version 1.4 or 1.4a or 1.4b
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildCecVersion(int src, int dest, int version) {
+ byte[] params = new byte[] {
+ (byte) version
+ };
+ return buildCommand(src, dest, HdmiCec.MESSAGE_CEC_VERSION, params);
+ }
+
+ /**
+ * Build a {@link HdmiCecMessage} without extra parameter.
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @param opcode opcode for a message
+ * @return newly created {@link HdmiCecMessage}
+ */
+ private static HdmiCecMessage buildCommand(int src, int dest, int opcode) {
+ return new HdmiCecMessage(src, dest, opcode, HdmiCecMessage.EMPTY_PARAM);
+ }
+
+ /**
+ * Build a {@link HdmiCecMessage} with given values.
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @param opcode opcode for a message
+ * @param params extra parameters for command
+ * @return newly created {@link HdmiCecMessage}
+ */
+ private static HdmiCecMessage buildCommand(int src, int dest, int opcode, byte[] params) {
+ return new HdmiCecMessage(src, dest, opcode, params);
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
new file mode 100644
index 0000000..7c136db
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import java.util.Locale;
+
+/**
+ * Provides a service for sending and processing HDMI control messages,
+ * HDMI-CEC and MHL control command, and providing the information on both standard.
+ */
+public final class HdmiControlService extends SystemService {
+ private static final String TAG = "HdmiControlService";
+
+ // A thread to handle synchronous IO of CEC and MHL control service.
+ // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
+ // and sparse call it shares a thread to handle IO operations.
+ private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
+
+ @Nullable
+ private HdmiCecController mCecController;
+
+ @Nullable
+ private HdmiMhlController mMhlController;
+
+ public HdmiControlService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mCecController = HdmiCecController.create(this);
+ if (mCecController != null) {
+ mCecController.initializeLocalDevices(getContext().getResources()
+ .getIntArray(com.android.internal.R.array.config_hdmiCecLogicalDeviceType));
+ } else {
+ Slog.i(TAG, "Device does not support HDMI-CEC.");
+ }
+
+ mMhlController = HdmiMhlController.create(this);
+ if (mMhlController == null) {
+ Slog.i(TAG, "Device does not support MHL-control.");
+ }
+ }
+
+ /**
+ * Returns {@link Looper} for IO operation.
+ *
+ * <p>Declared as package-private.
+ */
+ Looper getIoLooper() {
+ return mIoThread.getLooper();
+ }
+
+ /**
+ * Returns {@link Looper} of main thread. Use this {@link Looper} instance
+ * for tasks that are running on main service thread.
+ *
+ * <p>Declared as package-private.
+ */
+ Looper getServiceLooper() {
+ return Looper.myLooper();
+ }
+
+ /**
+ * Add a new {@link FeatureAction} to the action queue.
+ *
+ * @param action {@link FeatureAction} to add
+ */
+ void addAction(FeatureAction action) {
+ // TODO: Implement this.
+ }
+
+
+ /**
+ * Remove the given {@link FeatureAction} object from the action queue.
+ *
+ * @param action {@link FeatureAction} to add
+ */
+ void removeAction(FeatureAction action) {
+ // TODO: Implement this.
+ }
+
+ /**
+ * Transmit a CEC command to CEC bus.
+ *
+ * @param command CEC command to send out
+ */
+ void sendCecCommand(HdmiCecMessage command) {
+ mCecController.sendCommand(command);
+ }
+
+ /**
+ * Add a new {@link HdmiCecDeviceInfo} to controller.
+ *
+ * @param deviceInfo new device information object to add
+ */
+ void addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
+ // TODO: Implement this.
+ }
+
+ boolean handleCecCommand(HdmiCecMessage message) {
+ // Commands that queries system information replies directly instead
+ // of creating FeatureAction because they are state-less.
+ switch (message.getOpcode()) {
+ case HdmiCec.MESSAGE_GET_MENU_LANGUAGE:
+ handleGetMenuLanguage(message);
+ return true;
+ case HdmiCec.MESSAGE_GIVE_OSD_NAME:
+ handleGiveOsdName(message);
+ return true;
+ case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS:
+ handleGivePhysicalAddress(message);
+ return true;
+ case HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID:
+ handleGiveDeviceVendorId(message);
+ return true;
+ case HdmiCec.MESSAGE_GET_CEC_VERSION:
+ handleGetCecVersion(message);
+ return true;
+ // TODO: Add remaining system information query such as
+ // <Give Device Power Status> and <Request Active Source> handler.
+ default:
+ Slog.w(TAG, "Unsupported cec command:" + message.toString());
+ return false;
+ }
+ }
+
+ private void handleGetCecVersion(HdmiCecMessage message) {
+ int version = mCecController.getVersion();
+ HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
+ message.getSource(),
+ version);
+ sendCecCommand(cecMessage);
+ }
+
+ private void handleGiveDeviceVendorId(HdmiCecMessage message) {
+ int vendorId = mCecController.getVendorId();
+ HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
+ message.getDestination(), vendorId);
+ sendCecCommand(cecMessage);
+ }
+
+ private void handleGivePhysicalAddress(HdmiCecMessage message) {
+ int physicalAddress = mCecController.getPhysicalAddress();
+ int deviceType = HdmiCec.getTypeFromAddress(message.getDestination());
+ HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ message.getDestination(), physicalAddress, deviceType);
+ sendCecCommand(cecMessage);
+ }
+
+ private void handleGiveOsdName(HdmiCecMessage message) {
+ // TODO: read device name from settings or property.
+ String name = HdmiCec.getDefaultDeviceName(message.getDestination());
+ HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand(
+ message.getDestination(), message.getSource(), name);
+ if (cecMessage != null) {
+ sendCecCommand(cecMessage);
+ } else {
+ Slog.w(TAG, "Failed to build <Get Osd Name>:" + name);
+ }
+ }
+
+ private void handleGetMenuLanguage(HdmiCecMessage message) {
+ // Only 0 (TV), 14 (specific use) can answer.
+ if (message.getDestination() != HdmiCec.ADDR_TV
+ && message.getDestination() != HdmiCec.ADDR_SPECIFIC_USE) {
+ Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
+ sendCecCommand(
+ HdmiCecMessageBuilder.buildFeatureAbortCommand(message.getDestination(),
+ message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE,
+ HdmiCecMessageBuilder.ABORT_UNRECOGNIZED_MODE));
+ return;
+ }
+
+ HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
+ message.getDestination(),
+ Locale.getDefault().getISO3Language());
+ // TODO: figure out how to handle failed to get language code.
+ if (command != null) {
+ sendCecCommand(command);
+ } else {
+ Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
new file mode 100644
index 0000000..e0bc718
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.util.Slog;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Feature action that discovers the information of a newly found logical device.
+ *
+ * This action is created when receiving &lt;Report Physical Address&gt;, a CEC command a newly
+ * connected HDMI-CEC device broadcasts to announce its advent. Additional commands are issued in
+ * this action to gather more information on the device such as OSD name and device vendor ID.
+ *
+ * <p>The result is made in the form of {@link HdmiCecDeviceInfo} object, and passed to service
+ * for the management through its life cycle.
+ *
+ * <p>Package-private, accessed by {@link HdmiControlService} only.
+ */
+final class NewDeviceAction extends FeatureAction {
+
+ private static final String TAG = "NewDeviceAction";
+
+ // State in which the action sent <Give OSD Name> and is waiting for <Set OSD Name>
+ // that contains the name of the device for display on screen.
+ static final int STATE_WAITING_FOR_SET_OSD_NAME = 1;
+
+ // State in which the action sent <Give Device Vendor ID> and is waiting for
+ // <Device Vendor ID> that contains the vendor ID of the device.
+ static final int STATE_WAITING_FOR_DEVICE_VENDOR_ID = 2;
+
+ private final int mDeviceLogicalAddress;
+ private final int mDevicePhysicalAddress;
+
+ private int mVendorId;
+ private String mDisplayName;
+
+ /**
+ * Constructor.
+ *
+ * @param service {@link HdmiControlService} instance
+ * @param sourceAddress logical address to be used as source address
+ * @param deviceLogicalAddress logical address of the device in interest
+ * @param devicePhysicalAddress physical address of the device in interest
+ */
+ NewDeviceAction(HdmiControlService service, int sourceAddress, int deviceLogicalAddress,
+ int devicePhysicalAddress) {
+ super(service, sourceAddress);
+ mDeviceLogicalAddress = deviceLogicalAddress;
+ mDevicePhysicalAddress = devicePhysicalAddress;
+ mVendorId = HdmiCec.UNKNOWN_VENDOR_ID;
+ }
+
+ @Override
+ public boolean start() {
+ sendCommand(
+ HdmiCecMessageBuilder.buildGiveOsdNameCommand(mSourceAddress,
+ mDeviceLogicalAddress));
+ mState = STATE_WAITING_FOR_SET_OSD_NAME;
+ addTimer(mState, TIMEOUT_MS);
+ return true;
+ }
+
+ @Override
+ public boolean processCommand(HdmiCecMessage cmd) {
+ // For the logical device in interest, we want two more pieces of information -
+ // osd name and vendor id. They are requested in sequence. In case we don't
+ // get the expected responses (either by timeout or by receiving <feature abort> command),
+ // set them to a default osd name and unknown vendor id respectively.
+ int opcode = cmd.getOpcode();
+ int src = cmd.getSource();
+ byte[] params = cmd.getParams();
+
+ if (mDeviceLogicalAddress != src) {
+ return false;
+ }
+
+ if (mState == STATE_WAITING_FOR_SET_OSD_NAME) {
+ if (opcode == HdmiCec.MESSAGE_SET_OSD_NAME) {
+ try {
+ mDisplayName = new String(params, "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ Slog.e(TAG, "Failed to get OSD name: " + e.getMessage());
+ }
+ mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID;
+ requestVendorId();
+ return true;
+ } else if (opcode == HdmiCec.MESSAGE_FEATURE_ABORT) {
+ int requestOpcode = params[1];
+ if (requestOpcode == HdmiCec.MESSAGE_SET_OSD_NAME) {
+ mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID;
+ requestVendorId();
+ return true;
+ }
+ }
+ } else if (mState == STATE_WAITING_FOR_DEVICE_VENDOR_ID) {
+ if (opcode == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) {
+ if (params.length == 3) {
+ mVendorId = (params[0] << 16) + (params[1] << 8) + params[2];
+ } else {
+ Slog.e(TAG, "Failed to get device vendor ID: ");
+ }
+ addDeviceInfo();
+ finish();
+ return true;
+ } else if (opcode == HdmiCec.MESSAGE_FEATURE_ABORT) {
+ int requestOpcode = params[1];
+ if (requestOpcode == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) {
+ addDeviceInfo();
+ finish();
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void requestVendorId() {
+ sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(mSourceAddress,
+ mDeviceLogicalAddress));
+ addTimer(mState, TIMEOUT_MS);
+ }
+
+ private void addDeviceInfo() {
+ if (mDisplayName == null) {
+ mDisplayName = HdmiCec.getDefaultDeviceName(mDeviceLogicalAddress);
+ }
+ mService.addDeviceInfo(new HdmiCecDeviceInfo(
+ mDeviceLogicalAddress, mDevicePhysicalAddress,
+ HdmiCec.getTypeFromAddress(mDeviceLogicalAddress),
+ mVendorId, mDisplayName));
+ }
+
+ @Override
+ public void handleTimerEvent(int state) {
+ if (mState == STATE_NONE || mState != state) {
+ return;
+ }
+ if (state == STATE_WAITING_FOR_SET_OSD_NAME) {
+ // Osd name request timed out. Try vendor id
+ mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID;
+ requestVendorId();
+ } else if (state == STATE_WAITING_FOR_DEVICE_VENDOR_ID) {
+ // vendor id timed out. Go ahead creating the device info what we've got so far.
+ addDeviceInfo();
+ finish();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index a32f7c1..0f5805c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -52,6 +52,7 @@ import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.KeyboardLayout;
+import android.hardware.input.TouchCalibration;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
@@ -76,6 +77,7 @@ import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.PointerIcon;
+import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicy;
import android.widget.Toast;
@@ -184,6 +186,7 @@ public class InputManagerService extends IInputManager.Stub
private static native void nativeSetPointerSpeed(long ptr, int speed);
private static native void nativeSetShowTouches(long ptr, boolean enabled);
private static native void nativeSetInteractive(long ptr, boolean interactive);
+ private static native void nativeReloadCalibration(long ptr);
private static native void nativeVibrate(long ptr, int deviceId, long[] pattern,
int repeat, int token);
private static native void nativeCancelVibrate(long ptr, int deviceId, int token);
@@ -320,6 +323,10 @@ public class InputManagerService extends IInputManager.Stub
mHandler.sendEmptyMessage(MSG_RELOAD_DEVICE_ALIASES);
mHandler.sendEmptyMessage(MSG_UPDATE_KEYBOARD_LAYOUTS);
+
+ if (mWiredAccessoryCallbacks != null) {
+ mWiredAccessoryCallbacks.systemReady();
+ }
}
private void reloadKeyboardLayouts() {
@@ -701,6 +708,47 @@ public class InputManagerService extends IInputManager.Stub
mTempFullKeyboards.clear();
}
+ @Override // Binder call & native callback
+ public TouchCalibration getTouchCalibrationForInputDevice(String inputDeviceDescriptor,
+ int surfaceRotation) {
+ if (inputDeviceDescriptor == null) {
+ throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+ }
+
+ synchronized (mDataStore) {
+ return mDataStore.getTouchCalibration(inputDeviceDescriptor, surfaceRotation);
+ }
+ }
+
+ @Override // Binder call
+ public void setTouchCalibrationForInputDevice(String inputDeviceDescriptor, int surfaceRotation,
+ TouchCalibration calibration) {
+ if (!checkCallingPermission(android.Manifest.permission.SET_INPUT_CALIBRATION,
+ "setTouchCalibrationForInputDevice()")) {
+ throw new SecurityException("Requires SET_INPUT_CALIBRATION permission");
+ }
+ if (inputDeviceDescriptor == null) {
+ throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+ }
+ if (calibration == null) {
+ throw new IllegalArgumentException("calibration must not be null");
+ }
+ if (surfaceRotation < Surface.ROTATION_0 || surfaceRotation > Surface.ROTATION_270) {
+ throw new IllegalArgumentException("surfaceRotation value out of bounds");
+ }
+
+ synchronized (mDataStore) {
+ try {
+ if (mDataStore.setTouchCalibration(inputDeviceDescriptor, surfaceRotation,
+ calibration)) {
+ nativeReloadCalibration(mPtr);
+ }
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
+ }
+ }
+
// Must be called on handler.
private void showMissingKeyboardLayoutNotification() {
if (!mKeyboardLayoutNotificationShown) {
@@ -1544,6 +1592,7 @@ public class InputManagerService extends IInputManager.Stub
*/
public interface WiredAccessoryCallbacks {
public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask);
+ public void systemReady();
}
/**
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index 71de776..92fa813 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -24,6 +24,8 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import android.view.Surface;
+import android.hardware.input.TouchCalibration;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.Xml;
@@ -82,6 +84,30 @@ final class PersistentDataStore {
}
}
+ public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+ if (state == null) {
+ return TouchCalibration.IDENTITY;
+ }
+
+ TouchCalibration cal = state.getTouchCalibration(surfaceRotation);
+ if (cal == null) {
+ return TouchCalibration.IDENTITY;
+ }
+ return cal;
+ }
+
+ public boolean setTouchCalibration(String inputDeviceDescriptor, int surfaceRotation, TouchCalibration calibration) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+
+ if (state.setTouchCalibration(surfaceRotation, calibration)) {
+ setDirty();
+ return true;
+ }
+
+ return false;
+ }
+
public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
return state != null ? state.getCurrentKeyboardLayout() : null;
@@ -275,9 +301,35 @@ final class PersistentDataStore {
}
private static final class InputDeviceState {
+ private static final String[] CALIBRATION_NAME = { "x_scale",
+ "x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
+
+ private TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
private String mCurrentKeyboardLayout;
private ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
+ public TouchCalibration getTouchCalibration(int surfaceRotation) {
+ try {
+ return mTouchCalibration[surfaceRotation];
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ Slog.w(InputManagerService.TAG, "Cannot get touch calibration.", ex);
+ return null;
+ }
+ }
+
+ public boolean setTouchCalibration(int surfaceRotation, TouchCalibration calibration) {
+ try {
+ if (!calibration.equals(mTouchCalibration[surfaceRotation])) {
+ mTouchCalibration[surfaceRotation] = calibration;
+ return true;
+ }
+ return false;
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ Slog.w(InputManagerService.TAG, "Cannot set touch calibration.", ex);
+ return false;
+ }
+ }
+
public String getCurrentKeyboardLayout() {
return mCurrentKeyboardLayout;
}
@@ -389,6 +441,52 @@ final class PersistentDataStore {
}
mCurrentKeyboardLayout = descriptor;
}
+ } else if (parser.getName().equals("calibration")) {
+ String format = parser.getAttributeValue(null, "format");
+ String rotation = parser.getAttributeValue(null, "rotation");
+ int r = -1;
+
+ if (format == null) {
+ throw new XmlPullParserException(
+ "Missing format attribute on calibration.");
+ }
+ if (!format.equals("affine")) {
+ throw new XmlPullParserException(
+ "Unsupported format for calibration.");
+ }
+ if (rotation != null) {
+ try {
+ r = stringToSurfaceRotation(rotation);
+ } catch (IllegalArgumentException e) {
+ throw new XmlPullParserException(
+ "Unsupported rotation for calibration.");
+ }
+ }
+
+ float[] matrix = TouchCalibration.IDENTITY.getAffineTransform();
+ int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ String tag = parser.getName().toLowerCase();
+ String value = parser.nextText();
+
+ for (int i = 0; i < matrix.length && i < CALIBRATION_NAME.length; i++) {
+ if (tag.equals(CALIBRATION_NAME[i])) {
+ matrix[i] = Float.parseFloat(value);
+ break;
+ }
+ }
+ }
+
+ if (r == -1) {
+ // Assume calibration applies to all rotations
+ for (r = 0; r < mTouchCalibration.length; r++) {
+ mTouchCalibration[r] = new TouchCalibration(matrix[0],
+ matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
+ }
+ } else {
+ mTouchCalibration[r] = new TouchCalibration(matrix[0],
+ matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
+ }
}
}
@@ -411,6 +509,49 @@ final class PersistentDataStore {
}
serializer.endTag(null, "keyboard-layout");
}
+
+ for (int i = 0; i < mTouchCalibration.length; i++) {
+ if (mTouchCalibration[i] != null) {
+ String rotation = surfaceRotationToString(i);
+ float[] transform = mTouchCalibration[i].getAffineTransform();
+
+ serializer.startTag(null, "calibration");
+ serializer.attribute(null, "format", "affine");
+ serializer.attribute(null, "rotation", rotation);
+ for (int j = 0; j < transform.length && j < CALIBRATION_NAME.length; j++) {
+ serializer.startTag(null, CALIBRATION_NAME[j]);
+ serializer.text(Float.toString(transform[j]));
+ serializer.endTag(null, CALIBRATION_NAME[j]);
+ }
+ serializer.endTag(null, "calibration");
+ }
+ }
+ }
+
+ private static String surfaceRotationToString(int surfaceRotation) {
+ switch (surfaceRotation) {
+ case Surface.ROTATION_0: return "0";
+ case Surface.ROTATION_90: return "90";
+ case Surface.ROTATION_180: return "180";
+ case Surface.ROTATION_270: return "270";
+ }
+ throw new IllegalArgumentException("Unsupported surface rotation value" + surfaceRotation);
+ }
+
+ private static int stringToSurfaceRotation(String s) {
+ if ("0".equals(s)) {
+ return Surface.ROTATION_0;
+ }
+ if ("90".equals(s)) {
+ return Surface.ROTATION_90;
+ }
+ if ("180".equals(s)) {
+ return Surface.ROTATION_180;
+ }
+ if ("270".equals(s)) {
+ return Surface.ROTATION_270;
+ }
+ throw new IllegalArgumentException("Unsupported surface rotation string '" + s + "'");
}
}
-} \ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/location/ComprehensiveCountryDetector.java b/services/core/java/com/android/server/location/ComprehensiveCountryDetector.java
index 354858b..6117a9b 100644
--- a/services/core/java/com/android/server/location/ComprehensiveCountryDetector.java
+++ b/services/core/java/com/android/server/location/ComprehensiveCountryDetector.java
@@ -26,7 +26,6 @@ import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.util.Log;
import android.util.Slog;
import java.util.Locale;
diff --git a/services/core/java/com/android/server/location/FlpHardwareProvider.java b/services/core/java/com/android/server/location/FlpHardwareProvider.java
index fab84a8..51ee93b 100644
--- a/services/core/java/com/android/server/location/FlpHardwareProvider.java
+++ b/services/core/java/com/android/server/location/FlpHardwareProvider.java
@@ -60,9 +60,14 @@ public class FlpHardwareProvider {
private static final int FLP_RESULT_ID_UNKNOWN = -5;
private static final int FLP_RESULT_INVALID_GEOFENCE_TRANSITION = -6;
+ // FlpHal monitor status codes, they must be equal to the ones in fused_location.h
+ private static final int FLP_GEOFENCE_MONITOR_STATUS_UNAVAILABLE = 1<<0;
+ private static final int FLP_GEOFENCE_MONITOR_STATUS_AVAILABLE = 1<<1;
+
public static FlpHardwareProvider getInstance(Context context) {
if (sSingletonInstance == null) {
sSingletonInstance = new FlpHardwareProvider(context);
+ sSingletonInstance.nativeInit();
}
return sSingletonInstance;
@@ -91,7 +96,7 @@ public class FlpHardwareProvider {
Looper.myLooper());
}
- public static boolean isSupported() {
+ public boolean isSupported() {
return nativeIsSupported();
}
@@ -141,6 +146,8 @@ public class FlpHardwareProvider {
int transition,
long timestamp,
int sourcesUsed) {
+ // the transition Id does not require translation because the values in fused_location.h
+ // and GeofenceHardware are in sync
getGeofenceHardwareSink().reportGeofenceTransition(
geofenceId,
updateLocationInformation(location),
@@ -157,9 +164,23 @@ public class FlpHardwareProvider {
updatedLocation = updateLocationInformation(location);
}
+ int monitorStatus;
+ switch (status) {
+ case FLP_GEOFENCE_MONITOR_STATUS_UNAVAILABLE:
+ monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE;
+ break;
+ case FLP_GEOFENCE_MONITOR_STATUS_AVAILABLE:
+ monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE;
+ break;
+ default:
+ Log.e(TAG, "Invalid FlpHal Geofence monitor status: " + status);
+ monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE;
+ break;
+ }
+
getGeofenceHardwareSink().reportGeofenceMonitorStatus(
GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE,
- status,
+ monitorStatus,
updatedLocation,
source);
}
@@ -238,12 +259,10 @@ public class FlpHardwareProvider {
public static final String GEOFENCING = "Geofencing";
public IFusedLocationHardware getLocationHardware() {
- nativeInit();
return mLocationHardware;
}
public IFusedGeofenceHardware getGeofenceHardware() {
- nativeInit();
return mGeofenceHardwareService;
}
diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java
index 5d4a770..422b94b 100644
--- a/services/core/java/com/android/server/location/GeocoderProxy.java
+++ b/services/core/java/com/android/server/location/GeocoderProxy.java
@@ -22,7 +22,6 @@ import android.location.GeocoderParams;
import android.location.IGeocodeProvider;
import android.os.Handler;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.Log;
import com.android.server.ServiceWatcher;
diff --git a/services/core/java/com/android/server/location/GeofenceProxy.java b/services/core/java/com/android/server/location/GeofenceProxy.java
index bbc1f47..b886eef 100644
--- a/services/core/java/com/android/server/location/GeofenceProxy.java
+++ b/services/core/java/com/android/server/location/GeofenceProxy.java
@@ -32,8 +32,6 @@ import android.os.UserHandle;
import android.util.Log;
import com.android.server.ServiceWatcher;
-import java.util.List;
-
/**
* @hide
*/
diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java
index 9c76c19..c6cf68f 100644
--- a/services/core/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/core/java/com/android/server/location/GpsLocationProvider.java
@@ -791,6 +791,7 @@ public class GpsLocationProvider implements LocationProviderInterface {
private void handleDisable() {
if (DEBUG) Log.d(TAG, "handleDisable");
+ updateClientUids(new WorkSource());
stopNavigating();
mAlarmManager.cancel(mWakeupIntent);
mAlarmManager.cancel(mTimeoutIntent);
diff --git a/services/core/java/com/android/server/location/GpsXtraDownloader.java b/services/core/java/com/android/server/location/GpsXtraDownloader.java
index e420073..9dedb35 100644
--- a/services/core/java/com/android/server/location/GpsXtraDownloader.java
+++ b/services/core/java/com/android/server/location/GpsXtraDownloader.java
@@ -25,7 +25,6 @@ import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
-import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.params.ConnRouteParams;
diff --git a/services/core/java/com/android/server/location/LocationFudger.java b/services/core/java/com/android/server/location/LocationFudger.java
index 2a68743..ae71fe3 100644
--- a/services/core/java/com/android/server/location/LocationFudger.java
+++ b/services/core/java/com/android/server/location/LocationFudger.java
@@ -22,10 +22,7 @@ import java.security.SecureRandom;
import android.content.Context;
import android.database.ContentObserver;
import android.location.Location;
-import android.location.LocationManager;
-import android.os.Bundle;
import android.os.Handler;
-import android.os.Parcelable;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
index 14db862..5eb06ed 100644
--- a/services/core/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/core/java/com/android/server/location/LocationProviderProxy.java
@@ -18,7 +18,6 @@ package com.android.server.location;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.List;
import android.content.Context;
import android.location.LocationProvider;
diff --git a/services/core/java/com/android/server/location/LocationRequestStatistics.java b/services/core/java/com/android/server/location/LocationRequestStatistics.java
new file mode 100644
index 0000000..264026e
--- /dev/null
+++ b/services/core/java/com/android/server/location/LocationRequestStatistics.java
@@ -0,0 +1,205 @@
+package com.android.server.location;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * Holds statistics for location requests (active requests by provider).
+ *
+ * <p>Must be externally synchronized.
+ */
+public class LocationRequestStatistics {
+ private static final String TAG = "LocationStats";
+
+ // Maps package name and provider to location request statistics.
+ public final HashMap<PackageProviderKey, PackageStatistics> statistics
+ = new HashMap<PackageProviderKey, PackageStatistics>();
+
+ /**
+ * Signals that a package has started requesting locations.
+ *
+ * @param packageName Name of package that has requested locations.
+ * @param providerName Name of provider that is requested (e.g. "gps").
+ * @param intervalMs The interval that is requested in ms.
+ */
+ public void startRequesting(String packageName, String providerName, long intervalMs) {
+ PackageProviderKey key = new PackageProviderKey(packageName, providerName);
+ PackageStatistics stats = statistics.get(key);
+ if (stats == null) {
+ stats = new PackageStatistics();
+ statistics.put(key, stats);
+ }
+ stats.startRequesting(intervalMs);
+ }
+
+ /**
+ * Signals that a package has stopped requesting locations.
+ *
+ * @param packageName Name of package that has stopped requesting locations.
+ * @param providerName Provider that is no longer being requested.
+ */
+ public void stopRequesting(String packageName, String providerName) {
+ PackageProviderKey key = new PackageProviderKey(packageName, providerName);
+ PackageStatistics stats = statistics.get(key);
+ if (stats != null) {
+ stats.stopRequesting();
+ } else {
+ // This shouldn't be a possible code path.
+ Log.e(TAG, "Couldn't find package statistics when removing location request.");
+ }
+ }
+
+ /**
+ * A key that holds both package and provider names.
+ */
+ public static class PackageProviderKey {
+ /**
+ * Name of package requesting location.
+ */
+ public final String packageName;
+ /**
+ * Name of provider being requested (e.g. "gps").
+ */
+ public final String providerName;
+
+ public PackageProviderKey(String packageName, String providerName) {
+ this.packageName = packageName;
+ this.providerName = providerName;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof PackageProviderKey)) {
+ return false;
+ }
+
+ PackageProviderKey otherKey = (PackageProviderKey) other;
+ return packageName.equals(otherKey.packageName)
+ && providerName.equals(otherKey.providerName);
+ }
+
+ @Override
+ public int hashCode() {
+ return packageName.hashCode() + 31 * providerName.hashCode();
+ }
+ }
+
+ /**
+ * Usage statistics for a package/provider pair.
+ */
+ public static class PackageStatistics {
+ // Time when this package first requested location.
+ private final long mInitialElapsedTimeMs;
+ // Number of active location requests this package currently has.
+ private int mNumActiveRequests;
+ // Time when this package most recently went from not requesting location to requesting.
+ private long mLastActivitationElapsedTimeMs;
+ // The fastest interval this package has ever requested.
+ private long mFastestIntervalMs;
+ // The slowest interval this package has ever requested.
+ private long mSlowestIntervalMs;
+ // The total time this app has requested location (not including currently running requests).
+ private long mTotalDurationMs;
+
+ private PackageStatistics() {
+ mInitialElapsedTimeMs = SystemClock.elapsedRealtime();
+ mNumActiveRequests = 0;
+ mTotalDurationMs = 0;
+ mFastestIntervalMs = Long.MAX_VALUE;
+ mSlowestIntervalMs = 0;
+ }
+
+ private void startRequesting(long intervalMs) {
+ if (mNumActiveRequests == 0) {
+ mLastActivitationElapsedTimeMs = SystemClock.elapsedRealtime();
+ }
+
+ if (intervalMs < mFastestIntervalMs) {
+ mFastestIntervalMs = intervalMs;
+ }
+
+ if (intervalMs > mSlowestIntervalMs) {
+ mSlowestIntervalMs = intervalMs;
+ }
+
+ mNumActiveRequests++;
+ }
+
+ private void stopRequesting() {
+ if (mNumActiveRequests <= 0) {
+ // Shouldn't be a possible code path
+ Log.e(TAG, "Reference counting corrupted in usage statistics.");
+ return;
+ }
+
+ mNumActiveRequests--;
+ if (mNumActiveRequests == 0) {
+ long lastDurationMs
+ = SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs;
+ mTotalDurationMs += lastDurationMs;
+ }
+ }
+
+ /**
+ * Returns the duration that this request has been active.
+ */
+ public long getDurationMs() {
+ long currentDurationMs = mTotalDurationMs;
+ if (mNumActiveRequests > 0) {
+ currentDurationMs
+ += SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs;
+ }
+ return currentDurationMs;
+ }
+
+ /**
+ * Returns the time since the initial request in ms.
+ */
+ public long getTimeSinceFirstRequestMs() {
+ return SystemClock.elapsedRealtime() - mInitialElapsedTimeMs;
+ }
+
+ /**
+ * Returns the fastest interval that has been tracked.
+ */
+ public long getFastestIntervalMs() {
+ return mFastestIntervalMs;
+ }
+
+ /**
+ * Returns the slowest interval that has been tracked.
+ */
+ public long getSlowestIntervalMs() {
+ return mSlowestIntervalMs;
+ }
+
+ /**
+ * Returns true if a request is active for these tracked statistics.
+ */
+ public boolean isActive() {
+ return mNumActiveRequests > 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder();
+ if (mFastestIntervalMs == mSlowestIntervalMs) {
+ s.append("Interval ").append(mFastestIntervalMs / 1000).append(" seconds");
+ } else {
+ s.append("Min interval ").append(mFastestIntervalMs / 1000).append(" seconds");
+ s.append(": Max interval ").append(mSlowestIntervalMs / 1000).append(" seconds");
+ }
+ s.append(": Duration requested ")
+ .append((getDurationMs() / 1000) / 60)
+ .append(" out of the last ")
+ .append((getTimeSinceFirstRequestMs() / 1000) / 60)
+ .append(" minutes");
+ if (isActive()) {
+ s.append(": Currently active");
+ }
+ return s.toString();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index 36c43ff..8578761 100644
--- a/services/core/java/com/android/server/location/MockProvider.java
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -16,7 +16,6 @@
package com.android.server.location;
-import android.location.Criteria;
import android.location.ILocationManager;
import android.location.Location;
import android.location.LocationProvider;
diff --git a/services/core/java/com/android/server/media/MediaRouteProviderProxy.java b/services/core/java/com/android/server/media/MediaRouteProviderProxy.java
new file mode 100644
index 0000000..1c5cacd
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaRouteProviderProxy.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.media;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.routeprovider.IRouteConnection;
+import android.media.routeprovider.IRouteProvider;
+import android.media.routeprovider.IRouteProviderCallback;
+import android.media.routeprovider.RouteProviderService;
+import android.media.routeprovider.RouteRequest;
+import android.media.session.RouteEvent;
+import android.media.session.RouteInfo;
+import android.media.session.Session;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * System representation and interface to a MediaRouteProvider. This class is
+ * not thread safe so all calls should be made on the main thread.
+ */
+public class MediaRouteProviderProxy {
+ private static final String TAG = "MRPProxy";
+ private static final boolean DEBUG = true;
+
+ private static final int MAX_RETRIES = 3;
+
+ private final Object mLock = new Object();
+ private final Context mContext;
+ private final String mId;
+ private final ComponentName mComponentName;
+ private final int mUserId;
+ // Interfaces declared in the manifest
+ private final ArrayList<String> mInterfaces = new ArrayList<String>();
+ private final ArrayList<RouteConnectionRecord> mConnections
+ = new ArrayList<RouteConnectionRecord>();
+ // The sessions that have a route from this provider selected
+ private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
+ private final Handler mHandler = new Handler();
+
+ private Intent mBindIntent;
+ private IRouteProvider mBinder;
+ private boolean mRunning;
+ private boolean mPaused;
+ private boolean mInterested;
+ private boolean mBound;
+ private int mRetryCount;
+
+ private RoutesListener mRouteListener;
+
+ public MediaRouteProviderProxy(Context context, String id, ComponentName component, int uid,
+ ArrayList<String> interfaces) {
+ mContext = context;
+ mId = id;
+ mComponentName = component;
+ mUserId = uid;
+ if (interfaces != null) {
+ mInterfaces.addAll(interfaces);
+ }
+ mBindIntent = new Intent(RouteProviderService.SERVICE_INTERFACE);
+ mBindIntent.setComponent(mComponentName);
+ }
+
+ public void destroy() {
+ stop();
+ mSessions.clear();
+ updateBinding();
+ }
+
+ /**
+ * Send any cleanup messages and unbind from the media route provider
+ */
+ public void stop() {
+ if (mRunning) {
+ mRunning = false;
+ mRetryCount = 0;
+ updateBinding();
+ }
+ }
+
+ /**
+ * Bind to the media route provider and perform any setup needed
+ */
+ public void start() {
+ if (!mRunning) {
+ mRunning = true;
+ updateBinding();
+ }
+ }
+
+ /**
+ * Set whether or not this provider is currently interesting to the system.
+ * In the future this may take a list of interfaces instead.
+ *
+ * @param interested True if we want to connect to this provider
+ */
+ public void setInterested(boolean interested) {
+ mInterested = interested;
+ updateBinding();
+ }
+
+ /**
+ * Set a listener to get route updates on.
+ *
+ * @param listener The listener to receive updates on.
+ */
+ public void setRoutesListener(RoutesListener listener) {
+ mRouteListener = listener;
+ }
+
+ /**
+ * Send a request to the Provider to get all the routes that the session can
+ * use.
+ *
+ * @param record The session to get routes for.
+ * @param requestId An id to identify this request.
+ */
+ public void getRoutes(MediaSessionRecord record, final int requestId) {
+ // TODO change routes to have a system global id and maintain a mapping
+ // to the original route
+ if (mBinder == null) {
+ Log.wtf(TAG, "Attempted to call getRoutes without a binder connection");
+ return;
+ }
+ List<RouteRequest> requests = record.getRouteRequests();
+ final String sessionId = record.getSessionInfo().getId();
+ try {
+ mBinder.getAvailableRoutes(requests, new ResultReceiver(mHandler) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode != RouteProviderService.RESULT_SUCCESS) {
+ // ignore failures, just means no routes were generated
+ return;
+ }
+ ArrayList<RouteInfo> routes
+ = resultData.getParcelableArrayList(RouteProviderService.KEY_ROUTES);
+ ArrayList<RouteInfo> sysRoutes = new ArrayList<RouteInfo>();
+ for (int i = 0; i < routes.size(); i++) {
+ RouteInfo route = routes.get(i);
+ RouteInfo.Builder bob = new RouteInfo.Builder(route);
+ bob.setProviderId(mId);
+ sysRoutes.add(bob.build());
+ }
+ if (mRouteListener != null) {
+ mRouteListener.onRoutesUpdated(sessionId, sysRoutes, requestId);
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ Log.d(TAG, "Error in getRoutes", e);
+ }
+ }
+
+ /**
+ * Try connecting again if we've been disconnected.
+ */
+ public void rebindIfDisconnected() {
+ if (mBinder == null && shouldBind()) {
+ unbind();
+ bind();
+ }
+ }
+
+ /**
+ * Send a request to connect to a route.
+ *
+ * @param session The session that is trying to connect.
+ * @param route The route it is connecting to.
+ * @param request The request with the connection parameters.
+ * @return true if the request was sent, false otherwise.
+ */
+ public boolean connectToRoute(MediaSessionRecord session, final RouteInfo route,
+ final RouteRequest request) {
+ final String sessionId = session.getSessionInfo().getId();
+ try {
+ mBinder.connect(route, request, new ResultReceiver(mHandler) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode != RouteProviderService.RESULT_SUCCESS) {
+ // TODO handle connection failure
+ return;
+ }
+ IBinder binder = resultData.getBinder(RouteProviderService.KEY_CONNECTION);
+ IRouteConnection connection = null;
+ if (binder != null) {
+ connection = IRouteConnection.Stub.asInterface(binder);
+ }
+
+ if (connection != null) {
+ RouteConnectionRecord record = new RouteConnectionRecord(
+ connection, mComponentName.getPackageName(), mUserId);
+ mConnections.add(record);
+ if (mRouteListener != null) {
+ mRouteListener.onRouteConnected(sessionId, route, request, record);
+ }
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error connecting to route.", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Check if this is the provider you're looking for.
+ */
+ public boolean hasComponentName(String packageName, String className) {
+ return mComponentName.getPackageName().equals(packageName)
+ && mComponentName.getClassName().equals(className);
+ }
+
+ /**
+ * Get the unique id for this provider.
+ *
+ * @return The provider's id.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ public void addSession(MediaSessionRecord session) {
+ mSessions.add(session);
+ }
+
+ public void removeSession(MediaSessionRecord session) {
+ mSessions.remove(session);
+ updateBinding();
+ }
+
+ public int getSessionCount() {
+ return mSessions.size();
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + mId + " " + this);
+ String indent = prefix + " ";
+
+ pw.println(indent + "component=" + mComponentName.toString());
+ pw.println(indent + "user id=" + mUserId);
+ pw.println(indent + "interfaces=" + mInterfaces.toString());
+ pw.println(indent + "connections=" + mConnections.toString());
+ pw.println(indent + "running=" + mRunning);
+ pw.println(indent + "interested=" + mInterested);
+ pw.println(indent + "bound=" + mBound);
+ }
+
+ private void updateBinding() {
+ if (shouldBind()) {
+ bind();
+ } else {
+ unbind();
+ }
+ }
+
+ // We want to bind as long as we're interested in this provider or there are
+ // sessions connected to it.
+ private boolean shouldBind() {
+ return (mRunning && mInterested) || (!mSessions.isEmpty());
+ }
+
+ private void bind() {
+ if (!mBound) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Binding");
+ }
+
+ try {
+ mBound = mContext.bindServiceAsUser(mBindIntent, mServiceConn,
+ Context.BIND_AUTO_CREATE, new UserHandle(mUserId));
+ if (!mBound && DEBUG) {
+ Slog.d(TAG, this + ": Bind failed");
+ }
+ } catch (SecurityException ex) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Bind failed", ex);
+ }
+ }
+ }
+ }
+
+ private void unbind() {
+ if (mBound) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Unbinding");
+ }
+
+ mBound = false;
+ mContext.unbindService(mServiceConn);
+ }
+ }
+
+ private RouteConnectionRecord getConnectionLocked(IBinder binder) {
+ for (int i = mConnections.size() - 1; i >= 0; i--) {
+ RouteConnectionRecord record = mConnections.get(i);
+ if (record.isConnection(binder)) {
+ return record;
+ }
+ }
+ return null;
+ }
+
+ private ServiceConnection mServiceConn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mBinder = IRouteProvider.Stub.asInterface(service);
+ if (DEBUG) {
+ Slog.d(TAG, "Connected to route provider");
+ }
+ try {
+ mBinder.registerCallback(mCbStub);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error registering callback on route provider. Retry count: "
+ + mRetryCount, e);
+ if (mRetryCount < MAX_RETRIES) {
+ mRetryCount++;
+ rebindIfDisconnected();
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mBinder = null;
+ if (DEBUG) {
+ Slog.d(TAG, "Disconnected from route provider");
+ }
+ }
+
+ };
+
+ private IRouteProviderCallback.Stub mCbStub = new IRouteProviderCallback.Stub() {
+ @Override
+ public void onConnectionStateChanged(IRouteConnection connection, int state)
+ throws RemoteException {
+ // TODO
+ }
+
+ @Override
+ public void onRouteEvent(RouteEvent event) throws RemoteException {
+ synchronized (mLock) {
+ RouteConnectionRecord record = getConnectionLocked(event.getConnection());
+ Log.d(TAG, "Received route event for record " + record);
+ if (record != null) {
+ record.sendEvent(event);
+ }
+ }
+ }
+
+ @Override
+ public void onConnectionTerminated(IRouteConnection connection) throws RemoteException {
+ synchronized (mLock) {
+ RouteConnectionRecord record = getConnectionLocked(connection.asBinder());
+ if (record != null) {
+ record.disconnect();
+ mConnections.remove(record);
+ }
+ }
+ }
+
+ @Override
+ public void onRoutesChanged() throws RemoteException {
+ // TODO
+ }
+ };
+
+ /**
+ * Listener for receiving responses to route requests on the provider.
+ */
+ public interface RoutesListener {
+ /**
+ * Called when routes have been returned from a request to getRoutes.
+ *
+ * @param record The session that the routes were requested for.
+ * @param routes The matching routes returned by the provider.
+ * @param reqId The request id this is responding to.
+ */
+ public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
+ int reqId);
+
+ /**
+ * Called when a route has successfully connected.
+ *
+ * @param session The session that was connected.
+ * @param route The route it connected to.
+ * @param options The options that were used for the connection.
+ * @param connection The connection instance that was created.
+ */
+ public void onRouteConnected(String sessionId, RouteInfo route,
+ RouteRequest options, RouteConnectionRecord connection);
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaRouteProviderWatcher.java b/services/core/java/com/android/server/media/MediaRouteProviderWatcher.java
new file mode 100644
index 0000000..734eab9
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaRouteProviderWatcher.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.media;
+
+import android.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.media.routeprovider.RouteProviderService;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.UUID;
+
+/**
+ * Watches for media route provider services to be installed. Adds a provider to
+ * the media session service for each registered service. For now just run all
+ * providers. In the future define a policy for when to run providers.
+ */
+public class MediaRouteProviderWatcher {
+ private static final String TAG = "MRPWatcher";
+ private static final boolean DEBUG = true; // Log.isLoggable(TAG,
+ // Log.DEBUG);
+
+ private final Context mContext;
+ private final Callback mCallback;
+ private final Handler mHandler;
+ private final int mUserId;
+ private final PackageManager mPackageManager;
+
+ private final ArrayList<MediaRouteProviderProxy> mProviders =
+ new ArrayList<MediaRouteProviderProxy>();
+ private boolean mRunning;
+
+ public MediaRouteProviderWatcher(Context context, Callback callback, Handler handler,
+ int userId) {
+ mContext = context;
+ mCallback = callback;
+ mHandler = handler;
+ mUserId = userId;
+ mPackageManager = context.getPackageManager();
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + " mUserId=" + mUserId);
+ pw.println(prefix + " mRunning=" + mRunning);
+ pw.println(prefix + " mProviders.size()=" + mProviders.size());
+ }
+
+ public void start() {
+ if (!mRunning) {
+ mRunning = true;
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ filter.addDataScheme("package");
+ mContext.registerReceiverAsUser(mScanPackagesReceiver,
+ new UserHandle(mUserId), filter, null, mHandler);
+
+ // Scan packages.
+ // Also has the side-effect of restarting providers if needed.
+ mHandler.post(mScanPackagesRunnable);
+ }
+ }
+
+ // Stop discovering providers and routes. Providers that still have an
+ // active session connected to them will not unbind.
+ public void stop() {
+ if (mRunning) {
+ mRunning = false;
+
+ mContext.unregisterReceiver(mScanPackagesReceiver);
+ mHandler.removeCallbacks(mScanPackagesRunnable);
+
+ // Stop all inactive providers.
+ for (int i = mProviders.size() - 1; i >= 0; i--) {
+ mProviders.get(i).stop();
+ }
+ }
+ }
+
+ // Clean up the providers forcibly unbinding if necessary
+ public void destroy() {
+ for (int i = mProviders.size() - 1; i >= 0; i--) {
+ mProviders.get(i).destroy();
+ mProviders.remove(i);
+ }
+ }
+
+ public ArrayList<MediaRouteProviderProxy> getProviders() {
+ return mProviders;
+ }
+
+ public MediaRouteProviderProxy getProvider(String id) {
+ int providerIndex = findProvider(id);
+ if (providerIndex != -1) {
+ return mProviders.get(providerIndex);
+ }
+ return null;
+ }
+
+ private void scanPackages() {
+ if (!mRunning) {
+ return;
+ }
+
+ // Add providers for all new services.
+ // Reorder the list so that providers left at the end will be the ones
+ // to remove.
+ int targetIndex = 0;
+ Intent intent = new Intent(RouteProviderService.SERVICE_INTERFACE);
+ for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser(
+ intent, 0, mUserId)) {
+ ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ if (DEBUG) {
+ Slog.d(TAG, "Checking service " + (serviceInfo == null ? null : serviceInfo.name));
+ }
+ if (serviceInfo != null && verifyServiceTrusted(serviceInfo)) {
+ int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
+ if (sourceIndex < 0) {
+ // TODO get declared interfaces from manifest
+ if (DEBUG) {
+ Slog.d(TAG, "Creating new provider proxy for service");
+ }
+ MediaRouteProviderProxy provider =
+ new MediaRouteProviderProxy(mContext, UUID.randomUUID().toString(),
+ new ComponentName(serviceInfo.packageName, serviceInfo.name),
+ mUserId, null);
+ provider.start();
+ mProviders.add(targetIndex++, provider);
+ mCallback.addProvider(provider);
+ } else if (sourceIndex >= targetIndex) {
+ MediaRouteProviderProxy provider = mProviders.get(sourceIndex);
+ provider.start(); // restart the provider if needed
+ provider.rebindIfDisconnected();
+ Collections.swap(mProviders, sourceIndex, targetIndex++);
+ }
+ }
+ }
+
+ // Remove providers for missing services.
+ if (targetIndex < mProviders.size()) {
+ for (int i = mProviders.size() - 1; i >= targetIndex; i--) {
+ MediaRouteProviderProxy provider = mProviders.get(i);
+ mCallback.removeProvider(provider);
+ mProviders.remove(provider);
+ provider.stop();
+ }
+ }
+ }
+
+ private boolean verifyServiceTrusted(ServiceInfo serviceInfo) {
+ if (serviceInfo.permission == null || !serviceInfo.permission.equals(
+ Manifest.permission.BIND_ROUTE_PROVIDER)) {
+ // If the service does not require this permission then any app
+ // could potentially bind to it and mess with their routes. So we
+ // only want to trust providers that require the
+ // correct permissions.
+ Slog.w(TAG, "Ignoring route provider service because it did not "
+ + "require the BIND_ROUTE_PROVIDER permission in its manifest: "
+ + serviceInfo.packageName + "/" + serviceInfo.name);
+ return false;
+ }
+ // Looks good.
+ return true;
+ }
+
+ private int findProvider(String id) {
+ int count = mProviders.size();
+ for (int i = 0; i < count; i++) {
+ MediaRouteProviderProxy provider = mProviders.get(i);
+ if (TextUtils.equals(id, provider.getId())) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private int findProvider(String packageName, String className) {
+ int count = mProviders.size();
+ for (int i = 0; i < count; i++) {
+ MediaRouteProviderProxy provider = mProviders.get(i);
+ if (provider.hasComponentName(packageName, className)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) {
+ Slog.d(TAG, "Received package manager broadcast: " + intent);
+ }
+ scanPackages();
+ }
+ };
+
+ private final Runnable mScanPackagesRunnable = new Runnable() {
+ @Override
+ public void run() {
+ scanPackages();
+ }
+ };
+
+ public interface Callback {
+ void addProvider(MediaRouteProviderProxy provider);
+
+ void removeProvider(MediaRouteProviderProxy provider);
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
new file mode 100644
index 0000000..9677577
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -0,0 +1,933 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.routeprovider.RouteRequest;
+import android.media.session.ISessionController;
+import android.media.session.ISessionControllerCallback;
+import android.media.session.ISession;
+import android.media.session.ISessionCallback;
+import android.media.session.SessionController;
+import android.media.session.MediaMetadata;
+import android.media.session.RouteCommand;
+import android.media.session.RouteInfo;
+import android.media.session.RouteOptions;
+import android.media.session.RouteEvent;
+import android.media.session.Session;
+import android.media.session.SessionInfo;
+import android.media.session.RouteInterface;
+import android.media.session.PlaybackState;
+import android.media.Rating;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.view.KeyEvent;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * This is the system implementation of a Session. Apps will interact with the
+ * MediaSession wrapper class instead.
+ */
+public class MediaSessionRecord implements IBinder.DeathRecipient {
+ private static final String TAG = "MediaSessionRecord";
+
+ /**
+ * These are the playback states that count as currently active.
+ */
+ private static final int[] ACTIVE_STATES = {
+ PlaybackState.PLAYSTATE_FAST_FORWARDING,
+ PlaybackState.PLAYSTATE_REWINDING,
+ PlaybackState.PLAYSTATE_SKIPPING_BACKWARDS,
+ PlaybackState.PLAYSTATE_SKIPPING_FORWARDS,
+ PlaybackState.PLAYSTATE_BUFFERING,
+ PlaybackState.PLAYSTATE_CONNECTING,
+ PlaybackState.PLAYSTATE_PLAYING };
+
+ /**
+ * The length of time a session will still be considered active after
+ * pausing in ms.
+ */
+ private static final int ACTIVE_BUFFER = 30000;
+
+ private final MessageHandler mHandler;
+
+ private final int mOwnerPid;
+ private final int mOwnerUid;
+ private final int mUserId;
+ private final SessionInfo mSessionInfo;
+ private final String mTag;
+ private final ControllerStub mController;
+ private final SessionStub mSession;
+ private final SessionCb mSessionCb;
+ private final MediaSessionService mService;
+
+ private final Object mLock = new Object();
+ private final ArrayList<ISessionControllerCallback> mControllerCallbacks =
+ new ArrayList<ISessionControllerCallback>();
+ private final ArrayList<RouteRequest> mRequests = new ArrayList<RouteRequest>();
+
+ private RouteInfo mRoute;
+ private RouteOptions mRequest;
+ private RouteConnectionRecord mConnection;
+ // TODO define a RouteState class with relevant info
+ private int mRouteState;
+ private long mFlags;
+
+ // TransportPerformer fields
+
+ private MediaMetadata mMetadata;
+ private PlaybackState mPlaybackState;
+ private int mRatingType;
+ private long mLastActiveTime;
+ // End TransportPerformer fields
+
+ private boolean mIsActive = false;
+ private boolean mDestroyed = false;
+
+ public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
+ ISessionCallback cb, String tag, MediaSessionService service, Handler handler) {
+ mOwnerPid = ownerPid;
+ mOwnerUid = ownerUid;
+ mUserId = userId;
+ mSessionInfo = new SessionInfo(UUID.randomUUID().toString(), ownerPackageName);
+ mTag = tag;
+ mController = new ControllerStub();
+ mSession = new SessionStub();
+ mSessionCb = new SessionCb(cb);
+ mService = service;
+ mHandler = new MessageHandler(handler.getLooper());
+ }
+
+ /**
+ * Get the binder for the {@link Session}.
+ *
+ * @return The session binder apps talk to.
+ */
+ public ISession getSessionBinder() {
+ return mSession;
+ }
+
+ /**
+ * Get the binder for the {@link SessionController}.
+ *
+ * @return The controller binder apps talk to.
+ */
+ public ISessionController getControllerBinder() {
+ return mController;
+ }
+
+ /**
+ * Get the set of route requests this session is interested in.
+ *
+ * @return The list of RouteRequests
+ */
+ public List<RouteRequest> getRouteRequests() {
+ return mRequests;
+ }
+
+ /**
+ * Get the route this session is currently on.
+ *
+ * @return The route the session is on.
+ */
+ public RouteInfo getRoute() {
+ return mRoute;
+ }
+
+ /**
+ * Get the info for this session.
+ *
+ * @return Info that identifies this session.
+ */
+ public SessionInfo getSessionInfo() {
+ return mSessionInfo;
+ }
+
+ /**
+ * Get this session's flags.
+ *
+ * @return The flags for this session.
+ */
+ public long getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Check if this session has the specified flag.
+ *
+ * @param flag The flag to check.
+ * @return True if this session has that flag set, false otherwise.
+ */
+ public boolean hasFlag(int flag) {
+ return (mFlags & flag) != 0;
+ }
+
+ /**
+ * Get the user id this session was created for.
+ *
+ * @return The user id for this session.
+ */
+ public int getUserId() {
+ return mUserId;
+ }
+
+ /**
+ * Check if this session has system priorty and should receive media buttons
+ * before any other sessions.
+ *
+ * @return True if this is a system priority session, false otherwise
+ */
+ public boolean isSystemPriority() {
+ return (mFlags & Session.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0;
+ }
+
+ /**
+ * Set the selected route. This does not connect to the route, just notifies
+ * the app that a new route has been selected.
+ *
+ * @param route The route that was selected.
+ */
+ public void selectRoute(RouteInfo route) {
+ synchronized (mLock) {
+ if (route != mRoute) {
+ disconnect(Session.DISCONNECT_REASON_ROUTE_CHANGED);
+ }
+ mRoute = route;
+ }
+ mSessionCb.sendRouteChange(route);
+ }
+
+ /**
+ * Update the state of the route this session is using and notify the
+ * session.
+ *
+ * @param state The new state of the route.
+ */
+ public void setRouteState(int state) {
+ mSessionCb.sendRouteStateChange(state);
+ }
+
+ /**
+ * Send an event to this session from the route it is using.
+ *
+ * @param event The event to send.
+ */
+ public void sendRouteEvent(RouteEvent event) {
+ mSessionCb.sendRouteEvent(event);
+ }
+
+ /**
+ * Set the connection to use for the selected route and notify the app it is
+ * now connected.
+ *
+ * @param route The route the connection is to.
+ * @param request The request that was used to connect.
+ * @param connection The connection to the route.
+ * @return True if this connection is still valid, false if it is stale.
+ */
+ public boolean setRouteConnected(RouteInfo route, RouteOptions request,
+ RouteConnectionRecord connection) {
+ synchronized (mLock) {
+ if (mDestroyed) {
+ Log.i(TAG, "setRouteConnected: session has been destroyed");
+ connection.disconnect();
+ return false;
+ }
+ if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
+ Log.w(TAG, "setRouteConnected: connected route is stale");
+ connection.disconnect();
+ return false;
+ }
+ if (request != mRequest) {
+ Log.w(TAG, "setRouteConnected: connection request is stale");
+ connection.disconnect();
+ return false;
+ }
+ mConnection = connection;
+ mConnection.setListener(mConnectionListener);
+ mSessionCb.sendRouteConnected();
+ }
+ return true;
+ }
+
+ /**
+ * Check if this session has been set to active by the app.
+ *
+ * @return True if the session is active, false otherwise.
+ */
+ public boolean isActive() {
+ return mIsActive && !mDestroyed;
+ }
+
+ /**
+ * Check if the session is currently performing playback. This will also
+ * return true if the session was recently paused.
+ *
+ * @return True if the session is performing playback, false otherwise.
+ */
+ public boolean isPlaybackActive() {
+ int state = mPlaybackState == null ? 0 : mPlaybackState.getState();
+ if (isActiveState(state)) {
+ return true;
+ }
+ if (state == mPlaybackState.PLAYSTATE_PAUSED) {
+ long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime;
+ if (inactiveTime < ACTIVE_BUFFER) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return True if this session is currently connected to a route.
+ */
+ public boolean isConnected() {
+ return mConnection != null;
+ }
+
+ public void disconnect(int reason) {
+ synchronized (mLock) {
+ if (!mDestroyed) {
+ disconnectLocked(reason);
+ }
+ }
+ }
+
+ private void disconnectLocked(int reason) {
+ if (mConnection != null) {
+ mConnection.setListener(null);
+ mConnection.disconnect();
+ mConnection = null;
+ pushDisconnected(reason);
+ }
+ }
+
+ public boolean isTransportControlEnabled() {
+ return hasFlag(Session.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ }
+
+ @Override
+ public void binderDied() {
+ mService.sessionDied(this);
+ }
+
+ /**
+ * Finish cleaning up this session, including disconnecting if connected and
+ * removing the death observer from the callback binder.
+ */
+ public void onDestroy() {
+ synchronized (mLock) {
+ if (mDestroyed) {
+ return;
+ }
+ if (isConnected()) {
+ disconnectLocked(Session.DISCONNECT_REASON_SESSION_DESTROYED);
+ }
+ mRoute = null;
+ mRequest = null;
+ mDestroyed = true;
+ }
+ }
+
+ public ISessionCallback getCallback() {
+ return mSessionCb.mCb;
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + mTag + " " + this);
+
+ final String indent = prefix + " ";
+ pw.println(indent + "ownerPid=" + mOwnerPid + ", ownerUid=" + mOwnerUid
+ + ", userId=" + mUserId);
+ pw.println(indent + "info=" + mSessionInfo.toString());
+ pw.println(indent + "active=" + mIsActive);
+ pw.println(indent + "flags=" + mFlags);
+ pw.println(indent + "rating type=" + mRatingType);
+ pw.println(indent + "controllers: " + mControllerCallbacks.size());
+ pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString()));
+ pw.println(indent + "metadata:" + getShortMetadataString());
+ pw.println(indent + "route requests {");
+ int size = mRequests.size();
+ for (int i = 0; i < size; i++) {
+ pw.println(indent + " " + mRequests.get(i).toString());
+ }
+ pw.println(indent + "}");
+ pw.println(indent + "route=" + (mRoute == null ? null : mRoute.toString()));
+ pw.println(indent + "connection=" + (mConnection == null ? null : mConnection.toString()));
+ pw.println(indent + "params=" + (mRequest == null ? null : mRequest.toString()));
+ }
+
+ private boolean isActiveState(int state) {
+ for (int i = 0; i < ACTIVE_STATES.length; i++) {
+ if (ACTIVE_STATES[i] == state) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private String getShortMetadataString() {
+ int fields = mMetadata == null ? 0 : mMetadata.size();
+ String title = mMetadata == null ? null : mMetadata
+ .getString(MediaMetadata.METADATA_KEY_TITLE);
+ return "size=" + fields + ", title=" + title;
+ }
+
+ private void pushDisconnected(int reason) {
+ synchronized (mLock) {
+ mSessionCb.sendRouteDisconnected(reason);
+ }
+ }
+
+ private void pushPlaybackStateUpdate() {
+ synchronized (mLock) {
+ if (mDestroyed) {
+ return;
+ }
+ for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
+ ISessionControllerCallback cb = mControllerCallbacks.get(i);
+ try {
+ cb.onPlaybackStateChanged(mPlaybackState);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Removing dead callback in pushPlaybackStateUpdate.", e);
+ mControllerCallbacks.remove(i);
+ }
+ }
+ }
+ }
+
+ private void pushMetadataUpdate() {
+ synchronized (mLock) {
+ if (mDestroyed) {
+ return;
+ }
+ for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
+ ISessionControllerCallback cb = mControllerCallbacks.get(i);
+ try {
+ cb.onMetadataChanged(mMetadata);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Removing dead callback in pushMetadataUpdate.", e);
+ mControllerCallbacks.remove(i);
+ }
+ }
+ }
+ }
+
+ private void pushRouteUpdate() {
+ synchronized (mLock) {
+ if (mDestroyed) {
+ return;
+ }
+ for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
+ ISessionControllerCallback cb = mControllerCallbacks.get(i);
+ try {
+ cb.onRouteChanged(mRoute);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Removing dead callback in pushRouteUpdate.", e);
+ mControllerCallbacks.remove(i);
+ }
+ }
+ }
+ }
+
+ private void pushEvent(String event, Bundle data) {
+ synchronized (mLock) {
+ if (mDestroyed) {
+ return;
+ }
+ for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
+ ISessionControllerCallback cb = mControllerCallbacks.get(i);
+ try {
+ cb.onEvent(event, data);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error with callback in pushEvent.", e);
+ }
+ }
+ }
+ }
+
+ private void pushRouteCommand(RouteCommand command, ResultReceiver cb) {
+ synchronized (mLock) {
+ if (mDestroyed) {
+ return;
+ }
+ if (mRoute == null || !TextUtils.equals(command.getRouteInfo(), mRoute.getId())) {
+ if (cb != null) {
+ cb.send(RouteInterface.RESULT_ROUTE_IS_STALE, null);
+ return;
+ }
+ }
+ if (mConnection != null) {
+ mConnection.sendCommand(command, cb);
+ } else if (cb != null) {
+ cb.send(RouteInterface.RESULT_NOT_CONNECTED, null);
+ }
+ }
+ }
+
+ private PlaybackState getStateWithUpdatedPosition() {
+ PlaybackState state = mPlaybackState;
+ long duration = -1;
+ if (mMetadata != null && mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
+ duration = mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
+ }
+ PlaybackState result = null;
+ if (state != null) {
+ if (state.getState() == PlaybackState.PLAYSTATE_PLAYING
+ || state.getState() == PlaybackState.PLAYSTATE_FAST_FORWARDING
+ || state.getState() == PlaybackState.PLAYSTATE_REWINDING) {
+ long updateTime = state.getLastPositionUpdateTime();
+ if (updateTime > 0) {
+ long position = (long) (state.getRate()
+ * (SystemClock.elapsedRealtime() - updateTime)) + state.getPosition();
+ if (duration >= 0 && position > duration) {
+ position = duration;
+ } else if (position < 0) {
+ position = 0;
+ }
+ result = new PlaybackState(state);
+ result.setState(state.getState(), position, state.getRate());
+ }
+ }
+ }
+ return result == null ? state : result;
+ }
+
+ private final RouteConnectionRecord.Listener mConnectionListener
+ = new RouteConnectionRecord.Listener() {
+ @Override
+ public void onEvent(RouteEvent event) {
+ RouteEvent eventForSession = new RouteEvent(null, event.getIface(),
+ event.getEvent(), event.getExtras());
+ mSessionCb.sendRouteEvent(eventForSession);
+ }
+
+ @Override
+ public void disconnect() {
+ MediaSessionRecord.this.disconnect(Session.DISCONNECT_REASON_PROVIDER_DISCONNECTED);
+ }
+ };
+
+ private final class SessionStub extends ISession.Stub {
+ @Override
+ public void destroy() {
+ mService.destroySession(MediaSessionRecord.this);
+ }
+
+ @Override
+ public void sendEvent(String event, Bundle data) {
+ mHandler.post(MessageHandler.MSG_SEND_EVENT, event, data);
+ }
+
+ @Override
+ public ISessionController getController() {
+ return mController;
+ }
+
+ @Override
+ public void setActive(boolean active) {
+ mIsActive = active;
+ mService.updateSession(MediaSessionRecord.this);
+ mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
+ }
+
+ @Override
+ public void setFlags(int flags) {
+ if ((flags & Session.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
+ int pid = getCallingPid();
+ int uid = getCallingUid();
+ mService.enforcePhoneStatePermission(pid, uid);
+ }
+ mFlags = flags;
+ mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
+ }
+
+ @Override
+ public void setMetadata(MediaMetadata metadata) {
+ mMetadata = metadata;
+ mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
+ }
+
+ @Override
+ public void setPlaybackState(PlaybackState state) {
+ int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState();
+ int newState = state == null ? 0 : state.getState();
+ if (isActiveState(oldState) && newState == PlaybackState.PLAYSTATE_PAUSED) {
+ mLastActiveTime = SystemClock.elapsedRealtime();
+ }
+ mPlaybackState = state;
+ mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState);
+ mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE);
+ }
+
+ @Override
+ public void setRatingType(int type) {
+ mRatingType = type;
+ }
+
+ @Override
+ public void sendRouteCommand(RouteCommand command, ResultReceiver cb) {
+ mHandler.post(MessageHandler.MSG_SEND_COMMAND,
+ new Pair<RouteCommand, ResultReceiver>(command, cb));
+ }
+
+ @Override
+ public boolean setRoute(RouteInfo route) throws RemoteException {
+ // TODO decide if allowed to set route and if the route exists
+ return false;
+ }
+
+ @Override
+ public void connectToRoute(RouteInfo route, RouteOptions request)
+ throws RemoteException {
+ if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
+ throw new RemoteException("RouteInfo does not match current route");
+ }
+ mService.connectToRoute(MediaSessionRecord.this, route, request);
+ mRequest = request;
+ }
+
+ @Override
+ public void disconnectFromRoute(RouteInfo route) {
+ if (route != null && mRoute != null
+ && TextUtils.equals(route.getId(), mRoute.getId())) {
+ disconnect(Session.DISCONNECT_REASON_SESSION_DISCONNECTED);
+ }
+ }
+
+ @Override
+ public void setRouteOptions(List<RouteOptions> options) throws RemoteException {
+ mRequests.clear();
+ for (int i = options.size() - 1; i >= 0; i--) {
+ RouteRequest request = new RouteRequest(mSessionInfo, options.get(i),
+ false);
+ mRequests.add(request);
+ }
+ }
+ }
+
+ class SessionCb {
+ private final ISessionCallback mCb;
+
+ public SessionCb(ISessionCallback cb) {
+ mCb = cb;
+ }
+
+ public void sendMediaButton(KeyEvent keyEvent) {
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+ try {
+ mCb.onMediaButton(mediaButtonIntent);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
+ }
+ }
+
+ public void sendCommand(String command, Bundle extras, ResultReceiver cb) {
+ try {
+ mCb.onCommand(command, extras, cb);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in sendCommand.", e);
+ }
+ }
+
+ public void sendRouteChange(RouteInfo route) {
+ try {
+ mCb.onRequestRouteChange(route);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in sendRouteChange.", e);
+ }
+ }
+
+ public void sendRouteStateChange(int state) {
+ try {
+ mCb.onRouteStateChange(state);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
+ }
+ }
+
+ public void sendRouteEvent(RouteEvent event) {
+ try {
+ mCb.onRouteEvent(event);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in sendRouteEvent.", e);
+ }
+ }
+
+ public void sendRouteConnected() {
+ try {
+ mCb.onRouteConnected(mRoute, mRequest);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
+ }
+ }
+
+ public void sendRouteDisconnected(int reason) {
+ try {
+ mCb.onRouteDisconnected(mRoute, reason);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in sendRouteDisconnected");
+ }
+ }
+
+ public void play() {
+ try {
+ mCb.onPlay();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in play.", e);
+ }
+ }
+
+ public void pause() {
+ try {
+ mCb.onPause();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in pause.", e);
+ }
+ }
+
+ public void stop() {
+ try {
+ mCb.onStop();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in stop.", e);
+ }
+ }
+
+ public void next() {
+ try {
+ mCb.onNext();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in next.", e);
+ }
+ }
+
+ public void previous() {
+ try {
+ mCb.onPrevious();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in previous.", e);
+ }
+ }
+
+ public void fastForward() {
+ try {
+ mCb.onFastForward();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in fastForward.", e);
+ }
+ }
+
+ public void rewind() {
+ try {
+ mCb.onRewind();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in rewind.", e);
+ }
+ }
+
+ public void seekTo(long pos) {
+ try {
+ mCb.onSeekTo(pos);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in seekTo.", e);
+ }
+ }
+
+ public void rate(Rating rating) {
+ try {
+ mCb.onRate(rating);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in rate.", e);
+ }
+ }
+ }
+
+ class ControllerStub extends ISessionController.Stub {
+ @Override
+ public void sendCommand(String command, Bundle extras, ResultReceiver cb)
+ throws RemoteException {
+ mSessionCb.sendCommand(command, extras, cb);
+ }
+
+ @Override
+ public void sendMediaButton(KeyEvent mediaButtonIntent) {
+ mSessionCb.sendMediaButton(mediaButtonIntent);
+ }
+
+ @Override
+ public void registerCallbackListener(ISessionControllerCallback cb) {
+ synchronized (mLock) {
+ if (!mControllerCallbacks.contains(cb)) {
+ mControllerCallbacks.add(cb);
+ }
+ }
+ }
+
+ @Override
+ public void unregisterCallbackListener(ISessionControllerCallback cb)
+ throws RemoteException {
+ synchronized (mLock) {
+ mControllerCallbacks.remove(cb);
+ }
+ }
+
+ @Override
+ public void play() throws RemoteException {
+ mSessionCb.play();
+ }
+
+ @Override
+ public void pause() throws RemoteException {
+ mSessionCb.pause();
+ }
+
+ @Override
+ public void stop() throws RemoteException {
+ mSessionCb.stop();
+ }
+
+ @Override
+ public void next() throws RemoteException {
+ mSessionCb.next();
+ }
+
+ @Override
+ public void previous() throws RemoteException {
+ mSessionCb.previous();
+ }
+
+ @Override
+ public void fastForward() throws RemoteException {
+ mSessionCb.fastForward();
+ }
+
+ @Override
+ public void rewind() throws RemoteException {
+ mSessionCb.rewind();
+ }
+
+ @Override
+ public void seekTo(long pos) throws RemoteException {
+ mSessionCb.seekTo(pos);
+ }
+
+ @Override
+ public void rate(Rating rating) throws RemoteException {
+ mSessionCb.rate(rating);
+ }
+
+
+ @Override
+ public MediaMetadata getMetadata() {
+ return mMetadata;
+ }
+
+ @Override
+ public PlaybackState getPlaybackState() {
+ return getStateWithUpdatedPosition();
+ }
+
+ @Override
+ public int getRatingType() {
+ return mRatingType;
+ }
+
+ @Override
+ public boolean isTransportControlEnabled() {
+ return MediaSessionRecord.this.isTransportControlEnabled();
+ }
+
+ @Override
+ public void showRoutePicker() {
+ mService.showRoutePickerForSession(MediaSessionRecord.this);
+ }
+ }
+
+ private class MessageHandler extends Handler {
+ private static final int MSG_UPDATE_METADATA = 1;
+ private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
+ private static final int MSG_UPDATE_ROUTE = 3;
+ private static final int MSG_SEND_EVENT = 4;
+ private static final int MSG_UPDATE_ROUTE_FILTERS = 5;
+ private static final int MSG_SEND_COMMAND = 6;
+ private static final int MSG_UPDATE_SESSION_STATE = 7;
+
+ public MessageHandler(Looper looper) {
+ super(looper);
+ }
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE_METADATA:
+ pushMetadataUpdate();
+ break;
+ case MSG_UPDATE_PLAYBACK_STATE:
+ pushPlaybackStateUpdate();
+ break;
+ case MSG_UPDATE_ROUTE:
+ pushRouteUpdate();
+ break;
+ case MSG_SEND_EVENT:
+ pushEvent((String) msg.obj, msg.getData());
+ break;
+ case MSG_SEND_COMMAND:
+ Pair<RouteCommand, ResultReceiver> cmd =
+ (Pair<RouteCommand, ResultReceiver>) msg.obj;
+ pushRouteCommand(cmd.first, cmd.second);
+ break;
+ case MSG_UPDATE_SESSION_STATE:
+ // TODO add session state
+ break;
+ }
+ }
+
+ public void post(int what) {
+ post(what, null);
+ }
+
+ public void post(int what, Object obj) {
+ obtainMessage(what, obj).sendToTarget();
+ }
+
+ public void post(int what, Object obj, Bundle data) {
+ Message msg = obtainMessage(what, obj);
+ msg.setData(data);
+ msg.sendToTarget();
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
new file mode 100644
index 0000000..78f3b5f
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -0,0 +1,683 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.routeprovider.RouteRequest;
+import android.media.session.ISession;
+import android.media.session.ISessionCallback;
+import android.media.session.ISessionManager;
+import android.media.session.RouteInfo;
+import android.media.session.RouteOptions;
+import android.media.session.Session;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.server.SystemService;
+import com.android.server.Watchdog;
+import com.android.server.Watchdog.Monitor;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * System implementation of MediaSessionManager
+ */
+public class MediaSessionService extends SystemService implements Monitor {
+ private static final String TAG = "MediaSessionService";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final SessionManagerImpl mSessionManagerImpl;
+ // private final MediaRouteProviderWatcher mRouteProviderWatcher;
+ private final MediaSessionStack mPriorityStack;
+
+ private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
+ private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
+ // private final ArrayList<MediaRouteProviderProxy> mProviders
+ // = new ArrayList<MediaRouteProviderProxy>();
+ private final Object mLock = new Object();
+ private final Handler mHandler = new Handler();
+
+ private MediaSessionRecord mPrioritySession;
+ private int mCurrentUserId = -1;
+
+ // Used to keep track of the current request to show routes for a specific
+ // session so we drop late callbacks properly.
+ private int mShowRoutesRequestId = 0;
+
+ // TODO refactor to have per user state for providers. See
+ // MediaRouterService for an example
+
+ public MediaSessionService(Context context) {
+ super(context);
+ mSessionManagerImpl = new SessionManagerImpl();
+ mPriorityStack = new MediaSessionStack();
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
+ Watchdog.getInstance().addMonitor(this);
+ updateUser();
+ }
+
+ /**
+ * Should trigger showing the Media route picker dialog. Right now it just
+ * kicks off a query to all the providers to get routes.
+ *
+ * @param record The session to show the picker for.
+ */
+ public void showRoutePickerForSession(MediaSessionRecord record) {
+ // TODO for now just toggle the route to test (we will only have one
+ // match for now)
+ synchronized (mLock) {
+ if (!mAllSessions.contains(record)) {
+ Log.d(TAG, "Unknown session tried to show route picker. Ignoring.");
+ return;
+ }
+ RouteInfo current = record.getRoute();
+ UserRecord user = mUserRecords.get(record.getUserId());
+ if (current != null) {
+ // For now send null to mean the local route
+ MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider());
+ if (proxy != null) {
+ proxy.removeSession(record);
+ }
+ record.selectRoute(null);
+ return;
+ }
+ ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked();
+ mShowRoutesRequestId++;
+ for (int i = providers.size() - 1; i >= 0; i--) {
+ MediaRouteProviderProxy provider = providers.get(i);
+ provider.getRoutes(record, mShowRoutesRequestId);
+ }
+ }
+ }
+
+ /**
+ * Connect a session to the given route.
+ *
+ * @param session The session to connect.
+ * @param route The route to connect to.
+ * @param options The options to use for the connection.
+ */
+ public void connectToRoute(MediaSessionRecord session, RouteInfo route,
+ RouteOptions options) {
+ synchronized (mLock) {
+ if (!mAllSessions.contains(session)) {
+ Log.d(TAG, "Unknown session attempting to connect to route. Ignoring");
+ return;
+ }
+ UserRecord user = mUserRecords.get(session.getUserId());
+ if (user == null) {
+ Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist.");
+ return;
+ }
+ MediaRouteProviderProxy proxy = user.getProviderLocked(route.getProvider());
+ if (proxy == null) {
+ Log.w(TAG, "Provider for route " + route.getName() + " does not exist.");
+ return;
+ }
+ RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true);
+ proxy.connectToRoute(session, route, request);
+ }
+ }
+
+ public void updateSession(MediaSessionRecord record) {
+ synchronized (mLock) {
+ if (!mAllSessions.contains(record)) {
+ Log.d(TAG, "Unknown session updated. Ignoring.");
+ return;
+ }
+ mPriorityStack.onSessionStateChange(record);
+ if (record.isSystemPriority()) {
+ if (record.isActive()) {
+ if (mPrioritySession != null) {
+ Log.w(TAG, "Replacing existing priority session with a new session");
+ }
+ mPrioritySession = record;
+ } else {
+ if (mPrioritySession == record) {
+ mPrioritySession = null;
+ }
+ }
+ }
+ }
+ }
+
+ public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
+ synchronized (mLock) {
+ if (!mAllSessions.contains(record)) {
+ Log.d(TAG, "Unknown session changed playback state. Ignoring.");
+ return;
+ }
+ mPriorityStack.onPlaystateChange(record, oldState, newState);
+ }
+ }
+
+ @Override
+ public void onStartUser(int userHandle) {
+ updateUser();
+ }
+
+ @Override
+ public void onSwitchUser(int userHandle) {
+ updateUser();
+ }
+
+ @Override
+ public void onStopUser(int userHandle) {
+ synchronized (mLock) {
+ UserRecord user = mUserRecords.get(userHandle);
+ if (user != null) {
+ destroyUserLocked(user);
+ }
+ }
+ }
+
+ @Override
+ public void monitor() {
+ synchronized (mLock) {
+ // Check for deadlock
+ }
+ }
+
+ protected void enforcePhoneStatePermission(int pid, int uid) {
+ if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
+ }
+ }
+
+ void sessionDied(MediaSessionRecord session) {
+ synchronized (mLock) {
+ destroySessionLocked(session);
+ }
+ }
+
+ void destroySession(MediaSessionRecord session) {
+ synchronized (mLock) {
+ destroySessionLocked(session);
+ }
+ }
+
+ private void updateUser() {
+ synchronized (mLock) {
+ int userId = ActivityManager.getCurrentUser();
+ if (mCurrentUserId != userId) {
+ final int oldUserId = mCurrentUserId;
+ mCurrentUserId = userId; // do this first
+
+ UserRecord oldUser = mUserRecords.get(oldUserId);
+ if (oldUser != null) {
+ oldUser.stopLocked();
+ }
+
+ UserRecord newUser = getOrCreateUser(userId);
+ newUser.startLocked();
+ }
+ }
+ }
+
+ /**
+ * Stop the user and unbind from everything.
+ *
+ * @param user The user to dispose of
+ */
+ private void destroyUserLocked(UserRecord user) {
+ user.stopLocked();
+ user.destroyLocked();
+ mUserRecords.remove(user.mUserId);
+ }
+
+ /*
+ * When a session is removed several things need to happen.
+ * 1. We need to remove it from the relevant user.
+ * 2. We need to remove it from the priority stack.
+ * 3. We need to remove it from all sessions.
+ * 4. If this is the system priority session we need to clear it.
+ * 5. We need to unlink to death from the cb binder
+ * 6. We need to tell the session to do any final cleanup (onDestroy)
+ */
+ private void destroySessionLocked(MediaSessionRecord session) {
+ int userId = session.getUserId();
+ UserRecord user = mUserRecords.get(userId);
+ if (user != null) {
+ user.removeSessionLocked(session);
+ }
+
+ mPriorityStack.removeSession(session);
+ mAllSessions.remove(session);
+ if (session == mPrioritySession) {
+ mPrioritySession = null;
+ }
+
+ try {
+ session.getCallback().asBinder().unlinkToDeath(session, 0);
+ } catch (Exception e) {
+ // ignore exceptions while destroying a session.
+ }
+ session.onDestroy();
+ }
+
+ private void enforcePackageName(String packageName, int uid) {
+ if (TextUtils.isEmpty(packageName)) {
+ throw new IllegalArgumentException("packageName may not be empty");
+ }
+ String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
+ final int packageCount = packages.length;
+ for (int i = 0; i < packageCount; i++) {
+ if (packageName.equals(packages[i])) {
+ return;
+ }
+ }
+ throw new IllegalArgumentException("packageName is not owned by the calling process");
+ }
+
+ /**
+ * Checks a caller's authorization to register an IRemoteControlDisplay.
+ * Authorization is granted if one of the following is true:
+ * <ul>
+ * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
+ * permission</li>
+ * <li>the caller's listener is one of the enabled notification listeners
+ * for the caller's user</li>
+ * </ul>
+ */
+ private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
+ int resolvedUserId) {
+ if (getContext()
+ .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
+ != PackageManager.PERMISSION_GRANTED
+ && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
+ resolvedUserId)) {
+ throw new SecurityException("Missing permission to control media.");
+ }
+ }
+
+ /**
+ * This checks if the component is an enabled notification listener for the
+ * specified user. Enabled components may only operate on behalf of the user
+ * they're running as.
+ *
+ * @param compName The component that is enabled.
+ * @param userId The user id of the caller.
+ * @param forUserId The user id they're making the request on behalf of.
+ * @return True if the component is enabled, false otherwise
+ */
+ private boolean isEnabledNotificationListener(ComponentName compName, int userId,
+ int forUserId) {
+ if (userId != forUserId) {
+ // You may not access another user's content as an enabled listener.
+ return false;
+ }
+ if (compName != null) {
+ final String enabledNotifListeners = Settings.Secure.getStringForUser(
+ getContext().getContentResolver(),
+ Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+ userId);
+ if (enabledNotifListeners != null) {
+ final String[] components = enabledNotifListeners.split(":");
+ for (int i = 0; i < components.length; i++) {
+ final ComponentName component =
+ ComponentName.unflattenFromString(components[i]);
+ if (component != null) {
+ if (compName.equals(component)) {
+ if (DEBUG) {
+ Log.d(TAG, "ok to get sessions: " + component +
+ " is authorized notification listener");
+ }
+ return true;
+ }
+ }
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "not ok to get sessions, " + compName +
+ " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
+ }
+ }
+ return false;
+ }
+
+ private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
+ String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
+ synchronized (mLock) {
+ return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
+ }
+ }
+
+ /*
+ * When a session is created the following things need to happen.
+ * 1. It's callback binder needs a link to death
+ * 2. It needs to be added to all sessions.
+ * 3. It needs to be added to the priority stack.
+ * 4. It needs to be added to the relevant user record.
+ */
+ private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
+ String callerPackageName, ISessionCallback cb, String tag) {
+
+ final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
+ callerPackageName, cb, tag, this, mHandler);
+ try {
+ cb.asBinder().linkToDeath(session, 0);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Media Session owner died prematurely.", e);
+ }
+
+ mAllSessions.add(session);
+ mPriorityStack.addSession(session);
+
+ UserRecord user = getOrCreateUser(userId);
+ user.addSessionLocked(session);
+
+ if (DEBUG) {
+ Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
+ }
+ return session;
+ }
+
+ private UserRecord getOrCreateUser(int userId) {
+ UserRecord user = mUserRecords.get(userId);
+ if (user == null) {
+ user = new UserRecord(getContext(), userId);
+ mUserRecords.put(userId, user);
+ }
+ return user;
+ }
+
+ private int findIndexOfSessionForIdLocked(String sessionId) {
+ for (int i = mAllSessions.size() - 1; i >= 0; i--) {
+ MediaSessionRecord session = mAllSessions.get(i);
+ if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private boolean isSessionDiscoverable(MediaSessionRecord record) {
+ // TODO probably want to check more than if it's active.
+ return record.isActive();
+ }
+
+ private MediaRouteProviderProxy.RoutesListener mRoutesCallback
+ = new MediaRouteProviderProxy.RoutesListener() {
+ @Override
+ public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
+ int reqId) {
+ // TODO for now select the first route to test, eventually add the
+ // new routes to the dialog if it is still open
+ synchronized (mLock) {
+ int index = findIndexOfSessionForIdLocked(sessionId);
+ if (index != -1 && routes != null && routes.size() > 0) {
+ MediaSessionRecord record = mAllSessions.get(index);
+ RouteInfo route = routes.get(0);
+ record.selectRoute(route);
+ UserRecord user = mUserRecords.get(record.getUserId());
+ MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
+ provider.addSession(record);
+ }
+ }
+ }
+
+ @Override
+ public void onRouteConnected(String sessionId, RouteInfo route,
+ RouteRequest options, RouteConnectionRecord connection) {
+ synchronized (mLock) {
+ int index = findIndexOfSessionForIdLocked(sessionId);
+ if (index != -1) {
+ MediaSessionRecord session = mAllSessions.get(index);
+ session.setRouteConnected(route, options.getConnectionOptions(), connection);
+ }
+ }
+ }
+ };
+
+ /**
+ * Information about a particular user. The contents of this object is
+ * guarded by mLock.
+ */
+ final class UserRecord {
+ private final int mUserId;
+ private final MediaRouteProviderWatcher mRouteProviderWatcher;
+ private final ArrayList<MediaRouteProviderProxy> mProviders
+ = new ArrayList<MediaRouteProviderProxy>();
+ private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
+
+ public UserRecord(Context context, int userId) {
+ mUserId = userId;
+ mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
+ mProviderWatcherCallback, mHandler, userId);
+ }
+
+ public void startLocked() {
+ mRouteProviderWatcher.start();
+ }
+
+ public void stopLocked() {
+ mRouteProviderWatcher.stop();
+ updateInterestLocked();
+ }
+
+ public void destroyLocked() {
+ for (int i = mSessions.size() - 1; i >= 0; i--) {
+ MediaSessionRecord session = mSessions.get(i);
+ MediaSessionService.this.destroySessionLocked(session);
+ if (session.isConnected()) {
+ session.disconnect(Session.DISCONNECT_REASON_USER_STOPPING);
+ }
+ }
+ }
+
+ public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
+ return mProviders;
+ }
+
+ public ArrayList<MediaSessionRecord> getSessionsLocked() {
+ return mSessions;
+ }
+
+ public void addSessionLocked(MediaSessionRecord session) {
+ mSessions.add(session);
+ updateInterestLocked();
+ }
+
+ public void removeSessionLocked(MediaSessionRecord session) {
+ mSessions.remove(session);
+ RouteInfo route = session.getRoute();
+ if (route != null) {
+ MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
+ if (provider != null) {
+ provider.removeSession(session);
+ }
+ }
+ updateInterestLocked();
+ }
+
+ public void dumpLocked(PrintWriter pw, String prefix) {
+ pw.println(prefix + "Record for user " + mUserId);
+ String indent = prefix + " ";
+ int size = mProviders.size();
+ pw.println(indent + size + " Providers:");
+ for (int i = 0; i < size; i++) {
+ mProviders.get(i).dump(pw, indent);
+ }
+ pw.println();
+ size = mSessions.size();
+ pw.println(indent + size + " Sessions:");
+ for (int i = 0; i < size; i++) {
+ // Just print the session info, the full session dump will
+ // already be in the list of all sessions.
+ pw.println(indent + mSessions.get(i).getSessionInfo());
+ }
+ }
+
+ public void updateInterestLocked() {
+ // TODO go through the sessions and build up the set of interfaces
+ // we're interested in. Update the provider watcher.
+ // For now, just express interest in all providers for the current
+ // user
+ boolean interested = mUserId == mCurrentUserId;
+ for (int i = mProviders.size() - 1; i >= 0; i--) {
+ mProviders.get(i).setInterested(interested);
+ }
+ }
+
+ private MediaRouteProviderProxy getProviderLocked(String providerId) {
+ for (int i = mProviders.size() - 1; i >= 0; i--) {
+ MediaRouteProviderProxy provider = mProviders.get(i);
+ if (TextUtils.equals(providerId, provider.getId())) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
+ = new MediaRouteProviderWatcher.Callback() {
+ @Override
+ public void removeProvider(MediaRouteProviderProxy provider) {
+ synchronized (mLock) {
+ mProviders.remove(provider);
+ provider.setRoutesListener(null);
+ provider.setInterested(false);
+ }
+ }
+
+ @Override
+ public void addProvider(MediaRouteProviderProxy provider) {
+ synchronized (mLock) {
+ mProviders.add(provider);
+ provider.setRoutesListener(mRoutesCallback);
+ provider.setInterested(true);
+ }
+ }
+ };
+ }
+
+ class SessionManagerImpl extends ISessionManager.Stub {
+ // TODO add createSessionAsUser, pass user-id to
+ // ActivityManagerNative.handleIncomingUser and stash result for use
+ // when starting services on that session's behalf.
+ @Override
+ public ISession createSession(String packageName, ISessionCallback cb, String tag,
+ int userId) throws RemoteException {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ enforcePackageName(packageName, uid);
+ int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+ false /* allowAll */, true /* requireFull */, "createSession", packageName);
+ if (cb == null) {
+ throw new IllegalArgumentException("Controller callback cannot be null");
+ }
+ return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
+ .getSessionBinder();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public List<IBinder> getSessions(ComponentName componentName, int userId) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+
+ try {
+ String packageName = null;
+ if (componentName != null) {
+ // If they gave us a component name verify they own the
+ // package
+ packageName = componentName.getPackageName();
+ enforcePackageName(packageName, uid);
+ }
+ // Check that they can make calls on behalf of the user and
+ // get the final user id
+ int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+ true /* allowAll */, true /* requireFull */, "getSessions", packageName);
+ // Check if they have the permissions or their component is
+ // enabled for the user they're calling from.
+ enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
+ ArrayList<IBinder> binders = new ArrayList<IBinder>();
+ synchronized (mLock) {
+ ArrayList<MediaSessionRecord> records = mPriorityStack
+ .getActiveSessions(resolvedUserId);
+ int size = records.size();
+ for (int i = 0; i < size; i++) {
+ binders.add(records.get(i).getControllerBinder().asBinder());
+ }
+ }
+ return binders;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+ if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump MediaSessionService from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
+ pw.println();
+
+ synchronized (mLock) {
+ pw.println("Session for calls:" + mPrioritySession);
+ if (mPrioritySession != null) {
+ mPrioritySession.dump(pw, "");
+ }
+ int count = mAllSessions.size();
+ pw.println(count + " Sessions:");
+ for (int i = 0; i < count; i++) {
+ mAllSessions.get(i).dump(pw, "");
+ pw.println();
+ }
+ mPriorityStack.dump(pw, "");
+
+ pw.println("User Records:");
+ count = mUserRecords.size();
+ for (int i = 0; i < count; i++) {
+ UserRecord user = mUserRecords.get(i);
+ user.dumpLocked(pw, "");
+ }
+ }
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
new file mode 100644
index 0000000..f89b14a
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.media.session.PlaybackState;
+import android.media.session.Session;
+import android.os.UserHandle;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Keeps track of media sessions and their priority for notifications, media
+ * button routing, etc.
+ */
+public class MediaSessionStack {
+ /**
+ * These are states that usually indicate the user took an action and should
+ * bump priority regardless of the old state.
+ */
+ private static final int[] ALWAYS_PRIORITY_STATES = {
+ PlaybackState.PLAYSTATE_FAST_FORWARDING,
+ PlaybackState.PLAYSTATE_REWINDING,
+ PlaybackState.PLAYSTATE_SKIPPING_BACKWARDS,
+ PlaybackState.PLAYSTATE_SKIPPING_FORWARDS };
+ /**
+ * These are states that usually indicate the user took an action if they
+ * were entered from a non-priority state.
+ */
+ private static final int[] TRANSITION_PRIORITY_STATES = {
+ PlaybackState.PLAYSTATE_BUFFERING,
+ PlaybackState.PLAYSTATE_CONNECTING,
+ PlaybackState.PLAYSTATE_PLAYING };
+
+ private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
+
+ private MediaSessionRecord mGlobalPrioritySession;
+
+ private MediaSessionRecord mCachedButtonReceiver;
+ private MediaSessionRecord mCachedDefault;
+ private ArrayList<MediaSessionRecord> mCachedActiveList;
+ private ArrayList<MediaSessionRecord> mCachedTransportControlList;
+
+ /**
+ * Add a record to the priority tracker.
+ *
+ * @param record The record to add.
+ */
+ public void addSession(MediaSessionRecord record) {
+ mSessions.add(record);
+ if ((record.getFlags() & Session.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
+ mGlobalPrioritySession = record;
+ }
+ clearCache();
+ }
+
+ /**
+ * Remove a record from the priority tracker.
+ *
+ * @param record The record to remove.
+ */
+ public void removeSession(MediaSessionRecord record) {
+ mSessions.remove(record);
+ if (record == mGlobalPrioritySession) {
+ mGlobalPrioritySession = null;
+ }
+ clearCache();
+ }
+
+ /**
+ * Notify the priority tracker that a session's state changed.
+ *
+ * @param record The record that changed.
+ * @param oldState Its old playback state.
+ * @param newState Its new playback state.
+ */
+ public void onPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
+ if (shouldUpdatePriority(oldState, newState)) {
+ mSessions.remove(record);
+ mSessions.add(0, record);
+ clearCache();
+ }
+ }
+
+ /**
+ * Handle any stack changes that need to occur in response to a session
+ * state change. TODO add the old and new session state as params
+ *
+ * @param record The record that changed.
+ */
+ public void onSessionStateChange(MediaSessionRecord record) {
+ // For now just clear the cache. Eventually we'll selectively clear
+ // depending on what changed.
+ clearCache();
+ }
+
+ /**
+ * Get the current priority sorted list of active sessions. The most
+ * important session is at index 0 and the least important at size - 1.
+ *
+ * @param userId The user to check.
+ * @return All the active sessions in priority order.
+ */
+ public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
+ if (mCachedActiveList == null) {
+ mCachedActiveList = getPriorityListLocked(true, 0, userId);
+ }
+ return mCachedActiveList;
+ }
+
+ /**
+ * Get the current priority sorted list of active sessions that use
+ * transport controls. The most important session is at index 0 and the
+ * least important at size -1.
+ *
+ * @param userId The user to check.
+ * @return All the active sessions that handle transport controls in
+ * priority order.
+ */
+ public ArrayList<MediaSessionRecord> getTransportControlSessions(int userId) {
+ if (mCachedTransportControlList == null) {
+ mCachedTransportControlList = getPriorityListLocked(true,
+ Session.FLAG_HANDLES_TRANSPORT_CONTROLS, userId);
+ }
+ return mCachedTransportControlList;
+ }
+
+ /**
+ * Get the highest priority active session.
+ *
+ * @param userId The user to check.
+ * @return The current highest priority session or null.
+ */
+ public MediaSessionRecord getDefaultSession(int userId) {
+ if (mCachedDefault != null) {
+ return mCachedDefault;
+ }
+ ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
+ if (records.size() > 0) {
+ return records.get(0);
+ }
+ return null;
+ }
+
+ /**
+ * Get the highest priority session that can handle media buttons.
+ *
+ * @param userId The user to check.
+ * @return The default media button session or null.
+ */
+ public MediaSessionRecord getDefaultMediaButtonSession(int userId) {
+ if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) {
+ return mGlobalPrioritySession;
+ }
+ if (mCachedButtonReceiver != null) {
+ return mCachedButtonReceiver;
+ }
+ ArrayList<MediaSessionRecord> records = getPriorityListLocked(true,
+ Session.FLAG_HANDLES_MEDIA_BUTTONS, userId);
+ if (records.size() > 0) {
+ mCachedButtonReceiver = records.get(0);
+ }
+ return mCachedButtonReceiver;
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0,
+ UserHandle.USER_ALL);
+ int count = sortedSessions.size();
+ pw.println(prefix + "Sessions Stack - have " + count + " sessions:");
+ String indent = prefix + " ";
+ for (int i = 0; i < count; i++) {
+ MediaSessionRecord record = sortedSessions.get(i);
+ record.dump(pw, indent);
+ pw.println();
+ }
+ }
+
+ /**
+ * Get a priority sorted list of sessions. Can filter to only return active
+ * sessions or sessions with specific flags.
+ *
+ * @param activeOnly True to only return active sessions, false to return
+ * all sessions.
+ * @param withFlags Only return sessions with all the specified flags set. 0
+ * returns all sessions.
+ * @param userId The user to get sessions for. {@link UserHandle#USER_ALL}
+ * will return sessions for all users.
+ * @return The priority sorted list of sessions.
+ */
+ private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags,
+ int userId) {
+ ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
+ int lastLocalIndex = 0;
+ int lastActiveIndex = 0;
+ int lastPublishedIndex = 0;
+
+ int size = mSessions.size();
+ for (int i = 0; i < size; i++) {
+ final MediaSessionRecord session = mSessions.get(i);
+
+ if (userId != UserHandle.USER_ALL && userId != session.getUserId()) {
+ // Filter out sessions for the wrong user
+ continue;
+ }
+ if ((session.getFlags() & withFlags) != withFlags) {
+ // Filter out sessions with the wrong flags
+ continue;
+ }
+ if (!session.isActive()) {
+ if (!activeOnly) {
+ // If we're getting unpublished as well always put them at
+ // the end
+ result.add(session);
+ }
+ continue;
+ }
+
+ if (session.isSystemPriority()) {
+ // System priority sessions are special and always go at the
+ // front. We expect there to only be one of these at a time.
+ result.add(0, session);
+ lastLocalIndex++;
+ lastActiveIndex++;
+ lastPublishedIndex++;
+ } else if (session.isPlaybackActive()) {
+ // TODO replace getRoute() == null with real local route check
+ if(session.getRoute() == null) {
+ // Active local sessions get top priority
+ result.add(lastLocalIndex, session);
+ lastLocalIndex++;
+ lastActiveIndex++;
+ lastPublishedIndex++;
+ } else {
+ // Then active remote sessions
+ result.add(lastActiveIndex, session);
+ lastActiveIndex++;
+ lastPublishedIndex++;
+ }
+ } else {
+ // inactive sessions go at the end in order of whoever last did
+ // something.
+ result.add(lastPublishedIndex, session);
+ lastPublishedIndex++;
+ }
+ }
+
+ return result;
+ }
+
+ private boolean shouldUpdatePriority(int oldState, int newState) {
+ if (containsState(newState, ALWAYS_PRIORITY_STATES)) {
+ return true;
+ }
+ if (!containsState(oldState, TRANSITION_PRIORITY_STATES)
+ && containsState(newState, TRANSITION_PRIORITY_STATES)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean containsState(int state, int[] states) {
+ for (int i = 0; i < states.length; i++) {
+ if (states[i] == state) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void clearCache() {
+ mCachedDefault = null;
+ mCachedButtonReceiver = null;
+ mCachedActiveList = null;
+ mCachedTransportControlList = null;
+ }
+}
diff --git a/services/core/java/com/android/server/media/RouteConnectionRecord.java b/services/core/java/com/android/server/media/RouteConnectionRecord.java
new file mode 100644
index 0000000..90ddf29
--- /dev/null
+++ b/services/core/java/com/android/server/media/RouteConnectionRecord.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.media;
+
+import android.media.routeprovider.IRouteConnection;
+import android.media.session.RouteCommand;
+import android.media.session.RouteEvent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+/**
+ * A connection between a Session and a Route.
+ */
+public class RouteConnectionRecord {
+ private static final String TAG = "RouteConnRecord";
+ private final IRouteConnection mBinder;
+ private final String mPackageName;
+ private final int mUid;
+ private Listener mListener;
+
+ public RouteConnectionRecord(IRouteConnection binder, String packageName, int uid) {
+ mBinder = binder;
+ mPackageName = packageName;
+ mUid = uid;
+ }
+
+ /**
+ * Add a listener to get route events on.
+ *
+ * @param listener The listener to get events on.
+ */
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Check if this connection matches the token given.
+ *
+ * @param binder The token to check
+ * @return True if this is the connection you're looking for, false
+ * otherwise.
+ */
+ public boolean isConnection(IBinder binder) {
+ return binder != null && binder.equals(mBinder.asBinder());
+ }
+
+ /**
+ * Send an event from this connection.
+ *
+ * @param event The event to send.
+ */
+ public void sendEvent(RouteEvent event) {
+ if (mListener != null) {
+ mListener.onEvent(event);
+ }
+ }
+
+ /**
+ * Send a command to this connection.
+ *
+ * @param command The command to send.
+ * @param cb The receiver to get a result on.
+ */
+ public void sendCommand(RouteCommand command, ResultReceiver cb) {
+ try {
+ mBinder.onCommand(command, cb);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in sendCommand", e);
+ }
+ }
+
+ /**
+ * Tell the session that the provider has disconnected it.
+ */
+ public void disconnect() {
+ if (mListener != null) {
+ mListener.disconnect();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "RouteConnection { binder=" + mBinder.toString() + ", package=" + mPackageName
+ + ", uid=" + mUid + "}";
+ }
+
+ /**
+ * Listener to receive updates from the provider for this connection.
+ */
+ public static interface Listener {
+ /**
+ * Called when an event is sent on this connection.
+ *
+ * @param event The event that was sent.
+ */
+ public void onEvent(RouteEvent event);
+
+ /**
+ * Called when the provider has disconnected the route.
+ */
+ public void disconnect();
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 855ae23..416a6b1 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -410,7 +410,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
@Override
- public void onImportanceChanged(int pid, int uid, int importance) {
+ public void onProcessStateChanged(int pid, int uid, int procState) {
}
@Override
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 5d6adc2..271e9e9 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -464,21 +464,21 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
private NetworkStatsCollection mUidTagComplete;
private NetworkStatsCollection getUidComplete() {
- if (mUidComplete == null) {
- synchronized (mStatsLock) {
+ synchronized (mStatsLock) {
+ if (mUidComplete == null) {
mUidComplete = mUidRecorder.getOrLoadCompleteLocked();
}
+ return mUidComplete;
}
- return mUidComplete;
}
private NetworkStatsCollection getUidTagComplete() {
- if (mUidTagComplete == null) {
- synchronized (mStatsLock) {
+ synchronized (mStatsLock) {
+ if (mUidTagComplete == null) {
mUidTagComplete = mUidTagRecorder.getOrLoadCompleteLocked();
}
+ return mUidTagComplete;
}
- return mUidTagComplete;
}
@Override
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
new file mode 100644
index 0000000..007032e
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -0,0 +1,546 @@
+/**
+ * Copyright (c) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.service.notification.Condition;
+import android.service.notification.ConditionProviderService;
+import android.service.notification.IConditionListener;
+import android.service.notification.IConditionProvider;
+import android.service.notification.ZenModeConfig;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+
+public class ConditionProviders extends ManagedServices {
+ private static final Condition[] NO_CONDITIONS = new Condition[0];
+
+ private final ZenModeHelper mZenModeHelper;
+ private final ArrayMap<IBinder, IConditionListener> mListeners
+ = new ArrayMap<IBinder, IConditionListener>();
+ private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
+ private final CountdownConditionHelper mCountdownHelper = new CountdownConditionHelper();
+
+ public ConditionProviders(Context context, Handler handler,
+ UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
+ super(context, handler, new Object(), userProfiles);
+ mZenModeHelper = zenModeHelper;
+ mZenModeHelper.addCallback(new ZenModeHelperCallback());
+ loadZenConfig();
+ }
+
+ @Override
+ protected Config getConfig() {
+ Config c = new Config();
+ c.caption = "condition provider";
+ c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
+ c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
+ c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
+ c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
+ c.clientLabel = R.string.condition_provider_service_binding_label;
+ return c;
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ super.dump(pw);
+ synchronized(mMutex) {
+ pw.print(" mListeners("); pw.print(mListeners.size()); pw.println("):");
+ for (int i = 0; i < mListeners.size(); i++) {
+ pw.print(" "); pw.println(mListeners.keyAt(i));
+ }
+ pw.print(" mRecords("); pw.print(mRecords.size()); pw.println("):");
+ for (int i = 0; i < mRecords.size(); i++) {
+ pw.print(" "); pw.println(mRecords.get(i));
+ }
+ }
+ }
+
+ @Override
+ protected IInterface asInterface(IBinder binder) {
+ return IConditionProvider.Stub.asInterface(binder);
+ }
+
+ @Override
+ protected void onServiceAdded(ManagedServiceInfo info) {
+ Slog.d(TAG, "onServiceAdded " + info);
+ final IConditionProvider provider = provider(info);
+ try {
+ provider.onConnected();
+ } catch (RemoteException e) {
+ // we tried
+ }
+ synchronized (mMutex) {
+ final int N = mRecords.size();
+ for(int i = 0; i < N; i++) {
+ final ConditionRecord r = mRecords.get(i);
+ if (!r.component.equals(info.component)) continue;
+ r.info = info;
+ // if automatic, auto-subscribe
+ if (r.isAutomatic) {
+ try {
+ final Uri id = r.id;
+ if (DEBUG) Slog.d(TAG, "Auto-subscribing to configured condition " + id);
+ provider.onSubscribe(id);
+ } catch (RemoteException e) {
+ // we tried
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
+ if (removed == null) return;
+ for (int i = mRecords.size() - 1; i >= 0; i--) {
+ final ConditionRecord r = mRecords.get(i);
+ if (!r.component.equals(removed.component)) continue;
+ if (r.isManual) {
+ // removing the current manual condition, exit zen
+ mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF);
+ }
+ if (r.isAutomatic) {
+ // removing an automatic condition, exit zen
+ mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF);
+ }
+ mRecords.remove(i);
+ }
+ }
+
+ public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
+ synchronized(mMutex) {
+ return checkServiceTokenLocked(provider);
+ }
+ }
+
+ public void requestZenModeConditions(IConditionListener callback, int relevance) {
+ synchronized(mMutex) {
+ if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback
+ + " relevance=" + Condition.relevanceToString(relevance));
+ if (callback == null) return;
+ relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS);
+ if (relevance != 0) {
+ mListeners.put(callback.asBinder(), callback);
+ requestConditionsLocked(relevance);
+ } else {
+ mListeners.remove(callback.asBinder());
+ if (mListeners.isEmpty()) {
+ requestConditionsLocked(0);
+ }
+ }
+ }
+ }
+
+ private Condition[] validateConditions(String pkg, Condition[] conditions) {
+ if (conditions == null || conditions.length == 0) return null;
+ final int N = conditions.length;
+ final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
+ for (int i = 0; i < N; i++) {
+ final Uri id = conditions[i].id;
+ if (!Condition.isValidId(id, pkg)) {
+ Slog.w(TAG, "Ignoring condition from " + pkg + " for invalid id: " + id);
+ continue;
+ }
+ if (valid.containsKey(id)) {
+ Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
+ continue;
+ }
+ valid.put(id, conditions[i]);
+ }
+ if (valid.size() == 0) return null;
+ if (valid.size() == N) return conditions;
+ final Condition[] rt = new Condition[valid.size()];
+ for (int i = 0; i < rt.length; i++) {
+ rt[i] = valid.valueAt(i);
+ }
+ return rt;
+ }
+
+ private ConditionRecord getRecordLocked(Uri id, ComponentName component) {
+ final int N = mRecords.size();
+ for (int i = 0; i < N; i++) {
+ final ConditionRecord r = mRecords.get(i);
+ if (r.id.equals(id) && r.component.equals(component)) {
+ return r;
+ }
+ }
+ final ConditionRecord r = new ConditionRecord(id, component);
+ mRecords.add(r);
+ return r;
+ }
+
+ public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
+ synchronized(mMutex) {
+ if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
+ + (conditions == null ? null : Arrays.asList(conditions)));
+ conditions = validateConditions(pkg, conditions);
+ if (conditions == null || conditions.length == 0) return;
+ final int N = conditions.length;
+ for (IConditionListener listener : mListeners.values()) {
+ try {
+ listener.onConditionsReceived(conditions);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error sending conditions to listener " + listener, e);
+ }
+ }
+ for (int i = 0; i < N; i++) {
+ final Condition c = conditions[i];
+ final ConditionRecord r = getRecordLocked(c.id, info.component);
+ r.info = info;
+ r.condition = c;
+ // if manual, exit zen if false (or failed)
+ if (r.isManual) {
+ if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
+ final boolean failed = c.state == Condition.STATE_ERROR;
+ if (failed) {
+ Slog.w(TAG, "Exit zen: manual condition failed: " + c);
+ } else if (DEBUG) {
+ Slog.d(TAG, "Exit zen: manual condition false: " + c);
+ }
+ mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF);
+ unsubscribeLocked(r);
+ r.isManual = false;
+ }
+ }
+ // if automatic, exit zen if false (or failed), enter zen if true
+ if (r.isAutomatic) {
+ if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
+ final boolean failed = c.state == Condition.STATE_ERROR;
+ if (failed) {
+ Slog.w(TAG, "Exit zen: automatic condition failed: " + c);
+ } else if (DEBUG) {
+ Slog.d(TAG, "Exit zen: automatic condition false: " + c);
+ }
+ mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF);
+ } else if (c.state == Condition.STATE_TRUE) {
+ Slog.d(TAG, "Enter zen: automatic condition true: " + c);
+ mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_ON);
+ }
+ }
+ }
+ }
+ }
+
+ public void setZenModeCondition(Uri conditionId) {
+ if (DEBUG) Slog.d(TAG, "setZenModeCondition " + conditionId);
+ synchronized(mMutex) {
+ final int N = mRecords.size();
+ for (int i = 0; i < N; i++) {
+ final ConditionRecord r = mRecords.get(i);
+ final boolean idEqual = r.id.equals(conditionId);
+ if (r.isManual && !idEqual) {
+ // was previous manual condition, unsubscribe
+ unsubscribeLocked(r);
+ r.isManual = false;
+ } else if (idEqual && !r.isManual) {
+ // is new manual condition, subscribe
+ subscribeLocked(r);
+ r.isManual = true;
+ }
+ }
+ }
+ mCountdownHelper.setZenModeCondition(conditionId);
+ }
+
+ private void subscribeLocked(ConditionRecord r) {
+ if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
+ final IConditionProvider provider = provider(r);
+ if (provider == null) {
+ Slog.w(TAG, "subscribeLocked: no provider");
+ return;
+ }
+ try {
+ provider.onSubscribe(r.id);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error subscribing to " + r, e);
+ }
+ }
+
+ private static <T> ArraySet<T> safeSet(T... items) {
+ final ArraySet<T> rt = new ArraySet<T>();
+ if (items == null || items.length == 0) return rt;
+ final int N = items.length;
+ for (int i = 0; i < N; i++) {
+ final T item = items[i];
+ if (item != null) {
+ rt.add(item);
+ }
+ }
+ return rt;
+ }
+
+ public void setAutomaticZenModeConditions(Uri[] conditionIds) {
+ setAutomaticZenModeConditions(conditionIds, true /*save*/);
+ }
+
+ private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) {
+ if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions "
+ + (conditionIds == null ? null : Arrays.asList(conditionIds)));
+ synchronized(mMutex) {
+ final ArraySet<Uri> newIds = safeSet(conditionIds);
+ final int N = mRecords.size();
+ boolean changed = false;
+ for (int i = 0; i < N; i++) {
+ final ConditionRecord r = mRecords.get(i);
+ final boolean automatic = newIds.contains(r.id);
+ if (!r.isAutomatic && automatic) {
+ // subscribe to new automatic
+ subscribeLocked(r);
+ r.isAutomatic = true;
+ changed = true;
+ } else if (r.isAutomatic && !automatic) {
+ // unsubscribe from old automatic
+ unsubscribeLocked(r);
+ r.isAutomatic = false;
+ changed = true;
+ }
+ }
+ if (save && changed) {
+ saveZenConfigLocked();
+ }
+ }
+ }
+
+ public Condition[] getAutomaticZenModeConditions() {
+ synchronized(mMutex) {
+ final int N = mRecords.size();
+ ArrayList<Condition> rt = null;
+ for (int i = 0; i < N; i++) {
+ final ConditionRecord r = mRecords.get(i);
+ if (r.isAutomatic && r.condition != null) {
+ if (rt == null) rt = new ArrayList<Condition>();
+ rt.add(r.condition);
+ }
+ }
+ return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]);
+ }
+ }
+
+ private void unsubscribeLocked(ConditionRecord r) {
+ if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
+ final IConditionProvider provider = provider(r);
+ if (provider == null) {
+ Slog.w(TAG, "unsubscribeLocked: no provider");
+ return;
+ }
+ try {
+ provider.onUnsubscribe(r.id);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error unsubscribing to " + r, e);
+ }
+ }
+
+ private static IConditionProvider provider(ConditionRecord r) {
+ return r == null ? null : provider(r.info);
+ }
+
+ private static IConditionProvider provider(ManagedServiceInfo info) {
+ return info == null ? null : (IConditionProvider) info.service;
+ }
+
+ private void requestConditionsLocked(int flags) {
+ for (ManagedServiceInfo info : mServices) {
+ final IConditionProvider provider = provider(info);
+ if (provider == null) continue;
+ try {
+ provider.onRequestConditions(flags);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error requesting conditions from " + info.component, e);
+ }
+ }
+ }
+
+ private void loadZenConfig() {
+ final ZenModeConfig config = mZenModeHelper.getConfig();
+ if (config == null) {
+ if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
+ return;
+ }
+ synchronized (mMutex) {
+ if (config.conditionComponents == null || config.conditionIds == null
+ || config.conditionComponents.length != config.conditionIds.length) {
+ if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions");
+ setAutomaticZenModeConditions(null, false /*save*/);
+ return;
+ }
+ final ArraySet<Uri> newIds = new ArraySet<Uri>();
+ final int N = config.conditionComponents.length;
+ for (int i = 0; i < N; i++) {
+ final ComponentName component = config.conditionComponents[i];
+ final Uri id = config.conditionIds[i];
+ if (component != null && id != null) {
+ getRecordLocked(id, component); // ensure record exists
+ newIds.add(id);
+ }
+ }
+ if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N);
+ setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/);
+ }
+ }
+
+ private void saveZenConfigLocked() {
+ ZenModeConfig config = mZenModeHelper.getConfig();
+ if (config == null) return;
+ config = config.copy();
+ final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>();
+ final int automaticN = mRecords.size();
+ for (int i = 0; i < automaticN; i++) {
+ final ConditionRecord r = mRecords.get(i);
+ if (r.isAutomatic) {
+ automatic.add(r);
+ }
+ }
+ if (automatic.isEmpty()) {
+ config.conditionComponents = null;
+ config.conditionIds = null;
+ } else {
+ final int N = automatic.size();
+ config.conditionComponents = new ComponentName[N];
+ config.conditionIds = new Uri[N];
+ for (int i = 0; i < N; i++) {
+ final ConditionRecord r = automatic.get(i);
+ config.conditionComponents[i] = r.component;
+ config.conditionIds[i] = r.id;
+ }
+ }
+ if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
+ mZenModeHelper.setConfig(config);
+ }
+
+ private final class CountdownConditionHelper extends BroadcastReceiver {
+ private static final String ACTION = "CountdownConditionHelper";
+ private static final int REQUEST_CODE = 100;
+ private static final String EXTRA_TIME = "time";
+
+ private long mCurrent;
+
+ public CountdownConditionHelper() {
+ mContext.registerReceiver(this, new IntentFilter(ACTION));
+ }
+
+ public void setZenModeCondition(Uri conditionId) {
+ final long time = tryParseCondition(conditionId);
+ final AlarmManager alarms = (AlarmManager)
+ mContext.getSystemService(Context.ALARM_SERVICE);
+ final Intent intent = new Intent(ACTION).putExtra(EXTRA_TIME, time)
+ .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE,
+ intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ alarms.cancel(pendingIntent);
+ mCurrent = time;
+ if (time > 0) {
+ final long now = System.currentTimeMillis();
+ final CharSequence span =
+ DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS);
+ Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future (%s), now=%s",
+ ACTION, ts(time), time - now, span, ts(now)));
+ alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
+ }
+ }
+
+ private String ts(long time) {
+ return new Date(time) + " (" + time + ")";
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION.equals(intent.getAction())) {
+ final long time = intent.getLongExtra(EXTRA_TIME, 0);
+ Slog.d(TAG, "Countdown condition fired. time=" + time + " mCurrent=" + mCurrent);
+ if (time > 0 && time == mCurrent) {
+ // countdown condition is still the manual condition, leave zen
+ mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF);
+ ConditionProviders.this.setZenModeCondition(null);
+ }
+ }
+ }
+
+ private long tryParseCondition(Uri conditionId) {
+ // condition://android/countdown/1399917958951
+ if (!Condition.isValidId(conditionId, "android")) return 0;
+ if (conditionId.getPathSegments().size() != 2
+ || !"countdown".equals(conditionId.getPathSegments().get(0))) return 0;
+ try {
+ return Long.parseLong(conditionId.getPathSegments().get(1));
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Error parsing countdown condition: " + conditionId, e);
+ return 0;
+ }
+ }
+ }
+
+ private class ZenModeHelperCallback extends ZenModeHelper.Callback {
+ @Override
+ void onConfigChanged() {
+ loadZenConfig();
+ }
+
+ @Override
+ void onZenModeChanged() {
+ final int mode = mZenModeHelper.getZenMode();
+ if (mode == Global.ZEN_MODE_OFF) {
+ // ensure any manual condition is cleared
+ setZenModeCondition(null);
+ }
+ }
+ }
+
+ private static class ConditionRecord {
+ public final Uri id;
+ public final ComponentName component;
+ public Condition condition;
+ public ManagedServiceInfo info;
+ public boolean isAutomatic;
+ public boolean isManual;
+
+ private ConditionRecord(Uri id, ComponentName component) {
+ this.id = id;
+ this.component = component;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
+ .append(id).append(",component=").append(component);
+ if (isAutomatic) sb.append(",automatic");
+ if (isManual) sb.append(",manual");
+ return sb.append(']').toString();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
new file mode 100644
index 0000000..584145f
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -0,0 +1,622 @@
+/**
+ * Copyright (c) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Manages the lifecycle of application-provided services bound by system server.
+ *
+ * Services managed by this helper must have:
+ * - An associated system settings value with a list of enabled component names.
+ * - A well-known action for services to use in their intent-filter.
+ * - A system permission for services to require in order to ensure system has exclusive binding.
+ * - A settings page for user configuration of enabled services, and associated intent action.
+ * - A remote interface definition (aidl) provided by the service used for communication.
+ */
+abstract public class ManagedServices {
+ protected final String TAG = getClass().getSimpleName();
+ protected static final boolean DEBUG = true;
+
+ private static final String ENABLED_SERVICES_SEPARATOR = ":";
+
+ protected final Context mContext;
+ protected final Object mMutex;
+ private final UserProfiles mUserProfiles;
+ private final SettingsObserver mSettingsObserver;
+ private final Config mConfig;
+
+ // contains connections to all connected services, including app services
+ // and system services
+ protected final ArrayList<ManagedServiceInfo> mServices = new ArrayList<ManagedServiceInfo>();
+ // things that will be put into mServices as soon as they're ready
+ private final ArrayList<String> mServicesBinding = new ArrayList<String>();
+ // lists the component names of all enabled (and therefore connected)
+ // app services for current profiles.
+ private ArraySet<ComponentName> mEnabledServicesForCurrentProfiles
+ = new ArraySet<ComponentName>();
+ // Just the packages from mEnabledServicesForCurrentProfiles
+ private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<String>();
+
+ public ManagedServices(Context context, Handler handler, Object mutex,
+ UserProfiles userProfiles) {
+ mContext = context;
+ mMutex = mutex;
+ mUserProfiles = userProfiles;
+ mConfig = getConfig();
+ mSettingsObserver = new SettingsObserver(handler);
+ }
+
+ abstract protected Config getConfig();
+
+ private String getCaption() {
+ return mConfig.caption;
+ }
+
+ abstract protected IInterface asInterface(IBinder binder);
+
+ abstract protected void onServiceAdded(ManagedServiceInfo info);
+
+ protected void onServiceRemovedLocked(ManagedServiceInfo removed) { }
+
+ private ManagedServiceInfo newServiceInfo(IInterface service,
+ ComponentName component, int userid, boolean isSystem, ServiceConnection connection,
+ int targetSdkVersion) {
+ return new ManagedServiceInfo(service, component, userid, isSystem, connection,
+ targetSdkVersion);
+ }
+
+ public void onBootPhaseAppsCanStart() {
+ mSettingsObserver.observe();
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println(" All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size()
+ + ") enabled for current profiles:");
+ for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
+ pw.println(" " + cmpt);
+ }
+
+ pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):");
+ for (ManagedServiceInfo info : mServices) {
+ pw.println(" " + info.component
+ + " (user " + info.userid + "): " + info.service
+ + (info.isSystem?" SYSTEM":""));
+ }
+ }
+
+ public void onPackagesChanged(boolean queryReplace, String[] pkgList) {
+ if (DEBUG) Slog.d(TAG, "onPackagesChanged queryReplace=" + queryReplace
+ + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList))
+ + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames);
+ boolean anyServicesInvolved = false;
+ if (pkgList != null && (pkgList.length > 0)) {
+ for (String pkgName : pkgList) {
+ if (mEnabledServicesPackageNames.contains(pkgName)) {
+ anyServicesInvolved = true;
+ }
+ }
+ }
+
+ if (anyServicesInvolved) {
+ // if we're not replacing a package, clean up orphaned bits
+ if (!queryReplace) {
+ disableNonexistentServices();
+ }
+ // make sure we're still bound to any of our services who may have just upgraded
+ rebindServices();
+ }
+ }
+
+ public ManagedServiceInfo checkServiceTokenLocked(IInterface service) {
+ checkNotNull(service);
+ final IBinder token = service.asBinder();
+ final int N = mServices.size();
+ for (int i=0; i<N; i++) {
+ final ManagedServiceInfo info = mServices.get(i);
+ if (info.service.asBinder() == token) return info;
+ }
+ throw new SecurityException("Disallowed call from unknown " + getCaption() + ": "
+ + service);
+ }
+
+ public void unregisterService(IInterface service, int userid) {
+ checkNotNull(service);
+ // no need to check permissions; if your service binder is in the list,
+ // that's proof that you had permission to add it in the first place
+ unregisterServiceImpl(service, userid);
+ }
+
+ public void registerService(IInterface service, ComponentName component, int userid) {
+ checkNotNull(service);
+ registerServiceImpl(service, component, userid);
+ }
+
+ /**
+ * Remove access for any services that no longer exist.
+ */
+ private void disableNonexistentServices() {
+ int[] userIds = mUserProfiles.getCurrentProfileIds();
+ final int N = userIds.length;
+ for (int i = 0 ; i < N; ++i) {
+ disableNonexistentServices(userIds[i]);
+ }
+ }
+
+ private void disableNonexistentServices(int userId) {
+ String flatIn = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ mConfig.secureSettingName,
+ userId);
+ if (!TextUtils.isEmpty(flatIn)) {
+ if (DEBUG) Slog.v(TAG, "flat before: " + flatIn);
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
+ new Intent(mConfig.serviceInterface),
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+ userId);
+ if (DEBUG) Slog.v(TAG, mConfig.serviceInterface + " services: " + installedServices);
+ Set<ComponentName> installed = new ArraySet<ComponentName>();
+ for (int i = 0, count = installedServices.size(); i < count; i++) {
+ ResolveInfo resolveInfo = installedServices.get(i);
+ ServiceInfo info = resolveInfo.serviceInfo;
+
+ if (!mConfig.bindPermission.equals(info.permission)) {
+ Slog.w(TAG, "Skipping " + getCaption() + " service "
+ + info.packageName + "/" + info.name
+ + ": it does not require the permission "
+ + mConfig.bindPermission);
+ continue;
+ }
+ installed.add(new ComponentName(info.packageName, info.name));
+ }
+
+ String flatOut = "";
+ if (!installed.isEmpty()) {
+ String[] enabled = flatIn.split(ENABLED_SERVICES_SEPARATOR);
+ ArrayList<String> remaining = new ArrayList<String>(enabled.length);
+ for (int i = 0; i < enabled.length; i++) {
+ ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]);
+ if (installed.contains(enabledComponent)) {
+ remaining.add(enabled[i]);
+ }
+ }
+ flatOut = TextUtils.join(ENABLED_SERVICES_SEPARATOR, remaining);
+ }
+ if (DEBUG) Slog.v(TAG, "flat after: " + flatOut);
+ if (!flatIn.equals(flatOut)) {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ mConfig.secureSettingName,
+ flatOut, userId);
+ }
+ }
+ }
+
+ /**
+ * Called whenever packages change, the user switches, or the secure setting
+ * is altered. (For example in response to USER_SWITCHED in our broadcast receiver)
+ */
+ private void rebindServices() {
+ if (DEBUG) Slog.d(TAG, "rebindServices");
+ final int[] userIds = mUserProfiles.getCurrentProfileIds();
+ final int nUserIds = userIds.length;
+
+ final SparseArray<String> flat = new SparseArray<String>();
+
+ for (int i = 0; i < nUserIds; ++i) {
+ flat.put(userIds[i], Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ mConfig.secureSettingName,
+ userIds[i]));
+ }
+
+ ManagedServiceInfo[] toRemove = new ManagedServiceInfo[mServices.size()];
+ final SparseArray<ArrayList<ComponentName>> toAdd
+ = new SparseArray<ArrayList<ComponentName>>();
+
+ synchronized (mMutex) {
+ // unbind and remove all existing services
+ toRemove = mServices.toArray(toRemove);
+
+ final ArraySet<ComponentName> newEnabled = new ArraySet<ComponentName>();
+ final ArraySet<String> newPackages = new ArraySet<String>();
+
+ for (int i = 0; i < nUserIds; ++i) {
+ final ArrayList<ComponentName> add = new ArrayList<ComponentName>();
+ toAdd.put(userIds[i], add);
+
+ // decode the list of components
+ String toDecode = flat.get(userIds[i]);
+ if (toDecode != null) {
+ String[] components = toDecode.split(ENABLED_SERVICES_SEPARATOR);
+ for (int j = 0; j < components.length; j++) {
+ final ComponentName component
+ = ComponentName.unflattenFromString(components[j]);
+ if (component != null) {
+ newEnabled.add(component);
+ add.add(component);
+ newPackages.add(component.getPackageName());
+ }
+ }
+
+ }
+ }
+ mEnabledServicesForCurrentProfiles = newEnabled;
+ mEnabledServicesPackageNames = newPackages;
+ }
+
+ for (ManagedServiceInfo info : toRemove) {
+ final ComponentName component = info.component;
+ final int oldUser = info.userid;
+ Slog.v(TAG, "disabling " + getCaption() + " for user "
+ + oldUser + ": " + component);
+ unregisterService(component, info.userid);
+ }
+
+ for (int i = 0; i < nUserIds; ++i) {
+ final ArrayList<ComponentName> add = toAdd.get(userIds[i]);
+ final int N = add.size();
+ for (int j = 0; j < N; j++) {
+ final ComponentName component = add.get(j);
+ Slog.v(TAG, "enabling " + getCaption() + " for user " + userIds[i] + ": "
+ + component);
+ registerService(component, userIds[i]);
+ }
+ }
+ }
+
+ /**
+ * Version of registerService that takes the name of a service component to bind to.
+ */
+ private void registerService(final ComponentName name, final int userid) {
+ if (DEBUG) Slog.v(TAG, "registerService: " + name + " u=" + userid);
+
+ synchronized (mMutex) {
+ final String servicesBindingTag = name.toString() + "/" + userid;
+ if (mServicesBinding.contains(servicesBindingTag)) {
+ // stop registering this thing already! we're working on it
+ return;
+ }
+ mServicesBinding.add(servicesBindingTag);
+
+ final int N = mServices.size();
+ for (int i=N-1; i>=0; i--) {
+ final ManagedServiceInfo info = mServices.get(i);
+ if (name.equals(info.component)
+ && info.userid == userid) {
+ // cut old connections
+ if (DEBUG) Slog.v(TAG, " disconnecting old " + getCaption() + ": "
+ + info.service);
+ removeServiceLocked(i);
+ if (info.connection != null) {
+ mContext.unbindService(info.connection);
+ }
+ }
+ }
+
+ Intent intent = new Intent(mConfig.serviceInterface);
+ intent.setComponent(name);
+
+ intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel);
+
+ final PendingIntent pendingIntent = PendingIntent.getActivity(
+ mContext, 0, new Intent(mConfig.settingsAction), 0);
+ intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
+
+ ApplicationInfo appInfo = null;
+ try {
+ appInfo = mContext.getPackageManager().getApplicationInfo(
+ name.getPackageName(), 0);
+ } catch (NameNotFoundException e) {
+ // Ignore if the package doesn't exist we won't be able to bind to the service.
+ }
+ final int targetSdkVersion =
+ appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;
+
+ try {
+ if (DEBUG) Slog.v(TAG, "binding: " + intent);
+ if (!mContext.bindServiceAsUser(intent,
+ new ServiceConnection() {
+ IInterface mService;
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ boolean added = false;
+ ManagedServiceInfo info = null;
+ synchronized (mMutex) {
+ mServicesBinding.remove(servicesBindingTag);
+ try {
+ mService = asInterface(binder);
+ info = newServiceInfo(mService, name,
+ userid, false /*isSystem*/, this, targetSdkVersion);
+ binder.linkToDeath(info, 0);
+ added = mServices.add(info);
+ } catch (RemoteException e) {
+ // already dead
+ }
+ }
+ if (added) {
+ onServiceAdded(info);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Slog.v(TAG, getCaption() + " connection lost: " + name);
+ }
+ },
+ Context.BIND_AUTO_CREATE,
+ new UserHandle(userid)))
+ {
+ mServicesBinding.remove(servicesBindingTag);
+ Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent);
+ return;
+ }
+ } catch (SecurityException ex) {
+ Slog.e(TAG, "Unable to bind " + getCaption() + " service: " + intent, ex);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Remove a service for the given user by ComponentName
+ */
+ private void unregisterService(ComponentName name, int userid) {
+ synchronized (mMutex) {
+ final int N = mServices.size();
+ for (int i=N-1; i>=0; i--) {
+ final ManagedServiceInfo info = mServices.get(i);
+ if (name.equals(info.component)
+ && info.userid == userid) {
+ removeServiceLocked(i);
+ if (info.connection != null) {
+ try {
+ mContext.unbindService(info.connection);
+ } catch (IllegalArgumentException ex) {
+ // something happened to the service: we think we have a connection
+ // but it's bogus.
+ Slog.e(TAG, getCaption() + " " + name + " could not be unbound: " + ex);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes a service from the list but does not unbind
+ *
+ * @return the removed service.
+ */
+ private ManagedServiceInfo removeServiceImpl(IInterface service, final int userid) {
+ if (DEBUG) Slog.d(TAG, "removeServiceImpl service=" + service + " u=" + userid);
+ ManagedServiceInfo serviceInfo = null;
+ synchronized (mMutex) {
+ final int N = mServices.size();
+ for (int i=N-1; i>=0; i--) {
+ final ManagedServiceInfo info = mServices.get(i);
+ if (info.service.asBinder() == service.asBinder()
+ && info.userid == userid) {
+ if (DEBUG) Slog.d(TAG, "Removing active service " + info.component);
+ serviceInfo = removeServiceLocked(i);
+ }
+ }
+ }
+ return serviceInfo;
+ }
+
+ private ManagedServiceInfo removeServiceLocked(int i) {
+ final ManagedServiceInfo info = mServices.remove(i);
+ onServiceRemovedLocked(info);
+ return info;
+ }
+
+ private void checkNotNull(IInterface service) {
+ if (service == null) {
+ throw new IllegalArgumentException(getCaption() + " must not be null");
+ }
+ }
+
+ private void registerServiceImpl(final IInterface service,
+ final ComponentName component, final int userid) {
+ synchronized (mMutex) {
+ try {
+ ManagedServiceInfo info = newServiceInfo(service, component, userid,
+ true /*isSystem*/, null, Build.VERSION_CODES.L);
+ service.asBinder().linkToDeath(info, 0);
+ mServices.add(info);
+ } catch (RemoteException e) {
+ // already dead
+ }
+ }
+ }
+
+ /**
+ * Removes a service from the list and unbinds.
+ */
+ private void unregisterServiceImpl(IInterface service, int userid) {
+ ManagedServiceInfo info = removeServiceImpl(service, userid);
+ if (info != null && info.connection != null) {
+ mContext.unbindService(info.connection);
+ }
+ }
+
+ private class SettingsObserver extends ContentObserver {
+ private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(mConfig.secureSettingName);
+
+ private SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ private void observe() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(mSecureSettingsUri,
+ false, this, UserHandle.USER_ALL);
+ update(null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ update(uri);
+ }
+
+ private void update(Uri uri) {
+ if (uri == null || mSecureSettingsUri.equals(uri)) {
+ rebindServices();
+ }
+ }
+ }
+
+ public class ManagedServiceInfo implements IBinder.DeathRecipient {
+ public IInterface service;
+ public ComponentName component;
+ public int userid;
+ public boolean isSystem;
+ public ServiceConnection connection;
+ public int targetSdkVersion;
+
+ public ManagedServiceInfo(IInterface service, ComponentName component,
+ int userid, boolean isSystem, ServiceConnection connection, int targetSdkVersion) {
+ this.service = service;
+ this.component = component;
+ this.userid = userid;
+ this.isSystem = isSystem;
+ this.connection = connection;
+ this.targetSdkVersion = targetSdkVersion;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("ManagedServiceInfo[")
+ .append("component=").append(component)
+ .append(",userid=").append(userid)
+ .append(",isSystem=").append(isSystem)
+ .append(",targetSdkVersion=").append(targetSdkVersion)
+ .append(",connection=").append(connection == null ? null : "<connection>")
+ .append(",service=").append(service)
+ .append(']').toString();
+ }
+
+ public boolean enabledAndUserMatches(int nid) {
+ if (!isEnabledForCurrentProfiles()) {
+ return false;
+ }
+ if (this.userid == UserHandle.USER_ALL) return true;
+ if (nid == UserHandle.USER_ALL || nid == this.userid) return true;
+ return supportsProfiles() && mUserProfiles.isCurrentProfile(nid);
+ }
+
+ public boolean supportsProfiles() {
+ return targetSdkVersion >= Build.VERSION_CODES.L;
+ }
+
+ @Override
+ public void binderDied() {
+ if (DEBUG) Slog.d(TAG, "binderDied");
+ // Remove the service, but don't unbind from the service. The system will bring the
+ // service back up, and the onServiceConnected handler will readd the service with the
+ // new binding. If this isn't a bound service, and is just a registered
+ // service, just removing it from the list is all we need to do anyway.
+ removeServiceImpl(this.service, this.userid);
+ }
+
+ /** convenience method for looking in mEnabledServicesForCurrentProfiles */
+ public boolean isEnabledForCurrentProfiles() {
+ if (this.isSystem) return true;
+ if (this.connection == null) return false;
+ return mEnabledServicesForCurrentProfiles.contains(this.component);
+ }
+ }
+
+ public static class UserProfiles {
+ // Profiles of the current user.
+ private final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
+
+ public void updateCache(Context context) {
+ UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ if (userManager != null) {
+ int currentUserId = ActivityManager.getCurrentUser();
+ List<UserInfo> profiles = userManager.getProfiles(currentUserId);
+ synchronized (mCurrentProfiles) {
+ mCurrentProfiles.clear();
+ for (UserInfo user : profiles) {
+ mCurrentProfiles.put(user.id, user);
+ }
+ }
+ }
+ }
+
+ public int[] getCurrentProfileIds() {
+ synchronized (mCurrentProfiles) {
+ int[] users = new int[mCurrentProfiles.size()];
+ final int N = mCurrentProfiles.size();
+ for (int i = 0; i < N; ++i) {
+ users[i] = mCurrentProfiles.keyAt(i);
+ }
+ return users;
+ }
+ }
+
+ public boolean isCurrentProfile(int userId) {
+ synchronized (mCurrentProfiles) {
+ return mCurrentProfiles.get(userId) != null;
+ }
+ }
+ }
+
+ protected static class Config {
+ String caption;
+ String serviceInterface;
+ String secureSettingName;
+ String bindPermission;
+ String settingsAction;
+ int clientLabel;
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
new file mode 100644
index 0000000..49293d3
--- /dev/null
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.notification;
+
+import java.util.Comparator;
+
+/**
+ * Sorts notificaitons into attention-relelvant order.
+ */
+public class NotificationComparator
+ implements Comparator<NotificationManagerService.NotificationRecord> {
+
+ @Override
+ public int compare(NotificationManagerService.NotificationRecord lhs,
+ NotificationManagerService.NotificationRecord rhs) {
+ if (lhs.isRecentlyIntrusive() != rhs.isRecentlyIntrusive()) {
+ return lhs.isRecentlyIntrusive() ? -1 : 1;
+ }
+ final int leftScore = lhs.sbn.getScore();
+ final int rightScore = rhs.sbn.getScore();
+ if (leftScore != rightScore) {
+ // by priority, high to low
+ return -1 * Integer.compare(leftScore, rightScore);
+ }
+ final float leftPeple = lhs.getContactAffinity();
+ final float rightPeople = rhs.getContactAffinity();
+ if (leftPeple != rightPeople) {
+ // by contact proximity, close to far
+ return -1 * Float.compare(leftPeple, rightPeople);
+ }
+ // then break ties by time, most recent first
+ return -1 * Long.compare(lhs.sbn.getPostTime(), rhs.sbn.getPostTime());
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index df2aaca..b41b478 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -16,12 +16,20 @@
package com.android.server.notification;
+import android.os.IBinder;
+
public interface NotificationDelegate {
void onSetDisabled(int status);
- void onClearAll();
- void onNotificationClick(String pkg, String tag, int id);
- void onNotificationClear(String pkg, String tag, int id);
- void onNotificationError(String pkg, String tag, int id,
- int uid, int initialPid, String message);
+ void onClearAll(int callingUid, int callingPid, int userId);
+ void onNotificationClick(int callingUid, int callingPid, String key);
+ void onNotificationClear(int callingUid, int callingPid,
+ String pkg, String tag, int id, int userId);
+ void onNotificationError(int callingUid, int callingPid,
+ String pkg, String tag, int id,
+ int uid, int initialPid, String message, int userId);
void onPanelRevealed();
+ void onPanelHidden();
+ boolean allowDisable(int what, IBinder token, String pkg);
+ void onNotificationVisibilityChanged(
+ String[] newlyVisibleKeys, String[] noLongerVisibleKeys);
}
diff --git a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
new file mode 100644
index 0000000..125158f
--- /dev/null
+++ b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
@@ -0,0 +1,64 @@
+/*
+* Copyright (C) 2014 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.android.server.notification;
+
+import android.app.Notification;
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.server.notification.NotificationManagerService.NotificationRecord;
+
+/**
+ * This {@link com.android.server.notification.NotificationSignalExtractor} noticies noisy
+ * notifications and marks them to get a temporary ranking bump.
+ */
+public class NotificationIntrusivenessExtractor implements NotificationSignalExtractor {
+ private static final String TAG = "NotificationNoiseExtractor";
+ private static final boolean DBG = false;
+
+ /** Length of time (in milliseconds) that an intrusive or noisy notification will stay at
+ the top of the ranking order, before it falls back to its natural position. */
+ private static final long HANG_TIME_MS = 10000;
+
+ public void initialize(Context ctx) {
+ if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
+ }
+
+ public RankingFuture process(NotificationRecord record) {
+ if (record == null || record.getNotification() == null) {
+ if (DBG) Slog.d(TAG, "skipping empty notification");
+ return null;
+ }
+
+ final Notification notification = record.getNotification();
+ if ((notification.defaults & Notification.DEFAULT_VIBRATE) != 0 ||
+ notification.vibrate != null ||
+ (notification.defaults & Notification.DEFAULT_SOUND) != 0 ||
+ notification.sound != null ||
+ notification.fullScreenIntent != null) {
+ record.setRecentlyIntusive(true);
+ }
+
+ return new RankingFuture(record, HANG_TIME_MS) {
+ @Override
+ public void work() {
+ mRecord.setRecentlyIntusive(false);
+ }
+ };
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 38b8dc6..1734a33 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -36,12 +36,9 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -50,8 +47,12 @@ import android.media.AudioManager;
import android.media.IRingtonePlayer;
import android.net.Uri;
import android.os.Binder;
+import android.os.Environment;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
@@ -59,13 +60,17 @@ import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.INotificationListener;
+import android.service.notification.IConditionListener;
+import android.service.notification.IConditionProvider;
import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationOrderUpdate;
import android.service.notification.StatusBarNotification;
+import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AtomicFile;
-import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.util.Xml;
@@ -74,34 +79,39 @@ import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
import com.android.internal.R;
-
-import com.android.internal.notification.NotificationScorer;
+import com.android.internal.util.FastXmlSerializer;
import com.android.server.EventLogTags;
-import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.SystemService;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
+import com.android.server.notification.ManagedServices.ManagedServiceInfo;
+import com.android.server.notification.ManagedServices.UserProfiles;
+import com.android.server.notification.NotificationUsageStats.SingleNotificationStats;
+import com.android.server.statusbar.StatusBarManagerInternal;
+
+import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.List;
import java.util.NoSuchElementException;
-import java.util.Set;
-
-import libcore.io.IoUtils;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
/** {@hide} */
public class NotificationManagerService extends SystemService {
@@ -112,6 +122,9 @@ public class NotificationManagerService extends SystemService {
// message codes
static final int MESSAGE_TIMEOUT = 2;
+ static final int MESSAGE_SAVE_POLICY_FILE = 3;
+ static final int MESSAGE_RECONSIDER_RANKING = 4;
+ static final int MESSAGE_SEND_RANKING_UPDATE = 5;
static final int LONG_DELAY = 3500; // 3.5 seconds
static final int SHORT_DELAY = 2000; // 2 seconds
@@ -134,8 +147,6 @@ public class NotificationManagerService extends SystemService {
static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true;
static final boolean ENABLE_BLOCKED_TOASTS = true;
- static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":";
-
private IActivityManager mAm;
AudioManager mAudioManager;
StatusBarManagerInternal mStatusBar;
@@ -143,6 +154,9 @@ public class NotificationManagerService extends SystemService {
final IBinder mForegroundToken = new Binder();
private WorkerHandler mHandler;
+ private final HandlerThread mRankingThread = new HandlerThread("ranker",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ private Handler mRankingHandler = null;
private Light mNotificationLight;
Light mAttentionLight;
@@ -155,7 +169,7 @@ public class NotificationManagerService extends SystemService {
private long[] mFallbackVibrationPattern;
boolean mSystemReady;
- int mDisabledNotifications;
+ private boolean mDisableNotificationAlerts;
NotificationRecord mSoundNotification;
NotificationRecord mVibrateNotification;
@@ -167,9 +181,9 @@ public class NotificationManagerService extends SystemService {
// used as a mutex for access to all active notifications & listeners
final ArrayList<NotificationRecord> mNotificationList =
new ArrayList<NotificationRecord>();
+ final NotificationComparator mRankingComparator = new NotificationComparator();
final ArrayMap<String, NotificationRecord> mNotificationsByKey =
new ArrayMap<String, NotificationRecord>();
-
final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
@@ -177,19 +191,6 @@ public class NotificationManagerService extends SystemService {
private AppOpsManager mAppOps;
- // contains connections to all connected listeners, including app services
- // and system listeners
- private ArrayList<NotificationListenerInfo> mListeners
- = new ArrayList<NotificationListenerInfo>();
- // things that will be put into mListeners as soon as they're ready
- private ArrayList<String> mServicesBinding = new ArrayList<String>();
- // lists the component names of all enabled (and therefore connected) listener
- // app services for the current user only
- private HashSet<ComponentName> mEnabledListenersForCurrentUser
- = new HashSet<ComponentName>();
- // Just the packages from mEnabledListenersForCurrentUser
- private HashSet<String> mEnabledListenerPackageNames = new HashSet<String>();
-
// Notification control database. For now just contains disabled packages.
private AtomicFile mPolicyFile;
private HashSet<String> mBlockedPackages = new HashSet<String>();
@@ -203,78 +204,28 @@ public class NotificationManagerService extends SystemService {
private static final String TAG_PACKAGE = "package";
private static final String ATTR_NAME = "name";
- final ArrayList<NotificationScorer> mScorers = new ArrayList<NotificationScorer>();
-
- private class NotificationListenerInfo implements IBinder.DeathRecipient {
- INotificationListener listener;
- ComponentName component;
- int userid;
- boolean isSystem;
- ServiceConnection connection;
-
- public NotificationListenerInfo(INotificationListener listener, ComponentName component,
- int userid, boolean isSystem) {
- this.listener = listener;
- this.component = component;
- this.userid = userid;
- this.isSystem = isSystem;
- this.connection = null;
- }
-
- public NotificationListenerInfo(INotificationListener listener, ComponentName component,
- int userid, ServiceConnection connection) {
- this.listener = listener;
- this.component = component;
- this.userid = userid;
- this.isSystem = false;
- this.connection = connection;
- }
-
- boolean enabledAndUserMatches(StatusBarNotification sbn) {
- final int nid = sbn.getUserId();
- if (!isEnabledForCurrentUser()) {
- return false;
- }
- if (this.userid == UserHandle.USER_ALL) return true;
- return (nid == UserHandle.USER_ALL || nid == this.userid);
- }
-
- public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
- if (!enabledAndUserMatches(sbn)) {
- return;
- }
- try {
- listener.onNotificationPosted(sbn);
- } catch (RemoteException ex) {
- Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
- }
- }
-
- public void notifyRemovedIfUserMatch(StatusBarNotification sbn) {
- if (!enabledAndUserMatches(sbn)) return;
- try {
- listener.onNotificationRemoved(sbn);
- } catch (RemoteException ex) {
- Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
- }
- }
-
- @Override
- public void binderDied() {
- // Remove the listener, but don't unbind from the service. The system will bring the
- // service back up, and the onServiceConnected handler will readd the listener with the
- // new binding. If this isn't a bound service, and is just a registered
- // INotificationListener, just removing it from the list is all we need to do anyway.
- removeListenerImpl(this.listener, this.userid);
- }
-
- /** convenience method for looking in mEnabledListenersForCurrentUser */
- public boolean isEnabledForCurrentUser() {
- if (this.isSystem) return true;
- if (this.connection == null) return false;
- return mEnabledListenersForCurrentUser.contains(this.component);
- }
- }
+ final ArrayList<NotificationSignalExtractor> mSignalExtractors = new ArrayList<NotificationSignalExtractor>();
+
+ private final UserProfiles mUserProfiles = new UserProfiles();
+ private NotificationListeners mListeners;
+ private ConditionProviders mConditionProviders;
+ private NotificationUsageStats mUsageStats;
+
+ private static final String EXTRA_INTERCEPT = "android.intercept";
+
+ private static final int MY_UID = Process.myUid();
+ private static final int MY_PID = Process.myPid();
+ private static final int REASON_DELEGATE_CLICK = 1;
+ private static final int REASON_DELEGATE_CANCEL = 2;
+ private static final int REASON_DELEGATE_CANCEL_ALL = 3;
+ private static final int REASON_DELEGATE_ERROR = 4;
+ private static final int REASON_PACKAGE_CHANGED = 5;
+ private static final int REASON_USER_STOPPED = 6;
+ private static final int REASON_PACKAGE_BANNED = 7;
+ private static final int REASON_NOMAN_CANCEL = 8;
+ private static final int REASON_NOMAN_CANCEL_ALL = 9;
+ private static final int REASON_LISTENER_CANCEL = 10;
+ private static final int REASON_LISTENER_CANCEL_ALL = 11;
private static class Archive {
static final int BUFFER_SIZE = 250;
@@ -381,53 +332,82 @@ public class NotificationManagerService extends SystemService {
Archive mArchive = new Archive();
- private void loadBlockDb() {
- synchronized(mBlockedPackages) {
- if (mPolicyFile == null) {
- File dir = new File("/data/system");
- mPolicyFile = new AtomicFile(new File(dir, "notification_policy.xml"));
+ private void loadPolicyFile() {
+ synchronized(mPolicyFile) {
+ mBlockedPackages.clear();
- mBlockedPackages.clear();
-
- FileInputStream infile = null;
- try {
- infile = mPolicyFile.openRead();
- final XmlPullParser parser = Xml.newPullParser();
- parser.setInput(infile, null);
-
- int type;
- String tag;
- int version = DB_VERSION;
- while ((type = parser.next()) != END_DOCUMENT) {
- tag = parser.getName();
- if (type == START_TAG) {
- if (TAG_BODY.equals(tag)) {
- version = Integer.parseInt(
- parser.getAttributeValue(null, ATTR_VERSION));
- } else if (TAG_BLOCKED_PKGS.equals(tag)) {
- while ((type = parser.next()) != END_DOCUMENT) {
- tag = parser.getName();
- if (TAG_PACKAGE.equals(tag)) {
- mBlockedPackages.add(
- parser.getAttributeValue(null, ATTR_NAME));
- } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
- break;
- }
+ FileInputStream infile = null;
+ try {
+ infile = mPolicyFile.openRead();
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(infile, null);
+
+ int type;
+ String tag;
+ int version = DB_VERSION;
+ while ((type = parser.next()) != END_DOCUMENT) {
+ tag = parser.getName();
+ if (type == START_TAG) {
+ if (TAG_BODY.equals(tag)) {
+ version = Integer.parseInt(
+ parser.getAttributeValue(null, ATTR_VERSION));
+ } else if (TAG_BLOCKED_PKGS.equals(tag)) {
+ while ((type = parser.next()) != END_DOCUMENT) {
+ tag = parser.getName();
+ if (TAG_PACKAGE.equals(tag)) {
+ mBlockedPackages.add(
+ parser.getAttributeValue(null, ATTR_NAME));
+ } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
+ break;
}
}
}
}
- } catch (FileNotFoundException e) {
- // No data yet
- } catch (IOException e) {
- Log.wtf(TAG, "Unable to read blocked notifications database", e);
- } catch (NumberFormatException e) {
- Log.wtf(TAG, "Unable to parse blocked notifications database", e);
- } catch (XmlPullParserException e) {
- Log.wtf(TAG, "Unable to parse blocked notifications database", e);
- } finally {
- IoUtils.closeQuietly(infile);
+ mZenModeHelper.readXml(parser);
}
+ } catch (FileNotFoundException e) {
+ // No data yet
+ } catch (IOException e) {
+ Log.wtf(TAG, "Unable to read notification policy", e);
+ } catch (NumberFormatException e) {
+ Log.wtf(TAG, "Unable to parse notification policy", e);
+ } catch (XmlPullParserException e) {
+ Log.wtf(TAG, "Unable to parse notification policy", e);
+ } finally {
+ IoUtils.closeQuietly(infile);
+ }
+ }
+ }
+
+ public void savePolicyFile() {
+ mHandler.removeMessages(MESSAGE_SAVE_POLICY_FILE);
+ mHandler.sendEmptyMessage(MESSAGE_SAVE_POLICY_FILE);
+ }
+
+ private void handleSavePolicyFile() {
+ Slog.d(TAG, "handleSavePolicyFile");
+ synchronized (mPolicyFile) {
+ final FileOutputStream stream;
+ try {
+ stream = mPolicyFile.startWrite();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to save policy file", e);
+ return;
+ }
+
+ try {
+ final XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, TAG_BODY);
+ out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
+ mZenModeHelper.writeXml(out);
+ out.endTag(null, TAG_BODY);
+ out.endDocument();
+ mPolicyFile.finishWrite(stream);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to save policy file, restoring backup", e);
+ mPolicyFile.failWrite(stream);
}
}
}
@@ -471,300 +451,18 @@ public class NotificationManagerService extends SystemService {
}
- /**
- * Remove notification access for any services that no longer exist.
- */
- void disableNonexistentListeners() {
- int currentUser = ActivityManager.getCurrentUser();
- String flatIn = Settings.Secure.getStringForUser(
- getContext().getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- currentUser);
- if (!TextUtils.isEmpty(flatIn)) {
- if (DBG) Slog.v(TAG, "flat before: " + flatIn);
- PackageManager pm = getContext().getPackageManager();
- List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
- new Intent(NotificationListenerService.SERVICE_INTERFACE),
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
- currentUser);
-
- Set<ComponentName> installed = new HashSet<ComponentName>();
- for (int i = 0, count = installedServices.size(); i < count; i++) {
- ResolveInfo resolveInfo = installedServices.get(i);
- ServiceInfo info = resolveInfo.serviceInfo;
-
- if (!android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE.equals(
- info.permission)) {
- Slog.w(TAG, "Skipping notification listener service "
- + info.packageName + "/" + info.name
- + ": it does not require the permission "
- + android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE);
- continue;
- }
- installed.add(new ComponentName(info.packageName, info.name));
- }
-
- String flatOut = "";
- if (!installed.isEmpty()) {
- String[] enabled = flatIn.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
- ArrayList<String> remaining = new ArrayList<String>(enabled.length);
- for (int i = 0; i < enabled.length; i++) {
- ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]);
- if (installed.contains(enabledComponent)) {
- remaining.add(enabled[i]);
- }
- }
- flatOut = TextUtils.join(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR, remaining);
- }
- if (DBG) Slog.v(TAG, "flat after: " + flatOut);
- if (!flatIn.equals(flatOut)) {
- Settings.Secure.putStringForUser(getContext().getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- flatOut, currentUser);
- }
- }
- }
-
- /**
- * Called whenever packages change, the user switches, or ENABLED_NOTIFICATION_LISTENERS
- * is altered. (For example in response to USER_SWITCHED in our broadcast receiver)
- */
- void rebindListenerServices() {
- final int currentUser = ActivityManager.getCurrentUser();
- String flat = Settings.Secure.getStringForUser(
- getContext().getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- currentUser);
-
- NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()];
- final ArrayList<ComponentName> toAdd;
-
- synchronized (mNotificationList) {
- // unbind and remove all existing listeners
- toRemove = mListeners.toArray(toRemove);
-
- toAdd = new ArrayList<ComponentName>();
- final HashSet<ComponentName> newEnabled = new HashSet<ComponentName>();
- final HashSet<String> newPackages = new HashSet<String>();
-
- // decode the list of components
- if (flat != null) {
- String[] components = flat.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
- for (int i=0; i<components.length; i++) {
- final ComponentName component
- = ComponentName.unflattenFromString(components[i]);
- if (component != null) {
- newEnabled.add(component);
- toAdd.add(component);
- newPackages.add(component.getPackageName());
- }
- }
-
- mEnabledListenersForCurrentUser = newEnabled;
- mEnabledListenerPackageNames = newPackages;
- }
- }
-
- for (NotificationListenerInfo info : toRemove) {
- final ComponentName component = info.component;
- final int oldUser = info.userid;
- Slog.v(TAG, "disabling notification listener for user " + oldUser + ": " + component);
- unregisterListenerService(component, info.userid);
- }
-
- final int N = toAdd.size();
- for (int i=0; i<N; i++) {
- final ComponentName component = toAdd.get(i);
- Slog.v(TAG, "enabling notification listener for user " + currentUser + ": "
- + component);
- registerListenerService(component, currentUser);
- }
- }
-
-
- /**
- * Version of registerListener that takes the name of a
- * {@link android.service.notification.NotificationListenerService} to bind to.
- *
- * This is the mechanism by which third parties may subscribe to notifications.
- */
- private void registerListenerService(final ComponentName name, final int userid) {
- checkCallerIsSystem();
-
- if (DBG) Slog.v(TAG, "registerListenerService: " + name + " u=" + userid);
-
- synchronized (mNotificationList) {
- final String servicesBindingTag = name.toString() + "/" + userid;
- if (mServicesBinding.contains(servicesBindingTag)) {
- // stop registering this thing already! we're working on it
- return;
- }
- mServicesBinding.add(servicesBindingTag);
-
- final int N = mListeners.size();
- for (int i=N-1; i>=0; i--) {
- final NotificationListenerInfo info = mListeners.get(i);
- if (name.equals(info.component)
- && info.userid == userid) {
- // cut old connections
- if (DBG) Slog.v(TAG, " disconnecting old listener: " + info.listener);
- mListeners.remove(i);
- if (info.connection != null) {
- getContext().unbindService(info.connection);
- }
- }
- }
-
- Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
- intent.setComponent(name);
-
- intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
- R.string.notification_listener_binding_label);
-
- final PendingIntent pendingIntent = PendingIntent.getActivity(
- getContext(), 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0);
- intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
-
- try {
- if (DBG) Slog.v(TAG, "binding: " + intent);
- if (!getContext().bindServiceAsUser(intent,
- new ServiceConnection() {
- INotificationListener mListener;
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- boolean added = false;
- synchronized (mNotificationList) {
- mServicesBinding.remove(servicesBindingTag);
- try {
- mListener = INotificationListener.Stub.asInterface(service);
- NotificationListenerInfo info
- = new NotificationListenerInfo(
- mListener, name, userid, this);
- service.linkToDeath(info, 0);
- added = mListeners.add(info);
- } catch (RemoteException e) {
- // already dead
- }
- }
- if (added) {
- final String[] keys =
- getActiveNotificationKeysFromListener(mListener);
- try {
- mListener.onListenerConnected(keys);
- } catch (RemoteException e) {
- // we tried
- }
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- Slog.v(TAG, "notification listener connection lost: " + name);
- }
- },
- Context.BIND_AUTO_CREATE,
- new UserHandle(userid)))
- {
- mServicesBinding.remove(servicesBindingTag);
- Slog.w(TAG, "Unable to bind listener service: " + intent);
- return;
- }
- } catch (SecurityException ex) {
- Slog.e(TAG, "Unable to bind listener service: " + intent, ex);
- return;
- }
- }
- }
-
-
- /**
- * Remove a listener service for the given user by ComponentName
- */
- private void unregisterListenerService(ComponentName name, int userid) {
- checkCallerIsSystem();
-
- synchronized (mNotificationList) {
- final int N = mListeners.size();
- for (int i=N-1; i>=0; i--) {
- final NotificationListenerInfo info = mListeners.get(i);
- if (name.equals(info.component)
- && info.userid == userid) {
- mListeners.remove(i);
- if (info.connection != null) {
- try {
- getContext().unbindService(info.connection);
- } catch (IllegalArgumentException ex) {
- // something happened to the service: we think we have a connection
- // but it's bogus.
- Slog.e(TAG, "Listener " + name + " could not be unbound: " + ex);
- }
- }
- }
- }
- }
- }
-
- /**
- * asynchronously notify all listeners about a new notification
- */
- void notifyPostedLocked(NotificationRecord n) {
- // make a copy in case changes are made to the underlying Notification object
- final StatusBarNotification sbn = n.sbn.clone();
- for (final NotificationListenerInfo info : mListeners) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- info.notifyPostedIfUserMatch(sbn);
- }});
- }
- }
-
- /**
- * asynchronously notify all listeners about a removed notification
- */
- void notifyRemovedLocked(NotificationRecord n) {
- // make a copy in case changes are made to the underlying Notification object
- // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the notification
- final StatusBarNotification sbn_light = n.sbn.cloneLight();
-
- for (final NotificationListenerInfo info : mListeners) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- info.notifyRemovedIfUserMatch(sbn_light);
- }});
- }
- }
-
- // -- APIs to support listeners clicking/clearing notifications --
-
- private void checkNullListener(INotificationListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("Listener must not be null");
- }
- }
-
- private NotificationListenerInfo checkListenerTokenLocked(INotificationListener listener) {
- checkNullListener(listener);
- final IBinder token = listener.asBinder();
- final int N = mListeners.size();
- for (int i=0; i<N; i++) {
- final NotificationListenerInfo info = mListeners.get(i);
- if (info.listener.asBinder() == token) return info;
- }
- throw new SecurityException("Disallowed call from unknown listener: " + listener);
- }
-
-
-
- // -- end of listener APIs --
public static final class NotificationRecord
{
final StatusBarNotification sbn;
+ SingleNotificationStats stats;
IBinder statusBarKey;
+ // These members are used by NotificationSignalExtractors
+ // to communicate with the ranking module.
+ private float mContactAffinity;
+ private boolean mRecentlyIntrusive;
+
NotificationRecord(StatusBarNotification sbn)
{
this.sbn = sbn;
@@ -789,6 +487,7 @@ public class NotificationManagerService extends SystemService {
pw.println(prefix + String.format(" defaults=0x%08x flags=0x%08x",
notification.defaults, notification.flags));
pw.println(prefix + " sound=" + notification.sound);
+ pw.println(prefix + String.format(" color=0x%08x", notification.color));
pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate));
pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d",
notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
@@ -833,6 +532,7 @@ public class NotificationManagerService extends SystemService {
}
pw.println(prefix + " }");
}
+ pw.println(prefix + " stats=" + stats.toString());
}
@Override
@@ -844,6 +544,22 @@ public class NotificationManagerService extends SystemService {
this.sbn.getTag(), this.sbn.getScore(), this.sbn.getKey(),
this.sbn.getNotification());
}
+
+ public void setContactAffinity(float contactAffinity) {
+ mContactAffinity = contactAffinity;
+ }
+
+ public float getContactAffinity() {
+ return mContactAffinity;
+ }
+
+ public boolean isRecentlyIntrusive() {
+ return mRecentlyIntrusive;
+ }
+
+ public void setRecentlyIntusive(boolean recentlyIntrusive) {
+ mRecentlyIntrusive = recentlyIntrusive;
+ }
}
private static final class ToastRecord
@@ -885,8 +601,8 @@ public class NotificationManagerService extends SystemService {
@Override
public void onSetDisabled(int status) {
synchronized (mNotificationList) {
- mDisabledNotifications = status;
- if ((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
+ mDisableNotificationAlerts = (status & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
+ if (mDisableNotificationAlerts) {
// cancel whatever's going on
long identity = Binder.clearCallingIdentity();
try {
@@ -910,35 +626,41 @@ public class NotificationManagerService extends SystemService {
}
@Override
- public void onClearAll() {
- // XXX to be totally correct, the caller should tell us which user
- // this is for.
- int currentUser = ActivityManager.getCurrentUser();
+ public void onClearAll(int callingUid, int callingPid, int userId) {
synchronized (mNotificationList) {
- cancelAllLocked(currentUser);
+ cancelAllLocked(callingUid, callingPid, userId, REASON_DELEGATE_CANCEL_ALL, null,
+ /*includeCurrentProfiles*/ true);
}
}
@Override
- public void onNotificationClick(String pkg, String tag, int id) {
- // XXX to be totally correct, the caller should tell us which user
- // this is for.
- cancelNotification(pkg, tag, id, Notification.FLAG_AUTO_CANCEL,
- Notification.FLAG_FOREGROUND_SERVICE, false,
- ActivityManager.getCurrentUser());
+ public void onNotificationClick(int callingUid, int callingPid, String key) {
+ synchronized (mNotificationList) {
+ EventLogTags.writeNotificationClicked(key);
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r == null) {
+ Log.w(TAG, "No notification with key: " + key);
+ return;
+ }
+ StatusBarNotification sbn = r.sbn;
+ cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
+ sbn.getId(), Notification.FLAG_AUTO_CANCEL,
+ Notification.FLAG_FOREGROUND_SERVICE, false, r.getUserId(),
+ REASON_DELEGATE_CLICK, null);
+ }
}
@Override
- public void onNotificationClear(String pkg, String tag, int id) {
- // XXX to be totally correct, the caller should tell us which user
- // this is for.
- cancelNotification(pkg, tag, id, 0,
- Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
- true, ActivityManager.getCurrentUser());
+ public void onNotificationClear(int callingUid, int callingPid,
+ String pkg, String tag, int id, int userId) {
+ cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
+ Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
+ true, userId, REASON_DELEGATE_CANCEL, null);
}
@Override
public void onPanelRevealed() {
+ EventLogTags.writeNotificationPanelRevealed();
synchronized (mNotificationList) {
// sound
mSoundNotification = null;
@@ -971,13 +693,17 @@ public class NotificationManagerService extends SystemService {
}
@Override
- public void onNotificationError(String pkg, String tag, int id,
- int uid, int initialPid, String message) {
+ public void onPanelHidden() {
+ EventLogTags.writeNotificationPanelHidden();
+ }
+
+ @Override
+ public void onNotificationError(int callingUid, int callingPid, String pkg, String tag, int id,
+ int uid, int initialPid, String message, int userId) {
Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id
+ "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")");
- // XXX to be totally correct, the caller should tell us which user
- // this is for.
- cancelNotification(pkg, tag, id, 0, 0, false, UserHandle.getUserId(uid));
+ cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, false, userId,
+ REASON_DELEGATE_ERROR, null);
long ident = Binder.clearCallingIdentity();
try {
ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg,
@@ -987,6 +713,37 @@ public class NotificationManagerService extends SystemService {
}
Binder.restoreCallingIdentity(ident);
}
+
+ @Override
+ public boolean allowDisable(int what, IBinder token, String pkg) {
+ return mZenModeHelper.allowDisable(what, token, pkg);
+ }
+
+ @Override
+ public void onNotificationVisibilityChanged(
+ String[] newlyVisibleKeys, String[] noLongerVisibleKeys) {
+ // Using ';' as separator since eventlogs uses ',' to separate
+ // args.
+ EventLogTags.writeNotificationVisibilityChanged(
+ TextUtils.join(";", newlyVisibleKeys),
+ TextUtils.join(";", noLongerVisibleKeys));
+ synchronized (mNotificationList) {
+ for (String key : newlyVisibleKeys) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r == null) continue;
+ r.stats.onVisibilityChanged(true);
+ }
+ // Note that we might receive this event after notifications
+ // have already left the system, e.g. after dismissing from the
+ // shade. Hence not finding notifications in
+ // mNotificationsByKey is not an exceptional condition.
+ for (String key : noLongerVisibleKeys) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r == null) continue;
+ r.stats.onVisibilityChanged(false);
+ }
+ }
+ }
};
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@@ -998,7 +755,7 @@ public class NotificationManagerService extends SystemService {
boolean queryRemove = false;
boolean packageChanged = false;
boolean cancelNotifications = true;
-
+
if (action.equals(Intent.ACTION_PACKAGE_ADDED)
|| (queryRemove=action.equals(Intent.ACTION_PACKAGE_REMOVED))
|| action.equals(Intent.ACTION_PACKAGE_RESTARTED)
@@ -1008,7 +765,7 @@ public class NotificationManagerService extends SystemService {
String pkgList[] = null;
boolean queryReplace = queryRemove &&
intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
- if (DBG) Slog.i(TAG, "queryReplace=" + queryReplace);
+ if (DBG) Slog.i(TAG, "action=" + action + " queryReplace=" + queryReplace);
if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
} else if (queryRestart) {
@@ -1042,28 +799,16 @@ public class NotificationManagerService extends SystemService {
pkgList = new String[]{pkgName};
}
- boolean anyListenersInvolved = false;
if (pkgList != null && (pkgList.length > 0)) {
for (String pkgName : pkgList) {
if (cancelNotifications) {
- cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart,
- UserHandle.USER_ALL);
- }
- if (mEnabledListenerPackageNames.contains(pkgName)) {
- anyListenersInvolved = true;
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, 0, 0, !queryRestart,
+ UserHandle.USER_ALL, REASON_PACKAGE_CHANGED, null);
}
}
}
-
- if (anyListenersInvolved) {
- // if we're not replacing a package, clean up orphaned bits
- if (!queryReplace) {
- disableNonexistentListeners();
- }
- // make sure we're still bound to any of our
- // listeners who may have just upgraded
- rebindListenerServices();
- }
+ mListeners.onPackagesChanged(queryReplace, pkgList);
+ mConditionProviders.onPackagesChanged(queryReplace, pkgList);
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
// Keep track of screen on/off state, but do not turn off the notification light
// until user passes through the lock screen or views the notification.
@@ -1071,13 +816,14 @@ public class NotificationManagerService extends SystemService {
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
mScreenOn = false;
} else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
- mInCall = (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(
- TelephonyManager.EXTRA_STATE_OFFHOOK));
+ mInCall = TelephonyManager.EXTRA_STATE_OFFHOOK
+ .equals(intent.getStringExtra(TelephonyManager.EXTRA_STATE));
updateNotificationPulse();
} else if (action.equals(Intent.ACTION_USER_STOPPED)) {
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userHandle >= 0) {
- cancelAllNotificationsInt(null, 0, 0, true, userHandle);
+ cancelAllNotificationsInt(MY_UID, MY_PID, null, 0, 0, true, userHandle,
+ REASON_USER_STOPPED, null);
}
} else if (action.equals(Intent.ACTION_USER_PRESENT)) {
// turn off LED when user passes through lock screen
@@ -1085,6 +831,9 @@ public class NotificationManagerService extends SystemService {
} else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
// reload per-user settings
mSettingsObserver.update(null);
+ mUserProfiles.updateCache(context);
+ } else if (action.equals(Intent.ACTION_USER_ADDED)) {
+ mUserProfiles.updateCache(context);
}
}
};
@@ -1093,9 +842,6 @@ public class NotificationManagerService extends SystemService {
private final Uri NOTIFICATION_LIGHT_PULSE_URI
= Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
- private final Uri ENABLED_NOTIFICATION_LISTENERS_URI
- = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
-
SettingsObserver(Handler handler) {
super(handler);
}
@@ -1104,8 +850,6 @@ public class NotificationManagerService extends SystemService {
ContentResolver resolver = getContext().getContentResolver();
resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
false, this, UserHandle.USER_ALL);
- resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI,
- false, this, UserHandle.USER_ALL);
update(null);
}
@@ -1123,13 +867,11 @@ public class NotificationManagerService extends SystemService {
updateNotificationPulse();
}
}
- if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) {
- rebindListenerServices();
- }
}
}
private SettingsObserver mSettingsObserver;
+ private ZenModeHelper mZenModeHelper;
static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) {
int[] ar = r.getIntArray(resid);
@@ -1155,9 +897,24 @@ public class NotificationManagerService extends SystemService {
mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
mHandler = new WorkerHandler();
+ mRankingThread.start();
+ mRankingHandler = new RankingWorkerHandler(mRankingThread.getLooper());
+ mZenModeHelper = new ZenModeHelper(getContext(), mHandler);
+ mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
+ @Override
+ public void onConfigChanged() {
+ savePolicyFile();
+ }
+ });
+ final File systemDir = new File(Environment.getDataDirectory(), "system");
+ mPolicyFile = new AtomicFile(new File(systemDir, "notification_policy.xml"));
+ mUsageStats = new NotificationUsageStats(getContext());
importOldBlockDb();
+ mListeners = new NotificationListeners();
+ mConditionProviders = new ConditionProviders(getContext(),
+ mHandler, mUserProfiles, mZenModeHelper);
mStatusBar = getLocalService(StatusBarManagerInternal.class);
mStatusBar.setNotificationDelegate(mNotificationDelegate);
@@ -1189,8 +946,11 @@ public class NotificationManagerService extends SystemService {
// flag at least once and we'll go back to 0 after that.
if (0 == Settings.Global.getInt(getContext().getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 0)) {
- mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
+ mDisableNotificationAlerts = true;
}
+ mZenModeHelper.updateZenMode();
+
+ mUserProfiles.updateCache(getContext());
// register for various Intents
IntentFilter filter = new IntentFilter();
@@ -1200,6 +960,7 @@ public class NotificationManagerService extends SystemService {
filter.addAction(Intent.ACTION_USER_PRESENT);
filter.addAction(Intent.ACTION_USER_STOPPED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
+ filter.addAction(Intent.ACTION_USER_ADDED);
getContext().registerReceiver(mIntentReceiver, filter);
IntentFilter pkgFilter = new IntentFilter();
pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
@@ -1214,21 +975,22 @@ public class NotificationManagerService extends SystemService {
mSettingsObserver = new SettingsObserver(mHandler);
- // spin up NotificationScorers
- String[] notificationScorerNames = resources.getStringArray(
- R.array.config_notificationScorers);
- for (String scorerName : notificationScorerNames) {
+ // spin up NotificationSignalExtractors
+ String[] extractorNames = resources.getStringArray(
+ R.array.config_notificationSignalExtractors);
+ for (String extractorName : extractorNames) {
try {
- Class<?> scorerClass = getContext().getClassLoader().loadClass(scorerName);
- NotificationScorer scorer = (NotificationScorer) scorerClass.newInstance();
- scorer.initialize(getContext());
- mScorers.add(scorer);
+ Class<?> extractorClass = getContext().getClassLoader().loadClass(extractorName);
+ NotificationSignalExtractor extractor =
+ (NotificationSignalExtractor) extractorClass.newInstance();
+ extractor.initialize(getContext());
+ mSignalExtractors.add(extractor);
} catch (ClassNotFoundException e) {
- Slog.w(TAG, "Couldn't find scorer " + scorerName + ".", e);
+ Slog.w(TAG, "Couldn't find extractor " + extractorName + ".", e);
} catch (InstantiationException e) {
- Slog.w(TAG, "Couldn't instantiate scorer " + scorerName + ".", e);
+ Slog.w(TAG, "Couldn't instantiate extractor " + extractorName + ".", e);
} catch (IllegalAccessException e) {
- Slog.w(TAG, "Problem accessing scorer " + scorerName + ".", e);
+ Slog.w(TAG, "Problem accessing extractor " + extractorName + ".", e);
}
}
@@ -1240,7 +1002,7 @@ public class NotificationManagerService extends SystemService {
* Read the old XML-based app block database and import those blockages into the AppOps system.
*/
private void importOldBlockDb() {
- loadBlockDb();
+ loadPolicyFile();
PackageManager pm = getContext().getPackageManager();
for (String pkg : mBlockedPackages) {
@@ -1253,9 +1015,6 @@ public class NotificationManagerService extends SystemService {
}
}
mBlockedPackages.clear();
- if (mPolicyFile != null) {
- mPolicyFile.delete();
- }
}
@Override
@@ -1271,6 +1030,8 @@ public class NotificationManagerService extends SystemService {
// This observer will force an update when observe is called, causing us to
// bind to listener services.
mSettingsObserver.observe();
+ mListeners.onBootPhaseAppsCanStart();
+ mConditionProviders.onBootPhaseAppsCanStart();
}
}
@@ -1282,7 +1043,8 @@ public class NotificationManagerService extends SystemService {
// Now, cancel any outstanding notifications that are part of a just-disabled app
if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
- cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid));
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, 0, 0, true, UserHandle.getUserId(uid),
+ REASON_PACKAGE_BANNED, null);
}
}
@@ -1386,9 +1148,9 @@ public class NotificationManagerService extends SystemService {
}
@Override
- public void enqueueNotificationWithTag(String pkg, String basePkg, String tag, int id,
+ public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
Notification notification, int[] idOut, int userId) throws RemoteException {
- enqueueNotificationInternal(pkg, basePkg, Binder.getCallingUid(),
+ enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
Binder.getCallingPid(), tag, id, notification, idOut, userId);
}
@@ -1398,9 +1160,10 @@ public class NotificationManagerService extends SystemService {
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg);
// Don't allow client applications to cancel foreground service notis.
- cancelNotification(pkg, tag, id, 0,
+ cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(), pkg, tag, id, 0,
Binder.getCallingUid() == Process.SYSTEM_UID
- ? 0 : Notification.FLAG_FOREGROUND_SERVICE, false, userId);
+ ? 0 : Notification.FLAG_FOREGROUND_SERVICE, false, userId, REASON_NOMAN_CANCEL,
+ null);
}
@Override
@@ -1412,7 +1175,9 @@ public class NotificationManagerService extends SystemService {
// Calling from user space, don't allow the canceling of actively
// running foreground services.
- cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId);
+ cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
+ pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId,
+ REASON_NOMAN_CANCEL_ALL, null);
}
@Override
@@ -1436,6 +1201,7 @@ public class NotificationManagerService extends SystemService {
* System-only API for getting a list of current (i.e. not cleared) notifications.
*
* Requires ACCESS_NOTIFICATIONS which is signature|system.
+ * @returns A list of all the notifications, in natural order.
*/
@Override
public StatusBarNotification[] getActiveNotifications(String callingPkg) {
@@ -1496,8 +1262,7 @@ public class NotificationManagerService extends SystemService {
public void registerListener(final INotificationListener listener,
final ComponentName component, final int userid) {
checkCallerIsSystem();
- checkNullListener(listener);
- registerListenerImpl(listener, component, userid);
+ mListeners.registerService(listener, component, userid);
}
/**
@@ -1505,10 +1270,7 @@ public class NotificationManagerService extends SystemService {
*/
@Override
public void unregisterListener(INotificationListener listener, int userid) {
- checkNullListener(listener);
- // no need to check permissions; if your listener binder is in the list,
- // that's proof that you had permission to add it in the first place
- unregisterListenerImpl(listener, userid);
+ mListeners.unregisterService(listener, userid);
}
/**
@@ -1520,21 +1282,31 @@ public class NotificationManagerService extends SystemService {
*/
@Override
public void cancelNotificationsFromListener(INotificationListener token, String[] keys) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationList) {
- final NotificationListenerInfo info = checkListenerTokenLocked(token);
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
if (keys != null) {
final int N = keys.length;
for (int i = 0; i < N; i++) {
NotificationRecord r = mNotificationsByKey.get(keys[i]);
+ final int userId = r.sbn.getUserId();
+ if (userId != info.userid && userId != UserHandle.USER_ALL &&
+ !mUserProfiles.isCurrentProfile(userId)) {
+ throw new SecurityException("Disallowed call from listener: "
+ + info.service);
+ }
if (r != null) {
- cancelNotificationFromListenerLocked(info,
- r.sbn.getPackageName(), r.sbn.getTag(), r.sbn.getId());
+ cancelNotificationFromListenerLocked(info, callingUid, callingPid,
+ r.sbn.getPackageName(), r.sbn.getTag(), r.sbn.getId(),
+ userId);
}
}
} else {
- cancelAllLocked(info.userid);
+ cancelAllLocked(callingUid, callingPid, info.userid,
+ REASON_LISTENER_CANCEL_ALL, info, info.supportsProfiles());
}
}
} finally {
@@ -1542,12 +1314,12 @@ public class NotificationManagerService extends SystemService {
}
}
- private void cancelNotificationFromListenerLocked(NotificationListenerInfo info,
- String pkg, String tag, int id) {
- cancelNotification(pkg, tag, id, 0,
+ private void cancelNotificationFromListenerLocked(ManagedServiceInfo info,
+ int callingUid, int callingPid, String pkg, String tag, int id, int userId) {
+ cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
true,
- info.userid);
+ userId, REASON_LISTENER_CANCEL, info);
}
/**
@@ -1560,12 +1332,20 @@ public class NotificationManagerService extends SystemService {
@Override
public void cancelNotificationFromListener(INotificationListener token, String pkg,
String tag, int id) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationList) {
- final NotificationListenerInfo info = checkListenerTokenLocked(token);
- cancelNotificationFromListenerLocked(info,
- pkg, tag, id);
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+ if (info.supportsProfiles()) {
+ Log.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) "
+ + "from " + info.component
+ + " use cancelNotification(key) instead.");
+ } else {
+ cancelNotificationFromListenerLocked(info, callingUid, callingPid,
+ pkg, tag, id, info.userid);
+ }
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -1578,19 +1358,22 @@ public class NotificationManagerService extends SystemService {
* should be used.
*
* @param token The binder for the listener, to check that the caller is allowed
+ * @param keys the notification keys to fetch, or null for all active notifications.
+ * @returns The return value will contain the notifications specified in keys, in that
+ * order, or if keys is null, all the notifications, in natural order.
*/
@Override
public StatusBarNotification[] getActiveNotificationsFromListener(
INotificationListener token, String[] keys) {
synchronized (mNotificationList) {
- final NotificationListenerInfo info = checkListenerTokenLocked(token);
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
final ArrayList<StatusBarNotification> list
= new ArrayList<StatusBarNotification>();
if (keys == null) {
final int N = mNotificationList.size();
for (int i=0; i<N; i++) {
StatusBarNotification sbn = mNotificationList.get(i).sbn;
- if (info.enabledAndUserMatches(sbn)) {
+ if (info.enabledAndUserMatches(sbn.getUserId())) {
list.add(sbn);
}
}
@@ -1598,7 +1381,7 @@ public class NotificationManagerService extends SystemService {
final int N = keys.length;
for (int i=0; i<N; i++) {
NotificationRecord r = mNotificationsByKey.get(keys[i]);
- if (r != null && info.enabledAndUserMatches(r.sbn)) {
+ if (r != null && info.enabledAndUserMatches(r.sbn.getUserId())) {
list.add(r.sbn);
}
}
@@ -1609,7 +1392,67 @@ public class NotificationManagerService extends SystemService {
@Override
public String[] getActiveNotificationKeysFromListener(INotificationListener token) {
- return NotificationManagerService.this.getActiveNotificationKeysFromListener(token);
+ return NotificationManagerService.this.getActiveNotificationKeys(token);
+ }
+
+ @Override
+ public ZenModeConfig getZenModeConfig() {
+ checkCallerIsSystem();
+ return mZenModeHelper.getConfig();
+ }
+
+ @Override
+ public boolean setZenModeConfig(ZenModeConfig config) {
+ checkCallerIsSystem();
+ return mZenModeHelper.setConfig(config);
+ }
+
+ @Override
+ public void notifyConditions(String pkg, IConditionProvider provider,
+ Condition[] conditions) {
+ final ManagedServiceInfo info = mConditionProviders.checkServiceToken(provider);
+ checkCallerIsSystemOrSameApp(pkg);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mConditionProviders.notifyConditions(pkg, info, conditions);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void requestZenModeConditions(IConditionListener callback, int relevance) {
+ enforceSystemOrSystemUI("INotificationManager.requestZenModeConditions");
+ mConditionProviders.requestZenModeConditions(callback, relevance);
+ }
+
+ @Override
+ public void setZenModeCondition(Uri conditionId) {
+ enforceSystemOrSystemUI("INotificationManager.setZenModeCondition");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mConditionProviders.setZenModeCondition(conditionId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void setAutomaticZenModeConditions(Uri[] conditionIds) {
+ enforceSystemOrSystemUI("INotificationManager.setAutomaticZenModeConditions");
+ mConditionProviders.setAutomaticZenModeConditions(conditionIds);
+ }
+
+ @Override
+ public Condition[] getAutomaticZenModeConditions() {
+ enforceSystemOrSystemUI("INotificationManager.getAutomaticZenModeConditions");
+ return mConditionProviders.getAutomaticZenModeConditions();
+ }
+
+ private void enforceSystemOrSystemUI(String message) {
+ if (isCallerSystem()) return;
+ getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+ message);
}
@Override
@@ -1626,37 +1469,26 @@ public class NotificationManagerService extends SystemService {
}
};
- private String[] getActiveNotificationKeysFromListener(INotificationListener token) {
- synchronized (mNotificationList) {
- final NotificationListenerInfo info = checkListenerTokenLocked(token);
- final ArrayList<String> keys = new ArrayList<String>();
- final int N = mNotificationList.size();
- for (int i=0; i<N; i++) {
- final StatusBarNotification sbn = mNotificationList.get(i).sbn;
- if (info.enabledAndUserMatches(sbn)) {
- keys.add(sbn.getKey());
+ private String[] getActiveNotificationKeys(INotificationListener token) {
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+ final ArrayList<String> keys = new ArrayList<String>();
+ if (info.isEnabledForCurrentProfiles()) {
+ synchronized (mNotificationList) {
+ final int N = mNotificationList.size();
+ for (int i = 0; i < N; i++) {
+ final StatusBarNotification sbn = mNotificationList.get(i).sbn;
+ if (info.enabledAndUserMatches(sbn.getUserId())) {
+ keys.add(sbn.getKey());
+ }
}
}
- return keys.toArray(new String[keys.size()]);
}
+ return keys.toArray(new String[keys.size()]);
}
void dumpImpl(PrintWriter pw) {
pw.println("Current Notification Manager state:");
- pw.println(" Listeners (" + mEnabledListenersForCurrentUser.size()
- + ") enabled for current user:");
- for (ComponentName cmpt : mEnabledListenersForCurrentUser) {
- pw.println(" " + cmpt);
- }
-
- pw.println(" Live listeners (" + mListeners.size() + "):");
- for (NotificationListenerInfo info : mListeners) {
- pw.println(" " + info.component
- + " (user " + info.userid + "): " + info.listener
- + (info.isSystem?" SYSTEM":""));
- }
-
int N;
synchronized (mToastQueue) {
@@ -1692,8 +1524,7 @@ public class NotificationManagerService extends SystemService {
pw.println(" mSoundNotification=" + mSoundNotification);
pw.println(" mVibrateNotification=" + mVibrateNotification);
- pw.println(" mDisabledNotifications=0x"
- + Integer.toHexString(mDisabledNotifications));
+ pw.println(" mDisableNotificationAlerts=" + mDisableNotificationAlerts);
pw.println(" mSystemReady=" + mSystemReady);
pw.println(" mArchive=" + mArchive.toString());
Iterator<StatusBarNotification> iter = mArchive.descendingIterator();
@@ -1706,6 +1537,17 @@ public class NotificationManagerService extends SystemService {
}
}
+ pw.println("\n Usage Stats:");
+ mUsageStats.dump(pw, " ");
+
+ pw.println("\n Zen Mode:");
+ mZenModeHelper.dump(pw, " ");
+
+ pw.println("\n Notification listeners:");
+ mListeners.dump(pw);
+
+ pw.println("\n Condition providers:");
+ mConditionProviders.dump(pw);
}
}
@@ -1714,14 +1556,14 @@ public class NotificationManagerService extends SystemService {
*/
private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
@Override
- public void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid,
+ public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid,
String tag, int id, Notification notification, int[] idReceived, int userId) {
- enqueueNotificationInternal(pkg, basePkg, callingUid, callingPid, tag, id, notification,
+ enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
idReceived, userId);
}
};
- void enqueueNotificationInternal(final String pkg, String basePkg, final int callingUid,
+ void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int[] idOut, int incomingUserId) {
if (DBG) {
@@ -1759,8 +1601,8 @@ public class NotificationManagerService extends SystemService {
// behalf of the download manager without affecting other apps.
if (!pkg.equals("com.android.providers.downloads")
|| Log.isLoggable("DownloadManager", Log.VERBOSE)) {
- EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, tag, userId,
- notification.toString());
+ EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
+ pkg, id, tag, userId, notification.toString());
}
if (pkg == null || notification == null) {
@@ -1798,26 +1640,23 @@ public class NotificationManagerService extends SystemService {
// 1. initial score: buckets of 10, around the app
int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20]
- // 2. Consult external heuristics (TBD)
-
- // 3. Apply local rules
-
- int initialScore = score;
- if (!mScorers.isEmpty()) {
- if (DBG) Slog.v(TAG, "Initial score is " + score + ".");
- for (NotificationScorer scorer : mScorers) {
+ // 2. extract ranking signals from the notification data
+ final StatusBarNotification n = new StatusBarNotification(
+ pkg, opPkg, id, tag, callingUid, callingPid, score, notification,
+ user);
+ NotificationRecord r = new NotificationRecord(n);
+ if (!mSignalExtractors.isEmpty()) {
+ for (NotificationSignalExtractor extractor : mSignalExtractors) {
try {
- score = scorer.getScore(notification, score);
+ RankingFuture future = extractor.process(r);
+ scheduleRankingReconsideration(future);
} catch (Throwable t) {
- Slog.w(TAG, "Scorer threw on .getScore.", t);
+ Slog.w(TAG, "NotificationSignalExtractor failed.", t);
}
}
- if (DBG) Slog.v(TAG, "Final score is " + score + ".");
}
- // add extra to indicate score modified by NotificationScorer
- notification.extras.putBoolean(Notification.EXTRA_SCORE_MODIFIED,
- score != initialScore);
+ // 3. Apply local rules
// blocked apps
if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) {
@@ -1828,30 +1667,29 @@ public class NotificationManagerService extends SystemService {
}
}
- if (DBG) {
- Slog.v(TAG, "Assigned score=" + score + " to " + notification);
- }
-
if (score < SCORE_DISPLAY_THRESHOLD) {
// Notification will be blocked because the score is too low.
return;
}
- // Should this notification make noise, vibe, or use the LED?
- final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD);
+ // Is this notification intercepted by zen mode?
+ final boolean intercept = mZenModeHelper.shouldIntercept(pkg, notification);
+ notification.extras.putBoolean(EXTRA_INTERCEPT, intercept);
+ // Should this notification make noise, vibe, or use the LED?
+ final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD) && !intercept;
+ if (DBG || intercept) Slog.v(TAG,
+ "pkg=" + pkg + " canInterrupt=" + canInterrupt + " intercept=" + intercept);
synchronized (mNotificationList) {
- final StatusBarNotification n = new StatusBarNotification(
- pkg, id, tag, callingUid, callingPid, score, notification, user);
- NotificationRecord r = new NotificationRecord(n);
NotificationRecord old = null;
-
int index = indexOfNotificationLocked(pkg, tag, id, userId);
if (index < 0) {
mNotificationList.add(r);
+ mUsageStats.registerPostedByApp(r);
} else {
- old = mNotificationList.remove(index);
- mNotificationList.add(index, r);
+ old = mNotificationList.get(index);
+ mNotificationList.set(index, r);
+ mUsageStats.registerUpdatedByApp(r, old);
// Make sure we don't lose the foreground service state.
if (old != null) {
notification.flags |=
@@ -1863,6 +1701,8 @@ public class NotificationManagerService extends SystemService {
}
mNotificationsByKey.put(n.getKey(), r);
+ Collections.sort(mNotificationList, mRankingComparator);
+
// Ensure if this is a foreground service that the proper additional
// flags are set.
if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) {
@@ -1904,7 +1744,7 @@ public class NotificationManagerService extends SystemService {
sendAccessibilityEvent(notification, pkg);
}
- notifyPostedLocked(r);
+ mListeners.notifyPostedLocked(r.sbn);
} else {
Slog.e(TAG, "Not posting notification with icon==0: " + notification);
if (old != null && old.statusBarKey != null) {
@@ -1915,7 +1755,7 @@ public class NotificationManagerService extends SystemService {
Binder.restoreCallingIdentity(identity);
}
- notifyRemovedLocked(r);
+ mListeners.notifyRemovedLocked(r.sbn);
}
// ATTENTION: in a future release we will bail out here
// so that we do not play sounds, show lights, etc. for invalid
@@ -1925,16 +1765,16 @@ public class NotificationManagerService extends SystemService {
}
// If we're not supposed to beep, vibrate, etc. then don't.
- if (((mDisabledNotifications
- & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
+ if (!mDisableNotificationAlerts
&& (!(old != null
&& (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
&& (r.getUserId() == UserHandle.USER_ALL ||
- (r.getUserId() == userId && r.getUserId() == currentUser))
+ (r.getUserId() == userId && r.getUserId() == currentUser) ||
+ mUserProfiles.isCurrentProfile(r.getUserId()))
&& canInterrupt
&& mSystemReady
&& mAudioManager != null) {
-
+ if (DBG) Slog.v(TAG, "Interrupting!");
// sound
// should we use the default notification sound? (indicated either by
@@ -1979,6 +1819,8 @@ public class NotificationManagerService extends SystemService {
final IRingtonePlayer player =
mAudioManager.getRingtonePlayer();
if (player != null) {
+ if (DBG) Slog.v(TAG, "Playing sound " + soundUri
+ + " on stream " + audioStreamType);
player.playAsync(soundUri, user, looping, audioStreamType);
}
} catch (RemoteException e) {
@@ -2014,21 +1856,21 @@ public class NotificationManagerService extends SystemService {
// notifying app does not have the VIBRATE permission.
long identity = Binder.clearCallingIdentity();
try {
- mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(),
+ mVibrator.vibrate(r.sbn.getUid(), r.sbn.getOpPkg(),
useDefaultVibrate ? mDefaultVibrationPattern
: mFallbackVibrationPattern,
((notification.flags & Notification.FLAG_INSISTENT) != 0)
- ? 0: -1);
+ ? 0: -1, notification.audioStreamType);
} finally {
Binder.restoreCallingIdentity(identity);
}
} else if (notification.vibrate.length > 1) {
// If you want your own vibration pattern, you need the VIBRATE
// permission
- mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(),
+ mVibrator.vibrate(r.sbn.getUid(), r.sbn.getOpPkg(),
notification.vibrate,
((notification.flags & Notification.FLAG_INSISTENT) != 0)
- ? 0: -1);
+ ? 0: -1, notification.audioStreamType);
}
}
}
@@ -2059,51 +1901,6 @@ public class NotificationManagerService extends SystemService {
idOut[0] = id;
}
- void registerListenerImpl(final INotificationListener listener,
- final ComponentName component, final int userid) {
- synchronized (mNotificationList) {
- try {
- NotificationListenerInfo info
- = new NotificationListenerInfo(listener, component, userid, true);
- listener.asBinder().linkToDeath(info, 0);
- mListeners.add(info);
- } catch (RemoteException e) {
- // already dead
- }
- }
- }
-
- /**
- * Removes a listener from the list and unbinds from its service.
- */
- void unregisterListenerImpl(final INotificationListener listener, final int userid) {
- NotificationListenerInfo info = removeListenerImpl(listener, userid);
- if (info != null && info.connection != null) {
- getContext().unbindService(info.connection);
- }
- }
-
- /**
- * Removes a listener from the list but does not unbind from the listener's service.
- *
- * @return the removed listener.
- */
- NotificationListenerInfo removeListenerImpl(
- final INotificationListener listener, final int userid) {
- NotificationListenerInfo listenerInfo = null;
- synchronized (mNotificationList) {
- final int N = mListeners.size();
- for (int i=N-1; i>=0; i--) {
- final NotificationListenerInfo info = mListeners.get(i);
- if (info.listener.asBinder() == listener.asBinder()
- && info.userid == userid) {
- listenerInfo = mListeners.remove(i);
- }
- }
- }
- return listenerInfo;
- }
-
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
@@ -2203,6 +2000,57 @@ public class NotificationManagerService extends SystemService {
}
}
+ private void scheduleRankingReconsideration(RankingFuture future) {
+ if (future != null) {
+ Message m = Message.obtain(mRankingHandler, MESSAGE_RECONSIDER_RANKING, future);
+ long delay = future.getDelay(TimeUnit.MILLISECONDS);
+ mRankingHandler.sendMessageDelayed(m, delay);
+ }
+ }
+
+ private void handleRankingReconsideration(Message message) {
+ if (!(message.obj instanceof RankingFuture)) return;
+
+ RankingFuture future = (RankingFuture) message.obj;
+ future.run();
+ try {
+ NotificationRecord record = future.get();
+ synchronized (mNotificationList) {
+ int before = mNotificationList.indexOf(record);
+ if (before != -1) {
+ Collections.sort(mNotificationList, mRankingComparator);
+ int after = mNotificationList.indexOf(record);
+
+ if (before != after) {
+ scheduleSendRankingUpdate();
+ }
+ }
+ }
+ } catch (InterruptedException e) {
+ // we're running the future explicitly, so this should never happen
+ } catch (ExecutionException e) {
+ // we're running the future explicitly, so this should never happen
+ }
+ }
+
+ private void scheduleSendRankingUpdate() {
+ mHandler.removeMessages(MESSAGE_SEND_RANKING_UPDATE);
+ Message m = Message.obtain(mHandler, MESSAGE_SEND_RANKING_UPDATE);
+ mHandler.sendMessage(m);
+ }
+
+ private void handleSendRankingUpdate() {
+ synchronized (mNotificationList) {
+ final int N = mNotificationList.size();
+ ArrayList<StatusBarNotification> sbns =
+ new ArrayList<StatusBarNotification>(N);
+ for (int i = 0; i < N; i++ ) {
+ sbns.add(mNotificationList.get(i).sbn);
+ }
+ mListeners.notifyOrderUpdateLocked(sbns);
+ }
+ }
+
private final class WorkerHandler extends Handler
{
@Override
@@ -2213,10 +2061,32 @@ public class NotificationManagerService extends SystemService {
case MESSAGE_TIMEOUT:
handleTimeout((ToastRecord)msg.obj);
break;
+ case MESSAGE_SAVE_POLICY_FILE:
+ handleSavePolicyFile();
+ break;
+ case MESSAGE_SEND_RANKING_UPDATE:
+ handleSendRankingUpdate();
+ break;
}
}
+
}
+ private final class RankingWorkerHandler extends Handler
+ {
+ public RankingWorkerHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_RECONSIDER_RANKING:
+ handleRankingReconsideration(msg);
+ break;
+ }
+ }
+ }
// Notifications
// ============================================================================
@@ -2243,7 +2113,7 @@ public class NotificationManagerService extends SystemService {
manager.sendAccessibilityEvent(event);
}
- private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete) {
+ private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason) {
// tell the app
if (sendDelete) {
if (r.getNotification().deleteIntent != null) {
@@ -2266,7 +2136,7 @@ public class NotificationManagerService extends SystemService {
Binder.restoreCallingIdentity(identity);
}
r.statusBarKey = null;
- notifyRemovedLocked(r);
+ mListeners.notifyRemovedLocked(r.sbn);
}
// sound
@@ -2302,6 +2172,26 @@ public class NotificationManagerService extends SystemService {
mLedNotification = null;
}
+ // Record usage stats
+ switch (reason) {
+ case REASON_DELEGATE_CANCEL:
+ case REASON_DELEGATE_CANCEL_ALL:
+ case REASON_LISTENER_CANCEL:
+ case REASON_LISTENER_CANCEL_ALL:
+ mUsageStats.registerDismissedByUser(r);
+ break;
+ case REASON_NOMAN_CANCEL:
+ case REASON_NOMAN_CANCEL_ALL:
+ mUsageStats.registerRemovedByApp(r);
+ break;
+ case REASON_DELEGATE_CLICK:
+ mUsageStats.registerCancelDueToClick(r);
+ break;
+ default:
+ mUsageStats.registerCancelUnknown(r);
+ break;
+ }
+
// Save it for users of getHistoricalNotifications()
mArchive.record(r.sbn);
}
@@ -2310,9 +2200,10 @@ public class NotificationManagerService extends SystemService {
* Cancels a notification ONLY if it has all of the {@code mustHaveFlags}
* and none of the {@code mustNotHaveFlags}.
*/
- void cancelNotification(final String pkg, final String tag, final int id,
+ void cancelNotification(final int callingUid, final int callingPid,
+ final String pkg, final String tag, final int id,
final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete,
- final int userId) {
+ final int userId, final int reason, final ManagedServiceInfo listener) {
// In enqueueNotificationInternal notifications are added by scheduling the
// work on the worker handler. Hence, we also schedule the cancel on this
// handler to avoid a scenario where an add notification call followed by a
@@ -2320,14 +2211,21 @@ public class NotificationManagerService extends SystemService {
mHandler.post(new Runnable() {
@Override
public void run() {
- EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, tag, userId,
- mustHaveFlags, mustNotHaveFlags);
+ EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, id, tag, userId,
+ mustHaveFlags, mustNotHaveFlags, reason,
+ listener == null ? null : listener.component.toShortString());
synchronized (mNotificationList) {
int index = indexOfNotificationLocked(pkg, tag, id, userId);
if (index >= 0) {
NotificationRecord r = mNotificationList.get(index);
+ // Ideally we'd do this in the caller of this method. However, that would
+ // require the caller to also find the notification.
+ if (reason == REASON_DELEGATE_CLICK) {
+ mUsageStats.registerClickedByUser(r);
+ }
+
if ((r.getNotification().flags & mustHaveFlags) != mustHaveFlags) {
return;
}
@@ -2338,7 +2236,7 @@ public class NotificationManagerService extends SystemService {
mNotificationList.remove(index);
mNotificationsByKey.remove(r.sbn.getKey());
- cancelNotificationLocked(r, sendDelete);
+ cancelNotificationLocked(r, sendDelete, reason);
updateLightsLocked();
}
}
@@ -2361,13 +2259,25 @@ public class NotificationManagerService extends SystemService {
}
/**
+ * Determine whether the userId applies to the notification in question, either because
+ * they match exactly, or one of them is USER_ALL (which is treated as a wildcard) or
+ * because it matches one of the users profiles.
+ */
+ private boolean notificationMatchesCurrentProfiles(NotificationRecord r, int userId) {
+ return notificationMatchesUserId(r, userId)
+ || mUserProfiles.isCurrentProfile(r.getUserId());
+ }
+
+ /**
* Cancels all notifications from a given package that have all of the
* {@code mustHaveFlags}.
*/
- boolean cancelAllNotificationsInt(String pkg, int mustHaveFlags,
- int mustNotHaveFlags, boolean doit, int userId) {
- EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL_ALL, pkg, userId,
- mustHaveFlags, mustNotHaveFlags);
+ boolean cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, int mustHaveFlags,
+ int mustNotHaveFlags, boolean doit, int userId, int reason,
+ ManagedServiceInfo listener) {
+ EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
+ pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
+ listener == null ? null : listener.component.toShortString());
synchronized (mNotificationList) {
final int N = mNotificationList.size();
@@ -2396,7 +2306,7 @@ public class NotificationManagerService extends SystemService {
}
mNotificationList.remove(i);
mNotificationsByKey.remove(r.sbn.getKey());
- cancelNotificationLocked(r, false);
+ cancelNotificationLocked(r, false, reason);
}
if (canceledSomething) {
updateLightsLocked();
@@ -2405,61 +2315,32 @@ public class NotificationManagerService extends SystemService {
}
}
+ void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
+ ManagedServiceInfo listener, boolean includeCurrentProfiles) {
+ EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
+ null, userId, 0, 0, reason,
+ listener == null ? null : listener.component.toShortString());
-
- // Return true if the UID is a system or phone UID and therefore should not have
- // any notifications or toasts blocked.
- boolean isUidSystem(int uid) {
- final int appid = UserHandle.getAppId(uid);
- return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
- }
-
- // same as isUidSystem(int, int) for the Binder caller's UID.
- boolean isCallerSystem() {
- return isUidSystem(Binder.getCallingUid());
- }
-
- void checkCallerIsSystem() {
- if (isCallerSystem()) {
- return;
- }
- throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid());
- }
-
- void checkCallerIsSystemOrSameApp(String pkg) {
- if (isCallerSystem()) {
- return;
- }
- final int uid = Binder.getCallingUid();
- try {
- ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
- pkg, 0, UserHandle.getCallingUserId());
- if (!UserHandle.isSameApp(ai.uid, uid)) {
- throw new SecurityException("Calling uid " + uid + " gave package"
- + pkg + " which is owned by uid " + ai.uid);
- }
- } catch (RemoteException re) {
- throw new SecurityException("Unknown package " + pkg + "\n" + re);
- }
- }
-
- void cancelAllLocked(int userId) {
final int N = mNotificationList.size();
for (int i=N-1; i>=0; i--) {
NotificationRecord r = mNotificationList.get(i);
-
- if (!notificationMatchesUserId(r, userId)) {
- continue;
+ if (includeCurrentProfiles) {
+ if (!notificationMatchesCurrentProfiles(r, userId)) {
+ continue;
+ }
+ } else {
+ if (!notificationMatchesUserId(r, userId)) {
+ continue;
+ }
}
if ((r.getFlags() & (Notification.FLAG_ONGOING_EVENT
| Notification.FLAG_NO_CLEAR)) == 0) {
mNotificationList.remove(i);
mNotificationsByKey.remove(r.sbn.getKey());
- cancelNotificationLocked(r, true);
+ cancelNotificationLocked(r, true, reason);
}
}
-
updateLightsLocked();
}
@@ -2527,4 +2408,181 @@ public class NotificationManagerService extends SystemService {
updateLightsLocked();
}
}
+
+ private static boolean isUidSystem(int uid) {
+ final int appid = UserHandle.getAppId(uid);
+ return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
+ }
+
+ private static boolean isCallerSystem() {
+ return isUidSystem(Binder.getCallingUid());
+ }
+
+ private static void checkCallerIsSystem() {
+ if (isCallerSystem()) {
+ return;
+ }
+ throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid());
+ }
+
+ private static void checkCallerIsSystemOrSameApp(String pkg) {
+ if (isCallerSystem()) {
+ return;
+ }
+ final int uid = Binder.getCallingUid();
+ try {
+ ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
+ pkg, 0, UserHandle.getCallingUserId());
+ if (!UserHandle.isSameApp(ai.uid, uid)) {
+ throw new SecurityException("Calling uid " + uid + " gave package"
+ + pkg + " which is owned by uid " + ai.uid);
+ }
+ } catch (RemoteException re) {
+ throw new SecurityException("Unknown package " + pkg + "\n" + re);
+ }
+ }
+
+ public class NotificationListeners extends ManagedServices {
+
+ public NotificationListeners() {
+ super(getContext(), mHandler, mNotificationList, mUserProfiles);
+ }
+
+ @Override
+ protected Config getConfig() {
+ Config c = new Config();
+ c.caption = "notification listener";
+ c.serviceInterface = NotificationListenerService.SERVICE_INTERFACE;
+ c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
+ c.bindPermission = android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE;
+ c.settingsAction = Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
+ c.clientLabel = R.string.notification_listener_binding_label;
+ return c;
+ }
+
+ @Override
+ protected IInterface asInterface(IBinder binder) {
+ return INotificationListener.Stub.asInterface(binder);
+ }
+
+ @Override
+ public void onServiceAdded(ManagedServiceInfo info) {
+ final INotificationListener listener = (INotificationListener) info.service;
+ final String[] keys = getActiveNotificationKeys(listener);
+ try {
+ listener.onListenerConnected(new NotificationOrderUpdate(keys));
+ } catch (RemoteException e) {
+ // we tried
+ }
+ }
+
+ /**
+ * asynchronously notify all listeners about a new notification
+ */
+ public void notifyPostedLocked(StatusBarNotification sbn) {
+ // make a copy in case changes are made to the underlying Notification object
+ final StatusBarNotification sbnClone = sbn.clone();
+ for (final ManagedServiceInfo info : mServices) {
+ if (info.isEnabledForCurrentProfiles()) {
+ final INotificationListener listener = (INotificationListener) info.service;
+ final String[] keys = getActiveNotificationKeys(listener);
+ if (keys.length > 0) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ notifyPostedIfUserMatch(info, sbnClone, keys);
+ }
+ });
+ }
+ }
+ }
+ }
+
+ /**
+ * asynchronously notify all listeners about a removed notification
+ */
+ public void notifyRemovedLocked(StatusBarNotification sbn) {
+ // make a copy in case changes are made to the underlying Notification object
+ // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the
+ // notification
+ final StatusBarNotification sbnLight = sbn.cloneLight();
+ for (final ManagedServiceInfo info : mServices) {
+ if (info.isEnabledForCurrentProfiles()) {
+ final INotificationListener listener = (INotificationListener) info.service;
+ final String[] keys = getActiveNotificationKeys(listener);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ notifyRemovedIfUserMatch(info, sbnLight, keys);
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * asynchronously notify all listeners about a reordering of notifications
+ * @param sbns an array of {@link StatusBarNotification}s to consider. This code
+ * must not rely on mutable members of these objects, such as the
+ * {@link Notification}.
+ */
+ public void notifyOrderUpdateLocked(final ArrayList<StatusBarNotification> sbns) {
+ for (final ManagedServiceInfo serviceInfo : mServices) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ notifyOrderUpdateIfUserMatch(serviceInfo, sbns);
+ }
+ });
+ }
+ }
+
+ private void notifyPostedIfUserMatch(final ManagedServiceInfo info,
+ final StatusBarNotification sbn, String[] keys) {
+ if (!info.enabledAndUserMatches(sbn.getUserId())) {
+ return;
+ }
+ final INotificationListener listener = (INotificationListener)info.service;
+ try {
+ listener.onNotificationPosted(sbn, new NotificationOrderUpdate(keys));
+ } catch (RemoteException ex) {
+ Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
+ }
+ }
+
+ private void notifyRemovedIfUserMatch(ManagedServiceInfo info, StatusBarNotification sbn,
+ String[] keys) {
+ if (!info.enabledAndUserMatches(sbn.getUserId())) {
+ return;
+ }
+ final INotificationListener listener = (INotificationListener)info.service;
+ try {
+ listener.onNotificationRemoved(sbn, new NotificationOrderUpdate(keys));
+ } catch (RemoteException ex) {
+ Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
+ }
+ }
+
+ /**
+ * @param sbns an array of {@link StatusBarNotification}s to consider. This code
+ * must not rely on mutable members of these objects, such as the
+ * {@link Notification}.
+ */
+ public void notifyOrderUpdateIfUserMatch(ManagedServiceInfo info,
+ ArrayList<StatusBarNotification> sbns) {
+ ArrayList<String> keys = new ArrayList<String>(sbns.size());
+ for (StatusBarNotification sbn: sbns) {
+ if (info.enabledAndUserMatches(sbn.getUserId())) {
+ keys.add(sbn.getKey());
+ }
+ }
+ final INotificationListener listener = (INotificationListener)info.service;
+ try {
+ listener.onNotificationOrderUpdate(
+ new NotificationOrderUpdate(keys.toArray(new String[keys.size()])));
+ } catch (RemoteException ex) {
+ Log.e(TAG, "unable to notify listener (ranking update): " + listener, ex);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
new file mode 100644
index 0000000..a41fdfe
--- /dev/null
+++ b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
@@ -0,0 +1,41 @@
+/*
+* Copyright (C) 2014 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.android.server.notification;
+
+import android.content.Context;
+
+/**
+ * Extracts signals that will be useful to the {@link NotificationComparator} and caches them
+ * on the {@link NotificationManagerService.NotificationRecord} object. These annotations will
+ * not be passed on to {@link android.service.notification.NotificationListenerService}s.
+ */
+public interface NotificationSignalExtractor {
+
+ /** One-time initialization. */
+ public void initialize(Context context);
+
+ /**
+ * Called once per notification that is posted or updated.
+ *
+ * @return null if the work is done, or a future if there is more to do. The
+ * {@link RankingFuture} will be run on a worker thread, and if notifications are re-ordered
+ * by that execution, the {@link NotificationManagerService} may send order update
+ * events to the {@link android.service.notification.NotificationListenerService}s.
+ */
+ public RankingFuture process(NotificationManagerService.NotificationRecord notification);
+
+}
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
new file mode 100644
index 0000000..d81a25e
--- /dev/null
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -0,0 +1,595 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.notification;
+
+import com.android.server.notification.NotificationManagerService.NotificationRecord;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.SystemClock;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Keeps track of notification activity, display, and user interaction.
+ *
+ * <p>This class receives signals from NoMan and keeps running stats of
+ * notification usage. Some metrics are updated as events occur. Others, namely
+ * those involving durations, are updated as the notification is canceled.</p>
+ *
+ * <p>This class is thread-safe.</p>
+ *
+ * {@hide}
+ */
+public class NotificationUsageStats {
+ // Guarded by synchronized(this).
+ private final Map<String, AggregatedStats> mStats = new HashMap<String, AggregatedStats>();
+ private final SQLiteLog mSQLiteLog;
+
+ public NotificationUsageStats(Context context) {
+ mSQLiteLog = new SQLiteLog(context);
+ }
+
+ /**
+ * Called when a notification has been posted.
+ */
+ public synchronized void registerPostedByApp(NotificationRecord notification) {
+ notification.stats = new SingleNotificationStats();
+ notification.stats.posttimeElapsedMs = SystemClock.elapsedRealtime();
+ for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
+ stats.numPostedByApp++;
+ }
+ mSQLiteLog.logPosted(notification);
+ }
+
+ /**
+ * Called when a notification has been updated.
+ */
+ public void registerUpdatedByApp(NotificationRecord notification, NotificationRecord old) {
+ notification.stats = old.stats;
+ for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
+ stats.numUpdatedByApp++;
+ }
+ }
+
+ /**
+ * Called when the originating app removed the notification programmatically.
+ */
+ public synchronized void registerRemovedByApp(NotificationRecord notification) {
+ notification.stats.onRemoved();
+ for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
+ stats.numRemovedByApp++;
+ stats.collect(notification.stats);
+ }
+ mSQLiteLog.logRemoved(notification);
+ }
+
+ /**
+ * Called when the user dismissed the notification via the UI.
+ */
+ public synchronized void registerDismissedByUser(NotificationRecord notification) {
+ notification.stats.onDismiss();
+ for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
+ stats.numDismissedByUser++;
+ stats.collect(notification.stats);
+ }
+ mSQLiteLog.logDismissed(notification);
+ }
+
+ /**
+ * Called when the user clicked the notification in the UI.
+ */
+ public synchronized void registerClickedByUser(NotificationRecord notification) {
+ notification.stats.onClick();
+ for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
+ stats.numClickedByUser++;
+ }
+ mSQLiteLog.logClicked(notification);
+ }
+
+ /**
+ * Called when the notification is canceled because the user clicked it.
+ *
+ * <p>Called after {@link #registerClickedByUser(NotificationRecord)}.</p>
+ */
+ public synchronized void registerCancelDueToClick(NotificationRecord notification) {
+ notification.stats.onCancel();
+ for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
+ stats.collect(notification.stats);
+ }
+ }
+
+ /**
+ * Called when the notification is canceled due to unknown reasons.
+ *
+ * <p>Called for notifications of apps being uninstalled, for example.</p>
+ */
+ public synchronized void registerCancelUnknown(NotificationRecord notification) {
+ notification.stats.onCancel();
+ for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
+ stats.collect(notification.stats);
+ }
+ }
+
+ // Locked by this.
+ private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) {
+ StatusBarNotification n = record.sbn;
+
+ String user = String.valueOf(n.getUserId());
+ String userPackage = user + ":" + n.getPackageName();
+
+ // TODO: Use pool of arrays.
+ return new AggregatedStats[] {
+ getOrCreateAggregatedStatsLocked(user),
+ getOrCreateAggregatedStatsLocked(userPackage),
+ getOrCreateAggregatedStatsLocked(n.getKey()),
+ };
+ }
+
+ // Locked by this.
+ private AggregatedStats getOrCreateAggregatedStatsLocked(String key) {
+ AggregatedStats result = mStats.get(key);
+ if (result == null) {
+ result = new AggregatedStats(key);
+ mStats.put(key, result);
+ }
+ return result;
+ }
+
+ public synchronized void dump(PrintWriter pw, String indent) {
+ for (AggregatedStats as : mStats.values()) {
+ as.dump(pw, indent);
+ }
+ mSQLiteLog.dump(pw, indent);
+ }
+
+ /**
+ * Aggregated notification stats.
+ */
+ private static class AggregatedStats {
+ public final String key;
+
+ // ---- Updated as the respective events occur.
+ public int numPostedByApp;
+ public int numUpdatedByApp;
+ public int numRemovedByApp;
+ public int numClickedByUser;
+ public int numDismissedByUser;
+
+ // ---- Updated when a notification is canceled.
+ public final Aggregate posttimeMs = new Aggregate();
+ public final Aggregate posttimeToDismissMs = new Aggregate();
+ public final Aggregate posttimeToFirstClickMs = new Aggregate();
+ public final Aggregate airtimeCount = new Aggregate();
+ public final Aggregate airtimeMs = new Aggregate();
+ public final Aggregate posttimeToFirstAirtimeMs = new Aggregate();
+
+ public AggregatedStats(String key) {
+ this.key = key;
+ }
+
+ public void collect(SingleNotificationStats singleNotificationStats) {
+ posttimeMs.addSample(
+ SystemClock.elapsedRealtime() - singleNotificationStats.posttimeElapsedMs);
+ if (singleNotificationStats.posttimeToDismissMs >= 0) {
+ posttimeToDismissMs.addSample(singleNotificationStats.posttimeToDismissMs);
+ }
+ if (singleNotificationStats.posttimeToFirstClickMs >= 0) {
+ posttimeToFirstClickMs.addSample(singleNotificationStats.posttimeToFirstClickMs);
+ }
+ airtimeCount.addSample(singleNotificationStats.airtimeCount);
+ if (singleNotificationStats.airtimeMs >= 0) {
+ airtimeMs.addSample(singleNotificationStats.airtimeMs);
+ }
+ if (singleNotificationStats.posttimeToFirstAirtimeMs >= 0) {
+ posttimeToFirstAirtimeMs.addSample(
+ singleNotificationStats.posttimeToFirstAirtimeMs);
+ }
+ }
+
+ public void dump(PrintWriter pw, String indent) {
+ pw.println(toStringWithIndent(indent));
+ }
+
+ @Override
+ public String toString() {
+ return toStringWithIndent("");
+ }
+
+ private String toStringWithIndent(String indent) {
+ return indent + "AggregatedStats{\n" +
+ indent + " key='" + key + "',\n" +
+ indent + " numPostedByApp=" + numPostedByApp + ",\n" +
+ indent + " numUpdatedByApp=" + numUpdatedByApp + ",\n" +
+ indent + " numRemovedByApp=" + numRemovedByApp + ",\n" +
+ indent + " numClickedByUser=" + numClickedByUser + ",\n" +
+ indent + " numDismissedByUser=" + numDismissedByUser + ",\n" +
+ indent + " posttimeMs=" + posttimeMs + ",\n" +
+ indent + " posttimeToDismissMs=" + posttimeToDismissMs + ",\n" +
+ indent + " posttimeToFirstClickMs=" + posttimeToFirstClickMs + ",\n" +
+ indent + " airtimeCount=" + airtimeCount + ",\n" +
+ indent + " airtimeMs=" + airtimeMs + ",\n" +
+ indent + " posttimeToFirstAirtimeMs=" + posttimeToFirstAirtimeMs + ",\n" +
+ indent + "}";
+ }
+ }
+
+ /**
+ * Tracks usage of an individual notification that is currently active.
+ */
+ public static class SingleNotificationStats {
+ /** SystemClock.elapsedRealtime() when the notification was posted. */
+ public long posttimeElapsedMs = -1;
+ /** Elapsed time since the notification was posted until it was first clicked, or -1. */
+ public long posttimeToFirstClickMs = -1;
+ /** Elpased time since the notification was posted until it was dismissed by the user. */
+ public long posttimeToDismissMs = -1;
+ /** Number of times the notification has been made visible. */
+ public long airtimeCount = 0;
+ /** Time in ms between the notification was posted and first shown; -1 if never shown. */
+ public long posttimeToFirstAirtimeMs = -1;
+ /**
+ * If currently visible, SystemClock.elapsedRealtime() when the notification was made
+ * visible; -1 otherwise.
+ */
+ public long currentAirtimeStartElapsedMs = -1;
+ /** Accumulated visible time. */
+ public long airtimeMs = 0;
+
+ public long getCurrentPosttimeMs() {
+ if (posttimeElapsedMs < 0) {
+ return 0;
+ }
+ return SystemClock.elapsedRealtime() - posttimeElapsedMs;
+ }
+
+ public long getCurrentAirtimeMs() {
+ long result = airtimeMs;
+ // Add incomplete airtime if currently shown.
+ if (currentAirtimeStartElapsedMs >= 0) {
+ result+= (SystemClock.elapsedRealtime() - currentAirtimeStartElapsedMs);
+ }
+ return result;
+ }
+
+ /**
+ * Called when the user clicked the notification.
+ */
+ public void onClick() {
+ if (posttimeToFirstClickMs < 0) {
+ posttimeToFirstClickMs = SystemClock.elapsedRealtime() - posttimeElapsedMs;
+ }
+ }
+
+ /**
+ * Called when the user removed the notification.
+ */
+ public void onDismiss() {
+ if (posttimeToDismissMs < 0) {
+ posttimeToDismissMs = SystemClock.elapsedRealtime() - posttimeElapsedMs;
+ }
+ finish();
+ }
+
+ public void onCancel() {
+ finish();
+ }
+
+ public void onRemoved() {
+ finish();
+ }
+
+ public void onVisibilityChanged(boolean visible) {
+ long elapsedNowMs = SystemClock.elapsedRealtime();
+ if (visible) {
+ if (currentAirtimeStartElapsedMs < 0) {
+ airtimeCount++;
+ currentAirtimeStartElapsedMs = elapsedNowMs;
+ }
+ if (posttimeToFirstAirtimeMs < 0) {
+ posttimeToFirstAirtimeMs = elapsedNowMs - posttimeElapsedMs;
+ }
+ } else {
+ if (currentAirtimeStartElapsedMs >= 0) {
+ airtimeMs += (elapsedNowMs - currentAirtimeStartElapsedMs);
+ currentAirtimeStartElapsedMs = -1;
+ }
+ }
+ }
+
+ /** The notification is leaving the system. Finalize. */
+ public void finish() {
+ onVisibilityChanged(false);
+ }
+
+ @Override
+ public String toString() {
+ return "SingleNotificationStats{" +
+ "posttimeElapsedMs=" + posttimeElapsedMs +
+ ", posttimeToFirstClickMs=" + posttimeToFirstClickMs +
+ ", posttimeToDismissMs=" + posttimeToDismissMs +
+ ", airtimeCount=" + airtimeCount +
+ ", airtimeMs=" + airtimeMs +
+ ", currentAirtimeStartElapsedMs=" + currentAirtimeStartElapsedMs +
+ '}';
+ }
+ }
+
+ /**
+ * Aggregates long samples to sum and averages.
+ */
+ public static class Aggregate {
+ long numSamples;
+ double avg;
+ double sum2;
+ double var;
+
+ public void addSample(long sample) {
+ // Welford's "Method for Calculating Corrected Sums of Squares"
+ // http://www.jstor.org/stable/1266577?seq=2
+ numSamples++;
+ final double n = numSamples;
+ final double delta = sample - avg;
+ avg += (1.0 / n) * delta;
+ sum2 += ((n - 1) / n) * delta * delta;
+ final double divisor = numSamples == 1 ? 1.0 : n - 1.0;
+ var = sum2 / divisor;
+ }
+
+ @Override
+ public String toString() {
+ return "Aggregate{" +
+ "numSamples=" + numSamples +
+ ", avg=" + avg +
+ ", var=" + var +
+ '}';
+ }
+ }
+
+ private static class SQLiteLog {
+ private static final String TAG = "NotificationSQLiteLog";
+
+ // Message types passed to the background handler.
+ private static final int MSG_POST = 1;
+ private static final int MSG_CLICK = 2;
+ private static final int MSG_REMOVE = 3;
+ private static final int MSG_DISMISS = 4;
+
+ private static final String DB_NAME = "notification_log.db";
+ private static final int DB_VERSION = 2;
+
+ /** Age in ms after which events are pruned from the DB. */
+ private static final long HORIZON_MS = 7 * 24 * 60 * 60 * 1000L; // 1 week
+ /** Delay between pruning the DB. Used to throttle pruning. */
+ private static final long PRUNE_MIN_DELAY_MS = 6 * 60 * 60 * 1000L; // 6 hours
+ /** Mininum number of writes between pruning the DB. Used to throttle pruning. */
+ private static final long PRUNE_MIN_WRITES = 1024;
+
+ // Table 'log'
+ private static final String TAB_LOG = "log";
+ private static final String COL_EVENT_USER_ID = "event_user_id";
+ private static final String COL_EVENT_TYPE = "event_type";
+ private static final String COL_EVENT_TIME = "event_time_ms";
+ private static final String COL_KEY = "key";
+ private static final String COL_PKG = "pkg";
+ private static final String COL_NOTIFICATION_ID = "nid";
+ private static final String COL_TAG = "tag";
+ private static final String COL_WHEN_MS = "when_ms";
+ private static final String COL_DEFAULTS = "defaults";
+ private static final String COL_FLAGS = "flags";
+ private static final String COL_PRIORITY = "priority";
+ private static final String COL_CATEGORY = "category";
+ private static final String COL_ACTION_COUNT = "action_count";
+ private static final String COL_POSTTIME_MS = "posttime_ms";
+ private static final String COL_AIRTIME_MS = "airtime_ms";
+
+ private static final int EVENT_TYPE_POST = 1;
+ private static final int EVENT_TYPE_CLICK = 2;
+ private static final int EVENT_TYPE_REMOVE = 3;
+ private static final int EVENT_TYPE_DISMISS = 4;
+
+ private static long sLastPruneMs;
+ private static long sNumWrites;
+
+ private final SQLiteOpenHelper mHelper;
+ private final Handler mWriteHandler;
+
+ private static final long DAY_MS = 24 * 60 * 60 * 1000;
+
+ public SQLiteLog(Context context) {
+ HandlerThread backgroundThread = new HandlerThread("notification-sqlite-log",
+ android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ backgroundThread.start();
+ mWriteHandler = new Handler(backgroundThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ NotificationRecord r = (NotificationRecord) msg.obj;
+ long nowMs = System.currentTimeMillis();
+ switch (msg.what) {
+ case MSG_POST:
+ writeEvent(r.sbn.getPostTime(), EVENT_TYPE_POST, r);
+ break;
+ case MSG_CLICK:
+ writeEvent(nowMs, EVENT_TYPE_CLICK, r);
+ break;
+ case MSG_REMOVE:
+ writeEvent(nowMs, EVENT_TYPE_REMOVE, r);
+ break;
+ case MSG_DISMISS:
+ writeEvent(nowMs, EVENT_TYPE_DISMISS, r);
+ break;
+ default:
+ Log.wtf(TAG, "Unknown message type: " + msg.what);
+ break;
+ }
+ }
+ };
+ mHelper = new SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TAB_LOG + " (" +
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
+ COL_EVENT_USER_ID + " INT," +
+ COL_EVENT_TYPE + " INT," +
+ COL_EVENT_TIME + " INT," +
+ COL_KEY + " TEXT," +
+ COL_PKG + " TEXT," +
+ COL_NOTIFICATION_ID + " INT," +
+ COL_TAG + " TEXT," +
+ COL_WHEN_MS + " INT," +
+ COL_DEFAULTS + " INT," +
+ COL_FLAGS + " INT," +
+ COL_PRIORITY + " INT," +
+ COL_CATEGORY + " TEXT," +
+ COL_ACTION_COUNT + " INT," +
+ COL_POSTTIME_MS + " INT," +
+ COL_AIRTIME_MS + " INT" +
+ ")");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ switch (oldVersion) {
+ case 1:
+ // Add COL_POSTTIME_MS, COL_AIRTIME_MS columns,
+ db.execSQL("ALTER TABLE " + TAB_LOG + " ADD COLUMN " +
+ COL_POSTTIME_MS + " INT");
+ db.execSQL("ALTER TABLE " + TAB_LOG + " ADD COLUMN " +
+ COL_AIRTIME_MS + " INT");
+ }
+ }
+ };
+ }
+
+ public void logPosted(NotificationRecord notification) {
+ mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_POST, notification));
+ }
+
+ public void logClicked(NotificationRecord notification) {
+ mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_CLICK, notification));
+ }
+
+ public void logRemoved(NotificationRecord notification) {
+ mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_REMOVE, notification));
+ }
+
+ public void logDismissed(NotificationRecord notification) {
+ mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_DISMISS, notification));
+ }
+
+ public void printPostFrequencies(PrintWriter pw, String indent) {
+ SQLiteDatabase db = mHelper.getReadableDatabase();
+ long nowMs = System.currentTimeMillis();
+ String q = "SELECT " +
+ COL_EVENT_USER_ID + ", " +
+ COL_PKG + ", " +
+ // Bucket by day by looking at 'floor((nowMs - eventTimeMs) / dayMs)'
+ "CAST(((" + nowMs + " - " + COL_EVENT_TIME + ") / " + DAY_MS + ") AS int) " +
+ "AS day, " +
+ "COUNT(*) AS cnt " +
+ "FROM " + TAB_LOG + " " +
+ "WHERE " +
+ COL_EVENT_TYPE + "=" + EVENT_TYPE_POST + " " +
+ "GROUP BY " + COL_EVENT_USER_ID + ", day, " + COL_PKG;
+ Cursor cursor = db.rawQuery(q, null);
+ try {
+ for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
+ int userId = cursor.getInt(0);
+ String pkg = cursor.getString(1);
+ int day = cursor.getInt(2);
+ int count = cursor.getInt(3);
+ pw.println(indent + "post_frequency{user_id=" + userId + ",pkg=" + pkg +
+ ",day=" + day + ",count=" + count + "}");
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private void writeEvent(long eventTimeMs, int eventType, NotificationRecord r) {
+ ContentValues cv = new ContentValues();
+ cv.put(COL_EVENT_USER_ID, r.sbn.getUser().getIdentifier());
+ cv.put(COL_EVENT_TIME, eventTimeMs);
+ cv.put(COL_EVENT_TYPE, eventType);
+ putNotificationIdentifiers(r, cv);
+ if (eventType == EVENT_TYPE_POST) {
+ putNotificationDetails(r, cv);
+ } else {
+ putPosttimeAirtime(r, cv);
+ }
+ SQLiteDatabase db = mHelper.getWritableDatabase();
+ if (db.insert(TAB_LOG, null, cv) < 0) {
+ Log.wtf(TAG, "Error while trying to insert values: " + cv);
+ }
+ sNumWrites++;
+ pruneIfNecessary(db);
+ }
+
+ private void pruneIfNecessary(SQLiteDatabase db) {
+ // Prune if we haven't in a while.
+ long nowMs = System.currentTimeMillis();
+ if (sNumWrites > PRUNE_MIN_WRITES ||
+ nowMs - sLastPruneMs > PRUNE_MIN_DELAY_MS) {
+ sNumWrites = 0;
+ sLastPruneMs = nowMs;
+ long horizonStartMs = nowMs - HORIZON_MS;
+ int deletedRows = db.delete(TAB_LOG, COL_EVENT_TIME + " < ?",
+ new String[] { String.valueOf(horizonStartMs) });
+ Log.d(TAG, "Pruned event entries: " + deletedRows);
+ }
+ }
+
+ private static void putNotificationIdentifiers(NotificationRecord r, ContentValues outCv) {
+ outCv.put(COL_KEY, r.sbn.getKey());
+ outCv.put(COL_PKG, r.sbn.getPackageName());
+ }
+
+ private static void putNotificationDetails(NotificationRecord r, ContentValues outCv) {
+ outCv.put(COL_NOTIFICATION_ID, r.sbn.getId());
+ if (r.sbn.getTag() != null) {
+ outCv.put(COL_TAG, r.sbn.getTag());
+ }
+ outCv.put(COL_WHEN_MS, r.sbn.getPostTime());
+ outCv.put(COL_FLAGS, r.getNotification().flags);
+ outCv.put(COL_PRIORITY, r.getNotification().priority);
+ if (r.getNotification().category != null) {
+ outCv.put(COL_CATEGORY, r.getNotification().category);
+ }
+ outCv.put(COL_ACTION_COUNT, r.getNotification().actions != null ?
+ r.getNotification().actions.length : 0);
+ }
+
+ private static void putPosttimeAirtime(NotificationRecord r, ContentValues outCv) {
+ outCv.put(COL_POSTTIME_MS, r.stats.getCurrentPosttimeMs());
+ outCv.put(COL_AIRTIME_MS, r.stats.getCurrentAirtimeMs());
+ }
+
+ public void dump(PrintWriter pw, String indent) {
+ printPostFrequencies(pw, indent);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/notification/RankingFuture.java b/services/core/java/com/android/server/notification/RankingFuture.java
new file mode 100644
index 0000000..d711d02
--- /dev/null
+++ b/services/core/java/com/android/server/notification/RankingFuture.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.notification;
+
+import java.util.concurrent.Delayed;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public abstract class RankingFuture
+ implements ScheduledFuture<NotificationManagerService.NotificationRecord> {
+ private static final long IMMEDIATE = 0l;
+
+ private static final int START = 0;
+ private static final int RUNNING = 1;
+ private static final int DONE = 2;
+ private static final int CANCELLED = 3;
+
+ private int mState;
+ private long mDelay;
+ protected NotificationManagerService.NotificationRecord mRecord;
+
+ public RankingFuture(NotificationManagerService.NotificationRecord record) {
+ this(record, IMMEDIATE);
+ }
+
+ public RankingFuture(NotificationManagerService.NotificationRecord record, long delay) {
+ mDelay = delay;
+ mRecord = record;
+ mState = START;
+ }
+
+ public void run() {
+ if (mState == START) {
+ mState = RUNNING;
+
+ work();
+
+ mState = DONE;
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public long getDelay(TimeUnit unit) {
+ return unit.convert(mDelay, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public int compareTo(Delayed another) {
+ return Long.compare(getDelay(TimeUnit.MILLISECONDS),
+ another.getDelay(TimeUnit.MILLISECONDS));
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ if (mState == START) { // can't cancel if running or done
+ mState = CANCELLED;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return mState == CANCELLED;
+ }
+
+ @Override
+ public boolean isDone() {
+ return mState == DONE;
+ }
+
+ @Override
+ public NotificationManagerService.NotificationRecord get()
+ throws InterruptedException, ExecutionException {
+ while (!isDone()) {
+ synchronized (this) {
+ this.wait();
+ }
+ }
+ return mRecord;
+ }
+
+ @Override
+ public NotificationManagerService.NotificationRecord get(long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ long timeoutMillis = unit.convert(timeout, TimeUnit.MILLISECONDS);
+ long start = System.currentTimeMillis();
+ long now = System.currentTimeMillis();
+ while (!isDone() && (now - start) < timeoutMillis) {
+ try {
+ wait(timeoutMillis - (now - start));
+ } catch (InterruptedException e) {
+ now = System.currentTimeMillis();
+ }
+ }
+ return mRecord;
+ }
+
+ public abstract void work();
+}
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
new file mode 100644
index 0000000..b5c2730
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -0,0 +1,304 @@
+/*
+* Copyright (C) 2014 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.android.server.notification;
+
+import android.app.Notification;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.LruCache;
+import android.util.Slog;
+
+import com.android.server.notification.NotificationManagerService.NotificationRecord;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+/**
+ * This {@link NotificationSignalExtractor} attempts to validate
+ * people references. Also elevates the priority of real people.
+ */
+public class ValidateNotificationPeople implements NotificationSignalExtractor {
+ private static final String TAG = "ValidateNotificationPeople";
+ private static final boolean INFO = true;
+ private static final boolean DEBUG = false;
+
+ private static final boolean ENABLE_PEOPLE_VALIDATOR = true;
+ private static final String SETTING_ENABLE_PEOPLE_VALIDATOR =
+ "validate_notification_people_enabled";
+ private static final String[] LOOKUP_PROJECTION = { Contacts._ID, Contacts.STARRED };
+ private static final int MAX_PEOPLE = 10;
+ private static final int PEOPLE_CACHE_SIZE = 200;
+
+ private static final float NONE = 0f;
+ private static final float VALID_CONTACT = 0.5f;
+ private static final float STARRED_CONTACT = 1f;
+
+ protected boolean mEnabled;
+ private Context mContext;
+
+ // maps raw person handle to resolved person object
+ private LruCache<String, LookupResult> mPeopleCache;
+
+ private RankingFuture validatePeople(NotificationRecord record) {
+ float affinity = NONE;
+ Bundle extras = record.getNotification().extras;
+ if (extras == null) {
+ return null;
+ }
+
+ final String[] people = getExtraPeople(extras);
+ if (people == null || people.length == 0) {
+ return null;
+ }
+
+ if (INFO) Slog.i(TAG, "Validating: " + record.sbn.getKey());
+ final LinkedList<String> pendingLookups = new LinkedList<String>();
+ for (int personIdx = 0; personIdx < people.length && personIdx < MAX_PEOPLE; personIdx++) {
+ final String handle = people[personIdx];
+ if (TextUtils.isEmpty(handle)) continue;
+
+ synchronized (mPeopleCache) {
+ LookupResult lookupResult = mPeopleCache.get(handle);
+ if (lookupResult == null || lookupResult.isExpired()) {
+ pendingLookups.add(handle);
+ } else {
+ if (DEBUG) Slog.d(TAG, "using cached lookupResult: " + lookupResult.mId);
+ }
+ if (lookupResult != null) {
+ affinity = Math.max(affinity, lookupResult.getAffinity());
+ }
+ }
+ }
+
+ // record the best available data, so far:
+ record.setContactAffinity(affinity);
+
+ if (pendingLookups.isEmpty()) {
+ if (INFO) Slog.i(TAG, "final affinity: " + affinity);
+ return null;
+ }
+
+ if (DEBUG) Slog.d(TAG, "Pending: future work scheduled for: " + record.sbn.getKey());
+ return new RankingFuture(record) {
+ @Override
+ public void work() {
+ if (INFO) Slog.i(TAG, "Executing: validation for: " + mRecord.sbn.getKey());
+ float affinity = NONE;
+ for (final String handle: pendingLookups) {
+ LookupResult lookupResult = null;
+ final Uri uri = Uri.parse(handle);
+ if ("tel".equals(uri.getScheme())) {
+ if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle);
+ lookupResult = resolvePhoneContact(uri.getSchemeSpecificPart());
+ } else if ("mailto".equals(uri.getScheme())) {
+ if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle);
+ lookupResult = resolveEmailContact(uri.getSchemeSpecificPart());
+ } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
+ if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
+ lookupResult = searchContacts(uri);
+ } else {
+ lookupResult = new LookupResult(); // invalid person for the cache
+ Slog.w(TAG, "unsupported URI " + handle);
+ }
+ if (lookupResult != null) {
+ synchronized (mPeopleCache) {
+ mPeopleCache.put(handle, lookupResult);
+ }
+ affinity = Math.max(affinity, lookupResult.getAffinity());
+ }
+ }
+ float affinityBound = mRecord.getContactAffinity();
+ affinity = Math.max(affinity, affinityBound);
+ mRecord.setContactAffinity(affinity);
+ if (INFO) Slog.i(TAG, "final affinity: " + affinity);
+ }
+ };
+ }
+
+ private String[] getExtraPeople(Bundle extras) {
+ String[] people = extras.getStringArray(Notification.EXTRA_PEOPLE);
+ if (people != null) {
+ return people;
+ }
+
+ ArrayList<String> stringArray = extras.getStringArrayList(Notification.EXTRA_PEOPLE);
+ if (stringArray != null) {
+ return (String[]) stringArray.toArray();
+ }
+
+ String string = extras.getString(Notification.EXTRA_PEOPLE);
+ if (string != null) {
+ people = new String[1];
+ people[0] = string;
+ return people;
+ }
+ char[] charArray = extras.getCharArray(Notification.EXTRA_PEOPLE);
+ if (charArray != null) {
+ people = new String[1];
+ people[0] = new String(charArray);
+ return people;
+ }
+
+ CharSequence charSeq = extras.getCharSequence(Notification.EXTRA_PEOPLE);
+ if (charSeq != null) {
+ people = new String[1];
+ people[0] = charSeq.toString();
+ return people;
+ }
+
+ CharSequence[] charSeqArray = extras.getCharSequenceArray(Notification.EXTRA_PEOPLE);
+ if (charSeqArray != null) {
+ final int N = charSeqArray.length;
+ people = new String[N];
+ for (int i = 0; i < N; i++) {
+ people[i] = charSeqArray[i].toString();
+ }
+ return people;
+ }
+
+ ArrayList<CharSequence> charSeqList =
+ extras.getCharSequenceArrayList(Notification.EXTRA_PEOPLE);
+ if (charSeqList != null) {
+ final int N = charSeqList.size();
+ people = new String[N];
+ for (int i = 0; i < N; i++) {
+ people[i] = charSeqList.get(i).toString();
+ }
+ return people;
+ }
+ return null;
+ }
+
+ private LookupResult resolvePhoneContact(final String number) {
+ Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(number));
+ return searchContacts(phoneUri);
+ }
+
+ private LookupResult resolveEmailContact(final String email) {
+ Uri numberUri = Uri.withAppendedPath(
+ ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI,
+ Uri.encode(email));
+ return searchContacts(numberUri);
+ }
+
+ private LookupResult searchContacts(Uri lookupUri) {
+ LookupResult lookupResult = new LookupResult();
+ Cursor c = null;
+ try {
+ c = mContext.getContentResolver().query(lookupUri, LOOKUP_PROJECTION, null, null, null);
+ if (c != null && c.getCount() > 0) {
+ c.moveToFirst();
+ lookupResult.readContact(c);
+ }
+ } catch(Throwable t) {
+ Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return lookupResult;
+ }
+
+ public void initialize(Context context) {
+ if (DEBUG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
+ mContext = context;
+ mPeopleCache = new LruCache<String, LookupResult>(PEOPLE_CACHE_SIZE);
+ mEnabled = ENABLE_PEOPLE_VALIDATOR && 1 == Settings.Global.getInt(
+ mContext.getContentResolver(), SETTING_ENABLE_PEOPLE_VALIDATOR, 1);
+ }
+
+ public RankingFuture process(NotificationManagerService.NotificationRecord record) {
+ if (!mEnabled) {
+ if (INFO) Slog.i(TAG, "disabled");
+ return null;
+ }
+ if (record == null || record.getNotification() == null) {
+ if (INFO) Slog.i(TAG, "skipping empty notification");
+ return null;
+ }
+ return validatePeople(record);
+ }
+
+ private static class LookupResult {
+ private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000; // 1hr
+ public static final int INVALID_ID = -1;
+
+ private final long mExpireMillis;
+ private int mId;
+ private boolean mStarred;
+
+ public LookupResult() {
+ mId = INVALID_ID;
+ mStarred = false;
+ mExpireMillis = System.currentTimeMillis() + CONTACT_REFRESH_MILLIS;
+ }
+
+ public void readContact(Cursor cursor) {
+ final int idIdx = cursor.getColumnIndex(Contacts._ID);
+ if (idIdx >= 0) {
+ mId = cursor.getInt(idIdx);
+ if (DEBUG) Slog.d(TAG, "contact _ID is: " + mId);
+ } else {
+ if (DEBUG) Slog.d(TAG, "invalid cursor: no _ID");
+ }
+ final int starIdx = cursor.getColumnIndex(Contacts.STARRED);
+ if (starIdx >= 0) {
+ mStarred = cursor.getInt(starIdx) != 0;
+ if (DEBUG) Slog.d(TAG, "contact STARRED is: " + mStarred);
+ } else {
+ if (DEBUG) Slog.d(TAG, "invalid cursor: no STARRED");
+ }
+ }
+
+ public boolean isExpired() {
+ return mExpireMillis < System.currentTimeMillis();
+ }
+
+ public boolean isInvalid() {
+ return mId == INVALID_ID || isExpired();
+ }
+
+ public float getAffinity() {
+ if (isInvalid()) {
+ return NONE;
+ } else if (mStarred) {
+ return STARRED_CONTACT;
+ } else {
+ return VALID_CONTACT;
+ }
+ }
+
+ public LookupResult setStarred(boolean starred) {
+ mStarred = starred;
+ return this;
+ }
+
+ public LookupResult setId(int id) {
+ mId = id;
+ return this;
+ }
+ }
+}
+
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
new file mode 100644
index 0000000..154ac96
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -0,0 +1,345 @@
+/**
+ * Copyright (c) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import android.app.AlarmManager;
+import android.app.AppOpsManager;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.database.ContentObserver;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.provider.Settings.Global;
+import android.service.notification.ZenModeConfig;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * NotificationManagerService helper for functionality related to zen mode.
+ */
+public class ZenModeHelper {
+ private static final String TAG = "ZenModeHelper";
+
+ private static final String ACTION_ENTER_ZEN = "enter_zen";
+ private static final int REQUEST_CODE_ENTER = 100;
+ private static final String ACTION_EXIT_ZEN = "exit_zen";
+ private static final int REQUEST_CODE_EXIT = 101;
+ private static final String EXTRA_TIME = "time";
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final SettingsObserver mSettingsObserver;
+ private final AppOpsManager mAppOps;
+ private final ZenModeConfig mDefaultConfig;
+ private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+
+ private int mZenMode;
+ private ZenModeConfig mConfig;
+
+ // temporary, until we update apps to provide metadata
+ private static final Set<String> CALL_PACKAGES = new HashSet<String>(Arrays.asList(
+ "com.google.android.dialer",
+ "com.android.phone"
+ ));
+ private static final Set<String> MESSAGE_PACKAGES = new HashSet<String>(Arrays.asList(
+ "com.google.android.talk",
+ "com.android.mms"
+ ));
+ private static final Set<String> ALARM_PACKAGES = new HashSet<String>(Arrays.asList(
+ "com.google.android.deskclock"
+ ));
+
+ public ZenModeHelper(Context context, Handler handler) {
+ mContext = context;
+ mHandler = handler;
+ mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ mDefaultConfig = readDefaultConfig(context.getResources());
+ mConfig = mDefaultConfig;
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mSettingsObserver.observe();
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_ENTER_ZEN);
+ filter.addAction(ACTION_EXIT_ZEN);
+ mContext.registerReceiver(new ZenBroadcastReceiver(), filter);
+ }
+
+ public static ZenModeConfig readDefaultConfig(Resources resources) {
+ XmlResourceParser parser = null;
+ try {
+ parser = resources.getXml(R.xml.default_zen_mode_config);
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ final ZenModeConfig config = ZenModeConfig.readXml(parser);
+ if (config != null) return config;
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Error reading default zen mode config from resource", e);
+ } finally {
+ IoUtils.closeQuietly(parser);
+ }
+ return new ZenModeConfig();
+ }
+
+ public void addCallback(Callback callback) {
+ mCallbacks.add(callback);
+ }
+
+ public boolean shouldIntercept(String pkg, Notification n) {
+ if (mZenMode != Global.ZEN_MODE_OFF) {
+ if (isAlarm(pkg, n)) {
+ return false;
+ }
+ if (isCall(pkg, n)) {
+ return !mConfig.allowCalls;
+ }
+ if (isMessage(pkg, n)) {
+ return !mConfig.allowMessages;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public int getZenMode() {
+ return mZenMode;
+ }
+
+ public void setZenMode(int zenModeValue) {
+ Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zenModeValue);
+ }
+
+ public void updateZenMode() {
+ final int mode = Global.getInt(mContext.getContentResolver(),
+ Global.ZEN_MODE, Global.ZEN_MODE_OFF);
+ if (mode != mZenMode) {
+ Slog.d(TAG, String.format("updateZenMode: %s -> %s",
+ Global.zenModeToString(mZenMode),
+ Global.zenModeToString(mode)));
+ }
+ mZenMode = mode;
+ final boolean zen = mZenMode != Global.ZEN_MODE_OFF;
+ final String[] exceptionPackages = null; // none (for now)
+
+ // call restrictions
+ final boolean muteCalls = zen && !mConfig.allowCalls;
+ mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.STREAM_RING,
+ muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
+ exceptionPackages);
+ mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, AudioManager.STREAM_RING,
+ muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
+ exceptionPackages);
+
+ // restrict vibrations with no hints
+ mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.USE_DEFAULT_STREAM_TYPE,
+ zen ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
+ exceptionPackages);
+ dispatchOnZenModeChanged();
+ }
+
+ public boolean allowDisable(int what, IBinder token, String pkg) {
+ if (isCall(pkg, null)) {
+ return mZenMode == Global.ZEN_MODE_OFF || mConfig.allowCalls;
+ }
+ return true;
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("mZenMode=");
+ pw.println(Global.zenModeToString(mZenMode));
+ pw.print(prefix); pw.print("mConfig="); pw.println(mConfig);
+ pw.print(prefix); pw.print("mDefaultConfig="); pw.println(mDefaultConfig);
+ }
+
+ public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
+ final ZenModeConfig config = ZenModeConfig.readXml(parser);
+ if (config != null) {
+ setConfig(config);
+ }
+ }
+
+ public void writeXml(XmlSerializer out) throws IOException {
+ mConfig.writeXml(out);
+ }
+
+ public ZenModeConfig getConfig() {
+ return mConfig;
+ }
+
+ public boolean setConfig(ZenModeConfig config) {
+ if (config == null || !config.isValid()) return false;
+ if (config.equals(mConfig)) return true;
+ mConfig = config;
+ Slog.d(TAG, "mConfig=" + mConfig);
+ dispatchOnConfigChanged();
+ final String val = Integer.toString(mConfig.hashCode());
+ Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
+ updateAlarms();
+ updateZenMode();
+ return true;
+ }
+
+ private void dispatchOnConfigChanged() {
+ for (Callback callback : mCallbacks) {
+ callback.onConfigChanged();
+ }
+ }
+
+ private void dispatchOnZenModeChanged() {
+ for (Callback callback : mCallbacks) {
+ callback.onZenModeChanged();
+ }
+ }
+
+ private boolean isAlarm(String pkg, Notification n) {
+ return ALARM_PACKAGES.contains(pkg);
+ }
+
+ private boolean isCall(String pkg, Notification n) {
+ return CALL_PACKAGES.contains(pkg);
+ }
+
+ private boolean isMessage(String pkg, Notification n) {
+ return MESSAGE_PACKAGES.contains(pkg);
+ }
+
+ private void updateAlarms() {
+ updateAlarm(ACTION_ENTER_ZEN, REQUEST_CODE_ENTER,
+ mConfig.sleepStartHour, mConfig.sleepStartMinute);
+ updateAlarm(ACTION_EXIT_ZEN, REQUEST_CODE_EXIT,
+ mConfig.sleepEndHour, mConfig.sleepEndMinute);
+ }
+
+ private void updateAlarm(String action, int requestCode, int hr, int min) {
+ final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ final long now = System.currentTimeMillis();
+ final Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(now);
+ c.set(Calendar.HOUR_OF_DAY, hr);
+ c.set(Calendar.MINUTE, min);
+ c.set(Calendar.SECOND, 0);
+ c.set(Calendar.MILLISECOND, 0);
+ if (c.getTimeInMillis() <= now) {
+ c.add(Calendar.DATE, 1);
+ }
+ final long time = c.getTimeInMillis();
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode,
+ new Intent(action).putExtra(EXTRA_TIME, time), PendingIntent.FLAG_UPDATE_CURRENT);
+ alarms.cancel(pendingIntent);
+ if (mConfig.sleepMode != null) {
+ Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future, now=%s",
+ action, ts(time), time - now, ts(now)));
+ alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
+ }
+ }
+
+ private static String ts(long time) {
+ return new Date(time) + " (" + time + ")";
+ }
+
+ public static boolean isWeekend(long time, int offsetDays) {
+ final Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(time);
+ if (offsetDays != 0) {
+ c.add(Calendar.DATE, offsetDays);
+ }
+ final int day = c.get(Calendar.DAY_OF_WEEK);
+ return day == Calendar.SATURDAY || day == Calendar.SUNDAY;
+ }
+
+ private class SettingsObserver extends ContentObserver {
+ private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE);
+
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void observe() {
+ final ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(ZEN_MODE, false /*notifyForDescendents*/, this);
+ update(null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ update(uri);
+ }
+
+ public void update(Uri uri) {
+ if (ZEN_MODE.equals(uri)) {
+ updateZenMode();
+ }
+ }
+ }
+
+ private class ZenBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_ENTER_ZEN.equals(intent.getAction())) {
+ setZenMode(intent, 1, Global.ZEN_MODE_ON);
+ } else if (ACTION_EXIT_ZEN.equals(intent.getAction())) {
+ setZenMode(intent, 0, Global.ZEN_MODE_OFF);
+ }
+ }
+
+ private void setZenMode(Intent intent, int wkendOffsetDays, int zenModeValue) {
+ final long schTime = intent.getLongExtra(EXTRA_TIME, 0);
+ final long now = System.currentTimeMillis();
+ Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s",
+ intent.getAction(), ts(schTime), ts(now), now - schTime));
+
+ final boolean skip = ZenModeConfig.SLEEP_MODE_WEEKNIGHTS.equals(mConfig.sleepMode) &&
+ isWeekend(schTime, wkendOffsetDays);
+
+ if (skip) {
+ Slog.d(TAG, "Skipping zen mode update for the weekend");
+ } else {
+ ZenModeHelper.this.setZenMode(zenModeValue);
+ }
+ updateAlarms();
+ }
+ }
+
+ public static class Callback {
+ void onConfigChanged() {}
+ void onZenModeChanged() {}
+ }
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
new file mode 100644
index 0000000..f2db791
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * {@hide}
+ */
+public class BackgroundDexOptService {
+
+ static final String TAG = "BackgroundDexOptService";
+
+ private final BroadcastReceiver mIdleMaintenanceReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)) {
+ onIdleStart();
+ } else if (Intent.ACTION_IDLE_MAINTENANCE_END.equals(action)) {
+ onIdleStop();
+ }
+ }
+ };
+
+ final PackageManagerService mPackageManager;
+
+ final AtomicBoolean mIdleTime = new AtomicBoolean(false);
+
+ public BackgroundDexOptService(Context context) {
+ mPackageManager = (PackageManagerService)ServiceManager.getService("package");
+
+ IntentFilter idleMaintenanceFilter = new IntentFilter();
+ idleMaintenanceFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
+ idleMaintenanceFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_END);
+ context.registerReceiverAsUser(mIdleMaintenanceReceiver, UserHandle.ALL,
+ idleMaintenanceFilter, null, null);
+ }
+
+ public boolean onIdleStart() {
+ Log.i(TAG, "onIdleStart");
+ if (mPackageManager.isStorageLow()) {
+ return false;
+ }
+ final HashSet<String> pkgs = mPackageManager.getPackagesThatNeedDexOpt();
+ if (pkgs == null) {
+ return false;
+ }
+ mIdleTime.set(true);
+ new Thread("BackgroundDexOptService_DexOpter") {
+ @Override
+ public void run() {
+ for (String pkg : pkgs) {
+ if (!mIdleTime.get()) {
+ break;
+ }
+ mPackageManager.performDexOpt(pkg, false);
+ }
+ }
+ }.start();
+ return true;
+ }
+
+ public void onIdleStop() {
+ Log.i(TAG, "onIdleStop");
+ mIdleTime.set(false);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/ForwardingIntentFilter.java b/services/core/java/com/android/server/pm/ForwardingIntentFilter.java
new file mode 100644
index 0000000..85bde98
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ForwardingIntentFilter.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+import android.content.IntentFilter;
+import android.util.Log;
+import java.io.IOException;
+import android.os.UserHandle;
+
+/**
+ * The {@link PackageManagerService} maintains some {@link ForwardingIntentFilter}s for every user.
+ * If an {@link Intent} matches the {@link ForwardingIntentFilter}, then it can be forwarded to the
+ * {@link #mUserIdDest}.
+ */
+class ForwardingIntentFilter extends IntentFilter {
+ private static final String ATTR_USER_ID_DEST = "userIdDest";
+ private static final String ATTR_REMOVABLE = "removable";
+ private static final String ATTR_FILTER = "filter";
+
+ private static final String TAG = "ForwardingIntentFilter";
+
+ // If the intent matches the IntentFilter, then it can be forwarded to this userId.
+ final int mUserIdDest;
+ boolean mRemovable;
+
+ ForwardingIntentFilter(IntentFilter filter, boolean removable, int userIdDest) {
+ super(filter);
+ mUserIdDest = userIdDest;
+ mRemovable = removable;
+ }
+
+ public int getUserIdDest() {
+ return mUserIdDest;
+ }
+
+ public boolean isRemovable() {
+ return mRemovable;
+ }
+
+ ForwardingIntentFilter(XmlPullParser parser) throws XmlPullParserException, IOException {
+ String userIdDestString = parser.getAttributeValue(null, ATTR_USER_ID_DEST);
+ if (userIdDestString == null) {
+ String msg = "Missing element under " + TAG +": " + ATTR_USER_ID_DEST + " at " +
+ parser.getPositionDescription();
+ PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+ mUserIdDest = UserHandle.USER_NULL;
+ } else {
+ mUserIdDest = Integer.parseInt(userIdDestString);
+ }
+ String removableString = parser.getAttributeValue(null, ATTR_REMOVABLE);
+ if (removableString != null) {
+ mRemovable = Boolean.parseBoolean(removableString);
+ }
+ int outerDepth = parser.getDepth();
+ String tagName = parser.getName();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ tagName = parser.getName();
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ } else if (type == XmlPullParser.START_TAG) {
+ if (tagName.equals(ATTR_FILTER)) {
+ break;
+ } else {
+ String msg = "Unknown element under " + Settings.TAG_FORWARDING_INTENT_FILTERS
+ + ": " + tagName + " at " + parser.getPositionDescription();
+ PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+ if (tagName.equals(ATTR_FILTER)) {
+ readFromXml(parser);
+ } else {
+ String msg = "Missing element under " + TAG + ": " + ATTR_FILTER +
+ " at " + parser.getPositionDescription();
+ PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ public void writeToXml(XmlSerializer serializer) throws IOException {
+ serializer.attribute(null, ATTR_USER_ID_DEST, Integer.toString(mUserIdDest));
+ serializer.attribute(null, ATTR_REMOVABLE, Boolean.toString(mRemovable));
+ serializer.startTag(null, ATTR_FILTER);
+ super.writeToXml(serializer);
+ serializer.endTag(null, ATTR_FILTER);
+ }
+
+ @Override
+ public String toString() {
+ return "ForwardingIntentFilter{0x" + Integer.toHexString(System.identityHashCode(this))
+ + " " + Integer.toString(mUserIdDest) + "}";
+ }
+}
diff --git a/services/core/java/com/android/server/pm/ForwardingIntentResolver.java b/services/core/java/com/android/server/pm/ForwardingIntentResolver.java
new file mode 100644
index 0000000..1616395
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ForwardingIntentResolver.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.server.pm;
+
+
+import java.io.PrintWriter;
+import com.android.server.IntentResolver;
+import java.util.List;
+
+/**
+ * Used to find a list of {@link ForwardingIntentFilter}s that match an intent.
+ */
+class ForwardingIntentResolver
+ extends IntentResolver<ForwardingIntentFilter, ForwardingIntentFilter> {
+ @Override
+ protected ForwardingIntentFilter[] newArray(int size) {
+ return new ForwardingIntentFilter[size];
+ }
+
+ @Override
+ protected boolean isPackageForFilter(String packageName, ForwardingIntentFilter filter) {
+ return false;
+ }
+
+ @Override
+ protected void sortResults(List<ForwardingIntentFilter> results) {
+ //We don't sort the results
+ }
+}
diff --git a/services/core/java/com/android/server/pm/KeySetManager.java b/services/core/java/com/android/server/pm/KeySetManager.java
index 66dc1d1..1056cd0 100644
--- a/services/core/java/com/android/server/pm/KeySetManager.java
+++ b/services/core/java/com/android/server/pm/KeySetManager.java
@@ -20,13 +20,11 @@ import android.content.pm.KeySet;
import android.content.pm.PackageParser;
import android.os.Binder;
import android.util.Base64;
-import android.util.Log;
import android.util.LongSparseArray;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.PublicKey;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
new file mode 100644
index 0000000..5e3325c
--- /dev/null
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ILauncherApps;
+import android.content.pm.IOnAppsChangedListener;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IInterface;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.content.PackageMonitor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Service that manages requests and callbacks for launchers that support
+ * managed profiles.
+ */
+public class LauncherAppsService extends ILauncherApps.Stub {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "LauncherAppsService";
+ private final Context mContext;
+ private final PackageManager mPm;
+ private final UserManager mUm;
+ private final PackageCallbackList<IOnAppsChangedListener> mListeners
+ = new PackageCallbackList<IOnAppsChangedListener>();
+
+ private MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
+
+ public LauncherAppsService(Context context) {
+ mContext = context;
+ mPm = mContext.getPackageManager();
+ mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ }
+
+ /*
+ * @see android.content.pm.ILauncherApps#addOnAppsChangedListener(
+ * android.content.pm.IOnAppsChangedListener)
+ */
+ @Override
+ public void addOnAppsChangedListener(IOnAppsChangedListener listener) throws RemoteException {
+ synchronized (mListeners) {
+ if (DEBUG) {
+ Log.d(TAG, "Adding listener from " + Binder.getCallingUserHandle());
+ }
+ if (mListeners.getRegisteredCallbackCount() == 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Starting package monitoring");
+ }
+ startWatchingPackageBroadcasts();
+ }
+ mListeners.unregister(listener);
+ mListeners.register(listener, Binder.getCallingUserHandle());
+ }
+ }
+
+ /*
+ * @see android.content.pm.ILauncherApps#removeOnAppsChangedListener(
+ * android.content.pm.IOnAppsChangedListener)
+ */
+ @Override
+ public void removeOnAppsChangedListener(IOnAppsChangedListener listener)
+ throws RemoteException {
+ synchronized (mListeners) {
+ if (DEBUG) {
+ Log.d(TAG, "Removing listener from " + Binder.getCallingUserHandle());
+ }
+ mListeners.unregister(listener);
+ if (mListeners.getRegisteredCallbackCount() == 0) {
+ stopWatchingPackageBroadcasts();
+ }
+ }
+ }
+
+ /**
+ * Register a receiver to watch for package broadcasts
+ */
+ private void startWatchingPackageBroadcasts() {
+ mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
+ }
+
+ /**
+ * Unregister package broadcast receiver
+ */
+ private void stopWatchingPackageBroadcasts() {
+ if (DEBUG) {
+ Log.d(TAG, "Stopped watching for packages");
+ }
+ mPackageMonitor.unregister();
+ }
+
+ void checkCallbackCount() {
+ synchronized (mListeners) {
+ if (DEBUG) {
+ Log.d(TAG, "Callback count = " + mListeners.getRegisteredCallbackCount());
+ }
+ if (mListeners.getRegisteredCallbackCount() == 0) {
+ stopWatchingPackageBroadcasts();
+ }
+ }
+ }
+
+ /**
+ * Checks if the caller is in the same group as the userToCheck.
+ */
+ private void ensureInUserProfiles(UserHandle userToCheck, String message) {
+ final int callingUserId = UserHandle.getCallingUserId();
+ final int targetUserId = userToCheck.getIdentifier();
+
+ if (targetUserId == callingUserId) return;
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ UserInfo callingUserInfo = mUm.getUserInfo(callingUserId);
+ UserInfo targetUserInfo = mUm.getUserInfo(targetUserId);
+ if (targetUserInfo == null
+ || targetUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID
+ || targetUserInfo.profileGroupId != callingUserInfo.profileGroupId) {
+ throw new SecurityException(message);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * Checks if the user is enabled.
+ */
+ private boolean isUserEnabled(UserHandle user) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ UserInfo targetUserInfo = mUm.getUserInfo(user.getIdentifier());
+ return targetUserInfo != null && targetUserInfo.isEnabled();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public List<ResolveInfo> getLauncherActivities(String packageName, UserHandle user)
+ throws RemoteException {
+ ensureInUserProfiles(user, "Cannot retrieve activities for unrelated profile " + user);
+ if (!isUserEnabled(user)) {
+ return new ArrayList<ResolveInfo>();
+ }
+
+ final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+ mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ mainIntent.setPackage(packageName);
+ long ident = Binder.clearCallingIdentity();
+ try {
+ List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent, 0,
+ user.getIdentifier());
+ return apps;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public ResolveInfo resolveActivity(Intent intent, UserHandle user)
+ throws RemoteException {
+ ensureInUserProfiles(user, "Cannot resolve activity for unrelated profile " + user);
+ if (!isUserEnabled(user)) {
+ return null;
+ }
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ ResolveInfo app = mPm.resolveActivityAsUser(intent, 0, user.getIdentifier());
+ return app;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public boolean isPackageEnabled(String packageName, UserHandle user)
+ throws RemoteException {
+ ensureInUserProfiles(user, "Cannot check package for unrelated profile " + user);
+ if (!isUserEnabled(user)) {
+ return false;
+ }
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ PackageInfo info = pm.getPackageInfo(packageName, 0, user.getIdentifier());
+ return info != null && info.applicationInfo.enabled;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public boolean isActivityEnabled(ComponentName component, UserHandle user)
+ throws RemoteException {
+ ensureInUserProfiles(user, "Cannot check component for unrelated profile " + user);
+ if (!isUserEnabled(user)) {
+ return false;
+ }
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ ActivityInfo info = pm.getActivityInfo(component, 0, user.getIdentifier());
+ return info != null && info.isEnabled();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public void startActivityAsUser(ComponentName component, Rect sourceBounds,
+ Bundle opts, UserHandle user) throws RemoteException {
+ ensureInUserProfiles(user, "Cannot start activity for unrelated profile " + user);
+ if (!isUserEnabled(user)) {
+ throw new IllegalStateException("Cannot start activity for disabled profile " + user);
+ }
+
+ Intent launchIntent = new Intent(Intent.ACTION_MAIN);
+ launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ launchIntent.setComponent(component);
+ launchIntent.setSourceBounds(sourceBounds);
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.startActivityAsUser(launchIntent, opts, user);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private class MyPackageMonitor extends PackageMonitor {
+
+ /** Checks if user is a profile of or same as listeningUser.
+ * and the user is enabled. */
+ private boolean isEnabledProfileOf(UserHandle user, UserHandle listeningUser,
+ String debugMsg) {
+ if (user.getIdentifier() == listeningUser.getIdentifier()) {
+ if (DEBUG) Log.d(TAG, "Delivering msg to same user " + debugMsg);
+ return true;
+ }
+ long ident = Binder.clearCallingIdentity();
+ try {
+ UserInfo userInfo = mUm.getUserInfo(user.getIdentifier());
+ UserInfo listeningUserInfo = mUm.getUserInfo(listeningUser.getIdentifier());
+ if (userInfo == null || listeningUserInfo == null
+ || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID
+ || userInfo.profileGroupId != listeningUserInfo.profileGroupId
+ || !userInfo.isEnabled()) {
+ if (DEBUG) {
+ Log.d(TAG, "Not delivering msg from " + user + " to " + listeningUser + ":"
+ + debugMsg);
+ }
+ return false;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Delivering msg from " + user + " to " + listeningUser + ":"
+ + debugMsg);
+ }
+ return true;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ UserHandle user = new UserHandle(getChangingUserId());
+ final int n = mListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(user, listeningUser, "onPackageAdded")) continue;
+ try {
+ listener.onPackageAdded(user, packageName);
+ } catch (RemoteException re) {
+ Slog.d(TAG, "Callback failed ", re);
+ }
+ }
+ mListeners.finishBroadcast();
+
+ super.onPackageAdded(packageName, uid);
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ UserHandle user = new UserHandle(getChangingUserId());
+ final int n = mListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(user, listeningUser, "onPackageRemoved")) continue;
+ try {
+ listener.onPackageRemoved(user, packageName);
+ } catch (RemoteException re) {
+ Slog.d(TAG, "Callback failed ", re);
+ }
+ }
+ mListeners.finishBroadcast();
+
+ super.onPackageRemoved(packageName, uid);
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ UserHandle user = new UserHandle(getChangingUserId());
+ final int n = mListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(user, listeningUser, "onPackageModified")) continue;
+ try {
+ listener.onPackageChanged(user, packageName);
+ } catch (RemoteException re) {
+ Slog.d(TAG, "Callback failed ", re);
+ }
+ }
+ mListeners.finishBroadcast();
+
+ super.onPackageModified(packageName);
+ }
+
+ @Override
+ public void onPackagesAvailable(String[] packages) {
+ UserHandle user = new UserHandle(getChangingUserId());
+ final int n = mListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(user, listeningUser, "onPackagesAvailable")) continue;
+ try {
+ listener.onPackagesAvailable(user, packages, isReplacing());
+ } catch (RemoteException re) {
+ Slog.d(TAG, "Callback failed ", re);
+ }
+ }
+ mListeners.finishBroadcast();
+
+ super.onPackagesAvailable(packages);
+ }
+
+ @Override
+ public void onPackagesUnavailable(String[] packages) {
+ UserHandle user = new UserHandle(getChangingUserId());
+ final int n = mListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(user, listeningUser, "onPackagesUnavailable")) continue;
+ try {
+ listener.onPackagesUnavailable(user, packages, isReplacing());
+ } catch (RemoteException re) {
+ Slog.d(TAG, "Callback failed ", re);
+ }
+ }
+ mListeners.finishBroadcast();
+
+ super.onPackagesUnavailable(packages);
+ }
+
+ }
+
+ class PackageCallbackList<T extends IInterface> extends RemoteCallbackList<T> {
+ @Override
+ public void onCallbackDied(T callback, Object cookie) {
+ checkCallbackCount();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
new file mode 100644
index 0000000..5fdfce4
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.INSTALL_ALL_USERS;
+import static android.content.pm.PackageManager.INSTALL_FROM_ADB;
+import static android.content.pm.PackageManager.INSTALL_REPLACE_EXISTING;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageInstaller;
+import android.content.pm.IPackageInstallerSession;
+import android.content.pm.PackageInstallerParams;
+import android.os.Binder;
+import android.os.FileUtils;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.IoThread;
+import com.google.android.collect.Sets;
+
+import java.io.File;
+
+public class PackageInstallerService extends IPackageInstaller.Stub {
+ private static final String TAG = "PackageInstaller";
+
+ // TODO: destroy sessions with old timestamps
+ // TODO: remove outstanding sessions when installer package goes away
+
+ private final Context mContext;
+ private final PackageManagerService mPm;
+ private final AppOpsManager mAppOps;
+
+ private final File mStagingDir;
+
+ private final HandlerThread mInstallThread = new HandlerThread(TAG);
+ private final Callback mCallback = new Callback();
+
+ @GuardedBy("mSessions")
+ private int mNextSessionId;
+ @GuardedBy("mSessions")
+ private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
+
+ public PackageInstallerService(Context context, PackageManagerService pm, File stagingDir) {
+ mContext = context;
+ mPm = pm;
+ mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+
+ mStagingDir = stagingDir;
+ mStagingDir.mkdirs();
+
+ synchronized (mSessions) {
+ readSessionsLocked();
+
+ // Clean up orphaned staging directories
+ final ArraySet<String> dirs = Sets.newArraySet(mStagingDir.list());
+ for (int i = 0; i < mSessions.size(); i++) {
+ dirs.remove(Integer.toString(mSessions.keyAt(i)));
+ }
+ for (String dirName : dirs) {
+ Slog.w(TAG, "Deleting orphan session " + dirName);
+ final File dir = new File(mStagingDir, dirName);
+ FileUtils.deleteContents(dir);
+ dir.delete();
+ }
+ }
+ }
+
+ private void readSessionsLocked() {
+ // TODO: implement persisting
+ mSessions.clear();
+ mNextSessionId = 1;
+ }
+
+ private void writeSessionsLocked() {
+ // TODO: implement persisting
+ }
+
+ private void writeSessionsAsync() {
+ IoThread.getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mSessions) {
+ writeSessionsLocked();
+ }
+ }
+ });
+ }
+
+ @Override
+ public int createSession(int userId, String installerPackageName,
+ PackageInstallerParams params) {
+ final int callingUid = Binder.getCallingUid();
+ mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
+ mAppOps.checkPackage(callingUid, installerPackageName);
+
+ if (mPm.isUserRestricted(UserHandle.getUserId(callingUid),
+ UserManager.DISALLOW_INSTALL_APPS)) {
+ throw new SecurityException("User restriction prevents installing");
+ }
+
+ if ((callingUid == Process.SHELL_UID) || (callingUid == 0)) {
+ params.installFlags |= INSTALL_FROM_ADB;
+ } else {
+ params.installFlags &= ~INSTALL_FROM_ADB;
+ params.installFlags &= ~INSTALL_ALL_USERS;
+ params.installFlags |= INSTALL_REPLACE_EXISTING;
+ }
+
+ synchronized (mSessions) {
+ final int sessionId = allocateSessionIdLocked();
+ final long createdMillis = System.currentTimeMillis();
+ final File sessionDir = new File(mStagingDir, Integer.toString(sessionId));
+ sessionDir.mkdirs();
+
+ final PackageInstallerSession session = new PackageInstallerSession(mCallback, mPm,
+ sessionId, userId, installerPackageName, callingUid, params, createdMillis,
+ sessionDir, mInstallThread.getLooper());
+ mSessions.put(sessionId, session);
+
+ writeSessionsAsync();
+ return sessionId;
+ }
+ }
+
+ @Override
+ public IPackageInstallerSession openSession(int sessionId) {
+ synchronized (mSessions) {
+ final PackageInstallerSession session = mSessions.get(sessionId);
+ if (session == null) {
+ throw new IllegalStateException("Missing session " + sessionId);
+ }
+ if (Binder.getCallingUid() != session.installerUid) {
+ throw new SecurityException("Caller has no access to session " + sessionId);
+ }
+ return session;
+ }
+ }
+
+ private int allocateSessionIdLocked() {
+ if (mSessions.get(mNextSessionId) != null) {
+ throw new IllegalStateException("Next session already allocated");
+ }
+ return mNextSessionId++;
+ }
+
+ @Override
+ public int[] getSessions(int userId, String installerPackageName) {
+ final int callingUid = Binder.getCallingUid();
+ mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
+ mAppOps.checkPackage(callingUid, installerPackageName);
+
+ int[] matching = new int[0];
+ synchronized (mSessions) {
+ for (int i = 0; i < mSessions.size(); i++) {
+ final int key = mSessions.keyAt(i);
+ final PackageInstallerSession session = mSessions.valueAt(i);
+ if (session.userId == userId
+ && session.installerPackageName.equals(installerPackageName)) {
+ matching = ArrayUtils.appendInt(matching, key);
+ }
+ }
+ }
+ return matching;
+ }
+
+ @Override
+ public void uninstall(int userId, String basePackageName, IPackageDeleteObserver observer) {
+ mPm.deletePackageAsUser(basePackageName, observer, userId, 0);
+ }
+
+ @Override
+ public void uninstallSplit(int userId, String basePackageName, String overlayName,
+ IPackageDeleteObserver observer) {
+ // TODO: flesh out once PM has split support
+ throw new UnsupportedOperationException();
+ }
+
+ class Callback {
+ public void onProgressChanged(PackageInstallerSession session) {
+ // TODO: notify listeners
+ }
+
+ public void onSessionInvalid(PackageInstallerSession session) {
+ writeSessionsAsync();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
new file mode 100644
index 0000000..f90d7ab
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
+import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageInstallObserver2;
+import android.content.pm.IPackageInstallerSession;
+import android.content.pm.PackageInstallerParams;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageLite;
+import android.content.pm.Signature;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.system.StructStat;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+import libcore.io.Streams;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class PackageInstallerSession extends IPackageInstallerSession.Stub {
+ private static final String TAG = "PackageInstaller";
+
+ private final PackageInstallerService.Callback mCallback;
+ private final PackageManagerService mPm;
+ private final Handler mHandler;
+
+ public final int sessionId;
+ public final int userId;
+ public final String installerPackageName;
+ /** UID not persisted */
+ public final int installerUid;
+ public final PackageInstallerParams params;
+ public final long createdMillis;
+ public final File sessionDir;
+
+ private static final int MSG_INSTALL = 0;
+
+ private Handler.Callback mHandlerCallback = new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ synchronized (mLock) {
+ if (msg.obj != null) {
+ mRemoteObserver = (IPackageInstallObserver2) msg.obj;
+ }
+
+ try {
+ installLocked();
+ } catch (InstallFailedException e) {
+ Slog.e(TAG, "Install failed: " + e);
+ try {
+ mRemoteObserver.packageInstalled(mPackageName, null, e.error);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ return true;
+ }
+ }
+ };
+
+ private final Object mLock = new Object();
+
+ private int mProgress;
+
+ private String mPackageName;
+ private int mVersionCode;
+ private Signature[] mSignatures;
+
+ private boolean mMutationsAllowed;
+ private boolean mVerifierConfirmed;
+ private boolean mPermissionsConfirmed;
+ private boolean mInvalid;
+
+ private ArrayList<WritePipe> mPipes = new ArrayList<>();
+
+ private IPackageInstallObserver2 mRemoteObserver;
+
+ public PackageInstallerSession(PackageInstallerService.Callback callback,
+ PackageManagerService pm, int sessionId, int userId, String installerPackageName,
+ int installerUid, PackageInstallerParams params, long createdMillis, File sessionDir,
+ Looper looper) {
+ mCallback = callback;
+ mPm = pm;
+ mHandler = new Handler(looper, mHandlerCallback);
+
+ this.sessionId = sessionId;
+ this.userId = userId;
+ this.installerPackageName = installerPackageName;
+ this.installerUid = installerUid;
+ this.params = params;
+ this.createdMillis = createdMillis;
+ this.sessionDir = sessionDir;
+
+ // Check against any explicitly provided signatures
+ mSignatures = params.signatures;
+
+ // TODO: splice in flag when restoring persisted session
+ mMutationsAllowed = true;
+
+ if (pm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, installerPackageName)
+ == PackageManager.PERMISSION_GRANTED) {
+ mPermissionsConfirmed = true;
+ }
+ }
+
+ @Override
+ public void updateProgress(int progress) {
+ mProgress = progress;
+ mCallback.onProgressChanged(this);
+ }
+
+ @Override
+ public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
+ // TODO: relay over to DCS when installing to ASEC
+
+ // Quick sanity check of state, and allocate a pipe for ourselves. We
+ // then do heavy disk allocation outside the lock, but this open pipe
+ // will block any attempted install transitions.
+ final WritePipe pipe;
+ synchronized (mLock) {
+ if (!mMutationsAllowed) {
+ throw new IllegalStateException("Mutations not allowed");
+ }
+
+ pipe = new WritePipe();
+ mPipes.add(pipe);
+ }
+
+ try {
+ // Use installer provided name for now; we always rename later
+ if (!FileUtils.isValidExtFilename(name)) {
+ throw new IllegalArgumentException("Invalid name: " + name);
+ }
+ final File target = new File(sessionDir, name);
+
+ final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
+ OsConstants.O_CREAT | OsConstants.O_WRONLY, 00700);
+
+ // If caller specified a total length, allocate it for them. Free up
+ // cache space to grow, if needed.
+ if (lengthBytes > 0) {
+ final StructStat stat = Libcore.os.fstat(targetFd);
+ final long deltaBytes = lengthBytes - stat.st_size;
+ if (deltaBytes > 0) {
+ mPm.freeStorage(deltaBytes);
+ }
+ Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
+ }
+
+ if (offsetBytes > 0) {
+ Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
+ }
+
+ pipe.setTargetFd(targetFd);
+ pipe.start();
+ return pipe.getWriteFd();
+
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Failed to write", e);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to write", e);
+ }
+ }
+
+ @Override
+ public void install(IPackageInstallObserver2 observer) {
+ Preconditions.checkNotNull(observer);
+ mHandler.obtainMessage(MSG_INSTALL, observer).sendToTarget();
+ }
+
+ private void installLocked() throws InstallFailedException {
+ if (mInvalid) {
+ throw new InstallFailedException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
+ }
+
+ // Verify that all writers are hands-off
+ if (mMutationsAllowed) {
+ for (WritePipe pipe : mPipes) {
+ if (!pipe.isClosed()) {
+ throw new InstallFailedException(INSTALL_FAILED_PACKAGE_CHANGED,
+ "Files still open");
+ }
+ }
+ mMutationsAllowed = false;
+
+ // TODO: persist disabled mutations before going forward, since
+ // beyond this point we may have hardlinks to the valid install
+ }
+
+ // Verify that stage looks sane with respect to existing application.
+ // This currently only ensures packageName, versionCode, and certificate
+ // consistency.
+ validateInstallLocked();
+
+ Preconditions.checkNotNull(mPackageName);
+ Preconditions.checkNotNull(mSignatures);
+
+ if (!mVerifierConfirmed) {
+ // TODO: async communication with verifier
+ // when they confirm, we'll kick off another install() pass
+ mVerifierConfirmed = true;
+ }
+
+ if (!mPermissionsConfirmed) {
+ // TODO: async confirm permissions with user
+ // when they confirm, we'll kick off another install() pass
+ mPermissionsConfirmed = true;
+ }
+
+ // Unpack any native libraries contained in this session
+ unpackNativeLibraries();
+
+ // Inherit any packages and native libraries from existing install that
+ // haven't been overridden.
+ if (!params.fullInstall) {
+ spliceExistingFilesIntoStage();
+ }
+
+ // TODO: for ASEC based applications, grow and stream in packages
+
+ // We've reached point of no return; call into PMS to install the stage.
+ // Regardless of success or failure we always destroy session.
+ final IPackageInstallObserver2 remoteObserver = mRemoteObserver;
+ final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
+ @Override
+ public void packageInstalled(String basePackageName, Bundle extras, int returnCode)
+ throws RemoteException {
+ destroy();
+ remoteObserver.packageInstalled(basePackageName, extras, returnCode);
+ }
+ };
+
+ mPm.installStage(mPackageName, this.sessionDir, localObserver, params.installFlags);
+ }
+
+ /**
+ * Validate install by confirming that all application packages are have
+ * consistent package name, version code, and signing certificates.
+ * <p>
+ * Renames package files in stage to match split names defined inside.
+ */
+ private void validateInstallLocked() throws InstallFailedException {
+ mPackageName = null;
+ mVersionCode = -1;
+ mSignatures = null;
+
+ final File[] files = sessionDir.listFiles();
+ if (ArrayUtils.isEmpty(files)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged");
+ }
+
+ final ArraySet<String> seenSplits = new ArraySet<>();
+
+ // Verify that all staged packages are internally consistent
+ for (File file : files) {
+ final PackageLite info = PackageParser.parsePackageLite(file.getAbsolutePath(),
+ PackageParser.PARSE_GET_SIGNATURES);
+ if (info == null) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Failed to parse " + file);
+ }
+
+ if (!seenSplits.add(info.splitName)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Split " + info.splitName + " was defined multiple times");
+ }
+
+ // Use first package to define unknown values
+ if (mPackageName != null) {
+ mPackageName = info.packageName;
+ mVersionCode = info.versionCode;
+ }
+ if (mSignatures != null) {
+ mSignatures = info.signatures;
+ }
+
+ assertPackageConsistent(String.valueOf(file), info.packageName, info.versionCode,
+ info.signatures);
+
+ // Take this opportunity to enforce uniform naming
+ final String name;
+ if (info.splitName == null) {
+ name = info.packageName + ".apk";
+ } else {
+ name = info.packageName + "-" + info.splitName + ".apk";
+ }
+ if (!FileUtils.isValidExtFilename(name)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Invalid filename: " + name);
+ }
+ if (!file.getName().equals(name)) {
+ file.renameTo(new File(file.getParentFile(), name));
+ }
+ }
+
+ // TODO: shift package signature verification to installer; we're
+ // currently relying on PMS to do this.
+ // TODO: teach about compatible upgrade keysets.
+
+ if (params.fullInstall) {
+ // Full installs must include a base package
+ if (!seenSplits.contains(null)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Full install must include a base package");
+ }
+
+ } else {
+ // Partial installs must be consistent with existing install.
+ final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
+ if (app == null) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Missing existing base package for " + mPackageName);
+ }
+
+ final PackageLite info = PackageParser.parsePackageLite(app.sourceDir,
+ PackageParser.PARSE_GET_SIGNATURES);
+ if (info == null) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Failed to parse existing base " + app.sourceDir);
+ }
+
+ assertPackageConsistent("Existing base", info.packageName, info.versionCode,
+ info.signatures);
+ }
+ }
+
+ private void assertPackageConsistent(String tag, String packageName, int versionCode,
+ Signature[] signatures) throws InstallFailedException {
+ if (!mPackageName.equals(packageName)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag + " package "
+ + packageName + " inconsistent with " + mPackageName);
+ }
+ if (mVersionCode != versionCode) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag
+ + " version code " + versionCode + " inconsistent with "
+ + mVersionCode);
+ }
+ if (!Signature.areExactMatch(mSignatures, signatures)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ tag + " signatures are inconsistent");
+ }
+ }
+
+ /**
+ * Application is already installed; splice existing files that haven't been
+ * overridden into our stage.
+ */
+ private void spliceExistingFilesIntoStage() throws InstallFailedException {
+ final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
+ final File existingDir = new File(app.sourceDir).getParentFile();
+
+ try {
+ linkTreeIgnoringExisting(existingDir, sessionDir);
+ } catch (ErrnoException e) {
+ throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to splice into stage");
+ }
+ }
+
+ /**
+ * Recursively hard link all files from source directory tree to target.
+ * When a file already exists in the target tree, it leaves that file
+ * intact.
+ */
+ private void linkTreeIgnoringExisting(File sourceDir, File targetDir) throws ErrnoException {
+ final File[] sourceContents = sourceDir.listFiles();
+ if (ArrayUtils.isEmpty(sourceContents)) return;
+
+ for (File sourceFile : sourceContents) {
+ final File targetFile = new File(targetDir, sourceFile.getName());
+
+ if (sourceFile.isDirectory()) {
+ targetFile.mkdir();
+ linkTreeIgnoringExisting(sourceFile, targetFile);
+ } else {
+ Libcore.os.link(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath());
+ }
+ }
+ }
+
+ private void unpackNativeLibraries() throws InstallFailedException {
+ final File libDir = new File(sessionDir, "lib");
+
+ if (!libDir.mkdir()) {
+ throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to create " + libDir);
+ }
+
+ try {
+ Libcore.os.chmod(libDir.getAbsolutePath(), 0755);
+ } catch (ErrnoException e) {
+ throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to prepare " + libDir + ": " + e);
+ }
+
+ if (!SELinux.restorecon(libDir)) {
+ throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to set context on " + libDir);
+ }
+
+ // Unpack all native libraries under stage
+ final File[] files = sessionDir.listFiles();
+ if (ArrayUtils.isEmpty(files)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged");
+ }
+
+ for (File file : files) {
+ final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(file);
+ try {
+ final int abiIndex = NativeLibraryHelper.findSupportedAbi(handle,
+ Build.SUPPORTED_ABIS);
+ if (abiIndex >= 0) {
+ int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, libDir,
+ Build.SUPPORTED_ABIS[abiIndex]);
+ if (copyRet != INSTALL_SUCCEEDED) {
+ throw new InstallFailedException(copyRet,
+ "Failed to copy native libraries for " + file);
+ }
+ } else if (abiIndex != PackageManager.NO_NATIVE_LIBRARIES) {
+ throw new InstallFailedException(abiIndex,
+ "Failed to copy native libraries for " + file);
+ }
+ } finally {
+ handle.close();
+ }
+ }
+ }
+
+ @Override
+ public void destroy() {
+ try {
+ synchronized (mLock) {
+ mInvalid = true;
+ }
+ FileUtils.deleteContents(sessionDir);
+ sessionDir.delete();
+ } finally {
+ mCallback.onSessionInvalid(this);
+ }
+ }
+
+ private static class WritePipe extends Thread {
+ private final ParcelFileDescriptor[] mPipe;
+
+ private FileDescriptor mTargetFd;
+
+ private volatile boolean mClosed;
+
+ public WritePipe() {
+ try {
+ mPipe = ParcelFileDescriptor.createPipe();
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to create pipe");
+ }
+ }
+
+ public boolean isClosed() {
+ return mClosed;
+ }
+
+ public void setTargetFd(FileDescriptor targetFd) {
+ mTargetFd = targetFd;
+ }
+
+ public ParcelFileDescriptor getWriteFd() {
+ return mPipe[1];
+ }
+
+ @Override
+ public void run() {
+ FileInputStream in = null;
+ FileOutputStream out = null;
+ try {
+ // TODO: look at switching to sendfile(2) to speed up
+ in = new FileInputStream(mPipe[0].getFileDescriptor());
+ out = new FileOutputStream(mTargetFd);
+ Streams.copy(in, out);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to stream data: " + e);
+ } finally {
+ IoUtils.closeQuietly(mPipe[0]);
+ IoUtils.closeQuietly(mTargetFd);
+ mClosed = true;
+ }
+ }
+ }
+
+ private class InstallFailedException extends Exception {
+ private final int error;
+
+ public InstallFailedException(int error, String detailMessage) {
+ super(detailMessage);
+ this.error = error;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageKeySetData.java b/services/core/java/com/android/server/pm/PackageKeySetData.java
index cb60621..ebded28 100644
--- a/services/core/java/com/android/server/pm/PackageKeySetData.java
+++ b/services/core/java/com/android/server/pm/PackageKeySetData.java
@@ -18,9 +18,7 @@ package com.android.server.pm;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Map;
-import java.util.Set;
public class PackageKeySetData {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 980476f..61b3a89 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -18,18 +18,21 @@ package com.android.server.pm;
import static android.Manifest.permission.GRANT_REVOKE_PERMISSIONS;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.Manifest.permission.INSTALL_PACKAGES;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static android.system.OsConstants.S_IRWXU;
+import static android.os.Process.PACKAGE_INFO_GID;
+import static android.os.Process.SYSTEM_UID;
import static android.system.OsConstants.S_IRGRP;
-import static android.system.OsConstants.S_IXGRP;
import static android.system.OsConstants.S_IROTH;
+import static android.system.OsConstants.S_IRWXU;
+import static android.system.OsConstants.S_IXGRP;
import static android.system.OsConstants.S_IXOTH;
-import static android.os.Process.PACKAGE_INFO_GID;
-import static android.os.Process.SYSTEM_UID;
+import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
+import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_USER_OWNER;
import static com.android.internal.util.ArrayUtils.appendInt;
import static com.android.internal.util.ArrayUtils.removeInt;
@@ -46,6 +49,8 @@ import com.android.server.IntentResolver;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.Watchdog;
+import com.android.server.pm.Settings.DatabaseVersion;
+import com.android.server.storage.DeviceStorageMonitorInternal;
import com.android.server.storage.DeviceStorageMonitorInternal;
import org.xmlpull.v1.XmlPullParser;
@@ -55,6 +60,7 @@ import org.xmlpull.v1.XmlSerializer;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
+import android.app.PackageInstallObserver;
import android.app.admin.IDevicePolicyManager;
import android.app.backup.IBackupManager;
import android.content.BroadcastReceiver;
@@ -73,6 +79,8 @@ import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageInstallObserver;
+import android.content.pm.IPackageInstallObserver2;
+import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
@@ -81,6 +89,7 @@ import android.content.pm.ManifestDigest;
import android.content.pm.PackageCleanItem;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
+import android.content.pm.PackageInstallerParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser.ActivityIntentInfo;
import android.content.pm.PackageParser;
@@ -152,6 +161,7 @@ import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
+import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -172,6 +182,7 @@ import java.util.concurrent.atomic.AtomicLong;
import dalvik.system.DexFile;
import dalvik.system.StaleDexCacheError;
import dalvik.system.VMRuntime;
+
import libcore.io.IoUtils;
/**
@@ -208,7 +219,8 @@ public class PackageManagerService extends IPackageManager.Stub {
private static final int BLUETOOTH_UID = Process.BLUETOOTH_UID;
private static final int SHELL_UID = Process.SHELL_UID;
- private static final boolean GET_CERTIFICATES = true;
+ // Cap the size of permission trees that 3rd party apps can define
+ private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768; // characters of text
private static final int REMOVE_EVENTS =
FileObserver.CLOSE_WRITE | FileObserver.DELETE | FileObserver.MOVED_FROM;
@@ -276,18 +288,16 @@ public class PackageManagerService extends IPackageManager.Stub {
static final String mTempContainerPrefix = "smdl2tmp";
- final ServiceThread mHandlerThread;
-
private static String sPreferredInstructionSet;
+ final ServiceThread mHandlerThread;
+
private static final String IDMAP_PREFIX = "/data/resource-cache/";
private static final String IDMAP_SUFFIX = "@idmap";
final PackageHandler mHandler;
final int mSdkVersion = Build.VERSION.SDK_INT;
- final String mSdkCodename = "REL".equals(Build.VERSION.CODENAME)
- ? null : Build.VERSION.CODENAME;
final Context mContext;
final boolean mFactoryTest;
@@ -314,12 +324,15 @@ public class PackageManagerService extends IPackageManager.Stub {
// This is the object monitoring the privileged system app dir.
final FileObserver mPrivilegedInstallObserver;
- // This is the object monitoring the system app dir.
+ // This is the object monitoring the vendor app dir.
final FileObserver mVendorInstallObserver;
// This is the object monitoring the vendor overlay package dir.
final FileObserver mVendorOverlayInstallObserver;
+ // This is the object monitoring the OEM app dir.
+ final FileObserver mOemInstallObserver;
+
// This is the object monitoring mAppInstallDir.
final FileObserver mAppInstallObserver;
@@ -342,11 +355,13 @@ public class PackageManagerService extends IPackageManager.Stub {
// apps.
final File mDrmAppPrivateInstallDir;
+ final File mAppStagingDir;
+
// ----------------------------------------------------------------
// Lock for state used when installing and doing other long running
// operations. Methods that must be called with this lock held have
- // the prefix "LI".
+ // the suffix "LI".
final Object mInstallLock = new Object();
// These are the directories in the 3rd party applications installed dir
@@ -448,6 +463,8 @@ public class PackageManagerService extends IPackageManager.Stub {
final SparseArray<PackageVerificationState> mPendingVerification
= new SparseArray<PackageVerificationState>();
+ final PackageInstallerService mInstallerService;
+
HashSet<PackageParser.Package> mDeferredDexOpt = null;
/** Token for keys in mPendingVerification. */
@@ -845,7 +862,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// Just post MCS_BOUND message to trigger processing
// of next pending install.
if (DEBUG_SD_INSTALL) Log.i(TAG,
- "Posting MCS_BOUND for next woek");
+ "Posting MCS_BOUND for next work");
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
@@ -1065,6 +1082,14 @@ public class PackageManagerService extends IPackageManager.Stub {
Slog.i(TAG, "Observer no longer exists.");
}
}
+ if (args.observer2 != null) {
+ try {
+ Bundle extras = extrasForInstallResult(res);
+ args.observer2.packageInstalled(res.name, extras, res.returnCode);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Observer no longer exists.");
+ }
+ }
} else {
Slog.e(TAG, "Bogus post-install token " + msg.arg1);
}
@@ -1193,6 +1218,21 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ Bundle extrasForInstallResult(PackageInstalledInfo res) {
+ Bundle extras = null;
+ switch (res.returnCode) {
+ case PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION: {
+ extras = new Bundle();
+ extras.putString(PackageManager.EXTRA_FAILURE_EXISTING_PERMISSION,
+ res.origPermission);
+ extras.putString(PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE,
+ res.origPackage);
+ break;
+ }
+ }
+ return extras;
+ }
+
void scheduleWriteSettingsLocked() {
if (!mHandler.hasMessages(WRITE_SETTINGS)) {
mHandler.sendEmptyMessageDelayed(WRITE_SETTINGS, WRITE_SETTINGS_DELAY);
@@ -1307,11 +1347,17 @@ public class PackageManagerService extends IPackageManager.Stub {
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
+ mAppStagingDir = new File(dataDir, "app-staging");
sUserManager = new UserManagerService(context, this,
mInstallLock, mPackages);
- readPermissions();
+ // Read permissions and features from system
+ readPermissions(Environment.buildPath(
+ Environment.getRootDirectory(), "etc", "permissions"), false);
+ // Only read features from OEM
+ readPermissions(Environment.buildPath(
+ Environment.getOemDirectory(), "etc", "permissions"), true);
mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();
@@ -1491,6 +1537,14 @@ public class PackageManagerService extends IPackageManager.Stub {
scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);
+ // Collect all OEM packages.
+ File oemAppDir = new File(Environment.getOemDirectory(), "app");
+ mOemInstallObserver = new AppDirObserver(
+ oemAppDir.getPath(), OBSERVER_EVENTS, true, false);
+ mOemInstallObserver.startWatching();
+ scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM
+ | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);
+
if (DEBUG_UPGRADE) Log.v(TAG, "Running installd update commands");
mInstaller.moveFiles();
@@ -1650,20 +1704,26 @@ public class PackageManagerService extends IPackageManager.Stub {
mSettings.readDefaultPreferredAppsLPw(this, 0);
}
+ // All the changes are done during package scanning.
+ mSettings.updateInternalDatabaseVersion();
+
// can downgrade to reader
mSettings.writeLPr();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
SystemClock.uptimeMillis());
- // Now after opening every single application zip, make sure they
- // are all flushed. Not really needed, but keeps things nice and
- // tidy.
- Runtime.getRuntime().gc();
mRequiredVerifierPackage = getRequiredVerifierLPr();
} // synchronized (mPackages)
} // synchronized (mInstallLock)
+
+ mInstallerService = new PackageInstallerService(context, this, mAppStagingDir);
+
+ // Now after opening every single application zip, make sure they
+ // are all flushed. Not really needed, but keeps things nice and
+ // tidy.
+ Runtime.getRuntime().gc();
}
private static void pruneDexFiles(File cacheDir) {
@@ -1782,9 +1842,8 @@ public class PackageManagerService extends IPackageManager.Stub {
mSettings.removePackageLPw(ps.name);
}
- void readPermissions() {
+ void readPermissions(File libraryDir, boolean onlyFeatures) {
// Read permissions from .../etc/permission directory.
- File libraryDir = new File(Environment.getRootDirectory(), "etc/permissions");
if (!libraryDir.exists() || !libraryDir.isDirectory()) {
Slog.w(TAG, "No directory " + libraryDir + ", skipping");
return;
@@ -1810,16 +1869,16 @@ public class PackageManagerService extends IPackageManager.Stub {
continue;
}
- readPermissionsFromXml(f);
+ readPermissionsFromXml(f, onlyFeatures);
}
// Read permissions from .../etc/permissions/platform.xml last so it will take precedence
final File permFile = new File(Environment.getRootDirectory(),
"etc/permissions/platform.xml");
- readPermissionsFromXml(permFile);
+ readPermissionsFromXml(permFile, onlyFeatures);
}
- private void readPermissionsFromXml(File permFile) {
+ private void readPermissionsFromXml(File permFile, boolean onlyFeatures) {
FileReader permReader = null;
try {
permReader = new FileReader(permFile);
@@ -1841,7 +1900,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
String name = parser.getName();
- if ("group".equals(name)) {
+ if ("group".equals(name) && !onlyFeatures) {
String gidStr = parser.getAttributeValue(null, "gid");
if (gidStr != null) {
int gid = Process.getGidForName(gidStr);
@@ -1853,7 +1912,7 @@ public class PackageManagerService extends IPackageManager.Stub {
XmlUtils.skipCurrentTag(parser);
continue;
- } else if ("permission".equals(name)) {
+ } else if ("permission".equals(name) && !onlyFeatures) {
String perm = parser.getAttributeValue(null, "name");
if (perm == null) {
Slog.w(TAG, "<permission> without name at "
@@ -1864,7 +1923,7 @@ public class PackageManagerService extends IPackageManager.Stub {
perm = perm.intern();
readPermission(parser, perm);
- } else if ("assign-permission".equals(name)) {
+ } else if ("assign-permission".equals(name) && !onlyFeatures) {
String perm = parser.getAttributeValue(null, "name");
if (perm == null) {
Slog.w(TAG, "<assign-permission> without name at "
@@ -1896,7 +1955,7 @@ public class PackageManagerService extends IPackageManager.Stub {
perms.add(perm);
XmlUtils.skipCurrentTag(parser);
- } else if ("library".equals(name)) {
+ } else if ("library".equals(name) && !onlyFeatures) {
String lname = parser.getAttributeValue(null, "name");
String lfile = parser.getAttributeValue(null, "file");
if (lname == null) {
@@ -2208,7 +2267,8 @@ public class PackageManagerService extends IPackageManager.Stub {
if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) == 0) {
return null;
}
- pkg = new PackageParser.Package(packageName);
+ // TODO: teach about reading split name
+ pkg = new PackageParser.Package(packageName, null);
pkg.applicationInfo.packageName = packageName;
pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY;
pkg.applicationInfo.publicSourceDir = ps.resourcePathString;
@@ -2306,6 +2366,14 @@ public class PackageManagerService extends IPackageManager.Stub {
});
}
+ void freeStorage(long freeStorageSize) throws IOException {
+ synchronized (mInstallLock) {
+ if (mInstaller.freeCache(freeStorageSize) < 0) {
+ throw new IOException("Failed to free enough space");
+ }
+ }
+ }
+
@Override
public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
@@ -2328,6 +2396,24 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
+ public boolean activitySupportsIntent(ComponentName component, Intent intent,
+ String resolvedType) {
+ synchronized (mPackages) {
+ PackageParser.Activity a = mActivities.mActivities.get(component);
+ if (a == null) {
+ return false;
+ }
+ for (int i=0; i<a.intents.size(); i++) {
+ if (a.intents.get(i).match(intent.getAction(), resolvedType, intent.getScheme(),
+ intent.getData(), intent.getCategories(), TAG) >= 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ @Override
public ActivityInfo getReceiverInfo(ComponentName component, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get receiver info");
@@ -2471,10 +2557,9 @@ public class PackageManagerService extends IPackageManager.Stub {
* Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS
* or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller.
* @param message the message to log on security exception
- * @return
*/
- private void enforceCrossUserPermission(int callingUid, int userId,
- boolean requireFullPermission, String message) {
+ void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission,
+ String message) {
if (userId < 0) {
throw new IllegalArgumentException("Invalid userId " + userId);
}
@@ -2550,7 +2635,35 @@ public class PackageManagerService extends IPackageManager.Stub {
//if (pi1.descriptionRes != pi2.descriptionRes) return false;
return true;
}
-
+
+ int permissionInfoFootprint(PermissionInfo info) {
+ int size = info.name.length();
+ if (info.nonLocalizedLabel != null) size += info.nonLocalizedLabel.length();
+ if (info.nonLocalizedDescription != null) size += info.nonLocalizedDescription.length();
+ return size;
+ }
+
+ int calculateCurrentPermissionFootprintLocked(BasePermission tree) {
+ int size = 0;
+ for (BasePermission perm : mSettings.mPermissions.values()) {
+ if (perm.uid == tree.uid) {
+ size += perm.name.length() + permissionInfoFootprint(perm.perm.info);
+ }
+ }
+ return size;
+ }
+
+ void enforcePermissionCapLocked(PermissionInfo info, BasePermission tree) {
+ // We calculate the max size of permissions defined by this uid and throw
+ // if that plus the size of 'info' would exceed our stated maximum.
+ if (tree.uid != Process.SYSTEM_UID) {
+ final int curTreeSize = calculateCurrentPermissionFootprintLocked(tree);
+ if (curTreeSize + permissionInfoFootprint(info) > MAX_PERMISSION_TREE_FOOTPRINT) {
+ throw new SecurityException("Permission tree size cap exceeded");
+ }
+ }
+ }
+
boolean addPermissionLocked(PermissionInfo info, boolean async) {
if (info.labelRes == 0 && info.nonLocalizedLabel == null) {
throw new SecurityException("Label must be specified in permission");
@@ -2561,6 +2674,7 @@ public class PackageManagerService extends IPackageManager.Stub {
boolean changed = true;
int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
if (added) {
+ enforcePermissionCapLocked(info, tree);
bp = new BasePermission(info.name, tree.sourcePackage,
BasePermission.TYPE_DYNAMIC);
} else if (bp.type != BasePermission.TYPE_DYNAMIC) {
@@ -2849,6 +2963,59 @@ public class PackageManagerService extends IPackageManager.Stub {
return PackageManager.SIGNATURE_NO_MATCH;
}
+ /**
+ * If the database version for this type of package (internal storage or
+ * external storage) is less than the version where package signatures
+ * were updated, return true.
+ */
+ private boolean isCompatSignatureUpdateNeeded(PackageParser.Package scannedPkg) {
+ return (isExternal(scannedPkg) && mSettings.isExternalDatabaseVersionOlderThan(
+ DatabaseVersion.SIGNATURE_END_ENTITY))
+ || (!isExternal(scannedPkg) && mSettings.isInternalDatabaseVersionOlderThan(
+ DatabaseVersion.SIGNATURE_END_ENTITY));
+ }
+
+ /**
+ * Used for backward compatibility to make sure any packages with
+ * certificate chains get upgraded to the new style. {@code existingSigs}
+ * will be in the old format (since they were stored on disk from before the
+ * system upgrade) and {@code scannedSigs} will be in the newer format.
+ */
+ private int compareSignaturesCompat(PackageSignatures existingSigs,
+ PackageParser.Package scannedPkg) {
+ if (!isCompatSignatureUpdateNeeded(scannedPkg)) {
+ return PackageManager.SIGNATURE_NO_MATCH;
+ }
+
+ HashSet<Signature> existingSet = new HashSet<Signature>();
+ for (Signature sig : existingSigs.mSignatures) {
+ existingSet.add(sig);
+ }
+ HashSet<Signature> scannedCompatSet = new HashSet<Signature>();
+ for (Signature sig : scannedPkg.mSignatures) {
+ try {
+ Signature[] chainSignatures = sig.getChainSignatures();
+ for (Signature chainSig : chainSignatures) {
+ scannedCompatSet.add(chainSig);
+ }
+ } catch (CertificateEncodingException e) {
+ scannedCompatSet.add(sig);
+ }
+ }
+ /*
+ * Make sure the expanded scanned set contains all signatures in the
+ * existing one.
+ */
+ if (scannedCompatSet.equals(existingSet)) {
+ // Migrate the old signatures to the new scheme.
+ existingSigs.assignSignatures(scannedPkg.mSignatures);
+ // The new KeySets will be re-added later in the scanning process.
+ mSettings.mKeySetManager.removeAppKeySetData(scannedPkg.packageName);
+ return PackageManager.SIGNATURE_MATCH;
+ }
+ return PackageManager.SIGNATURE_NO_MATCH;
+ }
+
@Override
public String[] getPackagesForUid(int uid) {
uid = UserHandle.getAppId(uid);
@@ -3004,6 +3171,63 @@ public class PackageManagerService extends IPackageManager.Stub {
return null;
}
+ private ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType,
+ int flags, List<ResolveInfo> query, boolean debug, int userId) {
+ final int N = query.size();
+ PersistentPreferredIntentResolver ppir = mSettings.mPersistentPreferredActivities
+ .get(userId);
+ // Get the list of persistent preferred activities that handle the intent
+ if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for presistent preferred activities...");
+ List<PersistentPreferredActivity> pprefs = ppir != null
+ ? ppir.queryIntent(intent, resolvedType,
+ (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId)
+ : null;
+ if (pprefs != null && pprefs.size() > 0) {
+ final int M = pprefs.size();
+ for (int i=0; i<M; i++) {
+ final PersistentPreferredActivity ppa = pprefs.get(i);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Checking PersistentPreferredActivity ds="
+ + (ppa.countDataSchemes() > 0 ? ppa.getDataScheme(0) : "<none>")
+ + "\n component=" + ppa.mComponent);
+ ppa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ }
+ final ActivityInfo ai = getActivityInfo(ppa.mComponent,
+ flags | PackageManager.GET_DISABLED_COMPONENTS, userId);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Found persistent preferred activity:");
+ if (ai != null) {
+ ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ } else {
+ Slog.v(TAG, " null");
+ }
+ }
+ if (ai == null) {
+ // This previously registered persistent preferred activity
+ // component is no longer known. Ignore it and do NOT remove it.
+ continue;
+ }
+ for (int j=0; j<N; j++) {
+ final ResolveInfo ri = query.get(j);
+ if (!ri.activityInfo.applicationInfo.packageName
+ .equals(ai.applicationInfo.packageName)) {
+ continue;
+ }
+ if (!ri.activityInfo.name.equals(ai.name)) {
+ continue;
+ }
+ // Found a persistent preference that can handle the intent.
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Returning persistent preferred activity: " +
+ ri.activityInfo.packageName + "/" + ri.activityInfo.name);
+ }
+ return ri;
+ }
+ }
+ }
+ return null;
+ }
+
ResolveInfo findPreferredActivity(Intent intent, String resolvedType, int flags,
List<ResolveInfo> query, int priority, boolean always,
boolean removeMatches, boolean debug, int userId) {
@@ -3014,6 +3238,16 @@ public class PackageManagerService extends IPackageManager.Stub {
intent = intent.getSelector();
}
if (DEBUG_PREFERRED) intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
+
+ // Try to find a matching persistent preferred activity.
+ ResolveInfo pri = findPersistentPreferredActivityLP(intent, resolvedType, flags, query,
+ debug, userId);
+
+ // If a persistent preferred activity matched, use it.
+ if (pri != null) {
+ return pri;
+ }
+
PreferredIntentResolver pir = mSettings.mPreferredActivities.get(userId);
// Get the list of preferred activities that handle the intent
if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for preferred activities...");
@@ -3136,6 +3370,33 @@ public class PackageManagerService extends IPackageManager.Stub {
return null;
}
+ /*
+ * Returns if intent can be forwarded from the userId from to dest
+ */
+ @Override
+ public boolean canForwardTo(Intent intent, String resolvedType, int userIdFrom, int userIdDest) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ List<ForwardingIntentFilter> matches =
+ getMatchingForwardingIntentFilters(intent, resolvedType, userIdFrom);
+ if (matches != null) {
+ int size = matches.size();
+ for (int i = 0; i < size; i++) {
+ if (matches.get(i).getUserIdDest() == userIdDest) return true;
+ }
+ }
+ return false;
+ }
+
+ private List<ForwardingIntentFilter> getMatchingForwardingIntentFilters(Intent intent,
+ String resolvedType, int userId) {
+ ForwardingIntentResolver fir = mSettings.mForwardingIntentResolvers.get(userId);
+ if (fir != null) {
+ return fir.queryIntent(intent, resolvedType, false, userId);
+ }
+ return null;
+ }
+
@Override
public List<ResolveInfo> queryIntentActivities(Intent intent,
String resolvedType, int flags, int userId) {
@@ -3164,7 +3425,38 @@ public class PackageManagerService extends IPackageManager.Stub {
synchronized (mPackages) {
final String pkgName = intent.getPackage();
if (pkgName == null) {
- return mActivities.queryIntent(intent, resolvedType, flags, userId);
+ List<ResolveInfo> result =
+ mActivities.queryIntent(intent, resolvedType, flags, userId);
+ // Checking if we can forward the intent to another user
+ List<ForwardingIntentFilter> fifs =
+ getMatchingForwardingIntentFilters(intent, resolvedType, userId);
+ if (fifs != null) {
+ ForwardingIntentFilter forwardingIntentFilterWithResult = null;
+ HashSet<Integer> alreadyTriedUserIds = new HashSet<Integer>();
+ for (ForwardingIntentFilter fif : fifs) {
+ int userIdDest = fif.getUserIdDest();
+ // Two {@link ForwardingIntentFilter}s can have the same userIdDest and
+ // match the same an intent. For performance reasons, it is better not to
+ // run queryIntent twice for the same userId
+ if (!alreadyTriedUserIds.contains(userIdDest)) {
+ List<ResolveInfo> resultUser = mActivities.queryIntent(intent,
+ resolvedType, flags, userIdDest);
+ if (resultUser != null) {
+ forwardingIntentFilterWithResult = fif;
+ // As soon as there is a match in another user, we add the
+ // intentForwarderActivity to the list of ResolveInfo.
+ break;
+ }
+ alreadyTriedUserIds.add(userIdDest);
+ }
+ }
+ if (forwardingIntentFilterWithResult != null) {
+ ResolveInfo forwardingResolveInfo = createForwardingResolveInfo(
+ forwardingIntentFilterWithResult, userId);
+ result.add(forwardingResolveInfo);
+ }
+ }
+ return result;
}
final PackageParser.Package pkg = mPackages.get(pkgName);
if (pkg != null) {
@@ -3175,6 +3467,28 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ private ResolveInfo createForwardingResolveInfo(ForwardingIntentFilter fif, int userIdFrom) {
+ String className;
+ int userIdDest = fif.getUserIdDest();
+ if (userIdDest == UserHandle.USER_OWNER) {
+ className = FORWARD_INTENT_TO_USER_OWNER;
+ } else {
+ className = FORWARD_INTENT_TO_MANAGED_PROFILE;
+ }
+ ComponentName forwardingActivityComponentName = new ComponentName(
+ mAndroidApplication.packageName, className);
+ ActivityInfo forwardingActivityInfo = getActivityInfo(forwardingActivityComponentName, 0,
+ userIdFrom);
+ ResolveInfo forwardingResolveInfo = new ResolveInfo();
+ forwardingResolveInfo.activityInfo = forwardingActivityInfo;
+ forwardingResolveInfo.priority = 0;
+ forwardingResolveInfo.preferredOrder = 0;
+ forwardingResolveInfo.match = 0;
+ forwardingResolveInfo.isDefault = true;
+ forwardingResolveInfo.filter = fif;
+ return forwardingResolveInfo;
+ }
+
@Override
public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller,
Intent[] specifics, String[] specificTypes, Intent intent,
@@ -3870,27 +4184,26 @@ public class PackageManagerService extends IPackageManager.Stub {
private boolean collectCertificatesLI(PackageParser pp, PackageSetting ps,
PackageParser.Package pkg, File srcFile, int parseFlags) {
- if (GET_CERTIFICATES) {
- if (ps != null
- && ps.codePath.equals(srcFile)
- && ps.timeStamp == srcFile.lastModified()) {
- if (ps.signatures.mSignatures != null
- && ps.signatures.mSignatures.length != 0) {
- // Optimization: reuse the existing cached certificates
- // if the package appears to be unchanged.
- pkg.mSignatures = ps.signatures.mSignatures;
- return true;
- }
-
- Slog.w(TAG, "PackageSetting for " + ps.name + " is missing signatures. Collecting certs again to recover them.");
- } else {
- Log.i(TAG, srcFile.toString() + " changed; collecting certs");
- }
-
- if (!pp.collectCertificates(pkg, parseFlags)) {
- mLastScanError = pp.getParseError();
- return false;
+ if (ps != null
+ && ps.codePath.equals(srcFile)
+ && ps.timeStamp == srcFile.lastModified()
+ && !isCompatSignatureUpdateNeeded(pkg)) {
+ if (ps.signatures.mSignatures != null
+ && ps.signatures.mSignatures.length != 0) {
+ // Optimization: reuse the existing cached certificates
+ // if the package appears to be unchanged.
+ pkg.mSignatures = ps.signatures.mSignatures;
+ return true;
}
+
+ Slog.w(TAG, "PackageSetting for " + ps.name + " is missing signatures. Collecting certs again to recover them.");
+ } else {
+ Log.i(TAG, srcFile.toString() + " changed; collecting certs");
+ }
+
+ if (!pp.collectCertificates(pkg, parseFlags)) {
+ mLastScanError = pp.getParseError();
+ return false;
}
return true;
}
@@ -4124,22 +4437,32 @@ public class PackageManagerService extends IPackageManager.Stub {
return processName;
}
- private boolean verifySignaturesLP(PackageSetting pkgSetting,
- PackageParser.Package pkg) {
+ private boolean verifySignaturesLP(PackageSetting pkgSetting, PackageParser.Package pkg) {
if (pkgSetting.signatures.mSignatures != null) {
// Already existing package. Make sure signatures match
- if (compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures) !=
- PackageManager.SIGNATURE_MATCH) {
- Slog.e(TAG, "Package " + pkg.packageName
- + " signatures do not match the previously installed version; ignoring!");
- mLastScanError = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
- return false;
- }
+ boolean match = compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures)
+ == PackageManager.SIGNATURE_MATCH;
+ if (!match) {
+ match = compareSignaturesCompat(pkgSetting.signatures, pkg)
+ == PackageManager.SIGNATURE_MATCH;
+ }
+ if (!match) {
+ Slog.e(TAG, "Package " + pkg.packageName
+ + " signatures do not match the previously installed version; ignoring!");
+ mLastScanError = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+ return false;
+ }
}
// Check for shared user signatures
if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
- if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
- pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
+ // Already existing package. Make sure signatures match
+ boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
+ pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
+ if (!match) {
+ match = compareSignaturesCompat(pkgSetting.sharedUser.signatures, pkg)
+ == PackageManager.SIGNATURE_MATCH;
+ }
+ if (!match) {
Slog.e(TAG, "Package " + pkg.packageName
+ " has no signatures that match those in shared user "
+ pkgSetting.sharedUser.name + "; ignoring!");
@@ -5530,8 +5853,9 @@ public class PackageManagerService extends IPackageManager.Stub {
// discard the previous declaration and consider the system's to be
// canonical.
if (isSystemApp(p.owner)) {
- Slog.i(TAG, "New decl " + p.owner + " of permission "
- + p.info.name + " is system");
+ String msg = "New decl " + p.owner + " of permission "
+ + p.info.name + " is system";
+ reportSettingsProblem(Log.WARN, msg);
bp.sourcePackage = null;
}
}
@@ -6789,6 +7113,11 @@ public class PackageManagerService extends IPackageManager.Stub {
}
public final void addProvider(PackageParser.Provider p) {
+ if (mProviders.containsKey(p.getComponentName())) {
+ Slog.w(TAG, "Provider " + p.getComponentName() + " already defined; ignoring");
+ return;
+ }
+
mProviders.put(p.getComponentName(), p);
if (DEBUG_SHOW_INFO) {
Log.v(TAG, " "
@@ -7197,10 +7526,16 @@ public class PackageManagerService extends IPackageManager.Stub {
private final boolean mIsPrivileged;
}
+ /*
+ * The old-style observer methods all just trampoline to the newer signature with
+ * expanded install observer API. The older API continues to work but does not
+ * supply the additional details of the Observer2 API.
+ */
+
/* Called when a downloaded package installation has been confirmed by the user */
public void installPackage(
final Uri packageURI, final IPackageInstallObserver observer, final int flags) {
- installPackage(packageURI, observer, flags, null);
+ installPackageEtc(packageURI, observer, null, flags, null);
}
/* Called when a downloaded package installation has been confirmed by the user */
@@ -7208,8 +7543,8 @@ public class PackageManagerService extends IPackageManager.Stub {
public void installPackage(
final Uri packageURI, final IPackageInstallObserver observer, final int flags,
final String installerPackageName) {
- installPackageWithVerification(packageURI, observer, flags, installerPackageName, null,
- null, null);
+ installPackageWithVerificationEtc(packageURI, observer, null, flags,
+ installerPackageName, null, null, null);
}
@Override
@@ -7218,7 +7553,7 @@ public class PackageManagerService extends IPackageManager.Stub {
ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) {
VerificationParams verificationParams = new VerificationParams(verificationURI, null, null,
VerificationParams.NO_UID, manifestDigest);
- installPackageWithVerificationAndEncryption(packageURI, observer, flags,
+ installPackageWithVerificationAndEncryptionEtc(packageURI, observer, null, flags,
installerPackageName, verificationParams, encryptionParams);
}
@@ -7226,13 +7561,60 @@ public class PackageManagerService extends IPackageManager.Stub {
public void installPackageWithVerificationAndEncryption(Uri packageURI,
IPackageInstallObserver observer, int flags, String installerPackageName,
VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
+ installPackageWithVerificationAndEncryptionEtc(packageURI, observer, null, flags,
+ installerPackageName, verificationParams, encryptionParams);
+ }
+
+ /*
+ * And here are the "live" versions that take both observer arguments
+ */
+ public void installPackageEtc(
+ final Uri packageURI, final IPackageInstallObserver observer,
+ IPackageInstallObserver2 observer2, final int flags) {
+ installPackageEtc(packageURI, observer, observer2, flags, null);
+ }
+
+ public void installPackageEtc(
+ final Uri packageURI, final IPackageInstallObserver observer,
+ final IPackageInstallObserver2 observer2, final int flags,
+ final String installerPackageName) {
+ installPackageWithVerificationEtc(packageURI, observer, observer2, flags,
+ installerPackageName, null, null, null);
+ }
+
+ @Override
+ public void installPackageWithVerificationEtc(Uri packageURI, IPackageInstallObserver observer,
+ IPackageInstallObserver2 observer2,
+ int flags, String installerPackageName, Uri verificationURI,
+ ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) {
+ VerificationParams verificationParams = new VerificationParams(verificationURI, null, null,
+ VerificationParams.NO_UID, manifestDigest);
+ installPackageWithVerificationAndEncryptionEtc(packageURI, observer, observer2, flags,
+ installerPackageName, verificationParams, encryptionParams);
+ }
+
+ /*
+ * All of the installPackage...*() methods redirect to this one for the master implementation
+ */
+ public void installPackageWithVerificationAndEncryptionEtc(Uri packageURI,
+ IPackageInstallObserver observer, IPackageInstallObserver2 observer2,
+ int flags, String installerPackageName,
+ VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
+ if (observer == null && observer2 == null) {
+ throw new IllegalArgumentException("No install observer supplied");
+ }
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
null);
final int uid = Binder.getCallingUid();
if (isUserRestricted(UserHandle.getUserId(uid), UserManager.DISALLOW_INSTALL_APPS)) {
try {
- observer.packageInstalled("", PackageManager.INSTALL_FAILED_USER_RESTRICTED);
+ if (observer != null) {
+ observer.packageInstalled("", PackageManager.INSTALL_FAILED_USER_RESTRICTED);
+ }
+ if (observer2 != null) {
+ observer2.packageInstalled("", null, PackageManager.INSTALL_FAILED_USER_RESTRICTED);
+ }
} catch (RemoteException re) {
}
return;
@@ -7259,8 +7641,8 @@ public class PackageManagerService extends IPackageManager.Stub {
verificationParams.setInstallerUid(uid);
final Message msg = mHandler.obtainMessage(INIT_COPY);
- msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName,
- verificationParams, encryptionParams, user);
+ msg.obj = new InstallParams(packageURI, observer, observer2, filteredFlags,
+ installerPackageName, verificationParams, encryptionParams, user);
mHandler.sendMessage(msg);
}
@@ -7380,6 +7762,16 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ void installStage(String basePackageName, File stageDir, IPackageInstallObserver2 observer,
+ int flags) {
+ // TODO: install stage!
+ try {
+ observer.packageInstalled(basePackageName, null,
+ PackageManager.INSTALL_FAILED_INTERNAL_ERROR);
+ } catch (RemoteException ignored) {
+ }
+ }
+
/**
* @hide
*/
@@ -7389,11 +7781,7 @@ public class PackageManagerService extends IPackageManager.Stub {
null);
PackageSetting pkgSetting;
final int uid = Binder.getCallingUid();
- if (UserHandle.getUserId(uid) != userId) {
- mContext.enforceCallingPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "installExistingPackage for user " + userId);
- }
+ enforceCrossUserPermission(uid, userId, true, "installExistingPackage for user " + userId);
if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
return PackageManager.INSTALL_FAILED_USER_RESTRICTED;
}
@@ -7427,7 +7815,7 @@ public class PackageManagerService extends IPackageManager.Stub {
return PackageManager.INSTALL_SUCCEEDED;
}
- private boolean isUserRestricted(int userId, String restrictionKey) {
+ boolean isUserRestricted(int userId, String restrictionKey) {
Bundle restrictions = sUserManager.getUserRestrictions(userId);
if (restrictions.getBoolean(restrictionKey, false)) {
Log.w(TAG, "User is restricted: " + restrictionKey);
@@ -7884,32 +8272,34 @@ public class PackageManagerService extends IPackageManager.Stub {
mSuccess = getPackageSizeInfoLI(mStats.packageName, mStats.userHandle, mStats);
}
- final boolean mounted;
- if (Environment.isExternalStorageEmulated()) {
- mounted = true;
- } else {
- final String status = Environment.getExternalStorageState();
- mounted = (Environment.MEDIA_MOUNTED.equals(status)
- || Environment.MEDIA_MOUNTED_READ_ONLY.equals(status));
- }
+ if (mSuccess) {
+ final boolean mounted;
+ if (Environment.isExternalStorageEmulated()) {
+ mounted = true;
+ } else {
+ final String status = Environment.getExternalStorageState();
+ mounted = (Environment.MEDIA_MOUNTED.equals(status)
+ || Environment.MEDIA_MOUNTED_READ_ONLY.equals(status));
+ }
- if (mounted) {
- final UserEnvironment userEnv = new UserEnvironment(mStats.userHandle);
+ if (mounted) {
+ final UserEnvironment userEnv = new UserEnvironment(mStats.userHandle);
- mStats.externalCacheSize = calculateDirectorySize(mContainerService,
- userEnv.buildExternalStorageAppCacheDirs(mStats.packageName));
+ mStats.externalCacheSize = calculateDirectorySize(mContainerService,
+ userEnv.buildExternalStorageAppCacheDirs(mStats.packageName));
- mStats.externalDataSize = calculateDirectorySize(mContainerService,
- userEnv.buildExternalStorageAppDataDirs(mStats.packageName));
+ mStats.externalDataSize = calculateDirectorySize(mContainerService,
+ userEnv.buildExternalStorageAppDataDirs(mStats.packageName));
- // Always subtract cache size, since it's a subdirectory
- mStats.externalDataSize -= mStats.externalCacheSize;
+ // Always subtract cache size, since it's a subdirectory
+ mStats.externalDataSize -= mStats.externalCacheSize;
- mStats.externalMediaSize = calculateDirectorySize(mContainerService,
- userEnv.buildExternalStorageAppMediaDirs(mStats.packageName));
+ mStats.externalMediaSize = calculateDirectorySize(mContainerService,
+ userEnv.buildExternalStorageAppMediaDirs(mStats.packageName));
- mStats.externalObbSize = calculateDirectorySize(mContainerService,
- userEnv.buildExternalStorageAppObbDirs(mStats.packageName));
+ mStats.externalObbSize = calculateDirectorySize(mContainerService,
+ userEnv.buildExternalStorageAppObbDirs(mStats.packageName));
+ }
}
}
@@ -7951,6 +8341,7 @@ public class PackageManagerService extends IPackageManager.Stub {
class InstallParams extends HandlerParams {
final IPackageInstallObserver observer;
+ final IPackageInstallObserver2 observer2;
int flags;
private final Uri mPackageURI;
@@ -7962,13 +8353,14 @@ public class PackageManagerService extends IPackageManager.Stub {
final ContainerEncryptionParams encryptionParams;
InstallParams(Uri packageURI,
- IPackageInstallObserver observer, int flags,
- String installerPackageName, VerificationParams verificationParams,
+ IPackageInstallObserver observer, IPackageInstallObserver2 observer2,
+ int flags, String installerPackageName, VerificationParams verificationParams,
ContainerEncryptionParams encryptionParams, UserHandle user) {
super(user);
this.mPackageURI = packageURI;
this.flags = flags;
this.observer = observer;
+ this.observer2 = observer2;
this.installerPackageName = installerPackageName;
this.verificationParams = verificationParams;
this.encryptionParams = encryptionParams;
@@ -8541,6 +8933,7 @@ public class PackageManagerService extends IPackageManager.Stub {
static abstract class InstallArgs {
final IPackageInstallObserver observer;
+ final IPackageInstallObserver2 observer2;
// Always refers to PackageManager flags only
final int flags;
final Uri packageURI;
@@ -8549,12 +8942,14 @@ public class PackageManagerService extends IPackageManager.Stub {
final UserHandle user;
final String instructionSet;
- InstallArgs(Uri packageURI, IPackageInstallObserver observer, int flags,
- String installerPackageName, ManifestDigest manifestDigest,
+ InstallArgs(Uri packageURI,
+ IPackageInstallObserver observer, IPackageInstallObserver2 observer2,
+ int flags, String installerPackageName, ManifestDigest manifestDigest,
UserHandle user, String instructionSet) {
this.packageURI = packageURI;
this.flags = flags;
this.observer = observer;
+ this.observer2 = observer2;
this.installerPackageName = installerPackageName;
this.manifestDigest = manifestDigest;
this.user = user;
@@ -8612,14 +9007,14 @@ public class PackageManagerService extends IPackageManager.Stub {
boolean created = false;
FileInstallArgs(InstallParams params) {
- super(params.getPackageUri(), params.observer, params.flags,
+ super(params.getPackageUri(), params.observer, params.observer2, params.flags,
params.installerPackageName, params.getManifestDigest(),
params.getUser(), null /* instruction set */);
}
FileInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath,
String instructionSet) {
- super(null, null, 0, null, null, null, instructionSet);
+ super(null, null, null, 0, null, null, null, instructionSet);
File codeFile = new File(fullCodePath);
installDir = codeFile.getParentFile();
codeFileName = fullCodePath;
@@ -8628,7 +9023,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
FileInstallArgs(Uri packageURI, String pkgName, String dataDir, String instructionSet) {
- super(packageURI, null, 0, null, null, null, instructionSet);
+ super(packageURI, null, null, 0, null, null, null, instructionSet);
installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir;
String apkName = getNextCodePath(null, pkgName, ".apk");
codeFileName = new File(installDir, apkName + ".apk").getPath();
@@ -8952,14 +9347,14 @@ public class PackageManagerService extends IPackageManager.Stub {
String libraryPath;
AsecInstallArgs(InstallParams params) {
- super(params.getPackageUri(), params.observer, params.flags,
+ super(params.getPackageUri(), params.observer, params.observer2, params.flags,
params.installerPackageName, params.getManifestDigest(),
params.getUser(), null /* instruction set */);
}
AsecInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath,
String instructionSet, boolean isExternal, boolean isForwardLocked) {
- super(null, null, (isExternal ? PackageManager.INSTALL_EXTERNAL : 0)
+ super(null, null, null, (isExternal ? PackageManager.INSTALL_EXTERNAL : 0)
| (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0),
null, null, null, instructionSet);
// Extract cid from fullCodePath
@@ -8971,7 +9366,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
AsecInstallArgs(String cid, String instructionSet, boolean isForwardLocked) {
- super(null, null, (isAsecExternal(cid) ? PackageManager.INSTALL_EXTERNAL : 0)
+ super(null, null, null, (isAsecExternal(cid) ? PackageManager.INSTALL_EXTERNAL : 0)
| (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0),
null, null, null, instructionSet);
this.cid = cid;
@@ -8980,7 +9375,7 @@ public class PackageManagerService extends IPackageManager.Stub {
AsecInstallArgs(Uri packageURI, String cid, String instructionSet,
boolean isExternal, boolean isForwardLocked) {
- super(packageURI, null, (isExternal ? PackageManager.INSTALL_EXTERNAL : 0)
+ super(packageURI, null, null, (isExternal ? PackageManager.INSTALL_EXTERNAL : 0)
| (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0),
null, null, null, instructionSet);
this.cid = cid;
@@ -9317,6 +9712,10 @@ public class PackageManagerService extends IPackageManager.Stub {
PackageParser.Package pkg;
int returnCode;
PackageRemovedInfo removedInfo;
+
+ // In some error cases we want to convey more info back to the observer
+ String origPackage;
+ String origPermission;
}
/*
@@ -9555,7 +9954,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
// Successfully disabled the old package. Now proceed with re-installation
- mLastScanError = PackageManager.INSTALL_SUCCEEDED;
+ res.returnCode = mLastScanError = PackageManager.INSTALL_SUCCEEDED;
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
newPackage = scanPackageLI(pkg, parseFlags, scanMode, 0, user);
if (newPackage == null) {
@@ -9568,9 +9967,20 @@ public class PackageManagerService extends IPackageManager.Stub {
final PackageSetting newPkgSetting = (PackageSetting)newPackage.mExtras;
newPkgSetting.firstInstallTime = oldPkgSetting.firstInstallTime;
newPkgSetting.lastUpdateTime = System.currentTimeMillis();
+
+ // is the update attempting to change shared user? that isn't going to work...
+ if (oldPkgSetting.sharedUser != newPkgSetting.sharedUser) {
+ Slog.w(TAG, "Forbidding shared user change from " + oldPkgSetting.sharedUser
+ + " to " + newPkgSetting.sharedUser);
+ res.returnCode = PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
+ updatedSettings = true;
+ }
+ }
+
+ if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
+ updateSettingsLI(newPackage, installerPackageName, allUsers, perUserInstalled, res);
+ updatedSettings = true;
}
- updateSettingsLI(newPackage, installerPackageName, allUsers, perUserInstalled, res);
- updatedSettings = true;
}
if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
@@ -9714,7 +10124,7 @@ public class PackageManagerService extends IPackageManager.Stub {
return;
}
}
- if (GET_CERTIFICATES && !pp.collectCertificates(pkg, parseFlags)) {
+ if (!pp.collectCertificates(pkg, parseFlags)) {
res.returnCode = pp.getParseError();
return;
}
@@ -9743,6 +10153,27 @@ public class PackageManagerService extends IPackageManager.Stub {
String oldCodePath = null;
boolean systemApp = false;
synchronized (mPackages) {
+ // Check whether the newly-scanned package wants to define an already-defined perm
+ int N = pkg.permissions.size();
+ for (int i = 0; i < N; i++) {
+ PackageParser.Permission perm = pkg.permissions.get(i);
+ BasePermission bp = mSettings.mPermissions.get(perm.info.name);
+ if (bp != null) {
+ // If the defining package is signed with our cert, it's okay. This
+ // also includes the "updating the same package" case, of course.
+ if (compareSignatures(bp.packageSetting.signatures.mSignatures,
+ pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
+ Slog.w(TAG, "Package " + pkg.packageName
+ + " attempting to redeclare permission " + perm.info.name
+ + " already owned by " + bp.sourcePackage);
+ res.returnCode = PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
+ res.origPermission = perm.info.name;
+ res.origPackage = bp.sourcePackage;
+ return;
+ }
+ }
+ }
+
// Check if installing already existing package
if ((pFlags&PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
String oldName = mSettings.mRenamedPackages.get(pkgName);
@@ -10611,6 +11042,9 @@ public class PackageManagerService extends IPackageManager.Stub {
final IPackageStatsObserver observer) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.GET_PACKAGE_SIZE, null);
+ if (packageName == null) {
+ throw new IllegalArgumentException("Attempt to get size of null packageName");
+ }
PackageStats stats = new PackageStats(packageName, userHandle);
@@ -10924,6 +11358,102 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
+ public void addPersistentPreferredActivity(IntentFilter filter, ComponentName activity,
+ int userId) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException(
+ "addPersistentPreferredActivity can only be run by the system");
+ }
+ if (filter.countActions() == 0) {
+ Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
+ return;
+ }
+ synchronized (mPackages) {
+ Slog.i(TAG, "Adding persistent preferred activity " + activity + " for user " + userId +
+ " :");
+ filter.dump(new LogPrinter(Log.INFO, TAG), " ");
+ mSettings.editPersistentPreferredActivitiesLPw(userId).addFilter(
+ new PersistentPreferredActivity(filter, activity));
+ mSettings.writePackageRestrictionsLPr(userId);
+ }
+ }
+
+ @Override
+ public void clearPackagePersistentPreferredActivities(String packageName, int userId) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException(
+ "clearPackagePersistentPreferredActivities can only be run by the system");
+ }
+ ArrayList<PersistentPreferredActivity> removed = null;
+ boolean changed = false;
+ synchronized (mPackages) {
+ for (int i=0; i<mSettings.mPersistentPreferredActivities.size(); i++) {
+ final int thisUserId = mSettings.mPersistentPreferredActivities.keyAt(i);
+ PersistentPreferredIntentResolver ppir = mSettings.mPersistentPreferredActivities
+ .valueAt(i);
+ if (userId != thisUserId) {
+ continue;
+ }
+ Iterator<PersistentPreferredActivity> it = ppir.filterIterator();
+ while (it.hasNext()) {
+ PersistentPreferredActivity ppa = it.next();
+ // Mark entry for removal only if it matches the package name.
+ if (ppa.mComponent.getPackageName().equals(packageName)) {
+ if (removed == null) {
+ removed = new ArrayList<PersistentPreferredActivity>();
+ }
+ removed.add(ppa);
+ }
+ }
+ if (removed != null) {
+ for (int j=0; j<removed.size(); j++) {
+ PersistentPreferredActivity ppa = removed.get(j);
+ ppir.removeFilter(ppa);
+ }
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ mSettings.writePackageRestrictionsLPr(userId);
+ }
+ }
+ }
+
+ @Override
+ public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int userIdOrig,
+ int userIdDest) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ if (filter.countActions() == 0) {
+ Slog.w(TAG, "Cannot set a forwarding intent filter with no filter actions");
+ return;
+ }
+ synchronized (mPackages) {
+ mSettings.editForwardingIntentResolverLPw(userIdOrig).addFilter(
+ new ForwardingIntentFilter(filter, removable, userIdDest));
+ mSettings.writePackageRestrictionsLPr(userIdOrig);
+ }
+ }
+
+ @Override
+ public void clearForwardingIntentFilters(int userIdOrig) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ synchronized (mPackages) {
+ ForwardingIntentResolver fir = mSettings.editForwardingIntentResolverLPw(userIdOrig);
+ HashSet<ForwardingIntentFilter> set =
+ new HashSet<ForwardingIntentFilter>(fir.filterSet());
+ for (ForwardingIntentFilter fif : set) {
+ if (fif.isRemovable()) fir.removeFilter(fif);
+ }
+ mSettings.writePackageRestrictionsLPr(userIdOrig);
+ }
+ }
+
+ @Override
public ComponentName getHomeActivities(List<ResolveInfo> allHomeCandidates) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
@@ -11255,6 +11785,8 @@ public class PackageManagerService extends IPackageManager.Stub {
public static final int DUMP_KEYSETS = 1 << 11;
+ public static final int DUMP_VERSION = 1 << 12;
+
public static final int OPTION_SHOW_FILTERS = 1 << 0;
private int mTypes;
@@ -11353,6 +11885,7 @@ public class PackageManagerService extends IPackageManager.Stub {
pw.println(" s[hared-users]: dump shared user IDs");
pw.println(" m[essages]: print collected runtime messages");
pw.println(" v[erifiers]: print package verifier info");
+ pw.println(" version: print database version info");
pw.println(" <package.name>: info about given package");
pw.println(" k[eysets]: print known keysets");
return;
@@ -11401,6 +11934,8 @@ public class PackageManagerService extends IPackageManager.Stub {
dumpState.setDump(DumpState.DUMP_MESSAGES);
} else if ("v".equals(cmd) || "verifiers".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_VERIFIERS);
+ } else if ("version".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_VERSION);
} else if ("k".equals(cmd) || "keysets".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_KEYSETS);
}
@@ -11412,6 +11947,24 @@ public class PackageManagerService extends IPackageManager.Stub {
// reader
synchronized (mPackages) {
+ if (dumpState.isDumping(DumpState.DUMP_VERSION) && packageName == null) {
+ if (!checkin) {
+ if (dumpState.onTitlePrinted())
+ pw.println();
+ pw.println("Database versions:");
+ pw.print(" SDK Version:");
+ pw.print(" internal=");
+ pw.print(mSettings.mInternalSdkPlatform);
+ pw.print(" external=");
+ pw.println(mSettings.mExternalSdkPlatform);
+ pw.print(" DB Version:");
+ pw.print(" internal=");
+ pw.print(mSettings.mInternalDatabaseVersion);
+ pw.print(" external=");
+ pw.println(mSettings.mExternalDatabaseVersion);
+ }
+ }
+
if (dumpState.isDumping(DumpState.DUMP_VERIFIERS) && packageName == null) {
if (!checkin) {
if (dumpState.onTitlePrinted())
@@ -11945,6 +12498,9 @@ public class PackageManagerService extends IPackageManager.Stub {
| (regrantPermissions
? (UPDATE_PERMISSIONS_REPLACE_PKG|UPDATE_PERMISSIONS_REPLACE_ALL)
: 0));
+
+ mSettings.updateExternalDatabaseVersion();
+
// can downgrade to reader
// Persist settings
mSettings.writeLPr();
@@ -12382,4 +12938,9 @@ public class PackageManagerService extends IPackageManager.Stub {
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public IPackageInstaller getPackageInstaller() {
+ return mInstallerService;
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 44803a5..7fee372 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -20,14 +20,11 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageUserState;
-import android.content.pm.UserInfo;
import android.util.SparseArray;
import java.io.File;
import java.util.HashSet;
-import java.util.List;
/**
* Settings base class for pending and resolved classes.
diff --git a/services/core/java/com/android/server/pm/PersistentPreferredActivity.java b/services/core/java/com/android/server/pm/PersistentPreferredActivity.java
new file mode 100644
index 0000000..0d4cdf9
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PersistentPreferredActivity.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.content.ComponentName;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import java.io.IOException;
+
+class PersistentPreferredActivity extends IntentFilter {
+ private static final String ATTR_NAME = "name"; // component name
+ private static final String ATTR_FILTER = "filter"; // filter
+
+ private static final String TAG = "PersistentPreferredActivity";
+
+ private static final boolean DEBUG_FILTERS = false;
+
+ final ComponentName mComponent;
+
+ PersistentPreferredActivity(IntentFilter filter, ComponentName activity) {
+ super(filter);
+ mComponent = activity;
+ }
+
+ PersistentPreferredActivity(XmlPullParser parser) throws XmlPullParserException, IOException {
+ String shortComponent = parser.getAttributeValue(null, ATTR_NAME);
+ mComponent = ComponentName.unflattenFromString(shortComponent);
+ if (mComponent == null) {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Error in package manager settings: " +
+ "Bad activity name " + shortComponent +
+ " at " + parser.getPositionDescription());
+ }
+ int outerDepth = parser.getDepth();
+ String tagName = parser.getName();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ tagName = parser.getName();
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ } else if (type == XmlPullParser.START_TAG) {
+ if (tagName.equals(ATTR_FILTER)) {
+ break;
+ } else {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Unknown element: " + tagName +
+ " at " + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+ if (tagName.equals(ATTR_FILTER)) {
+ readFromXml(parser);
+ } else {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Missing element filter at " +
+ parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ public void writeToXml(XmlSerializer serializer) throws IOException {
+ serializer.attribute(null, ATTR_NAME, mComponent.flattenToShortString());
+ serializer.startTag(null, ATTR_FILTER);
+ super.writeToXml(serializer);
+ serializer.endTag(null, ATTR_FILTER);
+ }
+
+ @Override
+ public String toString() {
+ return "PersistentPreferredActivity{0x" + Integer.toHexString(System.identityHashCode(this))
+ + " " + mComponent.flattenToShortString() + "}";
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java b/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java
new file mode 100644
index 0000000..9c8a9bd
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import java.io.PrintWriter;
+
+import com.android.server.IntentResolver;
+
+public class PersistentPreferredIntentResolver
+ extends IntentResolver<PersistentPreferredActivity, PersistentPreferredActivity> {
+ @Override
+ protected PersistentPreferredActivity[] newArray(int size) {
+ return new PersistentPreferredActivity[size];
+ }
+
+ @Override
+ protected boolean isPackageForFilter(String packageName, PersistentPreferredActivity filter) {
+ return packageName.equals(filter.mComponent.getPackageName());
+ }
+}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 34a4e1d..d37b240 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -46,7 +46,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
-import android.content.pm.KeySet;
import android.content.pm.PackageCleanItem;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
@@ -61,7 +60,6 @@ import android.os.FileUtils;
import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
-import android.util.LongSparseArray;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
@@ -72,7 +70,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
-import java.security.PublicKey;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -93,6 +90,34 @@ import libcore.io.IoUtils;
final class Settings {
private static final String TAG = "PackageSettings";
+ /**
+ * Current version of the package database. Set it to the latest version in
+ * the {@link DatabaseVersion} class below to ensure the database upgrade
+ * doesn't happen repeatedly.
+ * <p>
+ * Note that care should be taken to make sure all database upgrades are
+ * idempotent.
+ */
+ private static final int CURRENT_DATABASE_VERSION = DatabaseVersion.SIGNATURE_END_ENTITY;
+
+ /**
+ * This class contains constants that can be referred to from upgrade code.
+ * Insert constant values here that describe the upgrade reason. The version
+ * code must be monotonically increasing.
+ */
+ public static class DatabaseVersion {
+ /**
+ * The initial version of the database.
+ */
+ public static final int FIRST_VERSION = 1;
+
+ /**
+ * Migrating the Signature array from the entire certificate chain to
+ * just the signing certificate.
+ */
+ public static final int SIGNATURE_END_ENTITY = 2;
+ }
+
private static final boolean DEBUG_STOPPED = false;
private static final boolean DEBUG_MU = false;
@@ -104,6 +129,10 @@ final class Settings {
private static final String TAG_ENABLED_COMPONENTS = "enabled-components";
private static final String TAG_PACKAGE_RESTRICTIONS = "package-restrictions";
private static final String TAG_PACKAGE = "pkg";
+ private static final String TAG_PERSISTENT_PREFERRED_ACTIVITIES =
+ "persistent-preferred-activities";
+ static final String TAG_FORWARDING_INTENT_FILTERS =
+ "forwarding-intent-filters";
private static final String ATTR_NAME = "name";
private static final String ATTR_USER = "user";
@@ -135,6 +164,14 @@ final class Settings {
int mInternalSdkPlatform;
int mExternalSdkPlatform;
+ /**
+ * The current database version for apps on internal storage. This is
+ * used to upgrade the format of the packages.xml database not necessarily
+ * tied to an SDK version.
+ */
+ int mInternalDatabaseVersion;
+ int mExternalDatabaseVersion;
+
Boolean mReadExternalStorageEnforced;
/** Device identity for the purpose of package verification. */
@@ -145,6 +182,15 @@ final class Settings {
final SparseArray<PreferredIntentResolver> mPreferredActivities =
new SparseArray<PreferredIntentResolver>();
+ // The persistent preferred activities of the user's profile/device owner
+ // associated with particular intent filters.
+ final SparseArray<PersistentPreferredIntentResolver> mPersistentPreferredActivities =
+ new SparseArray<PersistentPreferredIntentResolver>();
+
+ // For every user, it is used to find to which other users the intent can be forwarded.
+ final SparseArray<ForwardingIntentResolver> mForwardingIntentResolvers =
+ new SparseArray<ForwardingIntentResolver>();
+
final HashMap<String, SharedUserSetting> mSharedUsers =
new HashMap<String, SharedUserSetting>();
private final ArrayList<Object> mUserIds = new ArrayList<Object>();
@@ -788,6 +834,24 @@ final class Settings {
return pir;
}
+ PersistentPreferredIntentResolver editPersistentPreferredActivitiesLPw(int userId) {
+ PersistentPreferredIntentResolver ppir = mPersistentPreferredActivities.get(userId);
+ if (ppir == null) {
+ ppir = new PersistentPreferredIntentResolver();
+ mPersistentPreferredActivities.put(userId, ppir);
+ }
+ return ppir;
+ }
+
+ ForwardingIntentResolver editForwardingIntentResolverLPw(int userId) {
+ ForwardingIntentResolver fir = mForwardingIntentResolvers.get(userId);
+ if (fir == null) {
+ fir = new ForwardingIntentResolver();
+ mForwardingIntentResolvers.put(userId, fir);
+ }
+ return fir;
+ }
+
private File getUserPackagesStateFile(int userId) {
return new File(Environment.getUserSystemDirectory(userId), "package-restrictions.xml");
}
@@ -818,6 +882,40 @@ final class Settings {
}
}
+ /**
+ * Returns whether the current database has is older than {@code version}
+ * for apps on internal storage.
+ */
+ public boolean isInternalDatabaseVersionOlderThan(int version) {
+ return mInternalDatabaseVersion < version;
+ }
+
+ /**
+ * Returns whether the current database has is older than {@code version}
+ * for apps on external storage.
+ */
+ public boolean isExternalDatabaseVersionOlderThan(int version) {
+ return mExternalDatabaseVersion < version;
+ }
+
+ /**
+ * Updates the database version for apps on internal storage. Called after
+ * call the updates to the database format are done for apps on internal
+ * storage after the initial start-up scan.
+ */
+ public void updateInternalDatabaseVersion() {
+ mInternalDatabaseVersion = CURRENT_DATABASE_VERSION;
+ }
+
+ /**
+ * Updates the database version for apps on internal storage. Called after
+ * call the updates to the database format are done for apps on internal
+ * storage after the initial start-up scan.
+ */
+ public void updateExternalDatabaseVersion() {
+ mExternalDatabaseVersion = CURRENT_DATABASE_VERSION;
+ }
+
private void readPreferredActivitiesLPw(XmlPullParser parser, int userId)
throws XmlPullParserException, IOException {
int outerDepth = parser.getDepth();
@@ -847,6 +945,50 @@ final class Settings {
}
}
+ private void readPersistentPreferredActivitiesLPw(XmlPullParser parser, int userId)
+ throws XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals(TAG_ITEM)) {
+ PersistentPreferredActivity ppa = new PersistentPreferredActivity(parser);
+ editPersistentPreferredActivitiesLPw(userId).addFilter(ppa);
+ } else {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Unknown element under <" + TAG_PERSISTENT_PREFERRED_ACTIVITIES + ">: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ private void readForwardingIntentFiltersLPw(XmlPullParser parser, int userId)
+ throws XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals(TAG_ITEM)) {
+ ForwardingIntentFilter fif = new ForwardingIntentFilter(parser);
+ editForwardingIntentResolverLPw(userId).addFilter(fif);
+ } else {
+ String msg = "Unknown element under " + TAG_FORWARDING_INTENT_FILTERS + ": " +
+ parser.getName();
+ PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
void readPackageRestrictionsLPr(int userId) {
if (DEBUG_MU) {
Log.i(TAG, "Reading package restrictions for user=" + userId);
@@ -973,6 +1115,10 @@ final class Settings {
enabledCaller, enabledComponents, disabledComponents);
} else if (tagName.equals("preferred-activities")) {
readPreferredActivitiesLPw(parser, userId);
+ } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
+ readPersistentPreferredActivitiesLPw(parser, userId);
+ } else if (tagName.equals(TAG_FORWARDING_INTENT_FILTERS)) {
+ readForwardingIntentFiltersLPw(parser, userId);
} else {
Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: "
+ parser.getName());
@@ -1036,6 +1182,34 @@ final class Settings {
serializer.endTag(null, "preferred-activities");
}
+ void writePersistentPreferredActivitiesLPr(XmlSerializer serializer, int userId)
+ throws IllegalArgumentException, IllegalStateException, IOException {
+ serializer.startTag(null, TAG_PERSISTENT_PREFERRED_ACTIVITIES);
+ PersistentPreferredIntentResolver ppir = mPersistentPreferredActivities.get(userId);
+ if (ppir != null) {
+ for (final PersistentPreferredActivity ppa : ppir.filterSet()) {
+ serializer.startTag(null, TAG_ITEM);
+ ppa.writeToXml(serializer);
+ serializer.endTag(null, TAG_ITEM);
+ }
+ }
+ serializer.endTag(null, TAG_PERSISTENT_PREFERRED_ACTIVITIES);
+ }
+
+ void writeForwardingIntentFiltersLPr(XmlSerializer serializer, int userId)
+ throws IllegalArgumentException, IllegalStateException, IOException {
+ serializer.startTag(null, TAG_FORWARDING_INTENT_FILTERS);
+ ForwardingIntentResolver fir = mForwardingIntentResolvers.get(userId);
+ if (fir != null) {
+ for (final ForwardingIntentFilter fif : fir.filterSet()) {
+ serializer.startTag(null, TAG_ITEM);
+ fif.writeToXml(serializer);
+ serializer.endTag(null, TAG_ITEM);
+ }
+ }
+ serializer.endTag(null, TAG_FORWARDING_INTENT_FILTERS);
+ }
+
void writePackageRestrictionsLPr(int userId) {
if (DEBUG_MU) {
Log.i(TAG, "Writing package restrictions for user=" + userId);
@@ -1132,6 +1306,10 @@ final class Settings {
writePreferredActivitiesLPr(serializer, userId, true);
+ writePersistentPreferredActivitiesLPr(serializer, userId);
+
+ writeForwardingIntentFiltersLPr(serializer, userId);
+
serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);
serializer.endDocument();
@@ -1308,6 +1486,11 @@ final class Settings {
serializer.attribute(null, "external", Integer.toString(mExternalSdkPlatform));
serializer.endTag(null, "last-platform-version");
+ serializer.startTag(null, "database-version");
+ serializer.attribute(null, "internal", Integer.toString(mInternalDatabaseVersion));
+ serializer.attribute(null, "external", Integer.toString(mExternalDatabaseVersion));
+ serializer.endTag(null, "database-version");
+
if (mVerifierDeviceIdentity != null) {
serializer.startTag(null, "verifier");
serializer.attribute(null, "device", mVerifierDeviceIdentity.toString());
@@ -1740,6 +1923,14 @@ final class Settings {
// Upgrading from old single-user implementation;
// these are the preferred activities for user 0.
readPreferredActivitiesLPw(parser, 0);
+ } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
+ // TODO: check whether this is okay! as it is very
+ // similar to how preferred-activities are treated
+ readPersistentPreferredActivitiesLPw(parser, 0);
+ } else if (tagName.equals(TAG_FORWARDING_INTENT_FILTERS)) {
+ // TODO: check whether this is okay! as it is very
+ // similar to how preferred-activities are treated
+ readForwardingIntentFiltersLPw(parser, 0);
} else if (tagName.equals("updated-package")) {
readDisabledSysPackageLPw(parser);
} else if (tagName.equals("cleaning-package")) {
@@ -1779,6 +1970,19 @@ final class Settings {
}
} catch (NumberFormatException e) {
}
+ } else if (tagName.equals("database-version")) {
+ mInternalDatabaseVersion = mExternalDatabaseVersion = 0;
+ try {
+ String internalDbVersionString = parser.getAttributeValue(null, "internal");
+ if (internalDbVersionString != null) {
+ mInternalDatabaseVersion = Integer.parseInt(internalDbVersionString);
+ }
+ String externalDbVersionString = parser.getAttributeValue(null, "external");
+ if (externalDbVersionString != null) {
+ mExternalDatabaseVersion = Integer.parseInt(externalDbVersionString);
+ }
+ } catch (NumberFormatException ignored) {
+ }
} else if (tagName.equals("verifier")) {
final String deviceIdentity = parser.getAttributeValue(null, "device");
try {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 557b6a3..131d05b 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -40,6 +40,7 @@ import android.os.Handler;
import android.os.IUserManager;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.AtomicFile;
@@ -50,6 +51,7 @@ import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import android.util.Xml;
+import com.android.internal.app.IAppOpsService;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
@@ -92,6 +94,7 @@ public class UserManagerService extends IUserManager.Stub {
private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber";
private static final String ATTR_PARTIAL = "partial";
private static final String ATTR_USER_VERSION = "version";
+ private static final String ATTR_PROFILE_GROUP_ID = "profileGroupId";
private static final String TAG_USERS = "users";
private static final String TAG_USER = "user";
private static final String TAG_RESTRICTIONS = "restrictions";
@@ -161,6 +164,8 @@ public class UserManagerService extends IUserManager.Stub {
private int mNextSerialNumber;
private int mUserVersion = 0;
+ private IAppOpsService mAppOpsService;
+
private static UserManagerService sInstance;
public static UserManagerService getInstance() {
@@ -235,6 +240,15 @@ public class UserManagerService extends IUserManager.Stub {
void systemReady() {
mUserPackageMonitor.register(mContext, null, UserHandle.ALL, false);
userForeground(UserHandle.USER_OWNER);
+ mAppOpsService = IAppOpsService.Stub.asInterface(
+ ServiceManager.getService(Context.APP_OPS_SERVICE));
+ for (int i = 0; i < mUserIds.length; ++i) {
+ try {
+ mAppOpsService.setUserRestrictions(mUserRestrictions.get(mUserIds[i]), mUserIds[i]);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
+ }
+ }
}
@Override
@@ -256,6 +270,70 @@ public class UserManagerService extends IUserManager.Stub {
}
@Override
+ public List<UserInfo> getProfiles(int userId, boolean enabledOnly) {
+ if (userId != UserHandle.getCallingUserId()) {
+ checkManageUsersPermission("getting profiles related to user " + userId);
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mPackagesLock) {
+ return getProfilesLocked(userId, enabledOnly);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /** Assume permissions already checked and caller's identity cleared */
+ private List<UserInfo> getProfilesLocked(int userId, boolean enabledOnly) {
+ UserInfo user = getUserInfoLocked(userId);
+ ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
+ for (int i = 0; i < mUsers.size(); i++) {
+ UserInfo profile = mUsers.valueAt(i);
+ if (!isProfileOf(user, profile)) {
+ continue;
+ }
+ if (enabledOnly && !profile.isEnabled()) {
+ continue;
+ }
+ users.add(profile);
+ }
+ return users;
+ }
+
+ @Override
+ public UserInfo getProfileParent(int userHandle) {
+ checkManageUsersPermission("get the profile parent");
+ synchronized (mPackagesLock) {
+ UserInfo profile = getUserInfoLocked(userHandle);
+ int parentUserId = profile.profileGroupId;
+ if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
+ return null;
+ } else {
+ return getUserInfoLocked(parentUserId);
+ }
+ }
+ }
+
+ private boolean isProfileOf(UserInfo user, UserInfo profile) {
+ return user.id == profile.id ||
+ (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
+ && user.profileGroupId == profile.profileGroupId);
+ }
+
+ @Override
+ public void setUserEnabled(int userId) {
+ checkManageUsersPermission("enable user");
+ synchronized (mPackagesLock) {
+ UserInfo info = getUserInfoLocked(userId);
+ if (info != null && !info.isEnabled()) {
+ info.flags ^= UserInfo.FLAG_DISABLED;
+ writeUserLocked(info);
+ }
+ }
+ }
+
+ @Override
public UserInfo getUserInfo(int userId) {
checkManageUsersPermission("query user");
synchronized (mPackagesLock) {
@@ -405,7 +483,7 @@ public class UserManagerService extends IUserManager.Stub {
synchronized (mPackagesLock) {
Bundle restrictions = mUserRestrictions.get(userId);
- return restrictions != null ? restrictions : Bundle.EMPTY;
+ return restrictions != null ? new Bundle(restrictions) : new Bundle();
}
}
@@ -417,6 +495,14 @@ public class UserManagerService extends IUserManager.Stub {
synchronized (mPackagesLock) {
mUserRestrictions.get(userId).clear();
mUserRestrictions.get(userId).putAll(restrictions);
+ long token = Binder.clearCallingIdentity();
+ try {
+ mAppOpsService.setUserRestrictions(mUserRestrictions.get(userId), userId);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
writeUserLocked(mUsers.get(userId));
}
}
@@ -431,7 +517,7 @@ public class UserManagerService extends IUserManager.Stub {
/**
* Enforces that only the system UID or root's UID or apps that have the
- * {@link android.Manifest.permission.MANAGE_USERS MANAGE_USERS}
+ * {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS}
* permission can make certain calls to the UserManager.
*
* @param message used as message if SecurityException is thrown
@@ -660,6 +746,10 @@ public class UserManagerService extends IUserManager.Stub {
if (userInfo.partial) {
serializer.attribute(null, ATTR_PARTIAL, "true");
}
+ if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
+ serializer.attribute(null, ATTR_PROFILE_GROUP_ID,
+ Integer.toString(userInfo.profileGroupId));
+ }
serializer.startTag(null, TAG_NAME);
serializer.text(userInfo.name);
@@ -679,6 +769,18 @@ public class UserManagerService extends IUserManager.Stub {
writeBoolean(serializer, restrictions, UserManager.DISALLOW_USB_FILE_TRANSFER);
writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_CREDENTIALS);
writeBoolean(serializer, restrictions, UserManager.DISALLOW_REMOVE_USER);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_DEBUGGING_FEATURES);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_VPN);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_TETHERING);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_FACTORY_RESET);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_ADD_USER);
+ writeBoolean(serializer, restrictions, UserManager.ENSURE_VERIFY_APPS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_APPS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_UNMUTE_MICROPHONE);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_ADJUST_VOLUME);
serializer.endTag(null, TAG_RESTRICTIONS);
}
serializer.endTag(null, TAG_USER);
@@ -743,6 +845,7 @@ public class UserManagerService extends IUserManager.Stub {
long salt = 0L;
String pinHash = null;
int failedAttempts = 0;
+ int profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
long lastAttemptTime = 0L;
boolean partial = false;
Bundle restrictions = new Bundle();
@@ -780,6 +883,14 @@ public class UserManagerService extends IUserManager.Stub {
pinHash = parser.getAttributeValue(null, ATTR_PIN_HASH);
failedAttempts = readIntAttribute(parser, ATTR_FAILED_ATTEMPTS, 0);
lastAttemptTime = readLongAttribute(parser, ATTR_LAST_RETRY_MS, 0L);
+ profileGroupId = readIntAttribute(parser, ATTR_PROFILE_GROUP_ID,
+ UserInfo.NO_PROFILE_GROUP_ID);
+ if (profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+ // This attribute was added and renamed during development of L.
+ // TODO Remove upgrade path by 1st May 2014
+ profileGroupId = readIntAttribute(parser, "relatedGroupId",
+ UserInfo.NO_PROFILE_GROUP_ID);
+ }
String valueString = parser.getAttributeValue(null, ATTR_PARTIAL);
if ("true".equals(valueString)) {
partial = true;
@@ -809,6 +920,19 @@ public class UserManagerService extends IUserManager.Stub {
readBoolean(parser, restrictions, UserManager.DISALLOW_USB_FILE_TRANSFER);
readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_CREDENTIALS);
readBoolean(parser, restrictions, UserManager.DISALLOW_REMOVE_USER);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_DEBUGGING_FEATURES);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_VPN);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_TETHERING);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_FACTORY_RESET);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_ADD_USER);
+ readBoolean(parser, restrictions, UserManager.ENSURE_VERIFY_APPS);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_APPS);
+ readBoolean(parser, restrictions,
+ UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_UNMUTE_MICROPHONE);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_ADJUST_VOLUME);
}
}
}
@@ -818,6 +942,7 @@ public class UserManagerService extends IUserManager.Stub {
userInfo.creationTime = creationTime;
userInfo.lastLoggedInTime = lastLoggedInTime;
userInfo.partial = partial;
+ userInfo.profileGroupId = profileGroupId;
mUserRestrictions.append(id, restrictions);
if (salt != 0L) {
RestrictionsPinState pinState = mRestrictionsPinStates.get(id);
@@ -933,14 +1058,32 @@ public class UserManagerService extends IUserManager.Stub {
}
@Override
+ public UserInfo createProfileForUser(String name, int flags, int userId) {
+ checkManageUsersPermission("Only the system can create users");
+ if (userId != UserHandle.USER_OWNER) {
+ Slog.w(LOG_TAG, "Only user owner can have profiles");
+ return null;
+ }
+ return createUserInternal(name, flags, userId);
+ }
+
+ @Override
public UserInfo createUser(String name, int flags) {
checkManageUsersPermission("Only the system can create users");
+ return createUserInternal(name, flags, UserHandle.USER_NULL);
+ }
+ private UserInfo createUserInternal(String name, int flags, int parentId) {
final long ident = Binder.clearCallingIdentity();
- final UserInfo userInfo;
+ UserInfo userInfo = null;
try {
synchronized (mInstallLock) {
synchronized (mPackagesLock) {
+ UserInfo parent = null;
+ if (parentId != UserHandle.USER_NULL) {
+ parent = getUserInfoLocked(parentId);
+ if (parent == null) return null;
+ }
if (isUserLimitReachedLocked()) return null;
int userId = getNextAvailableIdLocked();
userInfo = new UserInfo(userId, name, null, flags);
@@ -952,6 +1095,13 @@ public class UserManagerService extends IUserManager.Stub {
Environment.getUserSystemDirectory(userInfo.id).mkdirs();
mUsers.put(userId, userInfo);
writeUserListLocked();
+ if (parent != null) {
+ if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+ parent.profileGroupId = parent.id;
+ writeUserLocked(parent);
+ }
+ userInfo.profileGroupId = parent.profileGroupId;
+ }
writeUserLocked(userInfo);
mPm.createNewUserLILPw(userId, userPath);
userInfo.partial = false;
@@ -976,7 +1126,7 @@ public class UserManagerService extends IUserManager.Stub {
/**
* Removes a user and all data directories created for that user. This method should be called
* after the user's processes have been terminated.
- * @param id the user's id
+ * @param userHandle the user's id
*/
public boolean removeUser(int userHandle) {
checkManageUsersPermission("Only the system can remove users");
@@ -987,12 +1137,28 @@ public class UserManagerService extends IUserManager.Stub {
return false;
}
mRemovingUserIds.put(userHandle, true);
+ try {
+ mAppOpsService.removeUser(userHandle);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user", e);
+ }
// Set this to a partially created user, so that the user will be purged
// on next startup, in case the runtime stops now before stopping and
// removing the user completely.
user.partial = true;
+ // Mark it as disabled, so that it isn't returned any more when
+ // profiles are queried.
+ user.flags |= UserInfo.FLAG_DISABLED;
writeUserLocked(user);
}
+
+ if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
+ && user.isManagedProfile()) {
+ // Send broadcast to notify system that the user removed was a
+ // managed user.
+ sendProfileRemovedBroadcast(user.profileGroupId, user.id);
+ }
+
if (DBG) Slog.i(LOG_TAG, "Stopping user " + userHandle);
int res;
try {
@@ -1090,6 +1256,13 @@ public class UserManagerService extends IUserManager.Stub {
parent.delete();
}
+ private void sendProfileRemovedBroadcast(int parentUserId, int removedUserId) {
+ Intent managedProfileIntent = new Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+ managedProfileIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ managedProfileIntent.putExtra(Intent.EXTRA_USER, new UserHandle(removedUserId));
+ mContext.sendBroadcastAsUser(managedProfileIntent, new UserHandle(parentUserId), null);
+ }
+
@Override
public Bundle getApplicationRestrictions(String packageName) {
return getApplicationRestrictionsForUser(packageName, UserHandle.getCallingUserId());
@@ -1118,6 +1291,12 @@ public class UserManagerService extends IUserManager.Stub {
// Write the restrictions to XML
writeApplicationRestrictionsLocked(packageName, restrictions, userId);
}
+
+ // Notify package of changes via an intent - only sent to explicitly registered receivers.
+ Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
+ changeIntent.setPackage(packageName);
+ changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcastAsUser(changeIntent, new UserHandle(userId));
}
@Override
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index f431b0d..e244bde 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -38,6 +38,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -142,7 +143,7 @@ final class Notifier {
* Called when a wake lock is acquired.
*/
public void onWakeLockAcquired(int flags, String tag, String packageName,
- int ownerUid, int ownerPid, WorkSource workSource) {
+ int ownerUid, int ownerPid, WorkSource workSource, String historyTag) {
if (DEBUG) {
Slog.d(TAG, "onWakeLockAcquired: flags=" + flags + ", tag=\"" + tag
+ "\", packageName=" + packageName
@@ -152,10 +153,14 @@ final class Notifier {
try {
final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+ boolean unimportantForLogging = (flags&PowerManager.UNIMPORTANT_FOR_LOGGING) != 0
+ && ownerUid == Process.SYSTEM_UID;
if (workSource != null) {
- mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag, monitorType);
+ mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag, historyTag,
+ monitorType, unimportantForLogging);
} else {
- mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, monitorType);
+ mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, historyTag,
+ monitorType, unimportantForLogging);
// XXX need to deal with disabled operations.
mAppOps.startOperation(AppOpsManager.getToken(mAppOps),
AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
@@ -166,10 +171,43 @@ final class Notifier {
}
/**
+ * Called when a wake lock is changing.
+ */
+ public void onWakeLockChanging(int flags, String tag, String packageName,
+ int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
+ int newFlags, String newTag, String newPackageName, int newOwnerUid,
+ int newOwnerPid, WorkSource newWorkSource, String newHistoryTag) {
+
+ if (workSource != null && newWorkSource != null) {
+ final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+ final int newMonitorType = getBatteryStatsWakeLockMonitorType(newFlags);
+ boolean unimportantForLogging = (newFlags&PowerManager.UNIMPORTANT_FOR_LOGGING) != 0
+ && newOwnerUid == Process.SYSTEM_UID;
+ if (DEBUG) {
+ Slog.d(TAG, "onWakeLockChanging: flags=" + newFlags + ", tag=\"" + newTag
+ + "\", packageName=" + newPackageName
+ + ", ownerUid=" + newOwnerUid + ", ownerPid=" + newOwnerPid
+ + ", workSource=" + newWorkSource);
+ }
+ try {
+ mBatteryStats.noteChangeWakelockFromSource(workSource, ownerPid, tag, historyTag,
+ monitorType, newWorkSource, newOwnerPid, newTag, newHistoryTag,
+ newMonitorType, unimportantForLogging);
+ } catch (RemoteException ex) {
+ // Ignore
+ }
+ } else {
+ onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag);
+ onWakeLockAcquired(newFlags, newTag, newPackageName, newOwnerUid, newOwnerPid,
+ newWorkSource, newHistoryTag);
+ }
+ }
+
+ /**
* Called when a wake lock is released.
*/
public void onWakeLockReleased(int flags, String tag, String packageName,
- int ownerUid, int ownerPid, WorkSource workSource) {
+ int ownerUid, int ownerPid, WorkSource workSource, String historyTag) {
if (DEBUG) {
Slog.d(TAG, "onWakeLockReleased: flags=" + flags + ", tag=\"" + tag
+ "\", packageName=" + packageName
@@ -180,9 +218,10 @@ final class Notifier {
try {
final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
if (workSource != null) {
- mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag, monitorType);
+ mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag, historyTag,
+ monitorType);
} else {
- mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag, monitorType);
+ mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag, historyTag, monitorType);
mAppOps.finishOperation(AppOpsManager.getToken(mAppOps),
AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 7138c3e..f0b7861 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -18,6 +18,7 @@ package com.android.server.power;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.os.BackgroundThread;
import com.android.server.BatteryService;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
@@ -168,6 +169,10 @@ public final class PowerManagerService extends com.android.server.SystemService
// Poll interval in milliseconds for watching boot animation finished.
private static final int BOOT_ANIMATION_POLL_INTERVAL = 200;
+ // Used to send the hint to the PowerHAL indicating transitions
+ // from and to the low power mode.
+ private static final int POWER_HINT_LOW_POWER_MODE = 5;
+
private final Context mContext;
private LightsManager mLightsManager;
private BatteryService mBatteryService;
@@ -395,12 +400,19 @@ public final class PowerManagerService extends com.android.server.SystemService
// Time when we last logged a warning about calling userActivity() without permission.
private long mLastWarningAboutUserActivityPermission = Long.MIN_VALUE;
+ // If true, the device is in low power mode.
+ private boolean mLowPowerModeEnabled;
+
+ private final ArrayList<PowerManagerInternal.LowPowerModeListener> mLowPowerModeListeners
+ = new ArrayList<PowerManagerInternal.LowPowerModeListener>();
+
private native void nativeInit();
private static native void nativeAcquireSuspendBlocker(String name);
private static native void nativeReleaseSuspendBlocker(String name);
private static native void nativeSetInteractive(boolean enable);
private static native void nativeSetAutoSuspend(boolean enable);
+ private static native void nativeSendPowerHint(int hintId, int data);
public PowerManagerService(Context context) {
super(context);
@@ -491,6 +503,7 @@ public final class PowerManagerService extends com.android.server.SystemService
filter = new IntentFilter();
filter.addAction(Intent.ACTION_BOOT_COMPLETED);
+ filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiver(new BootCompletedReceiver(), filter, null, mHandler);
filter = new IntentFilter();
@@ -529,7 +542,12 @@ public final class PowerManagerService extends com.android.server.SystemService
resolver.registerContentObserver(Settings.System.getUriFor(
Settings.System.SCREEN_BRIGHTNESS_MODE),
false, mSettingsObserver, UserHandle.USER_ALL);
-
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_MODE),
+ false, mSettingsObserver, UserHandle.USER_ALL);
// Go.
readConfigurationLocked();
updateSettingsLocked();
@@ -609,6 +627,26 @@ public final class PowerManagerService extends com.android.server.SystemService
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
+ final boolean lowPowerModeEnabled = Settings.Global.getInt(resolver,
+ Settings.Global.LOW_POWER_MODE, 0) != 0;
+ if (lowPowerModeEnabled != mLowPowerModeEnabled) {
+ powerHintInternal(POWER_HINT_LOW_POWER_MODE, lowPowerModeEnabled ? 1 : 0);
+ mLowPowerModeEnabled = lowPowerModeEnabled;
+ BackgroundThread.getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ ArrayList<PowerManagerInternal.LowPowerModeListener> listeners;
+ synchronized (mLock) {
+ listeners = new ArrayList<PowerManagerInternal.LowPowerModeListener>(
+ mLowPowerModeListeners);
+ }
+ for (int i=0; i<listeners.size(); i++) {
+ listeners.get(i).onLowPowerModeChanged(lowPowerModeEnabled);
+ }
+ }
+ });
+ }
+
mDirty |= DIRTY_SETTINGS;
}
@@ -618,7 +656,7 @@ public final class PowerManagerService extends com.android.server.SystemService
}
private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName,
- WorkSource ws, int uid, int pid) {
+ WorkSource ws, String historyTag, int uid, int pid) {
synchronized (mLock) {
if (DEBUG_SPEW) {
Slog.d(TAG, "acquireWakeLockInternal: lock=" + Objects.hashCode(lock)
@@ -632,12 +670,12 @@ public final class PowerManagerService extends com.android.server.SystemService
wakeLock = mWakeLocks.get(index);
if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) {
// Update existing wake lock. This shouldn't happen but is harmless.
- notifyWakeLockReleasedLocked(wakeLock);
- wakeLock.updateProperties(flags, tag, packageName, ws, uid, pid);
- notifyWakeLockAcquiredLocked(wakeLock);
+ notifyWakeLockChangingLocked(wakeLock, flags, tag, packageName,
+ uid, pid, ws, historyTag);
+ wakeLock.updateProperties(flags, tag, packageName, ws, historyTag, uid, pid);
}
} else {
- wakeLock = new WakeLock(lock, flags, tag, packageName, ws, uid, pid);
+ wakeLock = new WakeLock(lock, flags, tag, packageName, ws, historyTag, uid, pid);
try {
lock.linkToDeath(wakeLock, 0);
} catch (RemoteException ex) {
@@ -733,7 +771,7 @@ public final class PowerManagerService extends com.android.server.SystemService
}
}
- private void updateWakeLockWorkSourceInternal(IBinder lock, WorkSource ws) {
+ private void updateWakeLockWorkSourceInternal(IBinder lock, WorkSource ws, String historyTag) {
synchronized (mLock) {
int index = findWakeLockIndexLocked(lock);
if (index < 0) {
@@ -751,9 +789,11 @@ public final class PowerManagerService extends com.android.server.SystemService
}
if (!wakeLock.hasSameWorkSource(ws)) {
- notifyWakeLockReleasedLocked(wakeLock);
+ notifyWakeLockChangingLocked(wakeLock, wakeLock.mFlags, wakeLock.mTag,
+ wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid,
+ ws, historyTag);
+ wakeLock.mHistoryTag = historyTag;
wakeLock.updateWorkSource(ws);
- notifyWakeLockAcquiredLocked(wakeLock);
}
}
}
@@ -772,15 +812,26 @@ public final class PowerManagerService extends com.android.server.SystemService
if (mSystemReady) {
wakeLock.mNotifiedAcquired = true;
mNotifier.onWakeLockAcquired(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName,
- wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource);
+ wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource,
+ wakeLock.mHistoryTag);
+ }
+ }
+
+ private void notifyWakeLockChangingLocked(WakeLock wakeLock, int flags, String tag,
+ String packageName, int uid, int pid, WorkSource ws, String historyTag) {
+ if (mSystemReady && wakeLock.mNotifiedAcquired) {
+ mNotifier.onWakeLockChanging(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName,
+ wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource,
+ wakeLock.mHistoryTag, flags, tag, packageName, uid, pid, ws, historyTag);
}
}
private void notifyWakeLockReleasedLocked(WakeLock wakeLock) {
if (mSystemReady && wakeLock.mNotifiedAcquired) {
wakeLock.mNotifiedAcquired = false;
- mNotifier.onWakeLockReleased(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName,
- wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource);
+ mNotifier.onWakeLockReleased(wakeLock.mFlags, wakeLock.mTag,
+ wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid,
+ wakeLock.mWorkSource, wakeLock.mHistoryTag);
}
}
@@ -1606,6 +1657,8 @@ public final class PowerManagerService extends com.android.server.SystemService
mDisplayPowerRequest.blockScreenOn = mScreenOnBlocker.isHeld();
+ mDisplayPowerRequest.lowPowerMode = mLowPowerModeEnabled;
+
mDisplayReady = mDisplayManagerInternal.requestPowerState(mDisplayPowerRequest,
mRequestWaitForNegativeProximity);
mRequestWaitForNegativeProximity = false;
@@ -1990,6 +2043,10 @@ public final class PowerManagerService extends com.android.server.SystemService
}
}
+ private void powerHintInternal(int hintId, int data) {
+ nativeSendPowerHint(hintId, data);
+ }
+
/**
* Low-level function turn the device off immediately, without trying
* to be clean. Most people should use {@link ShutdownThread} for a clean shutdown.
@@ -1999,9 +2056,10 @@ public final class PowerManagerService extends com.android.server.SystemService
}
/**
- * Low-level function to reboot the device. On success, this function
- * doesn't return. If more than 5 seconds passes from the time,
- * a reboot is requested, this method returns.
+ * Low-level function to reboot the device. On success, this
+ * function doesn't return. If more than 20 seconds passes from
+ * the time a reboot is requested (120 seconds for reboot to
+ * recovery), this method returns.
*
* @param reason code to pass to the kernel (e.g. "recovery"), or null.
*/
@@ -2009,9 +2067,24 @@ public final class PowerManagerService extends com.android.server.SystemService
if (reason == null) {
reason = "";
}
- SystemProperties.set("sys.powerctl", "reboot," + reason);
+ long duration;
+ if (reason.equals(PowerManager.REBOOT_RECOVERY)) {
+ // If we are rebooting to go into recovery, instead of
+ // setting sys.powerctl directly we'll start the
+ // pre-recovery service which will do some preparation for
+ // recovery and then reboot for us.
+ //
+ // This preparation can take more than 20 seconds if
+ // there's a very large update package, so lengthen the
+ // timeout.
+ SystemProperties.set("ctl.start", "pre-recovery");
+ duration = 120 * 1000L;
+ } else {
+ SystemProperties.set("sys.powerctl", "reboot," + reason);
+ duration = 20 * 1000L;
+ }
try {
- Thread.sleep(20000);
+ Thread.sleep(duration);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
@@ -2267,17 +2340,19 @@ public final class PowerManagerService extends com.android.server.SystemService
public String mTag;
public final String mPackageName;
public WorkSource mWorkSource;
+ public String mHistoryTag;
public final int mOwnerUid;
public final int mOwnerPid;
public boolean mNotifiedAcquired;
public WakeLock(IBinder lock, int flags, String tag, String packageName,
- WorkSource workSource, int ownerUid, int ownerPid) {
+ WorkSource workSource, String historyTag, int ownerUid, int ownerPid) {
mLock = lock;
mFlags = flags;
mTag = tag;
mPackageName = packageName;
mWorkSource = copyWorkSource(workSource);
+ mHistoryTag = historyTag;
mOwnerUid = ownerUid;
mOwnerPid = ownerPid;
}
@@ -2297,7 +2372,7 @@ public final class PowerManagerService extends com.android.server.SystemService
}
public void updateProperties(int flags, String tag, String packageName,
- WorkSource workSource, int ownerUid, int ownerPid) {
+ WorkSource workSource, String historyTag, int ownerUid, int ownerPid) {
if (!mPackageName.equals(packageName)) {
throw new IllegalStateException("Existing wake lock package name changed: "
+ mPackageName + " to " + packageName);
@@ -2313,6 +2388,7 @@ public final class PowerManagerService extends com.android.server.SystemService
mFlags = flags;
mTag = tag;
updateWorkSource(workSource);
+ mHistoryTag = historyTag;
}
public boolean hasSameWorkSource(WorkSource workSource) {
@@ -2471,12 +2547,21 @@ public final class PowerManagerService extends com.android.server.SystemService
@Override // Binder call
public void acquireWakeLockWithUid(IBinder lock, int flags, String tag,
String packageName, int uid) {
- acquireWakeLock(lock, flags, tag, packageName, new WorkSource(uid));
+ if (uid < 0) {
+ uid = Binder.getCallingUid();
+ }
+ acquireWakeLock(lock, flags, tag, packageName, new WorkSource(uid), null);
+ }
+
+ @Override // Binder call
+ public void powerHint(int hintId, int data) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+ powerHintInternal(hintId, data);
}
@Override // Binder call
public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
- WorkSource ws) {
+ WorkSource ws, String historyTag) {
if (lock == null) {
throw new IllegalArgumentException("lock must not be null");
}
@@ -2497,7 +2582,7 @@ public final class PowerManagerService extends com.android.server.SystemService
final int pid = Binder.getCallingPid();
final long ident = Binder.clearCallingIdentity();
try {
- acquireWakeLockInternal(lock, flags, tag, packageName, ws, uid, pid);
+ acquireWakeLockInternal(lock, flags, tag, packageName, ws, historyTag, uid, pid);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2531,11 +2616,11 @@ public final class PowerManagerService extends com.android.server.SystemService
ws.add(uids[i]);
}
}
- updateWakeLockWorkSource(lock, ws);
+ updateWakeLockWorkSource(lock, ws, null);
}
@Override // Binder call
- public void updateWakeLockWorkSource(IBinder lock, WorkSource ws) {
+ public void updateWakeLockWorkSource(IBinder lock, WorkSource ws, String historyTag) {
if (lock == null) {
throw new IllegalArgumentException("lock must not be null");
}
@@ -2550,7 +2635,7 @@ public final class PowerManagerService extends com.android.server.SystemService
final long ident = Binder.clearCallingIdentity();
try {
- updateWakeLockWorkSourceInternal(lock, ws);
+ updateWakeLockWorkSourceInternal(lock, ws, historyTag);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2671,6 +2756,9 @@ public final class PowerManagerService extends com.android.server.SystemService
@Override // Binder call
public void reboot(boolean confirm, String reason, boolean wait) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
+ if (PowerManager.REBOOT_RECOVERY.equals(reason)) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+ }
final long ident = Binder.clearCallingIdentity();
try {
@@ -2902,6 +2990,20 @@ public final class PowerManagerService extends com.android.server.SystemService
}
@Override
+ public boolean getLowPowerModeEnabled() {
+ synchronized (mLock) {
+ return mLowPowerModeEnabled;
+ }
+ }
+
+ @Override
+ public void registerLowPowerModeObserver(LowPowerModeListener listener) {
+ synchronized (mLock) {
+ mLowPowerModeListeners.add(listener);
+ }
+ }
+
+ @Override
public void setPolicy(WindowManagerPolicy policy) {
PowerManagerService.this.setPolicy(policy);
}
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 126d4c0..956e3e6 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -24,6 +24,7 @@ import android.app.IActivityManager;
import android.app.ProgressDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.IBluetoothManager;
+import android.media.AudioManager;
import android.nfc.NfcAdapter;
import android.nfc.INfcAdapter;
import android.content.BroadcastReceiver;
@@ -507,7 +508,7 @@ public final class ShutdownThread extends Thread {
// vibrate before shutting down
Vibrator vibrator = new SystemVibrator();
try {
- vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
+ vibrator.vibrate(SHUTDOWN_VIBRATE_MS, AudioManager.STREAM_SYSTEM);
} catch (Exception e) {
// Failure to vibrate shouldn't interrupt shutdown. Just log it.
Log.w(TAG, "Failed to vibrate during shutdown.", e);
diff --git a/services/core/java/com/android/server/search/SearchManagerService.java b/services/core/java/com/android/server/search/SearchManagerService.java
index b5d81d1..5deb2b8 100644
--- a/services/core/java/com/android/server/search/SearchManagerService.java
+++ b/services/core/java/com/android/server/search/SearchManagerService.java
@@ -17,7 +17,6 @@
package com.android.server.search;
import android.app.ActivityManager;
-import android.app.ActivityManagerNative;
import android.app.AppGlobals;
import android.app.ISearchManager;
import android.app.SearchManager;
@@ -39,7 +38,6 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
-import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.content.PackageMonitor;
@@ -72,8 +70,9 @@ public class SearchManagerService extends ISearchManager.Stub {
*/
public SearchManagerService(Context context) {
mContext = context;
- mContext.registerReceiver(new BootCompletedReceiver(),
- new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+ IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
+ filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ mContext.registerReceiver(new BootCompletedReceiver(), filter);
mContext.registerReceiver(new UserReceiver(),
new IntentFilter(Intent.ACTION_USER_REMOVED));
new MyPackageMonitor().register(context, null, UserHandle.ALL, true);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 2ae467e..2c38d3c 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -23,9 +23,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.util.Slog;
@@ -76,6 +74,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
private boolean mMenuVisible = false;
private int mImeWindowVis = 0;
private int mImeBackDisposition;
+ private boolean mShowImeSwitcher;
private IBinder mImeToken = null;
private int mCurrentUserId;
@@ -207,6 +206,10 @@ public class StatusBarManagerService extends IStatusBarService.Stub
@Override
public void disable(int what, IBinder token, String pkg) {
+ if (!mNotificationDelegate.allowDisable(what, token, pkg)) {
+ if (SPEW) Slog.d(TAG, "Blocking disable request from " + pkg);
+ return;
+ }
disableInternal(mCurrentUserId, what, token, pkg);
}
@@ -344,7 +347,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
@Override
- public void setImeWindowStatus(final IBinder token, final int vis, final int backDisposition) {
+ public void setImeWindowStatus(final IBinder token, final int vis, final int backDisposition,
+ final boolean showImeSwitcher) {
enforceStatusBar();
if (SPEW) {
@@ -358,11 +362,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub
mImeWindowVis = vis;
mImeBackDisposition = backDisposition;
mImeToken = token;
+ mShowImeSwitcher = showImeSwitcher;
mHandler.post(new Runnable() {
public void run() {
if (mBar != null) {
try {
- mBar.setImeWindowStatus(token, vis, backDisposition);
+ mBar.setImeWindowStatus(token, vis, backDisposition, showImeSwitcher);
} catch (RemoteException ex) {
}
}
@@ -455,6 +460,24 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
@Override
+ public void showRecentApps(boolean triggeredFromAltTab) {
+ if (mBar != null) {
+ try {
+ mBar.showRecentApps(triggeredFromAltTab);
+ } catch (RemoteException ex) {}
+ }
+ }
+
+ @Override
+ public void hideRecentApps() {
+ if (mBar != null) {
+ try {
+ mBar.hideRecentApps();
+ } catch (RemoteException ex) {}
+ }
+ }
+
+ @Override
public void setCurrentUser(int newUserId) {
if (SPEW) Slog.d(TAG, "Setting current user to user " + newUserId);
mCurrentUserId = newUserId;
@@ -510,6 +533,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
switches[2] = mMenuVisible ? 1 : 0;
switches[3] = mImeWindowVis;
switches[4] = mImeBackDisposition;
+ switches[7] = mShowImeSwitcher ? 1 : 0;
binders.add(mImeToken);
}
switches[5] = mWindowManager.isHardKeyboardAvailable() ? 1 : 0;
@@ -533,11 +557,24 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
@Override
- public void onNotificationClick(String pkg, String tag, int id) {
+ public void onPanelHidden() throws RemoteException {
enforceStatusBarService();
long identity = Binder.clearCallingIdentity();
try {
- mNotificationDelegate.onNotificationClick(pkg, tag, id);
+ mNotificationDelegate.onPanelHidden();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onNotificationClick(String key) {
+ enforceStatusBarService();
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mNotificationDelegate.onNotificationClick(callingUid, callingPid, key);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -545,34 +582,54 @@ public class StatusBarManagerService extends IStatusBarService.Stub
@Override
public void onNotificationError(String pkg, String tag, int id,
- int uid, int initialPid, String message) {
+ int uid, int initialPid, String message, int userId) {
enforceStatusBarService();
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
long identity = Binder.clearCallingIdentity();
try {
// WARNING: this will call back into us to do the remove. Don't hold any locks.
- mNotificationDelegate.onNotificationError(pkg, tag, id, uid, initialPid, message);
+ mNotificationDelegate.onNotificationError(callingUid, callingPid,
+ pkg, tag, id, uid, initialPid, message, userId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onNotificationClear(String pkg, String tag, int id, int userId) {
+ enforceStatusBarService();
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mNotificationDelegate.onNotificationClear(callingUid, callingPid, pkg, tag, id, userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
- public void onNotificationClear(String pkg, String tag, int id) {
+ public void onNotificationVisibilityChanged(
+ String[] newlyVisibleKeys, String[] noLongerVisibleKeys) throws RemoteException {
enforceStatusBarService();
long identity = Binder.clearCallingIdentity();
try {
- mNotificationDelegate.onNotificationClear(pkg, tag, id);
+ mNotificationDelegate.onNotificationVisibilityChanged(
+ newlyVisibleKeys, noLongerVisibleKeys);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
- public void onClearAllNotifications() {
+ public void onClearAllNotifications(int userId) {
enforceStatusBarService();
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
long identity = Binder.clearCallingIdentity();
try {
- mNotificationDelegate.onClearAll();
+ mNotificationDelegate.onClearAll(callingUid, callingPid, userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -676,26 +733,4 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
}
}
-
- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
- || Intent.ACTION_SCREEN_OFF.equals(action)) {
- collapsePanels();
- }
- /*
- else if (Telephony.Intents.SPN_STRINGS_UPDATED_ACTION.equals(action)) {
- updateNetworkName(intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_SPN, false),
- intent.getStringExtra(Telephony.Intents.EXTRA_SPN),
- intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_PLMN, false),
- intent.getStringExtra(Telephony.Intents.EXTRA_PLMN));
- }
- else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
- updateResources();
- }
- */
- }
- };
-
}
diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
index 43a99e0..eb38f4a 100644
--- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
+++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
@@ -454,6 +454,8 @@ public class DeviceStorageMonitorService extends SystemService {
notification.tickerText = title;
notification.flags |= Notification.FLAG_NO_CLEAR;
notification.setLatestEventInfo(context, title, details, intent);
+ notification.visibility = Notification.VISIBILITY_PUBLIC;
+ notification.category = Notification.CATEGORY_SYSTEM;
mNotificationMgr.notifyAsUser(null, LOW_MEMORY_NOTIFICATION_ID, notification,
UserHandle.ALL);
context.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
diff --git a/services/core/java/com/android/server/task/StateChangedListener.java b/services/core/java/com/android/server/task/StateChangedListener.java
new file mode 100644
index 0000000..db2d4ee
--- /dev/null
+++ b/services/core/java/com/android/server/task/StateChangedListener.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.task;
+
+import com.android.server.task.controllers.TaskStatus;
+
+/**
+ * Interface through which a {@link com.android.server.task.controllers.StateController} informs
+ * the {@link com.android.server.task.TaskManagerService} that there are some tasks potentially
+ * ready to be run.
+ */
+public interface StateChangedListener {
+ /**
+ * Called by the controller to notify the TaskManager that it should check on the state of a
+ * task.
+ * @param taskStatus The state of the task which has changed.
+ */
+ public void onTaskStateChanged(TaskStatus taskStatus);
+
+ /**
+ * Called by the controller to notify the TaskManager that regardless of the state of the task,
+ * it must be run immediately.
+ * @param taskStatus The state of the task which is to be run immediately.
+ */
+ public void onTaskDeadlineExpired(TaskStatus taskStatus);
+}
diff --git a/services/core/java/com/android/server/task/TaskCompletedListener.java b/services/core/java/com/android/server/task/TaskCompletedListener.java
new file mode 100644
index 0000000..0210442
--- /dev/null
+++ b/services/core/java/com/android/server/task/TaskCompletedListener.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.task;
+
+/**
+ * Used for communication between {@link com.android.server.task.TaskServiceContext} and the
+ * {@link com.android.server.task.TaskManagerService}.
+ */
+public interface TaskCompletedListener {
+
+ /**
+ * Callback for when a task is completed.
+ * @param needsReschedule Whether the implementing class should reschedule this task.
+ */
+ public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule);
+
+ /**
+ * Callback for when the implementing class needs to clean up the
+ * {@link com.android.server.task.TaskServiceContext}. The scheduler can get this callback
+ * several times if the TaskServiceContext got into a bad state (for e.g. the client crashed
+ * and it needs to clean up).
+ */
+ public void onAllTasksCompleted(int serviceToken);
+}
diff --git a/services/core/java/com/android/server/task/TaskManagerService.java b/services/core/java/com/android/server/task/TaskManagerService.java
new file mode 100644
index 0000000..6d208ff
--- /dev/null
+++ b/services/core/java/com/android/server/task/TaskManagerService.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.task;
+
+import android.content.Context;
+import android.content.Task;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.server.task.controllers.TaskStatus;
+
+/**
+ * Responsible for taking tasks representing work to be performed by a client app, and determining
+ * based on the criteria specified when that task should be run against the client application's
+ * endpoint.
+ * @hide
+ */
+public class TaskManagerService extends com.android.server.SystemService
+ implements StateChangedListener, TaskCompletedListener {
+ static final String TAG = "TaskManager";
+
+ /** Master list of tasks. */
+ private final TaskStore mTasks;
+
+ /** Check the pending queue and start any tasks. */
+ static final int MSG_RUN_PENDING = 0;
+ /** Initiate the stop task flow. */
+ static final int MSG_STOP_TASK = 1;
+ /** */
+ static final int MSG_CHECK_TASKS = 2;
+
+ /**
+ * Track Services that have currently active or pending tasks. The index is provided by
+ * {@link TaskStatus#getServiceToken()}
+ */
+ private final SparseArray<TaskServiceContext> mActiveServices =
+ new SparseArray<TaskServiceContext>();
+
+ private final TaskHandler mHandler;
+
+ private class TaskHandler extends Handler {
+
+ public TaskHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_RUN_PENDING:
+
+ break;
+ case MSG_STOP_TASK:
+
+ break;
+ case MSG_CHECK_TASKS:
+ checkTasks();
+ break;
+ }
+ }
+
+ /**
+ * Called when we need to run through the list of all tasks and start/stop executing one or
+ * more of them.
+ */
+ private void checkTasks() {
+ synchronized (mTasks) {
+ final SparseArray<TaskStatus> tasks = mTasks.getTasks();
+ for (int i = 0; i < tasks.size(); i++) {
+ TaskStatus ts = tasks.valueAt(i);
+ if (ts.isReady() && ! isCurrentlyActive(ts)) {
+ assignTaskToServiceContext(ts);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Entry point from client to schedule the provided task.
+ * This will add the task to the
+ * @param task Task object containing execution parameters
+ * @param userId The id of the user this task is for.
+ * @param uId The package identifier of the application this task is for.
+ * @param canPersistTask Whether or not the client has the appropriate permissions for persisting
+ * of this task.
+ * @return Result of this operation. See <code>TaskManager#RESULT_*</code> return codes.
+ */
+ public int schedule(Task task, int userId, int uId, boolean canPersistTask) {
+ TaskStatus taskStatus = mTasks.addNewTaskForUser(task, userId, uId, canPersistTask);
+ return 0;
+ }
+
+ /**
+ * Initializes the system service.
+ * <p>
+ * Subclasses must define a single argument constructor that accepts the context
+ * and passes it to super.
+ * </p>
+ *
+ * @param context The system server context.
+ */
+ public TaskManagerService(Context context) {
+ super(context);
+ mTasks = new TaskStore(context);
+ mHandler = new TaskHandler(context.getMainLooper());
+ }
+
+ @Override
+ public void onStart() {
+
+ }
+
+ // StateChangedListener implementations.
+
+ /**
+ * Off-board work to our handler thread as quickly as possible, b/c this call is probably being
+ * made on the main thread.
+ * For now this takes the task and if it's ready to run it will run it. In future we might not
+ * provide the task, so that the StateChangedListener has to run through its list of tasks to
+ * see which are ready. This will further decouple the controllers from the execution logic.
+ */
+ @Override
+ public void onTaskStateChanged(TaskStatus taskStatus) {
+ postCheckTasksMessage();
+
+ }
+
+ @Override
+ public void onTaskDeadlineExpired(TaskStatus taskStatus) {
+
+ }
+
+ // TaskCompletedListener implementations.
+
+ /**
+ * A task just finished executing. We fetch the
+ * {@link com.android.server.task.controllers.TaskStatus} from the store and depending on
+ * whether we want to reschedule we readd it to the controllers.
+ * @param serviceToken key for the service context in {@link #mActiveServices}.
+ * @param taskId Id of the task that is complete.
+ * @param needsReschedule Whether the implementing class should reschedule this task.
+ */
+ @Override
+ public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule) {
+ final TaskServiceContext serviceContext = mActiveServices.get(serviceToken);
+ if (serviceContext == null) {
+ Log.e(TAG, "Task completed for invalid service context; " + serviceToken);
+ return;
+ }
+
+ }
+
+ @Override
+ public void onAllTasksCompleted(int serviceToken) {
+
+ }
+
+ private void assignTaskToServiceContext(TaskStatus ts) {
+ TaskServiceContext serviceContext =
+ mActiveServices.get(ts.getServiceToken());
+ if (serviceContext == null) {
+ serviceContext = new TaskServiceContext(this, mHandler.getLooper(), ts);
+ mActiveServices.put(ts.getServiceToken(), serviceContext);
+ }
+ serviceContext.addPendingTask(ts);
+ }
+
+ /**
+ * @param ts TaskStatus we are querying against.
+ * @return Whether or not the task represented by the status object is currently being run or
+ * is pending.
+ */
+ private boolean isCurrentlyActive(TaskStatus ts) {
+ TaskServiceContext serviceContext = mActiveServices.get(ts.getServiceToken());
+ if (serviceContext == null) {
+ return false;
+ }
+ return serviceContext.hasTaskPending(ts);
+ }
+
+ /**
+ * Post a message to {@link #mHandler} to run through the list of tasks and start/stop any that
+ * are eligible.
+ */
+ private void postCheckTasksMessage() {
+ mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget();
+ }
+}
diff --git a/services/core/java/com/android/server/task/TaskServiceContext.java b/services/core/java/com/android/server/task/TaskServiceContext.java
new file mode 100644
index 0000000..b51cbb3
--- /dev/null
+++ b/services/core/java/com/android/server/task/TaskServiceContext.java
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.task;
+
+import android.app.ActivityManager;
+import android.app.task.ITaskCallback;
+import android.app.task.ITaskService;
+import android.app.task.TaskParams;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.server.task.controllers.TaskStatus;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Maintains information required to bind to a {@link android.app.task.TaskService}. This binding
+ * is reused to start concurrent tasks on the TaskService. Information here is unique
+ * to the service.
+ * Functionality provided by this class:
+ * - Managages wakelock for the service.
+ * - Sends onStartTask() and onStopTask() messages to client app, and handles callbacks.
+ * -
+ */
+public class TaskServiceContext extends ITaskCallback.Stub implements ServiceConnection {
+ private static final String TAG = "TaskServiceContext";
+ /** Define the maximum # of tasks allowed to run on a service at once. */
+ private static final int defaultMaxActiveTasksPerService =
+ ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
+ /** Amount of time a task is allowed to execute for before being considered timed-out. */
+ private static final long EXECUTING_TIMESLICE_MILLIS = 5 * 60 * 1000;
+ /** Amount of time the TaskManager will wait for a response from an app for a message. */
+ private static final long OP_TIMEOUT_MILLIS = 8 * 1000;
+ /** String prefix for all wakelock names. */
+ private static final String TM_WAKELOCK_PREFIX = "*task*/";
+
+ private static final String[] VERB_STRINGS = {
+ "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_PENDING"
+ };
+
+ // States that a task occupies while interacting with the client.
+ private static final int VERB_STARTING = 0;
+ private static final int VERB_EXECUTING = 1;
+ private static final int VERB_STOPPING = 2;
+ private static final int VERB_PENDING = 3;
+
+ // Messages that result from interactions with the client service.
+ /** System timed out waiting for a response. */
+ private static final int MSG_TIMEOUT = 0;
+ /** Received a callback from client. */
+ private static final int MSG_CALLBACK = 1;
+ /** Run through list and start any ready tasks.*/
+ private static final int MSG_CHECK_PENDING = 2;
+ /** Cancel an active task. */
+ private static final int MSG_CANCEL = 3;
+ /** Add a pending task. */
+ private static final int MSG_ADD_PENDING = 4;
+ /** Client crashed, so we need to wind things down. */
+ private static final int MSG_SHUTDOWN = 5;
+
+ /** Used to identify this task service context when communicating with the TaskManager. */
+ final int token;
+ final ComponentName component;
+ final int userId;
+ ITaskService service;
+ private final Handler mCallbackHandler;
+ /** Tasks that haven't been sent to the client for execution yet. */
+ private final SparseArray<ActiveTask> mPending;
+ /** Used for service binding, etc. */
+ private final Context mContext;
+ /** Make callbacks to {@link TaskManagerService} to inform on task completion status. */
+ final private TaskCompletedListener mCompletedListener;
+ private final PowerManager.WakeLock mWakeLock;
+
+ /** Whether this service is actively bound. */
+ boolean mBound;
+
+ TaskServiceContext(TaskManagerService taskManager, Looper looper, TaskStatus taskStatus) {
+ mContext = taskManager.getContext();
+ this.component = taskStatus.getServiceComponent();
+ this.token = taskStatus.getServiceToken();
+ this.userId = taskStatus.getUserId();
+ mCallbackHandler = new TaskServiceHandler(looper);
+ mPending = new SparseArray<ActiveTask>();
+ mCompletedListener = taskManager;
+ final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ TM_WAKELOCK_PREFIX + component.getPackageName());
+ mWakeLock.setWorkSource(new WorkSource(taskStatus.getUid()));
+ mWakeLock.setReferenceCounted(false);
+ }
+
+ @Override
+ public void taskFinished(int taskId, boolean reschedule) {
+ mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
+ .sendToTarget();
+ }
+
+ @Override
+ public void acknowledgeStopMessage(int taskId, boolean reschedule) {
+ mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
+ .sendToTarget();
+ }
+
+ @Override
+ public void acknowledgeStartMessage(int taskId, boolean ongoing) {
+ mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, ongoing ? 1 : 0).sendToTarget();
+ }
+
+ /**
+ * Queue up this task to run on the client. This will execute the task as quickly as possible.
+ * @param ts Status of the task to run.
+ */
+ public void addPendingTask(TaskStatus ts) {
+ final TaskParams params = new TaskParams(ts.getTaskId(), ts.getExtras(), this);
+ final ActiveTask newTask = new ActiveTask(params, VERB_PENDING);
+ mCallbackHandler.obtainMessage(MSG_ADD_PENDING, newTask).sendToTarget();
+ if (!mBound) {
+ Intent intent = new Intent().setComponent(component);
+ boolean binding = mContext.bindServiceAsUser(intent, this,
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
+ new UserHandle(userId));
+ if (!binding) {
+ Log.e(TAG, component.getShortClassName() + " unavailable.");
+ cancelPendingTask(ts);
+ }
+ }
+ }
+
+ /**
+ * Called externally when a task that was scheduled for execution should be cancelled.
+ * @param ts The status of the task to cancel.
+ */
+ public void cancelPendingTask(TaskStatus ts) {
+ mCallbackHandler.obtainMessage(MSG_CANCEL, ts.getTaskId(), -1 /* arg2 */)
+ .sendToTarget();
+ }
+
+ /**
+ * MSG_TIMEOUT is sent with the {@link com.android.server.task.TaskServiceContext.ActiveTask}
+ * set in the {@link Message#obj} field. This makes it easier to remove timeouts for a given
+ * ActiveTask.
+ * @param op Operation that is taking place.
+ */
+ private void scheduleOpTimeOut(ActiveTask op) {
+ mCallbackHandler.removeMessages(MSG_TIMEOUT, op);
+
+ final long timeoutMillis = (op.verb == VERB_EXECUTING) ?
+ EXECUTING_TIMESLICE_MILLIS : OP_TIMEOUT_MILLIS;
+ if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
+ Slog.d(TAG, "Scheduling time out for '" + component.getShortClassName() + "' tId: " +
+ op.params.getTaskId() + ", in " + (timeoutMillis / 1000) + " s");
+ }
+ Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, op);
+ mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
+ }
+
+ /**
+ * @return true if this task is pending or active within this context.
+ */
+ public boolean hasTaskPending(TaskStatus taskStatus) {
+ synchronized (mPending) {
+ return mPending.get(taskStatus.getTaskId()) != null;
+ }
+ }
+
+ public boolean isBound() {
+ return mBound;
+ }
+
+ /**
+ * We acquire/release the wakelock on onServiceConnected/unbindService. This mirrors the work
+ * we intend to send to the client - we stop sending work when the service is unbound so until
+ * then we keep the wakelock.
+ * @param name The concrete component name of the service that has
+ * been connected.
+ * @param service The IBinder of the Service's communication channel,
+ */
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mBound = true;
+ this.service = ITaskService.Stub.asInterface(service);
+ // Remove all timeouts. We've just connected to the client so there are no other
+ // MSG_TIMEOUTs at this point.
+ mCallbackHandler.removeMessages(MSG_TIMEOUT);
+ mWakeLock.acquire();
+ mCallbackHandler.obtainMessage(MSG_CHECK_PENDING).sendToTarget();
+ }
+
+ /**
+ * When the client service crashes we can have a couple tasks executing, in various stages of
+ * undress. We'll cancel all of them and request that they be rescheduled.
+ * @param name The concrete component name of the service whose
+ */
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // Service disconnected... probably client crashed.
+ startShutdown();
+ }
+
+ /**
+ * We don't just shutdown outright - we make sure the scheduler isn't going to send us any more
+ * tasks, then we do the shutdown.
+ */
+ private void startShutdown() {
+ mCompletedListener.onAllTasksCompleted(token);
+ mCallbackHandler.obtainMessage(MSG_SHUTDOWN).sendToTarget();
+ }
+
+ /** Tracks a task across its various state changes. */
+ private static class ActiveTask {
+ final TaskParams params;
+ int verb;
+ AtomicBoolean cancelled = new AtomicBoolean();
+
+ ActiveTask(TaskParams params, int verb) {
+ this.params = params;
+ this.verb = verb;
+ }
+
+ @Override
+ public String toString() {
+ return params.getTaskId() + " " + VERB_STRINGS[verb];
+ }
+ }
+
+ /**
+ * Handles the lifecycle of the TaskService binding/callbacks, etc. The convention within this
+ * class is to append 'H' to each function name that can only be called on this handler. This
+ * isn't strictly necessary because all of these functions are private, but helps clarity.
+ */
+ private class TaskServiceHandler extends Handler {
+ TaskServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_ADD_PENDING:
+ if (message.obj != null) {
+ ActiveTask pendingTask = (ActiveTask) message.obj;
+ mPending.put(pendingTask.params.getTaskId(), pendingTask);
+ }
+ // fall through.
+ case MSG_CHECK_PENDING:
+ checkPendingTasksH();
+ break;
+ case MSG_CALLBACK:
+ ActiveTask receivedCallback = mPending.get(message.arg1);
+ removeMessages(MSG_TIMEOUT, receivedCallback);
+
+ if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
+ Log.d(TAG, "MSG_CALLBACK of : " + receivedCallback);
+ }
+
+ if (receivedCallback.verb == VERB_STARTING) {
+ final boolean workOngoing = message.arg2 == 1;
+ handleStartedH(receivedCallback, workOngoing);
+ } else if (receivedCallback.verb == VERB_EXECUTING ||
+ receivedCallback.verb == VERB_STOPPING) {
+ final boolean reschedule = message.arg2 == 1;
+ handleFinishedH(receivedCallback, reschedule);
+ } else {
+ if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
+ Log.d(TAG, "Unrecognised callback: " + receivedCallback);
+ }
+ }
+ break;
+ case MSG_CANCEL:
+ ActiveTask cancelled = mPending.get(message.arg1);
+ handleCancelH(cancelled);
+ break;
+ case MSG_TIMEOUT:
+ // Timeout msgs have the ActiveTask ref so we can remove them easily.
+ handleOpTimeoutH((ActiveTask) message.obj);
+ break;
+ case MSG_SHUTDOWN:
+ handleShutdownH();
+ break;
+ default:
+ Log.e(TAG, "Unrecognised message: " + message);
+ }
+ }
+
+ /**
+ * State behaviours.
+ * VERB_STARTING -> Successful start, change task to VERB_EXECUTING and post timeout.
+ * _PENDING -> Error
+ * _EXECUTING -> Error
+ * _STOPPING -> Error
+ */
+ private void handleStartedH(ActiveTask started, boolean workOngoing) {
+ switch (started.verb) {
+ case VERB_STARTING:
+ started.verb = VERB_EXECUTING;
+ if (!workOngoing) {
+ // Task is finished already so fast-forward to handleFinished.
+ handleFinishedH(started, false);
+ return;
+ } else if (started.cancelled.get()) {
+ // Cancelled *while* waiting for acknowledgeStartMessage from client.
+ handleCancelH(started);
+ return;
+ } else {
+ scheduleOpTimeOut(started);
+ }
+ break;
+ default:
+ Log.e(TAG, "Handling started task but task wasn't starting! " + started);
+ return;
+ }
+ }
+
+ /**
+ * VERB_EXECUTING -> Client called taskFinished(), clean up and notify done.
+ * _STOPPING -> Successful finish, clean up and notify done.
+ * _STARTING -> Error
+ * _PENDING -> Error
+ */
+ private void handleFinishedH(ActiveTask executedTask, boolean reschedule) {
+ switch (executedTask.verb) {
+ case VERB_EXECUTING:
+ case VERB_STOPPING:
+ closeAndCleanupTaskH(executedTask, reschedule);
+ break;
+ default:
+ Log.e(TAG, "Got an execution complete message for a task that wasn't being" +
+ "executed. " + executedTask);
+ }
+ }
+
+ /**
+ * A task can be in various states when a cancel request comes in:
+ * VERB_PENDING -> Remove from queue.
+ * _STARTING -> Mark as cancelled and wait for {@link #acknowledgeStartMessage(int)}.
+ * _EXECUTING -> call {@link #sendStopMessageH}}.
+ * _ENDING -> No point in doing anything here, so we ignore.
+ */
+ private void handleCancelH(ActiveTask cancelledTask) {
+ switch (cancelledTask.verb) {
+ case VERB_PENDING:
+ mPending.remove(cancelledTask.params.getTaskId());
+ break;
+ case VERB_STARTING:
+ cancelledTask.cancelled.set(true);
+ break;
+ case VERB_EXECUTING:
+ cancelledTask.verb = VERB_STOPPING;
+ sendStopMessageH(cancelledTask);
+ break;
+ case VERB_STOPPING:
+ // Nada.
+ break;
+ default:
+ Log.e(TAG, "Cancelling a task without a valid verb: " + cancelledTask);
+ break;
+ }
+ }
+
+ /**
+ * This TaskServiceContext is shutting down. Remove all the tasks from the pending queue
+ * and reschedule them as if they had failed.
+ * Before posting this message, caller must invoke
+ * {@link com.android.server.task.TaskCompletedListener#onAllTasksCompleted(int)}.
+ */
+ private void handleShutdownH() {
+ for (int i = 0; i < mPending.size(); i++) {
+ ActiveTask at = mPending.valueAt(i);
+ closeAndCleanupTaskH(at, true /* needsReschedule */);
+ }
+ mWakeLock.release();
+ mContext.unbindService(TaskServiceContext.this);
+ service = null;
+ mBound = false;
+ }
+
+ /**
+ * MSG_TIMEOUT gets processed here.
+ * @param timedOutTask The task that timed out.
+ */
+ private void handleOpTimeoutH(ActiveTask timedOutTask) {
+ if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
+ Log.d(TAG, "MSG_TIMEOUT of " + component.getShortClassName() + " : "
+ + timedOutTask.params.getTaskId());
+ }
+
+ final int taskId = timedOutTask.params.getTaskId();
+ switch (timedOutTask.verb) {
+ case VERB_STARTING:
+ // Client unresponsive - wedged or failed to respond in time. We don't really
+ // know what happened so let's log it and notify the TaskManager
+ // FINISHED/NO-RETRY.
+ Log.e(TAG, "No response from client for onStartTask '" +
+ component.getShortClassName() + "' tId: " + taskId);
+ closeAndCleanupTaskH(timedOutTask, false /* needsReschedule */);
+ break;
+ case VERB_STOPPING:
+ // At least we got somewhere, so fail but ask the TaskManager to reschedule.
+ Log.e(TAG, "No response from client for onStopTask, '" +
+ component.getShortClassName() + "' tId: " + taskId);
+ closeAndCleanupTaskH(timedOutTask, true /* needsReschedule */);
+ break;
+ case VERB_EXECUTING:
+ // Not an error - client ran out of time.
+ Log.i(TAG, "Client timed out while executing (no taskFinished received)." +
+ " Reporting failure and asking for reschedule. " +
+ component.getShortClassName() + "' tId: " + taskId);
+ sendStopMessageH(timedOutTask);
+ break;
+ default:
+ Log.e(TAG, "Handling timeout for an unknown active task state: "
+ + timedOutTask);
+ return;
+ }
+ }
+
+ /**
+ * Called on the handler thread. Checks the state of the pending queue and starts the task
+ * if it can. The task only starts if there is capacity on the service.
+ */
+ private void checkPendingTasksH() {
+ if (!mBound) {
+ return;
+ }
+ for (int i = 0; i < mPending.size() && i < defaultMaxActiveTasksPerService; i++) {
+ ActiveTask at = mPending.valueAt(i);
+ if (at.verb != VERB_PENDING) {
+ continue;
+ }
+ sendStartMessageH(at);
+ }
+ }
+
+ /**
+ * Already running, need to stop. Rund on handler.
+ * @param stoppingTask Task we are sending onStopMessage for. This task will be moved from
+ * VERB_EXECUTING -> VERB_STOPPING.
+ */
+ private void sendStopMessageH(ActiveTask stoppingTask) {
+ mCallbackHandler.removeMessages(MSG_TIMEOUT, stoppingTask);
+ if (stoppingTask.verb != VERB_EXECUTING) {
+ Log.e(TAG, "Sending onStopTask for a task that isn't started. " + stoppingTask);
+ // TODO: Handle error?
+ return;
+ }
+ try {
+ service.stopTask(stoppingTask.params);
+ stoppingTask.verb = VERB_STOPPING;
+ scheduleOpTimeOut(stoppingTask);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending onStopTask to client.", e);
+ closeAndCleanupTaskH(stoppingTask, false);
+ }
+ }
+
+ /** Start the task on the service. */
+ private void sendStartMessageH(ActiveTask pendingTask) {
+ if (pendingTask.verb != VERB_PENDING) {
+ Log.e(TAG, "Sending onStartTask for a task that isn't pending. " + pendingTask);
+ // TODO: Handle error?
+ }
+ try {
+ service.startTask(pendingTask.params);
+ pendingTask.verb = VERB_STARTING;
+ scheduleOpTimeOut(pendingTask);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending onStart message to '" + component.getShortClassName()
+ + "' ", e);
+ }
+ }
+
+ /**
+ * The provided task has finished, either by calling
+ * {@link android.app.task.TaskService#taskFinished(android.app.task.TaskParams, boolean)}
+ * or from acknowledging the stop message we sent. Either way, we're done tracking it and
+ * we want to clean up internally.
+ */
+ private void closeAndCleanupTaskH(ActiveTask completedTask, boolean reschedule) {
+ removeMessages(MSG_TIMEOUT, completedTask);
+ mPending.remove(completedTask.params.getTaskId());
+ if (mPending.size() == 0) {
+ startShutdown();
+ }
+ mCompletedListener.onTaskCompleted(token, completedTask.params.getTaskId(), reschedule);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/task/TaskStore.java b/services/core/java/com/android/server/task/TaskStore.java
new file mode 100644
index 0000000..3bfc8a5
--- /dev/null
+++ b/services/core/java/com/android/server/task/TaskStore.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.task;
+
+import android.content.Context;
+import android.content.Task;
+import android.util.SparseArray;
+
+import com.android.server.task.controllers.TaskStatus;
+
+/**
+ * Maintain a list of classes, and accessor methods/logic for these tasks.
+ * This class offers the following functionality:
+ * - When a task is added, it will determine if the task requirements have changed (update) and
+ * whether the controllers need to be updated.
+ * - Persists Tasks, figures out when to to rewrite the Task to disk.
+ * - Is threadsafe.
+ * - Handles rescheduling of tasks.
+ * - When a periodic task is executed and must be re-added.
+ * - When a task fails and the client requests that it be retried with backoff.
+ * - This class is <strong>not</strong> thread-safe.
+ */
+public class TaskStore {
+
+ /**
+ * Master list, indexed by {@link com.android.server.task.controllers.TaskStatus#hashCode()}.
+ */
+ final SparseArray<TaskStatus> mTasks;
+ final Context mContext;
+
+ TaskStore(Context context) {
+ mTasks = intialiseTaskMapFromDisk();
+ mContext = context;
+ }
+
+ /**
+ * Add a task to the master list, persisting it if necessary.
+ * Will first check to see if the task already exists. If so, it will replace it.
+ * {@link android.content.pm.PackageManager} is queried to see if the calling package has
+ * permission to
+ * @param task Task to add.
+ * @return The initialised TaskStatus object if this operation was successful, null if it
+ * failed.
+ */
+ public TaskStatus addNewTaskForUser(Task task, int userId, int uId,
+ boolean canPersistTask) {
+ TaskStatus taskStatus = TaskStatus.getForTaskAndUser(task, userId, uId);
+ if (canPersistTask && task.isPeriodic()) {
+ if (writeStatusToDisk()) {
+ mTasks.put(taskStatus.hashCode(), taskStatus);
+ }
+ }
+ return taskStatus;
+ }
+
+ /**
+ * Remove the provided task. Will also delete the task if it was persisted. Note that this
+ * function does not return the validity of the operation, as we assume a delete will always
+ * succeed.
+ * @param task Task to remove.
+ */
+ public void remove(Task task) {
+
+ }
+
+ /**
+ * Every time the state changes we write all the tasks in one swathe, instead of trying to
+ * track incremental changes.
+ */
+ private boolean writeStatusToDisk() {
+ return true;
+ }
+
+ /**
+ *
+ * @return
+ */
+ // TODO: Implement this.
+ private SparseArray<TaskStatus> intialiseTaskMapFromDisk() {
+ return new SparseArray<TaskStatus>();
+ }
+
+ /**
+ * @return The live array of TaskStatus objects.
+ */
+ public SparseArray<TaskStatus> getTasks() {
+ return mTasks;
+ }
+}
diff --git a/services/core/java/com/android/server/task/controllers/ConnectivityController.java b/services/core/java/com/android/server/task/controllers/ConnectivityController.java
new file mode 100644
index 0000000..6a4e1f3
--- /dev/null
+++ b/services/core/java/com/android/server/task/controllers/ConnectivityController.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.task.controllers;
+
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.server.task.TaskManagerService;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ *
+ */
+public class ConnectivityController extends StateController {
+ private static final String TAG = "TaskManager.Connectivity";
+
+ private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>();
+ private final BroadcastReceiver mConnectivityChangedReceiver =
+ new ConnectivityChangedReceiver();
+
+ /** Track whether the latest active network is metered. */
+ private boolean mMetered;
+ /** Track whether the latest active network is connected. */
+ private boolean mConnectivity;
+
+ public ConnectivityController(TaskManagerService service) {
+ super(service);
+ // Register connectivity changed BR.
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ mContext.registerReceiverAsUser(
+ mConnectivityChangedReceiver, UserHandle.ALL, intentFilter, null, null);
+ }
+
+ @Override
+ public void maybeTrackTaskState(TaskStatus taskStatus) {
+ if (taskStatus.hasConnectivityConstraint() || taskStatus.hasMeteredConstraint()) {
+ taskStatus.connectivityConstraintSatisfied.set(mConnectivity);
+ taskStatus.meteredConstraintSatisfied.set(mMetered);
+ mTrackedTasks.add(taskStatus);
+ }
+ }
+
+ @Override
+ public void removeTaskStateIfTracked(TaskStatus taskStatus) {
+ mTrackedTasks.remove(taskStatus);
+ }
+
+ /**
+ * @param userId Id of the user for whom we are updating the connectivity state.
+ */
+ private void updateTrackedTasks(int userId) {
+ for (TaskStatus ts : mTrackedTasks) {
+ if (ts.userId != userId) {
+ continue;
+ }
+ boolean prevIsConnected = ts.connectivityConstraintSatisfied.getAndSet(mConnectivity);
+ boolean prevIsMetered = ts.meteredConstraintSatisfied.getAndSet(mMetered);
+ if (prevIsConnected != mConnectivity || prevIsMetered != mMetered) {
+ mStateChangedListener.onTaskStateChanged(ts);
+ }
+ }
+ }
+
+ class ConnectivityChangedReceiver extends BroadcastReceiver {
+ /**
+ * We'll receive connectivity changes for each user here, which we process independently.
+ * We are only interested in the active network here. We're only interested in the active
+ * network, b/c the end result of this will be for apps to try to hit the network.
+ * @param context The Context in which the receiver is running.
+ * @param intent The Intent being received.
+ */
+ // TODO: Test whether this will be called twice for each user.
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ final int networkType =
+ intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
+ ConnectivityManager.TYPE_NONE);
+ // Connectivity manager for THIS context - important!
+ final ConnectivityManager connManager = (ConnectivityManager)
+ context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo();
+ // This broadcast gets sent a lot, only update if the active network has changed.
+ if (activeNetwork.getType() == networkType) {
+ final int userid = context.getUserId();
+ mMetered = false;
+ mConnectivity =
+ !intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
+ if (mConnectivity) { // No point making the call if we know there's no conn.
+ mMetered = connManager.isActiveNetworkMetered();
+ }
+ updateTrackedTasks(userid);
+ }
+ } else {
+ Log.w(TAG, "Unrecognised action in intent: " + action);
+ }
+ }
+ };
+}
diff --git a/services/core/java/com/android/server/task/controllers/IdleController.java b/services/core/java/com/android/server/task/controllers/IdleController.java
new file mode 100644
index 0000000..a319a31
--- /dev/null
+++ b/services/core/java/com/android/server/task/controllers/IdleController.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.task.controllers;
+
+import java.util.ArrayList;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.android.server.task.TaskManagerService;
+
+public class IdleController extends StateController {
+ private static final String TAG = "IdleController";
+ private static final boolean DEBUG = false;
+
+ // Policy: we decide that we're "idle" if the device has been unused /
+ // screen off or dreaming for at least this long
+ private static final long INACTIVITY_IDLE_THRESHOLD = 71 * 60 * 1000; // millis; 71 min
+ private static final long IDLE_WINDOW_SLOP = 5 * 60 * 1000; // 5 minute window, to be nice
+
+ private static final String ACTION_TRIGGER_IDLE =
+ "com.android.server.task.controllers.IdleController.ACTION_TRIGGER_IDLE";
+
+ final ArrayList<TaskStatus> mTrackedTasks = new ArrayList<TaskStatus>();
+ IdlenessTracker mIdleTracker;
+
+ // Singleton factory
+ private static Object sCreationLock = new Object();
+ private static volatile IdleController sController;
+
+ public IdleController getController(TaskManagerService service) {
+ synchronized (sCreationLock) {
+ if (sController == null) {
+ sController = new IdleController(service);
+ }
+ return sController;
+ }
+ }
+
+ private IdleController(TaskManagerService service) {
+ super(service);
+ initIdleStateTracking();
+ }
+
+ /**
+ * StateController interface
+ */
+ @Override
+ public void maybeTrackTaskState(TaskStatus taskStatus) {
+ if (taskStatus.hasIdleConstraint()) {
+ synchronized (mTrackedTasks) {
+ mTrackedTasks.add(taskStatus);
+ taskStatus.idleConstraintSatisfied.set(mIdleTracker.isIdle());
+ }
+ }
+ }
+
+ @Override
+ public void removeTaskStateIfTracked(TaskStatus taskStatus) {
+ synchronized (mTrackedTasks) {
+ mTrackedTasks.remove(taskStatus);
+ }
+ }
+
+ /**
+ * Interaction with the task manager service
+ */
+ void reportNewIdleState(boolean isIdle) {
+ synchronized (mTrackedTasks) {
+ for (TaskStatus task : mTrackedTasks) {
+ task.idleConstraintSatisfied.set(isIdle);
+ mStateChangedListener.onTaskStateChanged(task);
+ }
+ }
+ }
+
+ /**
+ * Idle state tracking, and messaging with the task manager when
+ * significant state changes occur
+ */
+ private void initIdleStateTracking() {
+ mIdleTracker = new IdlenessTracker();
+ mIdleTracker.startTracking();
+ }
+
+ class IdlenessTracker extends BroadcastReceiver {
+ private AlarmManager mAlarm;
+ private PendingIntent mIdleTriggerIntent;
+ boolean mIdle;
+
+ public IdlenessTracker() {
+ mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+
+ Intent intent = new Intent(ACTION_TRIGGER_IDLE);
+ intent.setComponent(new ComponentName(mContext, this.getClass()));
+ mIdleTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+
+ // at boot we presume that the user has just "interacted" with the
+ // device in some meaningful way
+ mIdle = false;
+ }
+
+ public boolean isIdle() {
+ return mIdle;
+ }
+
+ public void startTracking() {
+ IntentFilter filter = new IntentFilter();
+
+ // Screen state
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+
+ // Dreaming state
+ filter.addAction(Intent.ACTION_DREAMING_STARTED);
+ filter.addAction(Intent.ACTION_DREAMING_STOPPED);
+
+ mContext.registerReceiver(this, filter);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+
+ if (action.equals(Intent.ACTION_SCREEN_ON)
+ || action.equals(Intent.ACTION_DREAMING_STOPPED)) {
+ // possible transition to not-idle
+ if (mIdle) {
+ if (DEBUG) {
+ Slog.v(TAG, "exiting idle : " + action);
+ }
+ mAlarm.cancel(mIdleTriggerIntent);
+ mIdle = false;
+ reportNewIdleState(mIdle);
+ }
+ } else if (action.equals(Intent.ACTION_SCREEN_OFF)
+ || action.equals(Intent.ACTION_DREAMING_STARTED)) {
+ // when the screen goes off or dreaming starts, we schedule the
+ // alarm that will tell us when we have decided the device is
+ // truly idle.
+ long when = SystemClock.elapsedRealtime() + INACTIVITY_IDLE_THRESHOLD;
+ if (DEBUG) {
+ Slog.v(TAG, "Scheduling idle : " + action + " when=" + when);
+ }
+ mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ when, IDLE_WINDOW_SLOP, mIdleTriggerIntent);
+ } else if (action.equals(ACTION_TRIGGER_IDLE)) {
+ // idle time starts now
+ if (!mIdle) {
+ if (DEBUG) {
+ Slog.v(TAG, "Idle trigger fired @ " + SystemClock.elapsedRealtime());
+ }
+ mIdle = true;
+ reportNewIdleState(mIdle);
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/task/controllers/StateController.java b/services/core/java/com/android/server/task/controllers/StateController.java
new file mode 100644
index 0000000..e1cd662
--- /dev/null
+++ b/services/core/java/com/android/server/task/controllers/StateController.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.task.controllers;
+
+import android.content.Context;
+
+import com.android.server.task.StateChangedListener;
+import com.android.server.task.TaskManagerService;
+
+/**
+ * Incorporates shared controller logic between the various controllers of the TaskManager.
+ * These are solely responsible for tracking a list of tasks, and notifying the TM when these
+ * are ready to run, or whether they must be stopped.
+ */
+public abstract class StateController {
+
+ protected Context mContext;
+ protected StateChangedListener mStateChangedListener;
+
+ public StateController(TaskManagerService service) {
+ mStateChangedListener = service;
+ mContext = service.getContext();
+ }
+
+ /**
+ * Implement the logic here to decide whether a task should be tracked by this controller.
+ * This logic is put here so the TaskManger can be completely agnostic of Controller logic.
+ * Also called when updating a task, so implementing controllers have to be aware of
+ * preexisting tasks.
+ */
+ public abstract void maybeTrackTaskState(TaskStatus taskStatus);
+ /**
+ * Remove task - this will happen if the task is cancelled, completed, etc.
+ */
+ public abstract void removeTaskStateIfTracked(TaskStatus taskStatus);
+
+}
diff --git a/services/core/java/com/android/server/task/controllers/TaskStatus.java b/services/core/java/com/android/server/task/controllers/TaskStatus.java
new file mode 100644
index 0000000..d96fedc
--- /dev/null
+++ b/services/core/java/com/android/server/task/controllers/TaskStatus.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.task.controllers;
+
+import android.content.ComponentName;
+import android.content.Task;
+import android.content.pm.PackageParser;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Uniquely identifies a task internally.
+ * Created from the public {@link android.content.Task} object when it lands on the scheduler.
+ * Contains current state of the requirements of the task, as well as a function to evaluate
+ * whether it's ready to run.
+ * This object is shared among the various controllers - hence why the different fields are atomic.
+ * This isn't strictly necessary because each controller is only interested in a specific field,
+ * and the receivers that are listening for global state change will all run on the main looper,
+ * but we don't enforce that so this is safer.
+ * @hide
+ */
+public class TaskStatus {
+ final int taskId;
+ final int userId;
+ final int uId;
+ final ComponentName component;
+ final Bundle extras;
+
+ final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
+ final AtomicBoolean timeConstraintSatisfied = new AtomicBoolean();
+ final AtomicBoolean idleConstraintSatisfied = new AtomicBoolean();
+ final AtomicBoolean meteredConstraintSatisfied = new AtomicBoolean();
+ final AtomicBoolean connectivityConstraintSatisfied = new AtomicBoolean();
+
+ private final boolean hasChargingConstraint;
+ private final boolean hasTimingConstraint;
+ private final boolean hasIdleConstraint;
+ private final boolean hasMeteredConstraint;
+ private final boolean hasConnectivityConstraint;
+
+ private long earliestRunTimeElapsedMillis;
+ private long latestRunTimeElapsedMillis;
+
+ /** Provide a unique handle to the service that this task will be run on. */
+ public int getServiceToken() {
+ return component.hashCode() + userId;
+ }
+
+ /** Generate a TaskStatus object for a given task and uid. */
+ // TODO: reimplement this to reuse these objects instead of creating a new one each time?
+ public static TaskStatus getForTaskAndUser(Task task, int userId, int uId) {
+ return new TaskStatus(task, userId, uId);
+ }
+
+ /** Set up the state of a newly scheduled task. */
+ TaskStatus(Task task, int userId, int uId) {
+ this.taskId = task.getTaskId();
+ this.userId = userId;
+ this.component = task.getService();
+ this.extras = task.getExtras();
+ this.uId = uId;
+
+ hasChargingConstraint = task.isRequireCharging();
+ hasIdleConstraint = task.isRequireDeviceIdle();
+
+ // Timing constraints
+ if (task.isPeriodic()) {
+ long elapsedNow = SystemClock.elapsedRealtime();
+ earliestRunTimeElapsedMillis = elapsedNow;
+ latestRunTimeElapsedMillis = elapsedNow + task.getIntervalMillis();
+ hasTimingConstraint = true;
+ } else if (task.getMinLatencyMillis() != 0L || task.getMaxExecutionDelayMillis() != 0L) {
+ earliestRunTimeElapsedMillis = task.getMinLatencyMillis() > 0L ?
+ task.getMinLatencyMillis() : Long.MAX_VALUE;
+ latestRunTimeElapsedMillis = task.getMaxExecutionDelayMillis() > 0L ?
+ task.getMaxExecutionDelayMillis() : Long.MAX_VALUE;
+ hasTimingConstraint = true;
+ } else {
+ hasTimingConstraint = false;
+ }
+
+ // Networking constraints
+ hasMeteredConstraint = task.getNetworkCapabilities() == Task.NetworkType.UNMETERED;
+ hasConnectivityConstraint = task.getNetworkCapabilities() == Task.NetworkType.ANY;
+ }
+
+ public int getTaskId() {
+ return taskId;
+ }
+
+ public ComponentName getServiceComponent() {
+ return component;
+ }
+
+ public int getUserId() {
+ return userId;
+ }
+
+ public int getUid() {
+ return uId;
+ }
+
+ public Bundle getExtras() {
+ return extras;
+ }
+
+ boolean hasConnectivityConstraint() {
+ return hasConnectivityConstraint;
+ }
+
+ boolean hasMeteredConstraint() {
+ return hasMeteredConstraint;
+ }
+
+ boolean hasChargingConstraint() {
+ return hasChargingConstraint;
+ }
+
+ boolean hasTimingConstraint() {
+ return hasTimingConstraint;
+ }
+
+ boolean hasIdleConstraint() {
+ return hasIdleConstraint;
+ }
+
+ long getEarliestRunTime() {
+ return earliestRunTimeElapsedMillis;
+ }
+
+ long getLatestRunTime() {
+ return latestRunTimeElapsedMillis;
+ }
+
+ /**
+ * @return whether this task is ready to run, based on its requirements.
+ */
+ public synchronized boolean isReady() {
+ return (!hasChargingConstraint || chargingConstraintSatisfied.get())
+ && (!hasTimingConstraint || timeConstraintSatisfied.get())
+ && (!hasConnectivityConstraint || connectivityConstraintSatisfied.get())
+ && (!hasMeteredConstraint || meteredConstraintSatisfied.get())
+ && (!hasIdleConstraint || idleConstraintSatisfied.get());
+ }
+
+ @Override
+ public int hashCode() {
+ int result = component.hashCode();
+ result = 31 * result + taskId;
+ result = 31 * result + userId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TaskStatus)) return false;
+
+ TaskStatus that = (TaskStatus) o;
+ return ((taskId == that.taskId)
+ && (userId == that.userId)
+ && (component.equals(that.component)));
+ }
+}
diff --git a/services/core/java/com/android/server/task/controllers/TimeController.java b/services/core/java/com/android/server/task/controllers/TimeController.java
new file mode 100644
index 0000000..6d97a53
--- /dev/null
+++ b/services/core/java/com/android/server/task/controllers/TimeController.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.task.controllers;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.server.task.TaskManagerService;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * This class sets an alarm for the next expiring task, and determines whether a task's minimum
+ * delay has been satisfied.
+ */
+public class TimeController extends StateController {
+ private static final String TAG = "TaskManager.Time";
+ private static final String ACTION_TASK_EXPIRED =
+ "android.content.taskmanager.TASK_EXPIRED";
+ private static final String ACTION_TASK_DELAY_EXPIRED =
+ "android.content.taskmanager.TASK_DELAY_EXPIRED";
+
+ /** Set an alarm for the next task expiry. */
+ private final PendingIntent mTaskExpiredAlarmIntent;
+ /** Set an alarm for the next task delay expiry. This*/
+ private final PendingIntent mNextDelayExpiredAlarmIntent;
+
+ private long mNextTaskExpiredElapsedMillis;
+ private long mNextDelayExpiredElapsedMillis;
+
+ private AlarmManager mAlarmService = null;
+ /** List of tracked tasks, sorted asc. by deadline */
+ private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>();
+
+ public TimeController(TaskManagerService service) {
+ super(service);
+ mTaskExpiredAlarmIntent =
+ PendingIntent.getBroadcast(mContext, 0 /* ignored */,
+ new Intent(ACTION_TASK_EXPIRED), 0);
+ mNextDelayExpiredAlarmIntent =
+ PendingIntent.getBroadcast(mContext, 0 /* ignored */,
+ new Intent(ACTION_TASK_DELAY_EXPIRED), 0);
+
+ // Register BR for these intents.
+ IntentFilter intentFilter = new IntentFilter(ACTION_TASK_EXPIRED);
+ intentFilter.addAction(ACTION_TASK_DELAY_EXPIRED);
+ mContext.registerReceiver(mAlarmExpiredReceiver, intentFilter);
+ }
+
+ /**
+ * Check if the task has a timing constraint, and if so determine where to insert it in our
+ * list.
+ */
+ @Override
+ public synchronized void maybeTrackTaskState(TaskStatus task) {
+ if (task.hasTimingConstraint()) {
+ ListIterator<TaskStatus> it = mTrackedTasks.listIterator(mTrackedTasks.size());
+ while (it.hasPrevious()) {
+ TaskStatus ts = it.previous();
+ if (ts.equals(task)) {
+ // Update
+ it.remove();
+ it.add(task);
+ break;
+ } else if (ts.getLatestRunTime() < task.getLatestRunTime()) {
+ // Insert
+ it.add(task);
+ break;
+ }
+ }
+ maybeUpdateAlarms(task.getEarliestRunTime(), task.getLatestRunTime());
+ }
+ }
+
+ /**
+ * If the task passed in is being tracked, figure out if we need to update our alarms, and if
+ * so, update them.
+ */
+ @Override
+ public synchronized void removeTaskStateIfTracked(TaskStatus taskStatus) {
+ if (mTrackedTasks.remove(taskStatus)) {
+ if (mNextDelayExpiredElapsedMillis <= taskStatus.getEarliestRunTime()) {
+ handleTaskDelayExpired();
+ }
+ if (mNextTaskExpiredElapsedMillis <= taskStatus.getLatestRunTime()) {
+ handleTaskDeadlineExpired();
+ }
+ }
+ }
+
+ /**
+ * Set an alarm with the {@link android.app.AlarmManager} for the next time at which a task's
+ * delay will expire.
+ * This alarm <b>will not</b> wake up the phone.
+ */
+ private void setDelayExpiredAlarm(long alarmTimeElapsedMillis) {
+ ensureAlarmService();
+ mAlarmService.set(AlarmManager.ELAPSED_REALTIME, alarmTimeElapsedMillis,
+ mNextDelayExpiredAlarmIntent);
+ }
+
+ /**
+ * Set an alarm with the {@link android.app.AlarmManager} for the next time at which a task's
+ * deadline will expire.
+ * This alarm <b>will</b> wake up the phone.
+ */
+ private void setDeadlineExpiredAlarm(long alarmTimeElapsedMillis) {
+ ensureAlarmService();
+ mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsedMillis,
+ mTaskExpiredAlarmIntent);
+ }
+
+ /**
+ * Determines whether this controller can stop tracking the given task.
+ * The controller is no longer interested in a task once its time constraint is satisfied, and
+ * the task's deadline is fulfilled - unlike other controllers a time constraint can't toggle
+ * back and forth.
+ */
+ private boolean canStopTrackingTask(TaskStatus taskStatus) {
+ final long elapsedNowMillis = SystemClock.elapsedRealtime();
+ return taskStatus.timeConstraintSatisfied.get() &&
+ (taskStatus.getLatestRunTime() == Long.MAX_VALUE ||
+ taskStatus.getLatestRunTime() < elapsedNowMillis);
+ }
+
+ private void maybeUpdateAlarms(long delayExpiredElapsed, long deadlineExpiredElapsed) {
+ if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
+ mNextDelayExpiredElapsedMillis = delayExpiredElapsed;
+ setDelayExpiredAlarm(mNextDelayExpiredElapsedMillis);
+ }
+ if (deadlineExpiredElapsed < mNextTaskExpiredElapsedMillis) {
+ mNextTaskExpiredElapsedMillis = deadlineExpiredElapsed;
+ setDeadlineExpiredAlarm(mNextTaskExpiredElapsedMillis);
+ }
+ }
+
+ private void ensureAlarmService() {
+ if (mAlarmService == null) {
+ mAlarmService = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ }
+ }
+
+ /**
+ * Handles alarm that notifies that a task has expired. When this function is called at least
+ * one task must be run.
+ */
+ private synchronized void handleTaskDeadlineExpired() {
+ long nextExpiryTime = Long.MAX_VALUE;
+ final long nowElapsedMillis = SystemClock.elapsedRealtime();
+
+ Iterator<TaskStatus> it = mTrackedTasks.iterator();
+ while (it.hasNext()) {
+ TaskStatus ts = it.next();
+ final long taskDeadline = ts.getLatestRunTime();
+
+ if (taskDeadline <= nowElapsedMillis) {
+ ts.timeConstraintSatisfied.set(true);
+ mStateChangedListener.onTaskDeadlineExpired(ts);
+ it.remove();
+ } else { // Sorted by expiry time, so take the next one and stop.
+ nextExpiryTime = taskDeadline;
+ break;
+ }
+ }
+ maybeUpdateAlarms(Long.MAX_VALUE, nextExpiryTime);
+ }
+
+ /**
+ * Handles alarm that notifies us that a task's delay has expired. Iterates through the list of
+ * tracked tasks and marks them as ready as appropriate.
+ */
+ private synchronized void handleTaskDelayExpired() {
+ final long nowElapsedMillis = SystemClock.elapsedRealtime();
+ long nextDelayTime = Long.MAX_VALUE;
+
+ Iterator<TaskStatus> it = mTrackedTasks.iterator();
+ while (it.hasNext()) {
+ final TaskStatus ts = it.next();
+ final long taskDelayTime = ts.getEarliestRunTime();
+ if (taskDelayTime < nowElapsedMillis) {
+ ts.timeConstraintSatisfied.set(true);
+ mStateChangedListener.onTaskStateChanged(ts);
+ if (canStopTrackingTask(ts)) {
+ it.remove();
+ }
+ } else { // Keep going through list to get next delay time.
+ if (nextDelayTime > taskDelayTime) {
+ nextDelayTime = taskDelayTime;
+ }
+ }
+ }
+ maybeUpdateAlarms(nextDelayTime, Long.MAX_VALUE);
+ }
+
+ private final BroadcastReceiver mAlarmExpiredReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // An task has just expired, so we run through the list of tasks that we have and
+ // notify our StateChangedListener.
+ if (ACTION_TASK_EXPIRED.equals(intent.getAction())) {
+ handleTaskDeadlineExpired();
+ } else if (ACTION_TASK_DELAY_EXPIRED.equals(intent.getAction())) {
+ handleTaskDelayExpired();
+ }
+ }
+ };
+}
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
new file mode 100644
index 0000000..d8d3da1
--- /dev/null
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.trust;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+import android.service.trust.ITrustAgentService;
+import android.service.trust.ITrustAgentServiceCallback;
+
+/**
+ * A wrapper around a TrustAgentService interface. Coordinates communication between
+ * TrustManager and the actual TrustAgent.
+ */
+public class TrustAgentWrapper {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "TrustAgentWrapper";
+
+ private static final int MSG_ENABLE_TRUST = 1;
+ private static final int MSG_REVOKE_TRUST = 2;
+ private static final int MSG_TRUST_TIMEOUT = 3;
+
+ private final TrustManagerService mTrustManagerService;
+ private final int mUserId;
+ private final Context mContext;
+ private final ComponentName mName;
+
+ private ITrustAgentService mTrustAgentService;
+
+ // Trust state
+ private boolean mTrusted;
+ private CharSequence mMessage;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_ENABLE_TRUST:
+ mTrusted = true;
+ mMessage = (CharSequence) msg.obj;
+ boolean initiatedByUser = msg.arg1 != 0;
+ // TODO: Handle handle user initiated trust changes.
+ mTrustManagerService.updateTrust(mUserId);
+ break;
+ case MSG_TRUST_TIMEOUT:
+ if (DEBUG) Slog.v(TAG, "Trust timed out : " + mName.flattenToShortString());
+ // Fall through.
+ case MSG_REVOKE_TRUST:
+ mTrusted = false;
+ mMessage = null;
+ mTrustManagerService.updateTrust(mUserId);
+ break;
+ }
+ }
+ };
+
+ private ITrustAgentServiceCallback mCallback = new ITrustAgentServiceCallback.Stub() {
+
+ @Override
+ public void grantTrust(CharSequence userMessage, long durationMs, boolean initiatedByUser) {
+ if (DEBUG) Slog.v(TAG, "enableTrust(" + userMessage + ", durationMs = " + durationMs
+ + ", initiatedByUser = " + initiatedByUser + ")");
+
+ mHandler.obtainMessage(MSG_ENABLE_TRUST, initiatedByUser ? 1 : 0, 0, userMessage)
+ .sendToTarget();
+ if (durationMs > 0) {
+ mHandler.removeMessages(MSG_TRUST_TIMEOUT);
+ mHandler.sendEmptyMessageDelayed(MSG_TRUST_TIMEOUT, durationMs);
+ }
+ }
+
+ @Override
+ public void revokeTrust() {
+ if (DEBUG) Slog.v(TAG, "revokeTrust()");
+ mHandler.sendEmptyMessage(MSG_REVOKE_TRUST);
+ }
+ };
+
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) Log.v(TAG, "TrustAgent started : " + name.flattenToString());
+ mTrustAgentService = ITrustAgentService.Stub.asInterface(service);
+ setCallback(mCallback);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) Log.v(TAG, "TrustAgent disconnected : " + name.flattenToShortString());
+ mTrustAgentService = null;
+ mHandler.sendEmptyMessage(MSG_REVOKE_TRUST);
+ }
+ };
+
+
+ public TrustAgentWrapper(Context context, TrustManagerService trustManagerService,
+ Intent intent, UserHandle user) {
+ mContext = context;
+ mTrustManagerService = trustManagerService;
+ mUserId = user.getIdentifier();
+ mName = intent.getComponent();
+ if (!context.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, user)) {
+ if (DEBUG) Log.v(TAG, "can't bind to TrustAgent " + mName.flattenToShortString());
+ // TODO: retry somehow?
+ }
+ }
+
+ private void onError(Exception e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+
+ /**
+ * @see android.service.trust.TrustAgentService#onUnlockAttempt(boolean)
+ */
+ public void onUnlockAttempt(boolean successful) {
+ try {
+ if (mTrustAgentService != null) mTrustAgentService.onUnlockAttempt(successful);
+ } catch (RemoteException e) {
+ onError(e);
+ }
+ }
+
+ private void setCallback(ITrustAgentServiceCallback callback) {
+ try {
+ if (mTrustAgentService != null) {
+ mTrustAgentService.setCallback(callback);
+ }
+ } catch (RemoteException e) {
+ onError(e);
+ }
+ }
+
+ public boolean isTrusted() {
+ return mTrusted;
+ }
+
+ public CharSequence getMessage() {
+ return mMessage;
+ }
+
+ public void unbind() {
+ if (DEBUG) Log.v(TAG, "TrustAgent unbound : " + mName.flattenToShortString());
+ mContext.unbindService(mConnection);
+ }
+}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
new file mode 100644
index 0000000..c1b9a33
--- /dev/null
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.trust;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.SystemService;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.Manifest;
+import android.app.admin.DevicePolicyManager;
+import android.app.trust.ITrustListener;
+import android.app.trust.ITrustManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.service.trust.TrustAgentService;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.Xml;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages trust agents and trust listeners.
+ *
+ * It is responsible for binding to the enabled {@link android.service.trust.TrustAgentService}s
+ * of each user and notifies them about events that are relevant to them.
+ * It start and stops them based on the value of
+ * {@link com.android.internal.widget.LockPatternUtils#getEnabledTrustAgents(int)}.
+ *
+ * It also keeps a set of {@link android.app.trust.ITrustListener}s that are notified whenever the
+ * trust state changes for any user.
+ *
+ * Trust state and the setting of enabled agents is kept per user and each user has its own
+ * instance of a {@link android.service.trust.TrustAgentService}.
+ */
+public class TrustManagerService extends SystemService {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "TrustManagerService";
+
+ private static final Intent TRUST_AGENT_INTENT =
+ new Intent(TrustAgentService.SERVICE_INTERFACE);
+
+ private static final int MSG_REGISTER_LISTENER = 1;
+ 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 final ArraySet<AgentInfo> mActiveAgents = new ArraySet<AgentInfo>();
+ private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<ITrustListener>();
+ private final DevicePolicyReceiver mDevicePolicyReceiver = new DevicePolicyReceiver();
+ private final SparseBooleanArray mUserHasAuthenticatedSinceBoot = new SparseBooleanArray();
+ private final Context mContext;
+
+ private UserManager mUserManager;
+
+ /**
+ * Cache for {@link #refreshAgentList()}
+ */
+ private final ArraySet<AgentInfo> mObsoleteAgents = new ArraySet<AgentInfo>();
+
+
+ public TrustManagerService(Context context) {
+ super(context);
+ mContext = context;
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.TRUST_SERVICE, mService);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY && !isSafeMode()) {
+ mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
+ mDevicePolicyReceiver.register(mContext);
+ refreshAgentList();
+ }
+ }
+
+ // Agent management
+
+ private static final class AgentInfo {
+ CharSequence label;
+ Drawable icon;
+ ComponentName component; // service that implements ITrustAgent
+ ComponentName settings; // setting to launch to modify agent.
+ TrustAgentWrapper agent;
+ int userId;
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof AgentInfo)) {
+ return false;
+ }
+ AgentInfo o = (AgentInfo) other;
+ return component.equals(o.component) && userId == o.userId;
+ }
+
+ @Override
+ public int hashCode() {
+ return component.hashCode() * 31 + userId;
+ }
+ }
+
+ private void updateTrustAll() {
+ List<UserInfo> userInfos = mUserManager.getUsers(true /* excludeDying */);
+ for (UserInfo userInfo : userInfos) {
+ updateTrust(userInfo.id);
+ }
+ }
+
+ public void updateTrust(int userId) {
+ dispatchOnTrustChanged(aggregateIsTrusted(userId), userId);
+ }
+
+ protected void refreshAgentList() {
+ if (DEBUG) Slog.d(TAG, "refreshAgentList()");
+ PackageManager pm = mContext.getPackageManager();
+
+ List<UserInfo> userInfos = mUserManager.getUsers(true /* excludeDying */);
+ LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
+
+ mObsoleteAgents.clear();
+ mObsoleteAgents.addAll(mActiveAgents);
+
+ for (UserInfo userInfo : userInfos) {
+ int disabledFeatures = lockPatternUtils.getDevicePolicyManager()
+ .getKeyguardDisabledFeatures(null, userInfo.id);
+ boolean disableTrustAgents =
+ (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS) != 0;
+
+ List<ComponentName> enabledAgents = lockPatternUtils.getEnabledTrustAgents(userInfo.id);
+ if (disableTrustAgents || enabledAgents == null) {
+ continue;
+ }
+ List<ResolveInfo> resolveInfos = pm.queryIntentServicesAsUser(TRUST_AGENT_INTENT,
+ PackageManager.GET_META_DATA, userInfo.id);
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ if (resolveInfo.serviceInfo == null) continue;
+ ComponentName name = getComponentName(resolveInfo);
+ if (!enabledAgents.contains(name)) continue;
+
+ AgentInfo agentInfo = new AgentInfo();
+ agentInfo.component = name;
+ agentInfo.userId = userInfo.id;
+ if (!mActiveAgents.contains(agentInfo)) {
+ agentInfo.label = resolveInfo.loadLabel(pm);
+ agentInfo.icon = resolveInfo.loadIcon(pm);
+ agentInfo.settings = getSettingsComponentName(pm, resolveInfo);
+ agentInfo.agent = new TrustAgentWrapper(mContext, this,
+ new Intent().setComponent(name), userInfo.getUserHandle());
+ mActiveAgents.add(agentInfo);
+ } else {
+ mObsoleteAgents.remove(agentInfo);
+ }
+ }
+ }
+
+ boolean trustMayHaveChanged = false;
+ for (int i = 0; i < mObsoleteAgents.size(); i++) {
+ AgentInfo info = mObsoleteAgents.valueAt(i);
+ if (info.agent.isTrusted()) {
+ trustMayHaveChanged = true;
+ }
+ info.agent.unbind();
+ mActiveAgents.remove(info);
+ }
+
+ if (trustMayHaveChanged) {
+ updateTrustAll();
+ }
+ }
+
+ private ComponentName getSettingsComponentName(PackageManager pm, ResolveInfo resolveInfo) {
+ if (resolveInfo == null || resolveInfo.serviceInfo == null
+ || resolveInfo.serviceInfo.metaData == null) return null;
+ String cn = null;
+ XmlResourceParser parser = null;
+ Exception caughtException = null;
+ try {
+ parser = resolveInfo.serviceInfo.loadXmlMetaData(pm,
+ TrustAgentService.TRUST_AGENT_META_DATA);
+ if (parser == null) {
+ Slog.w(TAG, "Can't find " + TrustAgentService.TRUST_AGENT_META_DATA + " meta-data");
+ return null;
+ }
+ Resources res = pm.getResourcesForApplication(resolveInfo.serviceInfo.applicationInfo);
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ // Drain preamble.
+ }
+ String nodeName = parser.getName();
+ if (!"trust-agent".equals(nodeName)) {
+ Slog.w(TAG, "Meta-data does not start with trust-agent tag");
+ return null;
+ }
+ TypedArray sa = res
+ .obtainAttributes(attrs, com.android.internal.R.styleable.TrustAgent);
+ cn = sa.getString(com.android.internal.R.styleable.TrustAgent_settingsActivity);
+ sa.recycle();
+ } catch (PackageManager.NameNotFoundException e) {
+ caughtException = e;
+ } catch (IOException e) {
+ caughtException = e;
+ } catch (XmlPullParserException e) {
+ caughtException = e;
+ } finally {
+ if (parser != null) parser.close();
+ }
+ if (caughtException != null) {
+ Slog.w(TAG, "Error parsing : " + resolveInfo.serviceInfo.packageName, caughtException);
+ return null;
+ }
+ if (cn == null) {
+ return null;
+ }
+ if (cn.indexOf('/') < 0) {
+ cn = resolveInfo.serviceInfo.packageName + "/" + cn;
+ }
+ return ComponentName.unflattenFromString(cn);
+ }
+
+ private ComponentName getComponentName(ResolveInfo resolveInfo) {
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) return null;
+ return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
+ }
+
+ // Agent dispatch and aggregation
+
+ private boolean aggregateIsTrusted(int userId) {
+ if (!mUserHasAuthenticatedSinceBoot.get(userId)) {
+ return false;
+ }
+ for (int i = 0; i < mActiveAgents.size(); i++) {
+ AgentInfo info = mActiveAgents.valueAt(i);
+ if (info.userId == userId) {
+ if (info.agent.isTrusted()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void dispatchUnlockAttempt(boolean successful, int userId) {
+ for (int i = 0; i < mActiveAgents.size(); i++) {
+ AgentInfo info = mActiveAgents.valueAt(i);
+ if (info.userId == userId) {
+ info.agent.onUnlockAttempt(successful);
+ }
+ }
+
+ if (successful && !mUserHasAuthenticatedSinceBoot.get(userId)) {
+ mUserHasAuthenticatedSinceBoot.put(userId, true);
+ updateTrust(userId);
+ }
+ }
+
+ // Listeners
+
+ private void addListener(ITrustListener listener) {
+ for (int i = 0; i < mTrustListeners.size(); i++) {
+ if (mTrustListeners.get(i).asBinder() == listener.asBinder()) {
+ return;
+ }
+ }
+ mTrustListeners.add(listener);
+ }
+
+ private void removeListener(ITrustListener listener) {
+ for (int i = 0; i < mTrustListeners.size(); i++) {
+ if (mTrustListeners.get(i).asBinder() == listener.asBinder()) {
+ mTrustListeners.get(i);
+ return;
+ }
+ }
+ }
+
+ private void dispatchOnTrustChanged(boolean enabled, int userId) {
+ for (int i = 0; i < mTrustListeners.size(); i++) {
+ try {
+ mTrustListeners.get(i).onTrustChanged(enabled, userId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception while notifying TrustListener. Removing listener.", e);
+ mTrustListeners.get(i);
+ i--;
+ }
+ }
+ }
+
+ // Plumbing
+
+ private final IBinder mService = new ITrustManager.Stub() {
+ @Override
+ public void reportUnlockAttempt(boolean authenticated, int userId) throws RemoteException {
+ enforceReportPermission();
+ mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_ATTEMPT, authenticated ? 1 : 0, userId)
+ .sendToTarget();
+ }
+
+ @Override
+ public void reportEnabledTrustAgentsChanged(int userId) throws RemoteException {
+ enforceReportPermission();
+ // coalesce refresh messages.
+ mHandler.removeMessages(MSG_ENABLED_AGENTS_CHANGED);
+ mHandler.sendEmptyMessage(MSG_ENABLED_AGENTS_CHANGED);
+ }
+
+ @Override
+ public void registerTrustListener(ITrustListener trustListener) throws RemoteException {
+ enforceListenerPermission();
+ mHandler.obtainMessage(MSG_REGISTER_LISTENER, trustListener).sendToTarget();
+ }
+
+ @Override
+ public void unregisterTrustListener(ITrustListener trustListener) throws RemoteException {
+ enforceListenerPermission();
+ mHandler.obtainMessage(MSG_UNREGISTER_LISTENER, trustListener).sendToTarget();
+ }
+
+ private void enforceReportPermission() {
+ mContext.enforceCallingPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE,
+ "reporting trust events");
+ }
+
+ private void enforceListenerPermission() {
+ mContext.enforceCallingPermission(Manifest.permission.TRUST_LISTENER,
+ "register trust listener");
+ }
+ };
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_REGISTER_LISTENER:
+ addListener((ITrustListener) msg.obj);
+ break;
+ case MSG_UNREGISTER_LISTENER:
+ removeListener((ITrustListener) msg.obj);
+ break;
+ case MSG_DISPATCH_UNLOCK_ATTEMPT:
+ dispatchUnlockAttempt(msg.arg1 != 0, msg.arg2);
+ break;
+ case MSG_ENABLED_AGENTS_CHANGED:
+ refreshAgentList();
+ break;
+ }
+ }
+ };
+
+ private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public void onSomePackagesChanged() {
+ refreshAgentList();
+ }
+
+ @Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ // We're interested in all changes, even if just some components get enabled / disabled.
+ return true;
+ }
+ };
+
+ private class DevicePolicyReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(
+ intent.getAction())) {
+ refreshAgentList();
+ }
+ }
+
+ public void register(Context context) {
+ context.registerReceiverAsUser(this,
+ UserHandle.ALL,
+ new IntentFilter(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ null /* permission */,
+ null /* scheduler */);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
new file mode 100644
index 0000000..8ad7fff
--- /dev/null
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -0,0 +1,1043 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.tv;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.database.Cursor;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.TvContract;
+import android.tv.ITvInputClient;
+import android.tv.ITvInputManager;
+import android.tv.ITvInputService;
+import android.tv.ITvInputServiceCallback;
+import android.tv.ITvInputSession;
+import android.tv.ITvInputSessionCallback;
+import android.tv.TvInputInfo;
+import android.tv.TvInputService;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.InputChannel;
+import android.view.Surface;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.SomeArgs;
+import com.android.server.IoThread;
+import com.android.server.SystemService;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** This class provides a system service that manages television inputs. */
+public final class TvInputManagerService extends SystemService {
+ // STOPSHIP: Turn debugging off.
+ private static final boolean DEBUG = true;
+ private static final String TAG = "TvInputManagerService";
+
+ private final Context mContext;
+
+ private final ContentResolver mContentResolver;
+
+ // A global lock.
+ private final Object mLock = new Object();
+
+ // ID of the current user.
+ private int mCurrentUserId = UserHandle.USER_OWNER;
+
+ // A map from user id to UserState.
+ private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
+
+ private final Handler mLogHandler;
+
+ public TvInputManagerService(Context context) {
+ super(context);
+
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ mLogHandler = new LogHandler(IoThread.get().getLooper());
+
+ registerBroadcastReceivers();
+
+ synchronized (mLock) {
+ mUserStates.put(mCurrentUserId, new UserState());
+ buildTvInputListLocked(mCurrentUserId);
+ }
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
+ }
+
+ private void registerBroadcastReceivers() {
+ PackageMonitor monitor = new PackageMonitor() {
+ @Override
+ public void onSomePackagesChanged() {
+ synchronized (mLock) {
+ buildTvInputListLocked(mCurrentUserId);
+ }
+ }
+ };
+ monitor.register(mContext, null, UserHandle.ALL, true);
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+ } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+ removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+ }
+ }
+ }, UserHandle.ALL, intentFilter, null, null);
+ }
+
+ private void buildTvInputListLocked(int userId) {
+ UserState userState = getUserStateLocked(userId);
+ userState.inputMap.clear();
+
+ if (DEBUG) Slog.d(TAG, "buildTvInputList");
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> services = pm.queryIntentServices(
+ new Intent(TvInputService.SERVICE_INTERFACE), PackageManager.GET_SERVICES);
+ for (ResolveInfo ri : services) {
+ ServiceInfo si = ri.serviceInfo;
+ if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
+ Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
+ + android.Manifest.permission.BIND_TV_INPUT);
+ continue;
+ }
+ TvInputInfo info = new TvInputInfo(ri);
+ if (DEBUG) Slog.d(TAG, "add " + info.getId());
+ userState.inputMap.put(info.getId(), info);
+ }
+ }
+
+ private void switchUser(int userId) {
+ synchronized (mLock) {
+ if (mCurrentUserId == userId) {
+ return;
+ }
+ // final int oldUserId = mCurrentUserId;
+ // TODO: Release services and sessions in the old user state, if needed.
+ mCurrentUserId = userId;
+
+ UserState userState = mUserStates.get(userId);
+ if (userState == null) {
+ userState = new UserState();
+ }
+ mUserStates.put(userId, userState);
+ buildTvInputListLocked(userId);
+ }
+ }
+
+ private void removeUser(int userId) {
+ synchronized (mLock) {
+ UserState userState = mUserStates.get(userId);
+ if (userState == null) {
+ return;
+ }
+ // Release created sessions.
+ for (SessionState state : userState.sessionStateMap.values()) {
+ if (state.mSession != null) {
+ try {
+ state.mSession.release();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in release", e);
+ }
+ }
+ }
+ userState.sessionStateMap.clear();
+
+ // Unregister all callbacks and unbind all services.
+ for (ServiceState serviceState : userState.serviceStateMap.values()) {
+ if (serviceState.mCallback != null) {
+ try {
+ serviceState.mService.unregisterCallback(serviceState.mCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in unregisterCallback", e);
+ }
+ }
+ serviceState.mClients.clear();
+ mContext.unbindService(serviceState.mConnection);
+ }
+ userState.serviceStateMap.clear();
+
+ mUserStates.remove(userId);
+ }
+ }
+
+ private UserState getUserStateLocked(int userId) {
+ UserState userState = mUserStates.get(userId);
+ if (userState == null) {
+ throw new IllegalStateException("User state not found for user ID " + userId);
+ }
+ return userState;
+ }
+
+ private ServiceState getServiceStateLocked(String inputId, int userId) {
+ UserState userState = getUserStateLocked(userId);
+ ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ if (serviceState == null) {
+ throw new IllegalStateException("Service state not found for " + inputId + " (userId="
+ + userId + ")");
+ }
+ return serviceState;
+ }
+
+ private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
+ UserState userState = getUserStateLocked(userId);
+ SessionState sessionState = userState.sessionStateMap.get(sessionToken);
+ if (sessionState == null) {
+ throw new IllegalArgumentException("Session state not found for token " + sessionToken);
+ }
+ // Only the application that requested this session or the system can access it.
+ if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.mCallingUid) {
+ throw new SecurityException("Illegal access to the session with token " + sessionToken
+ + " from uid " + callingUid);
+ }
+ return sessionState;
+ }
+
+ private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
+ SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
+ ITvInputSession session = sessionState.mSession;
+ if (session == null) {
+ throw new IllegalStateException("Session not yet created for token " + sessionToken);
+ }
+ return session;
+ }
+
+ private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
+ String methodName) {
+ return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
+ false, methodName, null);
+ }
+
+ private void updateServiceConnectionLocked(String inputId, int userId) {
+ UserState userState = getUserStateLocked(userId);
+ ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ if (serviceState == null) {
+ return;
+ }
+ if (serviceState.mReconnecting) {
+ if (!serviceState.mSessionTokens.isEmpty()) {
+ // wait until all the sessions are removed.
+ return;
+ }
+ serviceState.mReconnecting = false;
+ }
+ boolean isStateEmpty = serviceState.mClients.isEmpty()
+ && serviceState.mSessionTokens.isEmpty();
+ if (serviceState.mService == null && !isStateEmpty && userId == mCurrentUserId) {
+ // This means that the service is not yet connected but its state indicates that we
+ // have pending requests. Then, connect the service.
+ if (serviceState.mBound) {
+ // We have already bound to the service so we don't try to bind again until after we
+ // unbind later on.
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "bindServiceAsUser(inputId=" + inputId + ", userId=" + userId
+ + ")");
+ }
+
+ Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(
+ userState.inputMap.get(inputId).getComponent());
+ mContext.bindServiceAsUser(i, serviceState.mConnection, Context.BIND_AUTO_CREATE,
+ new UserHandle(userId));
+ serviceState.mBound = true;
+ } else if (serviceState.mService != null && isStateEmpty) {
+ // This means that the service is already connected but its state indicates that we have
+ // nothing to do with it. Then, disconnect the service.
+ if (DEBUG) {
+ Slog.d(TAG, "unbindService(inputId=" + inputId + ")");
+ }
+ mContext.unbindService(serviceState.mConnection);
+ userState.serviceStateMap.remove(inputId);
+ }
+ }
+
+ private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken,
+ final int userId) {
+ final SessionState sessionState =
+ getUserStateLocked(userId).sessionStateMap.get(sessionToken);
+ if (DEBUG) {
+ Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.mInputId + ")");
+ }
+
+ final InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
+
+ // Set up a callback to send the session token.
+ ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() {
+ @Override
+ public void onSessionCreated(ITvInputSession session) {
+ if (DEBUG) {
+ Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.mInputId + ")");
+ }
+ synchronized (mLock) {
+ sessionState.mSession = session;
+ if (session == null) {
+ removeSessionStateLocked(sessionToken, userId);
+ sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId,
+ null, null, sessionState.mSeq, userId);
+ } else {
+ try {
+ session.asBinder().linkToDeath(sessionState, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Session is already died.");
+ }
+ sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId,
+ sessionToken, channels[0], sessionState.mSeq, userId);
+ }
+ channels[0].dispose();
+ }
+ }
+ };
+
+ // Create a session. When failed, send a null token immediately.
+ try {
+ service.createSession(channels[1], callback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in createSession", e);
+ removeSessionStateLocked(sessionToken, userId);
+ sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, null, null,
+ sessionState.mSeq, userId);
+ }
+ channels[1].dispose();
+ }
+
+ private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
+ IBinder sessionToken, InputChannel channel, int seq, int userId) {
+ try {
+ client.onSessionCreated(inputId, sessionToken, channel, seq);
+ } catch (RemoteException exception) {
+ Slog.e(TAG, "error in onSessionCreated", exception);
+ }
+ }
+
+ private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
+ SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
+ if (sessionState.mSession != null) {
+ try {
+ sessionState.mSession.release();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "session is already disapeared", e);
+ }
+ sessionState.mSession = null;
+ }
+ removeSessionStateLocked(sessionToken, userId);
+ }
+
+ private void removeSessionStateLocked(IBinder sessionToken, int userId) {
+ // Remove the session state from the global session state map of the current user.
+ UserState userState = getUserStateLocked(userId);
+ SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
+
+ // Close the open log entry, if any.
+ if (sessionState.mLogUri != null) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = sessionState.mLogUri;
+ args.arg2 = System.currentTimeMillis();
+ mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget();
+ }
+
+ // Also remove the session token from the session token list of the current service.
+ ServiceState serviceState = userState.serviceStateMap.get(sessionState.mInputId);
+ if (serviceState != null) {
+ serviceState.mSessionTokens.remove(sessionToken);
+ }
+ updateServiceConnectionLocked(sessionState.mInputId, userId);
+ }
+
+ private void broadcastServiceAvailabilityChangedLocked(ServiceState serviceState) {
+ for (IBinder iBinder : serviceState.mClients) {
+ ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder);
+ try {
+ client.onAvailabilityChanged(
+ serviceState.mTvInputInfo.getId(), serviceState.mAvailable);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onAvailabilityChanged", e);
+ }
+ }
+ }
+
+ private final class BinderService extends ITvInputManager.Stub {
+ @Override
+ public List<TvInputInfo> getTvInputList(int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "getTvInputList");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(resolvedUserId);
+ return new ArrayList<TvInputInfo>(userState.inputMap.values());
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public boolean getAvailability(final ITvInputClient client, final String inputId,
+ int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "getAvailability");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(resolvedUserId);
+ ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ if (serviceState != null) {
+ // We already know the status of this input service. Return the cached
+ // status.
+ return serviceState.mAvailable;
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return false;
+ }
+
+ @Override
+ public void registerCallback(final ITvInputClient client, final String inputId,
+ int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "registerCallback");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ // Create a new service callback and add it to the callback map of the current
+ // service.
+ UserState userState = getUserStateLocked(resolvedUserId);
+ ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ if (serviceState == null) {
+ serviceState = new ServiceState(
+ userState.inputMap.get(inputId), resolvedUserId);
+ userState.serviceStateMap.put(inputId, serviceState);
+ }
+ IBinder iBinder = client.asBinder();
+ if (!serviceState.mClients.contains(iBinder)) {
+ serviceState.mClients.add(iBinder);
+ }
+ if (serviceState.mService != null) {
+ if (serviceState.mCallback != null) {
+ // We already handled.
+ return;
+ }
+ serviceState.mCallback = new ServiceCallback(resolvedUserId);
+ try {
+ serviceState.mService.registerCallback(serviceState.mCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in registerCallback", e);
+ }
+ } else {
+ updateServiceConnectionLocked(inputId, resolvedUserId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void unregisterCallback(ITvInputClient client, String inputId, int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "unregisterCallback");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(resolvedUserId);
+ ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ if (serviceState == null) {
+ return;
+ }
+
+ // Remove this client from the client list and unregister the callback.
+ serviceState.mClients.remove(client.asBinder());
+ if (!serviceState.mClients.isEmpty()) {
+ // We have other clients who want to keep the callback. Do this later.
+ return;
+ }
+ if (serviceState.mService == null || serviceState.mCallback == null) {
+ return;
+ }
+ try {
+ serviceState.mService.unregisterCallback(serviceState.mCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in unregisterCallback", e);
+ } finally {
+ serviceState.mCallback = null;
+ updateServiceConnectionLocked(inputId, resolvedUserId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void createSession(final ITvInputClient client, final String inputId,
+ int seq, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "createSession");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(resolvedUserId);
+ ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ if (serviceState == null) {
+ serviceState = new ServiceState(
+ userState.inputMap.get(inputId), resolvedUserId);
+ userState.serviceStateMap.put(inputId, serviceState);
+ }
+ // Send a null token immediately while reconnecting.
+ if (serviceState.mReconnecting == true) {
+ sendSessionTokenToClientLocked(client, inputId, null, null, seq, userId);
+ return;
+ }
+
+ // Create a new session token and a session state.
+ IBinder sessionToken = new Binder();
+ SessionState sessionState = new SessionState(
+ sessionToken, inputId, client, seq, callingUid, resolvedUserId);
+
+ // Add them to the global session state map of the current user.
+ userState.sessionStateMap.put(sessionToken, sessionState);
+
+ // Also, add them to the session state map of the current service.
+ serviceState.mSessionTokens.add(sessionToken);
+
+ if (serviceState.mService != null) {
+ createSessionInternalLocked(serviceState.mService, sessionToken,
+ resolvedUserId);
+ } else {
+ updateServiceConnectionLocked(inputId, resolvedUserId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void releaseSession(IBinder sessionToken, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "releaseSession");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void setSurface(IBinder sessionToken, Surface surface, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "setSurface");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
+ surface);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in setSurface", e);
+ }
+ }
+ } finally {
+ if (surface != null) {
+ // surface is not used in TvInputManagerService.
+ surface.release();
+ }
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void setVolume(IBinder sessionToken, float volume, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "setVolume");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
+ volume);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in setVolume", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "tune");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
+
+ long currentTime = System.currentTimeMillis();
+ long channelId = ContentUris.parseId(channelUri);
+
+ // Close the open log entry first, if any.
+ UserState userState = getUserStateLocked(resolvedUserId);
+ SessionState sessionState = userState.sessionStateMap.get(sessionToken);
+ if (sessionState.mLogUri != null) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = sessionState.mLogUri;
+ args.arg2 = currentTime;
+ mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
+ .sendToTarget();
+ }
+
+ // Create a log entry and fill it later.
+ ContentValues values = new ContentValues();
+ values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
+ currentTime);
+ values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 0);
+ values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
+
+ sessionState.mLogUri = mContentResolver.insert(
+ TvContract.WatchedPrograms.CONTENT_URI, values);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = sessionState.mLogUri;
+ args.arg2 = ContentUris.parseId(channelUri);
+ args.arg3 = currentTime;
+ mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in tune", e);
+ return;
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
+ int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "createOverlayView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .createOverlayView(windowToken, frame);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in createOverlayView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "relayoutOverlayView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .relayoutOverlayView(frame);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in relayoutOverlayView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void removeOverlayView(IBinder sessionToken, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "removeOverlayView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .removeOverlayView();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in removeOverlayView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ private static final class UserState {
+ // A mapping from the TV input id to its TvInputInfo.
+ private final Map<String, TvInputInfo> inputMap = new HashMap<String,TvInputInfo>();
+
+ // A mapping from the name of a TV input service to its state.
+ private final Map<String, ServiceState> serviceStateMap =
+ new HashMap<String, ServiceState>();
+
+ // A mapping from the token of a TV input session to its state.
+ private final Map<IBinder, SessionState> sessionStateMap =
+ new HashMap<IBinder, SessionState>();
+ }
+
+ private final class ServiceState {
+ // TODO: need to implement DeathRecipient for clients.
+ private final List<IBinder> mClients = new ArrayList<IBinder>();
+ private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
+ private final ServiceConnection mConnection;
+ private final TvInputInfo mTvInputInfo;
+
+ private ITvInputService mService;
+ private ServiceCallback mCallback;
+ private boolean mBound;
+ private boolean mAvailable;
+ private boolean mReconnecting;
+
+ private ServiceState(TvInputInfo inputInfo, int userId) {
+ mTvInputInfo = inputInfo;
+ mConnection = new InputServiceConnection(inputInfo, userId);
+ }
+ }
+
+ private final class SessionState implements IBinder.DeathRecipient {
+ private final String mInputId;
+ private final ITvInputClient mClient;
+ private final int mSeq;
+ private final int mCallingUid;
+ private final int mUserId;
+ private final IBinder mToken;
+ private ITvInputSession mSession;
+ private Uri mLogUri;
+
+ private SessionState(IBinder token, String inputId, ITvInputClient client, int seq,
+ int callingUid, int userId) {
+ mToken = token;
+ mInputId = inputId;
+ mClient = client;
+ mSeq = seq;
+ mCallingUid = callingUid;
+ mUserId = userId;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ mSession = null;
+ if (mClient != null) {
+ try {
+ mClient.onSessionReleased(mSeq);
+ } catch(RemoteException e) {
+ Slog.e(TAG, "error in onSessionReleased", e);
+ }
+ }
+ removeSessionStateLocked(mToken, mUserId);
+ }
+ }
+ }
+
+ private final class InputServiceConnection implements ServiceConnection {
+ private final TvInputInfo mTvInputInfo;
+ private final int mUserId;
+
+ private InputServiceConnection(TvInputInfo inputInfo, int userId) {
+ mUserId = userId;
+ mTvInputInfo = inputInfo;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) {
+ Slog.d(TAG, "onServiceConnected(inputId=" + mTvInputInfo.getId() + ")");
+ }
+ synchronized (mLock) {
+ ServiceState serviceState = getServiceStateLocked(mTvInputInfo.getId(), mUserId);
+ serviceState.mService = ITvInputService.Stub.asInterface(service);
+
+ // Register a callback, if we need to.
+ if (!serviceState.mClients.isEmpty() && serviceState.mCallback == null) {
+ serviceState.mCallback = new ServiceCallback(mUserId);
+ try {
+ serviceState.mService.registerCallback(serviceState.mCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in registerCallback", e);
+ }
+ }
+
+ // And create sessions, if any.
+ for (IBinder sessionToken : serviceState.mSessionTokens) {
+ createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) {
+ Slog.d(TAG, "onServiceDisconnected(inputId=" + mTvInputInfo.getId() + ")");
+ }
+ if (!mTvInputInfo.getComponent().equals(name)) {
+ throw new IllegalArgumentException("Mismatched ComponentName: "
+ + mTvInputInfo.getComponent() + " (expected), " + name + " (actual).");
+ }
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(mUserId);
+ ServiceState serviceState = userState.serviceStateMap.get(mTvInputInfo.getId());
+ if (serviceState != null) {
+ serviceState.mReconnecting = true;
+ serviceState.mBound = false;
+ serviceState.mService = null;
+ serviceState.mCallback = null;
+
+ // Send null tokens for not finishing create session events.
+ for (IBinder sessionToken : serviceState.mSessionTokens) {
+ SessionState sessionState = userState.sessionStateMap.get(sessionToken);
+ if (sessionState.mSession == null) {
+ removeSessionStateLocked(sessionToken, sessionState.mUserId);
+ sendSessionTokenToClientLocked(sessionState.mClient,
+ sessionState.mInputId, null, null, sessionState.mSeq,
+ sessionState.mUserId);
+ }
+ }
+
+ if (serviceState.mAvailable) {
+ serviceState.mAvailable = false;
+ broadcastServiceAvailabilityChangedLocked(serviceState);
+ }
+ updateServiceConnectionLocked(mTvInputInfo.getId(), mUserId);
+ }
+ }
+ }
+ }
+
+ private final class ServiceCallback extends ITvInputServiceCallback.Stub {
+ private final int mUserId;
+
+ ServiceCallback(int userId) {
+ mUserId = userId;
+ }
+
+ @Override
+ public void onAvailabilityChanged(String inputId, boolean isAvailable) {
+ if (DEBUG) {
+ Slog.d(TAG, "onAvailabilityChanged(inputId=" + inputId + ", isAvailable="
+ + isAvailable + ")");
+ }
+ synchronized (mLock) {
+ ServiceState serviceState = getServiceStateLocked(inputId, mUserId);
+ if (serviceState.mAvailable != isAvailable) {
+ serviceState.mAvailable = isAvailable;
+ broadcastServiceAvailabilityChangedLocked(serviceState);
+ }
+ }
+ }
+ }
+
+ private final class LogHandler extends Handler {
+ private static final int MSG_OPEN_ENTRY = 1;
+ private static final int MSG_UPDATE_ENTRY = 2;
+ private static final int MSG_CLOSE_ENTRY = 3;
+
+ public LogHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_OPEN_ENTRY: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Uri uri = (Uri) args.arg1;
+ long channelId = (long) args.arg2;
+ long time = (long) args.arg3;
+ onOpenEntry(uri, channelId, time);
+ args.recycle();
+ return;
+ }
+ case MSG_UPDATE_ENTRY: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Uri uri = (Uri) args.arg1;
+ long channelId = (long) args.arg2;
+ long time = (long) args.arg3;
+ onUpdateEntry(uri, channelId, time);
+ args.recycle();
+ return;
+ }
+ case MSG_CLOSE_ENTRY: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Uri uri = (Uri) args.arg1;
+ long time = (long) args.arg2;
+ onCloseEntry(uri, time);
+ args.recycle();
+ return;
+ }
+ default: {
+ Slog.w(TAG, "Unhandled message code: " + msg.what);
+ return;
+ }
+ }
+ }
+
+ private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
+ String[] projection = {
+ TvContract.Programs.COLUMN_TITLE,
+ TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
+ TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
+ TvContract.Programs.COLUMN_DESCRIPTION
+ };
+ String selection = TvContract.Programs.COLUMN_CHANNEL_ID + "=? AND "
+ + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND "
+ + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS + ">?";
+ String[] selectionArgs = {
+ String.valueOf(channelId),
+ String.valueOf(watchStarttime),
+ String.valueOf(watchStarttime)
+ };
+ String sortOrder = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC";
+ Cursor cursor = null;
+ try {
+ cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
+ selection, selectionArgs, sortOrder);
+ if (cursor != null && cursor.moveToNext()) {
+ ContentValues values = new ContentValues();
+ values.put(TvContract.WatchedPrograms.COLUMN_TITLE, cursor.getString(0));
+ values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
+ cursor.getLong(1));
+ long endTime = cursor.getLong(2);
+ values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
+ values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, cursor.getString(3));
+ mContentResolver.update(uri, values, null, null);
+
+ // Schedule an update when the current program ends.
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = uri;
+ args.arg2 = channelId;
+ args.arg3 = endTime;
+ Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
+ sendMessageDelayed(msg, endTime - System.currentTimeMillis());
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ private void onUpdateEntry(Uri uri, long channelId, long time) {
+ String[] projection = {
+ TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
+ TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
+ TvContract.WatchedPrograms.COLUMN_TITLE,
+ TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
+ TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS,
+ TvContract.WatchedPrograms.COLUMN_DESCRIPTION
+ };
+ Cursor cursor = null;
+ try {
+ cursor = mContentResolver.query(uri, projection, null, null, null);
+ if (cursor != null && cursor.moveToNext()) {
+ long watchStartTime = cursor.getLong(0);
+ long watchEndTime = cursor.getLong(1);
+ String title = cursor.getString(2);
+ long startTime = cursor.getLong(3);
+ long endTime = cursor.getLong(4);
+ String description = cursor.getString(5);
+
+ // Do nothing if the current log entry is already closed.
+ if (watchEndTime > 0) {
+ return;
+ }
+
+ // The current program has just ended. Create a (complete) log entry off the
+ // current entry.
+ ContentValues values = new ContentValues();
+ values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
+ watchStartTime);
+ values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, time);
+ values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
+ values.put(TvContract.WatchedPrograms.COLUMN_TITLE, title);
+ values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS, startTime);
+ values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
+ values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, description);
+ mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ // Re-open the current log entry with the next program information.
+ onOpenEntry(uri, channelId, time);
+ }
+
+ private void onCloseEntry(Uri uri, long watchEndTime) {
+ ContentValues values = new ContentValues();
+ values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, watchEndTime);
+ mContentResolver.update(uri, values, null, null);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
index 9601e9a..1a68fb3 100644
--- a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
@@ -21,7 +21,6 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
-import android.os.FileUtils;
import android.util.Base64;
import android.util.EventLog;
import android.util.Slog;
@@ -30,18 +29,15 @@ import com.android.server.EventLogTags;
import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
-import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
-import java.security.SignatureException;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java b/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java
index 83adbdb..2fe68f8 100644
--- a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java
@@ -17,7 +17,6 @@
package com.android.server.updates;
import android.util.Base64;
-import android.util.Slog;
import java.io.IOException;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 97ea52c..3eb2d5f 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -25,6 +25,7 @@ import android.app.IWallpaperManager;
import android.app.IWallpaperManagerCallback;
import android.app.PendingIntent;
import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
import android.app.backup.BackupManager;
import android.app.backup.WallpaperBackupHelper;
import android.content.BroadcastReceiver;
@@ -230,7 +231,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
if (mWallpaper.connection == this) {
- mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
mService = IWallpaperService.Stub.asInterface(service);
attachServiceLocked(this, mWallpaper);
// XXX should probably do saveSettingsLocked() later
@@ -250,11 +250,21 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
if (mWallpaper.connection == this) {
Slog.w(TAG, "Wallpaper service gone: " + mWallpaper.wallpaperComponent);
if (!mWallpaper.wallpaperUpdating
- && (mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME)
- > SystemClock.uptimeMillis()
&& mWallpaper.userId == mCurrentUserId) {
- Slog.w(TAG, "Reverting to built-in wallpaper!");
- clearWallpaperLocked(true, mWallpaper.userId, null);
+ // There is a race condition which causes
+ // {@link #mWallpaper.wallpaperUpdating} to be false even if it is
+ // currently updating since the broadcast notifying us is async.
+ // This race is overcome by the general rule that we only reset the
+ // wallpaper if its service was shut down twice
+ // during {@link #MIN_WALLPAPER_CRASH_TIME} millis.
+ if (mWallpaper.lastDiedTime != 0
+ && mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME
+ > SystemClock.uptimeMillis()) {
+ Slog.w(TAG, "Reverting to built-in wallpaper!");
+ clearWallpaperLocked(true, mWallpaper.userId, null);
+ } else {
+ mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
+ }
}
}
}
@@ -835,13 +845,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
try {
if (componentName == null) {
- String defaultComponent =
- mContext.getString(com.android.internal.R.string.default_wallpaper_component);
- if (defaultComponent != null) {
- // See if there is a default wallpaper component specified
- componentName = ComponentName.unflattenFromString(defaultComponent);
- if (DEBUG) Slog.v(TAG, "Use default component wallpaper:" + componentName);
- }
+ componentName = WallpaperManager.getDefaultWallpaperComponent(mContext);
if (componentName == null) {
// Fall back to static image wallpaper
componentName = IMAGE_WALLPAPER;
@@ -938,7 +942,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
}
wallpaper.wallpaperComponent = componentName;
wallpaper.connection = newConn;
- wallpaper.lastDiedTime = SystemClock.uptimeMillis();
newConn.mReply = reply;
try {
if (wallpaper.userId == mCurrentUserId) {
diff --git a/services/core/java/com/android/server/wifi/README.txt b/services/core/java/com/android/server/wifi/README.txt
deleted file mode 100644
index 39e1475..0000000
--- a/services/core/java/com/android/server/wifi/README.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-WifiService: Implements the IWifiManager 3rd party API. The API and the device state information (screen on/off, battery state, sleep policy) go as input into the WifiController which tracks high level states as to whether STA or AP mode is operational and controls the WifiStateMachine to handle bringup and shut down.
-
-WifiController: Acts as a controller to the WifiStateMachine based on various inputs (API and device state). Runs on the same thread created in WifiService.
-
-WifiSettingsStore: Tracks the various settings (wifi toggle, airplane toggle, tethering toggle, scan mode toggle) and provides API to figure if wifi should be turned on or off.
-
-WifiTrafficPoller: Polls traffic on wifi and notifies apps listening on it.
-
-WifiNotificationController: Controls whether the open network notification is displayed or not based on the scan results.
-
-WifiStateMachine: Tracks the various states on STA and AP connectivity and handles bring up and shut down.
-
-
-Feature description:
-
-Scan-only mode with Wi-Fi turned off:
- - Setup wizard opts user into allowing scanning for improved location. We show no further dialogs in setup wizard since the user has just opted into the feature. This is the reason WifiService listens to DEVICE_PROVISIONED setting.
- - Once the user has his device provisioned, turning off Wi-Fi from settings or from a third party app will show up a dialog reminding the user that scan mode will be on even though Wi-Fi is being turned off. The user has the choice to turn this notification off.
- - In the scan mode, the device continues to allow scanning from any app with Wi-Fi turned off. This is done by disabling all networks and allowing only scans to be passed.
diff --git a/services/core/java/com/android/server/wifi/WifiController.java b/services/core/java/com/android/server/wifi/WifiController.java
deleted file mode 100644
index bdb0a5e..0000000
--- a/services/core/java/com/android/server/wifi/WifiController.java
+++ /dev/null
@@ -1,761 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
-import static android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF;
-import static android.net.wifi.WifiManager.WIFI_MODE_SCAN_ONLY;
-import android.net.wifi.WifiStateMachine;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.os.WorkSource;
-import android.provider.Settings;
-import android.util.Slog;
-
-import com.android.internal.util.Protocol;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.server.wifi.WifiService.LockList;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-class WifiController extends StateMachine {
- private static final String TAG = "WifiController";
- private static final boolean DBG = false;
- private Context mContext;
- private boolean mScreenOff;
- private boolean mDeviceIdle;
- private int mPluggedType;
- private int mStayAwakeConditions;
- private long mIdleMillis;
- private int mSleepPolicy;
- private boolean mFirstUserSignOnSeen = false;
-
- private AlarmManager mAlarmManager;
- private PendingIntent mIdleIntent;
- private static final int IDLE_REQUEST = 0;
-
- /**
- * See {@link Settings.Global#WIFI_IDLE_MS}. This is the default value if a
- * Settings.Global value is not present. This timeout value is chosen as
- * the approximate point at which the battery drain caused by Wi-Fi
- * being enabled but not active exceeds the battery drain caused by
- * re-establishing a connection to the mobile data network.
- */
- private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */
-
- /**
- * See {@link Settings.Global#WIFI_REENABLE_DELAY_MS}. This is the default value if a
- * Settings.Global value is not present. This is the minimum time after wifi is disabled
- * we'll act on an enable. Enable requests received before this delay will be deferred.
- */
- private static final long DEFAULT_REENABLE_DELAY_MS = 500;
-
- // finding that delayed messages can sometimes be delivered earlier than expected
- // probably rounding errors.. add a margin to prevent problems
- private static final long DEFER_MARGIN_MS = 5;
-
- NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
-
- private static final String ACTION_DEVICE_IDLE =
- "com.android.server.WifiManager.action.DEVICE_IDLE";
-
- /* References to values tracked in WifiService */
- final WifiStateMachine mWifiStateMachine;
- final WifiSettingsStore mSettingsStore;
- final LockList mLocks;
-
- /**
- * Temporary for computing UIDS that are responsible for starting WIFI.
- * Protected by mWifiStateTracker lock.
- */
- private final WorkSource mTmpWorkSource = new WorkSource();
-
- private long mReEnableDelayMillis;
-
- private static final int BASE = Protocol.BASE_WIFI_CONTROLLER;
-
- static final int CMD_EMERGENCY_MODE_CHANGED = BASE + 1;
- static final int CMD_SCREEN_ON = BASE + 2;
- static final int CMD_SCREEN_OFF = BASE + 3;
- static final int CMD_BATTERY_CHANGED = BASE + 4;
- static final int CMD_DEVICE_IDLE = BASE + 5;
- static final int CMD_LOCKS_CHANGED = BASE + 6;
- static final int CMD_SCAN_ALWAYS_MODE_CHANGED = BASE + 7;
- static final int CMD_WIFI_TOGGLED = BASE + 8;
- static final int CMD_AIRPLANE_TOGGLED = BASE + 9;
- static final int CMD_SET_AP = BASE + 10;
- static final int CMD_DEFERRED_TOGGLE = BASE + 11;
- static final int CMD_USER_PRESENT = BASE + 12;
-
- private DefaultState mDefaultState = new DefaultState();
- private StaEnabledState mStaEnabledState = new StaEnabledState();
- private ApStaDisabledState mApStaDisabledState = new ApStaDisabledState();
- private StaDisabledWithScanState mStaDisabledWithScanState = new StaDisabledWithScanState();
- private ApEnabledState mApEnabledState = new ApEnabledState();
- private DeviceActiveState mDeviceActiveState = new DeviceActiveState();
- private DeviceInactiveState mDeviceInactiveState = new DeviceInactiveState();
- private ScanOnlyLockHeldState mScanOnlyLockHeldState = new ScanOnlyLockHeldState();
- private FullLockHeldState mFullLockHeldState = new FullLockHeldState();
- private FullHighPerfLockHeldState mFullHighPerfLockHeldState = new FullHighPerfLockHeldState();
- private NoLockHeldState mNoLockHeldState = new NoLockHeldState();
- private EcmState mEcmState = new EcmState();
-
- WifiController(Context context, WifiService service, Looper looper) {
- super(TAG, looper);
- mContext = context;
- mWifiStateMachine = service.mWifiStateMachine;
- mSettingsStore = service.mSettingsStore;
- mLocks = service.mLocks;
-
- mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
- Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
- mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0);
-
- addState(mDefaultState);
- addState(mApStaDisabledState, mDefaultState);
- addState(mStaEnabledState, mDefaultState);
- addState(mDeviceActiveState, mStaEnabledState);
- addState(mDeviceInactiveState, mStaEnabledState);
- addState(mScanOnlyLockHeldState, mDeviceInactiveState);
- addState(mFullLockHeldState, mDeviceInactiveState);
- addState(mFullHighPerfLockHeldState, mDeviceInactiveState);
- addState(mNoLockHeldState, mDeviceInactiveState);
- addState(mStaDisabledWithScanState, mDefaultState);
- addState(mApEnabledState, mDefaultState);
- addState(mEcmState, mDefaultState);
-
- boolean isAirplaneModeOn = mSettingsStore.isAirplaneModeOn();
- boolean isWifiEnabled = mSettingsStore.isWifiToggleEnabled();
- boolean isScanningAlwaysAvailable = mSettingsStore.isScanAlwaysAvailable();
-
- log("isAirplaneModeOn = " + isAirplaneModeOn +
- ", isWifiEnabled = " + isWifiEnabled +
- ", isScanningAvailable = " + isScanningAlwaysAvailable);
-
- if (isWifiEnabled && isScanningAlwaysAvailable) {
- setInitialState(mStaDisabledWithScanState);
- } else {
- setInitialState(mApStaDisabledState);
- }
-
- setLogRecSize(100);
- setLogOnlyTransitions(false);
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_DEVICE_IDLE);
- filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
- mContext.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(ACTION_DEVICE_IDLE)) {
- sendMessage(CMD_DEVICE_IDLE);
- } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
- mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
- WifiManager.EXTRA_NETWORK_INFO);
- }
- }
- },
- new IntentFilter(filter));
-
- initializeAndRegisterForSettingsChange(looper);
- }
-
- private void initializeAndRegisterForSettingsChange(Looper looper) {
- Handler handler = new Handler(looper);
- readStayAwakeConditions();
- registerForStayAwakeModeChange(handler);
- readWifiIdleTime();
- registerForWifiIdleTimeChange(handler);
- readWifiSleepPolicy();
- registerForWifiSleepPolicyChange(handler);
- readWifiReEnableDelay();
- }
-
- private void readStayAwakeConditions() {
- mStayAwakeConditions = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
- }
-
- private void readWifiIdleTime() {
- mIdleMillis = Settings.Global.getLong(mContext.getContentResolver(),
- Settings.Global.WIFI_IDLE_MS, DEFAULT_IDLE_MS);
- }
-
- private void readWifiSleepPolicy() {
- mSleepPolicy = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.WIFI_SLEEP_POLICY,
- Settings.Global.WIFI_SLEEP_POLICY_NEVER);
- }
-
- private void readWifiReEnableDelay() {
- mReEnableDelayMillis = Settings.Global.getLong(mContext.getContentResolver(),
- Settings.Global.WIFI_REENABLE_DELAY_MS, DEFAULT_REENABLE_DELAY_MS);
- }
-
- /**
- * Observes settings changes to scan always mode.
- */
- private void registerForStayAwakeModeChange(Handler handler) {
- ContentObserver contentObserver = new ContentObserver(handler) {
- @Override
- public void onChange(boolean selfChange) {
- readStayAwakeConditions();
- }
- };
-
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.STAY_ON_WHILE_PLUGGED_IN),
- false, contentObserver);
- }
-
- /**
- * Observes settings changes to scan always mode.
- */
- private void registerForWifiIdleTimeChange(Handler handler) {
- ContentObserver contentObserver = new ContentObserver(handler) {
- @Override
- public void onChange(boolean selfChange) {
- readWifiIdleTime();
- }
- };
-
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.WIFI_IDLE_MS),
- false, contentObserver);
- }
-
- /**
- * Observes changes to wifi sleep policy
- */
- private void registerForWifiSleepPolicyChange(Handler handler) {
- ContentObserver contentObserver = new ContentObserver(handler) {
- @Override
- public void onChange(boolean selfChange) {
- readWifiSleepPolicy();
- }
- };
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.WIFI_SLEEP_POLICY),
- false, contentObserver);
- }
-
- /**
- * Determines whether the Wi-Fi chipset should stay awake or be put to
- * sleep. Looks at the setting for the sleep policy and the current
- * conditions.
- *
- * @see #shouldDeviceStayAwake(int)
- */
- private boolean shouldWifiStayAwake(int pluggedType) {
- if (mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER) {
- // Never sleep
- return true;
- } else if ((mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED) &&
- (pluggedType != 0)) {
- // Never sleep while plugged, and we're plugged
- return true;
- } else {
- // Default
- return shouldDeviceStayAwake(pluggedType);
- }
- }
-
- /**
- * Determine whether the bit value corresponding to {@code pluggedType} is set in
- * the bit string mStayAwakeConditions. This determines whether the device should
- * stay awake based on the current plugged type.
- *
- * @param pluggedType the type of plug (USB, AC, or none) for which the check is
- * being made
- * @return {@code true} if {@code pluggedType} indicates that the device is
- * supposed to stay awake, {@code false} otherwise.
- */
- private boolean shouldDeviceStayAwake(int pluggedType) {
- return (mStayAwakeConditions & pluggedType) != 0;
- }
-
- private void updateBatteryWorkSource() {
- mTmpWorkSource.clear();
- if (mDeviceIdle) {
- mLocks.updateWorkSource(mTmpWorkSource);
- }
- mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
- }
-
- class DefaultState extends State {
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_SCREEN_ON:
- mAlarmManager.cancel(mIdleIntent);
- mScreenOff = false;
- mDeviceIdle = false;
- updateBatteryWorkSource();
- break;
- case CMD_SCREEN_OFF:
- mScreenOff = true;
- /*
- * Set a timer to put Wi-Fi to sleep, but only if the screen is off
- * AND the "stay on while plugged in" setting doesn't match the
- * current power conditions (i.e, not plugged in, plugged in to USB,
- * or plugged in to AC).
- */
- if (!shouldWifiStayAwake(mPluggedType)) {
- //Delayed shutdown if wifi is connected
- if (mNetworkInfo.getDetailedState() ==
- NetworkInfo.DetailedState.CONNECTED) {
- if (DBG) Slog.d(TAG, "set idle timer: " + mIdleMillis + " ms");
- mAlarmManager.set(AlarmManager.RTC_WAKEUP,
- System.currentTimeMillis() + mIdleMillis, mIdleIntent);
- } else {
- sendMessage(CMD_DEVICE_IDLE);
- }
- }
- break;
- case CMD_DEVICE_IDLE:
- mDeviceIdle = true;
- updateBatteryWorkSource();
- break;
- case CMD_BATTERY_CHANGED:
- /*
- * Set a timer to put Wi-Fi to sleep, but only if the screen is off
- * AND we are transitioning from a state in which the device was supposed
- * to stay awake to a state in which it is not supposed to stay awake.
- * If "stay awake" state is not changing, we do nothing, to avoid resetting
- * the already-set timer.
- */
- int pluggedType = msg.arg1;
- if (DBG) Slog.d(TAG, "battery changed pluggedType: " + pluggedType);
- if (mScreenOff && shouldWifiStayAwake(mPluggedType) &&
- !shouldWifiStayAwake(pluggedType)) {
- long triggerTime = System.currentTimeMillis() + mIdleMillis;
- if (DBG) Slog.d(TAG, "set idle timer for " + mIdleMillis + "ms");
- mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
- }
-
- mPluggedType = pluggedType;
- break;
- case CMD_SET_AP:
- case CMD_SCAN_ALWAYS_MODE_CHANGED:
- case CMD_LOCKS_CHANGED:
- case CMD_WIFI_TOGGLED:
- case CMD_AIRPLANE_TOGGLED:
- case CMD_EMERGENCY_MODE_CHANGED:
- break;
- case CMD_USER_PRESENT:
- mFirstUserSignOnSeen = true;
- break;
- case CMD_DEFERRED_TOGGLE:
- log("DEFERRED_TOGGLE ignored due to state change");
- break;
- default:
- throw new RuntimeException("WifiController.handleMessage " + msg.what);
- }
- return HANDLED;
- }
-
- }
-
- class ApStaDisabledState extends State {
- private int mDeferredEnableSerialNumber = 0;
- private boolean mHaveDeferredEnable = false;
- private long mDisabledTimestamp;
-
- @Override
- public void enter() {
- mWifiStateMachine.setSupplicantRunning(false);
- // Supplicant can't restart right away, so not the time we switched off
- mDisabledTimestamp = SystemClock.elapsedRealtime();
- mDeferredEnableSerialNumber++;
- mHaveDeferredEnable = false;
- }
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_WIFI_TOGGLED:
- case CMD_AIRPLANE_TOGGLED:
- if (mSettingsStore.isWifiToggleEnabled()) {
- if (doDeferEnable(msg)) {
- if (mHaveDeferredEnable) {
- // have 2 toggles now, inc serial number an ignore both
- mDeferredEnableSerialNumber++;
- }
- mHaveDeferredEnable = !mHaveDeferredEnable;
- break;
- }
- if (mDeviceIdle == false) {
- transitionTo(mDeviceActiveState);
- } else {
- checkLocksAndTransitionWhenDeviceIdle();
- }
- }
- break;
- case CMD_SCAN_ALWAYS_MODE_CHANGED:
- if (mSettingsStore.isScanAlwaysAvailable()) {
- transitionTo(mStaDisabledWithScanState);
- }
- break;
- case CMD_SET_AP:
- if (msg.arg1 == 1) {
- mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
- true);
- transitionTo(mApEnabledState);
- }
- break;
- case CMD_DEFERRED_TOGGLE:
- if (msg.arg1 != mDeferredEnableSerialNumber) {
- log("DEFERRED_TOGGLE ignored due to serial mismatch");
- break;
- }
- log("DEFERRED_TOGGLE handled");
- sendMessage((Message)(msg.obj));
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
-
- private boolean doDeferEnable(Message msg) {
- long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
- if (delaySoFar >= mReEnableDelayMillis) {
- return false;
- }
-
- log("WifiController msg " + msg + " deferred for " +
- (mReEnableDelayMillis - delaySoFar) + "ms");
-
- // need to defer this action.
- Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
- deferredMsg.obj = Message.obtain(msg);
- deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
- sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
- return true;
- }
-
- }
-
- class StaEnabledState extends State {
- @Override
- public void enter() {
- mWifiStateMachine.setSupplicantRunning(true);
- }
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_WIFI_TOGGLED:
- if (! mSettingsStore.isWifiToggleEnabled()) {
- if (mSettingsStore.isScanAlwaysAvailable()) {
- transitionTo(mStaDisabledWithScanState);
- } else {
- transitionTo(mApStaDisabledState);
- }
- }
- break;
- case CMD_AIRPLANE_TOGGLED:
- /* When wi-fi is turned off due to airplane,
- * disable entirely (including scan)
- */
- if (! mSettingsStore.isWifiToggleEnabled()) {
- transitionTo(mApStaDisabledState);
- }
- break;
- case CMD_EMERGENCY_MODE_CHANGED:
- if (msg.arg1 == 1) {
- transitionTo(mEcmState);
- break;
- }
- default:
- return NOT_HANDLED;
-
- }
- return HANDLED;
- }
- }
-
- class StaDisabledWithScanState extends State {
- private int mDeferredEnableSerialNumber = 0;
- private boolean mHaveDeferredEnable = false;
- private long mDisabledTimestamp;
-
- @Override
- public void enter() {
- mWifiStateMachine.setSupplicantRunning(true);
- mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE);
- mWifiStateMachine.setDriverStart(true);
- // Supplicant can't restart right away, so not the time we switched off
- mDisabledTimestamp = SystemClock.elapsedRealtime();
- mDeferredEnableSerialNumber++;
- mHaveDeferredEnable = false;
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_WIFI_TOGGLED:
- if (mSettingsStore.isWifiToggleEnabled()) {
- if (doDeferEnable(msg)) {
- if (mHaveDeferredEnable) {
- // have 2 toggles now, inc serial number and ignore both
- mDeferredEnableSerialNumber++;
- }
- mHaveDeferredEnable = !mHaveDeferredEnable;
- break;
- }
- if (mDeviceIdle == false) {
- transitionTo(mDeviceActiveState);
- } else {
- checkLocksAndTransitionWhenDeviceIdle();
- }
- }
- break;
- case CMD_AIRPLANE_TOGGLED:
- if (mSettingsStore.isAirplaneModeOn() &&
- ! mSettingsStore.isWifiToggleEnabled()) {
- transitionTo(mApStaDisabledState);
- }
- case CMD_SCAN_ALWAYS_MODE_CHANGED:
- if (! mSettingsStore.isScanAlwaysAvailable()) {
- transitionTo(mApStaDisabledState);
- }
- break;
- case CMD_SET_AP:
- // Before starting tethering, turn off supplicant for scan mode
- if (msg.arg1 == 1) {
- deferMessage(msg);
- transitionTo(mApStaDisabledState);
- }
- break;
- case CMD_DEFERRED_TOGGLE:
- if (msg.arg1 != mDeferredEnableSerialNumber) {
- log("DEFERRED_TOGGLE ignored due to serial mismatch");
- break;
- }
- logd("DEFERRED_TOGGLE handled");
- sendMessage((Message)(msg.obj));
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
-
- private boolean doDeferEnable(Message msg) {
- long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
- if (delaySoFar >= mReEnableDelayMillis) {
- return false;
- }
-
- log("WifiController msg " + msg + " deferred for " +
- (mReEnableDelayMillis - delaySoFar) + "ms");
-
- // need to defer this action.
- Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
- deferredMsg.obj = Message.obtain(msg);
- deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
- sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
- return true;
- }
-
- }
-
- class ApEnabledState extends State {
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_AIRPLANE_TOGGLED:
- if (mSettingsStore.isAirplaneModeOn()) {
- mWifiStateMachine.setHostApRunning(null, false);
- transitionTo(mApStaDisabledState);
- }
- break;
- case CMD_SET_AP:
- if (msg.arg1 == 0) {
- mWifiStateMachine.setHostApRunning(null, false);
- transitionTo(mApStaDisabledState);
- }
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
- class EcmState extends State {
- @Override
- public void enter() {
- mWifiStateMachine.setSupplicantRunning(false);
- }
-
- @Override
- public boolean processMessage(Message msg) {
- if (msg.what == CMD_EMERGENCY_MODE_CHANGED && msg.arg1 == 0) {
- if (mSettingsStore.isWifiToggleEnabled()) {
- if (mDeviceIdle == false) {
- transitionTo(mDeviceActiveState);
- } else {
- checkLocksAndTransitionWhenDeviceIdle();
- }
- } else if (mSettingsStore.isScanAlwaysAvailable()) {
- transitionTo(mStaDisabledWithScanState);
- } else {
- transitionTo(mApStaDisabledState);
- }
- return HANDLED;
- } else {
- return NOT_HANDLED;
- }
- }
- }
-
- /* Parent: StaEnabledState */
- class DeviceActiveState extends State {
- @Override
- public void enter() {
- mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
- mWifiStateMachine.setDriverStart(true);
- mWifiStateMachine.setHighPerfModeEnabled(false);
- }
-
- @Override
- public boolean processMessage(Message msg) {
- if (msg.what == CMD_DEVICE_IDLE) {
- checkLocksAndTransitionWhenDeviceIdle();
- // We let default state handle the rest of work
- } else if (msg.what == CMD_USER_PRESENT) {
- // TLS networks can't connect until user unlocks keystore. KeyStore
- // unlocks when the user punches PIN after the reboot. So use this
- // trigger to get those networks connected.
- if (mFirstUserSignOnSeen == false) {
- mWifiStateMachine.reloadTlsNetworksAndReconnect();
- }
- mFirstUserSignOnSeen = true;
- return HANDLED;
- }
- return NOT_HANDLED;
- }
- }
-
- /* Parent: StaEnabledState */
- class DeviceInactiveState extends State {
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_LOCKS_CHANGED:
- checkLocksAndTransitionWhenDeviceIdle();
- updateBatteryWorkSource();
- return HANDLED;
- case CMD_SCREEN_ON:
- transitionTo(mDeviceActiveState);
- // More work in default state
- return NOT_HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
- }
-
- /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a scan only lock. */
- class ScanOnlyLockHeldState extends State {
- @Override
- public void enter() {
- mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
- mWifiStateMachine.setDriverStart(true);
- }
- }
-
- /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a full lock. */
- class FullLockHeldState extends State {
- @Override
- public void enter() {
- mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
- mWifiStateMachine.setDriverStart(true);
- mWifiStateMachine.setHighPerfModeEnabled(false);
- }
- }
-
- /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a high perf lock. */
- class FullHighPerfLockHeldState extends State {
- @Override
- public void enter() {
- mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
- mWifiStateMachine.setDriverStart(true);
- mWifiStateMachine.setHighPerfModeEnabled(true);
- }
- }
-
- /* Parent: DeviceInactiveState. Device is inactive and no app is holding a wifi lock. */
- class NoLockHeldState extends State {
- @Override
- public void enter() {
- mWifiStateMachine.setDriverStart(false);
- }
- }
-
- private void checkLocksAndTransitionWhenDeviceIdle() {
- if (mLocks.hasLocks()) {
- switch (mLocks.getStrongestLockMode()) {
- case WIFI_MODE_FULL:
- transitionTo(mFullLockHeldState);
- break;
- case WIFI_MODE_FULL_HIGH_PERF:
- transitionTo(mFullHighPerfLockHeldState);
- break;
- case WIFI_MODE_SCAN_ONLY:
- transitionTo(mScanOnlyLockHeldState);
- break;
- default:
- loge("Illegal lock " + mLocks.getStrongestLockMode());
- }
- } else {
- if (mSettingsStore.isScanAlwaysAvailable()) {
- transitionTo(mScanOnlyLockHeldState);
- } else {
- transitionTo(mNoLockHeldState);
- }
- }
- }
-
- @Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- super.dump(fd, pw, args);
-
- pw.println("mScreenOff " + mScreenOff);
- pw.println("mDeviceIdle " + mDeviceIdle);
- pw.println("mPluggedType " + mPluggedType);
- pw.println("mIdleMillis " + mIdleMillis);
- pw.println("mSleepPolicy " + mSleepPolicy);
- }
-}
diff --git a/services/core/java/com/android/server/wifi/WifiNotificationController.java b/services/core/java/com/android/server/wifi/WifiNotificationController.java
deleted file mode 100644
index a9206e0..0000000
--- a/services/core/java/com/android/server/wifi/WifiNotificationController.java
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.TaskStackBuilder;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.NetworkInfo;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiStateMachine;
-import android.os.Handler;
-import android.os.Message;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.List;
-
-/* Takes care of handling the "open wi-fi network available" notification @hide */
-final class WifiNotificationController {
- /**
- * The icon to show in the 'available networks' notification. This will also
- * be the ID of the Notification given to the NotificationManager.
- */
- private static final int ICON_NETWORKS_AVAILABLE =
- com.android.internal.R.drawable.stat_notify_wifi_in_range;
- /**
- * When a notification is shown, we wait this amount before possibly showing it again.
- */
- private final long NOTIFICATION_REPEAT_DELAY_MS;
- /**
- * Whether the user has set the setting to show the 'available networks' notification.
- */
- private boolean mNotificationEnabled;
- /**
- * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
- */
- private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
- /**
- * The {@link System#currentTimeMillis()} must be at least this value for us
- * to show the notification again.
- */
- private long mNotificationRepeatTime;
- /**
- * The Notification object given to the NotificationManager.
- */
- private Notification mNotification;
- /**
- * Whether the notification is being shown, as set by us. That is, if the
- * user cancels the notification, we will not receive the callback so this
- * will still be true. We only guarantee if this is false, then the
- * notification is not showing.
- */
- private boolean mNotificationShown;
- /**
- * The number of continuous scans that must occur before consider the
- * supplicant in a scanning state. This allows supplicant to associate with
- * remembered networks that are in the scan results.
- */
- private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
- /**
- * The number of scans since the last network state change. When this
- * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the
- * supplicant to actually be scanning. When the network state changes to
- * something other than scanning, we reset this to 0.
- */
- private int mNumScansSinceNetworkStateChange;
-
- private final Context mContext;
- private final WifiStateMachine mWifiStateMachine;
- private NetworkInfo mNetworkInfo;
- private volatile int mWifiState;
-
- WifiNotificationController(Context context, WifiStateMachine wsm) {
- mContext = context;
- mWifiStateMachine = wsm;
- mWifiState = WifiManager.WIFI_STATE_UNKNOWN;
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
- filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
- filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
-
- mContext.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
- mWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
- WifiManager.WIFI_STATE_UNKNOWN);
- resetNotification();
- } else if (intent.getAction().equals(
- WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
- mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
- WifiManager.EXTRA_NETWORK_INFO);
- // reset & clear notification on a network connect & disconnect
- switch(mNetworkInfo.getDetailedState()) {
- case CONNECTED:
- case DISCONNECTED:
- case CAPTIVE_PORTAL_CHECK:
- resetNotification();
- break;
- }
- } else if (intent.getAction().equals(
- WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
- checkAndSetNotification(mNetworkInfo,
- mWifiStateMachine.syncGetScanResultsList());
- }
- }
- }, filter);
-
- // Setting is in seconds
- NOTIFICATION_REPEAT_DELAY_MS = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l;
- mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler());
- mNotificationEnabledSettingObserver.register();
- }
-
- private synchronized void checkAndSetNotification(NetworkInfo networkInfo,
- List<ScanResult> scanResults) {
- // TODO: unregister broadcast so we do not have to check here
- // If we shouldn't place a notification on available networks, then
- // don't bother doing any of the following
- if (!mNotificationEnabled) return;
- if (networkInfo == null) return;
- if (mWifiState != WifiManager.WIFI_STATE_ENABLED) return;
-
- NetworkInfo.State state = networkInfo.getState();
- if ((state == NetworkInfo.State.DISCONNECTED)
- || (state == NetworkInfo.State.UNKNOWN)) {
- if (scanResults != null) {
- int numOpenNetworks = 0;
- for (int i = scanResults.size() - 1; i >= 0; i--) {
- ScanResult scanResult = scanResults.get(i);
-
- //A capability of [ESS] represents an open access point
- //that is available for an STA to connect
- if (scanResult.capabilities != null &&
- scanResult.capabilities.equals("[ESS]")) {
- numOpenNetworks++;
- }
- }
-
- if (numOpenNetworks > 0) {
- if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) {
- /*
- * We've scanned continuously at least
- * NUM_SCANS_BEFORE_NOTIFICATION times. The user
- * probably does not have a remembered network in range,
- * since otherwise supplicant would have tried to
- * associate and thus resetting this counter.
- */
- setNotificationVisible(true, numOpenNetworks, false, 0);
- }
- return;
- }
- }
- }
-
- // No open networks in range, remove the notification
- setNotificationVisible(false, 0, false, 0);
- }
-
- /**
- * Clears variables related to tracking whether a notification has been
- * shown recently and clears the current notification.
- */
- private synchronized void resetNotification() {
- mNotificationRepeatTime = 0;
- mNumScansSinceNetworkStateChange = 0;
- setNotificationVisible(false, 0, false, 0);
- }
-
- /**
- * Display or don't display a notification that there are open Wi-Fi networks.
- * @param visible {@code true} if notification should be visible, {@code false} otherwise
- * @param numNetworks the number networks seen
- * @param force {@code true} to force notification to be shown/not-shown,
- * even if it is already shown/not-shown.
- * @param delay time in milliseconds after which the notification should be made
- * visible or invisible.
- */
- private void setNotificationVisible(boolean visible, int numNetworks, boolean force,
- int delay) {
-
- // Since we use auto cancel on the notification, when the
- // mNetworksAvailableNotificationShown is true, the notification may
- // have actually been canceled. However, when it is false we know
- // for sure that it is not being shown (it will not be shown any other
- // place than here)
-
- // If it should be hidden and it is already hidden, then noop
- if (!visible && !mNotificationShown && !force) {
- return;
- }
-
- NotificationManager notificationManager = (NotificationManager) mContext
- .getSystemService(Context.NOTIFICATION_SERVICE);
-
- Message message;
- if (visible) {
-
- // Not enough time has passed to show the notification again
- if (System.currentTimeMillis() < mNotificationRepeatTime) {
- return;
- }
-
- if (mNotification == null) {
- // Cache the Notification object.
- mNotification = new Notification();
- mNotification.when = 0;
- mNotification.icon = ICON_NETWORKS_AVAILABLE;
- mNotification.flags = Notification.FLAG_AUTO_CANCEL;
- mNotification.contentIntent = TaskStackBuilder.create(mContext)
- .addNextIntentWithParentStack(
- new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK))
- .getPendingIntent(0, 0, null, UserHandle.CURRENT);
- }
-
- CharSequence title = mContext.getResources().getQuantityText(
- com.android.internal.R.plurals.wifi_available, numNetworks);
- CharSequence details = mContext.getResources().getQuantityText(
- com.android.internal.R.plurals.wifi_available_detailed, numNetworks);
- mNotification.tickerText = title;
- mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent);
-
- mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS;
-
- notificationManager.notifyAsUser(null, ICON_NETWORKS_AVAILABLE, mNotification,
- UserHandle.ALL);
- } else {
- notificationManager.cancelAsUser(null, ICON_NETWORKS_AVAILABLE, UserHandle.ALL);
- }
-
- mNotificationShown = visible;
- }
-
- void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("mNotificationEnabled " + mNotificationEnabled);
- pw.println("mNotificationRepeatTime " + mNotificationRepeatTime);
- pw.println("mNotificationShown " + mNotificationShown);
- pw.println("mNumScansSinceNetworkStateChange " + mNumScansSinceNetworkStateChange);
- }
-
- private class NotificationEnabledSettingObserver extends ContentObserver {
- public NotificationEnabledSettingObserver(Handler handler) {
- super(handler);
- }
-
- public void register() {
- ContentResolver cr = mContext.getContentResolver();
- cr.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
- synchronized (WifiNotificationController.this) {
- mNotificationEnabled = getValue();
- }
- }
-
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
-
- synchronized (WifiNotificationController.this) {
- mNotificationEnabled = getValue();
- resetNotification();
- }
- }
-
- private boolean getValue() {
- return Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
- }
- }
-
-}
diff --git a/services/core/java/com/android/server/wifi/WifiService.java b/services/core/java/com/android/server/wifi/WifiService.java
deleted file mode 100644
index aecf9ae..0000000
--- a/services/core/java/com/android/server/wifi/WifiService.java
+++ /dev/null
@@ -1,1599 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.bluetooth.BluetoothAdapter;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.database.ContentObserver;
-import android.net.DhcpInfo;
-import android.net.DhcpResults;
-import android.net.LinkAddress;
-import android.net.NetworkUtils;
-import android.net.RouteInfo;
-import android.net.wifi.IWifiManager;
-import android.net.wifi.ScanResult;
-import android.net.wifi.BatchedScanResult;
-import android.net.wifi.BatchedScanSettings;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.ProxySettings;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiStateMachine;
-import android.net.wifi.WifiWatchdogStateMachine;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.Messenger;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.INetworkManagementService;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.WorkSource;
-import android.os.AsyncTask;
-import android.provider.Settings;
-import android.util.Log;
-import android.util.Slog;
-
-import java.io.FileNotFoundException;
-import java.io.BufferedReader;
-import java.io.FileDescriptor;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-
-import java.net.InetAddress;
-import java.net.Inet4Address;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import com.android.internal.R;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.util.AsyncChannel;
-import com.android.server.am.BatteryStatsService;
-import static com.android.server.wifi.WifiController.CMD_AIRPLANE_TOGGLED;
-import static com.android.server.wifi.WifiController.CMD_BATTERY_CHANGED;
-import static com.android.server.wifi.WifiController.CMD_EMERGENCY_MODE_CHANGED;
-import static com.android.server.wifi.WifiController.CMD_LOCKS_CHANGED;
-import static com.android.server.wifi.WifiController.CMD_SCAN_ALWAYS_MODE_CHANGED;
-import static com.android.server.wifi.WifiController.CMD_SCREEN_OFF;
-import static com.android.server.wifi.WifiController.CMD_SCREEN_ON;
-import static com.android.server.wifi.WifiController.CMD_SET_AP;
-import static com.android.server.wifi.WifiController.CMD_USER_PRESENT;
-import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
-/**
- * WifiService handles remote WiFi operation requests by implementing
- * the IWifiManager interface.
- *
- * @hide
- */
-public final class WifiService extends IWifiManager.Stub {
- private static final String TAG = "WifiService";
- private static final boolean DBG = false;
-
- final WifiStateMachine mWifiStateMachine;
-
- private final Context mContext;
-
- final LockList mLocks = new LockList();
- // some wifi lock statistics
- private int mFullHighPerfLocksAcquired;
- private int mFullHighPerfLocksReleased;
- private int mFullLocksAcquired;
- private int mFullLocksReleased;
- private int mScanLocksAcquired;
- private int mScanLocksReleased;
-
- private final List<Multicaster> mMulticasters =
- new ArrayList<Multicaster>();
- private int mMulticastEnabled;
- private int mMulticastDisabled;
-
- private final IBatteryStats mBatteryStats;
- private final AppOpsManager mAppOps;
-
- private String mInterfaceName;
-
- /* Tracks the open wi-fi network notification */
- private WifiNotificationController mNotificationController;
- /* Polls traffic stats and notifies clients */
- private WifiTrafficPoller mTrafficPoller;
- /* Tracks the persisted states for wi-fi & airplane mode */
- final WifiSettingsStore mSettingsStore;
-
- final boolean mBatchedScanSupported;
-
- /**
- * Asynchronous channel to WifiStateMachine
- */
- private AsyncChannel mWifiStateMachineChannel;
-
- /**
- * Handles client connections
- */
- private class ClientHandler extends Handler {
-
- ClientHandler(android.os.Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
- if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
- if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
- // We track the clients by the Messenger
- // since it is expected to be always available
- mTrafficPoller.addClient(msg.replyTo);
- } else {
- Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
- }
- break;
- }
- case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
- if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
- if (DBG) Slog.d(TAG, "Send failed, client connection lost");
- } else {
- if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
- }
- mTrafficPoller.removeClient(msg.replyTo);
- break;
- }
- case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
- AsyncChannel ac = new AsyncChannel();
- ac.connect(mContext, this, msg.replyTo);
- break;
- }
- /* Client commands are forwarded to state machine */
- case WifiManager.CONNECT_NETWORK:
- case WifiManager.SAVE_NETWORK: {
- WifiConfiguration config = (WifiConfiguration) msg.obj;
- int networkId = msg.arg1;
- if (config != null && config.isValid()) {
- // This is restricted because there is no UI for the user to
- // monitor/control PAC.
- if (config.proxySettings != ProxySettings.PAC) {
- if (DBG) Slog.d(TAG, "Connect with config" + config);
- mWifiStateMachine.sendMessage(Message.obtain(msg));
- } else {
- Slog.e(TAG, "ClientHandler.handleMessage cannot process msg with PAC");
- if (msg.what == WifiManager.CONNECT_NETWORK) {
- replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED);
- } else {
- replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED);
- }
- }
- } else if (config == null
- && networkId != WifiConfiguration.INVALID_NETWORK_ID) {
- if (DBG) Slog.d(TAG, "Connect with networkId" + networkId);
- mWifiStateMachine.sendMessage(Message.obtain(msg));
- } else {
- Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);
- if (msg.what == WifiManager.CONNECT_NETWORK) {
- replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED);
- } else {
- replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED);
- }
- }
- break;
- }
- case WifiManager.FORGET_NETWORK:
- case WifiManager.START_WPS:
- case WifiManager.CANCEL_WPS:
- case WifiManager.DISABLE_NETWORK:
- case WifiManager.RSSI_PKTCNT_FETCH: {
- mWifiStateMachine.sendMessage(Message.obtain(msg));
- break;
- }
- default: {
- Slog.d(TAG, "ClientHandler.handleMessage ignoring msg=" + msg);
- break;
- }
- }
- }
-
- private void replyFailed(Message msg, int what) {
- Message reply = msg.obtain();
- reply.what = what;
- reply.arg1 = WifiManager.INVALID_ARGS;
- try {
- msg.replyTo.send(reply);
- } catch (RemoteException e) {
- // There's not much we can do if reply can't be sent!
- }
- }
- }
- private ClientHandler mClientHandler;
-
- /**
- * Handles interaction with WifiStateMachine
- */
- private class WifiStateMachineHandler extends Handler {
- private AsyncChannel mWsmChannel;
-
- WifiStateMachineHandler(android.os.Looper looper) {
- super(looper);
- mWsmChannel = new AsyncChannel();
- mWsmChannel.connect(mContext, this, mWifiStateMachine.getHandler());
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
- if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
- mWifiStateMachineChannel = mWsmChannel;
- } else {
- Slog.e(TAG, "WifiStateMachine connection failure, error=" + msg.arg1);
- mWifiStateMachineChannel = null;
- }
- break;
- }
- case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
- Slog.e(TAG, "WifiStateMachine channel lost, msg.arg1 =" + msg.arg1);
- mWifiStateMachineChannel = null;
- //Re-establish connection to state machine
- mWsmChannel.connect(mContext, this, mWifiStateMachine.getHandler());
- break;
- }
- default: {
- Slog.d(TAG, "WifiStateMachineHandler.handleMessage ignoring msg=" + msg);
- break;
- }
- }
- }
- }
- WifiStateMachineHandler mWifiStateMachineHandler;
-
- private WifiWatchdogStateMachine mWifiWatchdogStateMachine;
-
- public WifiService(Context context) {
- mContext = context;
-
- mInterfaceName = SystemProperties.get("wifi.interface", "wlan0");
-
- mWifiStateMachine = new WifiStateMachine(mContext, mInterfaceName);
- mWifiStateMachine.enableRssiPolling(true);
- mBatteryStats = BatteryStatsService.getService();
- mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
-
- mNotificationController = new WifiNotificationController(mContext, mWifiStateMachine);
- mTrafficPoller = new WifiTrafficPoller(mContext, mInterfaceName);
- mSettingsStore = new WifiSettingsStore(mContext);
-
- HandlerThread wifiThread = new HandlerThread("WifiService");
- wifiThread.start();
- mClientHandler = new ClientHandler(wifiThread.getLooper());
- mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
- mWifiController = new WifiController(mContext, this, wifiThread.getLooper());
- mWifiController.start();
-
- mBatchedScanSupported = mContext.getResources().getBoolean(
- R.bool.config_wifi_batched_scan_supported);
-
- registerForScanModeChange();
- mContext.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (mSettingsStore.handleAirplaneModeToggled()) {
- mWifiController.sendMessage(CMD_AIRPLANE_TOGGLED);
- }
- }
- },
- new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
-
- // Adding optimizations of only receiving broadcasts when wifi is enabled
- // can result in race conditions when apps toggle wifi in the background
- // without active user involvement. Always receive broadcasts.
- registerForBroadcasts();
- }
-
- private WifiController mWifiController;
-
- /**
- * Check if Wi-Fi needs to be enabled and start
- * if needed
- *
- * This function is used only at boot time
- */
- public void checkAndStartWifi() {
- /* Check if wi-fi needs to be enabled */
- boolean wifiEnabled = mSettingsStore.isWifiToggleEnabled();
- Slog.i(TAG, "WifiService starting up with Wi-Fi " +
- (wifiEnabled ? "enabled" : "disabled"));
-
- // If we are already disabled (could be due to airplane mode), avoid changing persist
- // state here
- if (wifiEnabled) setWifiEnabled(wifiEnabled);
-
- mWifiWatchdogStateMachine = WifiWatchdogStateMachine.
- makeWifiWatchdogStateMachine(mContext);
-
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#pingSupplicant()}
- * @return {@code true} if the operation succeeds, {@code false} otherwise
- */
- public boolean pingSupplicant() {
- enforceAccessPermission();
- if (mWifiStateMachineChannel != null) {
- return mWifiStateMachine.syncPingSupplicant(mWifiStateMachineChannel);
- } else {
- Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
- return false;
- }
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#startScan()}
- *
- * <p>If workSource is null, all blame is given to the calling uid.
- */
- public void startScan(WorkSource workSource) {
- enforceChangePermission();
- if (workSource != null) {
- enforceWorkSourcePermission();
- // WifiManager currently doesn't use names, so need to clear names out of the
- // supplied WorkSource to allow future WorkSource combining.
- workSource.clearNames();
- }
- mWifiStateMachine.startScan(Binder.getCallingUid(), workSource);
- }
-
- private class BatchedScanRequest extends DeathRecipient {
- final BatchedScanSettings settings;
- final int uid;
- final int pid;
- final WorkSource workSource;
-
- BatchedScanRequest(BatchedScanSettings settings, IBinder binder, WorkSource ws) {
- super(0, null, binder, null);
- this.settings = settings;
- this.uid = getCallingUid();
- this.pid = getCallingPid();
- workSource = ws;
- }
- public void binderDied() {
- stopBatchedScan(settings, uid, pid);
- }
- public String toString() {
- return "BatchedScanRequest{settings=" + settings + ", binder=" + mBinder + "}";
- }
-
- public boolean isSameApp(int uid, int pid) {
- return (this.uid == uid && this.pid == pid);
- }
- }
-
- private final List<BatchedScanRequest> mBatchedScanners = new ArrayList<BatchedScanRequest>();
-
- public boolean isBatchedScanSupported() {
- return mBatchedScanSupported;
- }
-
- public void pollBatchedScan() {
- enforceChangePermission();
- if (mBatchedScanSupported == false) return;
- mWifiStateMachine.requestBatchedScanPoll();
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#requestBatchedScan()}
- */
- public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder,
- WorkSource workSource) {
- enforceChangePermission();
- if (workSource != null) {
- enforceWorkSourcePermission();
- // WifiManager currently doesn't use names, so need to clear names out of the
- // supplied WorkSource to allow future WorkSource combining.
- workSource.clearNames();
- }
- if (mBatchedScanSupported == false) return false;
- requested = new BatchedScanSettings(requested);
- if (requested.isInvalid()) return false;
- BatchedScanRequest r = new BatchedScanRequest(requested, binder, workSource);
- synchronized(mBatchedScanners) {
- mBatchedScanners.add(r);
- resolveBatchedScannersLocked();
- }
- return true;
- }
-
- public List<BatchedScanResult> getBatchedScanResults(String callingPackage) {
- enforceAccessPermission();
- if (mBatchedScanSupported == false) return new ArrayList<BatchedScanResult>();
- int userId = UserHandle.getCallingUserId();
- int uid = Binder.getCallingUid();
- long ident = Binder.clearCallingIdentity();
- try {
- if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
- return new ArrayList<BatchedScanResult>();
- }
- int currentUser = ActivityManager.getCurrentUser();
- if (userId != currentUser) {
- return new ArrayList<BatchedScanResult>();
- } else {
- return mWifiStateMachine.syncGetBatchedScanResultsList();
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
-
- public void stopBatchedScan(BatchedScanSettings settings) {
- enforceChangePermission();
- if (mBatchedScanSupported == false) return;
- stopBatchedScan(settings, getCallingUid(), getCallingPid());
- }
-
- private void stopBatchedScan(BatchedScanSettings settings, int uid, int pid) {
- ArrayList<BatchedScanRequest> found = new ArrayList<BatchedScanRequest>();
- synchronized(mBatchedScanners) {
- for (BatchedScanRequest r : mBatchedScanners) {
- if (r.isSameApp(uid, pid) && (settings == null || settings.equals(r.settings))) {
- found.add(r);
- if (settings != null) break;
- }
- }
- for (BatchedScanRequest r : found) {
- mBatchedScanners.remove(r);
- }
- if (found.size() != 0) {
- resolveBatchedScannersLocked();
- }
- }
- }
-
- private void resolveBatchedScannersLocked() {
- BatchedScanSettings setting = new BatchedScanSettings();
- WorkSource responsibleWorkSource = null;
- int responsibleUid = 0;
- double responsibleCsph = 0; // Channel Scans Per Hour
-
- if (mBatchedScanners.size() == 0) {
- mWifiStateMachine.setBatchedScanSettings(null, 0, 0, null);
- return;
- }
- for (BatchedScanRequest r : mBatchedScanners) {
- BatchedScanSettings s = r.settings;
-
- // evaluate responsibility
- int currentChannelCount;
- int currentScanInterval;
- double currentCsph;
-
- if (s.channelSet == null || s.channelSet.isEmpty()) {
- // all channels - 11 B and 9 A channels roughly.
- currentChannelCount = 9 + 11;
- } else {
- currentChannelCount = s.channelSet.size();
- // these are rough est - no real need to correct for reg-domain;
- if (s.channelSet.contains("A")) currentChannelCount += (9 - 1);
- if (s.channelSet.contains("B")) currentChannelCount += (11 - 1);
-
- }
- if (s.scanIntervalSec == BatchedScanSettings.UNSPECIFIED) {
- currentScanInterval = BatchedScanSettings.DEFAULT_INTERVAL_SEC;
- } else {
- currentScanInterval = s.scanIntervalSec;
- }
- currentCsph = 60 * 60 * currentChannelCount / currentScanInterval;
-
- if (currentCsph > responsibleCsph) {
- responsibleUid = r.uid;
- responsibleWorkSource = r.workSource;
- responsibleCsph = currentCsph;
- }
-
- if (s.maxScansPerBatch != BatchedScanSettings.UNSPECIFIED &&
- s.maxScansPerBatch < setting.maxScansPerBatch) {
- setting.maxScansPerBatch = s.maxScansPerBatch;
- }
- if (s.maxApPerScan != BatchedScanSettings.UNSPECIFIED &&
- (setting.maxApPerScan == BatchedScanSettings.UNSPECIFIED ||
- s.maxApPerScan > setting.maxApPerScan)) {
- setting.maxApPerScan = s.maxApPerScan;
- }
- if (s.scanIntervalSec != BatchedScanSettings.UNSPECIFIED &&
- s.scanIntervalSec < setting.scanIntervalSec) {
- setting.scanIntervalSec = s.scanIntervalSec;
- }
- if (s.maxApForDistance != BatchedScanSettings.UNSPECIFIED &&
- (setting.maxApForDistance == BatchedScanSettings.UNSPECIFIED ||
- s.maxApForDistance > setting.maxApForDistance)) {
- setting.maxApForDistance = s.maxApForDistance;
- }
- if (s.channelSet != null && s.channelSet.size() != 0) {
- if (setting.channelSet == null || setting.channelSet.size() != 0) {
- if (setting.channelSet == null) setting.channelSet = new ArrayList<String>();
- for (String i : s.channelSet) {
- if (setting.channelSet.contains(i) == false) setting.channelSet.add(i);
- }
- } // else, ignore the constraint - we already use all channels
- } else {
- if (setting.channelSet == null || setting.channelSet.size() != 0) {
- setting.channelSet = new ArrayList<String>();
- }
- }
- }
-
- setting.constrain();
- mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid, (int)responsibleCsph,
- responsibleWorkSource);
- }
-
- private void enforceAccessPermission() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
- "WifiService");
- }
-
- private void enforceChangePermission() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
- "WifiService");
-
- }
-
- private void enforceWorkSourcePermission() {
- mContext.enforceCallingPermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
- "WifiService");
-
- }
-
- private void enforceMulticastChangePermission() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE,
- "WifiService");
- }
-
- private void enforceConnectivityInternalPermission() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CONNECTIVITY_INTERNAL,
- "ConnectivityService");
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
- * @param enable {@code true} to enable, {@code false} to disable.
- * @return {@code true} if the enable/disable operation was
- * started or is already in the queue.
- */
- public synchronized boolean setWifiEnabled(boolean enable) {
- enforceChangePermission();
- Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid());
- if (DBG) {
- Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n");
- }
-
- /*
- * Caller might not have WRITE_SECURE_SETTINGS,
- * only CHANGE_WIFI_STATE is enforced
- */
-
- long ident = Binder.clearCallingIdentity();
- try {
- if (! mSettingsStore.handleWifiToggled(enable)) {
- // Nothing to do if wifi cannot be toggled
- return true;
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
-
- mWifiController.sendMessage(CMD_WIFI_TOGGLED);
- return true;
- }
-
- /**
- * see {@link WifiManager#getWifiState()}
- * @return One of {@link WifiManager#WIFI_STATE_DISABLED},
- * {@link WifiManager#WIFI_STATE_DISABLING},
- * {@link WifiManager#WIFI_STATE_ENABLED},
- * {@link WifiManager#WIFI_STATE_ENABLING},
- * {@link WifiManager#WIFI_STATE_UNKNOWN}
- */
- public int getWifiEnabledState() {
- enforceAccessPermission();
- return mWifiStateMachine.syncGetWifiState();
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)}
- * @param wifiConfig SSID, security and channel details as
- * part of WifiConfiguration
- * @param enabled true to enable and false to disable
- */
- public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
- enforceChangePermission();
- // null wifiConfig is a meaningful input for CMD_SET_AP
- if (wifiConfig == null || wifiConfig.isValid()) {
- mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
- } else {
- Slog.e(TAG, "Invalid WifiConfiguration");
- }
- }
-
- /**
- * see {@link WifiManager#getWifiApState()}
- * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED},
- * {@link WifiManager#WIFI_AP_STATE_DISABLING},
- * {@link WifiManager#WIFI_AP_STATE_ENABLED},
- * {@link WifiManager#WIFI_AP_STATE_ENABLING},
- * {@link WifiManager#WIFI_AP_STATE_FAILED}
- */
- public int getWifiApEnabledState() {
- enforceAccessPermission();
- return mWifiStateMachine.syncGetWifiApState();
- }
-
- /**
- * see {@link WifiManager#getWifiApConfiguration()}
- * @return soft access point configuration
- */
- public WifiConfiguration getWifiApConfiguration() {
- enforceAccessPermission();
- return mWifiStateMachine.syncGetWifiApConfiguration();
- }
-
- /**
- * see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)}
- * @param wifiConfig WifiConfiguration details for soft access point
- */
- public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
- enforceChangePermission();
- if (wifiConfig == null)
- return;
- if (wifiConfig.isValid()) {
- mWifiStateMachine.setWifiApConfiguration(wifiConfig);
- } else {
- Slog.e(TAG, "Invalid WifiConfiguration");
- }
- }
-
- /**
- * @param enable {@code true} to enable, {@code false} to disable.
- * @return {@code true} if the enable/disable operation was
- * started or is already in the queue.
- */
- public boolean isScanAlwaysAvailable() {
- enforceAccessPermission();
- return mSettingsStore.isScanAlwaysAvailable();
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#disconnect()}
- */
- public void disconnect() {
- enforceChangePermission();
- mWifiStateMachine.disconnectCommand();
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#reconnect()}
- */
- public void reconnect() {
- enforceChangePermission();
- mWifiStateMachine.reconnectCommand();
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#reassociate()}
- */
- public void reassociate() {
- enforceChangePermission();
- mWifiStateMachine.reassociateCommand();
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#getConfiguredNetworks()}
- * @return the list of configured networks
- */
- public List<WifiConfiguration> getConfiguredNetworks() {
- enforceAccessPermission();
- if (mWifiStateMachineChannel != null) {
- return mWifiStateMachine.syncGetConfiguredNetworks(mWifiStateMachineChannel);
- } else {
- Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
- return null;
- }
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#addOrUpdateNetwork(WifiConfiguration)}
- * @return the supplicant-assigned identifier for the new or updated
- * network if the operation succeeds, or {@code -1} if it fails
- */
- public int addOrUpdateNetwork(WifiConfiguration config) {
- enforceChangePermission();
- if (config.proxySettings == ProxySettings.PAC) {
- enforceConnectivityInternalPermission();
- }
- if (config.isValid()) {
- if (mWifiStateMachineChannel != null) {
- return mWifiStateMachine.syncAddOrUpdateNetwork(mWifiStateMachineChannel, config);
- } else {
- Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
- return -1;
- }
- } else {
- Slog.e(TAG, "bad network configuration");
- return -1;
- }
- }
-
- /**
- * See {@link android.net.wifi.WifiManager#removeNetwork(int)}
- * @param netId the integer that identifies the network configuration
- * to the supplicant
- * @return {@code true} if the operation succeeded
- */
- public boolean removeNetwork(int netId) {
- enforceChangePermission();
- if (mWifiStateMachineChannel != null) {
- return mWifiStateMachine.syncRemoveNetwork(mWifiStateMachineChannel, netId);
- } else {
- Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
- return false;
- }
- }
-
- /**
- * See {@link android.net.wifi.WifiManager#enableNetwork(int, boolean)}
- * @param netId the integer that identifies the network configuration
- * to the supplicant
- * @param disableOthers if true, disable all other networks.
- * @return {@code true} if the operation succeeded
- */
- public boolean enableNetwork(int netId, boolean disableOthers) {
- enforceChangePermission();
- if (mWifiStateMachineChannel != null) {
- return mWifiStateMachine.syncEnableNetwork(mWifiStateMachineChannel, netId,
- disableOthers);
- } else {
- Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
- return false;
- }
- }
-
- /**
- * See {@link android.net.wifi.WifiManager#disableNetwork(int)}
- * @param netId the integer that identifies the network configuration
- * to the supplicant
- * @return {@code true} if the operation succeeded
- */
- public boolean disableNetwork(int netId) {
- enforceChangePermission();
- if (mWifiStateMachineChannel != null) {
- return mWifiStateMachine.syncDisableNetwork(mWifiStateMachineChannel, netId);
- } else {
- Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
- return false;
- }
- }
-
- /**
- * See {@link android.net.wifi.WifiManager#getConnectionInfo()}
- * @return the Wi-Fi information, contained in {@link WifiInfo}.
- */
- public WifiInfo getConnectionInfo() {
- enforceAccessPermission();
- /*
- * Make sure we have the latest information, by sending
- * a status request to the supplicant.
- */
- return mWifiStateMachine.syncRequestConnectionInfo();
- }
-
- /**
- * Return the results of the most recent access point scan, in the form of
- * a list of {@link ScanResult} objects.
- * @return the list of results
- */
- public List<ScanResult> getScanResults(String callingPackage) {
- enforceAccessPermission();
- int userId = UserHandle.getCallingUserId();
- int uid = Binder.getCallingUid();
- long ident = Binder.clearCallingIdentity();
- try {
- if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
- return new ArrayList<ScanResult>();
- }
- int currentUser = ActivityManager.getCurrentUser();
- if (userId != currentUser) {
- return new ArrayList<ScanResult>();
- } else {
- return mWifiStateMachine.syncGetScanResultsList();
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /**
- * Tell the supplicant to persist the current list of configured networks.
- * @return {@code true} if the operation succeeded
- *
- * TODO: deprecate this
- */
- public boolean saveConfiguration() {
- boolean result = true;
- enforceChangePermission();
- if (mWifiStateMachineChannel != null) {
- return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel);
- } else {
- Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
- return false;
- }
- }
-
- /**
- * Set the country code
- * @param countryCode ISO 3166 country code.
- * @param persist {@code true} if the setting should be remembered.
- *
- * The persist behavior exists so that wifi can fall back to the last
- * persisted country code on a restart, when the locale information is
- * not available from telephony.
- */
- public void setCountryCode(String countryCode, boolean persist) {
- Slog.i(TAG, "WifiService trying to set country code to " + countryCode +
- " with persist set to " + persist);
- enforceConnectivityInternalPermission();
- final long token = Binder.clearCallingIdentity();
- try {
- mWifiStateMachine.setCountryCode(countryCode, persist);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- /**
- * Set the operational frequency band
- * @param band One of
- * {@link WifiManager#WIFI_FREQUENCY_BAND_AUTO},
- * {@link WifiManager#WIFI_FREQUENCY_BAND_5GHZ},
- * {@link WifiManager#WIFI_FREQUENCY_BAND_2GHZ},
- * @param persist {@code true} if the setting should be remembered.
- *
- */
- public void setFrequencyBand(int band, boolean persist) {
- enforceChangePermission();
- if (!isDualBandSupported()) return;
- Slog.i(TAG, "WifiService trying to set frequency band to " + band +
- " with persist set to " + persist);
- final long token = Binder.clearCallingIdentity();
- try {
- mWifiStateMachine.setFrequencyBand(band, persist);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
-
- /**
- * Get the operational frequency band
- */
- public int getFrequencyBand() {
- enforceAccessPermission();
- return mWifiStateMachine.getFrequencyBand();
- }
-
- public boolean isDualBandSupported() {
- //TODO: Should move towards adding a driver API that checks at runtime
- return mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_wifi_dual_band_support);
- }
-
- /**
- * Return the DHCP-assigned addresses from the last successful DHCP request,
- * if any.
- * @return the DHCP information
- * @deprecated
- */
- public DhcpInfo getDhcpInfo() {
- enforceAccessPermission();
- DhcpResults dhcpResults = mWifiStateMachine.syncGetDhcpResults();
- if (dhcpResults.linkProperties == null) return null;
-
- DhcpInfo info = new DhcpInfo();
- for (LinkAddress la : dhcpResults.linkProperties.getLinkAddresses()) {
- InetAddress addr = la.getAddress();
- if (addr instanceof Inet4Address) {
- info.ipAddress = NetworkUtils.inetAddressToInt((Inet4Address)addr);
- break;
- }
- }
- for (RouteInfo r : dhcpResults.linkProperties.getRoutes()) {
- if (r.isDefaultRoute()) {
- InetAddress gateway = r.getGateway();
- if (gateway instanceof Inet4Address) {
- info.gateway = NetworkUtils.inetAddressToInt((Inet4Address)gateway);
- }
- } else if (r.hasGateway() == false) {
- LinkAddress dest = r.getDestination();
- if (dest.getAddress() instanceof Inet4Address) {
- info.netmask = NetworkUtils.prefixLengthToNetmaskInt(
- dest.getNetworkPrefixLength());
- }
- }
- }
- int dnsFound = 0;
- for (InetAddress dns : dhcpResults.linkProperties.getDnses()) {
- if (dns instanceof Inet4Address) {
- if (dnsFound == 0) {
- info.dns1 = NetworkUtils.inetAddressToInt((Inet4Address)dns);
- } else {
- info.dns2 = NetworkUtils.inetAddressToInt((Inet4Address)dns);
- }
- if (++dnsFound > 1) break;
- }
- }
- InetAddress serverAddress = dhcpResults.serverAddress;
- if (serverAddress instanceof Inet4Address) {
- info.serverAddress = NetworkUtils.inetAddressToInt((Inet4Address)serverAddress);
- }
- info.leaseDuration = dhcpResults.leaseDuration;
-
- return info;
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#startWifi}
- *
- */
- public void startWifi() {
- enforceConnectivityInternalPermission();
- /* TODO: may be add permissions for access only to connectivity service
- * TODO: if a start issued, keep wifi alive until a stop issued irrespective
- * of WifiLock & device idle status unless wifi enabled status is toggled
- */
-
- mWifiStateMachine.setDriverStart(true);
- mWifiStateMachine.reconnectCommand();
- }
-
- public void captivePortalCheckComplete() {
- enforceConnectivityInternalPermission();
- mWifiStateMachine.captivePortalCheckComplete();
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#stopWifi}
- *
- */
- public void stopWifi() {
- enforceConnectivityInternalPermission();
- /*
- * TODO: if a stop is issued, wifi is brought up only by startWifi
- * unless wifi enabled status is toggled
- */
- mWifiStateMachine.setDriverStart(false);
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#addToBlacklist}
- *
- */
- public void addToBlacklist(String bssid) {
- enforceChangePermission();
-
- mWifiStateMachine.addToBlacklist(bssid);
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#clearBlacklist}
- *
- */
- public void clearBlacklist() {
- enforceChangePermission();
-
- mWifiStateMachine.clearBlacklist();
- }
-
- /**
- * enable TDLS for the local NIC to remote NIC
- * The APPs don't know the remote MAC address to identify NIC though,
- * so we need to do additional work to find it from remote IP address
- */
-
- class TdlsTaskParams {
- public String remoteIpAddress;
- public boolean enable;
- }
-
- class TdlsTask extends AsyncTask<TdlsTaskParams, Integer, Integer> {
- @Override
- protected Integer doInBackground(TdlsTaskParams... params) {
-
- // Retrieve parameters for the call
- TdlsTaskParams param = params[0];
- String remoteIpAddress = param.remoteIpAddress.trim();
- boolean enable = param.enable;
-
- // Get MAC address of Remote IP
- String macAddress = null;
-
- BufferedReader reader = null;
-
- try {
- reader = new BufferedReader(new FileReader("/proc/net/arp"));
-
- // Skip over the line bearing colum titles
- String line = reader.readLine();
-
- while ((line = reader.readLine()) != null) {
- String[] tokens = line.split("[ ]+");
- if (tokens.length < 6) {
- continue;
- }
-
- // ARP column format is
- // Address HWType HWAddress Flags Mask IFace
- String ip = tokens[0];
- String mac = tokens[3];
-
- if (remoteIpAddress.equals(ip)) {
- macAddress = mac;
- break;
- }
- }
-
- if (macAddress == null) {
- Slog.w(TAG, "Did not find remoteAddress {" + remoteIpAddress + "} in " +
- "/proc/net/arp");
- } else {
- enableTdlsWithMacAddress(macAddress, enable);
- }
-
- } catch (FileNotFoundException e) {
- Slog.e(TAG, "Could not open /proc/net/arp to lookup mac address");
- } catch (IOException e) {
- Slog.e(TAG, "Could not read /proc/net/arp to lookup mac address");
- } finally {
- try {
- if (reader != null) {
- reader.close();
- }
- }
- catch (IOException e) {
- // Do nothing
- }
- }
-
- return 0;
- }
- }
-
- public void enableTdls(String remoteAddress, boolean enable) {
- TdlsTaskParams params = new TdlsTaskParams();
- params.remoteIpAddress = remoteAddress;
- params.enable = enable;
- new TdlsTask().execute(params);
- }
-
-
- public void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable) {
- mWifiStateMachine.enableTdls(remoteMacAddress, enable);
- }
-
- /**
- * Get a reference to handler. This is used by a client to establish
- * an AsyncChannel communication with WifiService
- */
- public Messenger getWifiServiceMessenger() {
- enforceAccessPermission();
- enforceChangePermission();
- return new Messenger(mClientHandler);
- }
-
- /** Get a reference to WifiStateMachine handler for AsyncChannel communication */
- public Messenger getWifiStateMachineMessenger() {
- enforceAccessPermission();
- enforceChangePermission();
- return mWifiStateMachine.getMessenger();
- }
-
- /**
- * Get the IP and proxy configuration file
- */
- public String getConfigFile() {
- enforceAccessPermission();
- return mWifiStateMachine.getConfigFile();
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(Intent.ACTION_SCREEN_ON)) {
- mWifiController.sendMessage(CMD_SCREEN_ON);
- } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
- mWifiController.sendMessage(CMD_USER_PRESENT);
- } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
- mWifiController.sendMessage(CMD_SCREEN_OFF);
- } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
- int pluggedType = intent.getIntExtra("plugged", 0);
- mWifiController.sendMessage(CMD_BATTERY_CHANGED, pluggedType, 0, null);
- } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
- int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
- BluetoothAdapter.STATE_DISCONNECTED);
- mWifiStateMachine.sendBluetoothAdapterStateChange(state);
- } else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
- boolean emergencyMode = intent.getBooleanExtra("phoneinECMState", false);
- mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, emergencyMode ? 1 : 0, 0);
- }
- }
- };
-
- /**
- * Observes settings changes to scan always mode.
- */
- private void registerForScanModeChange() {
- ContentObserver contentObserver = new ContentObserver(null) {
- @Override
- public void onChange(boolean selfChange) {
- mSettingsStore.handleWifiScanAlwaysAvailableToggled();
- mWifiController.sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED);
- }
- };
-
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE),
- false, contentObserver);
- }
-
- private void registerForBroadcasts() {
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_SCREEN_ON);
- intentFilter.addAction(Intent.ACTION_USER_PRESENT);
- intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
- intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
- intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
- intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
- intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
- mContext.registerReceiver(mReceiver, intentFilter);
- }
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
- != PackageManager.PERMISSION_GRANTED) {
- pw.println("Permission Denial: can't dump WifiService from from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid());
- return;
- }
- pw.println("Wi-Fi is " + mWifiStateMachine.syncGetWifiStateByName());
- pw.println("Stay-awake conditions: " +
- Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0));
- pw.println("mMulticastEnabled " + mMulticastEnabled);
- pw.println("mMulticastDisabled " + mMulticastDisabled);
- mWifiController.dump(fd, pw, args);
- mSettingsStore.dump(fd, pw, args);
- mNotificationController.dump(fd, pw, args);
- mTrafficPoller.dump(fd, pw, args);
-
- pw.println("Latest scan results:");
- List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList();
- if (scanResults != null && scanResults.size() != 0) {
- pw.println(" BSSID Frequency RSSI Flags SSID");
- for (ScanResult r : scanResults) {
- pw.printf(" %17s %9d %5d %-16s %s%n",
- r.BSSID,
- r.frequency,
- r.level,
- r.capabilities,
- r.SSID == null ? "" : r.SSID);
- }
- }
- pw.println();
- pw.println("Locks acquired: " + mFullLocksAcquired + " full, " +
- mFullHighPerfLocksAcquired + " full high perf, " +
- mScanLocksAcquired + " scan");
- pw.println("Locks released: " + mFullLocksReleased + " full, " +
- mFullHighPerfLocksReleased + " full high perf, " +
- mScanLocksReleased + " scan");
- pw.println();
- pw.println("Locks held:");
- mLocks.dump(pw);
-
- mWifiWatchdogStateMachine.dump(fd, pw, args);
- pw.println();
- mWifiStateMachine.dump(fd, pw, args);
- pw.println();
- }
-
- private class WifiLock extends DeathRecipient {
- WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
- super(lockMode, tag, binder, ws);
- }
-
- public void binderDied() {
- synchronized (mLocks) {
- releaseWifiLockLocked(mBinder);
- }
- }
-
- public String toString() {
- return "WifiLock{" + mTag + " type=" + mMode + " binder=" + mBinder + "}";
- }
- }
-
- class LockList {
- private List<WifiLock> mList;
-
- private LockList() {
- mList = new ArrayList<WifiLock>();
- }
-
- synchronized boolean hasLocks() {
- return !mList.isEmpty();
- }
-
- synchronized int getStrongestLockMode() {
- if (mList.isEmpty()) {
- return WifiManager.WIFI_MODE_FULL;
- }
-
- if (mFullHighPerfLocksAcquired > mFullHighPerfLocksReleased) {
- return WifiManager.WIFI_MODE_FULL_HIGH_PERF;
- }
-
- if (mFullLocksAcquired > mFullLocksReleased) {
- return WifiManager.WIFI_MODE_FULL;
- }
-
- return WifiManager.WIFI_MODE_SCAN_ONLY;
- }
-
- synchronized void updateWorkSource(WorkSource ws) {
- for (int i = 0; i < mLocks.mList.size(); i++) {
- ws.add(mLocks.mList.get(i).mWorkSource);
- }
- }
-
- private void addLock(WifiLock lock) {
- if (findLockByBinder(lock.mBinder) < 0) {
- mList.add(lock);
- }
- }
-
- private WifiLock removeLock(IBinder binder) {
- int index = findLockByBinder(binder);
- if (index >= 0) {
- WifiLock ret = mList.remove(index);
- ret.unlinkDeathRecipient();
- return ret;
- } else {
- return null;
- }
- }
-
- private int findLockByBinder(IBinder binder) {
- int size = mList.size();
- for (int i = size - 1; i >= 0; i--) {
- if (mList.get(i).mBinder == binder)
- return i;
- }
- return -1;
- }
-
- private void dump(PrintWriter pw) {
- for (WifiLock l : mList) {
- pw.print(" ");
- pw.println(l);
- }
- }
- }
-
- void enforceWakeSourcePermission(int uid, int pid) {
- if (uid == android.os.Process.myUid()) {
- return;
- }
- mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
- pid, uid, null);
- }
-
- public boolean acquireWifiLock(IBinder binder, int lockMode, String tag, WorkSource ws) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
- if (lockMode != WifiManager.WIFI_MODE_FULL &&
- lockMode != WifiManager.WIFI_MODE_SCAN_ONLY &&
- lockMode != WifiManager.WIFI_MODE_FULL_HIGH_PERF) {
- Slog.e(TAG, "Illegal argument, lockMode= " + lockMode);
- if (DBG) throw new IllegalArgumentException("lockMode=" + lockMode);
- return false;
- }
- if (ws != null && ws.size() == 0) {
- ws = null;
- }
- if (ws != null) {
- enforceWakeSourcePermission(Binder.getCallingUid(), Binder.getCallingPid());
- }
- if (ws == null) {
- ws = new WorkSource(Binder.getCallingUid());
- }
- WifiLock wifiLock = new WifiLock(lockMode, tag, binder, ws);
- synchronized (mLocks) {
- return acquireWifiLockLocked(wifiLock);
- }
- }
-
- private void noteAcquireWifiLock(WifiLock wifiLock) throws RemoteException {
- switch(wifiLock.mMode) {
- case WifiManager.WIFI_MODE_FULL:
- case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
- case WifiManager.WIFI_MODE_SCAN_ONLY:
- mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource);
- break;
- }
- }
-
- private void noteReleaseWifiLock(WifiLock wifiLock) throws RemoteException {
- switch(wifiLock.mMode) {
- case WifiManager.WIFI_MODE_FULL:
- case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
- case WifiManager.WIFI_MODE_SCAN_ONLY:
- mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
- break;
- }
- }
-
- private boolean acquireWifiLockLocked(WifiLock wifiLock) {
- if (DBG) Slog.d(TAG, "acquireWifiLockLocked: " + wifiLock);
-
- mLocks.addLock(wifiLock);
-
- long ident = Binder.clearCallingIdentity();
- try {
- noteAcquireWifiLock(wifiLock);
- switch(wifiLock.mMode) {
- case WifiManager.WIFI_MODE_FULL:
- ++mFullLocksAcquired;
- break;
- case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
- ++mFullHighPerfLocksAcquired;
- break;
-
- case WifiManager.WIFI_MODE_SCAN_ONLY:
- ++mScanLocksAcquired;
- break;
- }
- mWifiController.sendMessage(CMD_LOCKS_CHANGED);
- return true;
- } catch (RemoteException e) {
- return false;
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- public void updateWifiLockWorkSource(IBinder lock, WorkSource ws) {
- int uid = Binder.getCallingUid();
- int pid = Binder.getCallingPid();
- if (ws != null && ws.size() == 0) {
- ws = null;
- }
- if (ws != null) {
- enforceWakeSourcePermission(uid, pid);
- }
- long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mLocks) {
- int index = mLocks.findLockByBinder(lock);
- if (index < 0) {
- throw new IllegalArgumentException("Wifi lock not active");
- }
- WifiLock wl = mLocks.mList.get(index);
- noteReleaseWifiLock(wl);
- wl.mWorkSource = ws != null ? new WorkSource(ws) : new WorkSource(uid);
- noteAcquireWifiLock(wl);
- }
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- public boolean releaseWifiLock(IBinder lock) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
- synchronized (mLocks) {
- return releaseWifiLockLocked(lock);
- }
- }
-
- private boolean releaseWifiLockLocked(IBinder lock) {
- boolean hadLock;
-
- WifiLock wifiLock = mLocks.removeLock(lock);
-
- if (DBG) Slog.d(TAG, "releaseWifiLockLocked: " + wifiLock);
-
- hadLock = (wifiLock != null);
-
- long ident = Binder.clearCallingIdentity();
- try {
- if (hadLock) {
- noteReleaseWifiLock(wifiLock);
- switch(wifiLock.mMode) {
- case WifiManager.WIFI_MODE_FULL:
- ++mFullLocksReleased;
- break;
- case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
- ++mFullHighPerfLocksReleased;
- break;
- case WifiManager.WIFI_MODE_SCAN_ONLY:
- ++mScanLocksReleased;
- break;
- }
- mWifiController.sendMessage(CMD_LOCKS_CHANGED);
- }
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
-
- return hadLock;
- }
-
- private abstract class DeathRecipient
- implements IBinder.DeathRecipient {
- String mTag;
- int mMode;
- IBinder mBinder;
- WorkSource mWorkSource;
-
- DeathRecipient(int mode, String tag, IBinder binder, WorkSource ws) {
- super();
- mTag = tag;
- mMode = mode;
- mBinder = binder;
- mWorkSource = ws;
- try {
- mBinder.linkToDeath(this, 0);
- } catch (RemoteException e) {
- binderDied();
- }
- }
-
- void unlinkDeathRecipient() {
- mBinder.unlinkToDeath(this, 0);
- }
- }
-
- private class Multicaster extends DeathRecipient {
- Multicaster(String tag, IBinder binder) {
- super(Binder.getCallingUid(), tag, binder, null);
- }
-
- public void binderDied() {
- Slog.e(TAG, "Multicaster binderDied");
- synchronized (mMulticasters) {
- int i = mMulticasters.indexOf(this);
- if (i != -1) {
- removeMulticasterLocked(i, mMode);
- }
- }
- }
-
- public String toString() {
- return "Multicaster{" + mTag + " binder=" + mBinder + "}";
- }
-
- public int getUid() {
- return mMode;
- }
- }
-
- public void initializeMulticastFiltering() {
- enforceMulticastChangePermission();
-
- synchronized (mMulticasters) {
- // if anybody had requested filters be off, leave off
- if (mMulticasters.size() != 0) {
- return;
- } else {
- mWifiStateMachine.startFilteringMulticastV4Packets();
- }
- }
- }
-
- public void acquireMulticastLock(IBinder binder, String tag) {
- enforceMulticastChangePermission();
-
- synchronized (mMulticasters) {
- mMulticastEnabled++;
- mMulticasters.add(new Multicaster(tag, binder));
- // Note that we could call stopFilteringMulticastV4Packets only when
- // our new size == 1 (first call), but this function won't
- // be called often and by making the stopPacket call each
- // time we're less fragile and self-healing.
- mWifiStateMachine.stopFilteringMulticastV4Packets();
- }
-
- int uid = Binder.getCallingUid();
- final long ident = Binder.clearCallingIdentity();
- try {
- mBatteryStats.noteWifiMulticastEnabled(uid);
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- public void releaseMulticastLock() {
- enforceMulticastChangePermission();
-
- int uid = Binder.getCallingUid();
- synchronized (mMulticasters) {
- mMulticastDisabled++;
- int size = mMulticasters.size();
- for (int i = size - 1; i >= 0; i--) {
- Multicaster m = mMulticasters.get(i);
- if ((m != null) && (m.getUid() == uid)) {
- removeMulticasterLocked(i, uid);
- }
- }
- }
- }
-
- private void removeMulticasterLocked(int i, int uid)
- {
- Multicaster removed = mMulticasters.remove(i);
-
- if (removed != null) {
- removed.unlinkDeathRecipient();
- }
- if (mMulticasters.size() == 0) {
- mWifiStateMachine.startFilteringMulticastV4Packets();
- }
-
- final long ident = Binder.clearCallingIdentity();
- try {
- mBatteryStats.noteWifiMulticastDisabled(uid);
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- public boolean isMulticastEnabled() {
- enforceAccessPermission();
-
- synchronized (mMulticasters) {
- return (mMulticasters.size() > 0);
- }
- }
-}
diff --git a/services/core/java/com/android/server/wifi/WifiSettingsStore.java b/services/core/java/com/android/server/wifi/WifiSettingsStore.java
deleted file mode 100644
index 3ff8061..0000000
--- a/services/core/java/com/android/server/wifi/WifiSettingsStore.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.provider.Settings;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/* Tracks persisted settings for Wi-Fi and airplane mode interaction */
-final class WifiSettingsStore {
- /* Values tracked in Settings.Global.WIFI_ON */
- private static final int WIFI_DISABLED = 0;
- private static final int WIFI_ENABLED = 1;
- /* Wifi enabled while in airplane mode */
- private static final int WIFI_ENABLED_AIRPLANE_OVERRIDE = 2;
- /* Wifi disabled due to airplane mode on */
- private static final int WIFI_DISABLED_AIRPLANE_ON = 3;
-
- /* Persisted state that tracks the wifi & airplane interaction from settings */
- private int mPersistWifiState = WIFI_DISABLED;
- /* Tracks current airplane mode state */
- private boolean mAirplaneModeOn = false;
-
- /* Tracks the setting of scan being available even when wi-fi is turned off
- */
- private boolean mScanAlwaysAvailable;
-
- private final Context mContext;
-
- /* Tracks if we have checked the saved wi-fi state after boot */
- private boolean mCheckSavedStateAtBoot = false;
-
- WifiSettingsStore(Context context) {
- mContext = context;
- mAirplaneModeOn = getPersistedAirplaneModeOn();
- mPersistWifiState = getPersistedWifiState();
- mScanAlwaysAvailable = getPersistedScanAlwaysAvailable();
- }
-
- synchronized boolean isWifiToggleEnabled() {
- if (!mCheckSavedStateAtBoot) {
- mCheckSavedStateAtBoot = true;
- if (testAndClearWifiSavedState()) return true;
- }
-
- if (mAirplaneModeOn) {
- return mPersistWifiState == WIFI_ENABLED_AIRPLANE_OVERRIDE;
- } else {
- return mPersistWifiState != WIFI_DISABLED;
- }
- }
-
- /**
- * Returns true if airplane mode is currently on.
- * @return {@code true} if airplane mode is on.
- */
- synchronized boolean isAirplaneModeOn() {
- return mAirplaneModeOn;
- }
-
- synchronized boolean isScanAlwaysAvailable() {
- return mScanAlwaysAvailable;
- }
-
- synchronized boolean handleWifiToggled(boolean wifiEnabled) {
- // Can Wi-Fi be toggled in airplane mode ?
- if (mAirplaneModeOn && !isAirplaneToggleable()) {
- return false;
- }
-
- if (wifiEnabled) {
- if (mAirplaneModeOn) {
- persistWifiState(WIFI_ENABLED_AIRPLANE_OVERRIDE);
- } else {
- persistWifiState(WIFI_ENABLED);
- }
- } else {
- // When wifi state is disabled, we do not care
- // if airplane mode is on or not. The scenario of
- // wifi being disabled due to airplane mode being turned on
- // is handled handleAirplaneModeToggled()
- persistWifiState(WIFI_DISABLED);
- }
- return true;
- }
-
- synchronized boolean handleAirplaneModeToggled() {
- // Is Wi-Fi sensitive to airplane mode changes ?
- if (!isAirplaneSensitive()) {
- return false;
- }
-
- mAirplaneModeOn = getPersistedAirplaneModeOn();
- if (mAirplaneModeOn) {
- // Wifi disabled due to airplane on
- if (mPersistWifiState == WIFI_ENABLED) {
- persistWifiState(WIFI_DISABLED_AIRPLANE_ON);
- }
- } else {
- /* On airplane mode disable, restore wifi state if necessary */
- if (testAndClearWifiSavedState() ||
- mPersistWifiState == WIFI_ENABLED_AIRPLANE_OVERRIDE) {
- persistWifiState(WIFI_ENABLED);
- }
- }
- return true;
- }
-
- synchronized void handleWifiScanAlwaysAvailableToggled() {
- mScanAlwaysAvailable = getPersistedScanAlwaysAvailable();
- }
-
- void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("mPersistWifiState " + mPersistWifiState);
- pw.println("mAirplaneModeOn " + mAirplaneModeOn);
- }
-
- private void persistWifiState(int state) {
- final ContentResolver cr = mContext.getContentResolver();
- mPersistWifiState = state;
- Settings.Global.putInt(cr, Settings.Global.WIFI_ON, state);
- }
-
- /* Does Wi-Fi need to be disabled when airplane mode is on ? */
- private boolean isAirplaneSensitive() {
- String airplaneModeRadios = Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_RADIOS);
- return airplaneModeRadios == null
- || airplaneModeRadios.contains(Settings.Global.RADIO_WIFI);
- }
-
- /* Is Wi-Fi allowed to be re-enabled while airplane mode is on ? */
- private boolean isAirplaneToggleable() {
- String toggleableRadios = Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
- return toggleableRadios != null
- && toggleableRadios.contains(Settings.Global.RADIO_WIFI);
- }
-
- /* After a reboot, we restore wi-fi to be on if it was turned off temporarily for tethering.
- * The settings app tracks the saved state, but the framework has to check it at boot to
- * make sure the wi-fi is turned on in case it was turned off for the purpose of tethering.
- *
- * Note that this is not part of the regular WIFI_ON setting because this only needs to
- * be controlled through the settings app and not the Wi-Fi public API.
- */
- private boolean testAndClearWifiSavedState() {
- final ContentResolver cr = mContext.getContentResolver();
- int wifiSavedState = 0;
- try {
- wifiSavedState = Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE);
- if(wifiSavedState == 1)
- Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0);
- } catch (Settings.SettingNotFoundException e) {
- ;
- }
- return (wifiSavedState == 1);
- }
-
- private int getPersistedWifiState() {
- final ContentResolver cr = mContext.getContentResolver();
- try {
- return Settings.Global.getInt(cr, Settings.Global.WIFI_ON);
- } catch (Settings.SettingNotFoundException e) {
- Settings.Global.putInt(cr, Settings.Global.WIFI_ON, WIFI_DISABLED);
- return WIFI_DISABLED;
- }
- }
-
- private boolean getPersistedAirplaneModeOn() {
- return Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
- }
-
- private boolean getPersistedScanAlwaysAvailable() {
- return Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
- 0) == 1;
- }
-}
diff --git a/services/core/java/com/android/server/wifi/WifiTrafficPoller.java b/services/core/java/com/android/server/wifi/WifiTrafficPoller.java
deleted file mode 100644
index b498550..0000000
--- a/services/core/java/com/android/server/wifi/WifiTrafficPoller.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.NetworkInfo;
-import static android.net.NetworkInfo.DetailedState.CONNECTED;
-import android.net.TrafficStats;
-import android.net.wifi.WifiManager;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.util.Log;
-import android.os.Handler;
-import android.os.Message;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import com.android.internal.util.AsyncChannel;
-
-/* Polls for traffic stats and notifies the clients */
-final class WifiTrafficPoller {
- /**
- * Interval in milliseconds between polling for traffic
- * statistics
- */
- private static final int POLL_TRAFFIC_STATS_INTERVAL_MSECS = 1000;
-
- private static final int ENABLE_TRAFFIC_STATS_POLL = 1;
- private static final int TRAFFIC_STATS_POLL = 2;
- private static final int ADD_CLIENT = 3;
- private static final int REMOVE_CLIENT = 4;
-
- private boolean mEnableTrafficStatsPoll = false;
- private int mTrafficStatsPollToken = 0;
- private long mTxPkts;
- private long mRxPkts;
- /* Tracks last reported data activity */
- private int mDataActivity;
-
- private final List<Messenger> mClients = new ArrayList<Messenger>();
- // err on the side of updating at boot since screen on broadcast may be missed
- // the first time
- private AtomicBoolean mScreenOn = new AtomicBoolean(true);
- private final TrafficHandler mTrafficHandler;
- private NetworkInfo mNetworkInfo;
- private final String mInterface;
-
- WifiTrafficPoller(Context context, String iface) {
- mInterface = iface;
- mTrafficHandler = new TrafficHandler();
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_SCREEN_ON);
-
- context.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(
- WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
- mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
- WifiManager.EXTRA_NETWORK_INFO);
- } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
- mScreenOn.set(false);
- } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
- mScreenOn.set(true);
- }
- evaluateTrafficStatsPolling();
- }
- }, filter);
- }
-
- void addClient(Messenger client) {
- Message.obtain(mTrafficHandler, ADD_CLIENT, client).sendToTarget();
- }
-
- void removeClient(Messenger client) {
- Message.obtain(mTrafficHandler, REMOVE_CLIENT, client).sendToTarget();
- }
-
-
- private class TrafficHandler extends Handler {
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case ENABLE_TRAFFIC_STATS_POLL:
- mEnableTrafficStatsPoll = (msg.arg1 == 1);
- mTrafficStatsPollToken++;
- if (mEnableTrafficStatsPoll) {
- notifyOnDataActivity();
- sendMessageDelayed(Message.obtain(this, TRAFFIC_STATS_POLL,
- mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS);
- }
- break;
- case TRAFFIC_STATS_POLL:
- if (msg.arg1 == mTrafficStatsPollToken) {
- notifyOnDataActivity();
- sendMessageDelayed(Message.obtain(this, TRAFFIC_STATS_POLL,
- mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS);
- }
- break;
- case ADD_CLIENT:
- mClients.add((Messenger) msg.obj);
- break;
- case REMOVE_CLIENT:
- mClients.remove(msg.obj);
- break;
- }
-
- }
- }
-
- private void evaluateTrafficStatsPolling() {
- Message msg;
- if (mNetworkInfo == null) return;
- if (mNetworkInfo.getDetailedState() == CONNECTED && mScreenOn.get()) {
- msg = Message.obtain(mTrafficHandler,
- ENABLE_TRAFFIC_STATS_POLL, 1, 0);
- } else {
- msg = Message.obtain(mTrafficHandler,
- ENABLE_TRAFFIC_STATS_POLL, 0, 0);
- }
- msg.sendToTarget();
- }
-
- private void notifyOnDataActivity() {
- long sent, received;
- long preTxPkts = mTxPkts, preRxPkts = mRxPkts;
- int dataActivity = WifiManager.DATA_ACTIVITY_NONE;
-
- mTxPkts = TrafficStats.getTxPackets(mInterface);
- mRxPkts = TrafficStats.getRxPackets(mInterface);
-
- if (preTxPkts > 0 || preRxPkts > 0) {
- sent = mTxPkts - preTxPkts;
- received = mRxPkts - preRxPkts;
- if (sent > 0) {
- dataActivity |= WifiManager.DATA_ACTIVITY_OUT;
- }
- if (received > 0) {
- dataActivity |= WifiManager.DATA_ACTIVITY_IN;
- }
-
- if (dataActivity != mDataActivity && mScreenOn.get()) {
- mDataActivity = dataActivity;
- for (Messenger client : mClients) {
- Message msg = Message.obtain();
- msg.what = WifiManager.DATA_ACTIVITY_NOTIFICATION;
- msg.arg1 = mDataActivity;
- try {
- client.send(msg);
- } catch (RemoteException e) {
- // Failed to reach, skip
- // Client removal is handled in WifiService
- }
- }
- }
- }
- }
-
- void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("mEnableTrafficStatsPoll " + mEnableTrafficStatsPoll);
- pw.println("mTrafficStatsPollToken " + mTrafficStatsPollToken);
- pw.println("mTxPkts " + mTxPkts);
- pw.println("mRxPkts " + mRxPkts);
- pw.println("mDataActivity " + mDataActivity);
- }
-
-}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
new file mode 100644
index 0000000..35b7f99
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -0,0 +1,1173 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.app.Service;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.MagnificationSpec;
+import android.view.Surface;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.WindowInfo;
+import android.view.WindowManager;
+import android.view.WindowManagerInternal.MagnificationCallbacks;
+import android.view.WindowManagerInternal.WindowsForAccessibilityCallback;
+import android.view.WindowManagerPolicy;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class contains the accessibility related logic of the window manger.
+ */
+final class AccessibilityController {
+
+ private final WindowManagerService mWindowManagerService;
+
+ private static final float[] sTempFloats = new float[9];
+
+ public AccessibilityController(WindowManagerService service) {
+ mWindowManagerService = service;
+ }
+
+ private DisplayMagnifier mDisplayMagnifier;
+
+ private WindowsForAccessibilityObserver mWindowsForAccessibilityObserver;
+
+ public void setMagnificationCallbacksLocked(MagnificationCallbacks callbacks) {
+ if (callbacks != null) {
+ if (mDisplayMagnifier != null) {
+ throw new IllegalStateException("Magnification callbacks already set!");
+ }
+ mDisplayMagnifier = new DisplayMagnifier(mWindowManagerService, callbacks);
+ } else {
+ if (mDisplayMagnifier == null) {
+ throw new IllegalStateException("Magnification callbacks already cleared!");
+ }
+ mDisplayMagnifier.destroyLocked();
+ mDisplayMagnifier = null;
+ }
+ }
+
+ public void setWindowsForAccessibilityCallback(WindowsForAccessibilityCallback callback) {
+ if (callback != null) {
+ if (mWindowsForAccessibilityObserver != null) {
+ throw new IllegalStateException(
+ "Windows for accessibility callback already set!");
+ }
+ mWindowsForAccessibilityObserver = new WindowsForAccessibilityObserver(
+ mWindowManagerService, callback);
+ } else {
+ if (mWindowsForAccessibilityObserver == null) {
+ throw new IllegalStateException(
+ "Windows for accessibility callback already cleared!");
+ }
+ mWindowsForAccessibilityObserver = null;
+ }
+ }
+
+ public void setMagnificationSpecLocked(MagnificationSpec spec) {
+ if (mDisplayMagnifier != null) {
+ mDisplayMagnifier.setMagnificationSpecLocked(spec);
+ }
+ if (mWindowsForAccessibilityObserver != null) {
+ mWindowsForAccessibilityObserver.computeChangedWindows();
+ }
+ }
+
+ public void onRectangleOnScreenRequestedLocked(Rect rectangle, boolean immediate) {
+ if (mDisplayMagnifier != null) {
+ mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle, immediate);
+ }
+ // Not relevant for the window observer.
+ }
+
+ public void onWindowLayersChangedLocked() {
+ if (mDisplayMagnifier != null) {
+ mDisplayMagnifier.onWindowLayersChangedLocked();
+ }
+ if (mWindowsForAccessibilityObserver != null) {
+ mWindowsForAccessibilityObserver.computeChangedWindows();
+ }
+ }
+
+ public void onRotationChangedLocked(DisplayContent displayContent, int rotation) {
+ if (mDisplayMagnifier != null) {
+ mDisplayMagnifier.onRotationChangedLocked(displayContent, rotation);
+ }
+ if (mWindowsForAccessibilityObserver != null) {
+ mWindowsForAccessibilityObserver.computeChangedWindows();
+ }
+ }
+
+ public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
+ if (mDisplayMagnifier != null) {
+ mDisplayMagnifier.onAppWindowTransitionLocked(windowState, transition);
+ }
+ // Not relevant for the window observer.
+ }
+
+ public void onWindowTransitionLocked(WindowState windowState, int transition) {
+ if (mDisplayMagnifier != null) {
+ mDisplayMagnifier.onWindowTransitionLocked(windowState, transition);
+ }
+ if (mWindowsForAccessibilityObserver != null) {
+ mWindowsForAccessibilityObserver.computeChangedWindows();
+ }
+ }
+
+ public void onWindowFocusChangedLocked() {
+ // Not relevant for the display magnifier.
+
+ if (mWindowsForAccessibilityObserver != null) {
+ mWindowsForAccessibilityObserver.computeChangedWindows();
+ }
+ }
+
+ /** NOTE: This has to be called within a surface transaction. */
+ public void drawMagnifiedRegionBorderIfNeededLocked() {
+ if (mDisplayMagnifier != null) {
+ mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
+ }
+ // Not relevant for the window observer.
+ }
+
+ public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
+ if (mDisplayMagnifier != null) {
+ return mDisplayMagnifier.getMagnificationSpecForWindowLocked(windowState);
+ }
+ return null;
+ }
+
+ public boolean hasCallbacksLocked() {
+ return (mDisplayMagnifier != null
+ || mWindowsForAccessibilityObserver != null);
+ }
+
+ private static void populateTransformationMatrixLocked(WindowState windowState,
+ Matrix outMatrix) {
+ sTempFloats[Matrix.MSCALE_X] = windowState.mWinAnimator.mDsDx;
+ sTempFloats[Matrix.MSKEW_Y] = windowState.mWinAnimator.mDtDx;
+ sTempFloats[Matrix.MSKEW_X] = windowState.mWinAnimator.mDsDy;
+ sTempFloats[Matrix.MSCALE_Y] = windowState.mWinAnimator.mDtDy;
+ sTempFloats[Matrix.MTRANS_X] = windowState.mShownFrame.left;
+ sTempFloats[Matrix.MTRANS_Y] = windowState.mShownFrame.top;
+ sTempFloats[Matrix.MPERSP_0] = 0;
+ sTempFloats[Matrix.MPERSP_1] = 0;
+ sTempFloats[Matrix.MPERSP_2] = 1;
+ outMatrix.setValues(sTempFloats);
+ }
+
+ /**
+ * This class encapsulates the functionality related to display magnification.
+ */
+ private static final class DisplayMagnifier {
+
+ private static final String LOG_TAG = "DisplayMagnifier";
+
+ private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
+ private static final boolean DEBUG_ROTATION = false;
+ private static final boolean DEBUG_LAYERS = false;
+ private static final boolean DEBUG_RECTANGLE_REQUESTED = false;
+ private static final boolean DEBUG_VIEWPORT_WINDOW = false;
+
+ private final Rect mTempRect1 = new Rect();
+ private final Rect mTempRect2 = new Rect();
+
+ private final Region mTempRegion1 = new Region();
+ private final Region mTempRegion2 = new Region();
+ private final Region mTempRegion3 = new Region();
+ private final Region mTempRegion4 = new Region();
+
+ private final Context mContext;
+ private final WindowManagerService mWindowManagerService;
+ private final MagnifiedViewport mMagnifedViewport;
+ private final Handler mHandler;
+
+ private final MagnificationCallbacks mCallbacks;
+
+ private final long mLongAnimationDuration;
+
+ public DisplayMagnifier(WindowManagerService windowManagerService,
+ MagnificationCallbacks callbacks) {
+ mContext = windowManagerService.mContext;
+ mWindowManagerService = windowManagerService;
+ mCallbacks = callbacks;
+ mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
+ mMagnifedViewport = new MagnifiedViewport();
+ mLongAnimationDuration = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_longAnimTime);
+ }
+
+ public void setMagnificationSpecLocked(MagnificationSpec spec) {
+ mMagnifedViewport.updateMagnificationSpecLocked(spec);
+ mMagnifedViewport.recomputeBoundsLocked();
+ mWindowManagerService.scheduleAnimationLocked();
+ }
+
+ public void onRectangleOnScreenRequestedLocked(Rect rectangle, boolean immediate) {
+ if (DEBUG_RECTANGLE_REQUESTED) {
+ Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle);
+ }
+ if (!mMagnifedViewport.isMagnifyingLocked()) {
+ return;
+ }
+ Rect magnifiedRegionBounds = mTempRect2;
+ mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(magnifiedRegionBounds);
+ if (magnifiedRegionBounds.contains(rectangle)) {
+ return;
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = rectangle.left;
+ args.argi2 = rectangle.top;
+ args.argi3 = rectangle.right;
+ args.argi4 = rectangle.bottom;
+ mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED,
+ args).sendToTarget();
+ }
+
+ public void onWindowLayersChangedLocked() {
+ if (DEBUG_LAYERS) {
+ Slog.i(LOG_TAG, "Layers changed.");
+ }
+ mMagnifedViewport.recomputeBoundsLocked();
+ mWindowManagerService.scheduleAnimationLocked();
+ }
+
+ public void onRotationChangedLocked(DisplayContent displayContent, int rotation) {
+ if (DEBUG_ROTATION) {
+ Slog.i(LOG_TAG, "Rotaton: " + Surface.rotationToString(rotation)
+ + " displayId: " + displayContent.getDisplayId());
+ }
+ mMagnifedViewport.onRotationChangedLocked();
+ mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED);
+ }
+
+ public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
+ if (DEBUG_WINDOW_TRANSITIONS) {
+ Slog.i(LOG_TAG, "Window transition: "
+ + AppTransition.appTransitionToString(transition)
+ + " displayId: " + windowState.getDisplayId());
+ }
+ final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
+ if (magnifying) {
+ switch (transition) {
+ case AppTransition.TRANSIT_ACTIVITY_OPEN:
+ case AppTransition.TRANSIT_TASK_OPEN:
+ case AppTransition.TRANSIT_TASK_TO_FRONT:
+ case AppTransition.TRANSIT_WALLPAPER_OPEN:
+ case AppTransition.TRANSIT_WALLPAPER_CLOSE:
+ case AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN: {
+ mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED);
+ }
+ }
+ }
+ }
+
+ public void onWindowTransitionLocked(WindowState windowState, int transition) {
+ if (DEBUG_WINDOW_TRANSITIONS) {
+ Slog.i(LOG_TAG, "Window transition: "
+ + AppTransition.appTransitionToString(transition)
+ + " displayId: " + windowState.getDisplayId());
+ }
+ final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
+ final int type = windowState.mAttrs.type;
+ switch (transition) {
+ case WindowManagerPolicy.TRANSIT_ENTER:
+ case WindowManagerPolicy.TRANSIT_SHOW: {
+ if (!magnifying) {
+ break;
+ }
+ switch (type) {
+ case WindowManager.LayoutParams.TYPE_APPLICATION:
+ case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
+ case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
+ case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
+ case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
+ case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
+ case WindowManager.LayoutParams.TYPE_PHONE:
+ case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
+ case WindowManager.LayoutParams.TYPE_TOAST:
+ case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
+ case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
+ case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
+ case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
+ case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
+ case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
+ case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL:
+ case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: {
+ Rect magnifiedRegionBounds = mTempRect2;
+ mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(
+ magnifiedRegionBounds);
+ Rect touchableRegionBounds = mTempRect1;
+ windowState.getTouchableRegion(mTempRegion1);
+ mTempRegion1.getBounds(touchableRegionBounds);
+ if (!magnifiedRegionBounds.intersect(touchableRegionBounds)) {
+ mCallbacks.onRectangleOnScreenRequested(
+ touchableRegionBounds.left,
+ touchableRegionBounds.top,
+ touchableRegionBounds.right,
+ touchableRegionBounds.bottom);
+ }
+ } break;
+ } break;
+ }
+ }
+ }
+
+ public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
+ MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
+ if (spec != null && !spec.isNop()) {
+ WindowManagerPolicy policy = mWindowManagerService.mPolicy;
+ final int windowType = windowState.mAttrs.type;
+ if (!policy.isTopLevelWindow(windowType) && windowState.mAttachedWindow != null
+ && !policy.canMagnifyWindow(windowType)) {
+ return null;
+ }
+ if (!policy.canMagnifyWindow(windowState.mAttrs.type)) {
+ return null;
+ }
+ }
+ return spec;
+ }
+
+ public void destroyLocked() {
+ mMagnifedViewport.destroyWindow();
+ }
+
+ /** NOTE: This has to be called within a surface transaction. */
+ public void drawMagnifiedRegionBorderIfNeededLocked() {
+ mMagnifedViewport.drawWindowIfNeededLocked();
+ }
+
+ private final class MagnifiedViewport {
+
+ private static final int DEFAUTLT_BORDER_WIDTH_DIP = 5;
+
+ private final SparseArray<WindowState> mTempWindowStates =
+ new SparseArray<WindowState>();
+
+ private final RectF mTempRectF = new RectF();
+
+ private final Point mTempPoint = new Point();
+
+ private final Matrix mTempMatrix = new Matrix();
+
+ private final Region mMagnifiedBounds = new Region();
+ private final Region mOldMagnifiedBounds = new Region();
+
+ private final MagnificationSpec mMagnificationSpec = MagnificationSpec.obtain();
+
+ private final WindowManager mWindowManager;
+
+ private final int mBorderWidth;
+ private final int mHalfBorderWidth;
+
+ private final ViewportWindow mWindow;
+
+ private boolean mFullRedrawNeeded;
+
+ public MagnifiedViewport() {
+ mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE);
+ mBorderWidth = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, DEFAUTLT_BORDER_WIDTH_DIP,
+ mContext.getResources().getDisplayMetrics());
+ mHalfBorderWidth = (int) (mBorderWidth + 0.5) / 2;
+ mWindow = new ViewportWindow(mContext);
+ recomputeBoundsLocked();
+ }
+
+ public void updateMagnificationSpecLocked(MagnificationSpec spec) {
+ if (spec != null) {
+ mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY);
+ } else {
+ mMagnificationSpec.clear();
+ }
+ // If this message is pending we are in a rotation animation and do not want
+ // to show the border. We will do so when the pending message is handled.
+ if (!mHandler.hasMessages(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
+ setMagnifiedRegionBorderShownLocked(isMagnifyingLocked(), true);
+ }
+ }
+
+ public void recomputeBoundsLocked() {
+ mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+ final int screenWidth = mTempPoint.x;
+ final int screenHeight = mTempPoint.y;
+
+ Region magnifiedBounds = mMagnifiedBounds;
+ magnifiedBounds.set(0, 0, 0, 0);
+
+ Region availableBounds = mTempRegion1;
+ availableBounds.set(0, 0, screenWidth, screenHeight);
+
+ Region nonMagnifiedBounds = mTempRegion4;
+ nonMagnifiedBounds.set(0, 0, 0, 0);
+
+ SparseArray<WindowState> visibleWindows = mTempWindowStates;
+ visibleWindows.clear();
+ populateWindowsOnScreenLocked(visibleWindows);
+
+ final int visibleWindowCount = visibleWindows.size();
+ for (int i = visibleWindowCount - 1; i >= 0; i--) {
+ WindowState windowState = visibleWindows.valueAt(i);
+ if (windowState.mAttrs.type == WindowManager
+ .LayoutParams.TYPE_MAGNIFICATION_OVERLAY) {
+ continue;
+ }
+
+ Region windowBounds = mTempRegion2;
+ Matrix matrix = mTempMatrix;
+ populateTransformationMatrixLocked(windowState, matrix);
+ RectF windowFrame = mTempRectF;
+
+ if (mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+ windowFrame.set(windowState.mFrame);
+ windowFrame.offset(-windowFrame.left, -windowFrame.top);
+ matrix.mapRect(windowFrame);
+ windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
+ (int) windowFrame.right, (int) windowFrame.bottom);
+ magnifiedBounds.op(windowBounds, Region.Op.UNION);
+ magnifiedBounds.op(availableBounds, Region.Op.INTERSECT);
+ } else {
+ Region touchableRegion = mTempRegion3;
+ windowState.getTouchableRegion(touchableRegion);
+ Rect touchableFrame = mTempRect1;
+ touchableRegion.getBounds(touchableFrame);
+ windowFrame.set(touchableFrame);
+ windowFrame.offset(-windowState.mFrame.left, -windowState.mFrame.top);
+ matrix.mapRect(windowFrame);
+ windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
+ (int) windowFrame.right, (int) windowFrame.bottom);
+ nonMagnifiedBounds.op(windowBounds, Region.Op.UNION);
+ windowBounds.op(magnifiedBounds, Region.Op.DIFFERENCE);
+ availableBounds.op(windowBounds, Region.Op.DIFFERENCE);
+ }
+
+ Region accountedBounds = mTempRegion2;
+ accountedBounds.set(magnifiedBounds);
+ accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION);
+ accountedBounds.op(0, 0, screenWidth, screenHeight, Region.Op.INTERSECT);
+
+ if (accountedBounds.isRect()) {
+ Rect accountedFrame = mTempRect1;
+ accountedBounds.getBounds(accountedFrame);
+ if (accountedFrame.width() == screenWidth
+ && accountedFrame.height() == screenHeight) {
+ break;
+ }
+ }
+ }
+
+ visibleWindows.clear();
+
+ magnifiedBounds.op(mHalfBorderWidth, mHalfBorderWidth,
+ screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth,
+ Region.Op.INTERSECT);
+
+ if (!mOldMagnifiedBounds.equals(magnifiedBounds)) {
+ Region bounds = Region.obtain();
+ bounds.set(magnifiedBounds);
+ mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED,
+ bounds).sendToTarget();
+
+ mWindow.setBounds(magnifiedBounds);
+ Rect dirtyRect = mTempRect1;
+ if (mFullRedrawNeeded) {
+ mFullRedrawNeeded = false;
+ dirtyRect.set(mHalfBorderWidth, mHalfBorderWidth,
+ screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth);
+ mWindow.invalidate(dirtyRect);
+ } else {
+ Region dirtyRegion = mTempRegion3;
+ dirtyRegion.set(magnifiedBounds);
+ dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION);
+ dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
+ dirtyRegion.getBounds(dirtyRect);
+ mWindow.invalidate(dirtyRect);
+ }
+
+ mOldMagnifiedBounds.set(magnifiedBounds);
+ }
+ }
+
+ public void onRotationChangedLocked() {
+ // If we are magnifying, hide the magnified border window immediately so
+ // the user does not see strange artifacts during rotation. The screenshot
+ // used for rotation has already the border. After the rotation is complete
+ // we will show the border.
+ if (isMagnifyingLocked()) {
+ setMagnifiedRegionBorderShownLocked(false, false);
+ final long delay = (long) (mLongAnimationDuration
+ * mWindowManagerService.mWindowAnimationScale);
+ Message message = mHandler.obtainMessage(
+ MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
+ mHandler.sendMessageDelayed(message, delay);
+ }
+ recomputeBoundsLocked();
+ mWindow.updateSize();
+ }
+
+ public void setMagnifiedRegionBorderShownLocked(boolean shown, boolean animate) {
+ if (shown) {
+ mFullRedrawNeeded = true;
+ mOldMagnifiedBounds.set(0, 0, 0, 0);
+ }
+ mWindow.setShown(shown, animate);
+ }
+
+ public void getMagnifiedFrameInContentCoordsLocked(Rect rect) {
+ MagnificationSpec spec = mMagnificationSpec;
+ mMagnifiedBounds.getBounds(rect);
+ rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
+ rect.scale(1.0f / spec.scale);
+ }
+
+ public boolean isMagnifyingLocked() {
+ return mMagnificationSpec.scale > 1.0f;
+ }
+
+ public MagnificationSpec getMagnificationSpecLocked() {
+ return mMagnificationSpec;
+ }
+
+ /** NOTE: This has to be called within a surface transaction. */
+ public void drawWindowIfNeededLocked() {
+ recomputeBoundsLocked();
+ mWindow.drawIfNeeded();
+ }
+
+ public void destroyWindow() {
+ mWindow.releaseSurface();
+ }
+
+ private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
+ DisplayContent displayContent = mWindowManagerService
+ .getDefaultDisplayContentLocked();
+ WindowList windowList = displayContent.getWindowList();
+ final int windowCount = windowList.size();
+ for (int i = 0; i < windowCount; i++) {
+ WindowState windowState = windowList.get(i);
+ if ((windowState.isOnScreen() || windowState.mAttrs.type == WindowManager
+ .LayoutParams.TYPE_UNIVERSE_BACKGROUND)
+ && !windowState.mWinAnimator.mEnterAnimationPending) {
+ outWindows.put(windowState.mLayer, windowState);
+ }
+ }
+ }
+
+ private final class ViewportWindow {
+ private static final String SURFACE_TITLE = "Magnification Overlay";
+
+ private static final String PROPERTY_NAME_ALPHA = "alpha";
+
+ private static final int MIN_ALPHA = 0;
+ private static final int MAX_ALPHA = 255;
+
+ private final Region mBounds = new Region();
+ private final Rect mDirtyRect = new Rect();
+ private final Paint mPaint = new Paint();
+
+ private final ValueAnimator mShowHideFrameAnimator;
+ private final SurfaceControl mSurfaceControl;
+ private final Surface mSurface = new Surface();
+
+ private boolean mShown;
+ private int mAlpha;
+
+ private boolean mInvalidated;
+
+ public ViewportWindow(Context context) {
+ SurfaceControl surfaceControl = null;
+ try {
+ mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+ surfaceControl = new SurfaceControl(mWindowManagerService.mFxSession,
+ SURFACE_TITLE, mTempPoint.x, mTempPoint.y, PixelFormat.TRANSLUCENT,
+ SurfaceControl.HIDDEN);
+ } catch (OutOfResourcesException oore) {
+ /* ignore */
+ }
+ mSurfaceControl = surfaceControl;
+ mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay()
+ .getLayerStack());
+ mSurfaceControl.setLayer(mWindowManagerService.mPolicy.windowTypeToLayerLw(
+ WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY)
+ * WindowManagerService.TYPE_LAYER_MULTIPLIER);
+ mSurfaceControl.setPosition(0, 0);
+ mSurface.copyFrom(mSurfaceControl);
+
+ TypedValue typedValue = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight,
+ typedValue, true);
+ final int borderColor = context.getResources().getColor(typedValue.resourceId);
+
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(mBorderWidth);
+ mPaint.setColor(borderColor);
+
+ Interpolator interpolator = new DecelerateInterpolator(2.5f);
+ final long longAnimationDuration = context.getResources().getInteger(
+ com.android.internal.R.integer.config_longAnimTime);
+
+ mShowHideFrameAnimator = ObjectAnimator.ofInt(this, PROPERTY_NAME_ALPHA,
+ MIN_ALPHA, MAX_ALPHA);
+ mShowHideFrameAnimator.setInterpolator(interpolator);
+ mShowHideFrameAnimator.setDuration(longAnimationDuration);
+ mInvalidated = true;
+ }
+
+ public void setShown(boolean shown, boolean animate) {
+ synchronized (mWindowManagerService.mWindowMap) {
+ if (mShown == shown) {
+ return;
+ }
+ mShown = shown;
+ if (animate) {
+ if (mShowHideFrameAnimator.isRunning()) {
+ mShowHideFrameAnimator.reverse();
+ } else {
+ if (shown) {
+ mShowHideFrameAnimator.start();
+ } else {
+ mShowHideFrameAnimator.reverse();
+ }
+ }
+ } else {
+ mShowHideFrameAnimator.cancel();
+ if (shown) {
+ setAlpha(MAX_ALPHA);
+ } else {
+ setAlpha(MIN_ALPHA);
+ }
+ }
+ if (DEBUG_VIEWPORT_WINDOW) {
+ Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown);
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ // Called reflectively from an animator.
+ public int getAlpha() {
+ synchronized (mWindowManagerService.mWindowMap) {
+ return mAlpha;
+ }
+ }
+
+ public void setAlpha(int alpha) {
+ synchronized (mWindowManagerService.mWindowMap) {
+ if (mAlpha == alpha) {
+ return;
+ }
+ mAlpha = alpha;
+ invalidate(null);
+ if (DEBUG_VIEWPORT_WINDOW) {
+ Slog.i(LOG_TAG, "ViewportWindow set alpha: " + alpha);
+ }
+ }
+ }
+
+ public void setBounds(Region bounds) {
+ synchronized (mWindowManagerService.mWindowMap) {
+ if (mBounds.equals(bounds)) {
+ return;
+ }
+ mBounds.set(bounds);
+ invalidate(mDirtyRect);
+ if (DEBUG_VIEWPORT_WINDOW) {
+ Slog.i(LOG_TAG, "ViewportWindow set bounds: " + bounds);
+ }
+ }
+ }
+
+ public void updateSize() {
+ synchronized (mWindowManagerService.mWindowMap) {
+ mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+ mSurfaceControl.setSize(mTempPoint.x, mTempPoint.y);
+ invalidate(mDirtyRect);
+ }
+ }
+
+ public void invalidate(Rect dirtyRect) {
+ if (dirtyRect != null) {
+ mDirtyRect.set(dirtyRect);
+ } else {
+ mDirtyRect.setEmpty();
+ }
+ mInvalidated = true;
+ mWindowManagerService.scheduleAnimationLocked();
+ }
+
+ /** NOTE: This has to be called within a surface transaction. */
+ public void drawIfNeeded() {
+ synchronized (mWindowManagerService.mWindowMap) {
+ if (!mInvalidated) {
+ return;
+ }
+ mInvalidated = false;
+ Canvas canvas = null;
+ try {
+ // Empty dirty rectangle means unspecified.
+ if (mDirtyRect.isEmpty()) {
+ mBounds.getBounds(mDirtyRect);
+ }
+ mDirtyRect.inset(- mHalfBorderWidth, - mHalfBorderWidth);
+ canvas = mSurface.lockCanvas(mDirtyRect);
+ if (DEBUG_VIEWPORT_WINDOW) {
+ Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect);
+ }
+ } catch (IllegalArgumentException iae) {
+ /* ignore */
+ } catch (Surface.OutOfResourcesException oore) {
+ /* ignore */
+ }
+ if (canvas == null) {
+ return;
+ }
+ if (DEBUG_VIEWPORT_WINDOW) {
+ Slog.i(LOG_TAG, "Bounds: " + mBounds);
+ }
+ canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
+ mPaint.setAlpha(mAlpha);
+ Path path = mBounds.getBoundaryPath();
+ canvas.drawPath(path, mPaint);
+
+ mSurface.unlockCanvasAndPost(canvas);
+
+ if (mAlpha > 0) {
+ mSurfaceControl.show();
+ } else {
+ mSurfaceControl.hide();
+ }
+ }
+ }
+
+ public void releaseSurface() {
+ mSurfaceControl.release();
+ mSurface.release();
+ }
+ }
+ }
+
+ private class MyHandler extends Handler {
+ public static final int MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED = 1;
+ public static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2;
+ public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3;
+ public static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4;
+ public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
+
+ public MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED: {
+ Region bounds = (Region) message.obj;
+ mCallbacks.onMagnifedBoundsChanged(bounds);
+ bounds.recycle();
+ } break;
+
+ case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
+ SomeArgs args = (SomeArgs) message.obj;
+ final int left = args.argi1;
+ final int top = args.argi2;
+ final int right = args.argi3;
+ final int bottom = args.argi4;
+ mCallbacks.onRectangleOnScreenRequested(left, top, right, bottom);
+ args.recycle();
+ } break;
+
+ case MESSAGE_NOTIFY_USER_CONTEXT_CHANGED: {
+ mCallbacks.onUserContextChanged();
+ } break;
+
+ case MESSAGE_NOTIFY_ROTATION_CHANGED: {
+ final int rotation = message.arg1;
+ mCallbacks.onRotationChanged(rotation);
+ } break;
+
+ case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
+ synchronized (mWindowManagerService.mWindowMap) {
+ if (mMagnifedViewport.isMagnifyingLocked()) {
+ mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true);
+ mWindowManagerService.scheduleAnimationLocked();
+ }
+ }
+ } break;
+ }
+ }
+ }
+ }
+
+ /**
+ * This class encapsulates the functionality related to computing the windows
+ * reported for accessibility purposes. These windows are all windows a sighted
+ * user can see on the screen.
+ */
+ private static final class WindowsForAccessibilityObserver {
+ private static final String LOG_TAG = "WindowsForAccessibilityObserver";
+
+ private static final boolean DEBUG = false;
+
+ private final SparseArray<WindowState> mTempWindowStates =
+ new SparseArray<WindowState>();
+
+ private final List<WindowInfo> mOldWindows = new ArrayList<WindowInfo>();
+
+ private final Set<IBinder> mTempBinderSet = new ArraySet<IBinder>();
+
+ private final RectF mTempRectF = new RectF();
+
+ private final Matrix mTempMatrix = new Matrix();
+
+ private final Point mTempPoint = new Point();
+
+ private final Rect mTempRect = new Rect();
+
+ private final Region mTempRegion = new Region();
+
+ private final Region mTempRegion1 = new Region();
+
+ private final Context mContext;
+
+ private final WindowManagerService mWindowManagerService;
+
+ private final Handler mHandler;
+
+ private final WindowsForAccessibilityCallback mCallback;
+
+ public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
+ WindowsForAccessibilityCallback callback) {
+ mContext = windowManagerService.mContext;
+ mWindowManagerService = windowManagerService;
+ mCallback = callback;
+ mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
+ computeChangedWindows();
+ }
+
+ public void computeChangedWindows() {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "computeChangedWindows()");
+ }
+
+ synchronized (mWindowManagerService.mWindowMap) {
+ WindowManager windowManager = (WindowManager)
+ mContext.getSystemService(Context.WINDOW_SERVICE);
+ windowManager.getDefaultDisplay().getRealSize(mTempPoint);
+ final int screenWidth = mTempPoint.x;
+ final int screenHeight = mTempPoint.y;
+
+ Region unaccountedSpace = mTempRegion;
+ unaccountedSpace.set(0, 0, screenWidth, screenHeight);
+
+ SparseArray<WindowState> visibleWindows = mTempWindowStates;
+ populateVisibleWindowsOnScreenLocked(visibleWindows);
+
+ List<WindowInfo> windows = new ArrayList<WindowInfo>();
+
+ Set<IBinder> addedWindows = mTempBinderSet;
+ addedWindows.clear();
+
+ final int visibleWindowCount = visibleWindows.size();
+ for (int i = visibleWindowCount - 1; i >= 0; i--) {
+ WindowState windowState = visibleWindows.valueAt(i);
+ // Compute the window touchable frame as shown on the screen.
+
+ // Get the touchable frame.
+ Region touchableRegion = mTempRegion1;
+ windowState.getTouchableRegion(touchableRegion);
+ Rect touchableFrame = mTempRect;
+ touchableRegion.getBounds(touchableFrame);
+
+ // Move to origin as all transforms are captured by the matrix.
+ RectF windowFrame = mTempRectF;
+ windowFrame.set(touchableFrame);
+ windowFrame.offset(-windowState.mFrame.left, -windowState.mFrame.top);
+
+ // Map the frame to get what appears on the screen.
+ Matrix matrix = mTempMatrix;
+ populateTransformationMatrixLocked(windowState, matrix);
+ matrix.mapRect(windowFrame);
+
+ // Got the bounds.
+ Rect boundsInScreen = mTempRect;
+ boundsInScreen.set((int) windowFrame.left, (int) windowFrame.top,
+ (int) windowFrame.right, (int) windowFrame.bottom);
+
+ final int flags = windowState.mAttrs.flags;
+
+ // If the window is not touchable, do not report it but take into account
+ // the space it takes since the content behind it cannot be touched.
+ if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) == 1) {
+ unaccountedSpace.op(boundsInScreen, unaccountedSpace,
+ Region.Op.DIFFERENCE);
+ continue;
+ }
+
+ // If the window is completely covered by other windows - ignore.
+ if (unaccountedSpace.quickReject(boundsInScreen)) {
+ continue;
+ }
+
+ // Add windows of certain types not covered by modal windows.
+ if (isReportedWindowType(windowState.mAttrs.type)) {
+ // Add the window to the ones to be reported.
+ WindowInfo window = WindowInfo.obtain();
+ window.type = windowState.mAttrs.type;
+ window.layer = windowState.mLayer;
+ window.token = windowState.mClient.asBinder();
+
+ addedWindows.add(window.token);
+
+ WindowState attachedWindow = windowState.mAttachedWindow;
+ if (attachedWindow != null) {
+ window.parentToken = attachedWindow.mClient.asBinder();
+ }
+
+ window.focused = windowState.isFocused();
+ window.boundsInScreen.set(boundsInScreen);
+
+ final int childCount = windowState.mChildWindows.size();
+ if (childCount > 0) {
+ if (window.childTokens == null) {
+ window.childTokens = new ArrayList<IBinder>();
+ }
+ for (int j = 0; j < childCount; j++) {
+ WindowState child = windowState.mChildWindows.get(j);
+ window.childTokens.add(child.mClient.asBinder());
+ }
+ }
+
+ windows.add(window);
+ }
+
+ // Account for the space this window takes.
+ unaccountedSpace.op(boundsInScreen, unaccountedSpace,
+ Region.Op.REVERSE_DIFFERENCE);
+
+ // We figured out what is touchable for the entire screen - done.
+ if (unaccountedSpace.isEmpty()) {
+ break;
+ }
+
+ // If a window is modal, no other below can be touched - done.
+ if ((flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)) == 0) {
+ break;
+ }
+ }
+
+ // Remove child/parent references to windows that were not added.
+ final int windowCount = windows.size();
+ for (int i = 0; i < windowCount; i++) {
+ WindowInfo window = windows.get(i);
+ if (!addedWindows.contains(window.parentToken)) {
+ window.parentToken = null;
+ }
+ if (window.childTokens != null) {
+ final int childTokenCount = window.childTokens.size();
+ for (int j = childTokenCount - 1; j >= 0; j--) {
+ if (!addedWindows.contains(window.childTokens.get(j))) {
+ window.childTokens.remove(j);
+ }
+ }
+ // Leave the child token list if empty.
+ }
+ }
+
+ visibleWindows.clear();
+ addedWindows.clear();
+
+ // We computed the windows and if they changed notify the client.
+ boolean windowsChanged = false;
+ if (mOldWindows.size() != windows.size()) {
+ // Different size means something changed.
+ windowsChanged = true;
+ } else if (!mOldWindows.isEmpty() || !windows.isEmpty()) {
+ // Since we always traverse windows from high to low layer
+ // the old and new windows at the same index should be the
+ // same, otherwise something changed.
+ for (int i = 0; i < windowCount; i++) {
+ WindowInfo oldWindow = mOldWindows.get(i);
+ WindowInfo newWindow = windows.get(i);
+ // We do not care for layer changes given the window
+ // order does not change. This brings no new information
+ // to the clients.
+ if (windowChangedNoLayer(oldWindow, newWindow)) {
+ windowsChanged = true;
+ break;
+ }
+ }
+ }
+
+ if (windowsChanged) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Windows changed:" + windows);
+ }
+ // Remember the old windows to detect changes.
+ cacheWindows(windows);
+ // Announce the change.
+ mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED,
+ windows).sendToTarget();
+ } else {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "No windows changed.");
+ }
+ // Recycle the nodes as we do not need them.
+ clearAndRecycleWindows(windows);
+ }
+ }
+ }
+
+ private void cacheWindows(List<WindowInfo> windows) {
+ final int oldWindowCount = mOldWindows.size();
+ for (int i = oldWindowCount - 1; i >= 0; i--) {
+ mOldWindows.remove(i).recycle();
+ }
+ final int newWindowCount = windows.size();
+ for (int i = 0; i < newWindowCount; i++) {
+ WindowInfo newWindow = windows.get(i);
+ mOldWindows.add(WindowInfo.obtain(newWindow));
+ }
+ }
+
+ private boolean windowChangedNoLayer(WindowInfo oldWindow, WindowInfo newWindow) {
+ if (oldWindow == newWindow) {
+ return false;
+ }
+ if (oldWindow == null && newWindow != null) {
+ return true;
+ }
+ if (oldWindow != null && newWindow == null) {
+ return true;
+ }
+ if (oldWindow.type != newWindow.type) {
+ return true;
+ }
+ if (oldWindow.focused != newWindow.focused) {
+ return true;
+ }
+ if (oldWindow.token == null) {
+ if (newWindow.token != null) {
+ return true;
+ }
+ } else if (!oldWindow.token.equals(newWindow.token)) {
+ return true;
+ }
+ if (oldWindow.parentToken == null) {
+ if (newWindow.parentToken != null) {
+ return true;
+ }
+ } else if (!oldWindow.parentToken.equals(newWindow.parentToken)) {
+ return true;
+ }
+ if (!oldWindow.boundsInScreen.equals(newWindow.boundsInScreen)) {
+ return true;
+ }
+ if (oldWindow.childTokens != null && newWindow.childTokens != null
+ && !oldWindow.childTokens.equals(newWindow.childTokens)) {
+ return true;
+ }
+ return false;
+ }
+
+ private void clearAndRecycleWindows(List<WindowInfo> windows) {
+ final int windowCount = windows.size();
+ for (int i = windowCount - 1; i >= 0; i--) {
+ windows.remove(i).recycle();
+ }
+ }
+
+ private static boolean isReportedWindowType(int windowType) {
+ return (windowType != WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM
+ && windowType != WindowManager.LayoutParams.TYPE_WALLPAPER
+ && windowType != WindowManager.LayoutParams.TYPE_BOOT_PROGRESS
+ && windowType != WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
+ && windowType != WindowManager.LayoutParams.TYPE_DRAG
+ && windowType != WindowManager.LayoutParams.TYPE_HIDDEN_NAV_CONSUMER
+ && windowType != WindowManager.LayoutParams.TYPE_POINTER
+ && windowType != WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND
+ && windowType != WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY
+ && windowType != WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY
+ && windowType != WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY
+ && windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
+ }
+
+ private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
+ DisplayContent displayContent = mWindowManagerService
+ .getDefaultDisplayContentLocked();
+ WindowList windowList = displayContent.getWindowList();
+ final int windowCount = windowList.size();
+ for (int i = 0; i < windowCount; i++) {
+ WindowState windowState = windowList.get(i);
+ if (windowState.isVisibleLw()) {
+ outWindows.put(windowState.mLayer, windowState);
+ }
+ }
+ }
+
+ private class MyHandler extends Handler {
+ public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED = 1;
+
+ public MyHandler(Looper looper) {
+ super(looper, null, false);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MESSAGE_NOTIFY_WINDOWS_CHANGED: {
+ List<WindowInfo> windows = (List<WindowInfo>) message.obj;
+ mCallback.onWindowsForAccessibilityChanged(windows);
+ clearAndRecycleWindows(windows);
+ } break;
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 756e06a..c20e38c 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -17,20 +17,25 @@
package com.android.server.wm;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Point;
+import android.graphics.Rect;
import android.os.Debug;
import android.os.Handler;
import android.os.IRemoteCallback;
+import android.os.SystemProperties;
import android.util.Slog;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
+import android.view.animation.ClipRectAnimation;
import android.view.animation.Interpolator;
import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
import com.android.internal.util.DumpUtils.Dump;
import com.android.server.AttributeCache;
import com.android.server.wm.WindowManagerService.H;
@@ -111,7 +116,8 @@ public class AppTransition implements Dump {
/** Fraction of animation at which the recents thumbnail becomes completely transparent */
private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.25f;
- private static final long DEFAULT_APP_TRANSITION_DURATION = 250;
+ private static final int DEFAULT_APP_TRANSITION_DURATION = 250;
+ private static final int THUMBNAIL_APP_TRANSITION_DURATION = 225;
private final Context mContext;
private final Handler mH;
@@ -125,6 +131,12 @@ public class AppTransition implements Dump {
private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN = 4;
private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
+ // These are the possible states for the enter/exit activities during a thumbnail transition
+ private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0;
+ private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1;
+ private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2;
+ private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3;
+
private String mNextAppTransitionPackage;
private Bitmap mNextAppTransitionThumbnail;
// Used for thumbnail transitions. True if we're scaling up, false if scaling down
@@ -137,6 +149,9 @@ public class AppTransition implements Dump {
private int mNextAppTransitionStartWidth;
private int mNextAppTransitionStartHeight;
+ private Rect mTmpFromClipRect = new Rect();
+ private Rect mTmpToClipRect = new Rect();
+
private final static int APP_STATE_IDLE = 0;
private final static int APP_STATE_READY = 1;
private final static int APP_STATE_RUNNING = 2;
@@ -146,6 +161,7 @@ public class AppTransition implements Dump {
private final int mConfigShortAnimTime;
private final Interpolator mDecelerateInterpolator;
private final Interpolator mThumbnailFadeoutInterpolator;
+ private final Interpolator mThumbnailCubicInterpolator;
private int mCurrentUserId = 0;
@@ -156,6 +172,8 @@ public class AppTransition implements Dump {
com.android.internal.R.integer.config_shortAnimTime);
mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.decelerate_cubic);
+ mThumbnailCubicInterpolator = AnimationUtils.loadInterpolator(context,
+ com.android.internal.R.interpolator.fast_out_slow_in);
mThumbnailFadeoutInterpolator = new Interpolator() {
@Override
public float getInterpolation(float input) {
@@ -384,54 +402,239 @@ public class AppTransition implements Dump {
return a;
}
- Animation createThumbnailAnimationLocked(int transit, boolean enter, boolean thumb,
- int appWidth, int appHeight) {
+ /**
+ * Prepares the specified animation with a standard duration, interpolator, etc.
+ */
+ Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth, int appHeight,
+ int duration, Interpolator interpolator) {
+ a.setDuration(duration);
+ a.setFillAfter(true);
+ a.setInterpolator(interpolator);
+ a.initialize(appWidth, appHeight, appWidth, appHeight);
+ return a;
+ }
+
+ /**
+ * Prepares the specified animation with a standard duration, interpolator, etc.
+ */
+ Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight, int transit) {
+ // Pick the desired duration. If this is an inter-activity transition,
+ // it is the standard duration for that. Otherwise we use the longer
+ // task transition duration.
+ final int duration;
+ switch (transit) {
+ case TRANSIT_ACTIVITY_OPEN:
+ case TRANSIT_ACTIVITY_CLOSE:
+ duration = mConfigShortAnimTime;
+ break;
+ default:
+ duration = DEFAULT_APP_TRANSITION_DURATION;
+ break;
+ }
+ return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, duration,
+ mDecelerateInterpolator);
+ }
+
+ /**
+ * Return the current thumbnail transition state.
+ */
+ int getThumbnailTransitionState(boolean enter) {
+ if (enter) {
+ if (mNextAppTransitionScaleUp) {
+ return THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
+ } else {
+ return THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN;
+ }
+ } else {
+ if (mNextAppTransitionScaleUp) {
+ return THUMBNAIL_TRANSITION_EXIT_SCALE_UP;
+ } else {
+ return THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN;
+ }
+ }
+ }
+
+ /**
+ * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
+ * when a thumbnail is specified with the activity options.
+ */
+ Animation createThumbnailScaleAnimationLocked(int appWidth, int appHeight, int transit) {
Animation a;
final int thumbWidthI = mNextAppTransitionThumbnail.getWidth();
final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
final int thumbHeightI = mNextAppTransitionThumbnail.getHeight();
final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
- if (thumb) {
- // Animation for zooming thumbnail from its initial size to
- // filling the screen.
- if (mNextAppTransitionScaleUp) {
- float scaleW = appWidth / thumbWidth;
- float scaleH = appHeight / thumbHeight;
- Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
- computePivot(mNextAppTransitionStartX, 1 / scaleW),
- computePivot(mNextAppTransitionStartY, 1 / scaleH));
- scale.setInterpolator(mDecelerateInterpolator);
- Animation alpha = new AlphaAnimation(1, 0);
- alpha.setInterpolator(mThumbnailFadeoutInterpolator);
+ if (mNextAppTransitionScaleUp) {
+ // Animation for the thumbnail zooming from its initial size to the full screen
+ float scaleW = appWidth / thumbWidth;
+ float scaleH = appHeight / thumbHeight;
+ Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
+ computePivot(mNextAppTransitionStartX, 1 / scaleW),
+ computePivot(mNextAppTransitionStartY, 1 / scaleH));
+ scale.setInterpolator(mDecelerateInterpolator);
- // This AnimationSet uses the Interpolators assigned above.
- AnimationSet set = new AnimationSet(false);
- set.addAnimation(scale);
- set.addAnimation(alpha);
+ Animation alpha = new AlphaAnimation(1, 0);
+ alpha.setInterpolator(mThumbnailFadeoutInterpolator);
+
+ // This AnimationSet uses the Interpolators assigned above.
+ AnimationSet set = new AnimationSet(false);
+ set.addAnimation(scale);
+ set.addAnimation(alpha);
+ a = set;
+ } else {
+ // Animation for the thumbnail zooming down from the full screen to its final size
+ float scaleW = appWidth / thumbWidth;
+ float scaleH = appHeight / thumbHeight;
+ a = new ScaleAnimation(scaleW, 1, scaleH, 1,
+ computePivot(mNextAppTransitionStartX, 1 / scaleW),
+ computePivot(mNextAppTransitionStartY, 1 / scaleH));
+ }
+
+ return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
+ }
+
+ /**
+ * This alternate animation is created when we are doing a thumbnail transition, for the
+ * activity that is leaving, and the activity that is entering.
+ */
+ Animation createAlternateThumbnailEnterExitAnimationLocked(int thumbTransitState, int appWidth,
+ int appHeight, int orientation, int transit,
+ Rect containingFrame, Rect contentInsets) {
+ Animation a;
+ final int thumbWidthI = mNextAppTransitionThumbnail.getWidth();
+ final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
+ final int thumbHeightI = mNextAppTransitionThumbnail.getHeight();
+ final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
+
+ // Used for the ENTER_SCALE_UP and EXIT_SCALE_DOWN transitions
+ float scale = 1f;
+ int scaledTopDecor = 0;
+
+ switch (thumbTransitState) {
+ case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
+ // Entering app scales up with the thumbnail
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ // In portrait, we scale the width and clip to the top/left square
+ scale = thumbWidth / appWidth;
+ scaledTopDecor = (int) (scale * contentInsets.top);
+ int unscaledThumbHeight = (int) (thumbHeight / scale);
+ mTmpFromClipRect.set(containingFrame);
+ mTmpFromClipRect.bottom = (mTmpFromClipRect.top + unscaledThumbHeight);
+ mTmpToClipRect.set(containingFrame);
+ } else {
+ // In landscape, we scale the height and clip to the top/left square
+ scale = thumbHeight / (appHeight - contentInsets.top);
+ scaledTopDecor = (int) (scale * contentInsets.top);
+ int unscaledThumbWidth = (int) (thumbWidth / scale);
+ mTmpFromClipRect.set(containingFrame);
+ mTmpFromClipRect.right = (mTmpFromClipRect.left + unscaledThumbWidth);
+ mTmpToClipRect.set(containingFrame);
+ }
+
+ Animation scaleAnim = new ScaleAnimation(scale, 1, scale, 1,
+ computePivot(mNextAppTransitionStartX, scale),
+ computePivot(mNextAppTransitionStartY, scale));
+ Animation alphaAnim = new AlphaAnimation(1, 1);
+ Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
+ Animation translateAnim = new TranslateAnimation(0, 0, -scaledTopDecor, 0);
+
+ AnimationSet set = new AnimationSet(true);
+ set.addAnimation(alphaAnim);
+ set.addAnimation(clipAnim);
+ set.addAnimation(scaleAnim);
+ set.addAnimation(translateAnim);
a = set;
- } else {
- float scaleW = appWidth / thumbWidth;
- float scaleH = appHeight / thumbHeight;
- a = new ScaleAnimation(scaleW, 1, scaleH, 1,
- computePivot(mNextAppTransitionStartX, 1 / scaleW),
- computePivot(mNextAppTransitionStartY, 1 / scaleH));
+ break;
}
- } else if (enter) {
- // Entering app zooms out from the center of the thumbnail.
- if (mNextAppTransitionScaleUp) {
+ case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
+ // Exiting app while the thumbnail is scaling up should fade
+ if (transit == TRANSIT_WALLPAPER_INTRA_OPEN) {
+ // Fade out while bringing up selected activity. This keeps the
+ // current activity from showing through a launching wallpaper
+ // activity.
+ a = new AlphaAnimation(1, 0);
+ } else {
+ // noop animation
+ a = new AlphaAnimation(1, 1);
+ }
+ break;
+ }
+ case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: {
+ // Entering the other app, it should just be visible while we scale the thumbnail
+ // down above it
+ a = new AlphaAnimation(1, 1);
+ break;
+ }
+ case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
+ // Exiting the current app, the app should scale down with the thumbnail
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ // In portrait, we scale the width and clip to the top/left square
+ scale = thumbWidth / appWidth;
+ scaledTopDecor = (int) (scale * contentInsets.top);
+ int unscaledThumbHeight = (int) (thumbHeight / scale);
+ mTmpFromClipRect.set(containingFrame);
+ mTmpToClipRect.set(containingFrame);
+ mTmpToClipRect.bottom = (mTmpToClipRect.top + unscaledThumbHeight);
+ } else {
+ // In landscape, we scale the height and clip to the top/left square
+ scale = thumbHeight / (appHeight - contentInsets.top);
+ scaledTopDecor = (int) (scale * contentInsets.top);
+ int unscaledThumbWidth = (int) (thumbWidth / scale);
+ mTmpFromClipRect.set(containingFrame);
+ mTmpToClipRect.set(containingFrame);
+ mTmpToClipRect.right = (mTmpToClipRect.left + unscaledThumbWidth);
+ }
+
+ Animation scaleAnim = new ScaleAnimation(1, scale, 1, scale,
+ computePivot(mNextAppTransitionStartX, scale),
+ computePivot(mNextAppTransitionStartY, scale));
+ Animation alphaAnim = new AlphaAnimation(1, 1);
+ Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
+ Animation translateAnim = new TranslateAnimation(0, 0, 0, -scaledTopDecor);
+
+ AnimationSet set = new AnimationSet(true);
+ set.addAnimation(alphaAnim);
+ set.addAnimation(clipAnim);
+ set.addAnimation(scaleAnim);
+ set.addAnimation(translateAnim);
+
+ a = set;
+ a.setZAdjustment(Animation.ZORDER_TOP);
+ break;
+ }
+ default:
+ throw new RuntimeException("Invalid thumbnail transition state");
+ }
+
+ return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight,
+ THUMBNAIL_APP_TRANSITION_DURATION, mThumbnailCubicInterpolator);
+ }
+
+ /**
+ * This animation is created when we are doing a thumbnail transition, for the activity that is
+ * leaving, and the activity that is entering.
+ */
+ Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, int appWidth,
+ int appHeight, int transit) {
+ Animation a;
+ final int thumbWidthI = mNextAppTransitionThumbnail.getWidth();
+ final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
+ final int thumbHeightI = mNextAppTransitionThumbnail.getHeight();
+ final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
+
+ switch (thumbTransitState) {
+ case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
+ // Entering app scales up with the thumbnail
float scaleW = thumbWidth / appWidth;
float scaleH = thumbHeight / appHeight;
a = new ScaleAnimation(scaleW, 1, scaleH, 1,
computePivot(mNextAppTransitionStartX, scaleW),
computePivot(mNextAppTransitionStartY, scaleH));
- } else {
- // noop animation
- a = new AlphaAnimation(1, 1);
+ break;
}
- } else {
- // Exiting app
- if (mNextAppTransitionScaleUp) {
+ case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
+ // Exiting app while the thumbnail is scaling up should fade or stay in place
if (transit == TRANSIT_WALLPAPER_INTRA_OPEN) {
// Fade out while bringing up selected activity. This keeps the
// current activity from showing through a launching wallpaper
@@ -441,7 +644,16 @@ public class AppTransition implements Dump {
// noop animation
a = new AlphaAnimation(1, 1);
}
- } else {
+ break;
+ }
+ case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: {
+ // Entering the other app, it should just be visible while we scale the thumbnail
+ // down above it
+ a = new AlphaAnimation(1, 1);
+ break;
+ }
+ case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
+ // Exiting the current app, the app should scale down with the thumbnail
float scaleW = thumbWidth / appWidth;
float scaleH = thumbHeight / appHeight;
Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
@@ -455,32 +667,19 @@ public class AppTransition implements Dump {
set.addAnimation(alpha);
set.setZAdjustment(Animation.ZORDER_TOP);
a = set;
- }
- }
-
- // Pick the desired duration. If this is an inter-activity transition,
- // it is the standard duration for that. Otherwise we use the longer
- // task transition duration.
- final long duration;
- switch (transit) {
- case TRANSIT_ACTIVITY_OPEN:
- case TRANSIT_ACTIVITY_CLOSE:
- duration = mConfigShortAnimTime;
break;
+ }
default:
- duration = DEFAULT_APP_TRANSITION_DURATION;
- break;
+ throw new RuntimeException("Invalid thumbnail transition state");
}
- a.setDuration(duration);
- a.setFillAfter(true);
- a.setInterpolator(mDecelerateInterpolator);
- a.initialize(appWidth, appHeight, appWidth, appHeight);
- return a;
+
+ return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
}
Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
- int appWidth, int appHeight) {
+ int appWidth, int appHeight, int orientation,
+ Rect containingFrame, Rect contentInsets) {
Animation a;
if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
a = loadAnimation(mNextAppTransitionPackage, enter ?
@@ -501,7 +700,9 @@ public class AppTransition implements Dump {
mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN) {
mNextAppTransitionScaleUp =
(mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
- a = createThumbnailAnimationLocked(transit, enter, false, appWidth, appHeight);
+ a = createAlternateThumbnailEnterExitAnimationLocked(
+ getThumbnailTransitionState(enter), appWidth, appHeight, orientation,
+ transit, containingFrame, contentInsets);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
String animName = mNextAppTransitionScaleUp ?
"ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN";
diff --git a/services/core/java/com/android/server/wm/DisplayMagnifier.java b/services/core/java/com/android/server/wm/DisplayMagnifier.java
deleted file mode 100644
index 382d7b4..0000000
--- a/services/core/java/com/android/server/wm/DisplayMagnifier.java
+++ /dev/null
@@ -1,756 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.app.Service;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.util.Pools.SimplePool;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.TypedValue;
-import android.view.IMagnificationCallbacks;
-import android.view.MagnificationSpec;
-import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-
-import com.android.internal.R;
-import com.android.internal.os.SomeArgs;
-
-/**
- * This class is a part of the window manager and encapsulates the
- * functionality related to display magnification.
- */
-final class DisplayMagnifier {
- private static final String LOG_TAG = DisplayMagnifier.class.getSimpleName();
-
- private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
- private static final boolean DEBUG_ROTATION = false;
- private static final boolean DEBUG_LAYERS = false;
- private static final boolean DEBUG_RECTANGLE_REQUESTED = false;
- private static final boolean DEBUG_VIEWPORT_WINDOW = false;
-
- private final Rect mTempRect1 = new Rect();
- private final Rect mTempRect2 = new Rect();
-
- private final Region mTempRegion1 = new Region();
- private final Region mTempRegion2 = new Region();
- private final Region mTempRegion3 = new Region();
- private final Region mTempRegion4 = new Region();
-
- private final Context mContext;
- private final WindowManagerService mWindowManagerService;
- private final MagnifiedViewport mMagnifedViewport;
- private final Handler mHandler;
-
- private final IMagnificationCallbacks mCallbacks;
-
- private final long mLongAnimationDuration;
-
- public DisplayMagnifier(WindowManagerService windowManagerService,
- IMagnificationCallbacks callbacks) {
- mContext = windowManagerService.mContext;
- mWindowManagerService = windowManagerService;
- mCallbacks = callbacks;
- mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
- mMagnifedViewport = new MagnifiedViewport();
- mLongAnimationDuration = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_longAnimTime);
- }
-
- public void setMagnificationSpecLocked(MagnificationSpec spec) {
- mMagnifedViewport.updateMagnificationSpecLocked(spec);
- mMagnifedViewport.recomputeBoundsLocked();
- mWindowManagerService.scheduleAnimationLocked();
- }
-
- public void onRectangleOnScreenRequestedLocked(Rect rectangle, boolean immediate) {
- if (DEBUG_RECTANGLE_REQUESTED) {
- Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle);
- }
- if (!mMagnifedViewport.isMagnifyingLocked()) {
- return;
- }
- Rect magnifiedRegionBounds = mTempRect2;
- mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(magnifiedRegionBounds);
- if (magnifiedRegionBounds.contains(rectangle)) {
- return;
- }
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = rectangle.left;
- args.argi2 = rectangle.top;
- args.argi3 = rectangle.right;
- args.argi4 = rectangle.bottom;
- mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED,
- args).sendToTarget();
- }
-
- public void onWindowLayersChangedLocked() {
- if (DEBUG_LAYERS) {
- Slog.i(LOG_TAG, "Layers changed.");
- }
- mMagnifedViewport.recomputeBoundsLocked();
- mWindowManagerService.scheduleAnimationLocked();
- }
-
- public void onRotationChangedLocked(DisplayContent displayContent, int rotation) {
- if (DEBUG_ROTATION) {
- Slog.i(LOG_TAG, "Rotaton: " + Surface.rotationToString(rotation)
- + " displayId: " + displayContent.getDisplayId());
- }
- mMagnifedViewport.onRotationChangedLocked();
- mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED);
- }
-
- public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
- if (DEBUG_WINDOW_TRANSITIONS) {
- Slog.i(LOG_TAG, "Window transition: "
- + AppTransition.appTransitionToString(transition)
- + " displayId: " + windowState.getDisplayId());
- }
- final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
- if (magnifying) {
- switch (transition) {
- case AppTransition.TRANSIT_ACTIVITY_OPEN:
- case AppTransition.TRANSIT_TASK_OPEN:
- case AppTransition.TRANSIT_TASK_TO_FRONT:
- case AppTransition.TRANSIT_WALLPAPER_OPEN:
- case AppTransition.TRANSIT_WALLPAPER_CLOSE:
- case AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN: {
- mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED);
- }
- }
- }
- }
-
- public void onWindowTransitionLocked(WindowState windowState, int transition) {
- if (DEBUG_WINDOW_TRANSITIONS) {
- Slog.i(LOG_TAG, "Window transition: "
- + AppTransition.appTransitionToString(transition)
- + " displayId: " + windowState.getDisplayId());
- }
- final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
- final int type = windowState.mAttrs.type;
- switch (transition) {
- case WindowManagerPolicy.TRANSIT_ENTER:
- case WindowManagerPolicy.TRANSIT_SHOW: {
- if (!magnifying) {
- break;
- }
- switch (type) {
- case WindowManager.LayoutParams.TYPE_APPLICATION:
- case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
- case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
- case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
- case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
- case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
- case WindowManager.LayoutParams.TYPE_PHONE:
- case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
- case WindowManager.LayoutParams.TYPE_TOAST:
- case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
- case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
- case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
- case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
- case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
- case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
- case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL:
- case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: {
- Rect magnifiedRegionBounds = mTempRect2;
- mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(
- magnifiedRegionBounds);
- Rect touchableRegionBounds = mTempRect1;
- windowState.getTouchableRegion(mTempRegion1);
- mTempRegion1.getBounds(touchableRegionBounds);
- if (!magnifiedRegionBounds.intersect(touchableRegionBounds)) {
- try {
- mCallbacks.onRectangleOnScreenRequested(
- touchableRegionBounds.left,
- touchableRegionBounds.top,
- touchableRegionBounds.right,
- touchableRegionBounds.bottom);
- } catch (RemoteException re) {
- /* ignore */
- }
- }
- } break;
- } break;
- }
- }
- }
-
- public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
- MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
- if (spec != null && !spec.isNop()) {
- WindowManagerPolicy policy = mWindowManagerService.mPolicy;
- final int windowType = windowState.mAttrs.type;
- if (!policy.isTopLevelWindow(windowType) && windowState.mAttachedWindow != null
- && !policy.canMagnifyWindow(windowType)) {
- return null;
- }
- if (!policy.canMagnifyWindow(windowState.mAttrs.type)) {
- return null;
- }
- }
- return spec;
- }
-
- public void destroyLocked() {
- mMagnifedViewport.destroyWindow();
- }
-
- /** NOTE: This has to be called within a surface transaction. */
- public void drawMagnifiedRegionBorderIfNeededLocked() {
- mMagnifedViewport.drawWindowIfNeededLocked();
- }
-
- private final class MagnifiedViewport {
-
- private static final int DEFAUTLT_BORDER_WIDTH_DIP = 5;
-
- private final SparseArray<WindowStateInfo> mTempWindowStateInfos =
- new SparseArray<WindowStateInfo>();
-
- private final float[] mTempFloats = new float[9];
-
- private final RectF mTempRectF = new RectF();
-
- private final Point mTempPoint = new Point();
-
- private final Matrix mTempMatrix = new Matrix();
-
- private final Region mMagnifiedBounds = new Region();
- private final Region mOldMagnifiedBounds = new Region();
-
- private final MagnificationSpec mMagnificationSpec = MagnificationSpec.obtain();
-
- private final WindowManager mWindowManager;
-
- private final int mBorderWidth;
- private final int mHalfBorderWidth;
-
- private final ViewportWindow mWindow;
-
- private boolean mFullRedrawNeeded;
-
- public MagnifiedViewport() {
- mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE);
- mBorderWidth = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, DEFAUTLT_BORDER_WIDTH_DIP,
- mContext.getResources().getDisplayMetrics());
- mHalfBorderWidth = (int) (mBorderWidth + 0.5) / 2;
- mWindow = new ViewportWindow(mContext);
- recomputeBoundsLocked();
- }
-
- public void updateMagnificationSpecLocked(MagnificationSpec spec) {
- if (spec != null) {
- mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY);
- } else {
- mMagnificationSpec.clear();
- }
- // If this message is pending we are in a rotation animation and do not want
- // to show the border. We will do so when the pending message is handled.
- if (!mHandler.hasMessages(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
- setMagnifiedRegionBorderShownLocked(isMagnifyingLocked(), true);
- }
- }
-
- public void recomputeBoundsLocked() {
- mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
- final int screenWidth = mTempPoint.x;
- final int screenHeight = mTempPoint.y;
-
- Region magnifiedBounds = mMagnifiedBounds;
- magnifiedBounds.set(0, 0, 0, 0);
-
- Region availableBounds = mTempRegion1;
- availableBounds.set(0, 0, screenWidth, screenHeight);
-
- Region nonMagnifiedBounds = mTempRegion4;
- nonMagnifiedBounds.set(0, 0, 0, 0);
-
- SparseArray<WindowStateInfo> visibleWindows = mTempWindowStateInfos;
- visibleWindows.clear();
- getWindowsOnScreenLocked(visibleWindows);
-
- final int visibleWindowCount = visibleWindows.size();
- for (int i = visibleWindowCount - 1; i >= 0; i--) {
- WindowStateInfo info = visibleWindows.valueAt(i);
- if (info.mWindowState.mAttrs.type == WindowManager
- .LayoutParams.TYPE_MAGNIFICATION_OVERLAY) {
- continue;
- }
-
- Region windowBounds = mTempRegion2;
- Matrix matrix = mTempMatrix;
- populateTransformationMatrix(info.mWindowState, matrix);
- RectF windowFrame = mTempRectF;
-
- if (mWindowManagerService.mPolicy.canMagnifyWindow(info.mWindowState.mAttrs.type)) {
- windowFrame.set(info.mWindowState.mFrame);
- windowFrame.offset(-windowFrame.left, -windowFrame.top);
- matrix.mapRect(windowFrame);
- windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
- (int) windowFrame.right, (int) windowFrame.bottom);
- magnifiedBounds.op(windowBounds, Region.Op.UNION);
- magnifiedBounds.op(availableBounds, Region.Op.INTERSECT);
- } else {
- windowFrame.set(info.mTouchableRegion);
- windowFrame.offset(-info.mWindowState.mFrame.left,
- -info.mWindowState.mFrame.top);
- matrix.mapRect(windowFrame);
- windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
- (int) windowFrame.right, (int) windowFrame.bottom);
- nonMagnifiedBounds.op(windowBounds, Region.Op.UNION);
- windowBounds.op(magnifiedBounds, Region.Op.DIFFERENCE);
- availableBounds.op(windowBounds, Region.Op.DIFFERENCE);
- }
-
- Region accountedBounds = mTempRegion2;
- accountedBounds.set(magnifiedBounds);
- accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION);
- accountedBounds.op(0, 0, screenWidth, screenHeight, Region.Op.INTERSECT);
-
- if (accountedBounds.isRect()) {
- Rect accountedFrame = mTempRect1;
- accountedBounds.getBounds(accountedFrame);
- if (accountedFrame.width() == screenWidth
- && accountedFrame.height() == screenHeight) {
- break;
- }
- }
- }
-
- for (int i = visibleWindowCount - 1; i >= 0; i--) {
- WindowStateInfo info = visibleWindows.valueAt(i);
- info.recycle();
- visibleWindows.removeAt(i);
- }
-
- magnifiedBounds.op(mHalfBorderWidth, mHalfBorderWidth,
- screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth,
- Region.Op.INTERSECT);
-
- if (!mOldMagnifiedBounds.equals(magnifiedBounds)) {
- Region bounds = Region.obtain();
- bounds.set(magnifiedBounds);
- mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED,
- bounds).sendToTarget();
-
- mWindow.setBounds(magnifiedBounds);
- Rect dirtyRect = mTempRect1;
- if (mFullRedrawNeeded) {
- mFullRedrawNeeded = false;
- dirtyRect.set(mHalfBorderWidth, mHalfBorderWidth,
- screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth);
- mWindow.invalidate(dirtyRect);
- } else {
- Region dirtyRegion = mTempRegion3;
- dirtyRegion.set(magnifiedBounds);
- dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION);
- dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
- dirtyRegion.getBounds(dirtyRect);
- mWindow.invalidate(dirtyRect);
- }
-
- mOldMagnifiedBounds.set(magnifiedBounds);
- }
- }
-
- private void populateTransformationMatrix(WindowState windowState, Matrix outMatrix) {
- mTempFloats[Matrix.MSCALE_X] = windowState.mWinAnimator.mDsDx;
- mTempFloats[Matrix.MSKEW_Y] = windowState.mWinAnimator.mDtDx;
- mTempFloats[Matrix.MSKEW_X] = windowState.mWinAnimator.mDsDy;
- mTempFloats[Matrix.MSCALE_Y] = windowState.mWinAnimator.mDtDy;
- mTempFloats[Matrix.MTRANS_X] = windowState.mShownFrame.left;
- mTempFloats[Matrix.MTRANS_Y] = windowState.mShownFrame.top;
- mTempFloats[Matrix.MPERSP_0] = 0;
- mTempFloats[Matrix.MPERSP_1] = 0;
- mTempFloats[Matrix.MPERSP_2] = 1;
- outMatrix.setValues(mTempFloats);
- }
-
- private void getWindowsOnScreenLocked(SparseArray<WindowStateInfo> outWindowStates) {
- DisplayContent displayContent = mWindowManagerService.getDefaultDisplayContentLocked();
- WindowList windowList = displayContent.getWindowList();
- final int windowCount = windowList.size();
- for (int i = 0; i < windowCount; i++) {
- WindowState windowState = windowList.get(i);
- if ((windowState.isOnScreen() || windowState.mAttrs.type == WindowManager
- .LayoutParams.TYPE_UNIVERSE_BACKGROUND)
- && !windowState.mWinAnimator.mEnterAnimationPending) {
- outWindowStates.put(windowState.mLayer, WindowStateInfo.obtain(windowState));
- }
- }
- }
-
- public void onRotationChangedLocked() {
- // If we are magnifying, hide the magnified border window immediately so
- // the user does not see strange artifacts during rotation. The screenshot
- // used for rotation has already the border. After the rotation is complete
- // we will show the border.
- if (isMagnifyingLocked()) {
- setMagnifiedRegionBorderShownLocked(false, false);
- final long delay = (long) (mLongAnimationDuration
- * mWindowManagerService.mWindowAnimationScale);
- Message message = mHandler.obtainMessage(
- MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
- mHandler.sendMessageDelayed(message, delay);
- }
- recomputeBoundsLocked();
- mWindow.updateSize();
- }
-
- public void setMagnifiedRegionBorderShownLocked(boolean shown, boolean animate) {
- if (shown) {
- mFullRedrawNeeded = true;
- mOldMagnifiedBounds.set(0, 0, 0, 0);
- }
- mWindow.setShown(shown, animate);
- }
-
- public void getMagnifiedFrameInContentCoordsLocked(Rect rect) {
- MagnificationSpec spec = mMagnificationSpec;
- mMagnifiedBounds.getBounds(rect);
- rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
- rect.scale(1.0f / spec.scale);
- }
-
- public boolean isMagnifyingLocked() {
- return mMagnificationSpec.scale > 1.0f;
- }
-
- public MagnificationSpec getMagnificationSpecLocked() {
- return mMagnificationSpec;
- }
-
- /** NOTE: This has to be called within a surface transaction. */
- public void drawWindowIfNeededLocked() {
- recomputeBoundsLocked();
- mWindow.drawIfNeeded();
- }
-
- public void destroyWindow() {
- mWindow.releaseSurface();
- }
-
- private final class ViewportWindow {
- private static final String SURFACE_TITLE = "Magnification Overlay";
-
- private static final String PROPERTY_NAME_ALPHA = "alpha";
-
- private static final int MIN_ALPHA = 0;
- private static final int MAX_ALPHA = 255;
-
- private final Region mBounds = new Region();
- private final Rect mDirtyRect = new Rect();
- private final Paint mPaint = new Paint();
-
- private final ValueAnimator mShowHideFrameAnimator;
- private final SurfaceControl mSurfaceControl;
- private final Surface mSurface = new Surface();
-
- private boolean mShown;
- private int mAlpha;
-
- private boolean mInvalidated;
-
- public ViewportWindow(Context context) {
- SurfaceControl surfaceControl = null;
- try {
- mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
- surfaceControl = new SurfaceControl(mWindowManagerService.mFxSession, SURFACE_TITLE,
- mTempPoint.x, mTempPoint.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
- } catch (OutOfResourcesException oore) {
- /* ignore */
- }
- mSurfaceControl = surfaceControl;
- mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay().getLayerStack());
- mSurfaceControl.setLayer(mWindowManagerService.mPolicy.windowTypeToLayerLw(
- WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY)
- * WindowManagerService.TYPE_LAYER_MULTIPLIER);
- mSurfaceControl.setPosition(0, 0);
- mSurface.copyFrom(mSurfaceControl);
-
- TypedValue typedValue = new TypedValue();
- context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight,
- typedValue, true);
- final int borderColor = context.getResources().getColor(typedValue.resourceId);
-
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeWidth(mBorderWidth);
- mPaint.setColor(borderColor);
-
- Interpolator interpolator = new DecelerateInterpolator(2.5f);
- final long longAnimationDuration = context.getResources().getInteger(
- com.android.internal.R.integer.config_longAnimTime);
-
- mShowHideFrameAnimator = ObjectAnimator.ofInt(this, PROPERTY_NAME_ALPHA,
- MIN_ALPHA, MAX_ALPHA);
- mShowHideFrameAnimator.setInterpolator(interpolator);
- mShowHideFrameAnimator.setDuration(longAnimationDuration);
- mInvalidated = true;
- }
-
- public void setShown(boolean shown, boolean animate) {
- synchronized (mWindowManagerService.mWindowMap) {
- if (mShown == shown) {
- return;
- }
- mShown = shown;
- if (animate) {
- if (mShowHideFrameAnimator.isRunning()) {
- mShowHideFrameAnimator.reverse();
- } else {
- if (shown) {
- mShowHideFrameAnimator.start();
- } else {
- mShowHideFrameAnimator.reverse();
- }
- }
- } else {
- mShowHideFrameAnimator.cancel();
- if (shown) {
- setAlpha(MAX_ALPHA);
- } else {
- setAlpha(MIN_ALPHA);
- }
- }
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown);
- }
- }
- }
-
- @SuppressWarnings("unused")
- // Called reflectively from an animator.
- public int getAlpha() {
- synchronized (mWindowManagerService.mWindowMap) {
- return mAlpha;
- }
- }
-
- public void setAlpha(int alpha) {
- synchronized (mWindowManagerService.mWindowMap) {
- if (mAlpha == alpha) {
- return;
- }
- mAlpha = alpha;
- invalidate(null);
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "ViewportWindow set alpha: " + alpha);
- }
- }
- }
-
- public void setBounds(Region bounds) {
- synchronized (mWindowManagerService.mWindowMap) {
- if (mBounds.equals(bounds)) {
- return;
- }
- mBounds.set(bounds);
- invalidate(mDirtyRect);
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "ViewportWindow set bounds: " + bounds);
- }
- }
- }
-
- public void updateSize() {
- synchronized (mWindowManagerService.mWindowMap) {
- mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
- mSurfaceControl.setSize(mTempPoint.x, mTempPoint.y);
- invalidate(mDirtyRect);
- }
- }
-
- public void invalidate(Rect dirtyRect) {
- if (dirtyRect != null) {
- mDirtyRect.set(dirtyRect);
- } else {
- mDirtyRect.setEmpty();
- }
- mInvalidated = true;
- mWindowManagerService.scheduleAnimationLocked();
- }
-
- /** NOTE: This has to be called within a surface transaction. */
- public void drawIfNeeded() {
- synchronized (mWindowManagerService.mWindowMap) {
- if (!mInvalidated) {
- return;
- }
- mInvalidated = false;
- Canvas canvas = null;
- try {
- // Empty dirty rectangle means unspecified.
- if (mDirtyRect.isEmpty()) {
- mBounds.getBounds(mDirtyRect);
- }
- mDirtyRect.inset(- mHalfBorderWidth, - mHalfBorderWidth);
- canvas = mSurface.lockCanvas(mDirtyRect);
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect);
- }
- } catch (IllegalArgumentException iae) {
- /* ignore */
- } catch (Surface.OutOfResourcesException oore) {
- /* ignore */
- }
- if (canvas == null) {
- return;
- }
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "Bounds: " + mBounds);
- }
- canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
- mPaint.setAlpha(mAlpha);
- Path path = mBounds.getBoundaryPath();
- canvas.drawPath(path, mPaint);
-
- mSurface.unlockCanvasAndPost(canvas);
-
- if (mAlpha > 0) {
- mSurfaceControl.show();
- } else {
- mSurfaceControl.hide();
- }
- }
- }
-
- public void releaseSurface() {
- mSurfaceControl.release();
- mSurface.release();
- }
- }
- }
-
- private static final class WindowStateInfo {
- private static final int MAX_POOL_SIZE = 30;
-
- private static final SimplePool<WindowStateInfo> sPool =
- new SimplePool<WindowStateInfo>(MAX_POOL_SIZE);
-
- private static final Region mTempRegion = new Region();
-
- public WindowState mWindowState;
- public final Rect mTouchableRegion = new Rect();
-
- public static WindowStateInfo obtain(WindowState windowState) {
- WindowStateInfo info = sPool.acquire();
- if (info == null) {
- info = new WindowStateInfo();
- }
- info.mWindowState = windowState;
- windowState.getTouchableRegion(mTempRegion);
- mTempRegion.getBounds(info.mTouchableRegion);
- return info;
- }
-
- public void recycle() {
- mWindowState = null;
- mTouchableRegion.setEmpty();
- sPool.release(this);
- }
- }
-
- private class MyHandler extends Handler {
- public static final int MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED = 1;
- public static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2;
- public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3;
- public static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4;
- public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
-
- public MyHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED: {
- Region bounds = (Region) message.obj;
- try {
- mCallbacks.onMagnifedBoundsChanged(bounds);
- } catch (RemoteException re) {
- /* ignore */
- } finally {
- bounds.recycle();
- }
- } break;
- case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
- SomeArgs args = (SomeArgs) message.obj;
- final int left = args.argi1;
- final int top = args.argi2;
- final int right = args.argi3;
- final int bottom = args.argi4;
- try {
- mCallbacks.onRectangleOnScreenRequested(left, top, right, bottom);
- } catch (RemoteException re) {
- /* ignore */
- } finally {
- args.recycle();
- }
- } break;
- case MESSAGE_NOTIFY_USER_CONTEXT_CHANGED: {
- try {
- mCallbacks.onUserContextChanged();
- } catch (RemoteException re) {
- /* ignore */
- }
- } break;
- case MESSAGE_NOTIFY_ROTATION_CHANGED: {
- final int rotation = message.arg1;
- try {
- mCallbacks.onRotationChanged(rotation);
- } catch (RemoteException re) {
- /* ignore */
- }
- } break;
- case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
- synchronized (mWindowManagerService.mWindowMap) {
- if (mMagnifedViewport.isMagnifyingLocked()) {
- mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true);
- mWindowManagerService.scheduleAnimationLocked();
- }
- }
- } break;
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index a737939..edc7c93 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -33,7 +33,6 @@ import android.util.Slog;
import android.view.Display;
import android.view.DragEvent;
import android.view.InputChannel;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
diff --git a/services/core/java/com/android/server/wm/FakeWindowImpl.java b/services/core/java/com/android/server/wm/FakeWindowImpl.java
index 5a3471b..c18ea01 100644
--- a/services/core/java/com/android/server/wm/FakeWindowImpl.java
+++ b/services/core/java/com/android/server/wm/FakeWindowImpl.java
@@ -21,11 +21,9 @@ import com.android.server.input.InputWindowHandle;
import android.os.Looper;
import android.os.Process;
-import android.util.Slog;
import android.view.Display;
import android.view.InputChannel;
import android.view.InputEventReceiver;
-import android.view.InputQueue;
import android.view.WindowManagerPolicy;
public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index b27c8d6..f02c0e6 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -284,7 +284,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
final boolean hasFocus = (child == mInputFocus);
final boolean isVisible = child.isVisibleLw();
final boolean hasWallpaper = (child == mService.mWallpaperTarget)
- && (type != WindowManager.LayoutParams.TYPE_KEYGUARD);
+ && (privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD) == 0;
final boolean onDefaultDisplay = (child.getDisplayId() == Display.DEFAULT_DISPLAY);
// If there's a drag in progress and 'child' is a potential drop target,
diff --git a/services/core/java/com/android/server/wm/KeyguardDisableHandler.java b/services/core/java/com/android/server/wm/KeyguardDisableHandler.java
index 859df51..c1420a8 100644
--- a/services/core/java/com/android/server/wm/KeyguardDisableHandler.java
+++ b/services/core/java/com/android/server/wm/KeyguardDisableHandler.java
@@ -24,7 +24,6 @@ import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.os.TokenWatcher;
-import android.os.UserHandle;
import android.util.Log;
import android.util.Pair;
import android.view.WindowManagerPolicy;
diff --git a/services/core/java/com/android/server/wm/ViewServer.java b/services/core/java/com/android/server/wm/ViewServer.java
index a763e2c..741cee3 100644
--- a/services/core/java/com/android/server/wm/ViewServer.java
+++ b/services/core/java/com/android/server/wm/ViewServer.java
@@ -314,7 +314,7 @@ class ViewServer implements Runnable {
out.flush();
}
if (needFocusedWindowUpdate) {
- out.write("FOCUS UPDATE\n");
+ out.write("ACTION_FOCUS UPDATE\n");
out.flush();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 0c68258..266527d 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -529,8 +529,9 @@ public class WindowAnimator {
mAnimating |= mService.getDisplayContentLocked(displayId).animateDimLayers();
//TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mDisplayMagnifier != null && displayId == Display.DEFAULT_DISPLAY) {
- mService.mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
+ if (mService.mAccessibilityController != null
+ && displayId == Display.DEFAULT_DISPLAY) {
+ mService.mAccessibilityController.drawMagnifiedRegionBorderIfNeededLocked();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6cdbdeb..c6fffbf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -19,12 +19,12 @@ package com.android.server.wm;
import static android.view.WindowManager.LayoutParams.*;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-
import android.app.AppOpsManager;
import android.util.ArraySet;
import android.util.TimeUtils;
import android.view.IWindowId;
+import android.view.WindowContentFrameStats;
import com.android.internal.app.IBatteryStats;
import com.android.internal.policy.PolicyManager;
import com.android.internal.policy.impl.PhoneWindowManager;
@@ -87,6 +87,7 @@ import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
+import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
import android.util.DisplayMetrics;
@@ -104,7 +105,6 @@ import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.IApplicationToken;
import android.view.IInputFilter;
-import android.view.IMagnificationCallbacks;
import android.view.IOnKeyguardExitResult;
import android.view.IRotationWatcher;
import android.view.IWindow;
@@ -257,9 +257,6 @@ public class WindowManagerService extends IWindowManager.Stub
/** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */
static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000;
- /** Amount of time (in milliseconds) to delay before declaring a starting window leaked. */
- static final int STARTING_WINDOW_TIMEOUT_DURATION = 10000;
-
/**
* If true, the window manager will do its own custom freezing and general
* management of the screen during rotation.
@@ -305,8 +302,16 @@ public class WindowManagerService extends IWindowManager.Stub
}
};
- // Current user when multi-user is enabled. Don't show windows of non-current user.
+ /**
+ * Current user when multi-user is enabled. Don't show windows of
+ * non-current user. Also see mCurrentProfileIds.
+ */
int mCurrentUserId;
+ /**
+ * Users that are profiles of the current user. These are also allowed to show windows
+ * on the current user.
+ */
+ int[] mCurrentProfileIds = new int[] {UserHandle.USER_OWNER};
final Context mContext;
@@ -414,7 +419,7 @@ public class WindowManagerService extends IWindowManager.Stub
IInputMethodManager mInputMethodManager;
- DisplayMagnifier mDisplayMagnifier;
+ AccessibilityController mAccessibilityController;
final SurfaceSession mFxSession;
Watermark mWatermark;
@@ -444,10 +449,10 @@ public class WindowManagerService extends IWindowManager.Stub
boolean mAltOrientation = false;
class RotationWatcher {
IRotationWatcher watcher;
- IBinder.DeathRecipient dr;
+ IBinder.DeathRecipient deathRecipient;
RotationWatcher(IRotationWatcher w, IBinder.DeathRecipient d) {
watcher = w;
- dr = d;
+ deathRecipient = d;
}
}
ArrayList<RotationWatcher> mRotationWatchers = new ArrayList<RotationWatcher>();
@@ -620,6 +625,8 @@ public class WindowManagerService extends IWindowManager.Stub
private final PointerEventDispatcher mPointerEventDispatcher;
+ private WindowContentFrameStats mTempWindowRenderStats;
+
final class DragInputEventReceiver extends InputEventReceiver {
public DragInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
@@ -1884,7 +1891,9 @@ public class WindowManagerService extends IWindowManager.Stub
int insertionIndex = 0;
if (visible && foundW != null) {
final int type = foundW.mAttrs.type;
- if (type == TYPE_KEYGUARD || type == TYPE_KEYGUARD_SCRIM) {
+ final int privateFlags = foundW.mAttrs.privateFlags;
+ if ((privateFlags & PRIVATE_FLAG_KEYGUARD) != 0
+ || type == TYPE_KEYGUARD_SCRIM) {
insertionIndex = windows.indexOf(foundW);
}
}
@@ -2305,8 +2314,6 @@ public class WindowManagerService extends IWindowManager.Stub
token.appWindowToken.startingWindow = win;
if (DEBUG_STARTING_WINDOW) Slog.v (TAG, "addWindow: " + token.appWindowToken
+ " startingWindow=" + win);
- Message m = mH.obtainMessage(H.REMOVE_STARTING_TIMEOUT, token.appWindowToken);
- mH.sendMessageDelayed(m, STARTING_WINDOW_TIMEOUT_DURATION);
}
boolean imMayMove = true;
@@ -2409,7 +2416,6 @@ public class WindowManagerService extends IWindowManager.Stub
public void removeWindowLocked(Session session, WindowState win) {
if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "Starting window removed " + win);
- removeStartingWindowTimeout(win.mAppToken);
}
if (localLOGV || DEBUG_FOCUS || DEBUG_FOCUS_LIGHT && win==mCurrentFocus) Slog.v(
@@ -2452,9 +2458,9 @@ public class WindowManagerService extends IWindowManager.Stub
win.mExiting = true;
}
//TODO (multidisplay): Magnification is supported only for the default display.
- if (mDisplayMagnifier != null
+ if (mAccessibilityController != null
&& win.getDisplayId() == Display.DEFAULT_DISPLAY) {
- mDisplayMagnifier.onWindowTransitionLocked(win, transit);
+ mAccessibilityController.onWindowTransitionLocked(win, transit);
}
}
if (win.mExiting || win.mWinAnimator.isAnimating()) {
@@ -2554,7 +2560,6 @@ public class WindowManagerService extends IWindowManager.Stub
if (atoken != null) {
if (atoken.startingWindow == win) {
if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Nulling startingWindow " + win);
- removeStartingWindowTimeout(atoken);
atoken.startingWindow = null;
} else if (atoken.allAppWindows.size() == 0 && atoken.startingData != null) {
// If this is the last window and we had requested a starting
@@ -2773,11 +2778,12 @@ public class WindowManagerService extends IWindowManager.Stub
public void onRectangleOnScreenRequested(IBinder token, Rect rectangle, boolean immediate) {
synchronized (mWindowMap) {
- if (mDisplayMagnifier != null) {
+ if (mAccessibilityController != null) {
WindowState window = mWindowMap.get(token);
//TODO (multidisplay): Magnification is supported only for the default display.
if (window != null && window.getDisplayId() == Display.DEFAULT_DISPLAY) {
- mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle, immediate);
+ mAccessibilityController.onRectangleOnScreenRequestedLocked(rectangle,
+ immediate);
}
}
}
@@ -2800,18 +2806,10 @@ public class WindowManagerService extends IWindowManager.Stub
boolean configChanged;
boolean surfaceChanged = false;
boolean animating;
+ boolean hasStatusBarPermission =
+ mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
+ == PackageManager.PERMISSION_GRANTED;
- // if they don't have this permission, mask out the status bar bits
- int systemUiVisibility = 0;
- if (attrs != null) {
- systemUiVisibility = (attrs.systemUiVisibility|attrs.subtreeSystemUiVisibility);
- if ((systemUiVisibility & StatusBarManager.DISABLE_MASK) != 0) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
- != PackageManager.PERMISSION_GRANTED) {
- systemUiVisibility &= ~StatusBarManager.DISABLE_MASK;
- }
- }
- }
long origId = Binder.clearCallingIdentity();
synchronized(mWindowMap) {
@@ -2826,14 +2824,26 @@ public class WindowManagerService extends IWindowManager.Stub
win.mRequestedWidth = requestedWidth;
win.mRequestedHeight = requestedHeight;
}
- if (attrs != null && seq == win.mSeq) {
- win.mSystemUiVisibility = systemUiVisibility;
- }
if (attrs != null) {
mPolicy.adjustWindowParamsLw(attrs);
}
+ // if they don't have the permission, mask out the status bar bits
+ int systemUiVisibility = 0;
+ if (attrs != null) {
+ systemUiVisibility = (attrs.systemUiVisibility|attrs.subtreeSystemUiVisibility);
+ if ((systemUiVisibility & StatusBarManager.DISABLE_MASK) != 0) {
+ if (!hasStatusBarPermission) {
+ systemUiVisibility &= ~StatusBarManager.DISABLE_MASK;
+ }
+ }
+ }
+
+ if (attrs != null && seq == win.mSeq) {
+ win.mSystemUiVisibility = systemUiVisibility;
+ }
+
winAnimator.mSurfaceDestroyDeferred =
(flags&WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY) != 0;
@@ -3012,9 +3022,9 @@ public class WindowManagerService extends IWindowManager.Stub
winAnimator.destroySurfaceLocked();
}
//TODO (multidisplay): Magnification is supported only for the default
- if (mDisplayMagnifier != null
+ if (mAccessibilityController != null
&& win.getDisplayId() == Display.DEFAULT_DISPLAY) {
- mDisplayMagnifier.onWindowTransitionLocked(win, transit);
+ mAccessibilityController.onWindowTransitionLocked(win, transit);
}
}
}
@@ -3157,86 +3167,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- @Override
- public void getWindowFrame(IBinder token, Rect outBounds) {
- if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
- "getWindowInfo()")) {
- throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission.");
- }
- synchronized (mWindowMap) {
- WindowState windowState = mWindowMap.get(token);
- if (windowState != null) {
- outBounds.set(windowState.mFrame);
- } else {
- outBounds.setEmpty();
- }
- }
- }
-
- @Override
- public void setMagnificationSpec(MagnificationSpec spec) {
- if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY,
- "setMagnificationSpec()")) {
- throw new SecurityException("Requires MAGNIFY_DISPLAY permission.");
- }
- synchronized (mWindowMap) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.setMagnificationSpecLocked(spec);
- } else {
- throw new IllegalStateException("Magnification callbacks not set!");
- }
- }
- if (Binder.getCallingPid() != android.os.Process.myPid()) {
- spec.recycle();
- }
- }
-
- @Override
- public MagnificationSpec getCompatibleMagnificationSpecForWindow(IBinder windowToken) {
- if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY,
- "getCompatibleMagnificationSpecForWindow()")) {
- throw new SecurityException("Requires MAGNIFY_DISPLAY permission.");
- }
- synchronized (mWindowMap) {
- WindowState windowState = mWindowMap.get(windowToken);
- if (windowState == null) {
- return null;
- }
- MagnificationSpec spec = null;
- if (mDisplayMagnifier != null) {
- spec = mDisplayMagnifier.getMagnificationSpecForWindowLocked(windowState);
- }
- if ((spec == null || spec.isNop()) && windowState.mGlobalScale == 1.0f) {
- return null;
- }
- spec = (spec == null) ? MagnificationSpec.obtain() : MagnificationSpec.obtain(spec);
- spec.scale *= windowState.mGlobalScale;
- return spec;
- }
- }
-
- @Override
- public void setMagnificationCallbacks(IMagnificationCallbacks callbacks) {
- if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY,
- "setMagnificationCallbacks()")) {
- throw new SecurityException("Requires MAGNIFY_DISPLAY permission.");
- }
- synchronized (mWindowMap) {
- if (mDisplayMagnifier == null) {
- mDisplayMagnifier = new DisplayMagnifier(this, callbacks);
- } else {
- if (callbacks == null) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.destroyLocked();
- mDisplayMagnifier = null;
- }
- } else {
- throw new IllegalStateException("Magnification callbacks already set!");
- }
- }
- }
- }
-
private boolean applyAnimationLocked(AppWindowToken atoken,
WindowManager.LayoutParams lp, int transit, boolean enter) {
// Only apply an animation if the display isn't frozen. If it is
@@ -3249,7 +3179,22 @@ public class WindowManagerService extends IWindowManager.Stub
final int height = displayInfo.appHeight;
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, "applyAnimation: atoken="
+ atoken);
- Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height);
+
+ // Determine the visible rect to calculate the thumbnail clip
+ WindowState win = atoken.findMainWindow();
+ Rect containingFrame = new Rect(0, 0, width, height);
+ Rect contentInsets = new Rect();
+ if (win != null) {
+ if (win.mContainingFrame != null) {
+ containingFrame.set(win.mContainingFrame);
+ }
+ if (win.mContentInsets != null) {
+ contentInsets.set(win.mContentInsets);
+ }
+ }
+
+ Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height,
+ mCurConfiguration.orientation, containingFrame, contentInsets);
if (a != null) {
if (DEBUG_ANIM) {
RuntimeException e = null;
@@ -3416,8 +3361,8 @@ public class WindowManagerService extends IWindowManager.Stub
win.mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT,
false);
//TODO (multidisplay): Magnification is supported only for the default
- if (mDisplayMagnifier != null && win.isDefaultDisplay()) {
- mDisplayMagnifier.onWindowTransitionLocked(win,
+ if (mAccessibilityController != null && win.isDefaultDisplay()) {
+ mAccessibilityController.onWindowTransitionLocked(win,
WindowManagerPolicy.TRANSIT_EXIT);
}
changed = true;
@@ -3902,22 +3847,22 @@ public class WindowManagerService extends IWindowManager.Stub
+ " " + mAppTransition
+ " alwaysKeepCurrent=" + alwaysKeepCurrent
+ " Callers=" + Debug.getCallers(3));
- if (okToDisplay()) {
- if (!mAppTransition.isTransitionSet() || mAppTransition.isTransitionNone()) {
+ if (!mAppTransition.isTransitionSet() || mAppTransition.isTransitionNone()) {
+ mAppTransition.setAppTransition(transit);
+ } else if (!alwaysKeepCurrent) {
+ if (transit == AppTransition.TRANSIT_TASK_OPEN
+ && mAppTransition.isTransitionEqual(
+ AppTransition.TRANSIT_TASK_CLOSE)) {
+ // Opening a new task always supersedes a close for the anim.
+ mAppTransition.setAppTransition(transit);
+ } else if (transit == AppTransition.TRANSIT_ACTIVITY_OPEN
+ && mAppTransition.isTransitionEqual(
+ AppTransition.TRANSIT_ACTIVITY_CLOSE)) {
+ // Opening a new activity always supersedes a close for the anim.
mAppTransition.setAppTransition(transit);
- } else if (!alwaysKeepCurrent) {
- if (transit == AppTransition.TRANSIT_TASK_OPEN
- && mAppTransition.isTransitionEqual(
- AppTransition.TRANSIT_TASK_CLOSE)) {
- // Opening a new task always supersedes a close for the anim.
- mAppTransition.setAppTransition(transit);
- } else if (transit == AppTransition.TRANSIT_ACTIVITY_OPEN
- && mAppTransition.isTransitionEqual(
- AppTransition.TRANSIT_ACTIVITY_CLOSE)) {
- // Opening a new activity always supersedes a close for the anim.
- mAppTransition.setAppTransition(transit);
- }
}
+ }
+ if (okToDisplay()) {
mAppTransition.prepare();
mStartingIconInTransition = false;
mSkipAppTransitionAnimation = false;
@@ -4049,7 +3994,6 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) {
Slog.v(TAG, "Removing starting window: " + startingWindow);
}
- removeStartingWindowTimeout(ttoken);
startingWindow.getWindowList().remove(startingWindow);
mWindowsChanged = true;
if (DEBUG_ADD_REMOVE) Slog.v(TAG,
@@ -4196,6 +4140,15 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ public void removeAppStartingWindow(IBinder token) {
+ synchronized (mWindowMap) {
+ AppWindowToken wtoken = mTokenMap.get(token).appWindowToken;
+ if (wtoken.startingWindow != null) {
+ scheduleRemoveStartingWindow(wtoken);
+ }
+ }
+ }
+
@Override
public void setAppWillBeHidden(IBinder token) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
@@ -4265,9 +4218,9 @@ public class WindowManagerService extends IWindowManager.Stub
}
WindowState window = wtoken.findMainWindow();
//TODO (multidisplay): Magnification is supported only for the default display.
- if (window != null && mDisplayMagnifier != null
+ if (window != null && mAccessibilityController != null
&& window.getDisplayId() == Display.DEFAULT_DISPLAY) {
- mDisplayMagnifier.onAppWindowTransitionLocked(window, transit);
+ mAccessibilityController.onAppWindowTransitionLocked(window, transit);
}
changed = true;
}
@@ -4287,9 +4240,9 @@ public class WindowManagerService extends IWindowManager.Stub
win.mWinAnimator.applyAnimationLocked(
WindowManagerPolicy.TRANSIT_ENTER, true);
//TODO (multidisplay): Magnification is supported only for the default
- if (mDisplayMagnifier != null
+ if (mAccessibilityController != null
&& win.getDisplayId() == Display.DEFAULT_DISPLAY) {
- mDisplayMagnifier.onWindowTransitionLocked(win,
+ mAccessibilityController.onWindowTransitionLocked(win,
WindowManagerPolicy.TRANSIT_ENTER);
}
}
@@ -4304,9 +4257,9 @@ public class WindowManagerService extends IWindowManager.Stub
win.mWinAnimator.applyAnimationLocked(
WindowManagerPolicy.TRANSIT_EXIT, false);
//TODO (multidisplay): Magnification is supported only for the default
- if (mDisplayMagnifier != null
+ if (mAccessibilityController != null
&& win.getDisplayId() == Display.DEFAULT_DISPLAY) {
- mDisplayMagnifier.onWindowTransitionLocked(win,
+ mAccessibilityController.onWindowTransitionLocked(win,
WindowManagerPolicy.TRANSIT_EXIT);
}
}
@@ -4643,21 +4596,11 @@ public class WindowManagerService extends IWindowManager.Stub
scheduleRemoveStartingWindow(startingToken);
}
- void removeStartingWindowTimeout(AppWindowToken wtoken) {
- if (wtoken != null) {
- if (DEBUG_STARTING_WINDOW) Slog.v(TAG, Debug.getCallers(1) +
- ": Remove starting window timeout " + wtoken + (wtoken != null ?
- " startingWindow=" + wtoken.startingWindow : ""));
- mH.removeMessages(H.REMOVE_STARTING_TIMEOUT, wtoken);
- }
- }
-
void scheduleRemoveStartingWindow(AppWindowToken wtoken) {
if (wtoken != null && wtoken.startingWindow != null) {
if (DEBUG_STARTING_WINDOW) Slog.v(TAG, Debug.getCallers(1) +
": Schedule remove starting " + wtoken + (wtoken != null ?
" startingWindow=" + wtoken.startingWindow : ""));
- removeStartingWindowTimeout(wtoken);
Message m = mH.obtainMessage(H.REMOVE_STARTING, wtoken);
mH.sendMessage(m);
}
@@ -5099,6 +5042,10 @@ public class WindowManagerService extends IWindowManager.Stub
throw new SecurityException("Requires DISABLE_KEYGUARD permission");
}
+ if (token == null) {
+ throw new IllegalArgumentException("token == null");
+ }
+
mKeyguardDisableHandler.sendMessage(mKeyguardDisableHandler.obtainMessage(
KeyguardDisableHandler.KEYGUARD_DISABLE, new Pair<IBinder, String>(token, tag)));
}
@@ -5110,6 +5057,10 @@ public class WindowManagerService extends IWindowManager.Stub
throw new SecurityException("Requires DISABLE_KEYGUARD permission");
}
+ if (token == null) {
+ throw new IllegalArgumentException("token == null");
+ }
+
mKeyguardDisableHandler.sendMessage(mKeyguardDisableHandler.obtainMessage(
KeyguardDisableHandler.KEYGUARD_REENABLE, token));
}
@@ -5123,6 +5074,11 @@ public class WindowManagerService extends IWindowManager.Stub
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires DISABLE_KEYGUARD permission");
}
+
+ if (callback == null) {
+ throw new IllegalArgumentException("callback == null");
+ }
+
mPolicy.exitKeyguardSecurely(new WindowManagerPolicy.OnKeyguardExitResult() {
@Override
public void onKeyguardExitResult(boolean success) {
@@ -5294,23 +5250,16 @@ public class WindowManagerService extends IWindowManager.Stub
ShutdownThread.rebootSafeMode(mContext, confirm);
}
- @Override
- public void setInputFilter(IInputFilter filter) {
- if (!checkCallingPermission(android.Manifest.permission.FILTER_EVENTS, "setInputFilter()")) {
- throw new SecurityException("Requires FILTER_EVENTS permission");
+ public void setCurrentProfileIds(final int[] currentProfileIds) {
+ synchronized (mWindowMap) {
+ mCurrentProfileIds = currentProfileIds;
}
- mInputManager.setInputFilter(filter);
- }
-
- @Override
- public void setTouchExplorationEnabled(boolean enabled) {
- mPolicy.setTouchExplorationEnabled(enabled);
}
- public void setCurrentUser(final int newUserId) {
+ public void setCurrentUser(final int newUserId, final int[] currentProfileIds) {
synchronized (mWindowMap) {
- int oldUserId = mCurrentUserId;
mCurrentUserId = newUserId;
+ mCurrentProfileIds = currentProfileIds;
mAppTransition.setCurrentUser(newUserId);
mPolicy.setCurrentUserLw(newUserId);
@@ -5325,6 +5274,15 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ /* Called by WindowState */
+ boolean isCurrentProfileLocked(int userId) {
+ if (userId == mCurrentUserId) return true;
+ for (int i = 0; i < mCurrentProfileIds.length; i++) {
+ if (mCurrentProfileIds[i] == userId) return true;
+ }
+ return false;
+ }
+
public void enableScreenAfterBoot() {
synchronized(mWindowMap) {
if (DEBUG_BOOT) {
@@ -5350,6 +5308,13 @@ public class WindowManagerService extends IWindowManager.Stub
performEnableScreen();
}
+ @Override
+ public void enableScreenIfNeeded() {
+ synchronized (mWindowMap) {
+ enableScreenIfNeededLocked();
+ }
+ }
+
void enableScreenIfNeededLocked() {
if (DEBUG_BOOT) {
RuntimeException here = new RuntimeException("here");
@@ -5414,18 +5379,6 @@ public class WindowManagerService extends IWindowManager.Stub
final int N = windows.size();
for (int i=0; i<N; i++) {
WindowState w = windows.get(i);
- if (w.mAttrs.type == TYPE_KEYGUARD) {
- // Only if there is a keyguard attached to the window manager
- // will we consider ourselves as having a keyguard. If it
- // isn't attached, we don't know if it wants to be shown or
- // hidden. If it is attached, we will say we have a keyguard
- // if the window doesn't want to be visible, because in that
- // case it explicitly doesn't want to be shown so we should
- // not delay turning the screen on for it.
- boolean vis = w.mViewVisibility == View.VISIBLE
- && w.mPolicyVisibility;
- haveKeyguard = !vis;
- }
if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
return;
}
@@ -5436,8 +5389,8 @@ public class WindowManagerService extends IWindowManager.Stub
haveApp = true;
} else if (w.mAttrs.type == TYPE_WALLPAPER) {
haveWallpaper = true;
- } else if (w.mAttrs.type == TYPE_KEYGUARD) {
- haveKeyguard = true;
+ } else if (w.mAttrs.type == TYPE_STATUS_BAR) {
+ haveKeyguard = mPolicy.isKeyguardDrawnLw();
}
}
}
@@ -5836,7 +5789,11 @@ public class WindowManagerService extends IWindowManager.Stub
+ " surfaceLayer=" + win.mWinAnimator.mSurfaceLayer);
}
}
- rawss = SurfaceControl.screenshot(dw, dh, minLayer, maxLayer);
+ // TODO: Replace 'false' in the following line with a variable that indicates
+ // whether the screenshot should use the identity transformation matrix
+ // (e.g., enable it when taking a screenshot for recents, since we might be in
+ // the middle of the rotation animation, but don't want a rotated recent image).
+ rawss = SurfaceControl.screenshot(dw, dh, minLayer, maxLayer, false);
}
} while (!screenshotReady && retryCount <= MAX_SCREENSHOT_RETRIES);
if (retryCount > MAX_SCREENSHOT_RETRIES) Slog.i(TAG, "Screenshot max retries " +
@@ -6136,9 +6093,9 @@ public class WindowManagerService extends IWindowManager.Stub
}
//TODO (multidisplay): Magnification is supported only for the default display.
- if (mDisplayMagnifier != null
+ if (mAccessibilityController != null
&& displayContent.getDisplayId() == Display.DEFAULT_DISPLAY) {
- mDisplayMagnifier.onRotationChangedLocked(getDefaultDisplayContentLocked(), rotation);
+ mAccessibilityController.onRotationChangedLocked(getDefaultDisplayContentLocked(), rotation);
}
return true;
@@ -6164,8 +6121,9 @@ public class WindowManagerService extends IWindowManager.Stub
for (int i=0; i<mRotationWatchers.size(); i++) {
if (watcherBinder == mRotationWatchers.get(i).watcher.asBinder()) {
RotationWatcher removed = mRotationWatchers.remove(i);
- if (removed != null) {
- removed.watcher.asBinder().unlinkToDeath(this, 0);
+ IBinder binder = removed.watcher.asBinder();
+ if (binder != null) {
+ binder.unlinkToDeath(this, 0);
}
i--;
}
@@ -6194,10 +6152,11 @@ public class WindowManagerService extends IWindowManager.Stub
RotationWatcher rotationWatcher = mRotationWatchers.get(i);
if (watcherBinder == rotationWatcher.watcher.asBinder()) {
RotationWatcher removed = mRotationWatchers.remove(i);
- if (removed != null) {
- removed.watcher.asBinder().unlinkToDeath(removed.dr, 0);
- i--;
+ IBinder binder = removed.watcher.asBinder();
+ if (binder != null) {
+ binder.unlinkToDeath(removed.deathRecipient, 0);
}
+ i--;
}
}
}
@@ -7042,21 +7001,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- @Override
- public IBinder getFocusedWindowToken() {
- if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
- "getFocusedWindowToken()")) {
- throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission.");
- }
- synchronized (mWindowMap) {
- WindowState windowState = getFocusedWindowLocked();
- if (windowState != null) {
- return windowState.mClient.asBinder();
- }
- return null;
- }
- }
-
private WindowState getFocusedWindow() {
synchronized (mWindowMap) {
return getFocusedWindowLocked();
@@ -7298,7 +7242,6 @@ public class WindowManagerService extends IWindowManager.Stub
"Aborted starting " + wtoken
+ ": removed=" + wtoken.removed
+ " startingData=" + wtoken.startingData);
- removeStartingWindowTimeout(wtoken);
wtoken.startingWindow = null;
wtoken.startingData = null;
abort = true;
@@ -7323,11 +7266,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
} break;
- case REMOVE_STARTING_TIMEOUT: {
- final AppWindowToken wtoken = (AppWindowToken)msg.obj;
- Slog.e(TAG, "Starting window " + wtoken + " timed out");
- // Fall through.
- }
case REMOVE_STARTING: {
final AppWindowToken wtoken = (AppWindowToken)msg.obj;
IBinder token = null;
@@ -8232,9 +8170,9 @@ public class WindowManagerService extends IWindowManager.Stub
}
//TODO (multidisplay): Magnification is supported only for the default display.
- if (mDisplayMagnifier != null && anyLayerChanged
+ if (mAccessibilityController != null && anyLayerChanged
&& windows.get(windows.size() - 1).getDisplayId() == Display.DEFAULT_DISPLAY) {
- mDisplayMagnifier.onWindowLayersChangedLocked();
+ mAccessibilityController.onWindowLayersChangedLocked();
}
}
@@ -8411,7 +8349,7 @@ public class WindowManagerService extends IWindowManager.Stub
// just don't display").
if (!gone || !win.mHaveFrame || win.mLayoutNeeded
|| ((win.isConfigChanged() || win.setInsetsChanged()) &&
- (win.mAttrs.type == TYPE_KEYGUARD ||
+ ((win.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
win.mAppToken != null && win.mAppToken.layoutConfigChanges))
|| win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) {
if (!win.mLayoutAttached) {
@@ -8739,11 +8677,12 @@ public class WindowManagerService extends IWindowManager.Stub
wtoken.deferClearAllDrawn = false;
}
+ boolean useAlternateThumbnailAnimation = true;
AppWindowAnimator appAnimator =
topOpeningApp == null ? null : topOpeningApp.mAppAnimator;
Bitmap nextAppTransitionThumbnail = mAppTransition.getNextAppTransitionThumbnail();
- if (nextAppTransitionThumbnail != null && appAnimator != null
- && appAnimator.animation != null) {
+ if (!useAlternateThumbnailAnimation && nextAppTransitionThumbnail != null
+ && appAnimator != null && appAnimator.animation != null) {
// This thumbnail animation is very special, we need to have
// an extra surface with the thumbnail included with the animation.
Rect dirty = new Rect(0, 0, nextAppTransitionThumbnail.getWidth(),
@@ -8767,8 +8706,8 @@ public class WindowManagerService extends IWindowManager.Stub
drawSurface.release();
appAnimator.thumbnailLayer = topOpeningLayer;
DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
- Animation anim = mAppTransition.createThumbnailAnimationLocked(
- transit, true, true, displayInfo.appWidth, displayInfo.appHeight);
+ Animation anim = mAppTransition.createThumbnailScaleAnimationLocked(
+ displayInfo.appWidth, displayInfo.appHeight, transit);
appAnimator.thumbnailAnimation = anim;
anim.restrictDuration(MAX_ANIMATION_DURATION);
anim.scaleCurrentDuration(mTransitionAnimationScale);
@@ -8949,8 +8888,8 @@ public class WindowManagerService extends IWindowManager.Stub
if (canBeSeen
&& (type == TYPE_SYSTEM_DIALOG
|| type == TYPE_RECENTS_OVERLAY
- || type == TYPE_KEYGUARD
- || type == TYPE_SYSTEM_ERROR)) {
+ || type == TYPE_SYSTEM_ERROR
+ || (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0)) {
mInnerFields.mSyswin = true;
}
@@ -8963,7 +8902,7 @@ public class WindowManagerService extends IWindowManager.Stub
// content on secondary displays (by forcibly enabling mirroring unless
// there is other content we want to show) but still allow opaque
// keyguard dialogs to be shown.
- if (type == TYPE_DREAM || type == TYPE_KEYGUARD) {
+ if (type == TYPE_DREAM || (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
mInnerFields.mObscureApplicationContentOnSecondaryDisplays = true;
}
mInnerFields.mDisplayHasContent = true;
@@ -9870,6 +9809,11 @@ public class WindowManagerService extends IWindowManager.Stub
final WindowState oldFocus = mCurrentFocus;
mCurrentFocus = newFocus;
mLosingFocus.remove(newFocus);
+
+ if (mAccessibilityController != null) {
+ mAccessibilityController.onWindowFocusChangedLocked();
+ }
+
int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus);
if (imWindowChanged && oldFocus != mInputMethodWindow) {
@@ -10297,6 +10241,12 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ public int getInputMethodWindowVisibleHeight() {
+ synchronized (mWindowMap) {
+ return mPolicy.getInputMethodWindowVisibleHeightLw();
+ }
+ }
+
@Override
public boolean hasNavigationBar() {
return mPolicy.hasNavigationBar();
@@ -10312,6 +10262,51 @@ public class WindowManagerService extends IWindowManager.Stub
return mSafeMode;
}
+ @Override
+ public boolean clearWindowContentFrameStats(IBinder token) {
+ if (!checkCallingPermission(Manifest.permission.FRAME_STATS,
+ "clearWindowContentFrameStats()")) {
+ throw new SecurityException("Requires FRAME_STATS permission");
+ }
+ synchronized (mWindowMap) {
+ WindowState windowState = mWindowMap.get(token);
+ if (windowState == null) {
+ return false;
+ }
+ SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+ if (surfaceControl == null) {
+ return false;
+ }
+ return surfaceControl.clearContentFrameStats();
+ }
+ }
+
+ @Override
+ public WindowContentFrameStats getWindowContentFrameStats(IBinder token) {
+ if (!checkCallingPermission(Manifest.permission.FRAME_STATS,
+ "getWindowContentFrameStats()")) {
+ throw new SecurityException("Requires FRAME_STATS permission");
+ }
+ synchronized (mWindowMap) {
+ WindowState windowState = mWindowMap.get(token);
+ if (windowState == null) {
+ return null;
+ }
+ SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+ if (surfaceControl == null) {
+ return null;
+ }
+ if (mTempWindowRenderStats == null) {
+ mTempWindowRenderStats = new WindowContentFrameStats();
+ }
+ WindowContentFrameStats stats = mTempWindowRenderStats;
+ if (!surfaceControl.getContentFrameStats(stats)) {
+ return null;
+ }
+ return stats;
+ }
+ }
+
void dumpPolicyLocked(PrintWriter pw, String[] args, boolean dumpAll) {
pw.println("WINDOW MANAGER POLICY STATE (dumpsys window policy)");
mPolicy.dump(" ", pw, args);
@@ -10989,5 +10984,100 @@ public class WindowManagerService extends IWindowManager.Stub
public void requestTraversalFromDisplayManager() {
requestTraversal();
}
+
+ @Override
+ public void setMagnificationSpec(MagnificationSpec spec) {
+ synchronized (mWindowMap) {
+ if (mAccessibilityController != null) {
+ mAccessibilityController.setMagnificationSpecLocked(spec);
+ } else {
+ throw new IllegalStateException("Magnification callbacks not set!");
+ }
+ }
+ if (Binder.getCallingPid() != android.os.Process.myPid()) {
+ spec.recycle();
+ }
+ }
+
+ @Override
+ public MagnificationSpec getCompatibleMagnificationSpecForWindow(IBinder windowToken) {
+ synchronized (mWindowMap) {
+ WindowState windowState = mWindowMap.get(windowToken);
+ if (windowState == null) {
+ return null;
+ }
+ MagnificationSpec spec = null;
+ if (mAccessibilityController != null) {
+ spec = mAccessibilityController.getMagnificationSpecForWindowLocked(windowState);
+ }
+ if ((spec == null || spec.isNop()) && windowState.mGlobalScale == 1.0f) {
+ return null;
+ }
+ spec = (spec == null) ? MagnificationSpec.obtain() : MagnificationSpec.obtain(spec);
+ spec.scale *= windowState.mGlobalScale;
+ return spec;
+ }
+ }
+
+ @Override
+ public void setMagnificationCallbacks(MagnificationCallbacks callbacks) {
+ synchronized (mWindowMap) {
+ if (mAccessibilityController == null) {
+ mAccessibilityController = new AccessibilityController(
+ WindowManagerService.this);
+ }
+ mAccessibilityController.setMagnificationCallbacksLocked(callbacks);
+ if (!mAccessibilityController.hasCallbacksLocked()) {
+ mAccessibilityController = null;
+ }
+ }
+ }
+
+ @Override
+ public void setWindowsForAccessibilityCallback(WindowsForAccessibilityCallback callback) {
+ synchronized (mWindowMap) {
+ if (mAccessibilityController == null) {
+ mAccessibilityController = new AccessibilityController(
+ WindowManagerService.this);
+ }
+ mAccessibilityController.setWindowsForAccessibilityCallback(callback);
+ if (!mAccessibilityController.hasCallbacksLocked()) {
+ mAccessibilityController = null;
+ }
+ }
+ }
+
+ @Override
+ public void setInputFilter(IInputFilter filter) {
+ mInputManager.setInputFilter(filter);
+ }
+
+ @Override
+ public IBinder getFocusedWindowToken() {
+ synchronized (mWindowMap) {
+ WindowState windowState = getFocusedWindowLocked();
+ if (windowState != null) {
+ return windowState.mClient.asBinder();
+ }
+ return null;
+ }
+ }
+
+ @Override
+ public boolean isKeyguardLocked() {
+ return isKeyguardLocked();
+ }
+
+ @Override
+ public void getWindowFrame(IBinder token, Rect outBounds) {
+ synchronized (mWindowMap) {
+ WindowState windowState = mWindowMap.get(token);
+ if (windowState != null) {
+ outBounds.set(windowState.mFrame);
+ } else {
+ outBounds.setEmpty();
+ }
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9f3415e..e746c1a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -23,12 +23,12 @@ import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import android.app.AppOpsManager;
@@ -299,6 +299,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
boolean mHasSurface = false;
+ boolean mNotOnAppsDisplay = false;
DisplayContent mDisplayContent;
/** When true this window can be displayed on screens owther than mOwnerUid's */
@@ -375,17 +376,19 @@ final class WindowState implements WindowManagerPolicy.WindowState {
mAttachedWindow.mChildWindows.add(this);
} else {
for (int i = 0; i < children_size; i++) {
- WindowState child = mAttachedWindow.mChildWindows.get(i);
- if (mSubLayer < child.mSubLayer) {
+ WindowState child = (WindowState)mAttachedWindow.mChildWindows.get(i);
+ if (this.mSubLayer < child.mSubLayer) {
mAttachedWindow.mChildWindows.add(i, this);
break;
- } else if (mSubLayer > child.mSubLayer) {
+ } else if (this.mSubLayer > child.mSubLayer) {
continue;
}
- if (mBaseLayer <= child.mBaseLayer) {
+ if (this.mBaseLayer <= child.mBaseLayer) {
mAttachedWindow.mChildWindows.add(i, this);
break;
+ } else {
+ continue;
}
}
if (children_size == mAttachedWindow.mChildWindows.size()) {
@@ -428,6 +431,10 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
mRootToken = appToken;
mAppToken = appToken.appWindowToken;
+ if (mAppToken != null) {
+ final DisplayContent appDisplay = getDisplayContent();
+ mNotOnAppsDisplay = displayContent != appDisplay;
+ }
mWinAnimator = new WindowStateAnimator(this);
mWinAnimator.mAlpha = a.alpha;
@@ -715,7 +722,8 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
public DisplayContent getDisplayContent() {
- return mAppToken == null ? mDisplayContent : getStack().getDisplayContent();
+ return mAppToken == null || mNotOnAppsDisplay ?
+ mDisplayContent : getStack().getDisplayContent();
}
public int getDisplayId() {
@@ -1008,7 +1016,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
&& (mConfiguration == null
|| (mConfiguration.diff(mService.mCurConfiguration) != 0));
- if (mAttrs.type == TYPE_KEYGUARD) {
+ if ((mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
// Retain configuration changed status until resetConfiguration called.
mConfigHasChanged |= configChanged;
configChanged = mConfigHasChanged;
@@ -1247,7 +1255,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
return win.mShowToOwnerOnly
- && UserHandle.getUserId(win.mOwnerUid) != mService.mCurrentUserId;
+ && !mService.isCurrentProfileLocked(UserHandle.getUserId(win.mOwnerUid));
}
private static void applyInsets(Region outRegion, Rect frame, Rect inset) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 1ff0afb..1e79dcb 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -46,6 +46,7 @@ import android.view.MagnificationSpec;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
+import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerPolicy;
import android.view.WindowManager.LayoutParams;
@@ -113,6 +114,11 @@ class WindowStateAnimator {
float mAlpha = 0;
float mLastAlpha = 0;
+ boolean mHasClipRect;
+ Rect mClipRect = new Rect();
+ Rect mTmpClipRect = new Rect();
+ Rect mLastClipRect = new Rect();
+
// Used to save animation distances between the time they are calculated and when they are
// used.
int mAnimDw;
@@ -146,6 +152,10 @@ class WindowStateAnimator {
static final int READY_TO_SHOW = 3;
/** Set when the window has been shown in the screen the first time. */
static final int HAS_DRAWN = 4;
+
+ private static final int SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN =
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+
static String drawStateToString(int state) {
switch (state) {
case NO_SURFACE: return "NO_SURFACE";
@@ -957,9 +967,10 @@ class WindowStateAnimator {
if (screenAnimation) {
tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix());
}
+
//TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mDisplayMagnifier != null && displayId == Display.DEFAULT_DISPLAY) {
- MagnificationSpec spec = mService.mDisplayMagnifier
+ if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) {
+ MagnificationSpec spec = mService.mAccessibilityController
.getMagnificationSpecForWindowLocked(mWin);
if (spec != null && !spec.isNop()) {
tmpMatrix.postScale(spec.scale, spec.scale);
@@ -991,6 +1002,7 @@ class WindowStateAnimator {
// transforming since it is more important to have that
// animation be smooth.
mShownAlpha = mAlpha;
+ mHasClipRect = false;
if (!mService.mLimitedAlphaCompositing
|| (!PixelFormat.formatHasAlpha(mWin.mAttrs.format)
|| (mWin.isIdentityMatrix(mDsDx, mDtDx, mDsDy, mDtDy)
@@ -1004,6 +1016,10 @@ class WindowStateAnimator {
}
if (appTransformation != null) {
mShownAlpha *= appTransformation.getAlpha();
+ if (appTransformation.hasClipRect()) {
+ mClipRect.set(appTransformation.getClipRect());
+ mHasClipRect = true;
+ }
}
if (mAnimator.mUniverseBackground != null) {
mShownAlpha *= mAnimator.mUniverseBackground.mUniverseTransform.getAlpha();
@@ -1038,8 +1054,8 @@ class WindowStateAnimator {
&& mWin.mBaseLayer < mAnimator.mAboveUniverseLayer);
MagnificationSpec spec = null;
//TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mDisplayMagnifier != null && displayId == Display.DEFAULT_DISPLAY) {
- spec = mService.mDisplayMagnifier.getMagnificationSpecForWindowLocked(mWin);
+ if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) {
+ spec = mService.mAccessibilityController.getMagnificationSpecForWindowLocked(mWin);
}
if (applyUniverseTransformation || spec != null) {
final Rect frame = mWin.mFrame;
@@ -1155,15 +1171,38 @@ class WindowStateAnimator {
applyDecorRect(w.mDecorFrame);
}
- if (!w.mSystemDecorRect.equals(w.mLastSystemDecorRect)) {
- w.mLastSystemDecorRect.set(w.mSystemDecorRect);
+ // By default, the clip rect is the system decor rect
+ Rect clipRect = w.mSystemDecorRect;
+ if (mHasClipRect) {
+
+ // If we have an animated clip rect, intersect it with the system decor rect
+ // NOTE: We are adding a temporary workaround due to the status bar not always reporting
+ // the correct system decor rect. In such cases, we take into account the specified
+ // content insets as well.
+ int offsetTop = Math.max(w.mSystemDecorRect.top, w.mContentInsets.top);
+ mTmpClipRect.set(w.mSystemDecorRect);
+ // Don't apply the workaround to apps explicitly requesting fullscreen layout.
+ if ((w.mSystemUiVisibility & SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN)
+ == SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN) {
+ mTmpClipRect.intersect(mClipRect);
+ } else {
+ mTmpClipRect.offset(0, -offsetTop);
+ mTmpClipRect.intersect(mClipRect);
+ mTmpClipRect.offset(0, offsetTop);
+ }
+ clipRect = mTmpClipRect;
+
+ }
+
+ if (!clipRect.equals(mLastClipRect)) {
+ mLastClipRect.set(clipRect);
try {
if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
- "CROP " + w.mSystemDecorRect.toShortString(), null);
- mSurfaceControl.setWindowCrop(w.mSystemDecorRect);
+ "CROP " + clipRect.toShortString(), null);
+ mSurfaceControl.setWindowCrop(clipRect);
} catch (RuntimeException e) {
Slog.w(TAG, "Error setting crop surface of " + w
- + " crop=" + w.mSystemDecorRect.toShortString(), e);
+ + " crop=" + clipRect.toShortString(), e);
if (!recoveringMemory) {
mService.reclaimSomeSurfaceMemoryLocked(this, "crop", true);
}
@@ -1308,8 +1347,8 @@ class WindowStateAnimator {
mSurfaceLayer = mAnimLayer;
mSurfaceControl.setLayer(mAnimLayer);
mSurfaceControl.setMatrix(
- mDsDx*w.mHScale, mDtDx*w.mVScale,
- mDsDy*w.mHScale, mDtDy*w.mVScale);
+ mDsDx * w.mHScale, mDtDx * w.mVScale,
+ mDsDy * w.mHScale, mDtDy * w.mVScale);
if (mLastHidden && mDrawState == HAS_DRAWN) {
if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
@@ -1571,9 +1610,9 @@ class WindowStateAnimator {
}
applyAnimationLocked(transit, true);
//TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mDisplayMagnifier != null
+ if (mService.mAccessibilityController != null
&& mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
- mService.mDisplayMagnifier.onWindowTransitionLocked(mWin, transit);
+ mService.mAccessibilityController.onWindowTransitionLocked(mWin, transit);
}
}