diff options
Diffstat (limited to 'services/java/com/android')
62 files changed, 10951 insertions, 4260 deletions
diff --git a/services/java/com/android/server/AccessibilityManagerService.java b/services/java/com/android/server/AccessibilityManagerService.java index 87de79a..04ae490 100644 --- a/services/java/com/android/server/AccessibilityManagerService.java +++ b/services/java/com/android/server/AccessibilityManagerService.java @@ -240,10 +240,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public void onChange(boolean selfChange) { super.onChange(selfChange); - mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1; - synchronized (mLock) { + mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1; if (mIsEnabled) { manageServicesLocked(); } else { @@ -269,14 +268,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub }); } - public void addClient(IAccessibilityManagerClient client) { + public boolean addClient(IAccessibilityManagerClient client) { synchronized (mLock) { - try { - client.setEnabled(mIsEnabled); - mClients.add(client); - } catch (RemoteException re) { - Slog.w(LOG_TAG, "Dead AccessibilityManagerClient: " + client, re); - } + mClients.add(client); + return mIsEnabled; } } @@ -456,9 +451,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } catch (RemoteException re) { if (re instanceof DeadObjectException) { Slog.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up."); - synchronized (mLock) { - removeDeadServiceLocked(service); - } + removeDeadServiceLocked(service); } else { Slog.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re); } @@ -472,19 +465,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * @return True if the service was removed, false otherwise. */ private boolean removeDeadServiceLocked(Service service) { - mServices.remove(service); - mHandler.removeMessages(service.mId); - if (Config.DEBUG) { Slog.i(LOG_TAG, "Dead service " + service.mService + " removed"); } - - if (mServices.isEmpty()) { - mIsEnabled = false; - updateClientsLocked(); - } - - return true; + mHandler.removeMessages(service.mId); + return mServices.remove(service); } /** @@ -547,11 +532,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub for (int i = 0, count = services.size(); i < count; i++) { Service service = services.get(i); - - service.unbind(); - mComponentNameToServiceMap.remove(service.mComponentName); + if (service.unbind()) { + i--; + count--; + } } - services.clear(); } /** @@ -593,7 +578,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub Set<ComponentName> enabledServices) { Map<ComponentName, Service> componentNameToServiceMap = mComponentNameToServiceMap; - List<Service> services = mServices; boolean isEnabled = mIsEnabled; for (int i = 0, count = installedServices.size(); i < count; i++) { @@ -602,15 +586,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub intalledService.name); Service service = componentNameToServiceMap.get(componentName); - if (isEnabled && enabledServices.contains(componentName)) { - if (service == null) { - new Service(componentName).bind(); + if (isEnabled) { + if (enabledServices.contains(componentName)) { + if (service == null) { + service = new Service(componentName); + } + service.bind(); + } else if (!enabledServices.contains(componentName)) { + if (service != null) { + service.unbind(); + } } } else { if (service != null) { service.unbind(); - componentNameToServiceMap.remove(componentName); - services.remove(service); } } } @@ -678,21 +667,31 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub /** * Binds to the accessibility service. + * + * @return True if binding is successful. */ - public void bind() { + public boolean bind() { if (mService == null) { - mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE); + return mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE); } + return false; } /** * Unbinds form the accessibility service and removes it from the data * structures for service management. + * + * @return True if unbinding is successful. */ - public void unbind() { + public boolean unbind() { if (mService != null) { + mService = null; mContext.unbindService(this); + mComponentNameToServiceMap.remove(mComponentName); + mServices.remove(this); + return true; } + return false; } /** diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java index 4e2f1e3..5a36417 100644 --- a/services/java/com/android/server/AlarmManagerService.java +++ b/services/java/com/android/server/AlarmManagerService.java @@ -124,6 +124,14 @@ class AlarmManagerService extends IAlarmManager.Stub { public AlarmManagerService(Context context) { mContext = context; mDescriptor = init(); + + // We have to set current TimeZone info to kernel + // because kernel doesn't keep this after reboot + String tz = SystemProperties.get(TIMEZONE_PROPERTY); + if (tz != null) { + setTimeZone(tz); + } + PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); @@ -653,7 +661,8 @@ class AlarmManagerService extends IAlarmManager.Stub { remove(mTimeTickSender); mClockReceiver.scheduleTimeTickEvent(); Intent intent = new Intent(Intent.ACTION_TIME_CHANGED); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING + | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendBroadcast(intent); } diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index 731fb22..59a540b 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -16,6 +16,23 @@ package com.android.server; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + import android.app.AlarmManager; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; @@ -24,46 +41,37 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.Intent.FilterComparison; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.net.Uri; 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.SystemClock; import android.util.AttributeSet; +import android.util.Pair; import android.util.Slog; import android.util.TypedValue; import android.util.Xml; import android.widget.RemoteViews; -import java.io.IOException; -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.HashMap; -import java.util.HashSet; - -import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.appwidget.IAppWidgetHost; +import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.util.FastXmlSerializer; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; +import com.android.internal.widget.IRemoteViewsAdapterConnection; class AppWidgetService extends IAppWidgetService.Stub { @@ -107,6 +115,56 @@ class AppWidgetService extends IAppWidgetService.Stub Host host; } + /** + * Acts as a proxy between the ServiceConnection and the RemoteViewsAdapterConnection. + * This needs to be a static inner class since a reference to the ServiceConnection is held + * globally and may lead us to leak AppWidgetService instances (if there were more than one). + */ + static class ServiceConnectionProxy implements ServiceConnection { + private final AppWidgetService mAppWidgetService; + private final Pair<Integer, Intent.FilterComparison> mKey; + private final IBinder mConnectionCb; + + ServiceConnectionProxy(AppWidgetService appWidgetService, + Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) { + mAppWidgetService = appWidgetService; + mKey = key; + mConnectionCb = connectionCb; + } + public void onServiceConnected(ComponentName name, IBinder service) { + IRemoteViewsAdapterConnection cb = + IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb); + try { + cb.onServiceConnected(service); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + public void onServiceDisconnected(ComponentName name) { + IRemoteViewsAdapterConnection cb = + IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb); + try { + cb.onServiceDisconnected(); + mAppWidgetService.mServiceConnectionUpdateHandler.post(new Runnable() { + public void run() { + // We don't want to touch mBoundRemoteViewsServices from any other thread + // so queue this to run on the main thread. + if (mAppWidgetService.mBoundRemoteViewsServices.containsKey(mKey)) { + mAppWidgetService.mBoundRemoteViewsServices.remove(mKey); + } + } + }); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + + // Manages connections to RemoteViewsServices + private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection> + mBoundRemoteViewsServices = new HashMap<Pair<Integer,FilterComparison>,ServiceConnection>(); + private final Handler mServiceConnectionUpdateHandler = new Handler(); + Context mContext; Locale mLocale; PackageManager mPackageManager; @@ -144,6 +202,7 @@ class AppWidgetService extends IAppWidgetService.Stub // update the provider list. IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); mContext.registerReceiver(mBroadcastReceiver, filter); @@ -293,6 +352,9 @@ class AppWidgetService extends IAppWidgetService.Stub } void deleteAppWidgetLocked(AppWidgetId id) { + // We first unbind all services that are bound to this id + unbindAppWidgetRemoteViewsServicesLocked(id); + Host host = id.host; host.instances.remove(id); pruneHostLocked(host); @@ -375,6 +437,77 @@ class AppWidgetService extends IAppWidgetService.Stub } } + public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) { + synchronized (mAppWidgetIds) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id == null) { + throw new IllegalArgumentException("bad appWidgetId"); + } + final ComponentName componentName = intent.getComponent(); + try { + final ServiceInfo si = mContext.getPackageManager().getServiceInfo(componentName, + PackageManager.GET_PERMISSIONS); + if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(si.permission)) { + throw new SecurityException("Selected service does not require " + + android.Manifest.permission.BIND_REMOTEVIEWS + + ": " + componentName); + } + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException("Unknown component " + componentName); + } + + // Bind to the RemoteViewsService (which will trigger a callback to the + // RemoteViewsAdapter) + Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, + new FilterComparison(intent)); + final ServiceConnection conn = new ServiceConnectionProxy(this, key, connection); + final long token = Binder.clearCallingIdentity(); + try { + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); + mBoundRemoteViewsServices.put(key, conn); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + + public void unbindRemoteViewsService(int appWidgetId, Intent intent) { + synchronized (mAppWidgetIds) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id == null) { + throw new IllegalArgumentException("bad appWidgetId"); + } + + // Unbind from the RemoteViewsService (which will trigger a callback to the bound + // RemoteViewsAdapter) + Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, + new FilterComparison(intent)); + if (mBoundRemoteViewsServices.containsKey(key)) { + final ServiceConnection conn = mBoundRemoteViewsServices.get(key); + mBoundRemoteViewsServices.remove(key); + conn.onServiceDisconnected(null); + mContext.unbindService(conn); + } + } + } + + private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) { + Iterator<Pair<Integer, Intent.FilterComparison>> it = + mBoundRemoteViewsServices.keySet().iterator(); + int appWidgetId = id.appWidgetId; + + // Unbind all connections to AppWidgets bound to this id + while (it.hasNext()) { + final Pair<Integer, Intent.FilterComparison> key = it.next(); + if (key.first.intValue() == appWidgetId) { + final ServiceConnection conn = mBoundRemoteViewsServices.get(key); + it.remove(); + conn.onServiceDisconnected(null); + mContext.unbindService(conn); + } + } + } + public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { synchronized (mAppWidgetIds) { AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); @@ -426,6 +559,40 @@ class AppWidgetService extends IAppWidgetService.Stub } } + public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + for (int i=0; i<N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + updateAppWidgetInstanceLocked(id, views, true); + } + } + } + + public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + for (int i=0; i<N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + notifyAppWidgetViewDataChangedInstanceLocked(id, viewId); + } + } + } + public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) { synchronized (mAppWidgetIds) { Provider p = lookupProviderLocked(provider); @@ -443,11 +610,17 @@ class AppWidgetService extends IAppWidgetService.Stub } void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) { + updateAppWidgetInstanceLocked(id, views, false); + } + + void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) { // allow for stale appWidgetIds and other badness // lookup also checks that the calling process can access the appWidgetId // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { - id.views = views; + + // We do not want to save this RemoteViews + if (!isPartialUpdate) id.views = views; // is anyone listening? if (id.host.callbacks != null) { @@ -463,6 +636,25 @@ class AppWidgetService extends IAppWidgetService.Stub } } + void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, int viewId) { + // allow for stale appWidgetIds and other badness + // lookup also checks that the calling process can access the appWidgetId + // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) + if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { + // is anyone listening? + if (id.host.callbacks != null) { + try { + // the lock is held, but this is a oneway call + id.host.callbacks.viewDataChanged(id.appWidgetId, viewId); + } catch (RemoteException e) { + // It failed; remove the callback. No need to prune because + // we know that this host is still referenced by this instance. + id.host.callbacks = null; + } + } + } + } + public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId, List<RemoteViews> updatedViews) { int callingUid = enforceCallingUid(packageName); @@ -584,6 +776,12 @@ class AppWidgetService extends IAppWidgetService.Stub } boolean addProviderLocked(ResolveInfo ri) { + if ((ri.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + return false; + } + if (!ri.activityInfo.isEnabled()) { + return false; + } Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name), ri); if (p != null) { @@ -742,6 +940,11 @@ class AppWidgetService extends IAppWidgetService.Stub } info.label = activityInfo.loadLabel(mPackageManager).toString(); info.icon = ri.getIconResource(); + info.previewImage = sa.getResourceId( + com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0); + info.autoAdvanceViewId = sa.getResourceId( + com.android.internal.R.styleable.AppWidgetProviderInfo_autoAdvanceViewId, -1); + sa.recycle(); } catch (Exception e) { // Ok to catch Exception here, because anything going wrong because @@ -1096,6 +1299,7 @@ class AppWidgetService extends IAppWidgetService.Stub } } else { boolean added = false; + boolean changed = false; String pkgList[] = null; if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); @@ -1114,14 +1318,16 @@ class AppWidgetService extends IAppWidgetService.Stub } pkgList = new String[] { pkgName }; added = Intent.ACTION_PACKAGE_ADDED.equals(action); + changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); } if (pkgList == null || pkgList.length == 0) { return; } - if (added) { + if (added || changed) { synchronized (mAppWidgetIds) { Bundle extras = intent.getExtras(); - if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) { + if (changed || (extras != null && + extras.getBoolean(Intent.EXTRA_REPLACING, false))) { for (String pkgName : pkgList) { // The package was just upgraded updateProvidersForPackageLocked(pkgName); diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index f1e226e..c48f360 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -107,6 +107,7 @@ class BackupManagerService extends IBackupManager.Stub { private static final int MSG_RUN_INITIALIZE = 5; private static final int MSG_RUN_GET_RESTORE_SETS = 6; private static final int MSG_TIMEOUT = 7; + private static final int MSG_RESTORE_TIMEOUT = 8; // Timeout interval for deciding that a bind or clear-data has taken too long static final long TIMEOUT_INTERVAL = 10 * 1000; @@ -148,9 +149,9 @@ class BackupManagerService extends IBackupManager.Stub { return "BackupRequest{app=" + appInfo + " full=" + fullBackup + "}"; } } - // Backups that we haven't started yet. - HashMap<ApplicationInfo,BackupRequest> mPendingBackups - = new HashMap<ApplicationInfo,BackupRequest>(); + // Backups that we haven't started yet. Keys are package names. + HashMap<String,BackupRequest> mPendingBackups + = new HashMap<String,BackupRequest>(); // Pseudoname that we use for the Package Manager metadata "package" static final String PACKAGE_MANAGER_SENTINEL = "@pm@"; @@ -396,6 +397,21 @@ class BackupManagerService extends IBackupManager.Stub { } break; } + + case MSG_RESTORE_TIMEOUT: + { + synchronized (BackupManagerService.this) { + if (mActiveRestoreSession != null) { + // Client app left the restore session dangling. We know that it + // can't be in the middle of an actual restore operation because + // those are executed serially on this same handler thread. Clean + // up now. + Slog.w(TAG, "Restore session timed out; aborting"); + post(mActiveRestoreSession.new EndRestoreRunnable( + BackupManagerService.this, mActiveRestoreSession)); + } + } + } } } } @@ -590,6 +606,7 @@ class BackupManagerService extends IBackupManager.Stub { } } } + tf.close(); } catch (FileNotFoundException fnf) { // Probably innocuous Slog.v(TAG, "No ancestral data"); @@ -912,42 +929,48 @@ class BackupManagerService extends IBackupManager.Stub { // 'packageName' is null, *all* participating apps will be removed. void removePackageParticipantsLocked(String packageName) { if (DEBUG) Slog.v(TAG, "removePackageParticipantsLocked: " + packageName); - List<PackageInfo> allApps = null; + List<String> allApps = new ArrayList<String>(); if (packageName != null) { - allApps = new ArrayList<PackageInfo>(); - try { - int flags = PackageManager.GET_SIGNATURES; - allApps.add(mPackageManager.getPackageInfo(packageName, flags)); - } catch (Exception e) { - // just skip it (???) - } + allApps.add(packageName); } else { // all apps with agents - allApps = allAgentPackages(); + List<PackageInfo> knownPackages = allAgentPackages(); + for (PackageInfo pkg : knownPackages) { + allApps.add(pkg.packageName); + } } removePackageParticipantsLockedInner(packageName, allApps); } private void removePackageParticipantsLockedInner(String packageName, - List<PackageInfo> agents) { + List<String> allPackageNames) { if (DEBUG) { Slog.v(TAG, "removePackageParticipantsLockedInner (" + packageName - + ") removing " + agents.size() + " entries"); - for (PackageInfo p : agents) { + + ") removing " + allPackageNames.size() + " entries"); + for (String p : allPackageNames) { Slog.v(TAG, " - " + p); } } - for (PackageInfo pkg : agents) { - if (packageName == null || pkg.packageName.equals(packageName)) { - int uid = pkg.applicationInfo.uid; + for (String pkg : allPackageNames) { + if (packageName == null || pkg.equals(packageName)) { + int uid = -1; + try { + PackageInfo info = mPackageManager.getPackageInfo(packageName, 0); + uid = info.applicationInfo.uid; + } catch (NameNotFoundException e) { + // we don't know this package name, so just skip it for now + continue; + } + HashSet<ApplicationInfo> set = mBackupParticipants.get(uid); if (set != null) { // Find the existing entry with the same package name, and remove it. // We can't just remove(app) because the instances are different. for (ApplicationInfo entry: set) { - if (entry.packageName.equals(pkg.packageName)) { + if (entry.packageName.equals(pkg)) { + if (DEBUG) Slog.v(TAG, " removing participant " + pkg); set.remove(entry); - removeEverBackedUp(pkg.packageName); + removeEverBackedUp(pkg); break; } } @@ -997,7 +1020,11 @@ class BackupManagerService extends IBackupManager.Stub { // brute force but small code size List<PackageInfo> allApps = allAgentPackages(); - removePackageParticipantsLockedInner(packageName, allApps); + List<String> allAppNames = new ArrayList<String>(); + for (PackageInfo pkg : allApps) { + allAppNames.add(pkg.packageName); + } + removePackageParticipantsLockedInner(packageName, allAppNames); addPackageParticipantsLockedInner(packageName, allApps); } @@ -1364,6 +1391,17 @@ class BackupManagerService extends IBackupManager.Stub { for (BackupRequest request : mQueue) { Slog.d(TAG, "starting agent for backup of " + request); + // Verify that the requested app exists; it might be something that + // requested a backup but was then uninstalled. The request was + // journalled and rather than tamper with the journal it's safer + // to sanity-check here. + try { + mPackageManager.getPackageInfo(request.appInfo.packageName, 0); + } catch (NameNotFoundException e) { + Slog.d(TAG, "Package does not exist; skipping"); + continue; + } + IBackupAgent agent = null; int mode = (request.fullBackup) ? IApplicationThread.BACKUP_MODE_FULL @@ -1825,6 +1863,11 @@ class BackupManagerService extends IBackupManager.Stub { } catch (RemoteException e) { /* can't happen */ } } + // Furthermore we need to reset the session timeout clock + mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT); + mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT, + TIMEOUT_RESTORE_INTERVAL); + // done; we can finally release the wakelock mWakelock.release(); } @@ -2046,7 +2089,7 @@ class BackupManagerService extends IBackupManager.Stub { // Add the caller to the set of pending backups. If there is // one already there, then overwrite it, but no harm done. BackupRequest req = new BackupRequest(app, false); - if (mPendingBackups.put(app, req) == null) { + if (mPendingBackups.put(app.packageName, req) == null) { // Journal this request in case of crash. The put() // operation returned null when this package was not already // in the set; we want to avoid touching the disk redundantly. @@ -2343,6 +2386,55 @@ class BackupManagerService extends IBackupManager.Stub { } } + // Supply the configuration Intent for the given transport. If the name is not one + // of the available transports, or if the transport does not supply any configuration + // UI, the method returns null. + public Intent getConfigurationIntent(String transportName) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, + "getConfigurationIntent"); + + synchronized (mTransports) { + final IBackupTransport transport = mTransports.get(transportName); + if (transport != null) { + try { + final Intent intent = transport.configurationIntent(); + if (DEBUG) Slog.d(TAG, "getConfigurationIntent() returning config intent " + + intent); + return intent; + } catch (RemoteException e) { + /* fall through to return null */ + } + } + } + + return null; + } + + // Supply the configuration summary string for the given transport. If the name is + // not one of the available transports, or if the transport does not supply any + // summary / destination string, the method can return null. + // + // This string is used VERBATIM as the summary text of the relevant Settings item! + public String getDestinationString(String transportName) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, + "getConfigurationIntent"); + + synchronized (mTransports) { + final IBackupTransport transport = mTransports.get(transportName); + if (transport != null) { + try { + final String text = transport.currentDestinationString(); + if (DEBUG) Slog.d(TAG, "getDestinationString() returning " + text); + return text; + } catch (RemoteException e) { + /* fall through to return null */ + } + } + } + + return null; + } + // Callback: a requested backup agent has been instantiated. This should only // be called from the Activity Manager. public void agentConnected(String packageName, IBinder agentBinder) { @@ -2456,10 +2548,23 @@ class BackupManagerService extends IBackupManager.Stub { return null; } mActiveRestoreSession = new ActiveRestoreSession(packageName, transport); + mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT, TIMEOUT_RESTORE_INTERVAL); } return mActiveRestoreSession; } + void clearRestoreSession(ActiveRestoreSession currentSession) { + synchronized(this) { + if (currentSession != mActiveRestoreSession) { + Slog.e(TAG, "ending non-current restore session"); + } else { + if (DEBUG) Slog.v(TAG, "Clearing restore session and halting timeout"); + mActiveRestoreSession = null; + mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT); + } + } + } + // Note that a currently-active backup agent has notified us that it has // completed the given outstanding asynchronous backup/restore operation. public void opComplete(int token) { @@ -2478,6 +2583,7 @@ class BackupManagerService extends IBackupManager.Stub { private String mPackageName; private IBackupTransport mRestoreTransport = null; RestoreSet[] mRestoreSets = null; + boolean mEnded = false; ActiveRestoreSession(String packageName, String transport) { mPackageName = packageName; @@ -2492,6 +2598,10 @@ class BackupManagerService extends IBackupManager.Stub { throw new IllegalArgumentException("Observer must not be null"); } + if (mEnded) { + throw new IllegalStateException("Restore session already ended"); + } + long oldId = Binder.clearCallingIdentity(); try { if (mRestoreTransport == null) { @@ -2519,6 +2629,10 @@ class BackupManagerService extends IBackupManager.Stub { if (DEBUG) Slog.d(TAG, "restoreAll token=" + Long.toHexString(token) + " observer=" + observer); + if (mEnded) { + throw new IllegalStateException("Restore session already ended"); + } + if (mRestoreTransport == null || mRestoreSets == null) { Slog.e(TAG, "Ignoring restoreAll() with no restore set"); return -1; @@ -2550,6 +2664,10 @@ class BackupManagerService extends IBackupManager.Stub { public synchronized int restorePackage(String packageName, IRestoreObserver observer) { if (DEBUG) Slog.v(TAG, "restorePackage pkg=" + packageName + " obs=" + observer); + if (mEnded) { + throw new IllegalStateException("Restore session already ended"); + } + if (mPackageName != null) { if (! mPackageName.equals(packageName)) { Slog.e(TAG, "Ignoring attempt to restore pkg=" + packageName @@ -2606,34 +2724,59 @@ class BackupManagerService extends IBackupManager.Stub { return 0; } - public synchronized void endRestoreSession() { - if (DEBUG) Slog.d(TAG, "endRestoreSession"); + // Posted to the handler to tear down a restore session in a cleanly synchronized way + class EndRestoreRunnable implements Runnable { + BackupManagerService mBackupManager; + ActiveRestoreSession mSession; - synchronized (this) { - long oldId = Binder.clearCallingIdentity(); - try { - if (mRestoreTransport != null) mRestoreTransport.finishRestore(); - } catch (Exception e) { - Slog.e(TAG, "Error in finishRestore", e); - } finally { - mRestoreTransport = null; - Binder.restoreCallingIdentity(oldId); - } + EndRestoreRunnable(BackupManagerService manager, ActiveRestoreSession session) { + mBackupManager = manager; + mSession = session; } - synchronized (BackupManagerService.this) { - if (BackupManagerService.this.mActiveRestoreSession == this) { - BackupManagerService.this.mActiveRestoreSession = null; - } else { - Slog.e(TAG, "ending non-current restore session"); + public void run() { + // clean up the session's bookkeeping + synchronized (mSession) { + try { + if (mSession.mRestoreTransport != null) { + mSession.mRestoreTransport.finishRestore(); + } + } catch (Exception e) { + Slog.e(TAG, "Error in finishRestore", e); + } finally { + mSession.mRestoreTransport = null; + mSession.mEnded = true; + } } + + // clean up the BackupManagerService side of the bookkeeping + // and cancel any pending timeout message + mBackupManager.clearRestoreSession(mSession); } } - } + public synchronized void endRestoreSession() { + if (DEBUG) Slog.d(TAG, "endRestoreSession"); + + if (mEnded) { + throw new IllegalStateException("Restore session already ended"); + } + + mBackupHandler.post(new EndRestoreRunnable(BackupManagerService.this, this)); + } + } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + long identityToken = Binder.clearCallingIdentity(); + try { + dumpInternal(pw); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + private void dumpInternal(PrintWriter pw) { synchronized (mQueueLock) { pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled") + " / " + (!mProvisioned ? "not " : "") + "provisioned / " @@ -2647,12 +2790,15 @@ class BackupManagerService extends IBackupManager.Stub { for (String t : listAllTransports()) { pw.println((t.equals(mCurrentTransport) ? " * " : " ") + t); try { - File dir = new File(mBaseStateDir, getTransport(t).transportDirName()); + IBackupTransport transport = getTransport(t); + File dir = new File(mBaseStateDir, transport.transportDirName()); + pw.println(" destination: " + transport.currentDestinationString()); + pw.println(" intent: " + transport.configurationIntent()); for (File f : dir.listFiles()) { pw.println(" " + f.getName() + " - " + f.length() + " state bytes"); } - } catch (RemoteException e) { - Slog.e(TAG, "Error in transportDirName()", e); + } catch (Exception e) { + Slog.e(TAG, "Error in transport", e); pw.println(" Error: " + e); } } diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java index fc4e06f..47599c8 100644 --- a/services/java/com/android/server/BatteryService.java +++ b/services/java/com/android/server/BatteryService.java @@ -43,6 +43,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; +import java.util.Arrays; /** @@ -76,7 +77,7 @@ class BatteryService extends Binder { // Used locally for determining when to make a last ditch effort to log // discharge stats before the device dies. - private static final int CRITICAL_BATTERY_LEVEL = 4; + private int mCriticalBatteryLevel; private static final int DUMP_MAX_LENGTH = 24 * 1024; private static final String[] DUMPSYS_ARGS = new String[] { "--checkin", "-u" }; @@ -100,6 +101,7 @@ class BatteryService extends Binder { private int mBatteryTemperature; private String mBatteryTechnology; private boolean mBatteryLevelCritical; + private int mInvalidCharger; private int mLastBatteryStatus; private int mLastBatteryHealth; @@ -108,6 +110,7 @@ class BatteryService extends Binder { private int mLastBatteryVoltage; private int mLastBatteryTemperature; private boolean mLastBatteryLevelCritical; + private int mLastInvalidCharger; private int mLowBatteryWarningLevel; private int mLowBatteryCloseWarningLevel; @@ -118,18 +121,28 @@ class BatteryService extends Binder { private long mDischargeStartTime; private int mDischargeStartLevel; + private Led mLed; + private boolean mSentLowBatteryBroadcast = false; - public BatteryService(Context context) { + public BatteryService(Context context, LightsService lights) { mContext = context; + mLed = new Led(context, lights); mBatteryStats = BatteryStatsService.getService(); + mCriticalBatteryLevel = mContext.getResources().getInteger( + com.android.internal.R.integer.config_criticalBatteryWarningLevel); mLowBatteryWarningLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_lowBatteryWarningLevel); mLowBatteryCloseWarningLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_lowBatteryCloseWarningLevel); - mUEventObserver.startObserving("SUBSYSTEM=power_supply"); + mPowerSupplyObserver.startObserving("SUBSYSTEM=power_supply"); + + // watch for invalid charger messages if the invalid_charger switch exists + if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) { + mInvalidChargerObserver.startObserving("DEVPATH=/devices/virtual/switch/invalid_charger"); + } // set initial status update(); @@ -163,13 +176,24 @@ class BatteryService extends Binder { return mPlugType; } - private UEventObserver mUEventObserver = new UEventObserver() { + private UEventObserver mPowerSupplyObserver = new UEventObserver() { @Override public void onUEvent(UEventObserver.UEvent event) { update(); } }; + private UEventObserver mInvalidChargerObserver = new UEventObserver() { + @Override + public void onUEvent(UEventObserver.UEvent event) { + int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0; + if (mInvalidCharger != invalidCharger) { + mInvalidCharger = invalidCharger; + update(); + } + } + }; + // returns battery level as a percentage final int getBatteryLevel() { return mBatteryLevel; @@ -207,11 +231,14 @@ class BatteryService extends Binder { private synchronized final void update() { native_update(); + processValues(); + } + private void processValues() { boolean logOutlier = false; long dischargeDuration = 0; - mBatteryLevelCritical = mBatteryLevel <= CRITICAL_BATTERY_LEVEL; + mBatteryLevelCritical = mBatteryLevel <= mCriticalBatteryLevel; if (mAcOnline) { mPlugType = BatteryManager.BATTERY_PLUGGED_AC; } else if (mUsbOnline) { @@ -238,7 +265,8 @@ class BatteryService extends Binder { mBatteryLevel != mLastBatteryLevel || mPlugType != mLastPlugType || mBatteryVoltage != mLastBatteryVoltage || - mBatteryTemperature != mLastBatteryTemperature) { + mBatteryTemperature != mLastBatteryTemperature || + mInvalidCharger != mLastInvalidCharger) { if (mPlugType != mLastPlugType) { if (mLastPlugType == BATTERY_PLUGGED_NONE) { @@ -292,9 +320,9 @@ class BatteryService extends Binder { * (becomes <= mLowBatteryWarningLevel). */ final boolean sendBatteryLow = !plugged - && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN - && mBatteryLevel <= mLowBatteryWarningLevel - && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel); + && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN + && mBatteryLevel <= mLowBatteryWarningLevel + && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel); sendIntent(); @@ -322,6 +350,9 @@ class BatteryService extends Binder { mContext.sendBroadcast(statusIntent); } + // Update the battery LED + mLed.updateLightsLocked(); + // This needs to be done after sendIntent() so that we get the lastest battery stats. if (logOutlier && dischargeDuration != 0) { logOutlier(dischargeDuration); @@ -335,6 +366,7 @@ class BatteryService extends Binder { mLastBatteryVoltage = mBatteryVoltage; mLastBatteryTemperature = mBatteryTemperature; mLastBatteryLevelCritical = mBatteryLevelCritical; + mLastInvalidCharger = mInvalidCharger; } } @@ -356,16 +388,17 @@ class BatteryService extends Binder { intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryVoltage); intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryTemperature); intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryTechnology); + intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger); - if (false) { - Slog.d(TAG, "updateBattery level:" + mBatteryLevel + + if (true) { + Slog.d(TAG, "level:" + mBatteryLevel + " scale:" + BATTERY_SCALE + " status:" + mBatteryStatus + " health:" + mBatteryHealth + " present:" + mBatteryPresent + " voltage: " + mBatteryVoltage + " temperature: " + mBatteryTemperature + " technology: " + mBatteryTechnology + " AC powered:" + mAcOnline + " USB powered:" + mUsbOnline + - " icon:" + icon ); + " icon:" + icon + " invalid charger:" + mInvalidCharger); } ActivityManagerNative.broadcastStickyIntent(intent, null); @@ -440,10 +473,15 @@ class BatteryService extends Binder { private final int getIcon(int level) { if (mBatteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) { return com.android.internal.R.drawable.stat_sys_battery_charge; - } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING || - mBatteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING || - mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL) { + } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) { return com.android.internal.R.drawable.stat_sys_battery; + } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING + || mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL) { + if (isPowered() && mBatteryLevel >= 100) { + return com.android.internal.R.drawable.stat_sys_battery_charge; + } else { + return com.android.internal.R.drawable.stat_sys_battery; + } } else { return com.android.internal.R.drawable.stat_sys_battery_unknown; } @@ -460,18 +498,109 @@ class BatteryService extends Binder { return; } - synchronized (this) { - pw.println("Current Battery Service state:"); - pw.println(" AC powered: " + mAcOnline); - pw.println(" USB powered: " + mUsbOnline); - pw.println(" status: " + mBatteryStatus); - pw.println(" health: " + mBatteryHealth); - pw.println(" present: " + mBatteryPresent); - pw.println(" level: " + mBatteryLevel); - pw.println(" scale: " + BATTERY_SCALE); - pw.println(" voltage:" + mBatteryVoltage); - pw.println(" temperature: " + mBatteryTemperature); - pw.println(" technology: " + mBatteryTechnology); + if (args == null || args.length == 0) { + synchronized (this) { + pw.println("Current Battery Service state:"); + pw.println(" AC powered: " + mAcOnline); + pw.println(" USB powered: " + mUsbOnline); + pw.println(" status: " + mBatteryStatus); + pw.println(" health: " + mBatteryHealth); + pw.println(" present: " + mBatteryPresent); + pw.println(" level: " + mBatteryLevel); + pw.println(" scale: " + BATTERY_SCALE); + pw.println(" voltage:" + mBatteryVoltage); + pw.println(" temperature: " + mBatteryTemperature); + pw.println(" technology: " + mBatteryTechnology); + } + } else if (false) { + // DO NOT SUBMIT WITH THIS TURNED ON + if (args.length == 3 && "set".equals(args[0])) { + String key = args[1]; + String value = args[2]; + try { + boolean update = true; + if ("ac".equals(key)) { + mAcOnline = Integer.parseInt(value) != 0; + } else if ("usb".equals(key)) { + mUsbOnline = Integer.parseInt(value) != 0; + } else if ("status".equals(key)) { + mBatteryStatus = Integer.parseInt(value); + } else if ("level".equals(key)) { + mBatteryLevel = Integer.parseInt(value); + } else if ("invalid".equals(key)) { + mInvalidCharger = Integer.parseInt(value); + } else { + update = false; + } + if (update) { + processValues(); + } + } catch (NumberFormatException ex) { + pw.println("Bad value: " + value); + } + } + } + } + + class Led { + private LightsService mLightsService; + private LightsService.Light mBatteryLight; + + private int mBatteryLowARGB; + private int mBatteryMediumARGB; + private int mBatteryFullARGB; + private int mBatteryLedOn; + private int mBatteryLedOff; + + private boolean mBatteryCharging; + private boolean mBatteryLow; + private boolean mBatteryFull; + + Led(Context context, LightsService lights) { + mLightsService = lights; + mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY); + + mBatteryLowARGB = mContext.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryLowARGB); + mBatteryMediumARGB = mContext.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryMediumARGB); + mBatteryFullARGB = mContext.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryFullARGB); + mBatteryLedOn = mContext.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryLedOn); + mBatteryLedOff = mContext.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryLedOff); + } + + /** + * Synchronize on BatteryService. + */ + void updateLightsLocked() { + final int level = mBatteryLevel; + final int status = mBatteryStatus; + if (level < mLowBatteryWarningLevel) { + if (status == BatteryManager.BATTERY_STATUS_CHARGING) { + // Solid red when battery is charging + mBatteryLight.setColor(mBatteryLowARGB); + } else { + // Flash red when battery is low and not charging + mBatteryLight.setFlashing(mBatteryLowARGB, LightsService.LIGHT_FLASH_TIMED, + mBatteryLedOn, mBatteryLedOff); + } + } else if (status == BatteryManager.BATTERY_STATUS_CHARGING + || status == BatteryManager.BATTERY_STATUS_FULL) { + if (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90) { + // Solid green when full or charging and nearly full + mBatteryLight.setColor(mBatteryFullARGB); + } else { + // Solid orange when charging and halfway full + mBatteryLight.setColor(mBatteryMediumARGB); + } + } else { + // No lights if not charging and not low + mBatteryLight.turnOff(); + } } } } + diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java index aa8cded..062ab74 100644 --- a/services/java/com/android/server/ClipboardService.java +++ b/services/java/com/android/server/ClipboardService.java @@ -16,42 +16,240 @@ package com.android.server; -import android.text.IClipboard; +import android.app.ActivityManagerNative; +import android.app.IActivityManager; +import android.content.ClipData; +import android.content.ClipDescription; +import android.content.IClipboard; +import android.content.IOnPrimaryClipChangedListener; import android.content.Context; +import android.content.Intent; +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; +import android.os.Parcel; +import android.os.Process; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.Pair; +import android.util.Slog; + +import java.util.HashSet; /** * Implementation of the clipboard for copy and paste. */ public class ClipboardService extends IClipboard.Stub { - private CharSequence mClipboard = ""; + private final Context mContext; + private final IActivityManager mAm; + private final PackageManager mPm; + private final IBinder mPermissionOwner; + + private final RemoteCallbackList<IOnPrimaryClipChangedListener> mPrimaryClipListeners + = new RemoteCallbackList<IOnPrimaryClipChangedListener>(); + + private ClipData mPrimaryClip; + + private final HashSet<String> mActivePermissionOwners + = new HashSet<String>(); /** * Instantiates the clipboard. */ - public ClipboardService(Context context) { } + public ClipboardService(Context context) { + mContext = context; + mAm = ActivityManagerNative.getDefault(); + mPm = context.getPackageManager(); + IBinder permOwner = null; + try { + permOwner = mAm.newUriPermissionOwner("clipboard"); + } catch (RemoteException e) { + Slog.w("clipboard", "AM dead", e); + } + mPermissionOwner = permOwner; + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + try { + return super.onTransact(code, data, reply, flags); + } catch (RuntimeException e) { + Slog.w("clipboard", "Exception: ", e); + throw e; + } + + } - // javadoc from interface - public void setClipboardText(CharSequence text) { + public void setPrimaryClip(ClipData clip) { synchronized (this) { - if (text == null) { - text = ""; + if (clip != null && clip.getItemCount() <= 0) { + throw new IllegalArgumentException("No items"); + } + checkDataOwnerLocked(clip, Binder.getCallingUid()); + clearActiveOwnersLocked(); + mPrimaryClip = clip; + final int n = mPrimaryClipListeners.beginBroadcast(); + for (int i = 0; i < n; i++) { + try { + mPrimaryClipListeners.getBroadcastItem(i).dispatchPrimaryClipChanged(); + } catch (RemoteException e) { + + // The RemoteCallbackList will take care of removing + // the dead object for us. + } } + mPrimaryClipListeners.finishBroadcast(); + } + } - mClipboard = text; + public ClipData getPrimaryClip(String pkg) { + synchronized (this) { + addActiveOwnerLocked(Binder.getCallingUid(), pkg); + return mPrimaryClip; } } - // javadoc from interface - public CharSequence getClipboardText() { + public ClipDescription getPrimaryClipDescription() { synchronized (this) { - return mClipboard; + return mPrimaryClip != null ? mPrimaryClip.getDescription() : null; + } + } + + public boolean hasPrimaryClip() { + synchronized (this) { + return mPrimaryClip != null; + } + } + + public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { + synchronized (this) { + mPrimaryClipListeners.register(listener); + } + } + + public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { + synchronized (this) { + mPrimaryClipListeners.unregister(listener); } } - // javadoc from interface public boolean hasClipboardText() { synchronized (this) { - return mClipboard.length() > 0; + if (mPrimaryClip != null) { + CharSequence text = mPrimaryClip.getItemAt(0).getText(); + return text != null && text.length() > 0; + } + return false; + } + } + + private final void checkUriOwnerLocked(Uri uri, int uid) { + if (!"content".equals(uri.getScheme())) { + return; + } + long ident = Binder.clearCallingIdentity(); + boolean allowed = false; + try { + // This will throw SecurityException for us. + mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private final void checkItemOwnerLocked(ClipData.Item item, int uid) { + if (item.getUri() != null) { + checkUriOwnerLocked(item.getUri(), uid); + } + Intent intent = item.getIntent(); + if (intent != null && intent.getData() != null) { + checkUriOwnerLocked(intent.getData(), uid); + } + } + + private final void checkDataOwnerLocked(ClipData data, int uid) { + final int N = data.getItemCount(); + for (int i=0; i<N; i++) { + checkItemOwnerLocked(data.getItemAt(i), uid); + } + } + + 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); + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private final void grantItemLocked(ClipData.Item item, String pkg) { + if (item.getUri() != null) { + grantUriLocked(item.getUri(), pkg); + } + Intent intent = item.getIntent(); + if (intent != null && intent.getData() != null) { + grantUriLocked(intent.getData(), pkg); + } + } + + private final void addActiveOwnerLocked(int uid, String pkg) { + PackageInfo pi; + try { + pi = mPm.getPackageInfo(pkg, 0); + if (pi.applicationInfo.uid != uid) { + throw new SecurityException("Calling uid " + uid + + " does not own package " + pkg); + } + } catch (NameNotFoundException e) { + throw new IllegalArgumentException("Unknown package " + pkg, e); + } + if (mPrimaryClip != null && !mActivePermissionOwners.contains(pkg)) { + final int N = mPrimaryClip.getItemCount(); + for (int i=0; i<N; i++) { + grantItemLocked(mPrimaryClip.getItemAt(i), pkg); + } + mActivePermissionOwners.add(pkg); + } + } + + 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); + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private final void revokeItemLocked(ClipData.Item item) { + if (item.getUri() != null) { + revokeUriLocked(item.getUri()); + } + Intent intent = item.getIntent(); + if (intent != null && intent.getData() != null) { + revokeUriLocked(intent.getData()); + } + } + + private final void clearActiveOwnersLocked() { + mActivePermissionOwners.clear(); + if (mPrimaryClip == null) { + return; + } + final int N = mPrimaryClip.getItemCount(); + for (int i=0; i<N; i++) { + revokeItemLocked(mPrimaryClip.getItemAt(i)); } } } diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index bc102e4..bd3c554 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -16,23 +16,31 @@ package com.android.server; -import android.app.Notification; -import android.app.NotificationManager; +import android.bluetooth.BluetoothTetheringDataTracker; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.database.ContentObserver; import android.net.ConnectivityManager; +import android.net.DummyDataStateTracker; import android.net.IConnectivityManager; +import android.net.LinkProperties; import android.net.MobileDataStateTracker; import android.net.NetworkInfo; import android.net.NetworkStateTracker; +import android.net.NetworkUtils; +import android.net.Proxy; +import android.net.ProxyProperties; +import android.net.vpn.VpnManager; import android.net.wifi.WifiStateTracker; import android.os.Binder; import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; @@ -42,12 +50,17 @@ import android.util.EventLog; import android.util.Slog; import com.android.internal.telephony.Phone; - import com.android.server.connectivity.Tethering; import java.io.FileDescriptor; +import java.io.FileWriter; +import java.io.IOException; import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.GregorianCalendar; import java.util.List; @@ -56,7 +69,7 @@ import java.util.List; */ public class ConnectivityService extends IConnectivityManager.Stub { - private static final boolean DBG = false; + private static final boolean DBG = true; private static final String TAG = "ConnectivityService"; // how long to wait before switching back to a radio's default network @@ -65,7 +78,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final String NETWORK_RESTORE_DELAY_PROP_NAME = "android.telephony.apn-restore"; - private Tethering mTethering; private boolean mTetheringConfigValid = false; @@ -82,6 +94,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private List mNetRequestersPids[]; + private WifiWatchdogService mWifiWatchdogService; + // priority order of the nettrackers // (excluding dynamically set mNetworkPreference) // TODO - move mNetworkTypePreference into this @@ -101,6 +115,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { private boolean mTestMode; private static ConnectivityService sServiceInstance; + private AtomicBoolean mBackgroundDataEnabled = new AtomicBoolean(true); + private static final int ENABLED = 1; private static final int DISABLED = 0; @@ -159,6 +175,19 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final int EVENT_SET_MOBILE_DATA = MAX_NETWORK_STATE_TRACKER_EVENT + 7; + /** + * used internally to clear a wakelock when transitioning + * from one net to another + */ + private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = + MAX_NETWORK_STATE_TRACKER_EVENT + 8; + + /** + * used internally to reload global proxy settings + */ + private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY = + MAX_NETWORK_STATE_TRACKER_EVENT + 9; + private Handler mHandler; // list of DeathRecipients used to make sure features are turned off when @@ -168,10 +197,25 @@ public class ConnectivityService extends IConnectivityManager.Stub { private boolean mSystemReady; private Intent mInitialBroadcast; + private PowerManager.WakeLock mNetTransitionWakeLock; + private String mNetTransitionWakeLockCausedBy = ""; + private int mNetTransitionWakeLockSerialNumber; + private int mNetTransitionWakeLockTimeout; + + private InetAddress mDefaultDns; + // used in DBG mode to track inet condition reports private static final int INET_CONDITION_LOG_MAX_SIZE = 15; private ArrayList mInetLog; + // track the current default http proxy - tell the world if we get a new one (real change) + private ProxyProperties mDefaultProxy = null; + // track the global proxy. + private ProxyProperties mGlobalProxy = null; + private final Object mGlobalProxyLock = new Object(); + + private SettingsObserver mSettingsObserver; + private static class NetworkAttributes { /** * Class for holding settings read from resources. @@ -207,64 +251,55 @@ public class ConnectivityService extends IConnectivityManager.Stub { } RadioAttributes[] mRadioAttributes; - private static class ConnectivityThread extends Thread { - private Context mContext; - - private ConnectivityThread(Context context) { - super("ConnectivityThread"); - mContext = context; - } - - @Override - public void run() { - Looper.prepare(); - synchronized (this) { - sServiceInstance = new ConnectivityService(mContext); - notifyAll(); - } - Looper.loop(); - } - - public static ConnectivityService getServiceInstance(Context context) { - ConnectivityThread thread = new ConnectivityThread(context); - thread.start(); - - synchronized (thread) { - while (sServiceInstance == null) { - try { - // Wait until sServiceInstance has been initialized. - thread.wait(); - } catch (InterruptedException ignore) { - Slog.e(TAG, - "Unexpected InterruptedException while waiting"+ - " for ConnectivityService thread"); - } - } - } - - return sServiceInstance; + public static synchronized ConnectivityService getInstance(Context context) { + if (sServiceInstance == null) { + sServiceInstance = new ConnectivityService(context); } - } - - public static ConnectivityService getInstance(Context context) { - return ConnectivityThread.getServiceInstance(context); + return sServiceInstance; } private ConnectivityService(Context context) { - if (DBG) Slog.v(TAG, "ConnectivityService starting up"); + if (DBG) log("ConnectivityService starting up"); + + HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread"); + handlerThread.start(); + mHandler = new MyHandler(handlerThread.getLooper()); + + mBackgroundDataEnabled.set(Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.BACKGROUND_DATA, 1) == 1); // setup our unique device name - String id = Settings.Secure.getString(context.getContentResolver(), - Settings.Secure.ANDROID_ID); - if (id != null && id.length() > 0) { - String name = new String("android_").concat(id); - SystemProperties.set("net.hostname", name); + if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) { + String id = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.ANDROID_ID); + if (id != null && id.length() > 0) { + String name = new String("android_").concat(id); + SystemProperties.set("net.hostname", name); + } + } + + // read our default dns server ip + String dns = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.DEFAULT_DNS_SERVER); + if (dns == null || dns.length() == 0) { + dns = context.getResources().getString( + com.android.internal.R.string.config_default_dns_server); + } + try { + mDefaultDns = InetAddress.getByName(dns); + } catch (UnknownHostException e) { + loge("Error setting defaultDns using " + dns); } mContext = context; + + PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + mNetTransitionWakeLockTimeout = mContext.getResources().getInteger( + com.android.internal.R.integer.config_networkTransitionTimeout); + mNetTrackers = new NetworkStateTracker[ ConnectivityManager.MAX_NETWORK_TYPE+1]; - mHandler = new MyHandler(); mNetworkPreference = getPersistedNetworkPreference(); @@ -277,11 +312,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { for (String raString : raStrings) { RadioAttributes r = new RadioAttributes(raString); if (r.mType > ConnectivityManager.MAX_RADIO_TYPE) { - Slog.e(TAG, "Error in radioAttributes - ignoring attempt to define type " + r.mType); + loge("Error in radioAttributes - ignoring attempt to define type " + r.mType); continue; } if (mRadioAttributes[r.mType] != null) { - Slog.e(TAG, "Error in radioAttributes - ignoring attempt to redefine type " + + loge("Error in radioAttributes - ignoring attempt to redefine type " + r.mType); continue; } @@ -294,17 +329,17 @@ public class ConnectivityService extends IConnectivityManager.Stub { try { NetworkAttributes n = new NetworkAttributes(naString); if (n.mType > ConnectivityManager.MAX_NETWORK_TYPE) { - Slog.e(TAG, "Error in networkAttributes - ignoring attempt to define type " + + loge("Error in networkAttributes - ignoring attempt to define type " + n.mType); continue; } if (mNetAttributes[n.mType] != null) { - Slog.e(TAG, "Error in networkAttributes - ignoring attempt to redefine type " + + loge("Error in networkAttributes - ignoring attempt to redefine type " + n.mType); continue; } if (mRadioAttributes[n.mRadio] == null) { - Slog.e(TAG, "Error in networkAttributes - ignoring attempt to use undefined " + + loge("Error in networkAttributes - ignoring attempt to use undefined " + "radio " + n.mRadio + " in network type " + n.mType); continue; } @@ -356,30 +391,37 @@ public class ConnectivityService extends IConnectivityManager.Stub { * the number of different network types is not going * to change very often. */ - boolean noMobileData = !getMobileDataEnabled(); for (int netType : mPriorityList) { switch (mNetAttributes[netType].mRadio) { case ConnectivityManager.TYPE_WIFI: - if (DBG) Slog.v(TAG, "Starting Wifi Service."); - WifiStateTracker wst = new WifiStateTracker(context, mHandler); - WifiService wifiService = new WifiService(context, wst); + if (DBG) log("Starting Wifi Service."); + WifiStateTracker wst = new WifiStateTracker(); + WifiService wifiService = new WifiService(context); ServiceManager.addService(Context.WIFI_SERVICE, wifiService); - wifiService.startWifi(); + wifiService.checkAndStartWifi(); mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst; - wst.startMonitoring(); + wst.startMonitoring(context, mHandler); + + //TODO: as part of WWS refactor, create only when needed + mWifiWatchdogService = new WifiWatchdogService(context); break; case ConnectivityManager.TYPE_MOBILE: - mNetTrackers[netType] = new MobileDataStateTracker(context, mHandler, - netType, mNetAttributes[netType].mName); - mNetTrackers[netType].startMonitoring(); - if (noMobileData) { - if (DBG) Slog.d(TAG, "tearing down Mobile networks due to setting"); - mNetTrackers[netType].teardown(); - } + mNetTrackers[netType] = new MobileDataStateTracker(netType, + mNetAttributes[netType].mName); + mNetTrackers[netType].startMonitoring(context, mHandler); + break; + case ConnectivityManager.TYPE_DUMMY: + mNetTrackers[netType] = new DummyDataStateTracker(netType, + mNetAttributes[netType].mName); + mNetTrackers[netType].startMonitoring(context, mHandler); + break; + case ConnectivityManager.TYPE_BLUETOOTH: + mNetTrackers[netType] = BluetoothTetheringDataTracker.getInstance(); + mNetTrackers[netType].startMonitoring(context, mHandler); break; default: - Slog.e(TAG, "Trying to create a DataStateTracker for an unknown radio type " + + loge("Trying to create a DataStateTracker for an unknown radio type " + mNetAttributes[netType].mRadio); continue; } @@ -389,12 +431,20 @@ public class ConnectivityService extends IConnectivityManager.Stub { mTetheringConfigValid = (((mNetTrackers[ConnectivityManager.TYPE_MOBILE_DUN] != null) || !mTethering.isDunRequired()) && (mTethering.getTetherableUsbRegexs().length != 0 || - mTethering.getTetherableWifiRegexs().length != 0) && + mTethering.getTetherableWifiRegexs().length != 0 || + mTethering.getTetherableBluetoothRegexs().length != 0) && mTethering.getUpstreamIfaceRegexs().length != 0); if (DBG) { mInetLog = new ArrayList(); } + + mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY); + mSettingsObserver.observe(mContext); + + loadGlobalProxy(); + + VpnManager.startVpnService(context); } @@ -462,8 +512,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (t != mNetworkPreference && mNetTrackers[t] != null && mNetTrackers[t].getNetworkInfo().isConnected()) { if (DBG) { - Slog.d(TAG, "tearing down " + - mNetTrackers[t].getNetworkInfo() + + log("tearing down " + mNetTrackers[t].getNetworkInfo() + " in enforcePreference"); } teardown(mNetTrackers[t]); @@ -496,9 +545,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkStateTracker t = mNetTrackers[type]; NetworkInfo info = t.getNetworkInfo(); if (info.isConnected()) { - if (DBG && type != mActiveDefaultNetwork) Slog.e(TAG, - "connected default network is not " + - "mActiveDefaultNetwork!"); + if (DBG && type != mActiveDefaultNetwork) { + loge("connected default network is not mActiveDefaultNetwork!"); + } return info; } } @@ -525,6 +574,38 @@ public class ConnectivityService extends IConnectivityManager.Stub { return result; } + /** + * Return LinkProperties for the active (i.e., connected) default + * network interface. It is assumed that at most one default network + * is active at a time. If more than one is active, it is indeterminate + * which will be returned. + * @return the ip properties for the active network, or {@code null} if + * none is active + */ + public LinkProperties getActiveLinkProperties() { + enforceAccessPermission(); + for (int type=0; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) { + if (mNetAttributes[type] == null || !mNetAttributes[type].isDefault()) { + continue; + } + NetworkStateTracker t = mNetTrackers[type]; + NetworkInfo info = t.getNetworkInfo(); + if (info.isConnected()) { + return t.getLinkProperties(); + } + } + return null; + } + + public LinkProperties getLinkProperties(int networkType) { + enforceAccessPermission(); + if (ConnectivityManager.isNetworkTypeValid(networkType)) { + NetworkStateTracker t = mNetTrackers[networkType]; + if (t != null) return t.getLinkProperties(); + } + return null; + } + public boolean setRadios(boolean turnOn) { boolean result = true; enforceChangePermission(); @@ -579,14 +660,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { } public void binderDied() { - Slog.d(TAG, "ConnectivityService FeatureUser binderDied(" + + log("ConnectivityService FeatureUser binderDied(" + mNetworkType + ", " + mFeature + ", " + mBinder + "), created " + (System.currentTimeMillis() - mCreateTime) + " mSec ago"); stopUsingNetworkFeature(this, false); } public void expire() { - Slog.d(TAG, "ConnectivityService FeatureUser expire(" + + log("ConnectivityService FeatureUser expire(" + mNetworkType + ", " + mFeature + ", " + mBinder +"), created " + (System.currentTimeMillis() - mCreateTime) + " mSec ago"); stopUsingNetworkFeature(this, false); @@ -602,8 +683,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { public int startUsingNetworkFeature(int networkType, String feature, IBinder binder) { if (DBG) { - Slog.d(TAG, "startUsingNetworkFeature for net " + networkType + - ": " + feature); + log("startUsingNetworkFeature for net " + networkType + ": " + feature); } enforceChangePermission(); if (!ConnectivityManager.isNetworkTypeValid(networkType) || @@ -616,15 +696,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { // TODO - move this into the MobileDataStateTracker int usedNetworkType = networkType; if(networkType == ConnectivityManager.TYPE_MOBILE) { - if (!getMobileDataEnabled()) { - if (DBG) Slog.d(TAG, "requested special network with data disabled - rejected"); - return Phone.APN_TYPE_NOT_AVAILABLE; - } if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS; } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL; - } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN)) { + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN) || + TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN; } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI; @@ -632,15 +709,18 @@ public class ConnectivityService extends IConnectivityManager.Stub { } NetworkStateTracker network = mNetTrackers[usedNetworkType]; if (network != null) { + Integer currentPid = new Integer(getCallingPid()); if (usedNetworkType != networkType) { - Integer currentPid = new Integer(getCallingPid()); - NetworkStateTracker radio = mNetTrackers[networkType]; NetworkInfo ni = network.getNetworkInfo(); if (ni.isAvailable() == false) { - if (DBG) Slog.d(TAG, "special network not available"); - return Phone.APN_TYPE_NOT_AVAILABLE; + if (DBG) log("special network not available"); + if (!TextUtils.equals(feature,Phone.FEATURE_ENABLE_DUN_ALWAYS)) { + return Phone.APN_TYPE_NOT_AVAILABLE; + } else { + // else make the attempt anyway - probably giving REQUEST_STARTED below + } } synchronized(this) { @@ -659,28 +739,29 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (ni.isConnected() == true) { // add the pid-specific dns handleDnsConfigurationChange(networkType); - if (DBG) Slog.d(TAG, "special network already active"); + if (DBG) log("special network already active"); return Phone.APN_ALREADY_ACTIVE; } - if (DBG) Slog.d(TAG, "special network already connecting"); + if (DBG) log("special network already connecting"); return Phone.APN_REQUEST_STARTED; } // check if the radio in play can make another contact // assume if cannot for now - if (DBG) Slog.d(TAG, "reconnecting to special network"); + if (DBG) log("reconnecting to special network"); network.reconnect(); return Phone.APN_REQUEST_STARTED; } else { + // need to remember this unsupported request so we respond appropriately on stop synchronized(this) { mFeatureUsers.add(f); + if (!mNetRequestersPids[usedNetworkType].contains(currentPid)) { + // this gets used for per-pid dns when connected + mNetRequestersPids[usedNetworkType].add(currentPid); + } } - mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_RESTORE_DEFAULT_NETWORK, - f), getRestoreDefaultNetworkDelay()); - - return network.startUsingNetworkFeature(feature, - getCallingPid(), getCallingUid()); + return -1; } } return Phone.APN_TYPE_NOT_AVAILABLE; @@ -712,7 +793,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { return stopUsingNetworkFeature(u, true); } else { // none found! - if (DBG) Slog.d(TAG, "ignoring stopUsingNetworkFeature - not a live request"); + if (DBG) log("ignoring stopUsingNetworkFeature - not a live request"); return 1; } } @@ -727,7 +808,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { boolean callTeardown = false; // used to carry our decision outside of sync block if (DBG) { - Slog.d(TAG, "stopUsingNetworkFeature for net " + networkType + + log("stopUsingNetworkFeature for net " + networkType + ": " + feature); } @@ -740,7 +821,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { synchronized(this) { // check if this process still has an outstanding start request if (!mFeatureUsers.contains(u)) { - if (DBG) Slog.d(TAG, "ignoring - this process has no outstanding requests"); + if (DBG) log("ignoring - this process has no outstanding requests"); return 1; } u.unlinkDeathRecipient(); @@ -758,7 +839,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (x.mUid == u.mUid && x.mPid == u.mPid && x.mNetworkType == u.mNetworkType && TextUtils.equals(x.mFeature, u.mFeature)) { - if (DBG) Slog.d(TAG, "ignoring stopUsingNetworkFeature as dup is found"); + if (DBG) log("ignoring stopUsingNetworkFeature as dup is found"); return 1; } } @@ -771,7 +852,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS; } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL; - } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN)) { + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN) || + TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN; } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI; @@ -779,7 +861,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } tracker = mNetTrackers[usedNetworkType]; if (tracker == null) { - if (DBG) Slog.d(TAG, "ignoring - no known tracker for net type " + usedNetworkType); + if (DBG) log("ignoring - no known tracker for net type " + usedNetworkType); return -1; } if (usedNetworkType != networkType) { @@ -787,24 +869,27 @@ public class ConnectivityService extends IConnectivityManager.Stub { mNetRequestersPids[usedNetworkType].remove(currentPid); reassessPidDns(pid, true); if (mNetRequestersPids[usedNetworkType].size() != 0) { - if (DBG) Slog.d(TAG, "not tearing down special network - " + + if (DBG) log("not tearing down special network - " + "others still using it"); return 1; } callTeardown = true; + } else { + if (DBG) log("not a known feature - dropping"); } } - if (DBG) Slog.d(TAG, "Doing network teardown"); + if (DBG) log("Doing network teardown"); if (callTeardown) { tracker.teardown(); return 1; } else { - // do it the old fashioned way - return tracker.stopUsingNetworkFeature(feature, pid, uid); + return -1; } } /** + * @deprecated use requestRouteToHostAddress instead + * * Ensure that a network route exists to deliver traffic to the specified * host via the specified network interface. * @param networkType the type of the network over which traffic to the @@ -814,6 +899,25 @@ public class ConnectivityService extends IConnectivityManager.Stub { * @return {@code true} on success, {@code false} on failure */ public boolean requestRouteToHost(int networkType, int hostAddress) { + InetAddress inetAddress = NetworkUtils.intToInetAddress(hostAddress); + + if (inetAddress == null) { + return false; + } + + return requestRouteToHostAddress(networkType, inetAddress.getAddress()); + } + + /** + * Ensure that a network route exists to deliver traffic to the specified + * host via the specified network interface. + * @param networkType the type of the network over which traffic to the + * specified host is to be routed + * @param hostAddress the IP address of the host to which the route is + * desired + * @return {@code true} on success, {@code false} on failure + */ + public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress) { enforceChangePermission(); if (!ConnectivityManager.isNetworkTypeValid(networkType)) { return false; @@ -823,19 +927,51 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (tracker == null || !tracker.getNetworkInfo().isConnected() || tracker.isTeardownRequested()) { if (DBG) { - Slog.d(TAG, "requestRouteToHost on down network (" + networkType + ") - dropped"); + log("requestRouteToHostAddress on down network " + + "(" + networkType + ") - dropped"); } return false; } - return tracker.requestRouteToHost(hostAddress); + try { + InetAddress addr = InetAddress.getByAddress(hostAddress); + return addHostRoute(tracker, addr); + } catch (UnknownHostException e) {} + return false; + } + + /** + * Ensure that a network route exists to deliver traffic to the specified + * host via the mobile data network. + * @param hostAddress the IP address of the host to which the route is desired, + * in network byte order. + * TODO - deprecate + * @return {@code true} on success, {@code false} on failure + */ + private boolean addHostRoute(NetworkStateTracker nt, InetAddress hostAddress) { + if (nt.getNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI) { + return false; + } + + LinkProperties p = nt.getLinkProperties(); + if (p == null) return false; + String interfaceName = p.getInterfaceName(); + + if (DBG) { + log("Requested host route to " + hostAddress + "(" + interfaceName + ")"); + } + if (interfaceName != null) { + return NetworkUtils.addHostRoute(interfaceName, hostAddress, null); + } else { + if (DBG) loge("addHostRoute failed due to null interface name"); + return false; + } } /** * @see ConnectivityManager#getBackgroundDataSetting() */ public boolean getBackgroundDataSetting() { - return Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.BACKGROUND_DATA, 1) == 1; + return mBackgroundDataEnabled.get(); } /** @@ -846,6 +982,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { android.Manifest.permission.CHANGE_BACKGROUND_DATA_SETTING, "ConnectivityService"); + mBackgroundDataEnabled.set(allowBackgroundDataUsage); + mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_BACKGROUND_DATA, (allowBackgroundDataUsage ? ENABLED : DISABLED), 0)); } @@ -864,10 +1002,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { * @see ConnectivityManager#getMobileDataEnabled() */ public boolean getMobileDataEnabled() { + // TODO: This detail should probably be in DataConnectionTracker's + // which is where we store the value and maybe make this + // asynchronous. enforceAccessPermission(); boolean retVal = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.MOBILE_DATA, 1) == 1; - if (DBG) Slog.d(TAG, "getMobileDataEnabled returning " + retVal); + if (DBG) log("getMobileDataEnabled returning " + retVal); return retVal; } @@ -876,47 +1017,19 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ public void setMobileDataEnabled(boolean enabled) { enforceChangePermission(); - if (DBG) Slog.d(TAG, "setMobileDataEnabled(" + enabled + ")"); + if (DBG) log("setMobileDataEnabled(" + enabled + ")"); mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_MOBILE_DATA, (enabled ? ENABLED : DISABLED), 0)); } private void handleSetMobileData(boolean enabled) { - if (getMobileDataEnabled() == enabled) return; - - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.MOBILE_DATA, enabled ? 1 : 0); - - if (enabled) { - if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) { - if (DBG) { - Slog.d(TAG, "starting up " + mNetTrackers[ConnectivityManager.TYPE_MOBILE]); - } - mNetTrackers[ConnectivityManager.TYPE_MOBILE].reconnect(); - } - } else { - for (NetworkStateTracker nt : mNetTrackers) { - if (nt == null) continue; - int netType = nt.getNetworkInfo().getType(); - if (mNetAttributes[netType].mRadio == ConnectivityManager.TYPE_MOBILE) { - if (DBG) Slog.d(TAG, "tearing down " + nt); - nt.teardown(); - } - } - } - } - - private int getNumConnectedNetworks() { - int numConnectedNets = 0; - - for (NetworkStateTracker nt : mNetTrackers) { - if (nt != null && nt.getNetworkInfo().isConnected() && - !nt.isTeardownRequested()) { - ++numConnectedNets; + if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) { + if (DBG) { + Slog.d(TAG, mNetTrackers[ConnectivityManager.TYPE_MOBILE].toString() + enabled); } + mNetTrackers[ConnectivityManager.TYPE_MOBILE].setDataEnable(enabled); } - return numConnectedNets; } private void enforceAccessPermission() { @@ -944,6 +1057,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { "ConnectivityService"); } + private void enforceConnectivityInternalPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CONNECTIVITY_INTERNAL, + "ConnectivityService"); + } + /** * Handle a {@code DISCONNECTED} event. If this pertains to the non-active * network, we ignore it. If it is for the active network, we send out a @@ -987,18 +1106,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { info.getExtraInfo()); } - NetworkStateTracker newNet = null; if (mNetAttributes[prevNetType].isDefault()) { - newNet = tryFailover(prevNetType); - if (newNet != null) { - NetworkInfo switchTo = newNet.getNetworkInfo(); - if (!switchTo.isConnected()) { - // if the other net is connected they've already reset this and perhaps even gotten - // a positive report we don't want to overwrite, but if not we need to clear this now - // to turn our cellular sig strength white - mDefaultInetConditionPublished = 0; - intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); - } + tryFailover(prevNetType); + if (mActiveDefaultNetwork != -1) { + NetworkInfo switchTo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(); intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo); } else { mDefaultInetConditionPublished = 0; // we're not connected anymore @@ -1014,86 +1125,45 @@ public class ConnectivityService extends IConnectivityManager.Stub { * If the failover network is already connected, then immediately send * out a followup broadcast indicating successful failover */ - if (newNet != null && newNet.getNetworkInfo().isConnected()) { - sendConnectedBroadcast(newNet.getNetworkInfo()); + if (mActiveDefaultNetwork != -1) { + sendConnectedBroadcast(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo()); } } - // returns null if no failover available - private NetworkStateTracker tryFailover(int prevNetType) { + private void tryFailover(int prevNetType) { /* - * If this is a default network, check if other defaults are available - * or active + * If this is a default network, check if other defaults are available. + * Try to reconnect on all available and let them hash it out when + * more than one connects. */ - NetworkStateTracker newNet = null; if (mNetAttributes[prevNetType].isDefault()) { if (mActiveDefaultNetwork == prevNetType) { mActiveDefaultNetwork = -1; } - int newType = -1; - int newPriority = -1; - boolean noMobileData = !getMobileDataEnabled(); + // don't signal a reconnect for anything lower or equal priority than our + // current connected default + // TODO - don't filter by priority now - nice optimization but risky +// int currentPriority = -1; +// if (mActiveDefaultNetwork != -1) { +// currentPriority = mNetAttributes[mActiveDefaultNetwork].mPriority; +// } for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) { if (checkType == prevNetType) continue; if (mNetAttributes[checkType] == null) continue; - if (mNetAttributes[checkType].mRadio == ConnectivityManager.TYPE_MOBILE && - noMobileData) { - Slog.e(TAG, "not failing over to mobile type " + checkType + - " because Mobile Data Disabled"); - continue; - } - if (mNetAttributes[checkType].isDefault()) { - /* TODO - if we have multiple nets we could use - * we may want to put more thought into which we choose - */ - if (checkType == mNetworkPreference) { - newType = checkType; - break; - } - if (mNetAttributes[checkType].mPriority > newPriority) { - newType = checkType; - newPriority = mNetAttributes[newType].mPriority; - } + if (!mNetAttributes[checkType].isDefault()) continue; + if (!mNetTrackers[checkType].isAvailable()) continue; +// if (currentPriority >= mNetAttributes[checkType].mPriority) continue; + + NetworkStateTracker checkTracker = mNetTrackers[checkType]; + NetworkInfo checkInfo = checkTracker.getNetworkInfo(); + if (!checkInfo.isConnectedOrConnecting() || checkTracker.isTeardownRequested()) { + checkInfo.setFailover(true); + checkTracker.reconnect(); } - } - - if (newType != -1) { - newNet = mNetTrackers[newType]; - /** - * See if the other network is available to fail over to. - * If is not available, we enable it anyway, so that it - * will be able to connect when it does become available, - * but we report a total loss of connectivity rather than - * report that we are attempting to fail over. - */ - if (newNet.isAvailable()) { - NetworkInfo switchTo = newNet.getNetworkInfo(); - switchTo.setFailover(true); - if (!switchTo.isConnectedOrConnecting() || - newNet.isTeardownRequested()) { - newNet.reconnect(); - } - if (DBG) { - if (switchTo.isConnected()) { - Slog.v(TAG, "Switching to already connected " + - switchTo.getTypeName()); - } else { - Slog.v(TAG, "Attempting to switch to " + - switchTo.getTypeName()); - } - } - } else { - newNet.reconnect(); - newNet = null; // not officially avail.. try anyway, but - // report no failover - } - } else { - Slog.e(TAG, "Network failover failing."); + if (DBG) log("Attempting to switch to " + checkInfo.getTypeName()); } } - - return newNet; } private void sendConnectedBroadcast(NetworkInfo info) { @@ -1138,7 +1208,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } else { reasonText = " (" + reason + ")."; } - Slog.e(TAG, "Attempt to connect to " + info.getTypeName() + " failed" + reasonText); + loge("Attempt to connect to " + info.getTypeName() + " failed" + reasonText); Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); @@ -1156,17 +1226,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { info.setFailover(false); } - NetworkStateTracker newNet = null; if (mNetAttributes[info.getType()].isDefault()) { - newNet = tryFailover(info.getType()); - if (newNet != null) { - NetworkInfo switchTo = newNet.getNetworkInfo(); - if (!switchTo.isConnected()) { - // if the other net is connected they've already reset this and perhaps - // even gotten a positive report we don't want to overwrite, but if not - // we need to clear this now to turn our cellular sig strength white - mDefaultInetConditionPublished = 0; - } + tryFailover(info.getType()); + if (mActiveDefaultNetwork != -1) { + NetworkInfo switchTo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(); intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo); } else { mDefaultInetConditionPublished = 0; @@ -1180,8 +1243,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { * If the failover network is already connected, then immediately send * out a followup broadcast indicating successful failover */ - if (newNet != null && newNet.getNetworkInfo().isConnected()) { - sendConnectedBroadcast(newNet.getNetworkInfo()); + if (mActiveDefaultNetwork != -1) { + sendConnectedBroadcast(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo()); } } @@ -1203,6 +1266,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { mInitialBroadcast = null; } } + // load the global proxy at startup + mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY)); } private void handleConnect(NetworkInfo info) { @@ -1221,24 +1286,35 @@ public class ConnectivityService extends IConnectivityManager.Stub { mNetAttributes[type].mPriority) || mNetworkPreference == mActiveDefaultNetwork) { // don't accept this one - if (DBG) Slog.v(TAG, "Not broadcasting CONNECT_ACTION " + + if (DBG) { + log("Not broadcasting CONNECT_ACTION " + "to torn down network " + info.getTypeName()); + } teardown(thisNet); return; } else { // tear down the other NetworkStateTracker otherNet = mNetTrackers[mActiveDefaultNetwork]; - if (DBG) Slog.v(TAG, "Policy requires " + - otherNet.getNetworkInfo().getTypeName() + + if (DBG) { + log("Policy requires " + otherNet.getNetworkInfo().getTypeName() + " teardown"); + } if (!teardown(otherNet)) { - Slog.e(TAG, "Network declined teardown request"); + loge("Network declined teardown request"); return; } - if (isFailover) { - otherNet.releaseWakeLock(); - } + } + } + 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); } } mActiveDefaultNetwork = type; @@ -1252,36 +1328,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { //reportNetworkCondition(mActiveDefaultNetwork, 100); } thisNet.setTeardownRequested(false); - thisNet.updateNetworkSettings(); + updateNetworkSettings(thisNet); handleConnectivityChange(type); sendConnectedBroadcast(info); } - private void handleScanResultsAvailable(NetworkInfo info) { - int networkType = info.getType(); - if (networkType != ConnectivityManager.TYPE_WIFI) { - if (DBG) Slog.v(TAG, "Got ScanResultsAvailable for " + - info.getTypeName() + " network. Don't know how to handle."); - } - - mNetTrackers[networkType].interpretScanResultsAvailable(); - } - - private void handleNotificationChange(boolean visible, int id, - Notification notification) { - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - - if (visible) { - notificationManager.notify(id, notification); - } else { - notificationManager.cancel(id); - } - } - /** - * After a change in the connectivity state of any network, We're mainly - * concerned with making sure that the list of DNS servers is setupup + * After a change in the connectivity state of a network. We're mainly + * 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. */ @@ -1294,19 +1348,159 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (mNetTrackers[netType].getNetworkInfo().isConnected()) { if (mNetAttributes[netType].isDefault()) { - mNetTrackers[netType].addDefaultRoute(); + handleApplyDefaultProxy(netType); + addDefaultRoute(mNetTrackers[netType]); } else { - mNetTrackers[netType].addPrivateDnsRoutes(); + addPrivateDnsRoutes(mNetTrackers[netType]); } } else { if (mNetAttributes[netType].isDefault()) { - mNetTrackers[netType].removeDefaultRoute(); + removeDefaultRoute(mNetTrackers[netType]); + } else { + removePrivateDnsRoutes(mNetTrackers[netType]); + } + } + } + + private void addPrivateDnsRoutes(NetworkStateTracker nt) { + boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet(); + LinkProperties p = nt.getLinkProperties(); + if (p == null) return; + String interfaceName = p.getInterfaceName(); + + if (DBG) { + log("addPrivateDnsRoutes for " + nt + + "(" + interfaceName + ") - mPrivateDnsRouteSet = " + privateDnsRouteSet); + } + if (interfaceName != null && !privateDnsRouteSet) { + Collection<InetAddress> dnsList = p.getDnses(); + for (InetAddress dns : dnsList) { + if (DBG) log(" adding " + dns); + NetworkUtils.addHostRoute(interfaceName, dns, null); + } + nt.privateDnsRouteSet(true); + } + } + + private void removePrivateDnsRoutes(NetworkStateTracker nt) { + // TODO - we should do this explicitly but the NetUtils api doesnt + // support this yet - must remove all. No worse than before + LinkProperties p = nt.getLinkProperties(); + if (p == null) return; + String interfaceName = p.getInterfaceName(); + boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet(); + if (interfaceName != null && privateDnsRouteSet) { + if (DBG) { + log("removePrivateDnsRoutes for " + nt.getNetworkInfo().getTypeName() + + " (" + interfaceName + ")"); + } + NetworkUtils.removeHostRoutes(interfaceName); + nt.privateDnsRouteSet(false); + } + } + + + private void addDefaultRoute(NetworkStateTracker nt) { + LinkProperties p = nt.getLinkProperties(); + if (p == null) return; + String interfaceName = p.getInterfaceName(); + InetAddress defaultGatewayAddr = p.getGateway(); + + if ((interfaceName != null) && (defaultGatewayAddr != null )) { + if (!NetworkUtils.addDefaultRoute(interfaceName, defaultGatewayAddr) && DBG) { + NetworkInfo networkInfo = nt.getNetworkInfo(); + log("addDefaultRoute for " + networkInfo.getTypeName() + + " (" + interfaceName + "), GatewayAddr=" + defaultGatewayAddr); + } + } + } + + + public void removeDefaultRoute(NetworkStateTracker nt) { + LinkProperties p = nt.getLinkProperties(); + if (p == null) return; + String interfaceName = p.getInterfaceName(); + + if (interfaceName != null) { + if ((NetworkUtils.removeDefaultRoute(interfaceName) >= 0) && DBG) { + NetworkInfo networkInfo = nt.getNetworkInfo(); + log("removeDefaultRoute for " + networkInfo.getTypeName() + " (" + + interfaceName + ")"); + } + } + } + + /** + * Reads the network specific TCP buffer sizes from SystemProperties + * net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system + * wide use + */ + public void updateNetworkSettings(NetworkStateTracker nt) { + String key = nt.getTcpBufferSizesPropName(); + String bufferSizes = SystemProperties.get(key); + + if (bufferSizes.length() == 0) { + loge(key + " not found in system properties. Using defaults"); + + // Setting to default values so we won't be stuck to previous values + key = "net.tcp.buffersize.default"; + bufferSizes = SystemProperties.get(key); + } + + // Set values in kernel + if (bufferSizes.length() != 0) { + if (DBG) { + log("Setting TCP values: [" + bufferSizes + + "] which comes from [" + key + "]"); + } + setBufferSize(bufferSizes); + } + } + + /** + * Writes TCP buffer sizes to /sys/kernel/ipv4/tcp_[r/w]mem_[min/def/max] + * which maps to /proc/sys/net/ipv4/tcp_rmem and tcpwmem + * + * @param bufferSizes in the format of "readMin, readInitial, readMax, + * writeMin, writeInitial, writeMax" + */ + private void setBufferSize(String bufferSizes) { + try { + String[] values = bufferSizes.split(","); + + if (values.length == 6) { + final String prefix = "/sys/kernel/ipv4/tcp_"; + stringToFile(prefix + "rmem_min", values[0]); + stringToFile(prefix + "rmem_def", values[1]); + stringToFile(prefix + "rmem_max", values[2]); + stringToFile(prefix + "wmem_min", values[3]); + stringToFile(prefix + "wmem_def", values[4]); + stringToFile(prefix + "wmem_max", values[5]); } else { - mNetTrackers[netType].removePrivateDnsRoutes(); + loge("Invalid buffersize string: " + bufferSizes); } + } catch (IOException e) { + loge("Can't set tcp buffer sizes:" + e); } } + /** + * Writes string to file. Basically same as "echo -n $string > $filename" + * + * @param filename + * @param string + * @throws IOException + */ + private void stringToFile(String filename, String string) throws IOException { + FileWriter out = new FileWriter(filename); + try { + out.write(string); + } finally { + out.close(); + } + } + + /** * Adjust the per-process dns entries (net.dns<x>.<pid>) based * on the highest priority active net which this process requested. @@ -1314,7 +1508,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private void reassessPidDns(int myPid, boolean doBump) { - if (DBG) Slog.d(TAG, "reassessPidDns for pid " + myPid); + if (DBG) log("reassessPidDns for pid " + myPid); for(int i : mPriorityList) { if (mNetAttributes[i].isDefault()) { continue; @@ -1322,12 +1516,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkStateTracker nt = mNetTrackers[i]; if (nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { + LinkProperties p = nt.getLinkProperties(); + if (p == null) continue; List pids = mNetRequestersPids[i]; for (int j=0; j<pids.size(); j++) { Integer pid = (Integer)pids.get(j); if (pid.intValue() == myPid) { - String[] dnsList = nt.getNameServers(); - writePidDns(dnsList, myPid); + Collection<InetAddress> dnses = p.getDnses(); + writePidDns(dnses, myPid); if (doBump) { bumpDns(); } @@ -1349,13 +1545,18 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - private void writePidDns(String[] dnsList, int pid) { + // return true if results in a change + private boolean writePidDns(Collection <InetAddress> dnses, int pid) { int j = 1; - for (String dns : dnsList) { - if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) { - SystemProperties.set("net.dns" + j++ + "." + pid, dns); + boolean changed = false; + for (InetAddress dns : dnses) { + String dnsString = dns.getHostAddress(); + if (changed || !dnsString.equals(SystemProperties.get("net.dns" + j + "." + pid))) { + changed = true; + SystemProperties.set("net.dns" + j++ + "." + pid, dns.getHostAddress()); } } + return changed; } private void bumpDns() { @@ -1371,27 +1572,59 @@ public class ConnectivityService extends IConnectivityManager.Stub { } catch (NumberFormatException e) {} } SystemProperties.set("net.dnschange", "" + (n+1)); + /* + * Tell the VMs to toss their DNS caches + */ + Intent intent = new Intent(Intent.ACTION_CLEAR_DNS_CACHE); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + /* + * Connectivity events can happen before boot has completed ... + */ + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mContext.sendBroadcast(intent); } private void handleDnsConfigurationChange(int netType) { // add default net's dns entries NetworkStateTracker nt = mNetTrackers[netType]; if (nt != null && nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { - String[] dnsList = nt.getNameServers(); + LinkProperties p = nt.getLinkProperties(); + if (p == null) return; + Collection<InetAddress> dnses = p.getDnses(); + boolean changed = false; if (mNetAttributes[netType].isDefault()) { int j = 1; - for (String dns : dnsList) { - if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) { + if (dnses.size() == 0 && mDefaultDns != null) { + String dnsString = mDefaultDns.getHostAddress(); + if (!dnsString.equals(SystemProperties.get("net.dns1"))) { if (DBG) { - Slog.d(TAG, "adding dns " + dns + " for " + + log("no dns provided - using " + dnsString); + } + changed = true; + SystemProperties.set("net.dns1", dnsString); + } + j++; + } else { + for (InetAddress dns : dnses) { + String dnsString = dns.getHostAddress(); + if (!changed && dnsString.equals(SystemProperties.get("net.dns" + j))) { + j++; + continue; + } + if (DBG) { + log("adding dns " + dns + " for " + nt.getNetworkInfo().getTypeName()); } - SystemProperties.set("net.dns" + j++, dns); + changed = true; + SystemProperties.set("net.dns" + j++, dnsString); } } for (int k=j ; k<mNumDnsEntries; k++) { - if (DBG) Slog.d(TAG, "erasing net.dns" + k); - SystemProperties.set("net.dns" + k, ""); + if (changed || !TextUtils.isEmpty(SystemProperties.get("net.dns" + k))) { + if (DBG) log("erasing net.dns" + k); + changed = true; + SystemProperties.set("net.dns" + k, ""); + } } mNumDnsEntries = j; } else { @@ -1399,11 +1632,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { List pids = mNetRequestersPids[netType]; for (int y=0; y< pids.size(); y++) { Integer pid = (Integer)pids.get(y); - writePidDns(dnsList, pid.intValue()); + changed = writePidDns(dnses, pid.intValue()); } } + if (changed) bumpDns(); } - bumpDns(); } private int getRestoreDefaultNetworkDelay() { @@ -1458,6 +1691,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { } pw.println(); + synchronized (this) { + pw.println("NetworkTranstionWakeLock is currently " + + (mNetTransitionWakeLock.isHeld() ? "" : "not ") + "held."); + pw.println("It was last requested for "+mNetTransitionWakeLockCausedBy); + } + pw.println(); + mTethering.dump(fd, pw, args); if (mInetLog != null) { @@ -1471,6 +1711,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { // must be stateless - things change under us. private class MyHandler extends Handler { + public MyHandler(Looper looper) { + super(looper); + } + @Override public void handleMessage(Message msg) { NetworkInfo info; @@ -1489,7 +1733,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (DBG) { // TODO - remove this after we validate the dropping doesn't break // anything - Slog.d(TAG, "Dropping ConnectivityChange for " + + log("Dropping ConnectivityChange for " + info.getTypeName() + ": " + state + "/" + info.getDetailedState()); } @@ -1497,7 +1741,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } mNetAttributes[type].mLastState = state; - if (DBG) Slog.d(TAG, "ConnectivityChange for " + + if (DBG) log("ConnectivityChange for " + info.getTypeName() + ": " + state + "/" + info.getDetailedState()); @@ -1532,29 +1776,23 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleConnect(info); } break; - - case NetworkStateTracker.EVENT_SCAN_RESULTS_AVAILABLE: - info = (NetworkInfo) msg.obj; - handleScanResultsAvailable(info); - break; - - case NetworkStateTracker.EVENT_NOTIFICATION_CHANGED: - handleNotificationChange(msg.arg1 == 1, msg.arg2, - (Notification) msg.obj); - break; - case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: info = (NetworkInfo) msg.obj; type = info.getType(); - handleDnsConfigurationChange(type); + handleConnectivityChange(type); break; - - case NetworkStateTracker.EVENT_ROAMING_CHANGED: - // fill me in - break; - - case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: - // fill me in + case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: + String causedBy = null; + synchronized (ConnectivityService.this) { + if (msg.arg1 == mNetTransitionWakeLockSerialNumber && + mNetTransitionWakeLock.isHeld()) { + mNetTransitionWakeLock.release(); + causedBy = mNetTransitionWakeLockCausedBy; + } + } + if (causedBy != null) { + log("NetTransition Wakelock for " + causedBy + " released by timeout"); + } break; case EVENT_RESTORE_DEFAULT_NETWORK: FeatureUser u = (FeatureUser)msg.obj; @@ -1592,6 +1830,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleSetMobileData(enabled); break; } + case EVENT_APPLY_GLOBAL_HTTP_PROXY: + { + handleDeprecatedGlobalHttpProxy(); + } } } } @@ -1648,6 +1890,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + public String[] getTetherableBluetoothRegexs() { + enforceTetherAccessPermission(); + if (isTetheringSupported()) { + return mTethering.getTetherableBluetoothRegexs(); + } else { + return new String[0]; + } + } + // TODO - move iface listing, queries, etc to new module // javadoc from interface public String[] getTetherableIfaces() { @@ -1676,9 +1927,28 @@ public class ConnectivityService extends IConnectivityManager.Stub { return tetherEnabledInSettings && mTetheringConfigValid; } + // An API NetworkStateTrackers can call when they lose their network. + // This will automatically be cleared after X seconds or a network becomes CONNECTED, + // whichever happens first. The timer is started by the first caller and not + // restarted by subsequent callers. + public void requestNetworkTransitionWakelock(String forWhom) { + enforceConnectivityInternalPermission(); + synchronized (this) { + if (mNetTransitionWakeLock.isHeld()) return; + mNetTransitionWakeLockSerialNumber++; + mNetTransitionWakeLock.acquire(); + mNetTransitionWakeLockCausedBy = forWhom; + } + mHandler.sendMessageDelayed(mHandler.obtainMessage( + EVENT_CLEAR_NET_TRANSITION_WAKELOCK, + mNetTransitionWakeLockSerialNumber, 0), + mNetTransitionWakeLockTimeout); + return; + } + // 100 percent is full good, 0 is full bad. public void reportInetCondition(int networkType, int percentage) { - if (DBG) Slog.d(TAG, "reportNetworkCondition(" + networkType + ", " + percentage + ")"); + if (DBG) log("reportNetworkCondition(" + networkType + ", " + percentage + ")"); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.STATUS_BAR, "ConnectivityService"); @@ -1700,22 +1970,22 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void handleInetConditionChange(int netType, int condition) { if (DBG) { - Slog.d(TAG, "Inet connectivity change, net=" + + log("Inet connectivity change, net=" + netType + ", condition=" + condition + ",mActiveDefaultNetwork=" + mActiveDefaultNetwork); } if (mActiveDefaultNetwork == -1) { - if (DBG) Slog.d(TAG, "no active default network - aborting"); + if (DBG) log("no active default network - aborting"); return; } if (mActiveDefaultNetwork != netType) { - if (DBG) Slog.d(TAG, "given net not default - aborting"); + if (DBG) log("given net not default - aborting"); return; } mDefaultInetCondition = condition; int delay; if (mInetConditionChangeInFlight == false) { - if (DBG) Slog.d(TAG, "starting a change hold"); + if (DBG) log("starting a change hold"); // setup a new hold to debounce this if (mDefaultInetCondition > 50) { delay = Settings.Secure.getInt(mContext.getContentResolver(), @@ -1730,37 +2000,169 @@ public class ConnectivityService extends IConnectivityManager.Stub { } else { // we've set the new condition, when this hold ends that will get // picked up - if (DBG) Slog.d(TAG, "currently in hold - not setting new end evt"); + if (DBG) log("currently in hold - not setting new end evt"); } } private void handleInetConditionHoldEnd(int netType, int sequence) { if (DBG) { - Slog.d(TAG, "Inet hold end, net=" + netType + + log("Inet hold end, net=" + netType + ", condition =" + mDefaultInetCondition + ", published condition =" + mDefaultInetConditionPublished); } mInetConditionChangeInFlight = false; if (mActiveDefaultNetwork == -1) { - if (DBG) Slog.d(TAG, "no active default network - aborting"); + if (DBG) log("no active default network - aborting"); return; } if (mDefaultConnectionSequence != sequence) { - if (DBG) Slog.d(TAG, "event hold for obsolete network - aborting"); + if (DBG) log("event hold for obsolete network - aborting"); return; } if (mDefaultInetConditionPublished == mDefaultInetCondition) { - if (DBG) Slog.d(TAG, "no change in condition - aborting"); + if (DBG) log("no change in condition - aborting"); return; } NetworkInfo networkInfo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(); if (networkInfo.isConnected() == false) { - if (DBG) Slog.d(TAG, "default network not connected - aborting"); + if (DBG) log("default network not connected - aborting"); return; } mDefaultInetConditionPublished = mDefaultInetCondition; sendInetConditionBroadcast(networkInfo); return; } + + public synchronized ProxyProperties getProxy() { + if (mGlobalProxy != null) return mGlobalProxy; + if (mDefaultProxy != null) return mDefaultProxy; + return null; + } + + public void setGlobalProxy(ProxyProperties proxyProperties) { + enforceChangePermission(); + synchronized (mGlobalProxyLock) { + if (proxyProperties == mGlobalProxy) return; + if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return; + if (mGlobalProxy != null && mGlobalProxy.equals(proxyProperties)) return; + + String host = ""; + int port = 0; + String exclList = ""; + if (proxyProperties != null && !TextUtils.isEmpty(proxyProperties.getHost())) { + mGlobalProxy = new ProxyProperties(proxyProperties); + host = mGlobalProxy.getHost(); + port = mGlobalProxy.getPort(); + exclList = mGlobalProxy.getExclusionList(); + } else { + mGlobalProxy = null; + } + ContentResolver res = mContext.getContentResolver(); + Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_HOST, host); + Settings.Secure.putInt(res, Settings.Secure.GLOBAL_HTTP_PROXY_PORT, port); + Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, + exclList); + } + + if (mGlobalProxy == null) { + proxyProperties = mDefaultProxy; + } + sendProxyBroadcast(proxyProperties); + } + + private void loadGlobalProxy() { + ContentResolver res = mContext.getContentResolver(); + String host = Settings.Secure.getString(res, Settings.Secure.GLOBAL_HTTP_PROXY_HOST); + int port = Settings.Secure.getInt(res, Settings.Secure.GLOBAL_HTTP_PROXY_PORT, 0); + String exclList = Settings.Secure.getString(res, + Settings.Secure.GLOBAL_HTTP_PROXY_EXCLUSION_LIST); + if (!TextUtils.isEmpty(host)) { + ProxyProperties proxyProperties = new ProxyProperties(host, port, exclList); + synchronized (mGlobalProxyLock) { + mGlobalProxy = proxyProperties; + } + } + } + + public ProxyProperties getGlobalProxy() { + synchronized (mGlobalProxyLock) { + return mGlobalProxy; + } + } + + private void handleApplyDefaultProxy(int type) { + // check if new default - push it out to all VM if so + ProxyProperties proxy = mNetTrackers[type].getLinkProperties().getHttpProxy(); + synchronized (this) { + if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return; + if (mDefaultProxy == proxy) return; + if (!TextUtils.isEmpty(proxy.getHost())) { + mDefaultProxy = proxy; + } else { + mDefaultProxy = null; + } + } + if (DBG) log("changing default proxy to " + proxy); + if ((proxy == null && mGlobalProxy == null) || proxy.equals(mGlobalProxy)) return; + if (mGlobalProxy != null) return; + sendProxyBroadcast(proxy); + } + + private void handleDeprecatedGlobalHttpProxy() { + String proxy = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.HTTP_PROXY); + if (!TextUtils.isEmpty(proxy)) { + String data[] = proxy.split(":"); + String proxyHost = data[0]; + int proxyPort = 8080; + if (data.length > 1) { + try { + proxyPort = Integer.parseInt(data[1]); + } catch (NumberFormatException e) { + return; + } + } + ProxyProperties p = new ProxyProperties(data[0], proxyPort, ""); + setGlobalProxy(p); + } + } + + private void sendProxyBroadcast(ProxyProperties proxy) { + if (proxy == null) proxy = new ProxyProperties("", 0, ""); + log("sending Proxy Broadcast for " + proxy); + Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxy); + mContext.sendStickyBroadcast(intent); + } + + private static class SettingsObserver extends ContentObserver { + private int mWhat; + private Handler mHandler; + SettingsObserver(Handler handler, int what) { + super(handler); + mHandler = handler; + mWhat = what; + } + + void observe(Context context) { + ContentResolver resolver = context.getContentResolver(); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.HTTP_PROXY), false, this); + } + + @Override + public void onChange(boolean selfChange) { + mHandler.obtainMessage(mWhat).sendToTarget(); + } + } + + private void log(String s) { + Slog.d(TAG, s); + } + + private void loge(String s) { + Slog.e(TAG, s); + } } diff --git a/services/java/com/android/server/CountryDetectorService.java b/services/java/com/android/server/CountryDetectorService.java new file mode 100644 index 0000000..3081ebe --- /dev/null +++ b/services/java/com/android/server/CountryDetectorService.java @@ -0,0 +1,204 @@ +/* + * 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; + +import java.util.HashMap; + +import com.android.server.location.ComprehensiveCountryDetector; + +import android.content.Context; +import android.location.Country; +import android.location.CountryListener; +import android.location.ICountryDetector; +import android.location.ICountryListener; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Process; +import android.os.RemoteException; +import android.util.Slog; + +/** + * This class detects the country that the user is in through + * {@link ComprehensiveCountryDetector}. + * + * @hide + */ +public class CountryDetectorService extends ICountryDetector.Stub implements Runnable { + + /** + * The class represents the remote listener, it will also removes itself + * from listener list when the remote process was died. + */ + private final class Receiver implements IBinder.DeathRecipient { + private final ICountryListener mListener; + private final IBinder mKey; + + public Receiver(ICountryListener listener) { + mListener = listener; + mKey = listener.asBinder(); + } + + public void binderDied() { + removeListener(mKey); + } + + @Override + public boolean equals(Object otherObj) { + if (otherObj instanceof Receiver) { + return mKey.equals(((Receiver) otherObj).mKey); + } + return false; + } + + @Override + public int hashCode() { + return mKey.hashCode(); + } + + public ICountryListener getListener() { + return mListener; + } + } + + private final static String TAG = "CountryDetectorService"; + + private final HashMap<IBinder, Receiver> mReceivers; + private final Context mContext; + private ComprehensiveCountryDetector mCountryDetector; + private boolean mSystemReady; + private Handler mHandler; + private CountryListener mLocationBasedDetectorListener; + + public CountryDetectorService(Context context) { + super(); + mReceivers = new HashMap<IBinder, Receiver>(); + mContext = context; + } + + @Override + public Country detectCountry() throws RemoteException { + if (!mSystemReady) { + throw new RemoteException(); + } + return mCountryDetector.detectCountry(); + } + + /** + * Add the ICountryListener into the listener list. + */ + @Override + public void addCountryListener(ICountryListener listener) throws RemoteException { + if (!mSystemReady) { + throw new RemoteException(); + } + addListener(listener); + } + + /** + * Remove the ICountryListener from the listener list. + */ + @Override + public void removeCountryListener(ICountryListener listener) throws RemoteException { + if (!mSystemReady) { + throw new RemoteException(); + } + removeListener(listener.asBinder()); + } + + private void addListener(ICountryListener listener) { + synchronized (mReceivers) { + Receiver r = new Receiver(listener); + try { + listener.asBinder().linkToDeath(r, 0); + mReceivers.put(listener.asBinder(), r); + if (mReceivers.size() == 1) { + Slog.d(TAG, "The first listener is added"); + setCountryListener(mLocationBasedDetectorListener); + } + } catch (RemoteException e) { + Slog.e(TAG, "linkToDeath failed:", e); + } + } + } + + private void removeListener(IBinder key) { + synchronized (mReceivers) { + mReceivers.remove(key); + if (mReceivers.isEmpty()) { + setCountryListener(null); + Slog.d(TAG, "No listener is left"); + } + } + } + + + protected void notifyReceivers(Country country) { + synchronized(mReceivers) { + for (Receiver receiver : mReceivers.values()) { + try { + receiver.getListener().onCountryDetected(country); + } catch (RemoteException e) { + // TODO: Shall we remove the receiver? + Slog.e(TAG, "notifyReceivers failed:", e); + } + } + } + } + + void systemReady() { + // Shall we wait for the initialization finish. + Thread thread = new Thread(this, "CountryDetectorService"); + thread.start(); + } + + private void initialize() { + mCountryDetector = new ComprehensiveCountryDetector(mContext); + mLocationBasedDetectorListener = new CountryListener() { + public void onCountryDetected(final Country country) { + mHandler.post(new Runnable() { + public void run() { + notifyReceivers(country); + } + }); + } + }; + } + + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + Looper.prepare(); + mHandler = new Handler(); + initialize(); + mSystemReady = true; + Looper.loop(); + } + + protected void setCountryListener(final CountryListener listener) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCountryDetector.setCountryListener(listener); + } + }); + } + + // For testing + boolean isSystemReady() { + return mSystemReady; + } +} diff --git a/services/java/com/android/server/DemoDataSet.java b/services/java/com/android/server/DemoDataSet.java deleted file mode 100644 index 277985f..0000000 --- a/services/java/com/android/server/DemoDataSet.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.res.AssetManager; -import android.net.Uri; -import android.os.Environment; -import android.provider.Contacts; -import android.provider.Settings; -import android.provider.MediaStore.Images; -import android.util.Config; -import android.util.Slog; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.io.OutputStream; - -public class DemoDataSet -{ - private final static String LOG_TAG = "DemoDataSet"; - - private ContentResolver mContentResolver; - - public final void add(Context context) - { - mContentResolver = context.getContentResolver(); - - // Remove all the old data - mContentResolver.delete(Contacts.People.CONTENT_URI, null, null); - - // Add the new data - addDefaultData(); - - // Add images from /android/images - addDefaultImages(); - } - - private final void addDefaultImages() - { - File rootDirectory = Environment.getRootDirectory(); - String [] files - = new File(rootDirectory, "images").list(); - int count = files.length; - - if (count == 0) { - Slog.i(LOG_TAG, "addDefaultImages: no images found!"); - return; - } - - for (int i = 0; i < count; i++) - { - String name = files[i]; - String path = rootDirectory + "/" + name; - - try { - Images.Media.insertImage(mContentResolver, path, name, null); - } catch (FileNotFoundException e) { - Slog.e(LOG_TAG, "Failed to import image " + path, e); - } - } - } - - private final void addDefaultData() - { - Slog.i(LOG_TAG, "Adding default data..."); - -// addImage("Violet", "images/violet.png"); -// addImage("Corky", "images/corky.png"); - - // PENDING: should this be done here?!?! - Intent intent = new Intent( - Intent.ACTION_CALL, Uri.fromParts("voicemail", "", null)); - addShortcut("1", intent); - } - - private final Uri addImage(String name, Uri file) - { - ContentValues imagev = new ContentValues(); - imagev.put("name", name); - - Uri url = null; - - AssetManager ass = AssetManager.getSystem(); - InputStream in = null; - OutputStream out = null; - - try - { - in = ass.open(file.toString()); - - url = mContentResolver.insert(Images.Media.INTERNAL_CONTENT_URI, imagev); - out = mContentResolver.openOutputStream(url); - - final int size = 8 * 1024; - byte[] buf = new byte[size]; - - int count = 0; - do - { - count = in.read(buf, 0, size); - if (count > 0) { - out.write(buf, 0, count); - } - } while (count > 0); - } - catch (Exception e) - { - Slog.e(LOG_TAG, "Failed to insert image '" + file + "'", e); - url = null; - } - - return url; - } - - private final Uri addShortcut(String shortcut, Intent intent) - { - if (Config.LOGV) Slog.v(LOG_TAG, "addShortcut: shortcut=" + shortcut + ", intent=" + intent); - return Settings.Bookmarks.add(mContentResolver, intent, null, null, - shortcut != null ? shortcut.charAt(0) : 0, 0); - } -} diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index 1538003..b2d534b 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -28,18 +28,23 @@ import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import android.app.Activity; +import android.app.AlarmManager; +import android.app.PendingIntent; import android.app.admin.DeviceAdminInfo; import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.os.IPowerManager; import android.os.PowerManager; @@ -48,9 +53,11 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; -import android.util.Slog; +import android.os.SystemProperties; +import android.provider.Settings; import android.util.PrintWriterPrinter; import android.util.Printer; +import android.util.Slog; import android.util.Xml; import android.view.WindowManagerPolicy; @@ -61,47 +68,97 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; +import java.text.DateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Set; /** * Implementation of the device policy APIs. */ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + private static final int REQUEST_EXPIRE_PASSWORD = 5571; + static final String TAG = "DevicePolicyManagerService"; - + + private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * 86400 * 1000; // 5 days, in ms + + protected static final String ACTION_EXPIRED_PASSWORD_NOTIFICATION + = "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION"; + + private static final long MS_PER_DAY = 86400 * 1000; + final Context mContext; final MyPackageMonitor mMonitor; final PowerManager.WakeLock mWakeLock; IPowerManager mIPowerManager; - + int mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; int mActivePasswordLength = 0; + int mActivePasswordUpperCase = 0; + int mActivePasswordLowerCase = 0; + int mActivePasswordLetters = 0; + int mActivePasswordNumeric = 0; + int mActivePasswordSymbols = 0; + int mActivePasswordNonLetter = 0; int mFailedPasswordAttempts = 0; - + int mPasswordOwner = -1; - + Handler mHandler = new Handler(); + final HashMap<ComponentName, ActiveAdmin> mAdminMap = new HashMap<ComponentName, ActiveAdmin>(); final ArrayList<ActiveAdmin> mAdminList = new ArrayList<ActiveAdmin>(); - + + BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_BOOT_COMPLETED.equals(action) + || ACTION_EXPIRED_PASSWORD_NOTIFICATION.equals(action)) { + Slog.v(TAG, "Sending password expiration notifications for action " + action); + mHandler.post(new Runnable() { + public void run() { + handlePasswordExpirationNotification(); + } + }); + } + } + }; + static class ActiveAdmin { final DeviceAdminInfo info; - + int passwordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; int minimumPasswordLength = 0; + int passwordHistoryLength = 0; + int minimumPasswordUpperCase = 0; + int minimumPasswordLowerCase = 0; + int minimumPasswordLetters = 1; + int minimumPasswordNumeric = 1; + int minimumPasswordSymbols = 1; + int minimumPasswordNonLetter = 0; long maximumTimeToUnlock = 0; int maximumFailedPasswordsForWipe = 0; - + long passwordExpirationTimeout = 0L; + long passwordExpirationDate = 0L; + boolean encryptionRequested = false; + + // TODO: review implementation decisions with frameworks team + boolean specifiesGlobalProxy = false; + String globalProxySpec = null; + String globalProxyExclusionList = null; + ActiveAdmin(DeviceAdminInfo _info) { info = _info; } - + int getUid() { return info.getActivityInfo().applicationInfo.uid; } - + void writeToXml(XmlSerializer out) throws IllegalArgumentException, IllegalStateException, IOException { out.startTag(null, "policies"); @@ -114,10 +171,45 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (minimumPasswordLength > 0) { out.startTag(null, "min-password-length"); out.attribute(null, "value", Integer.toString(minimumPasswordLength)); - out.endTag(null, "mn-password-length"); + out.endTag(null, "min-password-length"); + } + if(passwordHistoryLength > 0) { + out.startTag(null, "password-history-length"); + out.attribute(null, "value", Integer.toString(passwordHistoryLength)); + out.endTag(null, "password-history-length"); + } + if (minimumPasswordUpperCase > 0) { + out.startTag(null, "min-password-uppercase"); + out.attribute(null, "value", Integer.toString(minimumPasswordUpperCase)); + out.endTag(null, "min-password-uppercase"); + } + if (minimumPasswordLowerCase > 0) { + out.startTag(null, "min-password-lowercase"); + out.attribute(null, "value", Integer.toString(minimumPasswordLowerCase)); + out.endTag(null, "min-password-lowercase"); + } + if (minimumPasswordLetters > 0) { + out.startTag(null, "min-password-letters"); + out.attribute(null, "value", Integer.toString(minimumPasswordLetters)); + out.endTag(null, "min-password-letters"); + } + if (minimumPasswordNumeric > 0) { + out.startTag(null, "min-password-numeric"); + out.attribute(null, "value", Integer.toString(minimumPasswordNumeric)); + out.endTag(null, "min-password-numeric"); + } + if (minimumPasswordSymbols > 0) { + out.startTag(null, "min-password-symbols"); + out.attribute(null, "value", Integer.toString(minimumPasswordSymbols)); + out.endTag(null, "min-password-symbols"); + } + if (minimumPasswordNonLetter > 0) { + out.startTag(null, "min-password-nonletter"); + out.attribute(null, "value", Integer.toString(minimumPasswordNonLetter)); + out.endTag(null, "min-password-nonletter"); } } - if (maximumTimeToUnlock != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { + if (maximumTimeToUnlock != 0) { out.startTag(null, "max-time-to-unlock"); out.attribute(null, "value", Long.toString(maximumTimeToUnlock)); out.endTag(null, "max-time-to-unlock"); @@ -127,8 +219,38 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.attribute(null, "value", Integer.toString(maximumFailedPasswordsForWipe)); out.endTag(null, "max-failed-password-wipe"); } + if (specifiesGlobalProxy) { + out.startTag(null, "specifies-global-proxy"); + out.attribute(null, "value", Boolean.toString(specifiesGlobalProxy)); + out.endTag(null, "specifies_global_proxy"); + if (globalProxySpec != null) { + out.startTag(null, "global-proxy-spec"); + out.attribute(null, "value", globalProxySpec); + out.endTag(null, "global-proxy-spec"); + } + if (globalProxyExclusionList != null) { + out.startTag(null, "global-proxy-exclusion-list"); + out.attribute(null, "value", globalProxyExclusionList); + out.endTag(null, "global-proxy-exclusion-list"); + } + } + if (passwordExpirationTimeout != 0L) { + out.startTag(null, "password-expiration-timeout"); + out.attribute(null, "value", Long.toString(passwordExpirationTimeout)); + out.endTag(null, "password-expiration-timeout"); + } + if (passwordExpirationDate != 0L) { + out.startTag(null, "password-expiration-date"); + out.attribute(null, "value", Long.toString(passwordExpirationDate)); + out.endTag(null, "password-expiration-date"); + } + if (encryptionRequested) { + out.startTag(null, "encryption-requested"); + out.attribute(null, "value", Boolean.toString(encryptionRequested)); + out.endTag(null, "encryption-requested"); + } } - + void readFromXml(XmlPullParser parser) throws XmlPullParserException, IOException { int outerDepth = parser.getDepth(); @@ -147,19 +269,58 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if ("min-password-length".equals(tag)) { minimumPasswordLength = Integer.parseInt( parser.getAttributeValue(null, "value")); + } else if ("password-history-length".equals(tag)) { + passwordHistoryLength = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("min-password-uppercase".equals(tag)) { + minimumPasswordUpperCase = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("min-password-lowercase".equals(tag)) { + minimumPasswordLowerCase = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("min-password-letters".equals(tag)) { + minimumPasswordLetters = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("min-password-numeric".equals(tag)) { + minimumPasswordNumeric = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("min-password-symbols".equals(tag)) { + minimumPasswordSymbols = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("min-password-nonletter".equals(tag)) { + minimumPasswordNonLetter = Integer.parseInt( + parser.getAttributeValue(null, "value")); } else if ("max-time-to-unlock".equals(tag)) { maximumTimeToUnlock = Long.parseLong( parser.getAttributeValue(null, "value")); } else if ("max-failed-password-wipe".equals(tag)) { maximumFailedPasswordsForWipe = Integer.parseInt( parser.getAttributeValue(null, "value")); + } else if ("specifies-global-proxy".equals(tag)) { + specifiesGlobalProxy = Boolean.parseBoolean( + parser.getAttributeValue(null, "value")); + } else if ("global-proxy-spec".equals(tag)) { + globalProxySpec = + parser.getAttributeValue(null, "value"); + } else if ("global-proxy-exclusion-list".equals(tag)) { + globalProxyExclusionList = + parser.getAttributeValue(null, "value"); + } else if ("password-expiration-timeout".equals(tag)) { + passwordExpirationTimeout = Long.parseLong( + parser.getAttributeValue(null, "value")); + } else if ("password-expiration-date".equals(tag)) { + passwordExpirationDate = Long.parseLong( + parser.getAttributeValue(null, "value")); + } else if ("encryption-requested".equals(tag)) { + encryptionRequested = Boolean.parseBoolean( + parser.getAttributeValue(null, "value")); } else { Slog.w(TAG, "Unknown admin tag: " + tag); } XmlUtils.skipCurrentTag(parser); } } - + void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("uid="); pw.println(getUid()); pw.print(prefix); pw.println("policies:"); @@ -170,23 +331,54 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } pw.print(prefix); pw.print("passwordQuality=0x"); - pw.print(Integer.toHexString(passwordQuality)); - pw.print(" minimumPasswordLength="); + pw.println(Integer.toHexString(passwordQuality)); + pw.print(prefix); pw.print("minimumPasswordLength="); pw.println(minimumPasswordLength); + pw.print(prefix); pw.print("passwordHistoryLength="); + pw.println(passwordHistoryLength); + pw.print(prefix); pw.print("minimumPasswordUpperCase="); + pw.println(minimumPasswordUpperCase); + pw.print(prefix); pw.print("minimumPasswordLowerCase="); + pw.println(minimumPasswordLowerCase); + pw.print(prefix); pw.print("minimumPasswordLetters="); + pw.println(minimumPasswordLetters); + pw.print(prefix); pw.print("minimumPasswordNumeric="); + pw.println(minimumPasswordNumeric); + pw.print(prefix); pw.print("minimumPasswordSymbols="); + pw.println(minimumPasswordSymbols); + pw.print(prefix); pw.print("minimumPasswordNonLetter="); + pw.println(minimumPasswordNonLetter); pw.print(prefix); pw.print("maximumTimeToUnlock="); pw.println(maximumTimeToUnlock); pw.print(prefix); pw.print("maximumFailedPasswordsForWipe="); pw.println(maximumFailedPasswordsForWipe); + pw.print(prefix); pw.print("specifiesGlobalProxy="); + pw.println(specifiesGlobalProxy); + pw.print(prefix); pw.print("passwordExpirationTimeout="); + pw.println(passwordExpirationTimeout); + pw.print(prefix); pw.print("passwordExpirationDate="); + pw.println(passwordExpirationDate); + if (globalProxySpec != null) { + pw.print(prefix); pw.print("globalProxySpec="); + pw.println(globalProxySpec); + } + if (globalProxyExclusionList != null) { + pw.print(prefix); pw.print("globalProxyEclusionList="); + pw.println(globalProxyExclusionList); + } + pw.print(prefix); pw.print("encryptionRequested="); + pw.println(encryptionRequested); } } - + class MyPackageMonitor extends PackageMonitor { + @Override public void onSomePackagesChanged() { synchronized (DevicePolicyManagerService.this) { boolean removed = false; for (int i=mAdminList.size()-1; i>=0; i--) { ActiveAdmin aa = mAdminList.get(i); - int change = isPackageDisappearing(aa.info.getPackageName()); + int change = isPackageDisappearing(aa.info.getPackageName()); if (change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE) { Slog.w(TAG, "Admin unexpectedly uninstalled: " @@ -211,7 +403,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + /** * Instantiates the service. */ @@ -221,6 +413,50 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mMonitor.register(context, true); mWakeLock = ((PowerManager)context.getSystemService(Context.POWER_SERVICE)) .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DPM"); + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BOOT_COMPLETED); + filter.addAction(ACTION_EXPIRED_PASSWORD_NOTIFICATION); + context.registerReceiver(mReceiver, filter); + } + + /** + * Set an alarm for an upcoming event - expiration warning, expiration, or post-expiration + * reminders. Clears alarm if no expirations are configured. + */ + protected void setExpirationAlarmCheckLocked(Context context) { + final long expiration = getPasswordExpirationLocked(null); + final long now = System.currentTimeMillis(); + final long timeToExpire = expiration - now; + final long alarmTime; + if (expiration == 0) { + // No expirations are currently configured: Cancel alarm. + alarmTime = 0; + } else if (timeToExpire <= 0) { + // The password has already expired: Repeat every 24 hours. + alarmTime = now + MS_PER_DAY; + } else { + // Selecting the next alarm time: Roll forward to the next 24 hour multiple before + // the expiration time. + long alarmInterval = timeToExpire % MS_PER_DAY; + if (alarmInterval == 0) { + alarmInterval = MS_PER_DAY; + } + alarmTime = now + alarmInterval; + } + + long token = Binder.clearCallingIdentity(); + try { + AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + PendingIntent pi = PendingIntent.getBroadcast(context, REQUEST_EXPIRE_PASSWORD, + new Intent(ACTION_EXPIRED_PASSWORD_NOTIFICATION), + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); + am.cancel(pi); + if (alarmTime != 0) { + am.set(AlarmManager.RTC, alarmTime, pi); + } + } finally { + Binder.restoreCallingIdentity(token); + } } private IPowerManager getIPowerManager() { @@ -230,7 +466,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } return mIPowerManager; } - + ActiveAdmin getActiveAdminUncheckedLocked(ComponentName who) { ActiveAdmin admin = mAdminMap.get(who); if (admin != null @@ -240,7 +476,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } return null; } - + ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy) throws SecurityException { final int callingUid = Binder.getCallingUid(); @@ -271,13 +507,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + Binder.getCallingUid() + " for policy #" + reqPolicy); } } - + void sendAdminCommandLocked(ActiveAdmin admin, String action) { Intent intent = new Intent(action); intent.setComponent(admin.info.getComponent()); + if (action.equals(DeviceAdminReceiver.ACTION_PASSWORD_EXPIRING)) { + intent.putExtra("expiration", admin.passwordExpirationDate); + } mContext.sendBroadcast(intent); } - + void sendAdminCommandLocked(String action, int reqPolicy) { final int N = mAdminList.size(); if (N > 0) { @@ -289,19 +528,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + void removeActiveAdminLocked(ComponentName adminReceiver) { ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver); if (admin != null) { + boolean doProxyCleanup = + admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY); sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED); // XXX need to wait for it to complete. mAdminList.remove(admin); mAdminMap.remove(adminReceiver); validatePasswordOwnerLocked(); + if (doProxyCleanup) { + resetGlobalProxy(); + } } } - + public DeviceAdminInfo findAdmin(ComponentName adminName) { Intent resolveIntent = new Intent(); resolveIntent.setComponent(adminName); @@ -310,7 +554,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (infos == null || infos.size() <= 0) { throw new IllegalArgumentException("Unknown admin: " + adminName); } - + try { return new DeviceAdminInfo(mContext, infos.get(0)); } catch (XmlPullParserException e) { @@ -321,7 +565,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return null; } } - + private static JournaledFile makeJournaledFile() { final String base = "/data/system/device_policies.xml"; return new JournaledFile(new File(base), new File(base + ".tmp")); @@ -337,7 +581,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.startDocument(null, true); out.startTag(null, "policies"); - + final int N = mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin ap = mAdminList.get(i); @@ -348,26 +592,36 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.endTag(null, "admin"); } } - + if (mPasswordOwner >= 0) { out.startTag(null, "password-owner"); out.attribute(null, "value", Integer.toString(mPasswordOwner)); out.endTag(null, "password-owner"); } - + if (mFailedPasswordAttempts != 0) { out.startTag(null, "failed-password-attempts"); out.attribute(null, "value", Integer.toString(mFailedPasswordAttempts)); out.endTag(null, "failed-password-attempts"); } - - if (mActivePasswordQuality != 0 || mActivePasswordLength != 0) { + + if (mActivePasswordQuality != 0 || mActivePasswordLength != 0 + || mActivePasswordUpperCase != 0 || mActivePasswordLowerCase != 0 + || mActivePasswordLetters != 0 || mActivePasswordNumeric != 0 + || mActivePasswordSymbols != 0 || mActivePasswordNonLetter != 0) { out.startTag(null, "active-password"); out.attribute(null, "quality", Integer.toString(mActivePasswordQuality)); out.attribute(null, "length", Integer.toString(mActivePasswordLength)); + out.attribute(null, "uppercase", Integer.toString(mActivePasswordUpperCase)); + out.attribute(null, "lowercase", Integer.toString(mActivePasswordLowerCase)); + out.attribute(null, "letters", Integer.toString(mActivePasswordLetters)); + out.attribute(null, "numeric", Integer + .toString(mActivePasswordNumeric)); + out.attribute(null, "symbols", Integer.toString(mActivePasswordSymbols)); + out.attribute(null, "nonletter", Integer.toString(mActivePasswordNonLetter)); out.endTag(null, "active-password"); } - + out.endTag(null, "policies"); out.endDocument(); @@ -445,6 +699,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { parser.getAttributeValue(null, "quality")); mActivePasswordLength = Integer.parseInt( parser.getAttributeValue(null, "length")); + mActivePasswordUpperCase = Integer.parseInt( + parser.getAttributeValue(null, "uppercase")); + mActivePasswordLowerCase = Integer.parseInt( + parser.getAttributeValue(null, "lowercase")); + mActivePasswordLetters = Integer.parseInt( + parser.getAttributeValue(null, "letters")); + mActivePasswordNumeric = Integer.parseInt( + parser.getAttributeValue(null, "numeric")); + mActivePasswordSymbols = Integer.parseInt( + parser.getAttributeValue(null, "symbols")); + mActivePasswordNonLetter = Integer.parseInt( + parser.getAttributeValue(null, "nonletter")); XmlUtils.skipCurrentTag(parser); } else { Slog.w(TAG, "Unknown tag: " + tag); @@ -484,10 +750,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + Integer.toHexString(utils.getActivePasswordQuality())); mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; mActivePasswordLength = 0; + mActivePasswordUpperCase = 0; + mActivePasswordLowerCase = 0; + mActivePasswordLetters = 0; + mActivePasswordNumeric = 0; + mActivePasswordSymbols = 0; + mActivePasswordNonLetter = 0; } - + validatePasswordOwnerLocked(); - + long timeMs = getMaximumTimeToLock(null); if (timeMs <= 0) { timeMs = Integer.MAX_VALUE; @@ -506,12 +778,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: return; } throw new IllegalArgumentException("Invalid quality constant: 0x" + Integer.toHexString(quality)); } - + void validatePasswordOwnerLocked() { if (mPasswordOwner >= 0) { boolean haveOwner = false; @@ -528,17 +801,41 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public void systemReady() { synchronized (this) { loadSettingsLocked(); } } - - public void setActiveAdmin(ComponentName adminReceiver) { + + private void handlePasswordExpirationNotification() { + synchronized (this) { + final long now = System.currentTimeMillis(); + final int N = mAdminList.size(); + if (N <= 0) { + return; + } + for (int i=0; i < N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD) + && admin.passwordExpirationTimeout > 0L + && admin.passwordExpirationDate > 0L + && now >= admin.passwordExpirationDate - EXPIRATION_GRACE_PERIOD_MS) { + sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_PASSWORD_EXPIRING); + } + } + setExpirationAlarmCheckLocked(mContext); + } + } + + /** + * @param adminReceiver The admin to add + * @param refreshing true = update an active admin, no error + */ + public void setActiveAdmin(ComponentName adminReceiver, boolean refreshing) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - + DeviceAdminInfo info = findAdmin(adminReceiver); if (info == null) { throw new IllegalArgumentException("Bad admin: " + adminReceiver); @@ -546,27 +843,51 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { long ident = Binder.clearCallingIdentity(); try { - if (getActiveAdminUncheckedLocked(adminReceiver) != null) { + if (!refreshing && getActiveAdminUncheckedLocked(adminReceiver) != null) { throw new IllegalArgumentException("Admin is already added"); } - ActiveAdmin admin = new ActiveAdmin(info); - mAdminMap.put(adminReceiver, admin); - mAdminList.add(admin); + ActiveAdmin newAdmin = new ActiveAdmin(info); + mAdminMap.put(adminReceiver, newAdmin); + int replaceIndex = -1; + if (refreshing) { + final int N = mAdminList.size(); + for (int i=0; i < N; i++) { + ActiveAdmin oldAdmin = mAdminList.get(i); + if (oldAdmin.info.getComponent().equals(adminReceiver)) { + replaceIndex = i; + break; + } + } + } + if (replaceIndex == -1) { + mAdminList.add(newAdmin); + } else { + mAdminList.set(replaceIndex, newAdmin); + } saveSettingsLocked(); - sendAdminCommandLocked(admin, - DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED); + sendAdminCommandLocked(newAdmin, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED); } finally { Binder.restoreCallingIdentity(ident); } } } - + public boolean isAdminActive(ComponentName adminReceiver) { synchronized (this) { return getActiveAdminUncheckedLocked(adminReceiver) != null; } } - + + public boolean hasGrantedPolicy(ComponentName adminReceiver, int policyId) { + synchronized (this) { + ActiveAdmin administrator = getActiveAdminUncheckedLocked(adminReceiver); + if (administrator == null) { + throw new SecurityException("No active admin " + adminReceiver); + } + return administrator.info.usesPolicy(policyId); + } + } + public List<ComponentName> getActiveAdmins() { synchronized (this) { final int N = mAdminList.size(); @@ -580,7 +901,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return res; } } - + public boolean packageHasActiveAdmins(String packageName) { synchronized (this) { final int N = mAdminList.size(); @@ -592,7 +913,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } } - + public void removeActiveAdmin(ComponentName adminReceiver) { synchronized (this) { ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver); @@ -611,10 +932,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public void setPasswordQuality(ComponentName who, int quality) { validateQualityConstant(quality); - + synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -627,16 +948,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public int getPasswordQuality(ComponentName who) { synchronized (this) { int mode = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; - + if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who); return admin != null ? admin.passwordQuality : mode; } - + final int N = mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = mAdminList.get(i); @@ -647,7 +968,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mode; } } - + public void setPasswordMinimumLength(ComponentName who, int length) { synchronized (this) { if (who == null) { @@ -661,16 +982,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public int getPasswordMinimumLength(ComponentName who) { synchronized (this) { int length = 0; - + if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who); return admin != null ? admin.minimumPasswordLength : length; } - + final int N = mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = mAdminList.get(i); @@ -681,18 +1002,343 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return length; } } - + + public void setPasswordHistoryLength(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.passwordHistoryLength != length) { + ap.passwordHistoryLength = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordHistoryLength(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.passwordHistoryLength : length; + } + + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (length < admin.passwordHistoryLength) { + length = admin.passwordHistoryLength; + } + } + return length; + } + } + + public void setPasswordExpirationTimeout(ComponentName who, long timeout) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + if (timeout < 0) { + throw new IllegalArgumentException("Timeout must be >= 0 ms"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD); + // Calling this API automatically bumps the expiration date + final long expiration = timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L; + ap.passwordExpirationDate = expiration; + ap.passwordExpirationTimeout = timeout; + if (timeout > 0L) { + Slog.w(TAG, "setPasswordExpiration(): password will expire on " + + DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT) + .format(new Date(expiration))); + } + saveSettingsLocked(); + setExpirationAlarmCheckLocked(mContext); // in case this is the first one + } + } + + /** + * Return a single admin's expiration cycle time, or the min of all cycle times. + * Returns 0 if not configured. + */ + public long getPasswordExpirationTimeout(ComponentName who) { + synchronized (this) { + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.passwordExpirationTimeout : 0L; + } + + long timeout = 0L; + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (timeout == 0L || (admin.passwordExpirationTimeout != 0L + && timeout > admin.passwordExpirationTimeout)) { + timeout = admin.passwordExpirationTimeout; + } + } + return timeout; + } + } + + /** + * Return a single admin's expiration date/time, or the min (soonest) for all admins. + * Returns 0 if not configured. + */ + private long getPasswordExpirationLocked(ComponentName who) { + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.passwordExpirationDate : 0L; + } + + long timeout = 0L; + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (timeout == 0L || (admin.passwordExpirationDate != 0 + && timeout > admin.passwordExpirationDate)) { + timeout = admin.passwordExpirationDate; + } + } + return timeout; + } + + public long getPasswordExpiration(ComponentName who) { + synchronized (this) { + return getPasswordExpirationLocked(who); + } + } + + public void setPasswordMinimumUpperCase(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.minimumPasswordUpperCase != length) { + ap.minimumPasswordUpperCase = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordMinimumUpperCase(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.minimumPasswordUpperCase : length; + } + + final int N = mAdminList.size(); + for (int i=0; i<N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (length < admin.minimumPasswordUpperCase) { + length = admin.minimumPasswordUpperCase; + } + } + return length; + } + } + + public void setPasswordMinimumLowerCase(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.minimumPasswordLowerCase != length) { + ap.minimumPasswordLowerCase = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordMinimumLowerCase(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.minimumPasswordLowerCase : length; + } + + final int N = mAdminList.size(); + for (int i=0; i<N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (length < admin.minimumPasswordLowerCase) { + length = admin.minimumPasswordLowerCase; + } + } + return length; + } + } + + public void setPasswordMinimumLetters(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.minimumPasswordLetters != length) { + ap.minimumPasswordLetters = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordMinimumLetters(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.minimumPasswordLetters : length; + } + + final int N = mAdminList.size(); + for (int i=0; i<N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (length < admin.minimumPasswordLetters) { + length = admin.minimumPasswordLetters; + } + } + return length; + } + } + + public void setPasswordMinimumNumeric(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.minimumPasswordNumeric != length) { + ap.minimumPasswordNumeric = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordMinimumNumeric(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.minimumPasswordNumeric : length; + } + + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (length < admin.minimumPasswordNumeric) { + length = admin.minimumPasswordNumeric; + } + } + return length; + } + } + + public void setPasswordMinimumSymbols(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.minimumPasswordSymbols != length) { + ap.minimumPasswordSymbols = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordMinimumSymbols(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.minimumPasswordSymbols : length; + } + + final int N = mAdminList.size(); + for (int i=0; i<N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (length < admin.minimumPasswordSymbols) { + length = admin.minimumPasswordSymbols; + } + } + return length; + } + } + + public void setPasswordMinimumNonLetter(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.minimumPasswordNonLetter != length) { + ap.minimumPasswordNonLetter = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordMinimumNonLetter(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.minimumPasswordNonLetter : length; + } + + final int N = mAdminList.size(); + for (int i=0; i<N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (length < admin.minimumPasswordNonLetter) { + length = admin.minimumPasswordNonLetter; + } + } + return length; + } + } + public boolean isActivePasswordSufficient() { synchronized (this) { // This API can only be called by an active device admin, // so try to retrieve it to check that the caller is one. getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); - return mActivePasswordQuality >= getPasswordQuality(null) - && mActivePasswordLength >= getPasswordMinimumLength(null); + if (mActivePasswordQuality < getPasswordQuality(null) + || mActivePasswordLength < getPasswordMinimumLength(null)) { + return false; + } + if(mActivePasswordQuality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) { + return true; + } + return mActivePasswordUpperCase >= getPasswordMinimumUpperCase(null) + && mActivePasswordLowerCase >= getPasswordMinimumLowerCase(null) + && mActivePasswordLetters >= getPasswordMinimumLetters(null) + && mActivePasswordNumeric >= getPasswordMinimumNumeric(null) + && mActivePasswordSymbols >= getPasswordMinimumSymbols(null) + && mActivePasswordNonLetter >= getPasswordMinimumNonLetter(null); } } - + public int getCurrentFailedPasswordAttempts() { synchronized (this) { // This API can only be called by an active device admin, @@ -702,7 +1348,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mFailedPasswordAttempts; } } - + public void setMaximumFailedPasswordsForWipe(ComponentName who, int num) { synchronized (this) { // This API can only be called by an active device admin, @@ -717,16 +1363,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public int getMaximumFailedPasswordsForWipe(ComponentName who) { synchronized (this) { int count = 0; - + if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who); return admin != null ? admin.maximumFailedPasswordsForWipe : count; } - + final int N = mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = mAdminList.get(i); @@ -740,7 +1386,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return count; } } - + public boolean resetPassword(String password, int flags) { int quality; synchronized (this) { @@ -751,14 +1397,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { quality = getPasswordQuality(null); if (quality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { int realQuality = LockPatternUtils.computePasswordQuality(password); - if (realQuality < quality) { + if (realQuality < quality + && quality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) { Slog.w(TAG, "resetPassword: password quality 0x" + Integer.toHexString(quality) + " does not meet required quality 0x" + Integer.toHexString(quality)); return false; } - quality = realQuality; + quality = Math.max(realQuality, quality); } int length = getPasswordMinimumLength(null); if (password.length() < length) { @@ -766,14 +1413,86 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + " does not meet required length " + length); return false; } + if (quality == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) { + int letters = 0; + int uppercase = 0; + int lowercase = 0; + int numbers = 0; + int symbols = 0; + int nonletter = 0; + for (int i = 0; i < password.length(); i++) { + char c = password.charAt(i); + if (c >= 'A' && c <= 'Z') { + letters++; + uppercase++; + } else if (c >= 'a' && c <= 'z') { + letters++; + lowercase++; + } else if (c >= '0' && c <= '9') { + numbers++; + nonletter++; + } else { + symbols++; + nonletter++; + } + } + int neededLetters = getPasswordMinimumLetters(null); + if(letters < neededLetters) { + Slog.w(TAG, "resetPassword: number of letters " + letters + + " does not meet required number of letters " + neededLetters); + return false; + } + int neededNumbers = getPasswordMinimumNumeric(null); + if (numbers < neededNumbers) { + Slog + .w(TAG, "resetPassword: number of numerical digits " + numbers + + " does not meet required number of numerical digits " + + neededNumbers); + return false; + } + int neededLowerCase = getPasswordMinimumLowerCase(null); + if (lowercase < neededLowerCase) { + Slog.w(TAG, "resetPassword: number of lowercase letters " + lowercase + + " does not meet required number of lowercase letters " + + neededLowerCase); + return false; + } + int neededUpperCase = getPasswordMinimumUpperCase(null); + if (uppercase < neededUpperCase) { + Slog.w(TAG, "resetPassword: number of uppercase letters " + uppercase + + " does not meet required number of uppercase letters " + + neededUpperCase); + return false; + } + int neededSymbols = getPasswordMinimumSymbols(null); + if (symbols < neededSymbols) { + Slog.w(TAG, "resetPassword: number of special symbols " + symbols + + " does not meet required number of special symbols " + neededSymbols); + return false; + } + int neededNonLetter = getPasswordMinimumNonLetter(null); + if (nonletter < neededNonLetter) { + Slog.w(TAG, "resetPassword: number of non-letter characters " + nonletter + + " does not meet required number of non-letter characters " + + neededNonLetter); + return false; + } + } + + LockPatternUtils utils = new LockPatternUtils(mContext); + if(utils.checkPasswordHistory(password)) { + Slog.w(TAG, "resetPassword: password is the same as one of the last " + + getPasswordHistoryLength(null) + " passwords"); + return false; + } } - + int callingUid = Binder.getCallingUid(); if (mPasswordOwner >= 0 && mPasswordOwner != callingUid) { Slog.w(TAG, "resetPassword: already set by another uid and not entered by user"); return false; } - + // Don't do this with the lock held, because it is going to call // back in to the service. long ident = Binder.clearCallingIdentity(); @@ -791,10 +1510,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } finally { Binder.restoreCallingIdentity(ident); } - + return true; } - + public void setMaximumTimeToLock(ComponentName who, long timeMs) { synchronized (this) { if (who == null) { @@ -804,16 +1523,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { DeviceAdminInfo.USES_POLICY_FORCE_LOCK); if (ap.maximumTimeToUnlock != timeMs) { ap.maximumTimeToUnlock = timeMs; - + long ident = Binder.clearCallingIdentity(); try { saveSettingsLocked(); - + timeMs = getMaximumTimeToLock(null); if (timeMs <= 0) { timeMs = Integer.MAX_VALUE; } - + try { getIPowerManager().setMaximumScreenOffTimeount((int)timeMs); } catch (RemoteException e) { @@ -825,16 +1544,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public long getMaximumTimeToLock(ComponentName who) { synchronized (this) { long time = 0; - + if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who); return admin != null ? admin.maximumTimeToUnlock : time; } - + final int N = mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = mAdminList.get(i); @@ -848,7 +1567,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return time; } } - + public void lockNow() { synchronized (this) { // This API can only be called by an active device admin, @@ -865,7 +1584,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + void wipeDataLocked(int flags) { if ((flags&DevicePolicyManager.WIPE_EXTERNAL_STORAGE) != 0) { Intent intent = new Intent(ExternalStorageFormatter.FORMAT_AND_FACTORY_RESET); @@ -880,7 +1599,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public void wipeData(int flags) { synchronized (this) { // This API can only be called by an active device admin, @@ -895,11 +1614,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public void getRemoveWarning(ComponentName comp, final RemoteCallback result) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - + synchronized (this) { ActiveAdmin admin = getActiveAdminUncheckedLocked(comp); if (admin == null) { @@ -922,22 +1641,34 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }, null, Activity.RESULT_OK, null, null); } } - - public void setActivePasswordState(int quality, int length) { + + public void setActivePasswordState(int quality, int length, int letters, int uppercase, + int lowercase, int numbers, int symbols, int nonletter) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - + validateQualityConstant(quality); - + synchronized (this) { if (mActivePasswordQuality != quality || mActivePasswordLength != length - || mFailedPasswordAttempts != 0) { + || mFailedPasswordAttempts != 0 || mActivePasswordLetters != letters + || mActivePasswordUpperCase != uppercase + || mActivePasswordLowerCase != lowercase || mActivePasswordNumeric != numbers + || mActivePasswordSymbols != symbols || mActivePasswordNonLetter != nonletter) { long ident = Binder.clearCallingIdentity(); try { mActivePasswordQuality = quality; mActivePasswordLength = length; + mActivePasswordLetters = letters; + mActivePasswordLowerCase = lowercase; + mActivePasswordUpperCase = uppercase; + mActivePasswordNumeric = numbers; + mActivePasswordSymbols = symbols; + mActivePasswordNonLetter = nonletter; mFailedPasswordAttempts = 0; saveSettingsLocked(); + updatePasswordExpirationsLocked(); + setExpirationAlarmCheckLocked(mContext); sendAdminCommandLocked(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); } finally { @@ -946,11 +1677,29 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + + /** + * Called any time the device password is updated. Resets all password expiration clocks. + */ + private void updatePasswordExpirationsLocked() { + final int N = mAdminList.size(); + if (N > 0) { + for (int i=0; i<N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) { + long timeout = admin.passwordExpirationTimeout; + long expiration = timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L; + admin.passwordExpirationDate = expiration; + } + } + saveSettingsLocked(); + } + } + public void reportFailedPasswordAttempt() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - + synchronized (this) { long ident = Binder.clearCallingIdentity(); try { @@ -967,11 +1716,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + public void reportSuccessfulPasswordAttempt() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - + synchronized (this) { if (mFailedPasswordAttempts != 0 || mPasswordOwner >= 0) { long ident = Binder.clearCallingIdentity(); @@ -987,7 +1736,212 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - + + public ComponentName setGlobalProxy(ComponentName who, String proxySpec, + String exclusionList) { + synchronized(this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + + ActiveAdmin admin = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY); + + // Scan through active admins and find if anyone has already + // set the global proxy. + final int N = mAdminList.size(); + Set<ComponentName> compSet = mAdminMap.keySet(); + for (ComponentName component : compSet) { + ActiveAdmin ap = mAdminMap.get(component); + if ((ap.specifiesGlobalProxy) && (!component.equals(who))) { + // Another admin already sets the global proxy + // Return it to the caller. + return component; + } + } + if (proxySpec == null) { + admin.specifiesGlobalProxy = false; + admin.globalProxySpec = null; + admin.globalProxyExclusionList = null; + } else { + + admin.specifiesGlobalProxy = true; + admin.globalProxySpec = proxySpec; + admin.globalProxyExclusionList = exclusionList; + } + + // Reset the global proxy accordingly + // Do this using system permissions, as apps cannot write to secure settings + long origId = Binder.clearCallingIdentity(); + resetGlobalProxy(); + Binder.restoreCallingIdentity(origId); + return null; + } + } + + public ComponentName getGlobalProxyAdmin() { + synchronized(this) { + // Scan through active admins and find if anyone has already + // set the global proxy. + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + ActiveAdmin ap = mAdminList.get(i); + if (ap.specifiesGlobalProxy) { + // Device admin sets the global proxy + // Return it to the caller. + return ap.info.getComponent(); + } + } + } + // No device admin sets the global proxy. + return null; + } + + private void resetGlobalProxy() { + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + ActiveAdmin ap = mAdminList.get(i); + if (ap.specifiesGlobalProxy) { + saveGlobalProxy(ap.globalProxySpec, ap.globalProxyExclusionList); + return; + } + } + // No device admins defining global proxies - reset global proxy settings to none + saveGlobalProxy(null, null); + } + + private void saveGlobalProxy(String proxySpec, String exclusionList) { + if (exclusionList == null) { + exclusionList = ""; + } + if (proxySpec == null) { + proxySpec = ""; + } + // Remove white spaces + proxySpec = proxySpec.trim(); + String data[] = proxySpec.split(":"); + int proxyPort = 8080; + if (data.length > 1) { + try { + proxyPort = Integer.parseInt(data[1]); + } catch (NumberFormatException e) {} + } + exclusionList = exclusionList.trim(); + ContentResolver res = mContext.getContentResolver(); + Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_HOST, data[0]); + Settings.Secure.putInt(res, Settings.Secure.GLOBAL_HTTP_PROXY_PORT, proxyPort); + Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, + exclusionList); + } + + /** + * Set the storage encryption request for a single admin. Returns the new total request + * status (for all admins). + */ + public int setStorageEncryption(ComponentName who, boolean encrypt) { + synchronized (this) { + // Check for permissions + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_ENCRYPTED_STORAGE); + + // Quick exit: If the filesystem does not support encryption, we can exit early. + if (!isEncryptionSupported()) { + return DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; + } + + // (1) Record the value for the admin so it's sticky + if (ap.encryptionRequested != encrypt) { + ap.encryptionRequested = encrypt; + saveSettingsLocked(); + } + + // (2) Compute "max" for all admins + boolean newRequested = false; + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + newRequested |= mAdminList.get(i).encryptionRequested; + } + + // Notify OS of new request + setEncryptionRequested(newRequested); + + // Return the new global request status + return newRequested + ? DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE + : DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE; + } + } + + /** + * Get the current storage encryption request status for a given admin, or aggregate of all + * active admins. + */ + public boolean getStorageEncryption(ComponentName who) { + synchronized (this) { + // Check for permissions if a particular caller is specified + if (who != null) { + // When checking for a single caller, status is based on caller's request + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_ENCRYPTED_STORAGE); + return ap.encryptionRequested; + } + + // If no particular caller is specified, return the aggregate set of requests. + // This is short circuited by returning true on the first hit. + final int N = mAdminList.size(); + for (int i = 0; i < N; i++) { + if (mAdminList.get(i).encryptionRequested) { + return true; + } + } + return false; + } + } + + /** + * Get the current encryption status of the device. + */ + public int getStorageEncryptionStatus() { + return getEncryptionStatus(); + } + + /** + * Hook to low-levels: This should report if the filesystem supports encrypted storage. + */ + private boolean isEncryptionSupported() { + // Note, this can be implemented as + // return getEncryptionStatus() != DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; + // But is provided as a separate internal method if there's a faster way to do a + // simple check for supported-or-not. + return getEncryptionStatus() != DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; + } + + /** + * Hook to low-levels: Reporting the current status of encryption. + * @return A value such as {@link DevicePolicyManager#ENCRYPTION_STATUS_UNSUPPORTED} or + * {@link DevicePolicyManager#ENCRYPTION_STATUS_INACTIVE} or + * {@link DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE}. + */ + private int getEncryptionStatus() { + String status = SystemProperties.get("ro.crypto.state", "unsupported"); + if ("encrypted".equalsIgnoreCase(status)) { + return DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE; + } else if ("unencrypted".equalsIgnoreCase(status)) { + return DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE; + } else { + return DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; + } + } + + /** + * Hook to low-levels: If needed, record the new admin setting for encryption. + */ + private void setEncryptionRequested(boolean encrypt) { + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) @@ -998,12 +1952,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + ", uid=" + Binder.getCallingUid()); return; } - + final Printer p = new PrintWriterPrinter(pw); - + synchronized (this) { p.println("Current Device Policy Manager state:"); - + p.println(" Enabled Device Admins:"); final int N = mAdminList.size(); for (int i=0; i<N; i++) { @@ -1014,11 +1968,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { ap.dump(" ", pw); } } - + pw.println(" "); pw.print(" mActivePasswordQuality=0x"); pw.println(Integer.toHexString(mActivePasswordQuality)); pw.print(" mActivePasswordLength="); pw.println(mActivePasswordLength); + pw.print(" mActivePasswordUpperCase="); pw.println(mActivePasswordUpperCase); + pw.print(" mActivePasswordLowerCase="); pw.println(mActivePasswordLowerCase); + pw.print(" mActivePasswordLetters="); pw.println(mActivePasswordLetters); + pw.print(" mActivePasswordNumeric="); pw.println(mActivePasswordNumeric); + pw.print(" mActivePasswordSymbols="); pw.println(mActivePasswordSymbols); + pw.print(" mActivePasswordNonLetter="); pw.println(mActivePasswordNonLetter); pw.print(" mFailedPasswordAttempts="); pw.println(mFailedPasswordAttempts); pw.print(" mPasswordOwner="); pw.println(mPasswordOwner); } diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java index 0b1a4a3..0fba7c3 100644 --- a/services/java/com/android/server/DeviceStorageMonitorService.java +++ b/services/java/com/android/server/DeviceStorageMonitorService.java @@ -16,7 +16,6 @@ package com.android.server; -import com.android.server.am.ActivityManagerService; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -38,7 +37,6 @@ import android.provider.Settings; import android.util.Config; import android.util.EventLog; import android.util.Slog; -import android.provider.Settings; /** * This class implements a service to monitor the amount of disk @@ -66,6 +64,7 @@ class DeviceStorageMonitorService extends Binder { private static final int MONITOR_INTERVAL = 1; //in minutes private static final int LOW_MEMORY_NOTIFICATION_ID = 1; private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10; + private static final int DEFAULT_THRESHOLD_MAX_BYTES = 500*1024*1024; // 500MB private static final int DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES = 12*60; //in minutes private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000; @@ -271,13 +270,18 @@ class DeviceStorageMonitorService extends Binder { * any way */ private long getMemThreshold() { - int value = Settings.Secure.getInt( + long value = Settings.Secure.getInt( mContentResolver, Settings.Secure.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE); if(localLOGV) Slog.v(TAG, "Threshold Percentage="+value); + value *= mTotalMemory; + long maxValue = Settings.Secure.getInt( + mContentResolver, + Settings.Secure.SYS_STORAGE_THRESHOLD_MAX_BYTES, + DEFAULT_THRESHOLD_MAX_BYTES); //evaluate threshold value - return mTotalMemory*value; + return value < maxValue ? value : maxValue; } /* diff --git a/services/java/com/android/server/DockObserver.java b/services/java/com/android/server/DockObserver.java index bee8872..dea9007 100644 --- a/services/java/com/android/server/DockObserver.java +++ b/services/java/com/android/server/DockObserver.java @@ -102,8 +102,8 @@ class DockObserver extends UEventObserver { try { FileReader file = new FileReader(DOCK_STATE_PATH); int len = file.read(buffer, 0, 1024); + file.close(); mPreviousDockState = mDockState = Integer.valueOf((new String(buffer, 0, len)).trim()); - } catch (FileNotFoundException e) { Slog.w(TAG, "This kernel does not have dock station support"); } catch (Exception e) { @@ -158,13 +158,17 @@ class DockObserver extends UEventObserver { { String whichSound = null; if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) { - if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) { + if ((mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) || + (mPreviousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || + (mPreviousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { whichSound = Settings.System.DESK_UNDOCK_SOUND; } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) { whichSound = Settings.System.CAR_UNDOCK_SOUND; } } else { - if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) { + if ((mDockState == Intent.EXTRA_DOCK_STATE_DESK) || + (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || + (mDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { whichSound = Settings.System.DESK_DOCK_SOUND; } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) { whichSound = Settings.System.CAR_DOCK_SOUND; diff --git a/services/java/com/android/server/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java index 0e45145..5c878c9 100644 --- a/services/java/com/android/server/DropBoxManagerService.java +++ b/services/java/com/android/server/DropBoxManagerService.java @@ -97,10 +97,18 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { // Ensure that all log entries have a unique timestamp private long mLastTimestamp = 0; + private volatile boolean mBooted = false; + /** Receives events that might indicate a need to clean up files. */ private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + if (intent != null && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { + mBooted = true; + return; + } + + // Else, for ACTION_DEVICE_STORAGE_LOW: mCachedQuotaUptimeMillis = 0; // Force a re-check of quota size // Run the initialization in the background (not this main thread). @@ -132,7 +140,11 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { // Set up intent receivers mContext = context; mContentResolver = context.getContentResolver(); - context.registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW)); + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW); + filter.addAction(Intent.ACTION_BOOT_COMPLETED); + context.registerReceiver(mReceiver, filter); mContentResolver.registerContentObserver( Settings.Secure.CONTENT_URI, true, @@ -218,8 +230,17 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { } } while (read > 0); - createEntry(temp, tag, flags); + long time = createEntry(temp, tag, flags); temp = null; + + Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED); + dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag); + dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time); + if (!mBooted) { + dropboxIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + } + mContext.sendBroadcast(dropboxIntent, android.Manifest.permission.READ_LOGS); + } catch (IOException e) { Slog.e(TAG, "Can't write: " + tag, e); } finally { @@ -343,16 +364,17 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { if ((entry.flags & DropBoxManager.IS_TEXT) != 0 && (doPrint || !doFile)) { DropBoxManager.Entry dbe = null; + InputStreamReader isr = null; try { dbe = new DropBoxManager.Entry( entry.tag, entry.timestampMillis, entry.file, entry.flags); if (doPrint) { - InputStreamReader r = new InputStreamReader(dbe.getInputStream()); + isr = new InputStreamReader(dbe.getInputStream()); char[] buf = new char[4096]; boolean newline = false; for (;;) { - int n = r.read(buf); + int n = isr.read(buf); if (n <= 0) break; out.append(buf, 0, n); newline = (buf[n - 1] == '\n'); @@ -376,6 +398,12 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { Slog.e(TAG, "Can't read: " + entry.file, e); } finally { if (dbe != null) dbe.close(); + if (isr != null) { + try { + isr.close(); + } catch (IOException unused) { + } + } } } @@ -597,7 +625,7 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { } /** Moves a temporary file to a final log filename and enrolls it. */ - private synchronized void createEntry(File temp, String tag, int flags) throws IOException { + private synchronized long createEntry(File temp, String tag, int flags) throws IOException { long t = System.currentTimeMillis(); // Require each entry to have a unique timestamp; if there are entries @@ -636,6 +664,7 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { } else { enrollEntry(new EntryFile(temp, mDropBoxDir, tag, t, flags, mBlockSize)); } + return t; } /** diff --git a/services/java/com/android/server/HeadsetObserver.java b/services/java/com/android/server/HeadsetObserver.java deleted file mode 100644 index 6f0a91d..0000000 --- a/services/java/com/android/server/HeadsetObserver.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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.ActivityManagerNative; -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.os.Message; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.os.UEventObserver; -import android.util.Slog; -import android.media.AudioManager; - -import java.io.FileReader; -import java.io.FileNotFoundException; - -/** - * <p>HeadsetObserver monitors for a wired headset. - */ -class HeadsetObserver extends UEventObserver { - private static final String TAG = HeadsetObserver.class.getSimpleName(); - private static final boolean LOG = true; - - private static final String HEADSET_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/h2w"; - private static final String HEADSET_STATE_PATH = "/sys/class/switch/h2w/state"; - private static final String HEADSET_NAME_PATH = "/sys/class/switch/h2w/name"; - - private static final int BIT_HEADSET = (1 << 0); - private static final int BIT_HEADSET_NO_MIC = (1 << 1); - private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC); - private static final int HEADSETS_WITH_MIC = BIT_HEADSET; - - private int mHeadsetState; - private int mPrevHeadsetState; - private String mHeadsetName; - - private final Context mContext; - private final WakeLock mWakeLock; // held while there is a pending route change - - public HeadsetObserver(Context context) { - mContext = context; - PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetObserver"); - mWakeLock.setReferenceCounted(false); - - startObserving(HEADSET_UEVENT_MATCH); - - init(); // set initial status - } - - @Override - public void onUEvent(UEventObserver.UEvent event) { - if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString()); - - try { - update(event.get("SWITCH_NAME"), Integer.parseInt(event.get("SWITCH_STATE"))); - } catch (NumberFormatException e) { - Slog.e(TAG, "Could not parse switch state from event " + event); - } - } - - private synchronized final void init() { - char[] buffer = new char[1024]; - - String newName = mHeadsetName; - int newState = mHeadsetState; - mPrevHeadsetState = mHeadsetState; - try { - FileReader file = new FileReader(HEADSET_STATE_PATH); - int len = file.read(buffer, 0, 1024); - newState = Integer.valueOf((new String(buffer, 0, len)).trim()); - - file = new FileReader(HEADSET_NAME_PATH); - len = file.read(buffer, 0, 1024); - newName = new String(buffer, 0, len).trim(); - - } catch (FileNotFoundException e) { - Slog.w(TAG, "This kernel does not have wired headset support"); - } catch (Exception e) { - Slog.e(TAG, "" , e); - } - - update(newName, newState); - } - - private synchronized final void update(String newName, int newState) { - // Retain only relevant bits - int headsetState = newState & SUPPORTED_HEADSETS; - int newOrOld = headsetState | mHeadsetState; - int delay = 0; - // reject all suspect transitions: only accept state changes from: - // - a: 0 heaset to 1 headset - // - b: 1 headset to 0 headset - if (mHeadsetState == headsetState || ((newOrOld & (newOrOld - 1)) != 0)) { - return; - } - - mHeadsetName = newName; - mPrevHeadsetState = mHeadsetState; - mHeadsetState = headsetState; - - if (headsetState == 0) { - Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY); - mContext.sendBroadcast(intent); - // It can take hundreds of ms flush the audio pipeline after - // apps pause audio playback, but audio route changes are - // immediate, so delay the route change by 1000ms. - // This could be improved once the audio sub-system provides an - // interface to clear the audio pipeline. - delay = 1000; - } else { - // Insert the same delay for headset connection so that the connection event is not - // broadcast before the disconnection event in case of fast removal/insertion - if (mHandler.hasMessages(0)) { - delay = 1000; - } - } - mWakeLock.acquire(); - mHandler.sendMessageDelayed(mHandler.obtainMessage(0, - mHeadsetState, - mPrevHeadsetState, - mHeadsetName), - delay); - } - - private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) { - int allHeadsets = SUPPORTED_HEADSETS; - for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) { - if ((curHeadset & allHeadsets) != 0) { - sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName); - allHeadsets &= ~curHeadset; - } - } - } - - private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) { - if ((headsetState & headset) != (prevHeadsetState & headset)) { - // Pack up the values and broadcast them to everyone - Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - int state = 0; - int microphone = 0; - - if ((headset & HEADSETS_WITH_MIC) != 0) { - microphone = 1; - } - if ((headsetState & headset) != 0) { - state = 1; - } - intent.putExtra("state", state); - intent.putExtra("name", headsetName); - intent.putExtra("microphone", microphone); - - if (LOG) Slog.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone); - // TODO: Should we require a permission? - ActivityManagerNative.broadcastStickyIntent(intent, null); - } - } - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - sendIntents(msg.arg1, msg.arg2, (String)msg.obj); - mWakeLock.release(); - } - }; -} diff --git a/services/java/com/android/server/InputApplication.java b/services/java/com/android/server/InputApplication.java index 38420d4..ae09484 100644 --- a/services/java/com/android/server/InputApplication.java +++ b/services/java/com/android/server/InputApplication.java @@ -18,16 +18,19 @@ package com.android.server; /** * Describes input-related application properties for use by the input dispatcher. - * * @hide */ public final class InputApplication { + // Application handle. + public InputApplicationHandle inputApplicationHandle; + // Application name. public String name; - + // Dispatching timeout. public long dispatchingTimeoutNanos; - - // The application window token. - public Object token; + + public void recycle() { + inputApplicationHandle = null; + } } diff --git a/services/java/com/android/server/InputApplicationHandle.java b/services/java/com/android/server/InputApplicationHandle.java new file mode 100644 index 0000000..d396d11 --- /dev/null +++ b/services/java/com/android/server/InputApplicationHandle.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2011 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; + +/** + * Functions as a handle for an application that can receive input. + * Enables the native input dispatcher to refer indirectly to the window manager's + * application window token. + * @hide + */ +public final class InputApplicationHandle { + // Pointer to the native input application handle. + // This field is lazily initialized via JNI. + @SuppressWarnings("unused") + private int ptr; + + // The window manager's application window token. + public final WindowManagerService.AppWindowToken appWindowToken; + + private native void nativeDispose(); + + public InputApplicationHandle(WindowManagerService.AppWindowToken appWindowToken) { + this.appWindowToken = appWindowToken; + } + + @Override + protected void finalize() throws Throwable { + nativeDispose(); + super.finalize(); + } +} diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java index ba39c57..8d249ff 100644 --- a/services/java/com/android/server/InputManager.java +++ b/services/java/com/android/server/InputManager.java @@ -19,10 +19,17 @@ package com.android.server; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.os.Environment; import android.os.SystemProperties; import android.util.Slog; @@ -30,7 +37,9 @@ import android.util.Xml; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; +import android.view.KeyEvent; import android.view.Surface; +import android.view.WindowManager; import java.io.BufferedReader; import java.io.File; @@ -41,7 +50,6 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Properties; /* * Wraps the C++ InputManager and provides its callbacks. @@ -69,7 +77,7 @@ public class InputManager { private static native boolean nativeHasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists); private static native void nativeRegisterInputChannel(InputChannel inputChannel, - boolean monitor); + InputWindowHandle inputWindowHandle, boolean monitor); private static native void nativeUnregisterInputChannel(InputChannel inputChannel); private static native int nativeInjectInputEvent(InputEvent event, int injectorPid, int injectorUid, int syncMode, int timeoutMillis); @@ -79,6 +87,8 @@ public class InputManager { private static native InputDevice nativeGetInputDevice(int deviceId); private static native void nativeGetInputConfiguration(Configuration configuration); private static native int[] nativeGetInputDeviceIds(); + private static native boolean nativeTransferTouchFocus(InputChannel fromChannel, + InputChannel toChannel); private static native String nativeDump(); // Input event injection constants defined in InputDispatcher.h. @@ -230,7 +240,7 @@ public class InputManager { } InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName); - nativeRegisterInputChannel(inputChannels[0], true); + nativeRegisterInputChannel(inputChannels[0], null, true); inputChannels[0].dispose(); // don't need to retain the Java object reference return inputChannels[1]; } @@ -238,13 +248,16 @@ public class InputManager { /** * Registers an input channel so that it can be used as an input event target. * @param inputChannel The input channel to register. + * @param inputWindowHandle The handle of the input window associated with the + * input channel, or null if none. */ - public void registerInputChannel(InputChannel inputChannel) { + public void registerInputChannel(InputChannel inputChannel, + InputWindowHandle inputWindowHandle) { if (inputChannel == null) { throw new IllegalArgumentException("inputChannel must not be null."); } - nativeRegisterInputChannel(inputChannel, false); + nativeRegisterInputChannel(inputChannel, inputWindowHandle, false); } /** @@ -326,28 +339,78 @@ public class InputManager { nativeSetInputDispatchMode(enabled, frozen); } + /** + * Atomically transfers touch focus from one window to another as identified by + * their input channels. It is possible for multiple windows to have + * touch focus if they support split touch dispatch + * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this + * method only transfers touch focus of the specified window without affecting + * other windows that may also have touch focus at the same time. + * @param fromChannel The channel of a window that currently has touch focus. + * @param toChannel The channel of the window that should receive touch focus in + * place of the first. + * @return True if the transfer was successful. False if the window with the + * specified channel did not actually have touch focus at the time of the request. + */ + public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) { + if (fromChannel == null) { + throw new IllegalArgumentException("fromChannel must not be null."); + } + if (toChannel == null) { + throw new IllegalArgumentException("toChannel must not be null."); + } + return nativeTransferTouchFocus(fromChannel, toChannel); + } + public void dump(PrintWriter pw) { String dumpStr = nativeDump(); if (dumpStr != null) { pw.println(dumpStr); } } - - private static final class VirtualKeyDefinition { - public int scanCode; - - // configured position data, specified in display coords - public int centerX; - public int centerY; - public int width; - public int height; - } - - private static final class InputDeviceCalibration { - public String[] keys; - public String[] values; + + private static final class PointerIcon { + public Bitmap bitmap; + public float hotSpotX; + public float hotSpotY; + + public static PointerIcon load(Resources resources, int resourceId) { + PointerIcon icon = new PointerIcon(); + + XmlResourceParser parser = resources.getXml(resourceId); + final int bitmapRes; + try { + XmlUtils.beginDocument(parser, "pointer-icon"); + + TypedArray a = resources.obtainAttributes( + parser, com.android.internal.R.styleable.PointerIcon); + bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0); + icon.hotSpotX = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0); + icon.hotSpotY = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0); + a.recycle(); + } catch (Exception ex) { + Slog.e(TAG, "Exception parsing pointer icon resource.", ex); + return null; + } finally { + parser.close(); + } + + if (bitmapRes == 0) { + Slog.e(TAG, "<pointer-icon> is missing bitmap attribute"); + return null; + } + + Drawable drawable = resources.getDrawable(bitmapRes); + if (!(drawable instanceof BitmapDrawable)) { + Slog.e(TAG, "<pointer-icon> bitmap attribute must refer to a bitmap drawable"); + return null; + } + + icon.bitmap = ((BitmapDrawable)drawable).getBitmap(); + return icon; + } } - + /* * Callbacks from native. */ @@ -360,7 +423,7 @@ public class InputManager { @SuppressWarnings("unused") public void notifyConfigurationChanged(long whenNanos) { - mWindowManagerService.sendNewConfiguration(); + mWindowManagerService.mInputMonitor.notifyConfigurationChanged(); } @SuppressWarnings("unused") @@ -369,28 +432,35 @@ public class InputManager { } @SuppressWarnings("unused") - public void notifyInputChannelBroken(InputChannel inputChannel) { - mWindowManagerService.mInputMonitor.notifyInputChannelBroken(inputChannel); + public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) { + mWindowManagerService.mInputMonitor.notifyInputChannelBroken(inputWindowHandle); } @SuppressWarnings("unused") - public long notifyANR(Object token, InputChannel inputChannel) { - return mWindowManagerService.mInputMonitor.notifyANR(token, inputChannel); + public long notifyANR(InputApplicationHandle inputApplicationHandle, + InputWindowHandle inputWindowHandle) { + return mWindowManagerService.mInputMonitor.notifyANR( + inputApplicationHandle, inputWindowHandle); } @SuppressWarnings("unused") - public int interceptKeyBeforeQueueing(long whenNanos, int action, int flags, - int keyCode, int scanCode, int policyFlags, boolean isScreenOn) { + public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) { return mWindowManagerService.mInputMonitor.interceptKeyBeforeQueueing( - whenNanos, action, flags, keyCode, scanCode, policyFlags, isScreenOn); + event, policyFlags, isScreenOn); + } + + @SuppressWarnings("unused") + public boolean interceptKeyBeforeDispatching(InputWindowHandle focus, + KeyEvent event, int policyFlags) { + return mWindowManagerService.mInputMonitor.interceptKeyBeforeDispatching( + focus, event, policyFlags); } @SuppressWarnings("unused") - public boolean interceptKeyBeforeDispatching(InputChannel focus, int action, - int flags, int keyCode, int scanCode, int metaState, int repeatCount, - int policyFlags) { - return mWindowManagerService.mInputMonitor.interceptKeyBeforeDispatching(focus, - action, flags, keyCode, scanCode, metaState, repeatCount, policyFlags); + public KeyEvent dispatchUnhandledKey(InputWindowHandle focus, + KeyEvent event, int policyFlags) { + return mWindowManagerService.mInputMonitor.dispatchUnhandledKey( + focus, event, policyFlags); } @SuppressWarnings("unused") @@ -419,79 +489,6 @@ public class InputManager { } @SuppressWarnings("unused") - public VirtualKeyDefinition[] getVirtualKeyDefinitions(String deviceName) { - ArrayList<VirtualKeyDefinition> keys = new ArrayList<VirtualKeyDefinition>(); - - try { - FileInputStream fis = new FileInputStream( - "/sys/board_properties/virtualkeys." + deviceName); - InputStreamReader isr = new InputStreamReader(fis); - BufferedReader br = new BufferedReader(isr, 2048); - String str = br.readLine(); - if (str != null) { - String[] it = str.split(":"); - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "***** VIRTUAL KEYS: " + it); - final int N = it.length-6; - for (int i=0; i<=N; i+=6) { - if (!"0x01".equals(it[i])) { - Slog.w(TAG, "Unknown virtual key type at elem #" - + i + ": " + it[i] + " for device " + deviceName); - continue; - } - try { - VirtualKeyDefinition key = new VirtualKeyDefinition(); - key.scanCode = Integer.parseInt(it[i+1]); - key.centerX = Integer.parseInt(it[i+2]); - key.centerY = Integer.parseInt(it[i+3]); - key.width = Integer.parseInt(it[i+4]); - key.height = Integer.parseInt(it[i+5]); - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Virtual key " - + key.scanCode + ": center=" + key.centerX + "," - + key.centerY + " size=" + key.width + "x" - + key.height); - keys.add(key); - } catch (NumberFormatException e) { - Slog.w(TAG, "Bad number in virtual key definition at region " - + i + " in: " + str + " for device " + deviceName, e); - } - } - } - br.close(); - } catch (FileNotFoundException e) { - Slog.i(TAG, "No virtual keys found for device " + deviceName + "."); - } catch (IOException e) { - Slog.w(TAG, "Error reading virtual keys for device " + deviceName + ".", e); - } - - return keys.toArray(new VirtualKeyDefinition[keys.size()]); - } - - @SuppressWarnings("unused") - public InputDeviceCalibration getInputDeviceCalibration(String deviceName) { - // Calibration is specified as a sequence of colon-delimited key value pairs. - Properties properties = new Properties(); - File calibrationFile = new File(Environment.getRootDirectory(), - CALIBRATION_DIR_PATH + deviceName + ".idc"); - if (calibrationFile.exists()) { - try { - properties.load(new FileInputStream(calibrationFile)); - } catch (IOException ex) { - Slog.w(TAG, "Error reading input device calibration properties for device " - + deviceName + " from " + calibrationFile + ".", ex); - } - } else { - Slog.i(TAG, "No input device calibration properties found for device " - + deviceName + "."); - return null; - } - - InputDeviceCalibration calibration = new InputDeviceCalibration(); - calibration.keys = properties.keySet().toArray(new String[properties.size()]); - calibration.values = properties.values().toArray(new String[properties.size()]); - return calibration; - } - - @SuppressWarnings("unused") public String[] getExcludedDeviceNames() { ArrayList<String> names = new ArrayList<String>(); @@ -539,5 +536,19 @@ public class InputManager { } return result; } + + @SuppressWarnings("unused") + public int getPointerLayer() { + return mWindowManagerService.mPolicy.windowTypeToLayerLw( + WindowManager.LayoutParams.TYPE_POINTER) + * WindowManagerService.TYPE_LAYER_MULTIPLIER + + WindowManagerService.TYPE_LAYER_OFFSET; + } + + @SuppressWarnings("unused") + public PointerIcon getPointerIcon() { + return PointerIcon.load(mContext.getResources(), + com.android.internal.R.drawable.pointer_arrow_icon); + } } } diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index ecad3cc..21c1e81 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -61,18 +61,21 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.provider.Settings; import android.provider.Settings.Secure; +import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; import android.util.EventLog; +import android.util.Pair; import android.util.Slog; import android.util.PrintWriterPrinter; import android.util.Printer; import android.view.IWindowManager; import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodSubtype; import java.io.FileDescriptor; import java.io.IOException; @@ -80,6 +83,7 @@ import java.io.PrintWriter; import java.text.Collator; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -93,6 +97,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final String TAG = "InputManagerService"; static final int MSG_SHOW_IM_PICKER = 1; + static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2; + static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3; + static final int MSG_SHOW_IM_CONFIG = 4; static final int MSG_UNBIND_INPUT = 1000; static final int MSG_BIND_INPUT = 1010; @@ -109,8 +116,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final long TIME_TO_RECONNECT = 10*1000; + private static final int NOT_A_SUBTYPE_ID = -1; + private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID); + private static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; + private static final String SUBTYPE_MODE_VOICE = "voice"; + + // TODO: Will formalize this value as API + private static final String SUBTYPE_EXTRAVALUE_EXCLUDE_FROM_LAST_IME = + "excludeFromLastInputMethod"; + final Context mContext; + final Resources mRes; final Handler mHandler; + final InputMethodSettings mSettings; final SettingsObserver mSettingsObserver; final StatusBarManagerService mStatusBar; final IWindowManager mIWindowManager; @@ -120,13 +138,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // All known input methods. mMethodMap also serves as the global // lock for this class. - final ArrayList<InputMethodInfo> mMethodList - = new ArrayList<InputMethodInfo>(); - final HashMap<String, InputMethodInfo> mMethodMap - = new HashMap<String, InputMethodInfo>(); - - final TextUtils.SimpleStringSplitter mStringColonSplitter - = new TextUtils.SimpleStringSplitter(':'); + final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>(); + final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>(); class SessionState { final ClientState client; @@ -224,6 +237,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub String mCurId; /** + * The current subtype of the current input method. + */ + private InputMethodSubtype mCurrentSubtype; + + // This list contains the pairs of InputMethodInfo and InputMethodSubtype. + private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>> + mShortcutInputMethodsAndSubtypes = + new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>(); + + /** * Set to true if our ServiceConnection is currently actively bound to * a service (whether or not we have gotten its IBinder back yet). */ @@ -292,6 +315,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub AlertDialog mSwitchingDialog; InputMethodInfo[] mIms; CharSequence[] mItems; + int[] mSubtypeIds; class SettingsObserver extends ContentObserver { SettingsObserver(Handler handler) { @@ -299,6 +323,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub ContentResolver resolver = mContext.getContentResolver(); resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.DEFAULT_INPUT_METHOD), false, this); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this); } @Override public void onChange(boolean selfChange) { @@ -351,9 +377,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!doit) { return true; } - - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, ""); + resetSelectedInputMethodAndSubtypeLocked(""); chooseNewDefaultIMELocked(); return true; } @@ -406,19 +430,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Uh oh, current input method is no longer around! // Pick another one... Slog.i(TAG, "Current input method removed: " + curInputMethodId); + mStatusBar.setIMEButtonVisible(mCurToken, false); if (!chooseNewDefaultIMELocked()) { changed = true; curIm = null; - curInputMethodId = ""; Slog.i(TAG, "Unsetting current input method"); - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, - curInputMethodId); + resetSelectedInputMethodAndSubtypeLocked(""); } } } } - + if (curIm == null) { // We currently don't have a default input method... is // one now available? @@ -449,6 +471,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public InputMethodManagerService(Context context, StatusBarManagerService statusBar) { mContext = context; + mRes = context.getResources(); mHandler = new Handler(this); mIWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService(Context.WINDOW_SERVICE)); @@ -469,27 +492,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mStatusBar = statusBar; statusBar.setIconVisibility("ime", false); + // mSettings should be created before buildInputMethodListLocked + mSettings = new InputMethodSettings( + mRes, context.getContentResolver(), mMethodMap, mMethodList); buildInputMethodListLocked(mMethodList, mMethodMap); + mSettings.enableAllIMEsIfThereIsNoEnabledIME(); - final String enabledStr = Settings.Secure.getString( - mContext.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS); - Slog.i(TAG, "Enabled input methods: " + enabledStr); - final String defaultIme = Settings.Secure.getString(mContext - .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); - if (enabledStr == null || TextUtils.isEmpty(defaultIme)) { - Slog.i(TAG, "Enabled input methods or default IME has not been set, enabling all"); + if (TextUtils.isEmpty(Settings.Secure.getString( + mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD))) { InputMethodInfo defIm = null; - StringBuilder sb = new StringBuilder(256); - final int N = mMethodList.size(); - for (int i=0; i<N; i++) { - InputMethodInfo imi = mMethodList.get(i); - Slog.i(TAG, "Adding: " + imi.getId()); - if (i > 0) sb.append(':'); - sb.append(imi.getId()); + for (InputMethodInfo imi: mMethodList) { if (defIm == null && imi.getIsDefaultResourceId() != 0) { try { - Resources res = mContext.createPackageContext( + Resources res = context.createPackageContext( imi.getPackageName(), 0).getResources(); if (res.getBoolean(imi.getIsDefaultResourceId())) { defIm = imi; @@ -500,15 +515,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } } - if (defIm == null && N > 0) { + if (defIm == null && mMethodList.size() > 0) { defIm = mMethodList.get(0); Slog.i(TAG, "No default found, using " + defIm.getId()); } - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS, sb.toString()); if (defIm != null) { - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, defIm.getId()); + setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false); } } @@ -552,29 +564,40 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public List<InputMethodInfo> getEnabledInputMethodList() { synchronized (mMethodMap) { - return getEnabledInputMethodListLocked(); + return mSettings.getEnabledInputMethodListLocked(); } } - List<InputMethodInfo> getEnabledInputMethodListLocked() { - final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>(); - - final String enabledStr = Settings.Secure.getString( - mContext.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS); - if (enabledStr != null) { - final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; - splitter.setString(enabledStr); + private HashMap<InputMethodInfo, List<InputMethodSubtype>> + getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked() { + HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes = + new HashMap<InputMethodInfo, List<InputMethodSubtype>>(); + for (InputMethodInfo imi: getEnabledInputMethodList()) { + enabledInputMethodAndSubtypes.put( + imi, getEnabledInputMethodSubtypeListLocked(imi, true)); + } + return enabledInputMethodAndSubtypes; + } - while (splitter.hasNext()) { - InputMethodInfo info = mMethodMap.get(splitter.next()); - if (info != null) { - res.add(info); - } - } + public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi, + boolean allowsImplicitlySelectedSubtypes) { + if (imi == null && mCurMethodId != null) { + imi = mMethodMap.get(mCurMethodId); + } + final List<InputMethodSubtype> enabledSubtypes = + mSettings.getEnabledInputMethodSubtypeListLocked(imi); + if (!allowsImplicitlySelectedSubtypes || enabledSubtypes.size() > 0) { + return enabledSubtypes; + } else { + return getApplicableSubtypesLocked(mRes, getSubtypes(imi)); } + } - return res; + public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi, + boolean allowsImplicitlySelectedSubtypes) { + synchronized (mMethodMap) { + return getEnabledInputMethodSubtypeListLocked(imi, allowsImplicitlySelectedSubtypes); + } } public void addClient(IInputMethodClient client, @@ -959,21 +982,44 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + public void setIMEButtonVisible(IBinder token, boolean visible) { + int uid = Binder.getCallingUid(); + long ident = Binder.clearCallingIdentity(); + try { + if (token == null || mCurToken != token) { + Slog.w(TAG, "Ignoring setIMEButtonVisible of uid " + uid + " token: " + token); + return; + } + + synchronized (mMethodMap) { + mStatusBar.setIMEButtonVisible(token, visible); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + void updateFromSettingsLocked() { // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and // ENABLED_INPUT_METHODS is taking care of keeping them correctly in // sync, so we will never have a DEFAULT_INPUT_METHOD that is not // enabled. String id = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD); - if (id != null && id.length() > 0) { + Settings.Secure.DEFAULT_INPUT_METHOD); + // There is no input method selected, try to choose new applicable input method. + if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) { + id = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.DEFAULT_INPUT_METHOD); + } + if (!TextUtils.isEmpty(id)) { try { - setInputMethodLocked(id); + setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id)); } catch (IllegalArgumentException e) { Slog.w(TAG, "Unknown input method from prefs: " + id, e); mCurMethodId = null; unbindCurrentMethodLocked(true); } + mShortcutInputMethodsAndSubtypes.clear(); } else { // There is no longer an input method set, so stop any current one. mCurMethodId = null; @@ -981,21 +1027,54 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - void setInputMethodLocked(String id) { + /* package */ void setInputMethodLocked(String id, int subtypeId) { InputMethodInfo info = mMethodMap.get(id); if (info == null) { throw new IllegalArgumentException("Unknown id: " + id); } if (id.equals(mCurMethodId)) { + InputMethodSubtype subtype = null; + if (subtypeId >= 0 && subtypeId < info.getSubtypeCount()) { + subtype = info.getSubtypeAt(subtypeId); + } + if (subtype != mCurrentSubtype) { + synchronized (mMethodMap) { + if (subtype != null) { + setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true); + } + if (mCurMethod != null) { + try { + if (mInputShown) { + // If mInputShown is false, there is no IME button on the + // system bar. + // Thus there is no need to make it invisible explicitly. + mStatusBar.setIMEButtonVisible(mCurToken, true); + } + // If subtype is null, try to find the most applicable one from + // getCurrentInputMethodSubtype. + if (subtype == null) { + subtype = getCurrentInputMethodSubtype(); + } + mCurMethod.changeInputMethodSubtype(subtype); + } catch (RemoteException e) { + return; + } + } + } + } return; } final long ident = Binder.clearCallingIdentity(); try { + // Set a subtype to this input method. + // subtypeId the name of a subtype which will be set. + setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false); + // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked() + // because mCurMethodId is stored as a history in + // setSelectedInputMethodAndSubtypeLocked(). mCurMethodId = id; - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, id); if (ActivityManagerNative.isSystemReady()) { Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED); @@ -1089,9 +1168,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!mIWindowManager.inputMethodClientHasFocus(client)) { if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid " + uid + ": " + client); + mStatusBar.setIMEButtonVisible(mCurToken, false); return false; } } catch (RemoteException e) { + mStatusBar.setIMEButtonVisible(mCurToken, false); return false; } } @@ -1164,11 +1245,22 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } mCurFocusedWindow = windowToken; + // Should we auto-show the IME even if the caller has not + // specified what should be done with it? + // We only do this automatically if the window can resize + // to accommodate the IME (so what the user sees will give + // them good context without input information being obscured + // by the IME) or if running on a large screen where there + // is more room for the target window + IME. + final boolean doAutoShow = + (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) + == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE + || mRes.getConfiguration().isLayoutSizeAtLeast( + Configuration.SCREENLAYOUT_SIZE_LARGE); + switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) { case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: - if (!isTextEditor || (softInputMode & - WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) - != WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) { + if (!isTextEditor || !doAutoShow) { if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) { // There is no focus view, and this window will // be behind any soft input window, so hide the @@ -1176,13 +1268,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) Slog.v(TAG, "Unspecified window will hide input"); hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null); } - } else if (isTextEditor && (softInputMode & - WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) - == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE - && (softInputMode & - WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { + } else if (isTextEditor && doAutoShow && (softInputMode & + WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { // There is a focus view, and we are navigating forward // into the window, so show the input window for the user. + // We only do this automatically if the window an resize + // to accomodate the IME (so what the user sees will give + // them good context without input information being obscured + // by the IME) or if running on a large screen where there + // is more room for the target window + IME. if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); } @@ -1223,15 +1317,59 @@ public class InputMethodManagerService extends IInputMethodManager.Stub synchronized (mMethodMap) { if (mCurClient == null || client == null || mCurClient.client.asBinder() != client.asBinder()) { - Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of uid " + Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid " + Binder.getCallingUid() + ": " + client); } - mHandler.sendEmptyMessage(MSG_SHOW_IM_PICKER); + // Always call subtype picker, because subtype picker is a superset of input method + // picker. + mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER); } } public void setInputMethod(IBinder token, String id) { + setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID); + } + + public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) { + synchronized (mMethodMap) { + if (subtype != null) { + setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode( + mMethodMap.get(id), subtype.hashCode())); + } else { + setInputMethod(token, id); + } + } + } + + public void showInputMethodAndSubtypeEnablerFromClient( + IInputMethodClient client, String inputMethodId) { + synchronized (mMethodMap) { + if (mCurClient == null || client == null + || mCurClient.client.asBinder() != client.asBinder()) { + Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client); + } + executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( + MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId)); + } + } + + public boolean switchToLastInputMethod(IBinder token) { + synchronized (mMethodMap) { + Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); + if (lastIme != null) { + InputMethodInfo imi = mMethodMap.get(lastIme.first); + if (imi != null) { + setInputMethodWithSubtypeId(token, lastIme.first, getSubtypeIdFromHashCode( + imi, Integer.valueOf(lastIme.second))); + return true; + } + } + return false; + } + } + + private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) { synchronized (mMethodMap) { if (token == null) { if (mContext.checkCallingOrSelfPermission( @@ -1249,7 +1387,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub long ident = Binder.clearCallingIdentity(); try { - setInputMethodLocked(id); + setInputMethodLocked(id, subtypeId); } finally { Binder.restoreCallingIdentity(ident); } @@ -1315,6 +1453,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub showInputMethodMenu(); return true; + case MSG_SHOW_IM_SUBTYPE_PICKER: + showInputMethodSubtypeMenu(); + return true; + + case MSG_SHOW_IM_SUBTYPE_ENABLER: + args = (HandlerCaller.SomeArgs)msg.obj; + showInputMethodAndSubtypeEnabler((String)args.arg1); + return true; + + case MSG_SHOW_IM_CONFIG: + showConfigureInputMethods(); + return true; + // --------------------------------------------------------- case MSG_UNBIND_INPUT: @@ -1413,8 +1564,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub & ApplicationInfo.FLAG_SYSTEM) != 0; } + private static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) { + ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); + final int subtypeCount = imi.getSubtypeCount(); + for (int i = 0; i < subtypeCount; ++i) { + subtypes.add(imi.getSubtypeAt(i)); + } + return subtypes; + } + private boolean chooseNewDefaultIMELocked() { - List<InputMethodInfo> enabled = getEnabledInputMethodListLocked(); + List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); if (enabled != null && enabled.size() > 0) { // We'd prefer to fall back on a system IME, since that is safer. int i=enabled.size(); @@ -1425,9 +1585,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub break; } } - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, - enabled.get(i).getId()); + InputMethodInfo imi = enabled.get(i); + if (DEBUG) { + Slog.d(TAG, "New default IME was selected: " + imi.getId()); + } + resetSelectedInputMethodAndSubtypeLocked(imi.getId()); return true; } @@ -1440,7 +1602,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub map.clear(); PackageManager pm = mContext.getPackageManager(); - final Configuration config = mContext.getResources().getConfiguration(); + final Configuration config = mRes.getConfiguration(); final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY; String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(), Secure.DISABLED_SYSTEM_INPUT_METHODS); @@ -1498,7 +1660,34 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // ---------------------------------------------------------------------- - void showInputMethodMenu() { + private void showInputMethodMenu() { + showInputMethodMenuInternal(false); + } + + private void showInputMethodSubtypeMenu() { + showInputMethodMenuInternal(true); + } + + private void showInputMethodAndSubtypeEnabler(String inputMethodId) { + Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + if (!TextUtils.isEmpty(inputMethodId)) { + intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId); + } + mContext.startActivity(intent); + } + + private void showConfigureInputMethods() { + Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + } + + private void showInputMethodMenuInternal(boolean showSubtypes) { if (DEBUG) Slog.v(TAG, "Show switching menu"); final Context context = mContext; @@ -1507,48 +1696,81 @@ public class InputMethodManagerService extends IInputMethodManager.Stub String lastInputMethodId = Settings.Secure.getString(context .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); + int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId); if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId); - final List<InputMethodInfo> immis = getEnabledInputMethodList(); - - if (immis == null) { - return; - } - synchronized (mMethodMap) { - hideInputMethodMenuLocked(); + final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis = + getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(); + if (immis == null || immis.size() == 0) { + return; + } - int N = immis.size(); + hideInputMethodMenuLocked(); - final Map<CharSequence, InputMethodInfo> imMap = - new TreeMap<CharSequence, InputMethodInfo>(Collator.getInstance()); + final Map<CharSequence, Pair<InputMethodInfo, Integer>> imMap = + new TreeMap<CharSequence, Pair<InputMethodInfo, Integer>>(Collator.getInstance()); - for (int i = 0; i < N; ++i) { - InputMethodInfo property = immis.get(i); - if (property == null) { - continue; + for (InputMethodInfo imi: immis.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())); + } + ArrayList<InputMethodSubtype> subtypes = getSubtypes(imi); + CharSequence label = imi.loadLabel(pm); + if (showSubtypes && enabledSubtypeSet.size() > 0) { + final int subtypeCount = imi.getSubtypeCount(); + for (int j = 0; j < subtypeCount; ++j) { + InputMethodSubtype subtype = imi.getSubtypeAt(j); + if (enabledSubtypeSet.contains(String.valueOf(subtype.hashCode()))) { + CharSequence title; + int nameResId = subtype.getNameResId(); + String mode = subtype.getMode(); + if (nameResId != 0) { + title = pm.getText(imi.getPackageName(), nameResId, + imi.getServiceInfo().applicationInfo); + } else { + CharSequence language = subtype.getLocale(); + // TODO: Use more friendly Title and UI + title = label + "," + (mode == null ? "" : mode) + "," + + (language == null ? "" : language); + } + imMap.put(title, new Pair<InputMethodInfo, Integer>(imi, j)); + } + } + } else { + imMap.put(label, + new Pair<InputMethodInfo, Integer>(imi, NOT_A_SUBTYPE_ID)); } - imMap.put(property.loadLabel(pm), property); } - N = imMap.size(); + final int N = imMap.size(); mItems = imMap.keySet().toArray(new CharSequence[N]); - mIms = imMap.values().toArray(new InputMethodInfo[N]); - + mIms = new InputMethodInfo[N]; + mSubtypeIds = new int[N]; int checkedItem = 0; for (int i = 0; i < N; ++i) { + Pair<InputMethodInfo, Integer> value = imMap.get(mItems[i]); + mIms[i] = value.first; + mSubtypeIds[i] = value.second; if (mIms[i].getId().equals(lastInputMethodId)) { - checkedItem = i; - break; + int subtypeId = mSubtypeIds[i]; + if ((subtypeId == NOT_A_SUBTYPE_ID) + || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0) + || (subtypeId == lastInputMethodSubtypeId)) { + checkedItem = i; + } } } - + AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() { public void onClick(DialogInterface dialog, int which) { hideInputMethodMenu(); } }; - + TypedArray a = context.obtainStyledAttributes(null, com.android.internal.R.styleable.DialogPreference, com.android.internal.R.attr.alertDialogStyle, 0); @@ -1562,23 +1784,38 @@ public class InputMethodManagerService extends IInputMethodManager.Stub .setIcon(a.getDrawable( com.android.internal.R.styleable.DialogPreference_dialogTitle)); a.recycle(); - + mDialogBuilder.setSingleChoiceItems(mItems, checkedItem, new AlertDialog.OnClickListener() { public void onClick(DialogInterface dialog, int which) { synchronized (mMethodMap) { - if (mIms == null || mIms.length <= which) { + if (mIms == null || mIms.length <= which + || mSubtypeIds == null || mSubtypeIds.length <= which) { return; } InputMethodInfo im = mIms[which]; + int subtypeId = mSubtypeIds[which]; hideInputMethodMenu(); if (im != null) { - setInputMethodLocked(im.getId()); + if ((subtypeId < 0) + || (subtypeId >= im.getSubtypeCount())) { + subtypeId = NOT_A_SUBTYPE_ID; + } + setInputMethodLocked(im.getId(), subtypeId); } } } }); + if (showSubtypes) { + mDialogBuilder.setPositiveButton( + com.android.internal.R.string.configure_input_methods, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + showConfigureInputMethods(); + } + }); + } mSwitchingDialog = mDialogBuilder.create(); mSwitchingDialog.getWindow().setType( WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); @@ -1630,80 +1867,795 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Make sure this is a valid input method. InputMethodInfo imm = mMethodMap.get(id); if (imm == null) { - if (imm == null) { - throw new IllegalArgumentException("Unknown id: " + mCurMethodId); + throw new IllegalArgumentException("Unknown id: " + mCurMethodId); + } + + List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings + .getEnabledInputMethodsAndSubtypeListLocked(); + + if (enabled) { + for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) { + if (pair.first.equals(id)) { + // We are enabling this input method, but it is already enabled. + // Nothing to do. The previous state was enabled. + return true; + } + } + mSettings.appendAndPutEnabledInputMethodLocked(id, false); + // Previous state was disabled. + return false; + } else { + StringBuilder builder = new StringBuilder(); + if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked( + builder, enabledInputMethodsList, id)) { + // Disabled input method is currently selected, switch to another one. + String selId = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.DEFAULT_INPUT_METHOD); + if (id.equals(selId) && !chooseNewDefaultIMELocked()) { + Slog.i(TAG, "Can't find new IME, unsetting the current input method."); + resetSelectedInputMethodAndSubtypeLocked(""); + } + // Previous state was enabled. + return true; + } else { + // We are disabling the input method but it is already disabled. + // Nothing to do. The previous state was disabled. + return false; } } + } - StringBuilder builder = new StringBuilder(256); + private boolean canAddToLastInputMethod(InputMethodSubtype subtype) { + if (subtype == null) return true; + String[] extraValues = subtype.getExtraValue().split(","); + final int N = extraValues.length; + for (int i = 0; i < N; ++i) { + if (SUBTYPE_EXTRAVALUE_EXCLUDE_FROM_LAST_IME.equals(extraValues[i])) { + return false; + } + } + return true; + } - boolean removed = false; - String firstId = null; + private void saveCurrentInputMethodAndSubtypeToHistory() { + String subtypeId = NOT_A_SUBTYPE_ID_STR; + if (mCurrentSubtype != null) { + subtypeId = String.valueOf(mCurrentSubtype.hashCode()); + } + if (canAddToLastInputMethod(mCurrentSubtype)) { + mSettings.addSubtypeToHistory(mCurMethodId, subtypeId); + } + } - // Look through the currently enabled input methods. - String enabledStr = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS); - if (enabledStr != null) { - final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; - splitter.setString(enabledStr); - while (splitter.hasNext()) { - String curId = splitter.next(); - if (curId.equals(id)) { - if (enabled) { - // We are enabling this input method, but it is - // already enabled. Nothing to do. The previous - // state was enabled. - return true; + private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, + boolean setSubtypeOnly) { + // Update the history of InputMethod and Subtype + saveCurrentInputMethodAndSubtypeToHistory(); + + // Set Subtype here + if (imi == null || subtypeId < 0) { + mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); + mCurrentSubtype = null; + } else { + if (subtypeId < imi.getSubtypeCount()) { + InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId); + mSettings.putSelectedSubtype(subtype.hashCode()); + mCurrentSubtype = subtype; + } else { + mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); + mCurrentSubtype = null; + } + } + + if (!setSubtypeOnly) { + // Set InputMethod here + mSettings.putSelectedInputMethod(imi != null ? imi.getId() : ""); + } + } + + private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) { + InputMethodInfo imi = mMethodMap.get(newDefaultIme); + int lastSubtypeId = NOT_A_SUBTYPE_ID; + // newDefaultIme is empty when there is no candidate for the selected IME. + if (imi != null && !TextUtils.isEmpty(newDefaultIme)) { + String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme); + if (subtypeHashCode != null) { + try { + lastSubtypeId = getSubtypeIdFromHashCode( + imi, Integer.valueOf(subtypeHashCode)); + } catch (NumberFormatException e) { + Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e); + } + } + } + setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false); + } + + private int getSelectedInputMethodSubtypeId(String id) { + InputMethodInfo imi = mMethodMap.get(id); + if (imi == null) { + return NOT_A_SUBTYPE_ID; + } + int subtypeId; + try { + subtypeId = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE); + } catch (SettingNotFoundException e) { + return NOT_A_SUBTYPE_ID; + } + return getSubtypeIdFromHashCode(imi, subtypeId); + } + + private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { + if (imi != null) { + final int subtypeCount = imi.getSubtypeCount(); + for (int i = 0; i < subtypeCount; ++i) { + InputMethodSubtype ims = imi.getSubtypeAt(i); + if (subtypeHashCode == ims.hashCode()) { + return i; + } + } + } + return NOT_A_SUBTYPE_ID; + } + + private static ArrayList<InputMethodSubtype> getApplicableSubtypesLocked( + Resources res, List<InputMethodSubtype> subtypes) { + final String systemLocale = res.getConfiguration().locale.toString(); + if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>(); + HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = + new HashMap<String, InputMethodSubtype>(); + final int N = subtypes.size(); + boolean containsKeyboardSubtype = false; + for (int i = 0; i < N; ++i) { + InputMethodSubtype subtype = subtypes.get(i); + final String locale = subtype.getLocale(); + final String mode = subtype.getMode(); + // When system locale starts with subtype's locale, that subtype will be applicable + // for system locale + // For instance, it's clearly applicable for cases like system locale = en_US and + // subtype = en, but it is not necessarily considered applicable for cases like system + // locale = en and subtype = en_US. + // We just call systemLocale.startsWith(locale) in this function because there is no + // need to find applicable subtypes aggressively unlike + // findLastResortApplicableSubtypeLocked. + if (systemLocale.startsWith(locale)) { + InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode); + // If more applicable subtypes are contained, skip. + if (applicableSubtype != null + && systemLocale.equals(applicableSubtype.getLocale())) continue; + applicableModeAndSubtypesMap.put(mode, subtype); + if (!containsKeyboardSubtype + && SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())) { + containsKeyboardSubtype = true; + } + } + } + ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>( + applicableModeAndSubtypesMap.values()); + if (!containsKeyboardSubtype) { + InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked( + res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true); + if (lastResortKeyboardSubtype != null) { + applicableSubtypes.add(lastResortKeyboardSubtype); + } + } + return applicableSubtypes; + } + + /** + * If there are no selected subtypes, tries finding the most applicable one according to the + * given locale. + * @param subtypes this function will search the most applicable subtype in subtypes + * @param mode subtypes will be filtered by mode + * @param locale subtypes will be filtered by locale + * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype, + * it will return the first subtype matched with mode + * @return the most applicable subtypeId + */ + private static InputMethodSubtype findLastResortApplicableSubtypeLocked( + Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, + boolean canIgnoreLocaleAsLastResort) { + if (subtypes == null || subtypes.size() == 0) { + return null; + } + if (TextUtils.isEmpty(locale)) { + locale = res.getConfiguration().locale.toString(); + } + final String language = locale.substring(0, 2); + boolean partialMatchFound = false; + InputMethodSubtype applicableSubtype = null; + InputMethodSubtype firstMatchedModeSubtype = null; + final int N = subtypes.size(); + for (int i = 0; i < N; ++i) { + InputMethodSubtype subtype = subtypes.get(i); + final String subtypeLocale = subtype.getLocale(); + // An applicable subtype should match "mode". If mode is null, mode will be ignored, + // and all subtypes with all modes can be candidates. + if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) { + if (firstMatchedModeSubtype == null) { + firstMatchedModeSubtype = subtype; + } + if (locale.equals(subtypeLocale)) { + // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US") + applicableSubtype = subtype; + break; + } else if (!partialMatchFound && subtypeLocale.startsWith(language)) { + // Partial match (e.g. system locale is "en_US" and subtype locale is "en") + applicableSubtype = subtype; + partialMatchFound = true; + } + } + } + + if (applicableSubtype == null && canIgnoreLocaleAsLastResort) { + return firstMatchedModeSubtype; + } + + // The first subtype applicable to the system locale will be defined as the most applicable + // subtype. + if (DEBUG) { + if (applicableSubtype != null) { + Slog.d(TAG, "Applicable InputMethodSubtype was found: " + + applicableSubtype.getMode() + "," + applicableSubtype.getLocale()); + } + } + return applicableSubtype; + } + + // If there are no selected shortcuts, tries finding the most applicable ones. + private Pair<InputMethodInfo, InputMethodSubtype> + findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) { + List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); + InputMethodInfo mostApplicableIMI = null; + InputMethodSubtype mostApplicableSubtype = null; + boolean foundInSystemIME = false; + + // Search applicable subtype for each InputMethodInfo + for (InputMethodInfo imi: imis) { + final String imiId = imi.getId(); + if (foundInSystemIME && !imiId.equals(mCurMethodId)) { + continue; + } + InputMethodSubtype subtype = null; + final List<InputMethodSubtype> enabledSubtypes = + getEnabledInputMethodSubtypeList(imi, true); + // 1. Search by the current subtype's locale from enabledSubtypes. + if (mCurrentSubtype != null) { + subtype = findLastResortApplicableSubtypeLocked( + mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false); + } + // 2. Search by the system locale from enabledSubtypes. + // 3. Search the first enabled subtype matched with mode from enabledSubtypes. + if (subtype == null) { + subtype = findLastResortApplicableSubtypeLocked( + mRes, enabledSubtypes, mode, null, true); + } + // 4. Search by the current subtype's locale from all subtypes. + if (subtype == null && mCurrentSubtype != null) { + subtype = findLastResortApplicableSubtypeLocked( + mRes, getSubtypes(imi), mode, mCurrentSubtype.getLocale(), false); + } + // 5. Search by the system locale from all subtypes. + // 6. Search the first enabled subtype matched with mode from all subtypes. + if (subtype == null) { + subtype = findLastResortApplicableSubtypeLocked( + mRes, getSubtypes(imi), mode, null, true); + } + if (subtype != null) { + if (imiId.equals(mCurMethodId)) { + // The current input method is the most applicable IME. + mostApplicableIMI = imi; + mostApplicableSubtype = subtype; + break; + } else if (!foundInSystemIME) { + // The system input method is 2nd applicable IME. + mostApplicableIMI = imi; + mostApplicableSubtype = subtype; + if ((imi.getServiceInfo().applicationInfo.flags + & ApplicationInfo.FLAG_SYSTEM) != 0) { + foundInSystemIME = true; + } + } + } + } + if (DEBUG) { + if (mostApplicableIMI != null) { + Slog.w(TAG, "Most applicable shortcut input method was:" + + mostApplicableIMI.getId()); + if (mostApplicableSubtype != null) { + Slog.w(TAG, "Most applicable shortcut input method subtype was:" + + "," + mostApplicableSubtype.getMode() + "," + + mostApplicableSubtype.getLocale()); + } + } + } + if (mostApplicableIMI != null) { + return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI, + mostApplicableSubtype); + } else { + return null; + } + } + + /** + * @return Return the current subtype of this input method. + */ + public InputMethodSubtype getCurrentInputMethodSubtype() { + boolean subtypeIsSelected = false; + try { + subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID; + } catch (SettingNotFoundException e) { + } + synchronized (mMethodMap) { + if (!subtypeIsSelected || mCurrentSubtype == null) { + String lastInputMethodId = Settings.Secure.getString( + mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); + int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId); + if (subtypeId == NOT_A_SUBTYPE_ID) { + InputMethodInfo imi = mMethodMap.get(lastInputMethodId); + if (imi != null) { + // If there are no selected subtypes, the framework will try to find + // the most applicable subtype from explicitly or implicitly enabled + // subtypes. + List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes = + getEnabledInputMethodSubtypeList(imi, true); + // If there is only one explicitly or implicitly enabled subtype, + // just returns it. + if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { + mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0); + } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) { + mCurrentSubtype = findLastResortApplicableSubtypeLocked( + mRes, explicitlyOrImplicitlyEnabledSubtypes, + SUBTYPE_MODE_KEYBOARD, null, true); + if (mCurrentSubtype == null) { + mCurrentSubtype = findLastResortApplicableSubtypeLocked( + mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null, + true); + } + } + } + } else { + mCurrentSubtype = + getSubtypes(mMethodMap.get(lastInputMethodId)).get(subtypeId); + } + } + return mCurrentSubtype; + } + } + + private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi, + InputMethodSubtype subtype) { + if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) { + mShortcutInputMethodsAndSubtypes.get(imi).add(subtype); + } else { + ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); + subtypes.add(subtype); + mShortcutInputMethodsAndSubtypes.put(imi, subtypes); + } + } + + // TODO: We should change the return type from List to List<Parcelable> + public List getShortcutInputMethodsAndSubtypes() { + synchronized (mMethodMap) { + ArrayList<Object> ret = new ArrayList<Object>(); + if (mShortcutInputMethodsAndSubtypes.size() == 0) { + // If there are no selected shortcut subtypes, the framework will try to find + // the most applicable subtype from all subtypes whose mode is + // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode. + Pair<InputMethodInfo, InputMethodSubtype> info = + findLastResortApplicableShortcutInputMethodAndSubtypeLocked( + SUBTYPE_MODE_VOICE); + if (info != null) { + ret.add(info.first); + ret.add(info.second); + } + return ret; + } + for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) { + ret.add(imi); + for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) { + ret.add(subtype); + } + } + return ret; + } + } + + public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { + synchronized (mMethodMap) { + if (subtype != null && mCurMethodId != null) { + InputMethodInfo imi = mMethodMap.get(mCurMethodId); + int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode()); + if (subtypeId != NOT_A_SUBTYPE_ID) { + setInputMethodLocked(mCurMethodId, subtypeId); + return true; + } + } + return false; + } + } + + /** + * Utility class for putting and getting settings for InputMethod + * TODO: Move all putters and getters of settings to this class. + */ + private static class InputMethodSettings { + // The string for enabled input method is saved as follows: + // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0") + private static final char INPUT_METHOD_SEPARATER = ':'; + private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';'; + private final TextUtils.SimpleStringSplitter mInputMethodSplitter = + new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER); + + private final TextUtils.SimpleStringSplitter mSubtypeSplitter = + new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER); + + private final Resources mRes; + private final ContentResolver mResolver; + private final HashMap<String, InputMethodInfo> mMethodMap; + private final ArrayList<InputMethodInfo> mMethodList; + + private String mEnabledInputMethodsStrCache; + + private static void buildEnabledInputMethodsSettingString( + StringBuilder builder, Pair<String, ArrayList<String>> pair) { + String id = pair.first; + ArrayList<String> subtypes = pair.second; + builder.append(id); + // Inputmethod and subtypes are saved in the settings as follows: + // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1 + for (String subtypeId: subtypes) { + builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId); + } + } + + public InputMethodSettings( + Resources res, ContentResolver resolver, + HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) { + mRes = res; + mResolver = resolver; + mMethodMap = methodMap; + mMethodList = methodList; + } + + public List<InputMethodInfo> getEnabledInputMethodListLocked() { + return createEnabledInputMethodListLocked( + getEnabledInputMethodsAndSubtypeListLocked()); + } + + public List<Pair<InputMethodInfo, ArrayList<String>>> + getEnabledInputMethodAndSubtypeHashCodeListLocked() { + return createEnabledInputMethodAndSubtypeHashCodeListLocked( + getEnabledInputMethodsAndSubtypeListLocked()); + } + + public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( + InputMethodInfo imi) { + List<Pair<String, ArrayList<String>>> imsList = + getEnabledInputMethodsAndSubtypeListLocked(); + ArrayList<InputMethodSubtype> enabledSubtypes = + new ArrayList<InputMethodSubtype>(); + if (imi != null) { + for (Pair<String, ArrayList<String>> imsPair : imsList) { + InputMethodInfo info = mMethodMap.get(imsPair.first); + if (info != null && info.getId().equals(imi.getId())) { + final int subtypeCount = info.getSubtypeCount(); + for (int i = 0; i < subtypeCount; ++i) { + InputMethodSubtype ims = info.getSubtypeAt(i); + for (String s: imsPair.second) { + if (String.valueOf(ims.hashCode()).equals(s)) { + enabledSubtypes.add(ims); + } + } + } + break; + } + } + } + return enabledSubtypes; + } + + // At the initial boot, the settings for input methods are not set, + // so we need to enable IME in that case. + public void enableAllIMEsIfThereIsNoEnabledIME() { + if (TextUtils.isEmpty(getEnabledInputMethodsStr())) { + StringBuilder sb = new StringBuilder(); + final int N = mMethodList.size(); + for (int i = 0; i < N; i++) { + InputMethodInfo imi = mMethodList.get(i); + Slog.i(TAG, "Adding: " + imi.getId()); + if (i > 0) sb.append(':'); + sb.append(imi.getId()); + } + putEnabledInputMethodsStr(sb.toString()); + } + } + + private List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { + ArrayList<Pair<String, ArrayList<String>>> imsList + = new ArrayList<Pair<String, ArrayList<String>>>(); + final String enabledInputMethodsStr = getEnabledInputMethodsStr(); + if (TextUtils.isEmpty(enabledInputMethodsStr)) { + return imsList; + } + mInputMethodSplitter.setString(enabledInputMethodsStr); + while (mInputMethodSplitter.hasNext()) { + String nextImsStr = mInputMethodSplitter.next(); + mSubtypeSplitter.setString(nextImsStr); + if (mSubtypeSplitter.hasNext()) { + ArrayList<String> subtypeHashes = new ArrayList<String>(); + // The first element is ime id. + String imeId = mSubtypeSplitter.next(); + while (mSubtypeSplitter.hasNext()) { + subtypeHashes.add(mSubtypeSplitter.next()); } + imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes)); + } + } + return imsList; + } + + public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { + if (reloadInputMethodStr) { + getEnabledInputMethodsStr(); + } + if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) { + // Add in the newly enabled input method. + putEnabledInputMethodsStr(id); + } else { + putEnabledInputMethodsStr( + mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id); + } + } + + /** + * Build and put a string of EnabledInputMethods with removing specified Id. + * @return the specified id was removed or not. + */ + public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( + StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) { + boolean isRemoved = false; + boolean needsAppendSeparator = false; + for (Pair<String, ArrayList<String>> ims: imsList) { + String curId = ims.first; + if (curId.equals(id)) { // We are disabling this input method, and it is // currently enabled. Skip it to remove from the // new list. - removed = true; - } else if (!enabled) { - // We are building a new list of input methods that - // doesn't contain the given one. - if (firstId == null) firstId = curId; - if (builder.length() > 0) builder.append(':'); - builder.append(curId); + isRemoved = true; + } else { + if (needsAppendSeparator) { + builder.append(INPUT_METHOD_SEPARATER); + } else { + needsAppendSeparator = true; + } + buildEnabledInputMethodsSettingString(builder, ims); } } + if (isRemoved) { + // Update the setting with the new list of input methods. + putEnabledInputMethodsStr(builder.toString()); + } + return isRemoved; } - if (!enabled) { - if (!removed) { - // We are disabling the input method but it is already - // disabled. Nothing to do. The previous state was - // disabled. - return false; + private List<InputMethodInfo> createEnabledInputMethodListLocked( + List<Pair<String, ArrayList<String>>> imsList) { + final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>(); + for (Pair<String, ArrayList<String>> ims: imsList) { + InputMethodInfo info = mMethodMap.get(ims.first); + if (info != null) { + res.add(info); + } } - // Update the setting with the new list of input methods. - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS, builder.toString()); - // We the disabled input method is currently selected, switch - // to another one. - String selId = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD); - if (id.equals(selId)) { - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, - firstId != null ? firstId : ""); + return res; + } + + private List<Pair<InputMethodInfo, ArrayList<String>>> + createEnabledInputMethodAndSubtypeHashCodeListLocked( + List<Pair<String, ArrayList<String>>> imsList) { + final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res + = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>(); + for (Pair<String, ArrayList<String>> ims : imsList) { + InputMethodInfo info = mMethodMap.get(ims.first); + if (info != null) { + res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second)); + } } - // Previous state was enabled. - return true; + return res; } - // Add in the newly enabled input method. - if (enabledStr == null || enabledStr.length() == 0) { - enabledStr = id; - } else { - enabledStr = enabledStr + ':' + id; + private void putEnabledInputMethodsStr(String str) { + Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str); + mEnabledInputMethodsStrCache = str; } - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS, enabledStr); + private String getEnabledInputMethodsStr() { + mEnabledInputMethodsStrCache = Settings.Secure.getString( + mResolver, Settings.Secure.ENABLED_INPUT_METHODS); + if (DEBUG) { + Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache); + } + return mEnabledInputMethodsStrCache; + } - // Previous state was disabled. - return false; + private void saveSubtypeHistory( + List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) { + StringBuilder builder = new StringBuilder(); + boolean isImeAdded = false; + if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) { + builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( + newSubtypeId); + isImeAdded = true; + } + for (Pair<String, String> ime: savedImes) { + String imeId = ime.first; + String subtypeId = ime.second; + if (TextUtils.isEmpty(subtypeId)) { + subtypeId = NOT_A_SUBTYPE_ID_STR; + } + if (isImeAdded) { + builder.append(INPUT_METHOD_SEPARATER); + } else { + isImeAdded = true; + } + builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( + subtypeId); + } + // Remove the last INPUT_METHOD_SEPARATER + putSubtypeHistoryStr(builder.toString()); + } + + public void addSubtypeToHistory(String imeId, String subtypeId) { + List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); + for (Pair<String, String> ime: subtypeHistory) { + if (ime.first.equals(imeId)) { + if (DEBUG) { + Slog.v(TAG, "Subtype found in the history: " + imeId + ", " + + ime.second); + } + // We should break here + subtypeHistory.remove(ime); + break; + } + } + if (DEBUG) { + Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId); + } + saveSubtypeHistory(subtypeHistory, imeId, subtypeId); + } + + private void putSubtypeHistoryStr(String str) { + if (DEBUG) { + Slog.d(TAG, "putSubtypeHistoryStr: " + str); + } + Settings.Secure.putString( + mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str); + } + + public Pair<String, String> getLastInputMethodAndSubtypeLocked() { + // Gets the first one from the history + return getLastSubtypeForInputMethodLockedInternal(null); + } + + public String getLastSubtypeForInputMethodLocked(String imeId) { + Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId); + if (ime != null) { + return ime.second; + } else { + return null; + } + } + + private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) { + List<Pair<String, ArrayList<String>>> enabledImes = + getEnabledInputMethodsAndSubtypeListLocked(); + List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); + for (Pair<String, String> imeAndSubtype: subtypeHistory) { + final String imeInTheHistory = imeAndSubtype.first; + // If imeId is empty, returns the first IME and subtype in the history + if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) { + final String subtypeInTheHistory = imeAndSubtype.second; + final String subtypeHashCode = + getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked( + enabledImes, imeInTheHistory, subtypeInTheHistory); + if (!TextUtils.isEmpty(subtypeHashCode)) { + if (DEBUG) { + Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode); + } + return new Pair<String, String>(imeInTheHistory, subtypeHashCode); + } + } + } + if (DEBUG) { + Slog.d(TAG, "No enabled IME found in the history"); + } + return null; + } + + private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String, + ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) { + for (Pair<String, ArrayList<String>> enabledIme: enabledImes) { + if (enabledIme.first.equals(imeId)) { + final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second; + if (explicitlyEnabledSubtypes.size() == 0) { + // If there are no explicitly enabled subtypes, applicable subtypes are + // enabled implicitly. + InputMethodInfo ime = mMethodMap.get(imeId); + // If IME is enabled and no subtypes are enabled, applicable subtypes + // are enabled implicitly, so needs to treat them to be enabled. + if (ime != null && ime.getSubtypeCount() > 0) { + List<InputMethodSubtype> implicitlySelectedSubtypes = + getApplicableSubtypesLocked(mRes, getSubtypes(ime)); + if (implicitlySelectedSubtypes != null) { + final int N = implicitlySelectedSubtypes.size(); + for (int i = 0; i < N; ++i) { + final InputMethodSubtype st = implicitlySelectedSubtypes.get(i); + if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) { + return subtypeHashCode; + } + } + } + } + } else { + for (String s: explicitlyEnabledSubtypes) { + if (s.equals(subtypeHashCode)) { + // If both imeId and subtypeId are enabled, return subtypeId. + return s; + } + } + } + // If imeId was enabled but subtypeId was disabled. + return NOT_A_SUBTYPE_ID_STR; + } + } + // If both imeId and subtypeId are disabled, return null + return null; + } + + private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() { + ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>(); + final String subtypeHistoryStr = getSubtypeHistoryStr(); + if (TextUtils.isEmpty(subtypeHistoryStr)) { + return imsList; + } + mInputMethodSplitter.setString(subtypeHistoryStr); + while (mInputMethodSplitter.hasNext()) { + String nextImsStr = mInputMethodSplitter.next(); + mSubtypeSplitter.setString(nextImsStr); + if (mSubtypeSplitter.hasNext()) { + String subtypeId = NOT_A_SUBTYPE_ID_STR; + // The first element is ime id. + String imeId = mSubtypeSplitter.next(); + while (mSubtypeSplitter.hasNext()) { + subtypeId = mSubtypeSplitter.next(); + break; + } + imsList.add(new Pair<String, String>(imeId, subtypeId)); + } + } + return imsList; + } + + private String getSubtypeHistoryStr() { + if (DEBUG) { + Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString( + mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY)); + } + return Settings.Secure.getString( + mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY); + } + + public void putSelectedInputMethod(String imeId) { + Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId); + } + + public void putSelectedSubtype(int subtypeId) { + Settings.Secure.putInt( + mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId); + } } // ---------------------------------------------------------------------- diff --git a/services/java/com/android/server/InputWindow.java b/services/java/com/android/server/InputWindow.java index befc770..2c2cdfe 100644 --- a/services/java/com/android/server/InputWindow.java +++ b/services/java/com/android/server/InputWindow.java @@ -16,68 +16,63 @@ package com.android.server; +import android.graphics.Region; import android.view.InputChannel; /** * Describes input-related window properties for use by the input dispatcher. - * * @hide */ public final class InputWindow { + // The window handle. + public InputWindowHandle inputWindowHandle; + // The input channel associated with the window. public InputChannel inputChannel; - + // The window name. public String name; - + // Window layout params attributes. (WindowManager.LayoutParams) public int layoutParamsFlags; public int layoutParamsType; - + // Dispatching timeout. public long dispatchingTimeoutNanos; - - // Window frame area. + + // Window frame. public int frameLeft; public int frameTop; public int frameRight; public int frameBottom; - - // Window visible frame area. - public int visibleFrameLeft; - public int visibleFrameTop; - public int visibleFrameRight; - public int visibleFrameBottom; - - // Window touchable area. - public int touchableAreaLeft; - public int touchableAreaTop; - public int touchableAreaRight; - public int touchableAreaBottom; - + + // Window touchable region. + public final Region touchableRegion = new Region(); + // Window is visible. public boolean visible; - + // Window can receive keys. public boolean canReceiveKeys; - + // Window has focus. public boolean hasFocus; - + // Window has wallpaper. (window is the current wallpaper target) public boolean hasWallpaper; - + // Input event dispatching is paused. public boolean paused; - + // Window layer. public int layer; - + // Id of process and user that owns the window. public int ownerPid; public int ownerUid; - + public void recycle() { + inputWindowHandle = null; inputChannel = null; } } diff --git a/services/java/com/android/server/InputWindowHandle.java b/services/java/com/android/server/InputWindowHandle.java new file mode 100644 index 0000000..4b92939 --- /dev/null +++ b/services/java/com/android/server/InputWindowHandle.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 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.view.WindowManagerPolicy; + +/** + * Functions as a handle for a window that can receive input. + * Enables the native input dispatcher to refer indirectly to the window manager's window state. + * @hide + */ +public final class InputWindowHandle { + // Pointer to the native input window handle. + // This field is lazily initialized via JNI. + @SuppressWarnings("unused") + private int ptr; + + // The input application handle. + public final InputApplicationHandle inputApplicationHandle; + + // The window manager's window state. + public final WindowManagerPolicy.WindowState windowState; + + private native void nativeDispose(); + + public InputWindowHandle(InputApplicationHandle inputApplicationHandle, + WindowManagerPolicy.WindowState windowState) { + this.inputApplicationHandle = inputApplicationHandle; + this.windowState = windowState; + } + + @Override + protected void finalize() throws Throwable { + nativeDispose(); + super.finalize(); + } +} diff --git a/services/java/com/android/server/LightsService.java b/services/java/com/android/server/LightsService.java index 9b57735..21f2bcf 100644 --- a/services/java/com/android/server/LightsService.java +++ b/services/java/com/android/server/LightsService.java @@ -24,12 +24,12 @@ import android.os.ServiceManager; import android.os.Message; import android.util.Slog; -import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; public class LightsService { private static final String TAG = "LightsService"; + private static final boolean DEBUG = false; static final int LIGHT_ID_BACKLIGHT = 0; static final int LIGHT_ID_KEYBOARD = 1; @@ -115,6 +115,8 @@ public class LightsService { private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) { if (color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS) { + if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#" + + Integer.toHexString(color)); mColor = color; mMode = mode; mOnMS = onMS; diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 10107c6..656ec4d 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -479,14 +479,17 @@ public class LocationManagerService extends ILocationManager.Stub implements Run mEnabledProviders.add(passiveProvider.getName()); // initialize external network location and geocoder services - if (mNetworkLocationProviderPackageName != null) { + PackageManager pm = mContext.getPackageManager(); + if (mNetworkLocationProviderPackageName != null && + pm.resolveService(new Intent(mNetworkLocationProviderPackageName), 0) != null) { mNetworkLocationProvider = new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER, mNetworkLocationProviderPackageName, mLocationHandler); addProvider(mNetworkLocationProvider); } - if (mGeocodeProviderPackageName != null) { + if (mGeocodeProviderPackageName != null && + pm.resolveService(new Intent(mGeocodeProviderPackageName), 0) != null) { mGeocodeProvider = new GeocoderProxy(mContext, mGeocodeProviderPackageName); } diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index a191549..3101222 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -19,6 +19,7 @@ package com.android.server; import com.android.internal.app.IMediaContainerService; import com.android.server.am.ActivityManagerService; +import android.Manifest; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -73,8 +74,8 @@ import javax.crypto.spec.PBEKeySpec; * @hide - Applications should use android.os.storage.StorageManager * to access the MountService. */ -class MountService extends IMountService.Stub - implements INativeDaemonConnectorCallbacks { +class MountService extends IMountService.Stub implements INativeDaemonConnectorCallbacks { + private static final boolean LOCAL_LOGD = false; private static final boolean DEBUG_UNMOUNT = false; private static final boolean DEBUG_EVENTS = false; @@ -151,6 +152,8 @@ class MountService extends IMountService.Stub private boolean mBooted = false; private boolean mReady = false; private boolean mSendUmsConnectedOnBoot = false; + // true if we should fake MEDIA_MOUNTED state for external storage + private boolean mEmulateExternalStorage = false; /** * Private hash of currently mounted secure containers. @@ -332,6 +335,7 @@ class MountService extends IMountService.Stub super(l); } + @Override public void handleMessage(Message msg) { switch (msg.what) { case H_UNMOUNT_PM_UPDATE: { @@ -425,6 +429,7 @@ class MountService extends IMountService.Stub } private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -440,12 +445,15 @@ class MountService extends IMountService.Stub return; } new Thread() { + @Override public void run() { try { String path = Environment.getExternalStorageDirectory().getPath(); String state = getVolumeState(path); - if (state.equals(Environment.MEDIA_UNMOUNTED)) { + if (mEmulateExternalStorage) { + notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Mounted); + } else if (state.equals(Environment.MEDIA_UNMOUNTED)) { int rc = doMountVolume(path); if (rc != StorageResultCode.OperationSucceeded) { Slog.e(TAG, String.format("Boot-time mount failed (%d)", rc)); @@ -516,21 +524,21 @@ class MountService extends IMountService.Stub Slog.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state)); return; } + // Update state on PackageManager, but only of real events + if (!mEmulateExternalStorage) { + if (Environment.MEDIA_UNMOUNTED.equals(state)) { + mPms.updateExternalMediaStatus(false, false); - if (Environment.MEDIA_UNMOUNTED.equals(state)) { - // Tell the package manager the media is gone. - mPms.updateExternalMediaStatus(false, false); - - /* - * Some OBBs might have been unmounted when this volume was - * unmounted, so send a message to the handler to let it know to - * remove those from the list of mounted OBBS. - */ - mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_FLUSH_MOUNT_STATE, - path)); - } else if (Environment.MEDIA_MOUNTED.equals(state)) { - // Tell the package manager the media is available for use. - mPms.updateExternalMediaStatus(true, false); + /* + * Some OBBs might have been unmounted when this volume was + * unmounted, so send a message to the handler to let it know to + * remove those from the list of mounted OBBS. + */ + mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_FLUSH_MOUNT_STATE, + path)); + } else if (Environment.MEDIA_MOUNTED.equals(state)) { + mPms.updateExternalMediaStatus(true, false); + } } String oldState = mLegacyState; @@ -561,6 +569,7 @@ class MountService extends IMountService.Stub * we need to do our work in a new thread. */ new Thread() { + @Override public void run() { /** * Determine media state and UMS detection status @@ -610,7 +619,7 @@ class MountService extends IMountService.Stub Slog.w(TAG, "Failed to get share availability"); } /* - * Now that we've done our initialization, release + * Now that we've done our initialization, release * the hounds! */ mReady = true; @@ -674,6 +683,7 @@ class MountService extends IMountService.Stub if (code == VoldResponseCode.VolumeDiskInserted) { new Thread() { + @Override public void run() { try { int rc; @@ -1003,6 +1013,7 @@ class MountService extends IMountService.Stub * USB mass storage disconnected while enabled */ new Thread() { + @Override public void run() { try { int rc; @@ -1040,6 +1051,13 @@ class MountService extends IMountService.Stub public MountService(Context context) { mContext = context; + mEmulateExternalStorage = context.getResources().getBoolean( + com.android.internal.R.bool.config_emulateExternalStorage); + if (mEmulateExternalStorage) { + Slog.d(TAG, "using emulated external storage"); + mLegacyState = Environment.MEDIA_MOUNTED; + } + // XXX: This will go away soon in favor of IMountServiceObserver mPms = (PackageManagerService) ServiceManager.getService("package"); @@ -1144,6 +1162,17 @@ class MountService extends IMountService.Stub // Post a unmount message. ShutdownCallBack ucb = new ShutdownCallBack(path, observer); mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb)); + } else if (observer != null) { + /* + * Observer is waiting for onShutDownComplete when we are done. + * Since nothing will be done send notification directly so shutdown + * sequence can continue. + */ + try { + observer.onShutDownComplete(StorageResultCode.OperationSucceeded); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException when shutting down"); + } } } @@ -1209,7 +1238,7 @@ class MountService extends IMountService.Stub waitForReady(); return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums"); } - + /** * @return state of the volume at the specified mount point */ @@ -1226,6 +1255,10 @@ class MountService extends IMountService.Stub return mLegacyState; } + public boolean isExternalStorageEmulated() { + return mEmulateExternalStorage; + } + public int mountVolume(String path) { validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); @@ -1375,7 +1408,7 @@ class MountService extends IMountService.Stub return rc; } - + public int mountSecureContainer(String id, String key, int ownerUid) { validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); waitForReady(); @@ -1463,7 +1496,7 @@ class MountService extends IMountService.Stub synchronized (mAsecMountSet) { /* - * Because a mounted container has active internal state which cannot be + * Because a mounted container has active internal state which cannot be * changed while active, we must ensure both ids are not currently mounted. */ if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) { @@ -1598,6 +1631,71 @@ class MountService extends IMountService.Stub Slog.i(TAG, "Send to OBB handler: " + action.toString()); } + public int decryptStorage(String password) { + if (password == null) { + throw new IllegalArgumentException("password cannot be null"); + } + + mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, + "no permission to access the crypt keeper"); + + waitForReady(); + + if (DEBUG_EVENTS) { + Slog.i(TAG, "decrypting storage..."); + } + + try { + ArrayList<String> rsp = mConnector.doCommand("cryptfs checkpw " + password); + String []tok = rsp.get(0).split(" "); + + if (tok == null || tok.length != 2) { + return -1; + } + + int code = Integer.parseInt(tok[1]); + + if (code == 0) { + // Decrypt was successful. Post a delayed message before restarting in order + // to let the UI to clear itself + mHandler.postDelayed(new Runnable() { + public void run() { + mConnector.doCommand(String.format("cryptfs restart")); + } + }, 2000); // 2 seconds + } + + return code; + } catch (NativeDaemonConnectorException e) { + // Decryption failed + return e.getCode(); + } + } + + public int encryptStorage(String password) { + if (password == null) { + throw new IllegalArgumentException("password cannot be null"); + } + + mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, + "no permission to access the crypt keeper"); + + waitForReady(); + + if (DEBUG_EVENTS) { + Slog.i(TAG, "encrypting storage..."); + } + + try { + mConnector.doCommand(String.format("cryptfs enablecrypto inplace %s", password)); + } catch (NativeDaemonConnectorException e) { + // Encryption failed + return e.getCode(); + } + + return 0; + } + private void addObbStateLocked(ObbState obbState) throws RemoteException { final IBinder binder = obbState.getBinder(); List<ObbState> obbStates = mObbMounts.get(binder); @@ -1885,6 +1983,7 @@ class MountService extends IMountService.Stub mKey = key; } + @Override public void handleExecute() throws IOException, RemoteException { waitForReady(); warnOnNotMounted(); @@ -1965,6 +2064,7 @@ class MountService extends IMountService.Stub } } + @Override public void handleError() { sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); } @@ -1994,6 +2094,7 @@ class MountService extends IMountService.Stub mForceUnmount = force; } + @Override public void handleExecute() throws IOException { waitForReady(); warnOnNotMounted(); @@ -2048,6 +2149,7 @@ class MountService extends IMountService.Stub } } + @Override public void handleError() { sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); } diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index f0acdc0..152605f 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -219,28 +219,6 @@ class NetworkManagementService extends INetworkManagementService.Stub { } } - private static int stringToIpAddr(String addrString) throws UnknownHostException { - try { - String[] parts = addrString.split("\\."); - if (parts.length != 4) { - throw new UnknownHostException(addrString); - } - - int a = Integer.parseInt(parts[0]) ; - int b = Integer.parseInt(parts[1]) << 8; - int c = Integer.parseInt(parts[2]) << 16; - int d = Integer.parseInt(parts[3]) << 24; - - return a | b | c | d; - } catch (NumberFormatException ex) { - throw new UnknownHostException(addrString); - } - } - - public static String intToIpString(int i) { - return ((i >> 24 ) & 0xFF) + "." + ((i >> 16 ) & 0xFF) + "." + ((i >> 8 ) & 0xFF) + "." + - (i & 0xFF); - } // // INetworkManagementService members @@ -288,18 +266,17 @@ class NetworkManagementService extends INetworkManagementService.Stub { cfg = new InterfaceConfiguration(); cfg.hwAddr = st.nextToken(" "); try { - cfg.ipAddr = stringToIpAddr(st.nextToken(" ")); + cfg.addr = InetAddress.getByName(st.nextToken(" ")); } catch (UnknownHostException uhe) { Slog.e(TAG, "Failed to parse ipaddr", uhe); - cfg.ipAddr = 0; } try { - cfg.netmask = stringToIpAddr(st.nextToken(" ")); + cfg.mask = InetAddress.getByName(st.nextToken(" ")); } catch (UnknownHostException uhe) { Slog.e(TAG, "Failed to parse netmask", uhe); - cfg.netmask = 0; } + cfg.interfaceFlags = st.nextToken("]").trim() +"]"; } catch (NoSuchElementException nsee) { throw new IllegalStateException( @@ -312,12 +289,13 @@ class NetworkManagementService extends INetworkManagementService.Stub { public void setInterfaceConfig( String iface, InterfaceConfiguration cfg) throws IllegalStateException { String cmd = String.format("interface setcfg %s %s %s %s", iface, - intToIpString(cfg.ipAddr), intToIpString(cfg.netmask), cfg.interfaceFlags); + cfg.addr.getHostAddress(), cfg.mask.getHostAddress(), + cfg.interfaceFlags); try { mConnector.doCommand(cmd); } catch (NativeDaemonConnectorException e) { throw new IllegalStateException( - "Unable to communicate with native daemon to interface setcfg"); + "Unable to communicate with native daemon to interface setcfg - " + e); } } diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java new file mode 100644 index 0000000..52f84eb --- /dev/null +++ b/services/java/com/android/server/NetworkTimeUpdateService.java @@ -0,0 +1,297 @@ +/* + * 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; + +import com.android.internal.telephony.TelephonyIntents; + +import android.app.AlarmManager; +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.database.ContentObserver; +import android.net.SntpClient; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; +import android.provider.Settings; +import android.util.Log; +import android.util.Slog; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +/** + * Monitors the network time and updates the system time if it is out of sync + * and there hasn't been any NITZ update from the carrier recently. + * If looking up the network time fails for some reason, it tries a few times with a short + * interval and then resets to checking on longer intervals. + * <p> + * If the user enables AUTO_TIME, it will check immediately for the network time, if NITZ wasn't + * available. + * </p> + */ +public class NetworkTimeUpdateService { + + private static final String TAG = "NetworkTimeUpdateService"; + private static final boolean DBG = false; + + private static final int EVENT_AUTO_TIME_CHANGED = 1; + private static final int EVENT_POLL_NETWORK_TIME = 2; + + /** Normal polling frequency */ + private static final long POLLING_INTERVAL_MS = 24L * 60 * 60 * 1000; // 24 hrs + /** Try-again polling interval, in case the network request failed */ + private static final long POLLING_INTERVAL_SHORTER_MS = 60 * 1000L; // 60 seconds + /** Number of times to try again */ + private static final int TRY_AGAIN_TIMES_MAX = 3; + /** How long to wait for the NTP server to respond. */ + private static final int MAX_NTP_FETCH_WAIT_MS = 20 * 1000; + /** If the time difference is greater than this threshold, then update the time. */ + private static final int TIME_ERROR_THRESHOLD_MS = 5 * 1000; + + private static final String ACTION_POLL = + "com.android.server.NetworkTimeUpdateService.action.POLL"; + private static final String PROPERTIES_FILE = "/etc/gps.conf"; + private static int POLL_REQUEST = 0; + + private static final long NOT_SET = -1; + private long mNitzTimeSetTime = NOT_SET; + // TODO: Have a way to look up the timezone we are in + private long mNitzZoneSetTime = NOT_SET; + + private Context mContext; + // NTP lookup is done on this thread and handler + private Handler mHandler; + private HandlerThread mThread; + private AlarmManager mAlarmManager; + private PendingIntent mPendingPollIntent; + private SettingsObserver mSettingsObserver; + // Address of the NTP server + private String mNtpServer; + // The last time that we successfully fetched the NTP time. + private long mLastNtpFetchTime = NOT_SET; + // Keeps track of how many quick attempts were made to fetch NTP time. + // During bootup, the network may not have been up yet, or it's taking time for the + // connection to happen. + private int mTryAgainCounter; + + public NetworkTimeUpdateService(Context context) { + mContext = context; + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + Intent pollIntent = new Intent(ACTION_POLL, null); + mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0); + } + + /** Initialize the receivers and initiate the first NTP request */ + public void systemReady() { + mNtpServer = getNtpServerAddress(); + if (mNtpServer == null) { + Slog.e(TAG, "NTP server address not found, not syncing to NTP time"); + return; + } + + registerForTelephonyIntents(); + registerForAlarms(); + + mThread = new HandlerThread(TAG); + mThread.start(); + mHandler = new MyHandler(mThread.getLooper()); + // Check the network time on the new thread + mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget(); + + mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED); + mSettingsObserver.observe(mContext); + } + + private String getNtpServerAddress() { + String serverAddress = null; + FileInputStream stream = null; + try { + Properties properties = new Properties(); + File file = new File(PROPERTIES_FILE); + stream = new FileInputStream(file); + properties.load(stream); + serverAddress = properties.getProperty("NTP_SERVER", null); + } catch (IOException e) { + Slog.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (Exception e) {} + } + } + return serverAddress; + } + + private void registerForTelephonyIntents() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME); + intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE); + mContext.registerReceiver(mNitzReceiver, intentFilter); + } + + private void registerForAlarms() { + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget(); + } + }, new IntentFilter(ACTION_POLL)); + } + + private void onPollNetworkTime(int event) { + // If Automatic time is not set, don't bother. + if (!isAutomaticTimeRequested()) return; + + final long refTime = SystemClock.elapsedRealtime(); + // If NITZ time was received less than POLLING_INTERVAL_MS time ago, + // no need to sync to NTP. + if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < POLLING_INTERVAL_MS) { + resetAlarm(POLLING_INTERVAL_MS); + return; + } + final long currentTime = System.currentTimeMillis(); + if (DBG) Log.d(TAG, "System time = " + currentTime); + // Get the NTP time + if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + POLLING_INTERVAL_MS + || event == EVENT_AUTO_TIME_CHANGED) { + if (DBG) Log.d(TAG, "Before Ntp fetch"); + long ntp = getNtpTime(); + if (DBG) Log.d(TAG, "Ntp = " + ntp); + if (ntp > 0) { + mTryAgainCounter = 0; + mLastNtpFetchTime = SystemClock.elapsedRealtime(); + if (Math.abs(ntp - currentTime) > TIME_ERROR_THRESHOLD_MS) { + // Set the system time + if (DBG) Log.d(TAG, "Ntp time to be set = " + ntp); + // Make sure we don't overflow, since it's going to be converted to an int + if (ntp / 1000 < Integer.MAX_VALUE) { + SystemClock.setCurrentTimeMillis(ntp); + } + } else { + if (DBG) Log.d(TAG, "Ntp time is close enough = " + ntp); + } + } else { + // Try again shortly + mTryAgainCounter++; + if (mTryAgainCounter <= TRY_AGAIN_TIMES_MAX) { + resetAlarm(POLLING_INTERVAL_SHORTER_MS); + } else { + // Try much later + mTryAgainCounter = 0; + resetAlarm(POLLING_INTERVAL_MS); + } + return; + } + } + resetAlarm(POLLING_INTERVAL_MS); + } + + /** + * Cancel old alarm and starts a new one for the specified interval. + * + * @param interval when to trigger the alarm, starting from now. + */ + private void resetAlarm(long interval) { + mAlarmManager.cancel(mPendingPollIntent); + long now = SystemClock.elapsedRealtime(); + long next = now + interval; + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent); + } + + private long getNtpTime() { + SntpClient client = new SntpClient(); + if (client.requestTime(mNtpServer, MAX_NTP_FETCH_WAIT_MS)) { + return client.getNtpTime(); + } else { + return 0; + } + } + + /** + * Checks if the user prefers to automatically set the time. + */ + private boolean isAutomaticTimeRequested() { + return Settings.System.getInt(mContext.getContentResolver(), Settings.System.AUTO_TIME, 0) + != 0; + } + + /** Receiver for Nitz time events */ + private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) { + mNitzTimeSetTime = SystemClock.elapsedRealtime(); + } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) { + mNitzZoneSetTime = SystemClock.elapsedRealtime(); + } + } + }; + + /** Handler to do the network accesses on */ + private class MyHandler extends Handler { + + public MyHandler(Looper l) { + super(l); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_AUTO_TIME_CHANGED: + case EVENT_POLL_NETWORK_TIME: + onPollNetworkTime(msg.what); + break; + } + } + } + + /** Observer to watch for changes to the AUTO_TIME setting */ + private static class SettingsObserver extends ContentObserver { + + private int mMsg; + private Handler mHandler; + + SettingsObserver(Handler handler, int msg) { + super(handler); + mHandler = handler; + mMsg = msg; + } + + void observe(Context context) { + ContentResolver resolver = context.getContentResolver(); + resolver.registerContentObserver(Settings.System.getUriFor(Settings.System.AUTO_TIME), + false, this); + } + + @Override + public void onChange(boolean selfChange) { + mHandler.obtainMessage(mMsg).sendToTarget(); + } + } +} diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index e8c1613..0490190 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -17,7 +17,6 @@ package com.android.server; import com.android.internal.statusbar.StatusBarNotification; -import com.android.server.StatusBarManagerService; import android.app.ActivityManagerNative; import android.app.IActivityManager; @@ -38,16 +37,14 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.database.ContentObserver; -import android.hardware.Usb; +import android.hardware.UsbManager; import android.media.AudioManager; import android.net.Uri; -import android.os.BatteryManager; -import android.os.Bundle; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; -import android.os.Power; import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; @@ -56,8 +53,8 @@ import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.EventLog; -import android.util.Slog; import android.util.Log; +import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; @@ -91,8 +88,6 @@ public class NotificationManagerService extends INotificationManager.Stub private WorkerHandler mHandler; private StatusBarManagerService mStatusBar; - private LightsService mLightsService; - private LightsService.Light mBatteryLight; private LightsService.Light mNotificationLight; private LightsService.Light mAttentionLight; @@ -127,18 +122,8 @@ public class NotificationManagerService extends INotificationManager.Stub private ArrayList<ToastRecord> mToastQueue; private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>(); - - private boolean mBatteryCharging; - private boolean mBatteryLow; - private boolean mBatteryFull; private NotificationRecord mLedNotification; - private static final int BATTERY_LOW_ARGB = 0xFFFF0000; // Charging Low - red solid on - private static final int BATTERY_MEDIUM_ARGB = 0xFFFFFF00; // Charging - orange solid on - private static final int BATTERY_FULL_ARGB = 0xFF00FF00; // Charging Full - green solid on - private static final int BATTERY_BLINK_ON = 125; - private static final int BATTERY_BLINK_OFF = 2875; - private static String idDebugString(Context baseContext, String packageName, int id) { Context c = null; @@ -171,8 +156,6 @@ public class NotificationManagerService extends INotificationManager.Stub final int id; final int uid; final int initialPid; - ITransientNotification callback; - int duration; final Notification notification; IBinder statusBarKey; @@ -282,7 +265,13 @@ public class NotificationManagerService extends INotificationManager.Stub public void onNotificationClick(String pkg, String tag, int id) { cancelNotification(pkg, tag, id, Notification.FLAG_AUTO_CANCEL, - Notification.FLAG_FOREGROUND_SERVICE); + Notification.FLAG_FOREGROUND_SERVICE, true); + } + + public void onNotificationClear(String pkg, String tag, int id) { + cancelNotification(pkg, tag, id, 0, + Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE, + true); } public void onPanelRevealed() { @@ -318,7 +307,7 @@ public class NotificationManagerService extends INotificationManager.Stub int uid, int initialPid, String message) { Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id + "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")"); - cancelNotification(pkg, tag, id, 0, 0); + cancelNotification(pkg, tag, id, 0, 0, false); long ident = Binder.clearCallingIdentity(); try { ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg, @@ -337,29 +326,12 @@ public class NotificationManagerService extends INotificationManager.Stub boolean queryRestart = false; - if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { - boolean batteryCharging = (intent.getIntExtra("plugged", 0) != 0); - int level = intent.getIntExtra("level", -1); - boolean batteryLow = (level >= 0 && level <= Power.LOW_BATTERY_THRESHOLD); - int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN); - boolean batteryFull = (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90); - - if (batteryCharging != mBatteryCharging || - batteryLow != mBatteryLow || - batteryFull != mBatteryFull) { - mBatteryCharging = batteryCharging; - mBatteryLow = batteryLow; - mBatteryFull = batteryFull; - updateLights(); - } - } else if (action.equals(Usb.ACTION_USB_STATE)) { + if (action.equals(UsbManager.ACTION_USB_STATE)) { Bundle extras = intent.getExtras(); - boolean usbConnected = extras.getBoolean(Usb.USB_CONNECTED); - boolean adbEnabled = (Usb.USB_FUNCTION_ENABLED.equals( - extras.getString(Usb.USB_FUNCTION_ADB))); + boolean usbConnected = extras.getBoolean(UsbManager.USB_CONNECTED); + boolean adbEnabled = (UsbManager.USB_FUNCTION_ENABLED.equals( + extras.getString(UsbManager.USB_FUNCTION_ADB))); updateAdbNotification(usbConnected && adbEnabled); - } else if (action.equals(Usb.ACTION_USB_DISCONNECTED)) { - updateAdbNotification(false); } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED) || action.equals(Intent.ACTION_PACKAGE_RESTARTED) || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART)) @@ -430,7 +402,6 @@ public class NotificationManagerService extends INotificationManager.Stub { super(); mContext = context; - mLightsService = lights; mAm = ActivityManagerNative.getDefault(); mSound = new NotificationPlayer(TAG); mSound.setUsesWakeLock(context); @@ -440,7 +411,6 @@ public class NotificationManagerService extends INotificationManager.Stub mStatusBar = statusBar; statusBar.setNotificationCallbacks(mNotificationCallbacks); - mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY); mNotificationLight = lights.getLight(LightsService.LIGHT_ID_NOTIFICATIONS); mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION); @@ -461,10 +431,9 @@ public class NotificationManagerService extends INotificationManager.Stub mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS; } - // register for battery changed notifications + // register for various Intents IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Usb.ACTION_USB_STATE); + filter.addAction(UsbManager.ACTION_USB_STATE); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); @@ -667,6 +636,7 @@ public class NotificationManagerService extends INotificationManager.Stub // Notifications // ============================================================================ + @Deprecated public void enqueueNotification(String pkg, int id, Notification notification, int[] idOut) { enqueueNotificationWithTag(pkg, null /* tag */, id, notification, idOut); @@ -722,10 +692,6 @@ public class NotificationManagerService extends INotificationManager.Stub throw new IllegalArgumentException("contentView required: pkg=" + pkg + " id=" + id + " notification=" + notification); } - if (notification.contentIntent == null) { - throw new IllegalArgumentException("contentIntent required: pkg=" + pkg - + " id=" + id + " notification=" + notification); - } } synchronized (mNotificationList) { @@ -883,7 +849,20 @@ public class NotificationManagerService extends INotificationManager.Stub manager.sendAccessibilityEvent(event); } - private void cancelNotificationLocked(NotificationRecord r) { + private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete) { + // tell the app + if (sendDelete) { + if (r.notification.deleteIntent != null) { + try { + r.notification.deleteIntent.send(); + } catch (PendingIntent.CanceledException ex) { + // do nothing - there's no relevant way to recover, and + // no reason to let this propagate + Slog.w(TAG, "canceled PendingIntent for " + r.pkg, ex); + } + } + } + // status bar if (r.notification.icon != 0) { long identity = Binder.clearCallingIdentity(); @@ -932,7 +911,7 @@ public class NotificationManagerService extends INotificationManager.Stub * and none of the {@code mustNotHaveFlags}. */ private void cancelNotification(String pkg, String tag, int id, int mustHaveFlags, - int mustNotHaveFlags) { + int mustNotHaveFlags, boolean sendDelete) { EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, mustHaveFlags); synchronized (mNotificationList) { @@ -949,7 +928,7 @@ public class NotificationManagerService extends INotificationManager.Stub mNotificationList.remove(index); - cancelNotificationLocked(r); + cancelNotificationLocked(r, sendDelete); updateLightsLocked(); } } @@ -982,7 +961,7 @@ public class NotificationManagerService extends INotificationManager.Stub return true; } mNotificationList.remove(i); - cancelNotificationLocked(r); + cancelNotificationLocked(r, false); } if (canceledSomething) { updateLightsLocked(); @@ -991,7 +970,7 @@ public class NotificationManagerService extends INotificationManager.Stub } } - + @Deprecated public void cancelNotification(String pkg, int id) { cancelNotificationWithTag(pkg, null /* tag */, id); } @@ -1001,7 +980,7 @@ public class NotificationManagerService extends INotificationManager.Stub // Don't allow client applications to cancel foreground service notis. cancelNotification(pkg, tag, id, 0, Binder.getCallingUid() == Process.SYSTEM_UID - ? 0 : Notification.FLAG_FOREGROUND_SERVICE); + ? 0 : Notification.FLAG_FOREGROUND_SERVICE, false); } public void cancelAllNotifications(String pkg) { @@ -1037,17 +1016,8 @@ public class NotificationManagerService extends INotificationManager.Stub if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR)) == 0) { - if (r.notification.deleteIntent != null) { - try { - r.notification.deleteIntent.send(); - } catch (PendingIntent.CanceledException ex) { - // do nothing - there's no relevant way to recover, and - // no reason to let this propagate - Slog.w(TAG, "canceled PendingIntent for " + r.pkg, ex); - } - } mNotificationList.remove(i); - cancelNotificationLocked(r); + cancelNotificationLocked(r, true); } } @@ -1055,34 +1025,9 @@ public class NotificationManagerService extends INotificationManager.Stub } } - private void updateLights() { - synchronized (mNotificationList) { - updateLightsLocked(); - } - } - // lock on mNotificationList private void updateLightsLocked() { - // Battery low always shows, other states only show if charging. - if (mBatteryLow) { - if (mBatteryCharging) { - mBatteryLight.setColor(BATTERY_LOW_ARGB); - } else { - // Flash when battery is low and not charging - mBatteryLight.setFlashing(BATTERY_LOW_ARGB, LightsService.LIGHT_FLASH_TIMED, - BATTERY_BLINK_ON, BATTERY_BLINK_OFF); - } - } else if (mBatteryCharging) { - if (mBatteryFull) { - mBatteryLight.setColor(BATTERY_FULL_ARGB); - } else { - mBatteryLight.setColor(BATTERY_MEDIUM_ARGB); - } - } else { - mBatteryLight.turnOff(); - } - // clear pending pulse notification if screen is on if (mScreenOn || mLedNotification == null) { mPendingPulseNotification = false; diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 9b8a605..94312fc 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -1050,7 +1050,7 @@ class PackageManagerService extends IPackageManager.Stub { void cleanupInstallFailedPackage(PackageSetting ps) { Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name); if (mInstaller != null) { - boolean useSecureFS = useEncryptedFilesystemForPackage(ps.pkg); + boolean useSecureFS = false; int retCode = mInstaller.remove(ps.name, useSecureFS); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data directory for package: " @@ -1225,6 +1225,7 @@ class PackageManagerService extends IPackageManager.Stub { } } + permReader.close(); } catch (XmlPullParserException e) { Slog.w(TAG, "Got execption parsing permissions.", e); } catch (IOException e) { @@ -2779,11 +2780,6 @@ class PackageManagerService extends IPackageManager.Stub { return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED; } - private static boolean useEncryptedFilesystemForPackage(PackageParser.Package pkg) { - return Environment.isEncryptedFilesystemEnabled() && - ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_NEVER_ENCRYPT) == 0); - } - private boolean verifyPackageUpdate(PackageSetting oldPkg, PackageParser.Package newPkg) { if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { Slog.w(TAG, "Unable to update from " + oldPkg.name @@ -2800,7 +2796,7 @@ class PackageManagerService extends IPackageManager.Stub { } private File getDataPathForPackage(PackageParser.Package pkg) { - boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(pkg); + boolean useEncryptedFSDir = false; File dataPath; if (useEncryptedFSDir) { dataPath = new File(mSecureAppDataDir, pkg.packageName); @@ -2852,7 +2848,7 @@ class PackageManagerService extends IPackageManager.Stub { mResolveActivity.processName = mAndroidApplication.processName; mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; - mResolveActivity.theme = com.android.internal.R.style.Theme_Dialog_Alert; + mResolveActivity.theme = com.android.internal.R.style.Theme_Holo_Dialog_Alert; mResolveActivity.exported = true; mResolveActivity.enabled = true; mResolveInfo.activityInfo = mResolveActivity; @@ -2926,24 +2922,23 @@ class PackageManagerService extends IPackageManager.Stub { System.arraycopy(mTmpSharedLibraries, 0, pkg.usesLibraryFiles, 0, num); } + } - if (pkg.reqFeatures != null) { - N = pkg.reqFeatures.size(); - for (int i=0; i<N; i++) { - FeatureInfo fi = pkg.reqFeatures.get(i); - if ((fi.flags&FeatureInfo.FLAG_REQUIRED) == 0) { - // Don't care. - continue; - } + if (pkg.reqFeatures != null) { + int N = pkg.reqFeatures.size(); + for (int i = 0; i < N; i++) { + FeatureInfo fi = pkg.reqFeatures.get(i); + if ((fi.flags & FeatureInfo.FLAG_REQUIRED) == 0) { + // Don't care. + continue; + } - if (fi.name != null) { - if (mAvailableFeatures.get(fi.name) == null) { - Slog.e(TAG, "Package " + pkg.packageName - + " requires unavailable feature " - + fi.name + "; failing!"); - mLastScanError = PackageManager.INSTALL_FAILED_MISSING_FEATURE; - return null; - } + if (fi.name != null) { + if (mAvailableFeatures.get(fi.name) == null) { + Slog.e(TAG, "Package " + pkg.packageName + + " requires unavailable feature " + fi.name + "; failing!"); + mLastScanError = PackageManager.INSTALL_FAILED_MISSING_FEATURE; + return null; } } } @@ -3157,7 +3152,7 @@ class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.dataDir = dataPath.getPath(); } else { // This is a normal package, need to make its data directory. - boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(pkg); + boolean useEncryptedFSDir = false; dataPath = getDataPathForPackage(pkg); boolean uidError = false; @@ -4412,10 +4407,19 @@ class PackageManagerService extends IPackageManager.Stub { } } } - + + /** + * Check if the external storage media is available. This is true if there + * is a mounted external storage medium or if the external storage is + * emulated. + */ + private boolean isExternalMediaAvailable() { + return mMediaMounted || Environment.isExternalStorageEmulated(); + } + public String nextPackageToClean(String lastPackage) { synchronized (mPackages) { - if (!mMediaMounted) { + if (!isExternalMediaAvailable()) { // If the external storage is no longer mounted at this point, // the caller may not have been able to delete all of this // packages files and can not delete any more. Bail. @@ -4435,7 +4439,7 @@ class PackageManagerService extends IPackageManager.Stub { void startCleaningPackages() { synchronized (mPackages) { - if (!mMediaMounted) { + if (!isExternalMediaAvailable()) { return; } if (mSettings.mPackagesToBeCleaned.size() <= 0) { @@ -4570,6 +4574,80 @@ class PackageManagerService extends IPackageManager.Stub { mHandler.sendMessage(msg); } + public void setInstallerPackageName(String targetPackage, + String installerPackageName) { + PackageSetting pkgSetting; + final int uid = Binder.getCallingUid(); + final int permission = mContext.checkCallingPermission( + android.Manifest.permission.INSTALL_PACKAGES); + final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); + synchronized (mPackages) { + PackageSetting targetPackageSetting = mSettings.mPackages.get(targetPackage); + if (targetPackageSetting == null) { + throw new IllegalArgumentException("Unknown target package: " + targetPackage); + } + + PackageSetting installerPackageSetting; + if (installerPackageName != null) { + installerPackageSetting = mSettings.mPackages.get(installerPackageName); + if (installerPackageSetting == null) { + throw new IllegalArgumentException("Unknown installer package: " + + installerPackageName); + } + } else { + installerPackageSetting = null; + } + + Signature[] callerSignature; + Object obj = mSettings.getUserIdLP(uid); + if (obj != null) { + if (obj instanceof SharedUserSetting) { + callerSignature = ((SharedUserSetting)obj).signatures.mSignatures; + } else if (obj instanceof PackageSetting) { + callerSignature = ((PackageSetting)obj).signatures.mSignatures; + } else { + throw new SecurityException("Bad object " + obj + " for uid " + uid); + } + } else { + throw new SecurityException("Unknown calling uid " + uid); + } + + // Verify: can't set installerPackageName to a package that is + // not signed with the same cert as the caller. + if (installerPackageSetting != null) { + if (checkSignaturesLP(callerSignature, + installerPackageSetting.signatures.mSignatures) + != PackageManager.SIGNATURE_MATCH) { + throw new SecurityException( + "Caller does not have same cert as new installer package " + + installerPackageName); + } + } + + // Verify: if target already has an installer package, it must + // be signed with the same cert as the caller. + if (targetPackageSetting.installerPackageName != null) { + PackageSetting setting = mSettings.mPackages.get( + targetPackageSetting.installerPackageName); + // If the currently set package isn't valid, then it's always + // okay to change it. + if (setting != null) { + if (checkSignaturesLP(callerSignature, + setting.signatures.mSignatures) + != PackageManager.SIGNATURE_MATCH) { + throw new SecurityException( + "Caller does not have same cert as old installer package " + + targetPackageSetting.installerPackageName); + } + } + } + + // Okay! + targetPackageSetting.installerPackageName = installerPackageName; + scheduleWriteSettingsLocked(); + } + } + private void processPendingInstall(final InstallArgs args, final int currentStatus) { // Queue up an async operation since the package installation may take a little while. mHandler.post(new Runnable() { @@ -4677,6 +4755,79 @@ class PackageManagerService extends IPackageManager.Stub { abstract void handleReturnCode(); } + class MeasureParams extends HandlerParams { + private final PackageStats mStats; + private boolean mSuccess; + + private final IPackageStatsObserver mObserver; + + public MeasureParams(PackageStats stats, boolean success, + IPackageStatsObserver observer) { + mObserver = observer; + mStats = stats; + mSuccess = success; + } + + @Override + void handleStartCopy() throws RemoteException { + final boolean mounted; + + if (Environment.isExternalStorageEmulated()) { + mounted = true; + } else { + final String status = Environment.getExternalStorageState(); + + mounted = status.equals(Environment.MEDIA_MOUNTED) + || status.equals(Environment.MEDIA_MOUNTED_READ_ONLY); + } + + if (mounted) { + final File externalCacheDir = Environment + .getExternalStorageAppCacheDirectory(mStats.packageName); + final long externalCacheSize = mContainerService + .calculateDirectorySize(externalCacheDir.getPath()); + mStats.externalCacheSize = externalCacheSize; + + final File externalDataDir = Environment + .getExternalStorageAppDataDirectory(mStats.packageName); + long externalDataSize = mContainerService.calculateDirectorySize(externalDataDir + .getPath()); + + if (externalCacheDir.getParentFile().equals(externalDataDir)) { + externalDataSize -= externalCacheSize; + } + mStats.externalDataSize = externalDataSize; + + final File externalMediaDir = Environment + .getExternalStorageAppMediaDirectory(mStats.packageName); + mStats.externalMediaSize = mContainerService + .calculateDirectorySize(externalCacheDir.getPath()); + + final File externalObbDir = Environment + .getExternalStorageAppObbDirectory(mStats.packageName); + mStats.externalObbSize = mContainerService.calculateDirectorySize(externalObbDir + .getPath()); + } + } + + @Override + void handleReturnCode() { + if (mObserver != null) { + try { + mObserver.onGetStatsCompleted(mStats, mSuccess); + } catch (RemoteException e) { + Slog.i(TAG, "Observer no longer exists."); + } + } + } + + @Override + void handleServiceError() { + Slog.e(TAG, "Could not measure application " + mStats.packageName + + " external storage"); + } + } + class InstallParams extends HandlerParams { final IPackageInstallObserver observer; int flags; @@ -6162,7 +6313,7 @@ class PackageManagerService extends IPackageManager.Stub { deletedPs = mSettings.mPackages.get(packageName); } if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { - boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p); + boolean useEncryptedFSDir = false; if (mInstaller != null) { int retCode = mInstaller.remove(packageName, useEncryptedFSDir); if (retCode < 0) { @@ -6406,7 +6557,7 @@ class PackageManagerService extends IPackageManager.Stub { } boolean useEncryptedFSDir = false; - if(!dataOnly) { + if (!dataOnly) { //need to check this only for fully installed applications if (p == null) { Slog.w(TAG, "Package named '" + packageName +"' doesn't exist."); @@ -6417,7 +6568,6 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Package " + packageName + " has no applicationInfo."); return false; } - useEncryptedFSDir = useEncryptedFilesystemForPackage(p); } if (mInstaller != null) { int retCode = mInstaller.clearUserData(packageName, useEncryptedFSDir); @@ -6471,7 +6621,7 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Package " + packageName + " has no applicationInfo."); return false; } - boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p); + boolean useEncryptedFSDir = false; if (mInstaller != null) { int retCode = mInstaller.deleteCacheFiles(packageName, useEncryptedFSDir); if (retCode < 0) { @@ -6491,18 +6641,16 @@ class PackageManagerService extends IPackageManager.Stub { mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this); - PackageStats lStats = new PackageStats(packageName); - final boolean succeded; + PackageStats stats = new PackageStats(packageName); + + final boolean success; synchronized (mInstallLock) { - succeded = getPackageSizeInfoLI(packageName, lStats); + success = getPackageSizeInfoLI(packageName, stats); } - if(observer != null) { - try { - observer.onGetStatsCompleted(lStats, succeded); - } catch (RemoteException e) { - Log.i(TAG, "Observer no longer exists."); - } - } //end if observer + + Message msg = mHandler.obtainMessage(INIT_COPY); + msg.obj = new MeasureParams(stats, success, observer); + mHandler.sendMessage(msg); } //end run }); } @@ -6535,7 +6683,7 @@ class PackageManagerService extends IPackageManager.Stub { } publicSrcDir = isForwardLocked(p) ? applicationInfo.publicSourceDir : null; } - boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p); + boolean useEncryptedFSDir = false; if (mInstaller != null) { int res = mInstaller.getSizeInfo(packageName, p.mPath, publicSrcDir, pStats, useEncryptedFSDir); @@ -7299,7 +7447,7 @@ class PackageManagerService extends IPackageManager.Stub { pw.println(" "); pw.println("Package warning messages:"); File fname = getSettingsProblemFile(); - FileInputStream in; + FileInputStream in = null; try { in = new FileInputStream(fname); int avail = in.available(); @@ -7308,6 +7456,13 @@ class PackageManagerService extends IPackageManager.Stub { pw.print(new String(data)); } catch (FileNotFoundException e) { } catch (IOException e) { + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + } + } } } } @@ -7700,8 +7855,7 @@ class PackageManagerService extends IPackageManager.Stub { this.pkgFlags = pkgFlags & ( ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_FORWARD_LOCK | - ApplicationInfo.FLAG_EXTERNAL_STORAGE | - ApplicationInfo.FLAG_NEVER_ENCRYPT); + ApplicationInfo.FLAG_EXTERNAL_STORAGE); } } @@ -7889,6 +8043,14 @@ class PackageManagerService extends IPackageManager.Stub { sharedUser = orig.sharedUser; } + public void copyFrom(PackageSetting base) { + super.copyFrom((PackageSettingBase) base); + + userId = base.userId; + sharedUser = base.sharedUser; + pkg = base.pkg; + } + @Override public String toString() { return "PackageSetting{" @@ -9736,7 +9898,8 @@ class PackageManagerService extends IPackageManager.Stub { * Update media status on PackageManager. */ public void updateExternalMediaStatus(final boolean mediaStatus, final boolean reportStatus) { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { + int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { throw new SecurityException("Media status can only be updated by the system"); } synchronized (mPackages) { diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index a6daaef..caf6376 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -219,6 +219,8 @@ class PowerManagerService extends IPowerManager.Stub private float mLightSensorValue = -1; private boolean mProxIgnoredBecauseScreenTurnedOff = false; private int mHighestLightSensorValue = -1; + private boolean mLightSensorPendingDecrease = false; + private boolean mLightSensorPendingIncrease = false; private float mLightSensorPendingValue = -1; private int mLightSensorScreenBrightness = -1; private int mLightSensorButtonBrightness = -1; @@ -1141,6 +1143,8 @@ class PowerManagerService extends IPowerManager.Stub pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); pw.println(" mLightSensorValue=" + mLightSensorValue + " mLightSensorPendingValue=" + mLightSensorPendingValue); + pw.println(" mLightSensorPendingDecrease=" + mLightSensorPendingDecrease + + " mLightSensorPendingIncrease=" + mLightSensorPendingIncrease); pw.println(" mLightSensorScreenBrightness=" + mLightSensorScreenBrightness + " mLightSensorButtonBrightness=" + mLightSensorButtonBrightness + " mLightSensorKeyboardBrightness=" + mLightSensorKeyboardBrightness); @@ -1171,10 +1175,8 @@ class PowerManagerService extends IPowerManager.Stub pw.println("mPokeLocks.size=" + mPokeLocks.size() + ":"); for (PokeLock p: mPokeLocks.values()) { pw.println(" poke lock '" + p.tag + "':" - + ((p.pokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0 - ? " POKE_LOCK_IGNORE_CHEEK_EVENTS" : "") - + ((p.pokey & POKE_LOCK_IGNORE_TOUCH_AND_CHEEK_EVENTS) != 0 - ? " POKE_LOCK_IGNORE_TOUCH_AND_CHEEK_EVENTS" : "") + + ((p.pokey & POKE_LOCK_IGNORE_TOUCH_EVENTS) != 0 + ? " POKE_LOCK_IGNORE_TOUCH_EVENTS" : "") + ((p.pokey & POKE_LOCK_SHORT_TIMEOUT) != 0 ? " POKE_LOCK_SHORT_TIMEOUT" : "") + ((p.pokey & POKE_LOCK_MEDIUM_TIMEOUT) != 0 @@ -1609,7 +1611,7 @@ class PowerManagerService extends IPowerManager.Stub if (err == 0) { mLastScreenOnTime = (on ? SystemClock.elapsedRealtime() : 0); if (mUseSoftwareAutoBrightness) { - enableLightSensor(on); + enableLightSensorLocked(on); if (!on) { // make sure button and key backlights are off too mButtonLight.turnOff(); @@ -1742,6 +1744,8 @@ class PowerManagerService extends IPowerManager.Stub } else { // cancel light sensor task mHandler.removeCallbacks(mAutoBrightnessTask); + mLightSensorPendingDecrease = false; + mLightSensorPendingIncrease = false; mScreenOffTime = SystemClock.elapsedRealtime(); long identity = Binder.clearCallingIdentity(); try { @@ -2212,31 +2216,13 @@ class PowerManagerService extends IPowerManager.Stub private void userActivity(long time, long timeoutOverride, boolean noChangeLights, int eventType, boolean force) { - if (((mPokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0) - && (eventType == CHEEK_EVENT)) { - if (false) { - Slog.d(TAG, "dropping cheek event mPokey=0x" + Integer.toHexString(mPokey)); - } - return; - } - - if (((mPokey & POKE_LOCK_IGNORE_TOUCH_AND_CHEEK_EVENTS) != 0) - && (eventType == TOUCH_EVENT || eventType == TOUCH_UP_EVENT - || eventType == LONG_TOUCH_EVENT || eventType == CHEEK_EVENT)) { + if (((mPokey & POKE_LOCK_IGNORE_TOUCH_EVENTS) != 0) && (eventType == TOUCH_EVENT)) { if (false) { Slog.d(TAG, "dropping touch mPokey=0x" + Integer.toHexString(mPokey)); } return; } - if (false) { - if (((mPokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0)) { - Slog.d(TAG, "userActivity !!!");//, new RuntimeException()); - } else { - Slog.d(TAG, "mPokey=0x" + Integer.toHexString(mPokey)); - } - } - synchronized (mLocks) { if (mSpew) { Slog.d(TAG, "userActivity mLastEventTime=" + mLastEventTime + " time=" + time @@ -2325,9 +2311,10 @@ class PowerManagerService extends IPowerManager.Stub private Runnable mAutoBrightnessTask = new Runnable() { public void run() { synchronized (mLocks) { - int value = (int)mLightSensorPendingValue; - if (value >= 0) { - mLightSensorPendingValue = -1; + if (mLightSensorPendingDecrease || mLightSensorPendingIncrease) { + int value = (int)mLightSensorPendingValue; + mLightSensorPendingDecrease = false; + mLightSensorPendingIncrease = false; lightSensorChangedLocked(value); } } @@ -2569,16 +2556,12 @@ class PowerManagerService extends IPowerManager.Stub } private void setScreenBrightnessMode(int mode) { - boolean enabled = (mode == SCREEN_BRIGHTNESS_MODE_AUTOMATIC); - if (mUseSoftwareAutoBrightness && mAutoBrightessEnabled != enabled) { - mAutoBrightessEnabled = enabled; - if (isScreenOn()) { - // force recompute of backlight values - if (mLightSensorValue >= 0) { - int value = (int)mLightSensorValue; - mLightSensorValue = -1; - lightSensorChangedLocked(value); - } + synchronized (mLocks) { + boolean enabled = (mode == SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + if (mUseSoftwareAutoBrightness && mAutoBrightessEnabled != enabled) { + mAutoBrightessEnabled = enabled; + // This will get us a new value + enableLightSensorLocked(mAutoBrightessEnabled && isScreenOn()); } } } @@ -2604,7 +2587,8 @@ class PowerManagerService extends IPowerManager.Stub } mKeylightDelay = LONG_KEYLIGHT_DELAY; if (totalDelay < 0) { - mScreenOffDelay = Integer.MAX_VALUE; + // negative number means stay on as long as possible. + mScreenOffDelay = mMaximumScreenOffTimeout; } else if (mKeylightDelay < totalDelay) { // subtract the time that the keylight delay. This will give us the // remainder of the time that we need to sleep to get the accurate @@ -2729,7 +2713,6 @@ class PowerManagerService extends IPowerManager.Stub // don't bother with the light sensor if auto brightness is handled in hardware if (mUseSoftwareAutoBrightness) { mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); - enableLightSensor(true); } // wait until sensors are enabled before turning on screen. @@ -2747,6 +2730,8 @@ class PowerManagerService extends IPowerManager.Stub Slog.d(TAG, "system ready!"); mDoneBooting = true; + enableLightSensorLocked(mUseSoftwareAutoBrightness && mAutoBrightessEnabled); + long identity = Binder.clearCallingIdentity(); try { mBatteryStats.noteScreenBrightness(getPreferredBrightness()); @@ -2901,9 +2886,13 @@ class PowerManagerService extends IPowerManager.Stub } } - private void enableLightSensor(boolean enable) { + private void enableLightSensorLocked(boolean enable) { if (mDebugLightSensor) { - Slog.d(TAG, "enableLightSensor " + enable); + Slog.d(TAG, "enableLightSensorLocked enable=" + enable + + " mAutoBrightessEnabled=" + mAutoBrightessEnabled); + } + if (!mAutoBrightessEnabled) { + enable = false; } if (mSensorManager != null && mLightSensorEnabled != enable) { mLightSensorEnabled = enable; @@ -2980,19 +2969,29 @@ class PowerManagerService extends IPowerManager.Stub if (mDebugLightSensor) { Slog.d(TAG, "onSensorChanged: light value: " + value); } - mHandler.removeCallbacks(mAutoBrightnessTask); - if (mLightSensorValue != value) { - if (mLightSensorValue == -1 || - milliseconds < mLastScreenOnTime + mLightSensorWarmupTime) { - // process the value immediately if screen has just turned on - lightSensorChangedLocked(value); - } else { + if (mLightSensorValue == -1 || + milliseconds < mLastScreenOnTime + mLightSensorWarmupTime) { + // process the value immediately if screen has just turned on + mHandler.removeCallbacks(mAutoBrightnessTask); + mLightSensorPendingDecrease = false; + mLightSensorPendingIncrease = false; + lightSensorChangedLocked(value); + } else { + if ((value > mLightSensorValue && mLightSensorPendingDecrease) || + (value < mLightSensorValue && mLightSensorPendingIncrease) || + (value == mLightSensorValue) || + (!mLightSensorPendingDecrease && !mLightSensorPendingIncrease)) { // delay processing to debounce the sensor + mHandler.removeCallbacks(mAutoBrightnessTask); + mLightSensorPendingDecrease = (value < mLightSensorValue); + mLightSensorPendingIncrease = (value > mLightSensorValue); + if (mLightSensorPendingDecrease || mLightSensorPendingIncrease) { + mLightSensorPendingValue = value; + mHandler.postDelayed(mAutoBrightnessTask, LIGHT_SENSOR_DELAY); + } + } else { mLightSensorPendingValue = value; - mHandler.postDelayed(mAutoBrightnessTask, LIGHT_SENSOR_DELAY); } - } else { - mLightSensorPendingValue = -1; } } } diff --git a/services/java/com/android/server/ProcessStats.java b/services/java/com/android/server/ProcessStats.java index 020f9ed..1a12a84 100644 --- a/services/java/com/android/server/ProcessStats.java +++ b/services/java/com/android/server/ProcessStats.java @@ -799,8 +799,9 @@ public class ProcessStats { } private String readFile(String file, char endChar) { + FileInputStream is = null; try { - FileInputStream is = new FileInputStream(file); + is = new FileInputStream(file); int len = is.read(mBuffer); is.close(); @@ -811,10 +812,17 @@ public class ProcessStats { break; } } - return new String(mBuffer, 0, 0, i); + return new String(mBuffer, 0, i); } } catch (java.io.FileNotFoundException e) { } catch (java.io.IOException e) { + } finally { + if (is != null) { + try { + is.close(); + } catch (java.io.IOException e) { + } + } } return null; } @@ -841,4 +849,3 @@ public class ProcessStats { } } } - diff --git a/services/java/com/android/server/SamplingProfilerService.java b/services/java/com/android/server/SamplingProfilerService.java new file mode 100644 index 0000000..61267d0 --- /dev/null +++ b/services/java/com/android/server/SamplingProfilerService.java @@ -0,0 +1,117 @@ +/* + * 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; + +import android.content.ContentResolver; +import android.os.DropBoxManager; +import android.os.FileObserver; +import android.os.Binder; + +import android.util.Slog; +import android.content.Context; +import android.database.ContentObserver; +import android.os.SystemProperties; +import android.provider.Settings; +import com.android.internal.os.SamplingProfilerIntegration; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; + +public class SamplingProfilerService extends Binder { + + private static final String TAG = "SamplingProfilerService"; + private static final boolean LOCAL_LOGV = false; + public static final String SNAPSHOT_DIR = SamplingProfilerIntegration.SNAPSHOT_DIR; + + private FileObserver snapshotObserver; + + public SamplingProfilerService(Context context) { + registerSettingObserver(context); + startWorking(context); + } + + private void startWorking(Context context) { + if (LOCAL_LOGV) Slog.v(TAG, "starting SamplingProfilerService!"); + + final DropBoxManager dropbox = + (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE); + + // before FileObserver is ready, there could have already been some snapshots + // in the directory, we don't want to miss them + File[] snapshotFiles = new File(SNAPSHOT_DIR).listFiles(); + for (int i = 0; snapshotFiles != null && i < snapshotFiles.length; i++) { + handleSnapshotFile(snapshotFiles[i], dropbox); + } + + // detect new snapshot and put it in dropbox + // delete it afterwards no matter what happened before + // Note: needs listening at event ATTRIB rather than CLOSE_WRITE, because we set the + // readability of snapshot files after writing them! + snapshotObserver = new FileObserver(SNAPSHOT_DIR, FileObserver.ATTRIB) { + @Override + public void onEvent(int event, String path) { + handleSnapshotFile(new File(SNAPSHOT_DIR, path), dropbox); + } + }; + snapshotObserver.startWatching(); + + if (LOCAL_LOGV) Slog.v(TAG, "SamplingProfilerService activated"); + } + + private void handleSnapshotFile(File file, DropBoxManager dropbox) { + try { + dropbox.addFile(TAG, file, 0); + if (LOCAL_LOGV) Slog.v(TAG, file.getPath() + " added to dropbox"); + } catch (IOException e) { + Slog.e(TAG, "Can't add " + file.getPath() + " to dropbox", e); + } finally { + file.delete(); + } + } + + private void registerSettingObserver(Context context) { + ContentResolver contentResolver = context.getContentResolver(); + contentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.SAMPLING_PROFILER_MS), + false, new SamplingProfilerSettingsObserver(contentResolver)); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("SamplingProfilerService:"); + pw.println("Watching directory: " + SNAPSHOT_DIR); + } + + private class SamplingProfilerSettingsObserver extends ContentObserver { + private ContentResolver mContentResolver; + public SamplingProfilerSettingsObserver(ContentResolver contentResolver) { + super(null); + mContentResolver = contentResolver; + onChange(false); + } + @Override + public void onChange(boolean selfChange) { + Integer samplingProfilerMs = Settings.Secure.getInt( + mContentResolver, Settings.Secure.SAMPLING_PROFILER_MS, 0); + // setting this secure property will start or stop sampling profiler, + // as well as adjust the the time between taking snapshots. + SystemProperties.set("persist.sys.profiler_ms", samplingProfilerMs.toString()); + } + } +} diff --git a/services/java/com/android/server/ScreenRotationAnimation.java b/services/java/com/android/server/ScreenRotationAnimation.java new file mode 100644 index 0000000..bef64b3 --- /dev/null +++ b/services/java/com/android/server/ScreenRotationAnimation.java @@ -0,0 +1,358 @@ +/* + * 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; // TODO: use com.android.server.wm, once things move there + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.view.Display; +import android.view.Surface; +import android.view.SurfaceSession; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.animation.Transformation; + +class ScreenRotationAnimation { + static final String TAG = "ScreenRotationAnimation"; + static final boolean DEBUG = false; + + final Context mContext; + final Display mDisplay; + Surface mSurface; + Surface mBlackSurface; + int mWidth, mHeight; + + int mSnapshotRotation; + int mSnapshotDeltaRotation; + int mOriginalRotation; + int mOriginalWidth, mOriginalHeight; + int mCurRotation; + + Animation mExitAnimation; + final Transformation mExitTransformation = new Transformation(); + Animation mEnterAnimation; + final Transformation mEnterTransformation = new Transformation(); + boolean mStarted; + + final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + final Matrix mSnapshotInitialMatrix = new Matrix(); + final Matrix mSnapshotFinalMatrix = new Matrix(); + final float[] mTmpFloats = new float[9]; + + public ScreenRotationAnimation(Context context, Display display, SurfaceSession session, + boolean inTransaction) { + mContext = context; + mDisplay = display; + + display.getMetrics(mDisplayMetrics); + + Bitmap screenshot = Surface.screenshot(0, 0); + + if (screenshot != null) { + // Screenshot does NOT include rotation! + mSnapshotRotation = 0; + mWidth = screenshot.getWidth(); + mHeight = screenshot.getHeight(); + } else { + // Just in case. + mSnapshotRotation = display.getRotation(); + mWidth = mDisplayMetrics.widthPixels; + mHeight = mDisplayMetrics.heightPixels; + } + + mOriginalRotation = display.getRotation(); + mOriginalWidth = mDisplayMetrics.widthPixels; + mOriginalHeight = mDisplayMetrics.heightPixels; + + if (!inTransaction) { + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, + ">>> OPEN TRANSACTION ScreenRotationAnimation"); + Surface.openTransaction(); + } + + try { + try { + mSurface = new Surface(session, 0, "FreezeSurface", + -1, mWidth, mHeight, PixelFormat.OPAQUE, 0); + mSurface.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 200); + } catch (Surface.OutOfResourcesException e) { + Slog.w(TAG, "Unable to allocate freeze surface", e); + } + + if (false) { + try { + int size = mOriginalWidth > mOriginalHeight ? mOriginalWidth : mOriginalHeight; + mBlackSurface = new Surface(session, 0, "BlackSurface", + -1, size, size, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM); + mBlackSurface.setAlpha(1.0f); + mBlackSurface.setLayer(0); + } catch (Surface.OutOfResourcesException e) { + Slog.w(TAG, "Unable to allocate black surface", e); + } + } + + setRotation(display.getRotation()); + + if (mSurface != null) { + Rect dirty = new Rect(0, 0, mWidth, mHeight); + Canvas c = null; + try { + c = mSurface.lockCanvas(dirty); + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Unable to lock surface", e); + return; + } catch (Surface.OutOfResourcesException e) { + Slog.w(TAG, "Unable to lock surface", e); + return; + } + if (c == null) { + Slog.w(TAG, "Null surface"); + return; + } + + if (screenshot != null) { + Paint paint = new Paint(0); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); + c.drawBitmap(screenshot, 0, 0, paint); + } else { + c.drawColor(Color.GREEN, PorterDuff.Mode.SRC); + } + + mSurface.unlockCanvasAndPost(c); + } + } finally { + if (!inTransaction) { + Surface.closeTransaction(); + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, + "<<< CLOSE TRANSACTION ScreenRotationAnimation"); + } + + if (screenshot != null) { + screenshot.recycle(); + } + } + } + + static int deltaRotation(int oldRotation, int newRotation) { + int delta = newRotation - oldRotation; + if (delta < 0) delta += 4; + return delta; + } + + void setSnapshotTransform(Matrix matrix, float alpha) { + if (mSurface != null) { + matrix.getValues(mTmpFloats); + mSurface.setPosition((int)mTmpFloats[Matrix.MTRANS_X], + (int)mTmpFloats[Matrix.MTRANS_Y]); + mSurface.setMatrix( + mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], + mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); + mSurface.setAlpha(alpha); + if (DEBUG) { + float[] srcPnts = new float[] { 0, 0, mWidth, mHeight }; + float[] dstPnts = new float[4]; + matrix.mapPoints(dstPnts, srcPnts); + Slog.i(TAG, "Original : (" + srcPnts[0] + "," + srcPnts[1] + + ")-(" + srcPnts[2] + "," + srcPnts[3] + ")"); + Slog.i(TAG, "Transformed: (" + dstPnts[0] + "," + dstPnts[1] + + ")-(" + dstPnts[2] + "," + dstPnts[3] + ")"); + } + } + } + + public static void createRotationMatrix(int rotation, int width, int height, + Matrix outMatrix) { + switch (rotation) { + case Surface.ROTATION_0: + outMatrix.reset(); + break; + case Surface.ROTATION_90: + outMatrix.setRotate(90, 0, 0); + outMatrix.postTranslate(height, 0); + break; + case Surface.ROTATION_180: + outMatrix.setRotate(180, 0, 0); + outMatrix.postTranslate(width, height); + break; + case Surface.ROTATION_270: + outMatrix.setRotate(270, 0, 0); + outMatrix.postTranslate(0, width); + break; + } + } + + // Must be called while in a transaction. + public void setRotation(int rotation) { + mCurRotation = rotation; + + // Compute the transformation matrix that must be applied + // to the snapshot to make it stay in the same original position + // with the current screen rotation. + int delta = deltaRotation(rotation, mSnapshotRotation); + createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); + + if (DEBUG) Slog.v(TAG, "**** ROTATION: " + delta); + setSnapshotTransform(mSnapshotInitialMatrix, 1.0f); + } + + /** + * Returns true if animating. + */ + public boolean dismiss(long maxAnimationDuration, float animationScale) { + // Figure out how the screen has moved from the original rotation. + int delta = deltaRotation(mCurRotation, mOriginalRotation); + if (false && delta == 0) { + // Nothing changed, just remove the snapshot. + if (mSurface != null) { + mSurface.destroy(); + mSurface = null; + } + return false; + } + + switch (delta) { + case Surface.ROTATION_0: + mExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_0_exit); + mEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_0_enter); + break; + case Surface.ROTATION_90: + mExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_plus_90_exit); + mEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_plus_90_enter); + break; + case Surface.ROTATION_180: + mExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_180_exit); + mEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_180_enter); + break; + case Surface.ROTATION_270: + mExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_minus_90_exit); + mEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_minus_90_enter); + break; + } + + mDisplay.getMetrics(mDisplayMetrics); + + // Initialize the animations. This is a hack, redefining what "parent" + // means to allow supplying the last and next size. In this definition + // "%p" is the original (let's call it "previous") size, and "%" is the + // screen's current/new size. + mEnterAnimation.initialize(mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, + mOriginalWidth, mOriginalHeight); + mExitAnimation.initialize(mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, + mOriginalWidth, mOriginalHeight); + mStarted = false; + + mExitAnimation.restrictDuration(maxAnimationDuration); + mExitAnimation.scaleCurrentDuration(animationScale); + mEnterAnimation.restrictDuration(maxAnimationDuration); + mEnterAnimation.scaleCurrentDuration(animationScale); + + return true; + } + + public void kill() { + if (mSurface != null) { + mSurface.destroy(); + mSurface = null; + } + if (mBlackSurface != null) { + mBlackSurface.destroy(); + mBlackSurface = null; + } + if (mExitAnimation != null) { + mExitAnimation.cancel(); + mExitAnimation = null; + } + if (mEnterAnimation != null) { + mEnterAnimation.cancel(); + mEnterAnimation = null; + } + } + + public boolean isAnimating() { + return mEnterAnimation != null || mExitAnimation != null; + } + + public boolean stepAnimation(long now) { + if (mEnterAnimation == null && mExitAnimation == null) { + return false; + } + + if (!mStarted) { + mEnterAnimation.setStartTime(now); + mExitAnimation.setStartTime(now); + mStarted = true; + } + + mExitTransformation.clear(); + boolean moreExit = false; + if (mExitAnimation != null) { + moreExit = mExitAnimation.getTransformation(now, mExitTransformation); + if (DEBUG) Slog.v(TAG, "Stepped exit: " + mExitTransformation); + if (!moreExit) { + if (DEBUG) Slog.v(TAG, "Exit animation done!"); + mExitAnimation.cancel(); + mExitAnimation = null; + mExitTransformation.clear(); + if (mSurface != null) { + mSurface.destroy(); + mSurface = null; + } + if (mBlackSurface != null) { + mBlackSurface.destroy(); + mBlackSurface = null; + } + } + } + + mEnterTransformation.clear(); + boolean moreEnter = false; + if (mEnterAnimation != null) { + moreEnter = mEnterAnimation.getTransformation(now, mEnterTransformation); + if (!moreEnter) { + mEnterAnimation.cancel(); + mEnterAnimation = null; + mEnterTransformation.clear(); + } + } + + mSnapshotFinalMatrix.setConcat(mExitTransformation.getMatrix(), mSnapshotInitialMatrix); + setSnapshotTransform(mSnapshotFinalMatrix, mExitTransformation.getAlpha()); + + return moreEnter || moreExit; + } + + public Transformation getEnterTransformation() { + return mEnterTransformation; + } +} diff --git a/services/java/com/android/server/ShutdownActivity.java b/services/java/com/android/server/ShutdownActivity.java index 64b9c5d..c9d4d01 100644 --- a/services/java/com/android/server/ShutdownActivity.java +++ b/services/java/com/android/server/ShutdownActivity.java @@ -27,19 +27,26 @@ import com.android.internal.app.ShutdownThread; public class ShutdownActivity extends Activity { private static final String TAG = "ShutdownActivity"; + private boolean mReboot; private boolean mConfirm; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mConfirm = getIntent().getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false); + Intent intent = getIntent(); + mReboot = Intent.ACTION_REBOOT.equals(intent.getAction()); + mConfirm = intent.getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false); Slog.i(TAG, "onCreate(): confirm=" + mConfirm); Handler h = new Handler(); h.post(new Runnable() { public void run() { - ShutdownThread.shutdown(ShutdownActivity.this, mConfirm); + if (mReboot) { + ShutdownThread.reboot(ShutdownActivity.this, null, mConfirm); + } else { + ShutdownThread.shutdown(ShutdownActivity.this, mConfirm); + } } }); } diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java index e5dfb18..50ea3fa 100644 --- a/services/java/com/android/server/StatusBarManagerService.java +++ b/services/java/com/android/server/StatusBarManagerService.java @@ -32,6 +32,7 @@ import android.os.Binder; import android.os.Handler; import android.os.SystemClock; import android.util.Slog; +import android.view.View; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.IStatusBarService; @@ -68,6 +69,13 @@ public class StatusBarManagerService extends IStatusBarService.Stub ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>(); int mDisabled = 0; + Object mLock = new Object(); + // We usually call it lights out mode, but double negatives are annoying + boolean mLightsOn = true; + boolean mMenuVisible = false; + boolean mIMEButtonVisible = false; + IBinder mIMEToken = null; + private class DisableRecord implements IBinder.DeathRecipient { String pkg; int what; @@ -84,6 +92,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub void onSetDisabled(int status); void onClearAll(); void onNotificationClick(String pkg, String tag, int id); + void onNotificationClear(String pkg, String tag, int id); void onPanelRevealed(); void onNotificationError(String pkg, String tag, int id, int uid, int initialPid, String message); @@ -104,22 +113,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub } // ================================================================================ - // Constructing the view - // ================================================================================ - - public void systemReady() { - } - - public void systemReady2() { - ComponentName cn = ComponentName.unflattenFromString( - mContext.getString(com.android.internal.R.string.config_statusBarComponent)); - Intent intent = new Intent(); - intent.setComponent(cn); - Slog.i(TAG, "Starting service: " + cn); - mContext.startService(intent); - } - - // ================================================================================ // From IStatusBarService // ================================================================================ public void expand() { @@ -240,6 +233,107 @@ public class StatusBarManagerService extends IStatusBarService.Stub } } + /** + * Hide or show the on-screen Menu key. Only call this from the window manager, typically in + * response to a window with FLAG_NEEDS_MENU_KEY set. + */ + public void setMenuKeyVisible(final boolean visible) { + enforceStatusBar(); + + if (SPEW) Slog.d(TAG, (visible?"showing":"hiding") + " MENU key"); + + synchronized(mLock) { + if (mMenuVisible != visible) { + mMenuVisible = visible; + mHandler.post(new Runnable() { + public void run() { + if (mBar != null) { + try { + mBar.setMenuKeyVisible(visible); + } catch (RemoteException ex) { + } + } + } + }); + } + } + } + + public void setIMEButtonVisible(final IBinder token, final boolean visible) { + enforceStatusBar(); + + if (SPEW) Slog.d(TAG, (visible?"showing":"hiding") + " IME Button"); + + synchronized(mLock) { + // In case of IME change, we need to call up setIMEButtonVisible() regardless of + // mIMEButtonVisible because mIMEButtonVisible may not have been set to false when the + // previous IME was destroyed. + mIMEButtonVisible = visible; + mIMEToken = token; + mHandler.post(new Runnable() { + public void run() { + if (mBar != null) { + try { + mBar.setIMEButtonVisible(token, visible); + } catch (RemoteException ex) { + } + } + } + }); + } + } + + /** + * This is used for the automatic version of lights-out mode. Only call this from + * the window manager. + * + * @see setLightsOn(boolean) + */ + public void setActiveWindowIsFullscreen(boolean fullscreen) { + // We could get away with a separate permission here, but STATUS_BAR is + // signatureOrSystem which is probably good enough. There is no public API + // for this, so the question is a security issue, not an API compatibility issue. + enforceStatusBar(); + + synchronized (mLock) { + updateLightsOnLocked(!fullscreen); + } + } + + /** + * This is used for the user-controlled version of lights-out mode. Only call this from + * the status bar itself. + * + * We have two different functions here, because I think we're going to want to + * tweak the behavior when the user keeps turning lights-out mode off and the + * app keeps trying to turn it on. For now they can just fight it out. Having + * these two separte inputs will allow us to keep that change local to here. --joeo + */ + public void setSystemUiVisibility(int vis) { + enforceStatusBarService(); + + synchronized (mLock) { + final boolean lightsOn = (vis & View.STATUS_BAR_HIDDEN) == 0; + updateLightsOnLocked(lightsOn); + } + } + + private void updateLightsOnLocked(final boolean lightsOn) { + if (mLightsOn != lightsOn) { + mLightsOn = lightsOn; + mHandler.post(new Runnable() { + public void run() { + if (mBar != null) { + try { + mBar.setLightsOn(lightsOn); + } catch (RemoteException ex) { + } + } + } + }); + } + } + private void enforceStatusBar() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR, "StatusBarManagerService"); @@ -260,7 +354,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub // Callbacks from the status bar service. // ================================================================================ public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList, - List<IBinder> notificationKeys, List<StatusBarNotification> notifications) { + List<IBinder> notificationKeys, List<StatusBarNotification> notifications, + int switches[], List<IBinder> binders) { enforceStatusBarService(); Slog.i(TAG, "registerStatusBar bar=" + bar); @@ -274,6 +369,13 @@ public class StatusBarManagerService extends IStatusBarService.Stub notifications.add(e.getValue()); } } + synchronized (mLock) { + switches[0] = gatherDisableActionsLocked(); + switches[1] = mLightsOn ? 1 : 0; + switches[2] = mMenuVisible ? 1 : 0; + switches[3] = mIMEButtonVisible ? 1 : 0; + binders.add(mIMEToken); + } } /** @@ -301,6 +403,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub mNotificationCallbacks.onNotificationError(pkg, tag, id, uid, initialPid, message); } + public void onNotificationClear(String pkg, String tag, int id) { + enforceStatusBarService(); + + mNotificationCallbacks.onNotificationClear(pkg, tag, id); + } + public void onClearAllNotifications() { enforceStatusBarService(); diff --git a/services/java/com/android/server/StrictModeFlash.java b/services/java/com/android/server/StrictModeFlash.java new file mode 100644 index 0000000..0a6c625 --- /dev/null +++ b/services/java/com/android/server/StrictModeFlash.java @@ -0,0 +1,114 @@ +/* + * 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; // TODO: use com.android.server.wm, once things move there + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Region; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.view.Display; +import android.view.Surface; +import android.view.SurfaceSession; + +class StrictModeFlash { + private static final String TAG = "StrictModeFlash"; + + Surface mSurface; + int mLastDW; + int mLastDH; + boolean mDrawNeeded; + final int mThickness = 20; + + public StrictModeFlash(Display display, SurfaceSession session) { + final DisplayMetrics dm = new DisplayMetrics(); + display.getMetrics(dm); + + try { + mSurface = new Surface(session, 0, "StrictModeFlash", -1, 1, 1, PixelFormat.TRANSLUCENT, 0); + } catch (Surface.OutOfResourcesException e) { + return; + } + + mSurface.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); // one more than Watermark? arbitrary. + mSurface.setPosition(0, 0); + mDrawNeeded = true; + } + + private void drawIfNeeded() { + if (!mDrawNeeded) { + return; + } + mDrawNeeded = false; + final int dw = mLastDW; + final int dh = mLastDH; + + Rect dirty = new Rect(0, 0, dw, dh); + Canvas c = null; + try { + c = mSurface.lockCanvas(dirty); + } catch (IllegalArgumentException e) { + } catch (Surface.OutOfResourcesException e) { + } + if (c == null) { + return; + } + + // Top + c.clipRect(new Rect(0, 0, dw, mThickness), Region.Op.REPLACE); + c.drawColor(Color.RED); + // Left + c.clipRect(new Rect(0, 0, mThickness, dh), Region.Op.REPLACE); + c.drawColor(Color.RED); + // Right + c.clipRect(new Rect(dw - mThickness, 0, dw, dh), Region.Op.REPLACE); + c.drawColor(Color.RED); + // Bottom + c.clipRect(new Rect(0, dh - mThickness, dw, dh), Region.Op.REPLACE); + c.drawColor(Color.RED); + + mSurface.unlockCanvasAndPost(c); + } + + // Note: caller responsible for being inside + // Surface.openTransaction() / closeTransaction() + public void setVisibility(boolean on) { + if (mSurface == null) { + return; + } + drawIfNeeded(); + if (on) { + mSurface.show(); + } else { + mSurface.hide(); + } + } + + void positionSurface(int dw, int dh) { + if (mLastDW == dw && mLastDH == dh) { + return; + } + mLastDW = dw; + mLastDH = dh; + mSurface.setSize(dw, dh); + mDrawNeeded = true; + } + +} diff --git a/services/java/com/android/server/SystemBackupAgent.java b/services/java/com/android/server/SystemBackupAgent.java index fff1874..a1f43b4 100644 --- a/services/java/com/android/server/SystemBackupAgent.java +++ b/services/java/com/android/server/SystemBackupAgent.java @@ -16,18 +16,16 @@ package com.android.server; -import android.app.backup.AbsoluteFileBackupHelper; import android.app.backup.BackupDataInput; -import android.app.backup.BackupDataInputStream; import android.app.backup.BackupDataOutput; -import android.app.backup.BackupHelper; import android.app.backup.BackupAgentHelper; +import android.app.backup.WallpaperBackupHelper; import android.content.Context; import android.os.ParcelFileDescriptor; import android.os.ServiceManager; -import android.os.SystemService; import android.util.Slog; + import java.io.File; import java.io.IOException; @@ -54,7 +52,7 @@ public class SystemBackupAgent extends BackupAgentHelper { // TODO: Send a delete for any stored wallpaper image in this case? files = new String[] { WALLPAPER_INFO }; } - addHelper("wallpaper", new AbsoluteFileBackupHelper(SystemBackupAgent.this, files)); + addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, files)); super.onBackup(oldState, data, newState); } @@ -62,12 +60,11 @@ public class SystemBackupAgent extends BackupAgentHelper { public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { // On restore, we also support a previous data schema "system_files" - addHelper("wallpaper", new AbsoluteFileBackupHelper(SystemBackupAgent.this, + addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO })); - addHelper("system_files", new AbsoluteFileBackupHelper(SystemBackupAgent.this, + addHelper("system_files", new WallpaperBackupHelper(SystemBackupAgent.this, new String[] { WALLPAPER_IMAGE })); - boolean success = false; try { super.onRestore(data, appVersionCode, newState); @@ -75,7 +72,7 @@ public class SystemBackupAgent extends BackupAgentHelper { Context.WALLPAPER_SERVICE); wallpaper.settingsRestored(); } catch (IOException ex) { - // If there was a failure, delete everything for the wallpaper, this is too aggresive, + // If there was a failure, delete everything for the wallpaper, this is too aggressive, // but this is hopefully a rare failure. Slog.d(TAG, "restore failed", ex); (new File(WALLPAPER_IMAGE)).delete(); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index df69b76..978946f 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -24,6 +24,7 @@ import com.android.internal.os.SamplingProfilerIntegration; import dalvik.system.VMRuntime; import dalvik.system.Zygote; +import android.accounts.AccountManagerService; import android.app.ActivityManagerNative; import android.bluetooth.BluetoothAdapter; import android.content.ComponentName; @@ -32,9 +33,10 @@ import android.content.ContentService; import android.content.Context; import android.content.Intent; import android.content.pm.IPackageManager; +import android.content.res.Configuration; import android.database.ContentObserver; -import android.database.Cursor; import android.media.AudioService; +import android.os.Build; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; @@ -46,10 +48,12 @@ import android.provider.Settings; import android.server.BluetoothA2dpService; import android.server.BluetoothService; import android.server.search.SearchManagerService; +import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.Slog; -import android.accounts.AccountManagerService; +import android.view.Display; +import android.view.WindowManager; import java.io.File; import java.util.Timer; @@ -57,11 +61,8 @@ import java.util.TimerTask; class ServerThread extends Thread { private static final String TAG = "SystemServer"; - private final static boolean INCLUDE_DEMO = false; - private static final int LOG_BOOT_PROGRESS_SYSTEM_RUN = 3010; - - private ContentResolver mContentResolver; + ContentResolver mContentResolver; private class AdbSettingsObserver extends ContentObserver { public AdbSettingsObserver() { @@ -120,12 +121,13 @@ class ServerThread extends Thread { WindowManagerService wm = null; BluetoothService bluetooth = null; BluetoothA2dpService bluetoothA2dp = null; - HeadsetObserver headset = null; + WiredAccessoryObserver wiredAccessory = null; DockObserver dock = null; - UsbObserver usb = null; + UsbService usb = null; UiModeManagerService uiMode = null; RecognitionManagerService recognition = null; ThrottleService throttle = null; + NetworkTimeUpdateService networkTimeUpdater = null; // Critical services... try { @@ -168,13 +170,13 @@ class ServerThread extends Thread { Slog.i(TAG, "System Content Providers"); ActivityManagerService.installSystemProviders(); - Slog.i(TAG, "Battery Service"); - battery = new BatteryService(context); - ServiceManager.addService("battery", battery); - Slog.i(TAG, "Lights Service"); lights = new LightsService(context); + Slog.i(TAG, "Battery Service"); + battery = new BatteryService(context, lights); + ServiceManager.addService("battery", battery); + Slog.i(TAG, "Vibrator Service"); ServiceManager.addService("vibrator", new VibratorService(context)); @@ -202,11 +204,9 @@ class ServerThread extends Thread { // TODO: Use a more reliable check to see if this product should // support Bluetooth - see bug 988521 if (SystemProperties.get("ro.kernel.qemu").equals("1")) { - Slog.i(TAG, "Registering null Bluetooth Service (emulator)"); - ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, null); + Slog.i(TAG, "No Bluetooh Service (emulator)"); } else if (factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) { - Slog.i(TAG, "Registering null Bluetooth Service (factory test)"); - ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, null); + Slog.i(TAG, "No Bluetooth Service (factory test)"); } else { Slog.i(TAG, "Bluetooth Service"); bluetooth = new BluetoothService(context); @@ -215,6 +215,7 @@ class ServerThread extends Thread { bluetoothA2dp = new BluetoothA2dpService(context, bluetooth); ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE, bluetoothA2dp); + bluetooth.initAfterA2dpRegistration(); int bluetoothOn = Settings.Secure.getInt(mContentResolver, Settings.Secure.BLUETOOTH_ON, 0); @@ -234,6 +235,7 @@ class ServerThread extends Thread { NotificationManagerService notification = null; WallpaperManagerService wallpaper = null; LocationManagerService location = null; + CountryDetectorService countryDetector = null; if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { try { @@ -345,6 +347,14 @@ class ServerThread extends Thread { } try { + Slog.i(TAG, "Country Detector"); + countryDetector = new CountryDetectorService(context); + ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting Country Detector", e); + } + + try { Slog.i(TAG, "Search Service"); ServiceManager.addService(Context.SEARCH_SERVICE, new SearchManagerService(context)); @@ -352,11 +362,6 @@ class ServerThread extends Thread { Slog.e(TAG, "Failure starting Search Service", e); } - if (INCLUDE_DEMO) { - Slog.i(TAG, "Installing demo data..."); - (new DemoThread(context)).start(); - } - try { Slog.i(TAG, "DropBox Service"); ServiceManager.addService(Context.DROPBOX_SERVICE, @@ -381,14 +386,6 @@ class ServerThread extends Thread { } try { - Slog.i(TAG, "Headset Observer"); - // Listen for wired headset changes - headset = new HeadsetObserver(context); - } catch (Throwable e) { - Slog.e(TAG, "Failure starting HeadsetObserver", e); - } - - try { Slog.i(TAG, "Dock Observer"); // Listen for dock station changes dock = new DockObserver(context, power); @@ -397,11 +394,19 @@ class ServerThread extends Thread { } try { + Slog.i(TAG, "Wired Accessory Observer"); + // Listen for wired headset changes + wiredAccessory = new WiredAccessoryObserver(context); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting WiredAccessoryObserver", e); + } + + try { Slog.i(TAG, "USB Observer"); // Listen for USB changes - usb = new UsbObserver(context); + usb = new UsbService(context); } catch (Throwable e) { - Slog.e(TAG, "Failure starting UsbObserver", e); + Slog.e(TAG, "Failure starting UsbService", e); } try { @@ -434,13 +439,32 @@ class ServerThread extends Thread { } catch (Throwable e) { Slog.e(TAG, "Failure starting Recognition Service", e); } - + try { Slog.i(TAG, "DiskStats Service"); ServiceManager.addService("diskstats", new DiskStatsService(context)); } catch (Throwable e) { Slog.e(TAG, "Failure starting DiskStats Service", e); } + + try { + // need to add this service even if SamplingProfilerIntegration.isEnabled() + // is false, because it is this service that detects system property change and + // turns on SamplingProfilerIntegration. Plus, when sampling profiler doesn't work, + // there is little overhead for running this service. + Slog.i(TAG, "SamplingProfiler Service"); + ServiceManager.addService("samplingprofiler", + new SamplingProfilerService(context)); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting SamplingProfiler Service", e); + } + + try { + Slog.i(TAG, "NetworkTimeUpdateService"); + networkTimeUpdater = new NetworkTimeUpdateService(context); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting NetworkTimeUpdate service"); + } } // make sure the ADB_ENABLED setting value matches the secure property value @@ -455,14 +479,11 @@ class ServerThread extends Thread { // we are in safe mode. final boolean safeMode = wm.detectSafeMode(); if (safeMode) { - try { - ActivityManagerNative.getDefault().enterSafeMode(); - // Post the safe mode state in the Zygote class - Zygote.systemInSafeMode = true; - // Disable the JIT for the system_server process - VMRuntime.getRuntime().disableJitCompilation(); - } catch (RemoteException e) { - } + ActivityManagerService.self().enterSafeMode(); + // Post the safe mode state in the Zygote class + Zygote.systemInSafeMode = true; + // Disable the JIT for the system_server process + VMRuntime.getRuntime().disableJitCompilation(); } else { // Enable the JIT for the system_server process VMRuntime.getRuntime().startJitCompilation(); @@ -478,10 +499,21 @@ class ServerThread extends Thread { notification.systemReady(); } - if (statusBar != null) { - statusBar.systemReady(); - } wm.systemReady(); + + if (safeMode) { + ActivityManagerService.self().showSafeModeOverlay(); + } + + // Update the configuration for this context by hand, because we're going + // to start using it before the config change done in wm.systemReady() will + // propagate to it. + Configuration config = wm.computeNewConfiguration(); + DisplayMetrics metrics = new DisplayMetrics(); + WindowManager w = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); + w.getDefaultDisplay().getMetrics(metrics); + context.getResources().updateConfiguration(config, metrics); + power.systemReady(); try { pm.systemReady(); @@ -489,11 +521,11 @@ class ServerThread extends Thread { } // These are needed to propagate to the runnable below. - final StatusBarManagerService statusBarF = statusBar; + final Context contextF = context; final BatteryService batteryF = battery; final ConnectivityService connectivityF = connectivity; final DockObserver dockF = dock; - final UsbObserver usbF = usb; + final UsbService usbF = usb; final ThrottleService throttleF = throttle; final UiModeManagerService uiModeF = uiMode; final AppWidgetService appWidgetF = appWidget; @@ -501,6 +533,8 @@ class ServerThread extends Thread { final InputMethodManagerService immF = imm; final RecognitionManagerService recognitionF = recognition; final LocationManagerService locationF = location; + final CountryDetectorService countryDetectorF = countryDetector; + final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater; // We now tell the activity manager it is okay to run third party // code. It will call back into us once it has gotten to the state @@ -512,7 +546,7 @@ class ServerThread extends Thread { public void run() { Slog.i(TAG, "Making services ready"); - if (statusBarF != null) statusBarF.systemReady2(); + startSystemUi(contextF); if (batteryF != null) batteryF.systemReady(); if (connectivityF != null) connectivityF.systemReady(); if (dockF != null) dockF.systemReady(); @@ -528,7 +562,9 @@ class ServerThread extends Thread { if (wallpaperF != null) wallpaperF.systemReady(); if (immF != null) immF.systemReady(); if (locationF != null) locationF.systemReady(); + if (countryDetectorF != null) countryDetectorF.systemReady(); if (throttleF != null) throttleF.systemReady(); + if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemReady(); } }); @@ -540,39 +576,17 @@ class ServerThread extends Thread { Looper.loop(); Slog.d(TAG, "System ServerThread is exiting!"); } -} -class DemoThread extends Thread -{ - DemoThread(Context context) - { - mContext = context; + static final void startSystemUi(Context context) { + Intent intent = new Intent(); + intent.setComponent(new ComponentName("com.android.systemui", + "com.android.systemui.SystemUIService")); + Slog.d(TAG, "Starting service: " + intent); + context.startService(intent); } - - @Override - public void run() - { - try { - Cursor c = mContext.getContentResolver().query(People.CONTENT_URI, null, null, null, null); - boolean hasData = c != null && c.moveToFirst(); - if (c != null) { - c.deactivate(); - } - if (!hasData) { - DemoDataSet dataset = new DemoDataSet(); - dataset.add(mContext); - } - } catch (Throwable e) { - Slog.e("SystemServer", "Failure installing demo data", e); - } - - } - - Context mContext; } -public class SystemServer -{ +public class SystemServer { private static final String TAG = "SystemServer"; public static final int FACTORY_TEST_OFF = 0; @@ -610,7 +624,7 @@ public class SystemServer timer.schedule(new TimerTask() { @Override public void run() { - SamplingProfilerIntegration.writeSnapshot("system_server"); + SamplingProfilerIntegration.writeSnapshot("system_server", null); } }, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL); } @@ -618,7 +632,7 @@ public class SystemServer // The system server has to run all of the time, so it needs to be // as efficient as possible with its memory usage. VMRuntime.getRuntime().setTargetHeapUtilization(0.8f); - + System.loadLibrary("android_servers"); init1(args); } diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java index 3370279..eb14180 100644 --- a/services/java/com/android/server/TelephonyRegistry.java +++ b/services/java/com/android/server/TelephonyRegistry.java @@ -19,7 +19,8 @@ package com.android.server; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.net.NetworkUtils; +import android.net.LinkCapabilities; +import android.net.LinkProperties; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -35,12 +36,14 @@ import android.util.Slog; import java.util.ArrayList; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.net.NetworkInterface; import com.android.internal.app.IBatteryStats; import com.android.internal.telephony.ITelephonyRegistry; import com.android.internal.telephony.IPhoneStateListener; import com.android.internal.telephony.DefaultPhoneNotifier; import com.android.internal.telephony.Phone; +import com.android.internal.telephony.ServiceStateTracker; import com.android.internal.telephony.TelephonyIntents; import com.android.server.am.BatteryStatsService; @@ -63,7 +66,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private final Context mContext; - private final ArrayList<Record> mRecords = new ArrayList(); + // access should be inside synchronized (mRecords) for these two fields + private final ArrayList<IBinder> mRemoveList = new ArrayList<IBinder>(); + private final ArrayList<Record> mRecords = new ArrayList<Record>(); private final IBatteryStats mBatteryStats; @@ -81,7 +86,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE; - private int mDataConnectionState = TelephonyManager.DATA_CONNECTED; + private int mDataConnectionState = TelephonyManager.DATA_UNKNOWN; private boolean mDataConnectionPossible = false; @@ -89,14 +94,18 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private String mDataConnectionApn = ""; - private String[] mDataConnectionApnTypes = null; + private ArrayList<String> mConnectedApns; - private String mDataConnectionInterfaceName = ""; + private LinkProperties mDataConnectionLinkProperties; + + private LinkCapabilities mDataConnectionLinkCapabilities; private Bundle mCellLocation = new Bundle(); private int mDataConnectionNetworkType; + private int mOtaspMode = ServiceStateTracker.OTASP_UNKNOWN; + static final int PHONE_STATE_PERMISSION_MASK = PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR | PhoneStateListener.LISTEN_CALL_STATE | @@ -121,6 +130,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } mContext = context; mBatteryStats = BatteryStatsService.getService(); + mConnectedApns = new ArrayList<String>(); } public void listen(String pkgForDebug, IPhoneStateListener callback, int events, @@ -153,7 +163,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { r.events = events; if (notifyNow) { if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) { - sendServiceState(r, mServiceState); + try { + r.callback.onServiceStateChanged(new ServiceState(mServiceState)); + } catch (RemoteException ex) { + remove(r.binder); + } } if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) { try { @@ -179,7 +193,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } if ((events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) { - sendCellLocation(r, mCellLocation); + try { + r.callback.onCellLocationChanged(new Bundle(mCellLocation)); + } catch (RemoteException ex) { + remove(r.binder); + } } if ((events & PhoneStateListener.LISTEN_CALL_STATE) != 0) { try { @@ -210,6 +228,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } + if ((events & PhoneStateListener.LISTEN_OTASP_CHANGED) != 0) { + try { + r.callback.onOtaspChanged(mOtaspMode); + } catch (RemoteException ex) { + remove(r.binder); + } + } } } } else { @@ -236,16 +261,16 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { mCallState = state; mCallIncomingNumber = incomingNumber; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); + for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_CALL_STATE) != 0) { try { r.callback.onCallStateChanged(state, incomingNumber); } catch (RemoteException ex) { - remove(r.binder); + mRemoveList.add(r.binder); } } } + handleRemoveListLocked(); } broadcastCallStateChanged(state, incomingNumber); } @@ -257,12 +282,16 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { Slog.i(TAG, "notifyServiceState: " + state); synchronized (mRecords) { mServiceState = state; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); + for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) { - sendServiceState(r, state); + try { + r.callback.onServiceStateChanged(new ServiceState(state)); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } } } + handleRemoveListLocked(); } broadcastServiceStateChanged(state); } @@ -273,10 +302,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } synchronized (mRecords) { mSignalStrength = signalStrength; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); + for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) { - sendSignalStrength(r, signalStrength); + try { + r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength)); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } } if ((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) { try { @@ -284,10 +316,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1 : gsmSignalStrength)); } catch (RemoteException ex) { - remove(r.binder); + mRemoveList.add(r.binder); } } } + handleRemoveListLocked(); } broadcastSignalStrengthChanged(signalStrength); } @@ -296,19 +329,18 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (!checkNotifyPermission("notifyMessageWaitingChanged()")) { return; } - Slog.i(TAG, "notifyMessageWaitingChanged: " + mwi); synchronized (mRecords) { mMessageWaiting = mwi; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); + for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) { try { r.callback.onMessageWaitingIndicatorChanged(mwi); } catch (RemoteException ex) { - remove(r.binder); + mRemoveList.add(r.binder); } } } + handleRemoveListLocked(); } } @@ -316,19 +348,18 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (!checkNotifyPermission("notifyCallForwardingChanged()")) { return; } - Slog.i(TAG, "notifyCallForwardingChanged: " + cfi); synchronized (mRecords) { mCallForwarding = cfi; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); + for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) { try { r.callback.onCallForwardingIndicatorChanged(cfi); } catch (RemoteException ex) { - remove(r.binder); + mRemoveList.add(r.binder); } } } + handleRemoveListLocked(); } } @@ -338,58 +369,83 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } synchronized (mRecords) { mDataActivity = state; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); + for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_DATA_ACTIVITY) != 0) { try { r.callback.onDataActivity(state); } catch (RemoteException ex) { - remove(r.binder); + mRemoveList.add(r.binder); } } } + handleRemoveListLocked(); } } public void notifyDataConnection(int state, boolean isDataConnectivityPossible, - String reason, String apn, String[] apnTypes, String interfaceName, int networkType, - String gateway) { + String reason, String apn, String apnType, LinkProperties linkProperties, + LinkCapabilities linkCapabilities, int networkType) { if (!checkNotifyPermission("notifyDataConnection()" )) { return; } Slog.i(TAG, "notifyDataConnection: state=" + state + " isDataConnectivityPossible=" - + isDataConnectivityPossible + " reason=" + reason - + " interfaceName=" + interfaceName + " networkType=" + networkType); + + isDataConnectivityPossible + " reason='" + reason + + "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType); synchronized (mRecords) { - mDataConnectionState = state; + boolean modified = false; + if (state == TelephonyManager.DATA_CONNECTED) { + if (!mConnectedApns.contains(apnType)) { + mConnectedApns.add(apnType); + if (mDataConnectionState != state) { + mDataConnectionState = state; + modified = true; + } + } + } else { + if (mConnectedApns.remove(apnType)) { + if (mConnectedApns.isEmpty()) { + mDataConnectionState = state; + modified = true; + } else { + // leave mDataConnectionState as is and + // send out the new status for the APN in question. + } + } + } mDataConnectionPossible = isDataConnectivityPossible; mDataConnectionReason = reason; - mDataConnectionApn = apn; - mDataConnectionApnTypes = apnTypes; - mDataConnectionInterfaceName = interfaceName; - mDataConnectionNetworkType = networkType; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); - if ((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) { - try { - r.callback.onDataConnectionStateChanged(state, networkType); - } catch (RemoteException ex) { - remove(r.binder); + mDataConnectionLinkProperties = linkProperties; + mDataConnectionLinkCapabilities = linkCapabilities; + if (mDataConnectionNetworkType != networkType) { + mDataConnectionNetworkType = networkType; + // need to tell registered listeners about the new network type + modified = true; + } + if (modified) { + Slog.d(TAG, "onDataConnectionStateChanged(" + state + ", " + networkType + ")"); + for (Record r : mRecords) { + if ((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) { + try { + r.callback.onDataConnectionStateChanged(state, networkType); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } } } + handleRemoveListLocked(); } } broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn, - apnTypes, interfaceName, gateway); + apnType, linkProperties, linkCapabilities); } - public void notifyDataConnectionFailed(String reason) { + public void notifyDataConnectionFailed(String reason, String apnType) { if (!checkNotifyPermission("notifyDataConnectionFailed()")) { return; } /* - * This is commented out because there is on onDataConnectionFailed callback - * on PhoneStateListener. There should be + * This is commented out because there is no onDataConnectionFailed callback + * in PhoneStateListener. There should be. synchronized (mRecords) { mDataConnectionFailedReason = reason; final int N = mRecords.size(); @@ -401,7 +457,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } */ - broadcastDataConnectionFailed(reason); + broadcastDataConnectionFailed(reason, apnType); } public void notifyCellLocation(Bundle cellLocation) { @@ -410,39 +466,36 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } synchronized (mRecords) { mCellLocation = cellLocation; - for (int i = mRecords.size() - 1; i >= 0; i--) { - Record r = mRecords.get(i); + for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) { - sendCellLocation(r, cellLocation); + try { + r.callback.onCellLocationChanged(new Bundle(cellLocation)); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } } + handleRemoveListLocked(); } } - /** - * Copy the service state object so they can't mess it up in the local calls - */ - public void sendServiceState(Record r, ServiceState state) { - try { - r.callback.onServiceStateChanged(new ServiceState(state)); - } catch (RemoteException ex) { - remove(r.binder); - } - } - - private void sendCellLocation(Record r, Bundle cellLocation) { - try { - r.callback.onCellLocationChanged(new Bundle(cellLocation)); - } catch (RemoteException ex) { - remove(r.binder); + public void notifyOtaspChanged(int otaspMode) { + if (!checkNotifyPermission("notifyOtaspChanged()" )) { + return; } - } - - private void sendSignalStrength(Record r, SignalStrength signalStrength) { - try { - r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength)); - } catch (RemoteException ex) { - remove(r.binder); + synchronized (mRecords) { + mOtaspMode = otaspMode; + for (Record r : mRecords) { + if ((r.events & PhoneStateListener.LISTEN_OTASP_CHANGED) != 0) { + try { + r.callback.onOtaspChanged(otaspMode); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + handleRemoveListLocked(); } } @@ -468,11 +521,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println(" mDataConnectionPossible=" + mDataConnectionPossible); pw.println(" mDataConnectionReason=" + mDataConnectionReason); pw.println(" mDataConnectionApn=" + mDataConnectionApn); - pw.println(" mDataConnectionInterfaceName=" + mDataConnectionInterfaceName); + pw.println(" mDataConnectionLinkProperties=" + mDataConnectionLinkProperties); + pw.println(" mDataConnectionLinkCapabilities=" + mDataConnectionLinkCapabilities); pw.println(" mCellLocation=" + mCellLocation); pw.println("registrations: count=" + recordCount); - for (int i = 0; i < recordCount; i++) { - Record r = mRecords.get(i); + for (Record r : mRecords) { pw.println(" " + r.pkgForDebug + " 0x" + Integer.toHexString(r.events)); } } @@ -543,7 +596,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private void broadcastDataConnectionStateChanged(int state, boolean isDataConnectivityPossible, - String reason, String apn, String[] apnTypes, String interfaceName, String gateway) { + String reason, String apn, String apnType, LinkProperties linkProperties, + LinkCapabilities linkCapabilities) { // 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. @@ -556,29 +610,26 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (reason != null) { intent.putExtra(Phone.STATE_CHANGE_REASON_KEY, reason); } - intent.putExtra(Phone.DATA_APN_KEY, apn); - String types = new String(""); - if (apnTypes.length > 0) { - types = apnTypes[0]; - for (int i = 1; i < apnTypes.length; i++) { - types = types+","+apnTypes[i]; + if (linkProperties != null) { + intent.putExtra(Phone.DATA_LINK_PROPERTIES_KEY, linkProperties); + String iface = linkProperties.getInterfaceName(); + if (iface != null) { + intent.putExtra(Phone.DATA_IFACE_NAME_KEY, iface); } } - intent.putExtra(Phone.DATA_APN_TYPES_KEY, types); - intent.putExtra(Phone.DATA_IFACE_NAME_KEY, interfaceName); - int gatewayAddr = 0; - if (gateway != null) { - gatewayAddr = NetworkUtils.v4StringToInt(gateway); + if (linkCapabilities != null) { + intent.putExtra(Phone.DATA_LINK_CAPABILITIES_KEY, linkCapabilities); } - intent.putExtra(Phone.DATA_GATEWAY_KEY, gatewayAddr); - + intent.putExtra(Phone.DATA_APN_KEY, apn); + intent.putExtra(Phone.DATA_APN_TYPE_KEY, apnType); mContext.sendStickyBroadcast(intent); } - private void broadcastDataConnectionFailed(String reason) { + private void broadcastDataConnectionFailed(String reason, String apnType) { Intent intent = new Intent(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); intent.putExtra(Phone.FAILURE_REASON_KEY, reason); + intent.putExtra(Phone.DATA_APN_TYPE_KEY, apnType); mContext.sendStickyBroadcast(intent); } @@ -605,4 +656,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { android.Manifest.permission.READ_PHONE_STATE, null); } } + + private void handleRemoveListLocked() { + if (mRemoveList.size() > 0) { + for (IBinder b: mRemoveList) { + remove(b); + } + mRemoveList.clear(); + } + } } diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java index a93a6ee..d841cb3 100644 --- a/services/java/com/android/server/ThrottleService.java +++ b/services/java/com/android/server/ThrottleService.java @@ -60,6 +60,8 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.Calendar; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.GregorianCalendar; import java.util.Properties; import java.util.Random; @@ -83,8 +85,8 @@ public class ThrottleService extends IThrottleManager.Stub { private static final long TESTING_THRESHOLD = 1 * 1024 * 1024; private int mPolicyPollPeriodSec; - private long mPolicyThreshold; - private int mPolicyThrottleValue; + private AtomicLong mPolicyThreshold; + private AtomicInteger mPolicyThrottleValue; private int mPolicyResetDay; // 1-28 private int mPolicyNotificationsAllowedMask; @@ -114,7 +116,7 @@ public class ThrottleService extends IThrottleManager.Stub { private InterfaceObserver mInterfaceObserver; private SettingsObserver mSettingsObserver; - private int mThrottleIndex; // 0 for none, 1 for first throttle val, 2 for next, etc + private AtomicInteger mThrottleIndex; // 0 for none, 1 for first throttle val, 2 for next, etc private static final int THROTTLE_INDEX_UNINITIALIZED = -1; private static final int THROTTLE_INDEX_UNTHROTTLED = 0; @@ -126,6 +128,10 @@ public class ThrottleService extends IThrottleManager.Stub { if (VDBG) Slog.v(TAG, "Starting ThrottleService"); mContext = context; + mPolicyThreshold = new AtomicLong(); + mPolicyThrottleValue = new AtomicInteger(); + mThrottleIndex = new AtomicInteger(); + mNtpActive = false; mIface = mContext.getResources().getString(R.string.config_datause_iface); @@ -214,7 +220,7 @@ public class ThrottleService extends IThrottleManager.Stub { } private long ntpToWallTime(long ntpTime) { - long bestNow = getBestTime(); + long bestNow = getBestTime(true); // do it quickly long localNow = System.currentTimeMillis(); return localNow + (ntpTime - bestNow); } @@ -222,40 +228,42 @@ public class ThrottleService extends IThrottleManager.Stub { // TODO - fetch for the iface // return time in the local, system wall time, correcting for the use of ntp - public synchronized long getResetTime(String iface) { + public long getResetTime(String iface) { enforceAccessPermission(); long resetTime = 0; if (mRecorder != null) { - resetTime = ntpToWallTime(mRecorder.getPeriodEnd()); + resetTime = mRecorder.getPeriodEnd(); } + resetTime = ntpToWallTime(resetTime); return resetTime; } // TODO - fetch for the iface // return time in the local, system wall time, correcting for the use of ntp - public synchronized long getPeriodStartTime(String iface) { - enforceAccessPermission(); + public long getPeriodStartTime(String iface) { long startTime = 0; + enforceAccessPermission(); if (mRecorder != null) { - startTime = ntpToWallTime(mRecorder.getPeriodStart()); + startTime = mRecorder.getPeriodStart(); } + startTime = ntpToWallTime(startTime); return startTime; } //TODO - a better name? getCliffByteCountThreshold? // TODO - fetch for the iface - public synchronized long getCliffThreshold(String iface, int cliff) { + public long getCliffThreshold(String iface, int cliff) { enforceAccessPermission(); if (cliff == 1) { - return mPolicyThreshold; + return mPolicyThreshold.get(); } return 0; } // TODO - a better name? getThrottleRate? // TODO - fetch for the iface - public synchronized int getCliffLevel(String iface, int cliff) { + public int getCliffLevel(String iface, int cliff) { enforceAccessPermission(); if (cliff == 1) { - return mPolicyThrottleValue; + return mPolicyThrottleValue.get(); } return 0; } @@ -267,10 +275,9 @@ public class ThrottleService extends IThrottleManager.Stub { } // TODO - fetch for the iface - public synchronized long getByteCount(String iface, int dir, int period, int ago) { + public long getByteCount(String iface, int dir, int period, int ago) { enforceAccessPermission(); - if ((period == ThrottleManager.PERIOD_CYCLE) && - (mRecorder != null)) { + if ((period == ThrottleManager.PERIOD_CYCLE) && (mRecorder != null)) { if (dir == ThrottleManager.DIRECTION_TX) return mRecorder.getPeriodTx(ago); if (dir == ThrottleManager.DIRECTION_RX) return mRecorder.getPeriodRx(ago); } @@ -279,10 +286,10 @@ public class ThrottleService extends IThrottleManager.Stub { // TODO - a better name - getCurrentThrottleRate? // TODO - fetch for the iface - public synchronized int getThrottle(String iface) { + public int getThrottle(String iface) { enforceAccessPermission(); - if (mThrottleIndex == 1) { - return mPolicyThrottleValue; + if (mThrottleIndex.get() == 1) { + return mPolicyThrottleValue.get(); } return 0; } @@ -305,22 +312,6 @@ public class ThrottleService extends IThrottleManager.Stub { } }, new IntentFilter(ACTION_RESET)); - // use a new thread as we don't want to stall the system for file writes - mThread = new HandlerThread(TAG); - mThread.start(); - mHandler = new MyHandler(mThread.getLooper()); - mHandler.obtainMessage(EVENT_REBOOT_RECOVERY).sendToTarget(); - - mInterfaceObserver = new InterfaceObserver(mHandler, EVENT_IFACE_UP, mIface); - try { - mNMService.registerObserver(mInterfaceObserver); - } catch (RemoteException e) { - Slog.e(TAG, "Could not register InterfaceObserver " + e); - } - - mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED); - mSettingsObserver.observe(mContext); - FileInputStream stream = null; try { Properties properties = new Properties(); @@ -337,6 +328,22 @@ public class ThrottleService extends IThrottleManager.Stub { } catch (Exception e) {} } } + + // use a new thread as we don't want to stall the system for file writes + mThread = new HandlerThread(TAG); + mThread.start(); + mHandler = new MyHandler(mThread.getLooper()); + mHandler.obtainMessage(EVENT_REBOOT_RECOVERY).sendToTarget(); + + mInterfaceObserver = new InterfaceObserver(mHandler, EVENT_IFACE_UP, mIface); + try { + mNMService.registerObserver(mInterfaceObserver); + } catch (RemoteException e) { + Slog.e(TAG, "Could not register InterfaceObserver " + e); + } + + mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED); + mSettingsObserver.observe(mContext); } @@ -375,7 +382,7 @@ public class ThrottleService extends IThrottleManager.Stub { // check for sim change TODO // reregister for notification of policy change - mThrottleIndex = THROTTLE_INDEX_UNINITIALIZED; + mThrottleIndex.set(THROTTLE_INDEX_UNINITIALIZED); mRecorder = new DataRecorder(mContext, ThrottleService.this); @@ -403,15 +410,16 @@ public class ThrottleService extends IThrottleManager.Stub { R.integer.config_datause_threshold_bytes); int defaultValue = mContext.getResources().getInteger( R.integer.config_datause_throttle_kbitsps); - synchronized (ThrottleService.this) { - mPolicyThreshold = Settings.Secure.getLong(mContext.getContentResolver(), - Settings.Secure.THROTTLE_THRESHOLD_BYTES, defaultThreshold); - mPolicyThrottleValue = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.THROTTLE_VALUE_KBITSPS, defaultValue); - if (testing) { - mPolicyPollPeriodSec = TESTING_POLLING_PERIOD_SEC; - mPolicyThreshold = TESTING_THRESHOLD; - } + long threshold = Settings.Secure.getLong(mContext.getContentResolver(), + Settings.Secure.THROTTLE_THRESHOLD_BYTES, defaultThreshold); + int value = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.THROTTLE_VALUE_KBITSPS, defaultValue); + + mPolicyThreshold.set(threshold); + mPolicyThrottleValue.set(value); + if (testing) { + mPolicyPollPeriodSec = TESTING_POLLING_PERIOD_SEC; + mPolicyThreshold.set(TESTING_THRESHOLD); } mPolicyResetDay = Settings.Secure.getInt(mContext.getContentResolver(), @@ -423,10 +431,8 @@ public class ThrottleService extends IThrottleManager.Stub { Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.THROTTLE_RESET_DAY, mPolicyResetDay); } - synchronized (ThrottleService.this) { - if (mIface == null) { - mPolicyThreshold = 0; - } + if (mIface == null) { + mPolicyThreshold.set(0); } int defaultNotificationType = mContext.getResources().getInteger( @@ -437,15 +443,16 @@ public class ThrottleService extends IThrottleManager.Stub { mMaxNtpCacheAgeSec = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC, MAX_NTP_CACHE_AGE_SEC); - if (VDBG || (mPolicyThreshold != 0)) { + if (VDBG || (mPolicyThreshold.get() != 0)) { Slog.d(TAG, "onPolicyChanged testing=" + testing +", period=" + - mPolicyPollPeriodSec + ", threshold=" + mPolicyThreshold + ", value=" + - mPolicyThrottleValue + ", resetDay=" + mPolicyResetDay + ", noteType=" + - mPolicyNotificationsAllowedMask + ", maxNtpCacheAge=" + mMaxNtpCacheAgeSec); + mPolicyPollPeriodSec + ", threshold=" + mPolicyThreshold.get() + + ", value=" + mPolicyThrottleValue.get() + ", resetDay=" + mPolicyResetDay + + ", noteType=" + mPolicyNotificationsAllowedMask + ", maxNtpCacheAge=" + + mMaxNtpCacheAgeSec); } // force updates - mThrottleIndex = THROTTLE_INDEX_UNINITIALIZED; + mThrottleIndex.set(THROTTLE_INDEX_UNINITIALIZED); onResetAlarm(); @@ -487,7 +494,7 @@ public class ThrottleService extends IThrottleManager.Stub { long periodRx = mRecorder.getPeriodRx(0); long periodTx = mRecorder.getPeriodTx(0); long total = periodRx + periodTx; - if (VDBG || (mPolicyThreshold != 0)) { + if (VDBG || (mPolicyThreshold.get() != 0)) { Slog.d(TAG, "onPollAlarm - roaming =" + roaming + ", read =" + incRead + ", written =" + incWrite + ", new total =" + total); } @@ -510,11 +517,11 @@ public class ThrottleService extends IThrottleManager.Stub { private void onIfaceUp() { // if we were throttled before, be sure and set it again - the iface went down // (and may have disappeared all together) and these settings were lost - if (mThrottleIndex == 1) { + if (mThrottleIndex.get() == 1) { try { mNMService.setInterfaceThrottle(mIface, -1, -1); mNMService.setInterfaceThrottle(mIface, - mPolicyThrottleValue, mPolicyThrottleValue); + mPolicyThrottleValue.get(), mPolicyThrottleValue.get()); } catch (Exception e) { Slog.e(TAG, "error setting Throttle: " + e); } @@ -523,7 +530,8 @@ public class ThrottleService extends IThrottleManager.Stub { private void checkThrottleAndPostNotification(long currentTotal) { // is throttling enabled? - if (mPolicyThreshold == 0) { + long threshold = mPolicyThreshold.get(); + if (threshold == 0) { clearThrottleAndNotification(); return; } @@ -535,15 +543,13 @@ public class ThrottleService extends IThrottleManager.Stub { } // check if we need to throttle - if (currentTotal > mPolicyThreshold) { - if (mThrottleIndex != 1) { - synchronized (ThrottleService.this) { - mThrottleIndex = 1; - } - if (DBG) Slog.d(TAG, "Threshold " + mPolicyThreshold + " exceeded!"); + if (currentTotal > threshold) { + if (mThrottleIndex.get() != 1) { + mThrottleIndex.set(1); + if (DBG) Slog.d(TAG, "Threshold " + threshold + " exceeded!"); try { mNMService.setInterfaceThrottle(mIface, - mPolicyThrottleValue, mPolicyThrottleValue); + mPolicyThrottleValue.get(), mPolicyThrottleValue.get()); } catch (Exception e) { Slog.e(TAG, "error setting Throttle: " + e); } @@ -556,7 +562,8 @@ public class ThrottleService extends IThrottleManager.Stub { Notification.FLAG_ONGOING_EVENT); Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION); - broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, mPolicyThrottleValue); + broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, + mPolicyThrottleValue.get()); mContext.sendStickyBroadcast(broadcast); } // else already up! @@ -579,8 +586,8 @@ public class ThrottleService extends IThrottleManager.Stub { long periodLength = end - start; long now = System.currentTimeMillis(); long timeUsed = now - start; - long warningThreshold = 2*mPolicyThreshold*timeUsed/(timeUsed+periodLength); - if ((currentTotal > warningThreshold) && (currentTotal > mPolicyThreshold/4)) { + long warningThreshold = 2*threshold*timeUsed/(timeUsed+periodLength); + if ((currentTotal > warningThreshold) && (currentTotal > threshold/4)) { if (mWarningNotificationSent == false) { mWarningNotificationSent = true; mNotificationManager.cancel(R.drawable.stat_sys_throttled); @@ -625,11 +632,9 @@ public class ThrottleService extends IThrottleManager.Stub { } - private synchronized void clearThrottleAndNotification() { - if (mThrottleIndex != THROTTLE_INDEX_UNTHROTTLED) { - synchronized (ThrottleService.this) { - mThrottleIndex = THROTTLE_INDEX_UNTHROTTLED; - } + private void clearThrottleAndNotification() { + if (mThrottleIndex.get() != THROTTLE_INDEX_UNTHROTTLED) { + mThrottleIndex.set(THROTTLE_INDEX_UNTHROTTLED); try { mNMService.setInterfaceThrottle(mIface, -1, -1); } catch (Exception e) { @@ -687,12 +692,12 @@ public class ThrottleService extends IThrottleManager.Stub { } private void onResetAlarm() { - if (VDBG || (mPolicyThreshold != 0)) { + if (VDBG || (mPolicyThreshold.get() != 0)) { Slog.d(TAG, "onResetAlarm - last period had " + mRecorder.getPeriodRx(0) + " bytes read and " + mRecorder.getPeriodTx(0) + " written"); } - long now = getBestTime(); + long now = getBestTime(false); if (mNtpActive || (mNtpServer == null)) { Calendar end = calculatePeriodEnd(now); @@ -719,20 +724,23 @@ public class ThrottleService extends IThrottleManager.Stub { // will try to get the ntp time and switch to it if found. // will also cache the time so we don't fetch it repeatedly. - getBestTime(); + getBestTime(false); } private static final int MAX_NTP_CACHE_AGE_SEC = 60 * 60 * 24; // 1 day - private static final int MAX_NTP_FETCH_WAIT = 10 * 1000; + private static final int MAX_NTP_FETCH_WAIT = 20 * 1000; private int mMaxNtpCacheAgeSec = MAX_NTP_CACHE_AGE_SEC; private long cachedNtp; private long cachedNtpTimestamp; - private long getBestTime() { + // if the request is tied to UI and ANR's are a danger, request a fast result + // the regular polling should have updated the cached time recently using the + // slower method (!fast) + private long getBestTime(boolean fast) { if (mNtpServer != null) { if (mNtpActive) { long ntpAge = SystemClock.elapsedRealtime() - cachedNtpTimestamp; - if (ntpAge < mMaxNtpCacheAgeSec * 1000) { + if (ntpAge < mMaxNtpCacheAgeSec * 1000 || fast) { if (VDBG) Slog.v(TAG, "using cached time"); return cachedNtp + ntpAge; } @@ -1025,39 +1033,57 @@ public class ThrottleService extends IThrottleManager.Stub { if (DBG) Slog.d(TAG, "data file empty"); return; } - synchronized (mParent) { - String[] parsed = data.split(":"); - int parsedUsed = 0; - if (parsed.length < 6) { - Slog.e(TAG, "reading data file with insufficient length - ignoring"); - return; - } + String[] parsed = data.split(":"); + int parsedUsed = 0; + if (parsed.length < 6) { + Slog.e(TAG, "reading data file with insufficient length - ignoring"); + return; + } + int periodCount; + long[] periodRxData; + long[] periodTxData; + int currentPeriod; + Calendar periodStart; + Calendar periodEnd; + try { if (Integer.parseInt(parsed[parsedUsed++]) != DATA_FILE_VERSION) { Slog.e(TAG, "reading data file with bad version - ignoring"); return; } - mPeriodCount = Integer.parseInt(parsed[parsedUsed++]); - if (parsed.length != 5 + (2 * mPeriodCount)) { + periodCount = Integer.parseInt(parsed[parsedUsed++]); + if (parsed.length != 5 + (2 * periodCount)) { Slog.e(TAG, "reading data file with bad length (" + parsed.length + - " != " + (5+(2*mPeriodCount)) + ") - ignoring"); + " != " + (5 + (2 * periodCount)) + ") - ignoring"); return; } - - mPeriodRxData = new long[mPeriodCount]; - for(int i = 0; i < mPeriodCount; i++) { - mPeriodRxData[i] = Long.parseLong(parsed[parsedUsed++]); + periodRxData = new long[periodCount]; + for (int i = 0; i < periodCount; i++) { + periodRxData[i] = Long.parseLong(parsed[parsedUsed++]); } - mPeriodTxData = new long[mPeriodCount]; - for(int i = 0; i < mPeriodCount; i++) { - mPeriodTxData[i] = Long.parseLong(parsed[parsedUsed++]); + periodTxData = new long[periodCount]; + for (int i = 0; i < periodCount; i++) { + periodTxData[i] = Long.parseLong(parsed[parsedUsed++]); } - mCurrentPeriod = Integer.parseInt(parsed[parsedUsed++]); - mPeriodStart = new GregorianCalendar(); - mPeriodStart.setTimeInMillis(Long.parseLong(parsed[parsedUsed++])); - mPeriodEnd = new GregorianCalendar(); - mPeriodEnd.setTimeInMillis(Long.parseLong(parsed[parsedUsed++])); + + currentPeriod = Integer.parseInt(parsed[parsedUsed++]); + + periodStart = new GregorianCalendar(); + periodStart.setTimeInMillis(Long.parseLong(parsed[parsedUsed++])); + periodEnd = new GregorianCalendar(); + periodEnd.setTimeInMillis(Long.parseLong(parsed[parsedUsed++])); + } catch (Exception e) { + Slog.e(TAG, "Error parsing data file - ignoring"); + return; + } + synchronized (mParent) { + mPeriodCount = periodCount; + mPeriodRxData = periodRxData; + mPeriodTxData = periodTxData; + mCurrentPeriod = currentPeriod; + mPeriodStart = periodStart; + mPeriodEnd = periodEnd; } } @@ -1091,15 +1117,15 @@ public class ThrottleService extends IThrottleManager.Stub { } pw.println(); - pw.println("The threshold is " + mPolicyThreshold + + pw.println("The threshold is " + mPolicyThreshold.get() + ", after which you experince throttling to " + - mPolicyThrottleValue + "kbps"); + mPolicyThrottleValue.get() + "kbps"); pw.println("Current period is " + (mRecorder.getPeriodEnd() - mRecorder.getPeriodStart())/1000 + " seconds long " + "and ends in " + (getResetTime(mIface) - System.currentTimeMillis()) / 1000 + " seconds."); pw.println("Polling every " + mPolicyPollPeriodSec + " seconds"); - pw.println("Current Throttle Index is " + mThrottleIndex); + pw.println("Current Throttle Index is " + mThrottleIndex.get()); pw.println("Max NTP Cache Age is " + mMaxNtpCacheAgeSec); for (int i = 0; i < mRecorder.getPeriodCount(); i++) { diff --git a/services/java/com/android/server/UsbObserver.java b/services/java/com/android/server/UsbObserver.java deleted file mode 100644 index d08fe9b..0000000 --- a/services/java/com/android/server/UsbObserver.java +++ /dev/null @@ -1,207 +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; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.hardware.Usb; -import android.net.Uri; -import android.os.Handler; -import android.os.Message; -import android.os.UEventObserver; -import android.provider.Settings; -import android.util.Log; -import android.util.Slog; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.util.ArrayList; - -/** - * <p>UsbObserver monitors for changes to USB state. - */ -class UsbObserver extends UEventObserver { - private static final String TAG = UsbObserver.class.getSimpleName(); - private static final boolean LOG = false; - - private static final String USB_CONFIGURATION_MATCH = "DEVPATH=/devices/virtual/switch/usb_configuration"; - private static final String USB_FUNCTIONS_MATCH = "DEVPATH=/devices/virtual/usb_composite/"; - private static final String USB_CONFIGURATION_PATH = "/sys/class/switch/usb_configuration/state"; - private static final String USB_COMPOSITE_CLASS_PATH = "/sys/class/usb_composite"; - - private static final int MSG_UPDATE = 0; - - private int mUsbConfig = 0; - private int mPreviousUsbConfig = 0; - - // lists of enabled and disabled USB functions - private final ArrayList<String> mEnabledFunctions = new ArrayList<String>(); - private final ArrayList<String> mDisabledFunctions = new ArrayList<String>(); - - private boolean mSystemReady; - - private final Context mContext; - - private PowerManagerService mPowerManager; - - public UsbObserver(Context context) { - mContext = context; - init(); // set initial status - - startObserving(USB_CONFIGURATION_MATCH); - startObserving(USB_FUNCTIONS_MATCH); - } - - @Override - public void onUEvent(UEventObserver.UEvent event) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.v(TAG, "USB UEVENT: " + event.toString()); - } - - synchronized (this) { - String switchState = event.get("SWITCH_STATE"); - if (switchState != null) { - try { - int newConfig = Integer.parseInt(switchState); - if (newConfig != mUsbConfig) { - mPreviousUsbConfig = mUsbConfig; - mUsbConfig = newConfig; - // trigger an Intent broadcast - if (mSystemReady) { - update(); - } - } - } catch (NumberFormatException e) { - Slog.e(TAG, "Could not parse switch state from event " + event); - } - } else { - String function = event.get("FUNCTION"); - String enabledStr = event.get("ENABLED"); - if (function != null && enabledStr != null) { - // Note: we do not broadcast a change when a function is enabled or disabled. - // We just record the state change for the next broadcast. - boolean enabled = "1".equals(enabledStr); - if (enabled) { - if (!mEnabledFunctions.contains(function)) { - mEnabledFunctions.add(function); - } - mDisabledFunctions.remove(function); - } else { - if (!mDisabledFunctions.contains(function)) { - mDisabledFunctions.add(function); - } - mEnabledFunctions.remove(function); - } - } - } - } - } - private final void init() { - char[] buffer = new char[1024]; - - try { - FileReader file = new FileReader(USB_CONFIGURATION_PATH); - int len = file.read(buffer, 0, 1024); - mPreviousUsbConfig = mUsbConfig = Integer.valueOf((new String(buffer, 0, len)).trim()); - - } catch (FileNotFoundException e) { - Slog.w(TAG, "This kernel does not have USB configuration switch support"); - } catch (Exception e) { - Slog.e(TAG, "" , e); - } - - try { - File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles(); - for (int i = 0; i < files.length; i++) { - File file = new File(files[i], "enable"); - FileReader reader = new FileReader(file); - int len = reader.read(buffer, 0, 1024); - int value = Integer.valueOf((new String(buffer, 0, len)).trim()); - String functionName = files[i].getName(); - if (value == 1) { - mEnabledFunctions.add(functionName); - } else { - mDisabledFunctions.add(functionName); - } - } - } catch (FileNotFoundException e) { - Slog.w(TAG, "This kernel does not have USB composite class support"); - } catch (Exception e) { - Slog.e(TAG, "" , e); - } - } - - void systemReady() { - synchronized (this) { - update(); - mSystemReady = true; - } - } - - private final void update() { - mHandler.sendEmptyMessage(MSG_UPDATE); - } - - private final Handler mHandler = new Handler() { - private void addEnabledFunctions(Intent intent) { - // include state of all USB functions in our extras - for (int i = 0; i < mEnabledFunctions.size(); i++) { - intent.putExtra(mEnabledFunctions.get(i), Usb.USB_FUNCTION_ENABLED); - } - for (int i = 0; i < mDisabledFunctions.size(); i++) { - intent.putExtra(mDisabledFunctions.get(i), Usb.USB_FUNCTION_DISABLED); - } - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE: - synchronized (this) { - final ContentResolver cr = mContext.getContentResolver(); - - if (Settings.Secure.getInt(cr, - Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { - Slog.i(TAG, "Device not provisioned, skipping USB broadcast"); - return; - } - // Send an Intent containing connected/disconnected state - // and the enabled/disabled state of all USB functions - Intent intent; - boolean usbConnected = (mUsbConfig != 0); - if (usbConnected) { - intent = new Intent(Usb.ACTION_USB_CONNECTED); - addEnabledFunctions(intent); - } else { - intent = new Intent(Usb.ACTION_USB_DISCONNECTED); - } - mContext.sendBroadcast(intent); - - // send a sticky broadcast for clients interested in both connect and disconnect - intent = new Intent(Usb.ACTION_USB_STATE); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra(Usb.USB_CONNECTED, usbConnected); - addEnabledFunctions(intent); - mContext.sendStickyBroadcast(intent); - } - break; - } - } - }; -} diff --git a/services/java/com/android/server/UsbService.java b/services/java/com/android/server/UsbService.java new file mode 100644 index 0000000..8ef03d4 --- /dev/null +++ b/services/java/com/android/server/UsbService.java @@ -0,0 +1,252 @@ +/* + * 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; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.hardware.UsbManager; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.os.UEventObserver; +import android.provider.Settings; +import android.util.Log; +import android.util.Slog; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.util.ArrayList; + +/** + * <p>UsbService monitors for changes to USB state. + */ +class UsbService { + private static final String TAG = UsbService.class.getSimpleName(); + private static final boolean LOG = false; + + private static final String USB_CONNECTED_MATCH = + "DEVPATH=/devices/virtual/switch/usb_connected"; + private static final String USB_CONFIGURATION_MATCH = + "DEVPATH=/devices/virtual/switch/usb_configuration"; + private static final String USB_FUNCTIONS_MATCH = + "DEVPATH=/devices/virtual/usb_composite/"; + private static final String USB_CONNECTED_PATH = + "/sys/class/switch/usb_connected/state"; + private static final String USB_CONFIGURATION_PATH = + "/sys/class/switch/usb_configuration/state"; + private static final String USB_COMPOSITE_CLASS_PATH = + "/sys/class/usb_composite"; + + private static final int MSG_UPDATE = 0; + + // Delay for debouncing USB disconnects. + // We often get rapid connect/disconnect events when enabling USB functions, + // which need debouncing. + private static final int UPDATE_DELAY = 1000; + + // current connected and configuration state + private int mConnected; + private int mConfiguration; + + // last broadcasted connected and configuration state + private int mLastConnected = -1; + private int mLastConfiguration = -1; + + // lists of enabled and disabled USB functions + private final ArrayList<String> mEnabledFunctions = new ArrayList<String>(); + private final ArrayList<String> mDisabledFunctions = new ArrayList<String>(); + + private boolean mSystemReady; + + private final Context mContext; + + private final UEventObserver mUEventObserver = new UEventObserver() { + @Override + public void onUEvent(UEventObserver.UEvent event) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Slog.v(TAG, "USB UEVENT: " + event.toString()); + } + + synchronized (this) { + String name = event.get("SWITCH_NAME"); + String state = event.get("SWITCH_STATE"); + if (name != null && state != null) { + try { + int intState = Integer.parseInt(state); + if ("usb_connected".equals(name)) { + mConnected = intState; + // trigger an Intent broadcast + if (mSystemReady) { + // debounce disconnects + update(mConnected == 0); + } + } else if ("usb_configuration".equals(name)) { + mConfiguration = intState; + // trigger an Intent broadcast + if (mSystemReady) { + update(mConnected == 0); + } + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Could not parse switch state from event " + event); + } + } else { + String function = event.get("FUNCTION"); + String enabledStr = event.get("ENABLED"); + if (function != null && enabledStr != null) { + // Note: we do not broadcast a change when a function is enabled or disabled. + // We just record the state change for the next broadcast. + boolean enabled = "1".equals(enabledStr); + if (enabled) { + if (!mEnabledFunctions.contains(function)) { + mEnabledFunctions.add(function); + } + mDisabledFunctions.remove(function); + } else { + if (!mDisabledFunctions.contains(function)) { + mDisabledFunctions.add(function); + } + mEnabledFunctions.remove(function); + } + } + } + } + } + }; + + public UsbService(Context context) { + mContext = context; + init(); // set initial status + + if (mConfiguration >= 0) { + mUEventObserver.startObserving(USB_CONNECTED_MATCH); + mUEventObserver.startObserving(USB_CONFIGURATION_MATCH); + mUEventObserver.startObserving(USB_FUNCTIONS_MATCH); + } + } + + private final void init() { + char[] buffer = new char[1024]; + + mConfiguration = -1; + try { + FileReader file = new FileReader(USB_CONNECTED_PATH); + int len = file.read(buffer, 0, 1024); + file.close(); + mConnected = Integer.valueOf((new String(buffer, 0, len)).trim()); + + file = new FileReader(USB_CONFIGURATION_PATH); + len = file.read(buffer, 0, 1024); + file.close(); + mConfiguration = Integer.valueOf((new String(buffer, 0, len)).trim()); + + } catch (FileNotFoundException e) { + Slog.i(TAG, "This kernel does not have USB configuration switch support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + if (mConfiguration < 0) + return; + + try { + File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles(); + for (int i = 0; i < files.length; i++) { + File file = new File(files[i], "enable"); + FileReader reader = new FileReader(file); + int len = reader.read(buffer, 0, 1024); + reader.close(); + int value = Integer.valueOf((new String(buffer, 0, len)).trim()); + String functionName = files[i].getName(); + if (value == 1) { + mEnabledFunctions.add(functionName); + } else { + mDisabledFunctions.add(functionName); + } + } + } catch (FileNotFoundException e) { + Slog.w(TAG, "This kernel does not have USB composite class support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + } + + private void initHostSupport() { + // temporarily disabled + } + + void systemReady() { + synchronized (this) { + if (mContext.getResources().getBoolean( + com.android.internal.R.bool.config_hasUsbHostSupport)) { + // start monitoring for connected USB devices + initHostSupport(); + } + + update(false); + mSystemReady = true; + } + } + + private final void update(boolean delayed) { + mHandler.removeMessages(MSG_UPDATE); + mHandler.sendEmptyMessageDelayed(MSG_UPDATE, delayed ? UPDATE_DELAY : 0); + } + + private final Handler mHandler = new Handler() { + private void addEnabledFunctions(Intent intent) { + // include state of all USB functions in our extras + for (int i = 0; i < mEnabledFunctions.size(); i++) { + intent.putExtra(mEnabledFunctions.get(i), UsbManager.USB_FUNCTION_ENABLED); + } + for (int i = 0; i < mDisabledFunctions.size(); i++) { + intent.putExtra(mDisabledFunctions.get(i), UsbManager.USB_FUNCTION_DISABLED); + } + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE: + synchronized (this) { + if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) { + + final ContentResolver cr = mContext.getContentResolver(); + if (Settings.Secure.getInt(cr, + Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { + Slog.i(TAG, "Device not provisioned, skipping USB broadcast"); + return; + } + + mLastConnected = mConnected; + mLastConfiguration = mConfiguration; + + // send a sticky broadcast containing current USB state + Intent intent = new Intent(UsbManager.ACTION_USB_STATE); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra(UsbManager.USB_CONNECTED, mConnected != 0); + intent.putExtra(UsbManager.USB_CONFIGURATION, mConfiguration); + addEnabledFunctions(intent); + mContext.sendStickyBroadcast(intent); + } + } + break; + } + } + }; +} diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java index f0b5955..2fcdb5d 100755 --- a/services/java/com/android/server/VibratorService.java +++ b/services/java/com/android/server/VibratorService.java @@ -112,6 +112,10 @@ public class VibratorService extends IVibratorService.Stub { context.registerReceiver(mIntentReceiver, filter); } + public boolean hasVibrator() { + return vibratorExists(); + } + public void vibrate(long milliseconds, IBinder token) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) { @@ -380,6 +384,7 @@ public class VibratorService extends IVibratorService.Stub { volatile VibrateThread mThread; + native static boolean vibratorExists(); native static void vibratorOn(long milliseconds); native static void vibratorOff(); } diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java index 859c85c..997e750 100644 --- a/services/java/com/android/server/WallpaperManagerService.java +++ b/services/java/com/android/server/WallpaperManagerService.java @@ -587,6 +587,8 @@ class WallpaperManagerService extends IWallpaperManager.Stub { mIWindowManager.removeWindowToken(mWallpaperConnection.mToken); } catch (RemoteException e) { } + mWallpaperConnection.mService = null; + mWallpaperConnection.mEngine = null; mWallpaperConnection = null; } } diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 5aa0111..cf07239 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * 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. @@ -16,92 +16,76 @@ package com.android.server; -import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED; -import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING; -import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; -import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING; -import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN; - -import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED; -import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING; -import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; -import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING; -import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED; - import android.app.AlarmManager; +import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.database.ContentObserver; import android.net.wifi.IWifiManager; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.net.wifi.WifiNative; -import android.net.wifi.WifiStateTracker; +import android.net.wifi.WifiStateMachine; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.SupplicantState; import android.net.wifi.WifiConfiguration.KeyMgmt; +import android.net.wifi.WpsConfiguration; +import android.net.wifi.WpsResult; import android.net.ConnectivityManager; import android.net.InterfaceConfiguration; -import android.net.NetworkStateTracker; import android.net.DhcpInfo; -import android.net.NetworkUtils; +import android.net.NetworkInfo; +import android.net.NetworkInfo.State; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.INetworkManagementService; -import android.os.Looper; import android.os.Message; -import android.os.PowerManager; -import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.WorkSource; import android.provider.Settings; -import android.util.Slog; import android.text.TextUtils; +import android.util.Slog; +import java.net.InetAddress; import java.util.ArrayList; -import java.util.BitSet; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.regex.Pattern; +import java.util.concurrent.atomic.AtomicBoolean; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.net.UnknownHostException; import com.android.internal.app.IBatteryStats; -import android.app.backup.IBackupManager; +import com.android.internal.util.AsyncChannel; import com.android.server.am.BatteryStatsService; import com.android.internal.R; /** * WifiService handles remote WiFi operation requests by implementing - * the IWifiManager interface. It also creates a WifiMonitor to listen - * for Wifi-related events. + * the IWifiManager interface. * * @hide */ +//TODO: Clean up multiple locks and implement WifiService +// as a SM to track soft AP/client/adhoc bring up based +// on device idle state, airplane mode and boot. + public class WifiService extends IWifiManager.Stub { private static final String TAG = "WifiService"; - private static final boolean DBG = false; - private static final Pattern scanResultPattern = Pattern.compile("\t+"); - private final WifiStateTracker mWifiStateTracker; - /* TODO: fetch a configurable interface */ - private static final String SOFTAP_IFACE = "wl0.1"; + private static final boolean DBG = true; + + private final WifiStateMachine mWifiStateMachine; private Context mContext; - private int mWifiApState; private AlarmManager mAlarmManager; private PendingIntent mIdleIntent; @@ -110,10 +94,8 @@ public class WifiService extends IWifiManager.Stub { private boolean mDeviceIdle; private int mPluggedType; - private enum DriverAction {DRIVER_UNLOAD, NO_DRIVER_UNLOAD}; - // true if the user enabled Wifi while in airplane mode - private boolean mAirplaneModeOverwridden; + private AtomicBoolean mAirplaneModeOverwridden = new AtomicBoolean(false); private final LockList mLocks = new LockList(); // some wifi lock statistics @@ -131,9 +113,7 @@ public class WifiService extends IWifiManager.Stub { private final IBatteryStats mBatteryStats; - private INetworkManagementService nwService; ConnectivityManager mCm; - private WifiWatchdogService mWifiWatchdogService = null; private String[] mWifiRegexs; /** @@ -143,122 +123,130 @@ public class WifiService extends IWifiManager.Stub { * 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_MILLIS = 15 * 60 * 1000; /* 15 minutes */ + private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */ + + private static final String ACTION_DEVICE_IDLE = + "com.android.server.WifiManager.action.DEVICE_IDLE"; + + private boolean mIsReceiverRegistered = false; + - private static final String WAKELOCK_TAG = "*wifi*"; + NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", ""); + // Variables relating to the 'available networks' notification /** - * The maximum amount of time to hold the wake lock after a disconnect - * caused by stopping the driver. Establishing an EDGE connection has been - * observed to take about 5 seconds under normal circumstances. This - * provides a bit of extra margin. - * <p> - * See {@link android.provider.Settings.Secure#WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS}. - * This is the default value if a Settings.Secure value is not present. + * 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 DEFAULT_WAKELOCK_TIMEOUT = 8000; - - // Wake lock used by driver-stop operation - private static PowerManager.WakeLock sDriverStopWakeLock; - // Wake lock used by other operations - private static PowerManager.WakeLock sWakeLock; - - private static final int MESSAGE_ENABLE_WIFI = 0; - private static final int MESSAGE_DISABLE_WIFI = 1; - private static final int MESSAGE_STOP_WIFI = 2; - private static final int MESSAGE_START_WIFI = 3; - private static final int MESSAGE_RELEASE_WAKELOCK = 4; - private static final int MESSAGE_UPDATE_STATE = 5; - private static final int MESSAGE_START_ACCESS_POINT = 6; - private static final int MESSAGE_STOP_ACCESS_POINT = 7; - private static final int MESSAGE_SET_CHANNELS = 8; - private static final int MESSAGE_ENABLE_NETWORKS = 9; - private static final int MESSAGE_START_SCAN = 10; - private static final int MESSAGE_REPORT_WORKSOURCE = 11; - private static final int MESSAGE_ENABLE_RSSI_POLLING = 12; - - - private final WifiHandler mWifiHandler; - - /* - * Cache of scan results objects (size is somewhat arbitrary) + 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 static final int SCAN_RESULT_CACHE_SIZE = 80; - private final LinkedHashMap<String, ScanResult> mScanResultCache; - - /* - * Character buffer used to parse scan results (optimization) + private final long NOTIFICATION_REPEAT_DELAY_MS; + /** + * Whether the user has set the setting to show the 'available networks' notification. */ - private static final int SCAN_RESULT_BUFFER_SIZE = 512; - private boolean mNeedReconfig; - + private boolean mNotificationEnabled; /** - * Temporary for computing UIDS that are responsible for starting WIFI. - * Protected by mWifiStateTracker lock. + * Observes the user setting to keep {@link #mNotificationEnabled} in sync. */ - private final WorkSource mTmpWorkSource = new WorkSource(); - - /* - * Last UID that asked to enable WIFI. + private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver; + /** + * The {@link System#currentTimeMillis()} must be at least this value for us + * to show the notification again. */ - private int mLastEnableUid = Process.myUid(); - - /* - * Last UID that asked to enable WIFI AP. + private long mNotificationRepeatTime; + /** + * The Notification object given to the NotificationManager. */ - private int mLastApEnableUid = Process.myUid(); - - + 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; /** - * Number of allowed radio frequency channels in various regulatory domains. - * This list is sufficient for 802.11b/g networks (2.4GHz range). + * 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 static int[] sValidRegulatoryChannelCounts = new int[] {11, 13, 14}; + private int mNumScansSinceNetworkStateChange; - private static final String ACTION_DEVICE_IDLE = - "com.android.server.WifiManager.action.DEVICE_IDLE"; + /** + * Asynchronous channel to WifiStateMachine + */ + private AsyncChannel mChannel; - WifiService(Context context, WifiStateTracker tracker) { - mContext = context; - mWifiStateTracker = tracker; - mWifiStateTracker.enableRssiPolling(true); - mBatteryStats = BatteryStatsService.getService(); + /** + * TODO: Possibly change WifiService into an AsyncService. + */ + private class WifiServiceHandler extends Handler { + private AsyncChannel mWshChannel; - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - nwService = INetworkManagementService.Stub.asInterface(b); + WifiServiceHandler(android.os.Looper looper, Context context) { + super(looper); + mWshChannel = new AsyncChannel(); + mWshChannel.connect(context, this, mWifiStateMachine.getHandler()); + } - mScanResultCache = new LinkedHashMap<String, ScanResult>( - SCAN_RESULT_CACHE_SIZE, 0.75f, true) { - /* - * Limit the cache size by SCAN_RESULT_CACHE_SIZE - * elements - */ - public boolean removeEldestEntry(Map.Entry eldest) { - return SCAN_RESULT_CACHE_SIZE < this.size(); + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: { + if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { + mChannel = mWshChannel; + } else { + Slog.d(TAG, "WifiServicehandler.handleMessage could not connect error=" + + msg.arg1); + mChannel = null; + } + break; } - }; + default: { + Slog.d(TAG, "WifiServicehandler.handleMessage ignoring msg=" + msg); + break; + } + } + } + } + WifiServiceHandler mHandler; - HandlerThread wifiThread = new HandlerThread("WifiService"); - wifiThread.start(); - mWifiHandler = new WifiHandler(wifiThread.getLooper()); + /** + * Temporary for computing UIDS that are responsible for starting WIFI. + * Protected by mWifiStateTracker lock. + */ + private final WorkSource mTmpWorkSource = new WorkSource(); - mWifiStateTracker.setWifiState(WIFI_STATE_DISABLED); - mWifiApState = WIFI_AP_STATE_DISABLED; + WifiService(Context context) { + mContext = context; + mWifiStateMachine = new WifiStateMachine(mContext); + mWifiStateMachine.enableRssiPolling(true); + mBatteryStats = BatteryStatsService.getService(); mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null); mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0); - PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); - sWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG); - sDriverStopWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG); + HandlerThread wifiThread = new HandlerThread("WifiService"); + wifiThread.start(); + mHandler = new WifiServiceHandler(wifiThread.getLooper(), context); mContext.registerReceiver( new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // clear our flag indicating the user has overwridden airplane mode - mAirplaneModeOverwridden = false; + mAirplaneModeOverwridden.set(false); // on airplane disable, restore Wifi if the saved state indicates so if (!isAirplaneModeOn() && testAndClearWifiSavedState()) { persistWifiEnabled(true); @@ -273,14 +261,50 @@ public class WifiService extends IWifiManager.Stub { @Override public void onReceive(Context context, Intent intent) { - ArrayList<String> available = intent.getStringArrayListExtra( - ConnectivityManager.EXTRA_AVAILABLE_TETHER); - ArrayList<String> active = intent.getStringArrayListExtra( - ConnectivityManager.EXTRA_ACTIVE_TETHER); - updateTetherState(available, active); + ArrayList<String> available = intent.getStringArrayListExtra( + ConnectivityManager.EXTRA_AVAILABLE_TETHER); + ArrayList<String> active = intent.getStringArrayListExtra( + ConnectivityManager.EXTRA_ACTIVE_TETHER); + updateTetherState(available, active); } },new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)); + + 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)) { + // reset & clear notification on any wifi state change + 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: + resetNotification(); + break; + } + } else if (intent.getAction().equals( + WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { + checkAndSetNotification(); + } + } + }, filter); + + // Setting is in seconds + NOTIFICATION_REPEAT_DELAY_MS = Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l; + mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler()); + mNotificationEnabledSettingObserver.register(); } /** @@ -289,7 +313,7 @@ public class WifiService extends IWifiManager.Stub { * * This function is used only at boot time */ - public void startWifi() { + public void checkAndStartWifi() { /* Start if Wi-Fi is enabled or the saved state indicates Wi-Fi was on */ boolean wifiEnabled = !isAirplaneModeOn() && (getPersistedWifiEnabled() || testAndClearWifiSavedState()); @@ -306,7 +330,10 @@ public class WifiService extends IWifiManager.Stub { IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); - mCm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + if (mCm == null) { + mCm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + } + mWifiRegexs = mCm.getTetherableWifiRegexs(); for (String intf : available) { @@ -318,25 +345,22 @@ public class WifiService extends IWifiManager.Stub { ifcg = service.getInterfaceConfig(intf); if (ifcg != null) { /* IP/netmask: 192.168.43.1/255.255.255.0 */ - ifcg.ipAddr = (192 << 24) + (168 << 16) + (43 << 8) + 1; - ifcg.netmask = (255 << 24) + (255 << 16) + (255 << 8) + 0; - ifcg.interfaceFlags = "up"; + ifcg.addr = InetAddress.getByName("192.168.43.1"); + ifcg.mask = InetAddress.getByName("255.255.255.0"); + ifcg.interfaceFlags = "[up]"; service.setInterfaceConfig(intf, ifcg); } } catch (Exception e) { Slog.e(TAG, "Error configuring interface " + intf + ", :" + e); - try { - nwService.stopAccessPoint(); - } catch (Exception ee) { - Slog.e(TAG, "Could not stop AP, :" + ee); - } - setWifiApEnabledState(WIFI_AP_STATE_FAILED, 0, DriverAction.DRIVER_UNLOAD); + setWifiApEnabled(null, false); return; } if(mCm.tether(intf) != ConnectivityManager.TETHER_ERROR_NO_ERROR) { - Slog.e(TAG, "Error tethering "+intf); + Slog.e(TAG, "Error tethering on " + intf); + setWifiApEnabled(null, false); + return; } break; } @@ -372,18 +396,18 @@ public class WifiService extends IWifiManager.Stub { Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, enabled ? 1 : 0); } - NetworkStateTracker getNetworkStateTracker() { - return mWifiStateTracker; - } - /** * see {@link android.net.wifi.WifiManager#pingSupplicant()} - * @return {@code true} if the operation succeeds + * @return {@code true} if the operation succeeds, {@code false} otherwise */ public boolean pingSupplicant() { - enforceChangePermission(); - - return mWifiStateTracker.ping(); + enforceAccessPermission(); + if (mChannel != null) { + return mWifiStateMachine.syncPingSupplicant(mChannel); + } else { + Slog.e(TAG, "mChannel is not initialized"); + return false; + } } /** @@ -391,9 +415,24 @@ public class WifiService extends IWifiManager.Stub { */ public void startScan(boolean forceActive) { enforceChangePermission(); - if (mWifiHandler == null) return; + mWifiStateMachine.startScan(forceActive); + } + + private void enforceAccessPermission() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, + "WifiService"); + } + + private void enforceChangePermission() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, + "WifiService"); - Message.obtain(mWifiHandler, MESSAGE_START_SCAN, forceActive ? 1 : 0, 0).sendToTarget(); + } + + private void enforceMulticastChangePermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, + "WifiService"); } /** @@ -402,168 +441,44 @@ public class WifiService extends IWifiManager.Stub { * @return {@code true} if the enable/disable operation was * started or is already in the queue. */ - public boolean setWifiEnabled(boolean enable) { + public synchronized boolean setWifiEnabled(boolean enable) { enforceChangePermission(); - if (mWifiHandler == null) return false; - - synchronized (mWifiHandler) { - // caller may not have WAKE_LOCK permission - it's not required here - long ident = Binder.clearCallingIdentity(); - sWakeLock.acquire(); - Binder.restoreCallingIdentity(ident); - mLastEnableUid = Binder.getCallingUid(); - // set a flag if the user is enabling Wifi while in airplane mode - mAirplaneModeOverwridden = (enable && isAirplaneModeOn() && isAirplaneToggleable()); - sendEnableMessage(enable, true, Binder.getCallingUid()); + if (DBG) { + Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n"); } - return true; - } - - /** - * Enables/disables Wi-Fi synchronously. - * @param enable {@code true} to turn Wi-Fi on, {@code false} to turn it off. - * @param persist {@code true} if the setting should be persisted. - * @param uid The UID of the process making the request. - * @return {@code true} if the operation succeeds (or if the existing state - * is the same as the requested state) - */ - private boolean setWifiEnabledBlocking(boolean enable, boolean persist, int uid) { - final int eventualWifiState = enable ? WIFI_STATE_ENABLED : WIFI_STATE_DISABLED; - final int wifiState = mWifiStateTracker.getWifiState(); - - if (wifiState == eventualWifiState) { - return true; - } - if (enable && isAirplaneModeOn() && !mAirplaneModeOverwridden) { - return false; + // set a flag if the user is enabling Wifi while in airplane mode + if (enable && isAirplaneModeOn() && isAirplaneToggleable()) { + mAirplaneModeOverwridden.set(true); } - /** - * Multiple calls to unregisterReceiver() cause exception and a system crash. - * This can happen if a supplicant is lost (or firmware crash occurs) and user indicates - * disable wifi at the same time. - * Avoid doing a disable when the current Wifi state is UNKNOWN - * TODO: Handle driver load fail and supplicant lost as seperate states - */ - if ((wifiState == WIFI_STATE_UNKNOWN) && !enable) { - return false; + if (enable) { + reportStartWorkSource(); } + mWifiStateMachine.setWifiEnabled(enable); - /** - * Fail Wifi if AP is enabled - * TODO: Deprecate WIFI_STATE_UNKNOWN and rename it - * WIFI_STATE_FAILED + /* + * Caller might not have WRITE_SECURE_SETTINGS, + * only CHANGE_WIFI_STATE is enforced */ - if ((mWifiApState == WIFI_AP_STATE_ENABLED) && enable) { - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - return false; - } - - setWifiEnabledState(enable ? WIFI_STATE_ENABLING : WIFI_STATE_DISABLING, uid); + long ident = Binder.clearCallingIdentity(); + persistWifiEnabled(enable); + Binder.restoreCallingIdentity(ident); if (enable) { - if (!mWifiStateTracker.loadDriver()) { - Slog.e(TAG, "Failed to load Wi-Fi driver."); - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - return false; - } - if (!mWifiStateTracker.startSupplicant()) { - mWifiStateTracker.unloadDriver(); - Slog.e(TAG, "Failed to start supplicant daemon."); - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - return false; + if (!mIsReceiverRegistered) { + registerForBroadcasts(); + mIsReceiverRegistered = true; } - - registerForBroadcasts(); - mWifiStateTracker.startEventLoop(); - - } else { - + } else if (mIsReceiverRegistered){ mContext.unregisterReceiver(mReceiver); - // Remove notification (it will no-op if it isn't visible) - mWifiStateTracker.setNotificationVisible(false, 0, false, 0); - - boolean failedToStopSupplicantOrUnloadDriver = false; - - if (!mWifiStateTracker.stopSupplicant()) { - Slog.e(TAG, "Failed to stop supplicant daemon."); - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - failedToStopSupplicantOrUnloadDriver = true; - } - - /** - * Reset connections and disable interface - * before we unload the driver - */ - mWifiStateTracker.resetConnections(true); - - if (!mWifiStateTracker.unloadDriver()) { - Slog.e(TAG, "Failed to unload Wi-Fi driver."); - if (!failedToStopSupplicantOrUnloadDriver) { - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - failedToStopSupplicantOrUnloadDriver = true; - } - } - - if (failedToStopSupplicantOrUnloadDriver) { - return false; - } + mIsReceiverRegistered = false; } - // Success! - - if (persist) { - persistWifiEnabled(enable); - } - setWifiEnabledState(eventualWifiState, uid); return true; } - private void setWifiEnabledState(int wifiState, int uid) { - final int previousWifiState = mWifiStateTracker.getWifiState(); - - long ident = Binder.clearCallingIdentity(); - try { - if (wifiState == WIFI_STATE_ENABLED) { - mBatteryStats.noteWifiOn(); - } else if (wifiState == WIFI_STATE_DISABLED) { - mBatteryStats.noteWifiOff(); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(ident); - } - - // Update state - mWifiStateTracker.setWifiState(wifiState); - - // Broadcast - final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - intent.putExtra(WifiManager.EXTRA_WIFI_STATE, wifiState); - intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, previousWifiState); - mContext.sendStickyBroadcast(intent); - } - - 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 enforceMulticastChangePermission() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, - "WifiService"); - } - /** * see {@link WifiManager#getWifiState()} * @return One of {@link WifiManager#WIFI_STATE_DISABLED}, @@ -574,66 +489,59 @@ public class WifiService extends IWifiManager.Stub { */ public int getWifiEnabledState() { enforceAccessPermission(); - return mWifiStateTracker.getWifiState(); - } - - /** - * see {@link android.net.wifi.WifiManager#disconnect()} - * @return {@code true} if the operation succeeds - */ - public boolean disconnect() { - enforceChangePermission(); - - return mWifiStateTracker.disconnect(); - } - - /** - * see {@link android.net.wifi.WifiManager#reconnect()} - * @return {@code true} if the operation succeeds - */ - public boolean reconnect() { - enforceChangePermission(); - - return mWifiStateTracker.reconnectCommand(); - } - - /** - * see {@link android.net.wifi.WifiManager#reassociate()} - * @return {@code true} if the operation succeeds - */ - public boolean reassociate() { - enforceChangePermission(); - - return mWifiStateTracker.reassociate(); + 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 + * @param enabled true to enable and false to disable * @return {@code true} if the start operation was * started or is already in the queue. */ - public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { + public synchronized boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { enforceChangePermission(); - if (mWifiHandler == null) return false; - - synchronized (mWifiHandler) { + if (enabled) { + /* Use default config if there is no existing config */ + if (wifiConfig == null && ((wifiConfig = getWifiApConfiguration()) == null)) { + wifiConfig = new WifiConfiguration(); + wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default); + wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE); + } + /* + * Caller might not have WRITE_SECURE_SETTINGS, + * only CHANGE_WIFI_STATE is enforced + */ long ident = Binder.clearCallingIdentity(); - sWakeLock.acquire(); + setWifiApConfiguration(wifiConfig); Binder.restoreCallingIdentity(ident); - - mLastApEnableUid = Binder.getCallingUid(); - sendAccessPointMessage(enabled, wifiConfig, Binder.getCallingUid()); } + mWifiStateMachine.setWifiApEnabled(wifiConfig, enabled); + return true; } - public WifiConfiguration getWifiApConfiguration() { + /** + * 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 synchronized WifiConfiguration getWifiApConfiguration() { final ContentResolver cr = mContext.getContentResolver(); WifiConfiguration wifiConfig = new WifiConfiguration(); int authType; @@ -651,7 +559,11 @@ public class WifiService extends IWifiManager.Stub { } } - public void setWifiApConfiguration(WifiConfiguration wifiConfig) { + /** + * see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)} + * @param wifiConfig WifiConfiguration details for soft access point + */ + public synchronized void setWifiApConfiguration(WifiConfiguration wifiConfig) { enforceChangePermission(); final ContentResolver cr = mContext.getContentResolver(); boolean isWpa; @@ -667,143 +579,27 @@ public class WifiService extends IWifiManager.Stub { } /** - * Enables/disables Wi-Fi AP synchronously. The driver is loaded - * and soft access point configured as a single operation. - * @param enable {@code true} to turn Wi-Fi on, {@code false} to turn it off. - * @param uid The UID of the process making the request. - * @param wifiConfig The WifiConfiguration for AP - * @return {@code true} if the operation succeeds (or if the existing state - * is the same as the requested state) + * see {@link android.net.wifi.WifiManager#disconnect()} */ - private boolean setWifiApEnabledBlocking(boolean enable, - int uid, WifiConfiguration wifiConfig) { - final int eventualWifiApState = enable ? WIFI_AP_STATE_ENABLED : WIFI_AP_STATE_DISABLED; - - if (mWifiApState == eventualWifiApState) { - /* Configuration changed on a running access point */ - if(enable && (wifiConfig != null)) { - try { - nwService.setAccessPoint(wifiConfig, mWifiStateTracker.getInterfaceName(), - SOFTAP_IFACE); - setWifiApConfiguration(wifiConfig); - return true; - } catch(Exception e) { - Slog.e(TAG, "Exception in nwService during AP restart"); - try { - nwService.stopAccessPoint(); - } catch (Exception ee) { - Slog.e(TAG, "Could not stop AP, :" + ee); - } - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD); - return false; - } - } else { - return true; - } - } - - /** - * Fail AP if Wifi is enabled - */ - if ((mWifiStateTracker.getWifiState() == WIFI_STATE_ENABLED) && enable) { - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD); - return false; - } - - setWifiApEnabledState(enable ? WIFI_AP_STATE_ENABLING : - WIFI_AP_STATE_DISABLING, uid, DriverAction.NO_DRIVER_UNLOAD); - - if (enable) { - - /* Use default config if there is no existing config */ - if (wifiConfig == null && ((wifiConfig = getWifiApConfiguration()) == null)) { - wifiConfig = new WifiConfiguration(); - wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default); - wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE); - } - - if (!mWifiStateTracker.loadDriver()) { - Slog.e(TAG, "Failed to load Wi-Fi driver for AP mode"); - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD); - return false; - } - - try { - nwService.startAccessPoint(wifiConfig, mWifiStateTracker.getInterfaceName(), - SOFTAP_IFACE); - } catch(Exception e) { - Slog.e(TAG, "Exception in startAccessPoint()"); - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD); - return false; - } - - setWifiApConfiguration(wifiConfig); - - } else { - - try { - nwService.stopAccessPoint(); - } catch(Exception e) { - Slog.e(TAG, "Exception in stopAccessPoint()"); - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD); - return false; - } - - if (!mWifiStateTracker.unloadDriver()) { - Slog.e(TAG, "Failed to unload Wi-Fi driver for AP mode"); - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD); - return false; - } - } - - setWifiApEnabledState(eventualWifiApState, uid, DriverAction.NO_DRIVER_UNLOAD); - return true; + public void disconnect() { + enforceChangePermission(); + mWifiStateMachine.disconnectCommand(); } /** - * 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} + * see {@link android.net.wifi.WifiManager#reconnect()} */ - public int getWifiApEnabledState() { - enforceAccessPermission(); - return mWifiApState; + public void reconnect() { + enforceChangePermission(); + mWifiStateMachine.reconnectCommand(); } - private void setWifiApEnabledState(int wifiAPState, int uid, DriverAction flag) { - final int previousWifiApState = mWifiApState; - - /** - * Unload the driver if going to a failed state - */ - if ((mWifiApState == WIFI_AP_STATE_FAILED) && (flag == DriverAction.DRIVER_UNLOAD)) { - mWifiStateTracker.unloadDriver(); - } - - long ident = Binder.clearCallingIdentity(); - try { - if (wifiAPState == WIFI_AP_STATE_ENABLED) { - mBatteryStats.noteWifiOn(); - } else if (wifiAPState == WIFI_AP_STATE_DISABLED) { - mBatteryStats.noteWifiOff(); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(ident); - } - - // Update state - mWifiApState = wifiAPState; - - // Broadcast - final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiAPState); - intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState); - mContext.sendStickyBroadcast(intent); + /** + * see {@link android.net.wifi.WifiManager#reassociate()} + */ + public void reassociate() { + enforceChangePermission(); + mWifiStateMachine.reassociateCommand(); } /** @@ -812,217 +608,7 @@ public class WifiService extends IWifiManager.Stub { */ public List<WifiConfiguration> getConfiguredNetworks() { enforceAccessPermission(); - String listStr; - - /* - * We don't cache the list, because we want to allow - * for the possibility that the configuration file - * has been modified through some external means, - * such as the wpa_cli command line program. - */ - listStr = mWifiStateTracker.listNetworks(); - - List<WifiConfiguration> networks = - new ArrayList<WifiConfiguration>(); - if (listStr == null) - return networks; - - String[] lines = listStr.split("\n"); - // Skip the first line, which is a header - for (int i = 1; i < lines.length; i++) { - String[] result = lines[i].split("\t"); - // network-id | ssid | bssid | flags - WifiConfiguration config = new WifiConfiguration(); - try { - config.networkId = Integer.parseInt(result[0]); - } catch(NumberFormatException e) { - continue; - } - if (result.length > 3) { - if (result[3].indexOf("[CURRENT]") != -1) - config.status = WifiConfiguration.Status.CURRENT; - else if (result[3].indexOf("[DISABLED]") != -1) - config.status = WifiConfiguration.Status.DISABLED; - else - config.status = WifiConfiguration.Status.ENABLED; - } else { - config.status = WifiConfiguration.Status.ENABLED; - } - readNetworkVariables(config); - networks.add(config); - } - - return networks; - } - - /** - * Read the variables from the supplicant daemon that are needed to - * fill in the WifiConfiguration object. - * <p/> - * The caller must hold the synchronization monitor. - * @param config the {@link WifiConfiguration} object to be filled in. - */ - private void readNetworkVariables(WifiConfiguration config) { - - int netId = config.networkId; - if (netId < 0) - return; - - /* - * TODO: maybe should have a native method that takes an array of - * variable names and returns an array of values. But we'd still - * be doing a round trip to the supplicant daemon for each variable. - */ - String value; - - value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.ssidVarName); - if (!TextUtils.isEmpty(value)) { - config.SSID = value; - } else { - config.SSID = null; - } - - value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.bssidVarName); - if (!TextUtils.isEmpty(value)) { - config.BSSID = value; - } else { - config.BSSID = null; - } - - value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.priorityVarName); - config.priority = -1; - if (!TextUtils.isEmpty(value)) { - try { - config.priority = Integer.parseInt(value); - } catch (NumberFormatException ignore) { - } - } - - value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.hiddenSSIDVarName); - config.hiddenSSID = false; - if (!TextUtils.isEmpty(value)) { - try { - config.hiddenSSID = Integer.parseInt(value) != 0; - } catch (NumberFormatException ignore) { - } - } - - value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.wepTxKeyIdxVarName); - config.wepTxKeyIndex = -1; - if (!TextUtils.isEmpty(value)) { - try { - config.wepTxKeyIndex = Integer.parseInt(value); - } catch (NumberFormatException ignore) { - } - } - - /* - * Get up to 4 WEP keys. Note that the actual keys are not passed back, - * just a "*" if the key is set, or the null string otherwise. - */ - for (int i = 0; i < 4; i++) { - value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.wepKeyVarNames[i]); - if (!TextUtils.isEmpty(value)) { - config.wepKeys[i] = value; - } else { - config.wepKeys[i] = null; - } - } - - /* - * Get the private shared key. Note that the actual keys are not passed back, - * just a "*" if the key is set, or the null string otherwise. - */ - value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.pskVarName); - if (!TextUtils.isEmpty(value)) { - config.preSharedKey = value; - } else { - config.preSharedKey = null; - } - - value = mWifiStateTracker.getNetworkVariable(config.networkId, - WifiConfiguration.Protocol.varName); - if (!TextUtils.isEmpty(value)) { - String vals[] = value.split(" "); - for (String val : vals) { - int index = - lookupString(val, WifiConfiguration.Protocol.strings); - if (0 <= index) { - config.allowedProtocols.set(index); - } - } - } - - value = mWifiStateTracker.getNetworkVariable(config.networkId, - WifiConfiguration.KeyMgmt.varName); - if (!TextUtils.isEmpty(value)) { - String vals[] = value.split(" "); - for (String val : vals) { - int index = - lookupString(val, WifiConfiguration.KeyMgmt.strings); - if (0 <= index) { - config.allowedKeyManagement.set(index); - } - } - } - - value = mWifiStateTracker.getNetworkVariable(config.networkId, - WifiConfiguration.AuthAlgorithm.varName); - if (!TextUtils.isEmpty(value)) { - String vals[] = value.split(" "); - for (String val : vals) { - int index = - lookupString(val, WifiConfiguration.AuthAlgorithm.strings); - if (0 <= index) { - config.allowedAuthAlgorithms.set(index); - } - } - } - - value = mWifiStateTracker.getNetworkVariable(config.networkId, - WifiConfiguration.PairwiseCipher.varName); - if (!TextUtils.isEmpty(value)) { - String vals[] = value.split(" "); - for (String val : vals) { - int index = - lookupString(val, WifiConfiguration.PairwiseCipher.strings); - if (0 <= index) { - config.allowedPairwiseCiphers.set(index); - } - } - } - - value = mWifiStateTracker.getNetworkVariable(config.networkId, - WifiConfiguration.GroupCipher.varName); - if (!TextUtils.isEmpty(value)) { - String vals[] = value.split(" "); - for (String val : vals) { - int index = - lookupString(val, WifiConfiguration.GroupCipher.strings); - if (0 <= index) { - config.allowedGroupCiphers.set(index); - } - } - } - - for (WifiConfiguration.EnterpriseField field : - config.enterpriseFields) { - value = mWifiStateTracker.getNetworkVariable(netId, - field.varName()); - if (!TextUtils.isEmpty(value)) { - if (field != config.eap) value = removeDoubleQuotes(value); - field.setValue(value); - } - } - } - - private static String removeDoubleQuotes(String string) { - if (string.length() <= 2) return ""; - return string.substring(1, string.length() - 1); - } - - private static String convertToQuotedString(String string) { - return "\"" + string + "\""; + return mWifiStateMachine.syncGetConfiguredNetworks(); } /** @@ -1032,280 +618,15 @@ public class WifiService extends IWifiManager.Stub { */ public int addOrUpdateNetwork(WifiConfiguration config) { enforceChangePermission(); - - /* - * If the supplied networkId is -1, we create a new empty - * network configuration. Otherwise, the networkId should - * refer to an existing configuration. - */ - int netId = config.networkId; - boolean newNetwork = netId == -1; - boolean doReconfig = false; - // networkId of -1 means we want to create a new network - synchronized (mWifiStateTracker) { - if (newNetwork) { - netId = mWifiStateTracker.addNetwork(); - if (netId < 0) { - if (DBG) { - Slog.d(TAG, "Failed to add a network!"); - } - return -1; - } - doReconfig = true; - } - mNeedReconfig = mNeedReconfig || doReconfig; - } - - setVariables: { - /* - * Note that if a networkId for a non-existent network - * was supplied, then the first setNetworkVariable() - * will fail, so we don't bother to make a separate check - * for the validity of the ID up front. - */ - if (config.SSID != null && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.ssidVarName, - config.SSID)) { - if (DBG) { - Slog.d(TAG, "failed to set SSID: "+config.SSID); - } - break setVariables; - } - - if (config.BSSID != null && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.bssidVarName, - config.BSSID)) { - if (DBG) { - Slog.d(TAG, "failed to set BSSID: "+config.BSSID); - } - break setVariables; - } - - String allowedKeyManagementString = - makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings); - if (config.allowedKeyManagement.cardinality() != 0 && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.KeyMgmt.varName, - allowedKeyManagementString)) { - if (DBG) { - Slog.d(TAG, "failed to set key_mgmt: "+ - allowedKeyManagementString); - } - break setVariables; - } - - String allowedProtocolsString = - makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings); - if (config.allowedProtocols.cardinality() != 0 && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.Protocol.varName, - allowedProtocolsString)) { - if (DBG) { - Slog.d(TAG, "failed to set proto: "+ - allowedProtocolsString); - } - break setVariables; - } - - String allowedAuthAlgorithmsString = - makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings); - if (config.allowedAuthAlgorithms.cardinality() != 0 && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.AuthAlgorithm.varName, - allowedAuthAlgorithmsString)) { - if (DBG) { - Slog.d(TAG, "failed to set auth_alg: "+ - allowedAuthAlgorithmsString); - } - break setVariables; - } - - String allowedPairwiseCiphersString = - makeString(config.allowedPairwiseCiphers, WifiConfiguration.PairwiseCipher.strings); - if (config.allowedPairwiseCiphers.cardinality() != 0 && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.PairwiseCipher.varName, - allowedPairwiseCiphersString)) { - if (DBG) { - Slog.d(TAG, "failed to set pairwise: "+ - allowedPairwiseCiphersString); - } - break setVariables; - } - - String allowedGroupCiphersString = - makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings); - if (config.allowedGroupCiphers.cardinality() != 0 && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.GroupCipher.varName, - allowedGroupCiphersString)) { - if (DBG) { - Slog.d(TAG, "failed to set group: "+ - allowedGroupCiphersString); - } - break setVariables; - } - - // Prevent client screw-up by passing in a WifiConfiguration we gave it - // by preventing "*" as a key. - if (config.preSharedKey != null && !config.preSharedKey.equals("*") && - !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.pskVarName, - config.preSharedKey)) { - if (DBG) { - Slog.d(TAG, "failed to set psk: "+config.preSharedKey); - } - break setVariables; - } - - boolean hasSetKey = false; - if (config.wepKeys != null) { - for (int i = 0; i < config.wepKeys.length; i++) { - // Prevent client screw-up by passing in a WifiConfiguration we gave it - // by preventing "*" as a key. - if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) { - if (!mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.wepKeyVarNames[i], - config.wepKeys[i])) { - if (DBG) { - Slog.d(TAG, - "failed to set wep_key"+i+": " + - config.wepKeys[i]); - } - break setVariables; - } - hasSetKey = true; - } - } - } - - if (hasSetKey) { - if (!mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.wepTxKeyIdxVarName, - Integer.toString(config.wepTxKeyIndex))) { - if (DBG) { - Slog.d(TAG, - "failed to set wep_tx_keyidx: "+ - config.wepTxKeyIndex); - } - break setVariables; - } - } - - if (!mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.priorityVarName, - Integer.toString(config.priority))) { - if (DBG) { - Slog.d(TAG, config.SSID + ": failed to set priority: " - +config.priority); - } - break setVariables; - } - - if (config.hiddenSSID && !mWifiStateTracker.setNetworkVariable( - netId, - WifiConfiguration.hiddenSSIDVarName, - Integer.toString(config.hiddenSSID ? 1 : 0))) { - if (DBG) { - Slog.d(TAG, config.SSID + ": failed to set hiddenSSID: "+ - config.hiddenSSID); - } - break setVariables; - } - - for (WifiConfiguration.EnterpriseField field - : config.enterpriseFields) { - String varName = field.varName(); - String value = field.value(); - if (value != null) { - if (field != config.eap) { - value = (value.length() == 0) ? "NULL" : convertToQuotedString(value); - } - if (!mWifiStateTracker.setNetworkVariable( - netId, - varName, - value)) { - if (DBG) { - Slog.d(TAG, config.SSID + ": failed to set " + varName + - ": " + value); - } - break setVariables; - } - } - } - return netId; - } - - /* - * For an update, if one of the setNetworkVariable operations fails, - * we might want to roll back all the changes already made. But the - * chances are that if anything is going to go wrong, it'll happen - * the first time we try to set one of the variables. - */ - if (newNetwork) { - removeNetwork(netId); - if (DBG) { - Slog.d(TAG, - "Failed to set a network variable, removed network: " - + netId); - } - } - return -1; - } - - private static String makeString(BitSet set, String[] strings) { - StringBuffer buf = new StringBuffer(); - int nextSetBit = -1; - - /* Make sure all set bits are in [0, strings.length) to avoid - * going out of bounds on strings. (Shouldn't happen, but...) */ - set = set.get(0, strings.length); - - while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) { - buf.append(strings[nextSetBit].replace('_', '-')).append(' '); - } - - // remove trailing space - if (set.cardinality() > 0) { - buf.setLength(buf.length() - 1); - } - - return buf.toString(); - } - - private static int lookupString(String string, String[] strings) { - int size = strings.length; - - string = string.replace('-', '_'); - - for (int i = 0; i < size; i++) - if (string.equals(strings[i])) - return i; - - if (DBG) { - // if we ever get here, we should probably add the - // value to WifiConfiguration to reflect that it's - // supported by the WPA supplicant - Slog.w(TAG, "Failed to look-up a string: " + string); + if (mChannel != null) { + return mWifiStateMachine.syncAddOrUpdateNetwork(mChannel, config); + } else { + Slog.e(TAG, "mChannel is not initialized"); + return -1; } - - return -1; } - /** + /** * See {@link android.net.wifi.WifiManager#removeNetwork(int)} * @param netId the integer that identifies the network configuration * to the supplicant @@ -1313,8 +634,12 @@ public class WifiService extends IWifiManager.Stub { */ public boolean removeNetwork(int netId) { enforceChangePermission(); - - return mWifiStateTracker.removeNetwork(netId); + if (mChannel != null) { + return mWifiStateMachine.syncRemoveNetwork(mChannel, netId); + } else { + Slog.e(TAG, "mChannel is not initialized"); + return false; + } } /** @@ -1326,14 +651,12 @@ public class WifiService extends IWifiManager.Stub { */ public boolean enableNetwork(int netId, boolean disableOthers) { enforceChangePermission(); - - String ifname = mWifiStateTracker.getInterfaceName(); - NetworkUtils.enableInterface(ifname); - boolean result = mWifiStateTracker.enableNetwork(netId, disableOthers); - if (!result) { - NetworkUtils.disableInterface(ifname); + if (mChannel != null) { + return mWifiStateMachine.syncEnableNetwork(mChannel, netId, disableOthers); + } else { + Slog.e(TAG, "mChannel is not initialized"); + return false; } - return result; } /** @@ -1344,8 +667,12 @@ public class WifiService extends IWifiManager.Stub { */ public boolean disableNetwork(int netId) { enforceChangePermission(); - - return mWifiStateTracker.disableNetwork(netId); + if (mChannel != null) { + return mWifiStateMachine.syncDisableNetwork(mChannel, netId); + } else { + Slog.e(TAG, "mChannel is not initialized"); + return false; + } } /** @@ -1358,7 +685,7 @@ public class WifiService extends IWifiManager.Stub { * Make sure we have the latest information, by sending * a status request to the supplicant. */ - return mWifiStateTracker.requestConnectionInfo(); + return mWifiStateMachine.syncRequestConnectionInfo(); } /** @@ -1368,282 +695,161 @@ public class WifiService extends IWifiManager.Stub { */ public List<ScanResult> getScanResults() { enforceAccessPermission(); - String reply; + return mWifiStateMachine.syncGetScanResultsList(); + } - reply = mWifiStateTracker.scanResults(); - if (reply == null) { - return null; + /** + * 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 (mChannel != null) { + return mWifiStateMachine.syncSaveConfig(mChannel); + } else { + Slog.e(TAG, "mChannel is not initialized"); + return false; } + } - List<ScanResult> scanList = new ArrayList<ScanResult>(); - - int lineCount = 0; - - int replyLen = reply.length(); - // Parse the result string, keeping in mind that the last line does - // not end with a newline. - for (int lineBeg = 0, lineEnd = 0; lineEnd <= replyLen; ++lineEnd) { - if (lineEnd == replyLen || reply.charAt(lineEnd) == '\n') { - ++lineCount; - /* - * Skip the first line, which is a header - */ - if (lineCount == 1) { - lineBeg = lineEnd + 1; - continue; - } - if (lineEnd > lineBeg) { - String line = reply.substring(lineBeg, lineEnd); - ScanResult scanResult = parseScanResult(line); - if (scanResult != null) { - scanList.add(scanResult); - } else if (DBG) { - Slog.w(TAG, "misformatted scan result for: " + line); - } - } - lineBeg = lineEnd + 1; - } - } - mWifiStateTracker.setScanResultsList(scanList); - return scanList; + /** + * 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); + enforceChangePermission(); + mWifiStateMachine.setCountryCode(countryCode, persist); } /** - * Parse the scan result line passed to us by wpa_supplicant (helper). - * @param line the line to parse - * @return the {@link ScanResult} object + * 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. + * */ - private ScanResult parseScanResult(String line) { - ScanResult scanResult = null; - if (line != null) { - /* - * Cache implementation (LinkedHashMap) is not synchronized, thus, - * must synchronized here! - */ - synchronized (mScanResultCache) { - String[] result = scanResultPattern.split(line); - if (3 <= result.length && result.length <= 5) { - String bssid = result[0]; - // bssid | frequency | level | flags | ssid - int frequency; - int level; - try { - frequency = Integer.parseInt(result[1]); - level = Integer.parseInt(result[2]); - /* some implementations avoid negative values by adding 256 - * so we need to adjust for that here. - */ - if (level > 0) level -= 256; - } catch (NumberFormatException e) { - frequency = 0; - level = 0; - } + 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); + mWifiStateMachine.setFrequencyBand(band, persist); + } - /* - * The formatting of the results returned by - * wpa_supplicant is intended to make the fields - * line up nicely when printed, - * not to make them easy to parse. So we have to - * apply some heuristics to figure out which field - * is the SSID and which field is the flags. - */ - String ssid; - String flags; - if (result.length == 4) { - if (result[3].charAt(0) == '[') { - flags = result[3]; - ssid = ""; - } else { - flags = ""; - ssid = result[3]; - } - } else if (result.length == 5) { - flags = result[3]; - ssid = result[4]; - } else { - // Here, we must have 3 fields: no flags and ssid - // set - flags = ""; - ssid = ""; - } - // bssid + ssid is the hash key - String key = bssid + ssid; - scanResult = mScanResultCache.get(key); - if (scanResult != null) { - scanResult.level = level; - scanResult.SSID = ssid; - scanResult.capabilities = flags; - scanResult.frequency = frequency; - } else { - // Do not add scan results that have no SSID set - if (0 < ssid.trim().length()) { - scanResult = - new ScanResult( - ssid, bssid, flags, level, frequency); - mScanResultCache.put(key, scanResult); - } - } - } else { - Slog.w(TAG, "Misformatted scan result text with " + - result.length + " fields: " + line); - } - } - } + /** + * Get the operational frequency band + */ + public int getFrequencyBand() { + enforceAccessPermission(); + return mWifiStateMachine.getFrequencyBand(); + } - return scanResult; + 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); } /** - * Parse the "flags" field passed back in a scan result by wpa_supplicant, - * and construct a {@code WifiConfiguration} that describes the encryption, - * key management, and authenticaion capabilities of the access point. - * @param flags the string returned by wpa_supplicant - * @return the {@link WifiConfiguration} object, filled in + * Return the DHCP-assigned addresses from the last successful DHCP request, + * if any. + * @return the DHCP information */ - WifiConfiguration parseScanFlags(String flags) { - WifiConfiguration config = new WifiConfiguration(); - - if (flags.length() == 0) { - config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); - } - // ... to be implemented - return config; + public DhcpInfo getDhcpInfo() { + enforceAccessPermission(); + return mWifiStateMachine.syncGetDhcpInfo(); } /** - * Tell the supplicant to persist the current list of configured networks. - * @return {@code true} if the operation succeeded + * see {@link android.net.wifi.WifiManager#startWifi} + * */ - public boolean saveConfiguration() { - boolean result; + public void startWifi() { enforceChangePermission(); + /* 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 + */ - synchronized (mWifiStateTracker) { - result = mWifiStateTracker.saveConfig(); - if (result && mNeedReconfig) { - mNeedReconfig = false; - result = mWifiStateTracker.reloadConfig(); - - if (result) { - Intent intent = new Intent(WifiManager.NETWORK_IDS_CHANGED_ACTION); - mContext.sendBroadcast(intent); - } - } - } - // Inform the backup manager about a data change - IBackupManager ibm = IBackupManager.Stub.asInterface( - ServiceManager.getService(Context.BACKUP_SERVICE)); - if (ibm != null) { - try { - ibm.dataChanged("com.android.providers.settings"); - } catch (Exception e) { - // Try again later - } - } - return result; + mWifiStateMachine.setDriverStart(true); + mWifiStateMachine.reconnectCommand(); } /** - * Set the number of radio frequency channels that are allowed to be used - * in the current regulatory domain. This method should be used only - * if the correct number of channels cannot be determined automatically - * for some reason. If the operation is successful, the new value may be - * persisted as a Secure setting. - * @param numChannels the number of allowed channels. Must be greater than 0 - * and less than or equal to 16. - * @param persist {@code true} if the setting should be remembered. - * @return {@code true} if the operation succeeds, {@code false} otherwise, e.g., - * {@code numChannels} is outside the valid range. + * see {@link android.net.wifi.WifiManager#stopWifi} + * */ - public boolean setNumAllowedChannels(int numChannels, boolean persist) { - Slog.i(TAG, "WifiService trying to setNumAllowed to "+numChannels+ - " with persist set to "+persist); + public void stopWifi() { enforceChangePermission(); - - /* - * Validate the argument. We'd like to let the Wi-Fi driver do this, - * but if Wi-Fi isn't currently enabled, that's not possible, and - * we want to persist the setting anyway,so that it will take - * effect when Wi-Fi does become enabled. + /* TODO: may be add permissions for access only to connectivity service + * TODO: if a stop is issued, wifi is brought up only by startWifi + * unless wifi enabled status is toggled */ - boolean found = false; - for (int validChan : sValidRegulatoryChannelCounts) { - if (validChan == numChannels) { - found = true; - break; - } - } - if (!found) { - return false; - } + mWifiStateMachine.setDriverStart(false); + } - if (mWifiHandler == null) return false; - Message.obtain(mWifiHandler, - MESSAGE_SET_CHANNELS, numChannels, (persist ? 1 : 0)).sendToTarget(); + /** + * see {@link android.net.wifi.WifiManager#addToBlacklist} + * + */ + public void addToBlacklist(String bssid) { + enforceChangePermission(); - return true; + mWifiStateMachine.addToBlacklist(bssid); } /** - * sets the number of allowed radio frequency channels synchronously - * @param numChannels the number of allowed channels. Must be greater than 0 - * and less than or equal to 16. - * @param persist {@code true} if the setting should be remembered. - * @return {@code true} if the operation succeeds, {@code false} otherwise + * see {@link android.net.wifi.WifiManager#clearBlacklist} + * */ - private boolean setNumAllowedChannelsBlocking(int numChannels, boolean persist) { - if (persist) { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS, - numChannels); - } - return mWifiStateTracker.setNumAllowedChannels(numChannels); + public void clearBlacklist() { + enforceChangePermission(); + + mWifiStateMachine.clearBlacklist(); } - /** - * Return the number of frequency channels that are allowed - * to be used in the current regulatory domain. - * @return the number of allowed channels, or {@code -1} if an error occurs - */ - public int getNumAllowedChannels() { - int numChannels; + public void connectNetworkWithId(int networkId) { + enforceChangePermission(); + mWifiStateMachine.connectNetwork(networkId); + } - enforceAccessPermission(); + public void connectNetworkWithConfig(WifiConfiguration config) { + enforceChangePermission(); + mWifiStateMachine.connectNetwork(config); + } - /* - * If we can't get the value from the driver (e.g., because - * Wi-Fi is not currently enabled), get the value from - * Settings. - */ - numChannels = mWifiStateTracker.getNumAllowedChannels(); - if (numChannels < 0) { - numChannels = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS, - -1); - } - return numChannels; + public void saveNetwork(WifiConfiguration config) { + enforceChangePermission(); + mWifiStateMachine.saveNetwork(config); } - /** - * Return the list of valid values for the number of allowed radio channels - * for various regulatory domains. - * @return the list of channel counts - */ - public int[] getValidChannelCounts() { - enforceAccessPermission(); - return sValidRegulatoryChannelCounts; + public void forgetNetwork(int netId) { + enforceChangePermission(); + mWifiStateMachine.forgetNetwork(netId); } - /** - * Return the DHCP-assigned addresses from the last successful DHCP request, - * if any. - * @return the DHCP information - */ - public DhcpInfo getDhcpInfo() { - enforceAccessPermission(); - return mWifiStateTracker.getDhcpInfo(); + public WpsResult startWps(WpsConfiguration config) { + enforceChangePermission(); + if (mChannel != null) { + return mWifiStateMachine.startWps(mChannel, config); + } else { + Slog.e(TAG, "mChannel is not initialized"); + return new WpsResult(WpsResult.Status.FAILURE); + } } private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @@ -1653,7 +859,7 @@ public class WifiService extends IWifiManager.Stub { long idleMillis = Settings.Secure.getLong(mContext.getContentResolver(), - Settings.Secure.WIFI_IDLE_MS, DEFAULT_IDLE_MILLIS); + Settings.Secure.WIFI_IDLE_MS, DEFAULT_IDLE_MS); int stayAwakeConditions = Settings.System.getInt(mContext.getContentResolver(), Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0); @@ -1666,20 +872,16 @@ public class WifiService extends IWifiManager.Stub { mScreenOff = false; // Once the screen is on, we are not keeping WIFI running // because of any locks so clear that tracking immediately. - sendReportWorkSourceMessage(); - sendEnableRssiPollingMessage(true); - /* DHCP or other temporary failures in the past can prevent - * a disabled network from being connected to, enable on screen on - */ - if (mWifiStateTracker.isAnyNetworkDisabled()) { - sendEnableNetworksMessage(); - } + reportStartWorkSource(); + mWifiStateMachine.enableRssiPolling(true); + mWifiStateMachine.enableAllNetworks(); + updateWifiState(); } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { if (DBG) { Slog.d(TAG, "ACTION_SCREEN_OFF"); } mScreenOff = true; - sendEnableRssiPollingMessage(false); + mWifiStateMachine.enableRssiPolling(false); /* * 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 @@ -1687,11 +889,11 @@ public class WifiService extends IWifiManager.Stub { * or plugged in to AC). */ if (!shouldWifiStayAwake(stayAwakeConditions, mPluggedType)) { - WifiInfo info = mWifiStateTracker.requestConnectionInfo(); + WifiInfo info = mWifiStateMachine.syncRequestConnectionInfo(); if (info.getSupplicantState() != SupplicantState.COMPLETED) { // we used to go to sleep immediately, but this caused some race conditions - // we don't have time to track down for this release. Delay instead, but not - // as long as we would if connected (below) + // we don't have time to track down for this release. Delay instead, + // but not as long as we would if connected (below) // TODO - fix the race conditions and switch back to the immediate turn-off long triggerTime = System.currentTimeMillis() + (2*60*1000); // 2 min if (DBG) { @@ -1710,14 +912,13 @@ public class WifiService extends IWifiManager.Stub { mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent); } } - /* we can return now -- there's nothing to do until we get the idle intent back */ - return; } else if (action.equals(ACTION_DEVICE_IDLE)) { if (DBG) { Slog.d(TAG, "got ACTION_DEVICE_IDLE"); } mDeviceIdle = true; - sendReportWorkSourceMessage(); + reportStartWorkSource(); + updateWifiState(); } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { /* * Set a timer to put Wi-Fi to sleep, but only if the screen is off @@ -1737,26 +938,13 @@ public class WifiService extends IWifiManager.Stub { Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis + "ms"); } mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent); - mPluggedType = pluggedType; - return; } mPluggedType = pluggedType; - } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { - BluetoothA2dp a2dp = new BluetoothA2dp(mContext); - Set<BluetoothDevice> sinks = a2dp.getConnectedSinks(); - boolean isBluetoothPlaying = false; - for (BluetoothDevice sink : sinks) { - if (a2dp.getSinkState(sink) == BluetoothA2dp.STATE_PLAYING) { - isBluetoothPlaying = true; - } - } - mWifiStateTracker.setBluetoothScanMode(isBluetoothPlaying); - - } else { - return; + } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, + BluetoothAdapter.STATE_DISCONNECTED); + mWifiStateMachine.sendBluetoothAdapterStateChange(state); } - - updateWifiState(); } /** @@ -1767,8 +955,10 @@ public class WifiService extends IWifiManager.Stub { * @see #shouldDeviceStayAwake(int, int) */ private boolean shouldWifiStayAwake(int stayAwakeConditions, int pluggedType) { + //Never sleep when plugged in as long as the user has not changed the settings int wifiSleepPolicy = Settings.System.getInt(mContext.getContentResolver(), - Settings.System.WIFI_SLEEP_POLICY, Settings.System.WIFI_SLEEP_POLICY_DEFAULT); + Settings.System.WIFI_SLEEP_POLICY, + Settings.System.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED); if (wifiSleepPolicy == Settings.System.WIFI_SLEEP_POLICY_NEVER) { // Never sleep @@ -1789,7 +979,7 @@ public class WifiService extends IWifiManager.Stub { * of {@code 0} isn't really a plugged type, but rather an indication that the * device isn't plugged in at all, there is no bit value corresponding to a * {@code pluggedType} value of {@code 0}. That is why we shift by - * {@code pluggedType — 1} instead of by {@code pluggedType}. + * {@code pluggedType - 1} instead of by {@code pluggedType}. * @param stayAwakeConditions a bit string specifying which "plugged types" should * keep the device (and hence Wi-Fi) awake. * @param pluggedType the type of plug (USB, AC, or none) for which the check is @@ -1802,63 +992,20 @@ public class WifiService extends IWifiManager.Stub { } }; - private void sendEnableMessage(boolean enable, boolean persist, int uid) { - Message msg = Message.obtain(mWifiHandler, - (enable ? MESSAGE_ENABLE_WIFI : MESSAGE_DISABLE_WIFI), - (persist ? 1 : 0), uid); - msg.sendToTarget(); - } - - private void sendStartMessage(int lockMode) { - Message.obtain(mWifiHandler, MESSAGE_START_WIFI, lockMode, 0).sendToTarget(); - } - - private void sendAccessPointMessage(boolean enable, WifiConfiguration wifiConfig, int uid) { - Message.obtain(mWifiHandler, - (enable ? MESSAGE_START_ACCESS_POINT : MESSAGE_STOP_ACCESS_POINT), - uid, 0, wifiConfig).sendToTarget(); - } - - private void sendEnableNetworksMessage() { - Message.obtain(mWifiHandler, MESSAGE_ENABLE_NETWORKS).sendToTarget(); - } - - private void sendReportWorkSourceMessage() { - Message.obtain(mWifiHandler, MESSAGE_REPORT_WORKSOURCE).sendToTarget(); - } - - private void sendEnableRssiPollingMessage(boolean enable) { - Message.obtain(mWifiHandler, MESSAGE_ENABLE_RSSI_POLLING, enable ? 1 : 0, 0).sendToTarget(); - } - - - private void reportStartWorkSource() { - synchronized (mWifiStateTracker) { - mTmpWorkSource.clear(); - if (mDeviceIdle) { - for (int i=0; i<mLocks.mList.size(); i++) { - mTmpWorkSource.add(mLocks.mList.get(i).mWorkSource); - } + private synchronized void reportStartWorkSource() { + mTmpWorkSource.clear(); + if (mDeviceIdle) { + for (int i=0; i<mLocks.mList.size(); i++) { + mTmpWorkSource.add(mLocks.mList.get(i).mWorkSource); } - mWifiStateTracker.updateBatteryWorkSourceLocked(mTmpWorkSource); - sWakeLock.setWorkSource(mTmpWorkSource); } + mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource); } private void updateWifiState() { - // send a message so it's all serialized - Message.obtain(mWifiHandler, MESSAGE_UPDATE_STATE, 0, 0).sendToTarget(); - } - - private void doUpdateWifiState() { boolean wifiEnabled = getPersistedWifiEnabled(); - boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden; - - boolean lockHeld; - synchronized (mLocks) { - lockHeld = mLocks.hasLocks(); - } - + boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden.get(); + boolean lockHeld = mLocks.hasLocks(); int strongestLockMode = WifiManager.WIFI_MODE_FULL; boolean wifiShouldBeEnabled = wifiEnabled && !airplaneMode; boolean wifiShouldBeStarted = !mDeviceIdle || lockHeld; @@ -1871,43 +1018,26 @@ public class WifiService extends IWifiManager.Stub { strongestLockMode = WifiManager.WIFI_MODE_FULL; } - synchronized (mWifiHandler) { - if ((mWifiStateTracker.getWifiState() == WIFI_STATE_ENABLING) && !airplaneMode) { - return; - } - - /* Disable tethering when airplane mode is enabled */ - if (airplaneMode && - (mWifiApState == WIFI_AP_STATE_ENABLING || mWifiApState == WIFI_AP_STATE_ENABLED)) { - sWakeLock.acquire(); - sendAccessPointMessage(false, null, mLastApEnableUid); - } + /* Disable tethering when airplane mode is enabled */ + if (airplaneMode) { + mWifiStateMachine.setWifiApEnabled(null, false); + } - if (wifiShouldBeEnabled) { - if (wifiShouldBeStarted) { - sWakeLock.acquire(); - sendEnableMessage(true, false, mLastEnableUid); - sWakeLock.acquire(); - sendStartMessage(strongestLockMode); - } else if (!mWifiStateTracker.isDriverStopped()) { - int wakeLockTimeout = - Settings.Secure.getInt( - mContext.getContentResolver(), - Settings.Secure.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS, - DEFAULT_WAKELOCK_TIMEOUT); - /* - * We are assuming that ConnectivityService can make - * a transition to cellular data within wakeLockTimeout time. - * The wakelock is released by the delayed message. - */ - sDriverStopWakeLock.acquire(); - mWifiHandler.sendEmptyMessage(MESSAGE_STOP_WIFI); - mWifiHandler.sendEmptyMessageDelayed(MESSAGE_RELEASE_WAKELOCK, wakeLockTimeout); - } + if (wifiShouldBeEnabled) { + if (wifiShouldBeStarted) { + reportStartWorkSource(); + mWifiStateMachine.setWifiEnabled(true); + mWifiStateMachine.setScanOnlyMode( + strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY); + mWifiStateMachine.setDriverStart(true); + mWifiStateMachine.setHighPerfModeEnabled(strongestLockMode + == WifiManager.WIFI_MODE_FULL_HIGH_PERF); } else { - sWakeLock.acquire(); - sendEnableMessage(false, false, mLastEnableUid); + mWifiStateMachine.requestCmWakeLock(); + mWifiStateMachine.setDriverStart(false); } + } else { + mWifiStateMachine.setWifiEnabled(false); } } @@ -1917,7 +1047,7 @@ public class WifiService extends IWifiManager.Stub { intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); intentFilter.addAction(ACTION_DEVICE_IDLE); - intentFilter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); + intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); mContext.registerReceiver(mReceiver, intentFilter); } @@ -1945,103 +1075,6 @@ public class WifiService extends IWifiManager.Stub { Settings.System.AIRPLANE_MODE_ON, 0) == 1; } - /** - * Handler that allows posting to the WifiThread. - */ - private class WifiHandler extends Handler { - public WifiHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - - case MESSAGE_ENABLE_WIFI: - setWifiEnabledBlocking(true, msg.arg1 == 1, msg.arg2); - if (mWifiWatchdogService == null) { - mWifiWatchdogService = new WifiWatchdogService(mContext, mWifiStateTracker); - } - sWakeLock.release(); - break; - - case MESSAGE_START_WIFI: - reportStartWorkSource(); - mWifiStateTracker.setScanOnlyMode(msg.arg1 == WifiManager.WIFI_MODE_SCAN_ONLY); - mWifiStateTracker.restart(); - mWifiStateTracker.setHighPerfMode(msg.arg1 == - WifiManager.WIFI_MODE_FULL_HIGH_PERF); - sWakeLock.release(); - break; - - case MESSAGE_UPDATE_STATE: - doUpdateWifiState(); - break; - - case MESSAGE_DISABLE_WIFI: - // a non-zero msg.arg1 value means the "enabled" setting - // should be persisted - setWifiEnabledBlocking(false, msg.arg1 == 1, msg.arg2); - mWifiWatchdogService = null; - sWakeLock.release(); - break; - - case MESSAGE_STOP_WIFI: - mWifiStateTracker.disconnectAndStop(); - // don't release wakelock - break; - - case MESSAGE_RELEASE_WAKELOCK: - sDriverStopWakeLock.release(); - break; - - case MESSAGE_START_ACCESS_POINT: - setWifiApEnabledBlocking(true, - msg.arg1, - (WifiConfiguration) msg.obj); - sWakeLock.release(); - break; - - case MESSAGE_STOP_ACCESS_POINT: - setWifiApEnabledBlocking(false, - msg.arg1, - (WifiConfiguration) msg.obj); - sWakeLock.release(); - break; - - case MESSAGE_SET_CHANNELS: - setNumAllowedChannelsBlocking(msg.arg1, msg.arg2 == 1); - break; - - case MESSAGE_ENABLE_NETWORKS: - mWifiStateTracker.enableAllNetworks(getConfiguredNetworks()); - break; - - case MESSAGE_START_SCAN: - boolean forceActive = (msg.arg1 == 1); - switch (mWifiStateTracker.getSupplicantState()) { - case DISCONNECTED: - case INACTIVE: - case SCANNING: - case DORMANT: - break; - default: - mWifiStateTracker.setScanResultHandling( - WifiStateTracker.SUPPL_SCAN_HANDLING_LIST_ONLY); - break; - } - mWifiStateTracker.scan(forceActive); - break; - case MESSAGE_REPORT_WORKSOURCE: - reportStartWorkSource(); - break; - case MESSAGE_ENABLE_RSSI_POLLING: - mWifiStateTracker.enableRssiPolling(msg.arg1 == 1); - break; - } - } - } - @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) @@ -2051,17 +1084,17 @@ public class WifiService extends IWifiManager.Stub { + ", uid=" + Binder.getCallingUid()); return; } - pw.println("Wi-Fi is " + stateName(mWifiStateTracker.getWifiState())); + pw.println("Wi-Fi is " + mWifiStateMachine.syncGetWifiStateByName()); pw.println("Stay-awake conditions: " + Settings.System.getInt(mContext.getContentResolver(), Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0)); pw.println(); pw.println("Internal state:"); - pw.println(mWifiStateTracker); + pw.println(mWifiStateMachine); pw.println(); pw.println("Latest scan results:"); - List<ScanResult> scanResults = mWifiStateTracker.getScanResultsList(); + List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList(); if (scanResults != null && scanResults.size() != 0) { pw.println(" BSSID Frequency RSSI Flags SSID"); for (ScanResult r : scanResults) { @@ -2085,23 +1118,6 @@ public class WifiService extends IWifiManager.Stub { mLocks.dump(pw); } - private static String stateName(int wifiState) { - switch (wifiState) { - case WIFI_STATE_DISABLING: - return "disabling"; - case WIFI_STATE_DISABLED: - return "disabled"; - case WIFI_STATE_ENABLING: - return "enabling"; - case WIFI_STATE_ENABLED: - return "enabled"; - case WIFI_STATE_UNKNOWN: - return "unknown state"; - default: - return "[invalid state]"; - } - } - private class WifiLock extends DeathRecipient { WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) { super(lockMode, tag, binder, ws); @@ -2179,7 +1195,7 @@ public class WifiService extends IWifiManager.Stub { } void enforceWakeSourcePermission(int uid, int pid) { - if (uid == Process.myUid()) { + if (uid == android.os.Process.myUid()) { return; } mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS, @@ -2213,10 +1229,7 @@ public class WifiService extends IWifiManager.Stub { private void noteAcquireWifiLock(WifiLock wifiLock) throws RemoteException { switch(wifiLock.mMode) { case WifiManager.WIFI_MODE_FULL: - mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource); - break; case WifiManager.WIFI_MODE_FULL_HIGH_PERF: - /* Treat high power as a full lock for battery stats */ mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource); break; case WifiManager.WIFI_MODE_SCAN_ONLY: @@ -2228,10 +1241,7 @@ public class WifiService extends IWifiManager.Stub { private void noteReleaseWifiLock(WifiLock wifiLock) throws RemoteException { switch(wifiLock.mMode) { case WifiManager.WIFI_MODE_FULL: - mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource); - break; case WifiManager.WIFI_MODE_FULL_HIGH_PERF: - /* Treat high power as a full lock for battery stats */ mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource); break; case WifiManager.WIFI_MODE_SCAN_ONLY: @@ -2255,6 +1265,7 @@ public class WifiService extends IWifiManager.Stub { case WifiManager.WIFI_MODE_FULL_HIGH_PERF: ++mFullHighPerfLocksAcquired; break; + case WifiManager.WIFI_MODE_SCAN_ONLY: ++mScanLocksAcquired; break; @@ -2262,7 +1273,7 @@ public class WifiService extends IWifiManager.Stub { // Be aggressive about adding new locks into the accounted state... // we want to over-report rather than under-report. - sendReportWorkSourceMessage(); + reportStartWorkSource(); updateWifiState(); return true; @@ -2401,7 +1412,7 @@ public class WifiService extends IWifiManager.Stub { if (mMulticasters.size() != 0) { return; } else { - mWifiStateTracker.startPacketFiltering(); + mWifiStateMachine.startPacketFiltering(); } } } @@ -2416,7 +1427,7 @@ public class WifiService extends IWifiManager.Stub { // 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. - mWifiStateTracker.stopPacketFiltering(); + mWifiStateMachine.stopPacketFiltering(); } int uid = Binder.getCallingUid(); @@ -2453,7 +1464,7 @@ public class WifiService extends IWifiManager.Stub { removed.unlinkDeathRecipient(); } if (mMulticasters.size() == 0) { - mWifiStateTracker.startPacketFiltering(); + mWifiStateMachine.startPacketFiltering(); } Long ident = Binder.clearCallingIdentity(); @@ -2472,4 +1483,144 @@ public class WifiService extends IWifiManager.Stub { return (mMulticasters.size() > 0); } } + + private void checkAndSetNotification() { + // If we shouldn't place a notification on available networks, then + // don't bother doing any of the following + if (!mNotificationEnabled) return; + + State state = mNetworkInfo.getState(); + if ((state == NetworkInfo.State.DISCONNECTED) + || (state == NetworkInfo.State.UNKNOWN)) { + // Look for an open network + List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList(); + if (scanResults != null) { + int numOpenNetworks = 0; + for (int i = scanResults.size() - 1; i >= 0; i--) { + ScanResult scanResult = scanResults.get(i); + + if (TextUtils.isEmpty(scanResult.capabilities)) { + 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 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 = PendingIntent.getActivity(mContext, 0, + new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), 0); + } + + 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.notify(ICON_NETWORKS_AVAILABLE, mNotification); + } else { + notificationManager.cancel(ICON_NETWORKS_AVAILABLE); + } + + mNotificationShown = visible; + } + + private class NotificationEnabledSettingObserver extends ContentObserver { + + public NotificationEnabledSettingObserver(Handler handler) { + super(handler); + } + + public void register() { + ContentResolver cr = mContext.getContentResolver(); + cr.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this); + mNotificationEnabled = getValue(); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + mNotificationEnabled = getValue(); + resetNotification(); + } + + private boolean getValue() { + return Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1; + } + } + + } diff --git a/services/java/com/android/server/WifiStateTracker.java b/services/java/com/android/server/WifiStateTracker.java new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/services/java/com/android/server/WifiStateTracker.java diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java index 445dd03..46d6bef 100644 --- a/services/java/com/android/server/WifiWatchdogService.java +++ b/services/java/com/android/server/WifiWatchdogService.java @@ -27,7 +27,6 @@ import android.net.DhcpInfo; import android.net.wifi.ScanResult; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.net.wifi.WifiStateTracker; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -77,7 +76,6 @@ public class WifiWatchdogService { private Context mContext; private ContentResolver mContentResolver; - private WifiStateTracker mWifiStateTracker; private WifiManager mWifiManager; /** @@ -108,10 +106,9 @@ public class WifiWatchdogService { /** Whether the current AP check should be canceled. */ private boolean mShouldCancel; - WifiWatchdogService(Context context, WifiStateTracker wifiStateTracker) { + WifiWatchdogService(Context context) { mContext = context; mContentResolver = context.getContentResolver(); - mWifiStateTracker = wifiStateTracker; mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); createThread(); @@ -275,12 +272,13 @@ public class WifiWatchdogService { /** * Unregister broadcasts and quit the watchdog thread */ - private void quit() { - unregisterForWifiBroadcasts(); - mContext.getContentResolver().unregisterContentObserver(mContentObserver); - mHandler.removeAllActions(); - mHandler.getLooper().quit(); - } + //TODO: Change back to running WWS when needed +// private void quit() { +// unregisterForWifiBroadcasts(); +// mContext.getContentResolver().unregisterContentObserver(mContentObserver); +// mHandler.removeAllActions(); +// mHandler.getLooper().quit(); +// } /** * Waits for the main watchdog thread to create the handler. @@ -751,7 +749,7 @@ public class WifiWatchdogService { // Black list this "bad" AP, this will cause an attempt to connect to another blacklistAp(ap.bssid); // Initiate an association to an alternate AP - mWifiStateTracker.reassociate(); + mWifiManager.reassociate(); } private void blacklistAp(String bssid) { @@ -762,10 +760,7 @@ public class WifiWatchdogService { // Before taking action, make sure we should not cancel our processing if (shouldCancel()) return; - if (!mWifiStateTracker.addToBlacklist(bssid)) { - // There's a known bug where this method returns failure on success - //Slog.e(TAG, "Blacklisting " + bssid + " failed"); - } + mWifiManager.addToBlacklist(bssid); if (D) { myLogD("Blacklisting " + bssid); @@ -860,10 +855,7 @@ public class WifiWatchdogService { * (and blacklisted them). Clear the blacklist so the AP with best * signal is chosen. */ - if (!mWifiStateTracker.clearBlacklist()) { - // There's a known bug where this method returns failure on success - //Slog.e(TAG, "Clearing blacklist failed"); - } + mWifiManager.clearBlacklist(); if (V) { myLogV("handleSleep: Set state to SLEEP and cleared blacklist"); @@ -934,7 +926,7 @@ public class WifiWatchdogService { * should revert anything done by the watchdog monitoring. */ private void handleReset() { - mWifiStateTracker.clearBlacklist(); + mWifiManager.clearBlacklist(); setIdleState(true); } @@ -1151,7 +1143,7 @@ public class WifiWatchdogService { private void handleWifiStateChanged(int wifiState) { if (wifiState == WifiManager.WIFI_STATE_DISABLED) { - quit(); + onDisconnected(); } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) { onEnabled(); } diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index f28bae0..eeb224c 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -23,13 +23,11 @@ import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; -import static android.view.WindowManager.LayoutParams.FLAG_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.MEMORY_TYPE_PUSH_BUFFERS; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; @@ -39,6 +37,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import com.android.internal.app.IBatteryStats; import com.android.internal.policy.PolicyManager; import com.android.internal.policy.impl.PhoneWindowManager; +import com.android.internal.view.BaseInputHandler; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; import com.android.internal.view.IInputMethodManager; @@ -50,6 +49,8 @@ import android.app.ActivityManagerNative; import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; +import android.content.ClipData; +import android.content.ClipDescription; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -57,10 +58,13 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Region; import android.graphics.Typeface; @@ -81,6 +85,7 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.os.TokenWatcher; @@ -92,8 +97,8 @@ import android.util.Slog; import android.util.SparseIntArray; import android.util.TypedValue; import android.view.Display; +import android.view.DragEvent; import android.view.Gravity; -import android.view.HapticFeedbackConstants; import android.view.IApplicationToken; import android.view.IOnKeyguardExitResult; import android.view.IRotationWatcher; @@ -103,6 +108,8 @@ import android.view.IWindowSession; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; +import android.view.InputHandler; +import android.view.InputQueue; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; @@ -119,7 +126,6 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; -import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.DataInputStream; import java.io.File; @@ -143,6 +149,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { static final String TAG = "WindowManager"; static final boolean DEBUG = false; + static final boolean DEBUG_ADD_REMOVE = false; static final boolean DEBUG_FOCUS = false; static final boolean DEBUG_ANIM = false; static final boolean DEBUG_LAYOUT = false; @@ -152,13 +159,14 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean DEBUG_INPUT_METHOD = false; static final boolean DEBUG_VISIBILITY = false; static final boolean DEBUG_WINDOW_MOVEMENT = false; + static final boolean DEBUG_TOKEN_MOVEMENT = false; static final boolean DEBUG_ORIENTATION = false; static final boolean DEBUG_CONFIGURATION = false; static final boolean DEBUG_APP_TRANSITIONS = false; static final boolean DEBUG_STARTING_WINDOW = false; static final boolean DEBUG_REORDER = false; static final boolean DEBUG_WALLPAPER = false; - static final boolean DEBUG_FREEZE = false; + static final boolean DEBUG_DRAG = false; static final boolean SHOW_TRANSACTIONS = false; static final boolean HIDE_STACK_CRAWLS = true; @@ -195,13 +203,19 @@ public class WindowManagerService extends IWindowManager.Stub */ static final int DEFAULT_FADE_IN_OUT_DURATION = 400; - /** Adjustment to time to perform a dim, to make it more dramatic. + /** + * If true, the window manager will do its own custom freezing and general + * management of the screen during rotation. */ - static final int DIM_DURATION_MULTIPLIER = 6; - + static final boolean CUSTOM_SCREEN_ROTATION = true; + // Maximum number of milliseconds to wait for input event injection. // FIXME is this value reasonable? private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000; + + // Maximum number of milliseconds to wait for input devices to be enumerated before + // proceding with safe mode detection. + private static final int INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS = 1000; // Default input dispatching timeout in nanoseconds. private static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS = 5000 * 1000000L; @@ -288,12 +302,6 @@ public class WindowManagerService extends IWindowManager.Stub new HashMap<IBinder, WindowToken>(); /** - * The same tokens as mTokenMap, stored in a list for efficient iteration - * over them. - */ - final ArrayList<WindowToken> mTokenList = new ArrayList<WindowToken>(); - - /** * Window tokens that are in the process of exiting, but still * on screen for animations. */ @@ -302,7 +310,7 @@ public class WindowManagerService extends IWindowManager.Stub /** * Z-ordered (bottom-most first) list of all application tokens, for * controlling the ordering of windows in different applications. This - * contains WindowToken objects. + * contains AppWindowToken objects. */ final ArrayList<AppWindowToken> mAppTokens = new ArrayList<AppWindowToken>(); @@ -319,18 +327,6 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<AppWindowToken>(); /** - * This was the app token that was used to retrieve the last enter - * animation. It will be used for the next exit animation. - */ - AppWindowToken mLastEnterAnimToken; - - /** - * These were the layout params used to retrieve the last enter animation. - * They will be used for the next exit animation. - */ - LayoutParams mLastEnterAnimParams; - - /** * Z-ordered (bottom-most first) list of all Window objects. */ final ArrayList<WindowState> mWindows = new ArrayList<WindowState>(); @@ -348,6 +344,11 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<WindowState> mPendingRemove = new ArrayList<WindowState>(); /** + * Used when processing mPendingRemove to avoid working on the original array. + */ + WindowState[] mPendingRemoveTmp = new WindowState[20]; + + /** * Windows whose surface should be destroyed. */ final ArrayList<WindowState> mDestroySurface = new ArrayList<WindowState>(); @@ -364,6 +365,12 @@ public class WindowManagerService extends IWindowManager.Stub */ ArrayList<WindowState> mForceRemoves; + /** + * Used when rebuilding window list to keep track of windows that have + * been removed. + */ + WindowState[] mRebuildTmp = new WindowState[20]; + IInputMethodManager mInputMethodManager; SurfaceSession mFxSession; @@ -371,6 +378,8 @@ public class WindowManagerService extends IWindowManager.Stub Surface mBlurSurface; boolean mBlurShown; Watermark mWatermark; + StrictModeFlash mStrictModeFlash; + ScreenRotationAnimation mScreenRotationAnimation; int mTransactionSequence = 0; @@ -387,6 +396,8 @@ public class WindowManagerService extends IWindowManager.Stub int mLastRotationFlags; ArrayList<IRotationWatcher> mRotationWatchers = new ArrayList<IRotationWatcher>(); + int mDeferredRotation; + int mDeferredRotationAnimFlags; boolean mLayoutNeeded = true; boolean mAnimationPending = false; @@ -437,7 +448,6 @@ public class WindowManagerService extends IWindowManager.Stub // This just indicates the window the input method is on top of, not // necessarily the window its input is going to. WindowState mInputMethodTarget = null; - WindowState mUpcomingInputMethodTarget = null; boolean mInputMethodTargetWaitingAnim; int mInputMethodAnimLayerAdjustment; @@ -455,6 +465,11 @@ public class WindowManagerService extends IWindowManager.Stub // If non-null, we are in the middle of animating from one wallpaper target // to another, and this is the higher one in Z-order. WindowState mUpperWallpaperTarget = null; + // Window currently running an animation that has requested it be detached + // from the wallpaper. This means we need to ensure the wallpaper is + // visible behind it in case it animates in a way that would allow it to be + // seen. + WindowState mWindowDetachedWallpaper = null; int mWallpaperAnimLayerAdjustment; float mLastWallpaperX = -1; float mLastWallpaperY = -1; @@ -486,6 +501,390 @@ public class WindowManagerService extends IWindowManager.Stub boolean mTurnOnScreen; /** + * Drag/drop state + */ + class DragState { + IBinder mToken; + Surface mSurface; + int mFlags; + IBinder mLocalWin; + ClipData mData; + ClipDescription mDataDescription; + boolean mDragResult; + float mCurrentX, mCurrentY; + float mThumbOffsetX, mThumbOffsetY; + InputChannel mServerChannel, mClientChannel; + WindowState mTargetWindow; + ArrayList<WindowState> mNotifiedWindows; + boolean mDragInProgress; + + private final Region mTmpRegion = new Region(); + + DragState(IBinder token, Surface surface, int flags, IBinder localWin) { + mToken = token; + mSurface = surface; + mFlags = flags; + mLocalWin = localWin; + mNotifiedWindows = new ArrayList<WindowState>(); + } + + void reset() { + if (mSurface != null) { + mSurface.destroy(); + } + mSurface = null; + mFlags = 0; + mLocalWin = null; + mToken = null; + mData = null; + mThumbOffsetX = mThumbOffsetY = 0; + mNotifiedWindows = null; + } + + void register() { + if (DEBUG_DRAG) Slog.d(TAG, "registering drag input channel"); + if (mClientChannel != null) { + Slog.e(TAG, "Duplicate register of drag input channel"); + } else { + InputChannel[] channels = InputChannel.openInputChannelPair("drag"); + mServerChannel = channels[0]; + mClientChannel = channels[1]; + mInputManager.registerInputChannel(mServerChannel, null); + InputQueue.registerInputChannel(mClientChannel, mDragInputHandler, + mH.getLooper().getQueue()); + } + } + + void unregister() { + if (DEBUG_DRAG) Slog.d(TAG, "unregistering drag input channel"); + if (mClientChannel == null) { + Slog.e(TAG, "Unregister of nonexistent drag input channel"); + } else { + mInputManager.unregisterInputChannel(mServerChannel); + InputQueue.unregisterInputChannel(mClientChannel); + mClientChannel.dispose(); + mServerChannel.dispose(); + mClientChannel = null; + mServerChannel = null; + } + } + + int getDragLayerLw() { + return mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG) + * TYPE_LAYER_MULTIPLIER + + TYPE_LAYER_OFFSET; + } + + /* call out to each visible window/session informing it about the drag + */ + void broadcastDragStartedLw(final float touchX, final float touchY) { + // Cache a base-class instance of the clip metadata so that parceling + // works correctly in calling out to the apps. + mDataDescription = (mData != null) ? mData.getDescription() : null; + mNotifiedWindows.clear(); + mDragInProgress = true; + + if (DEBUG_DRAG) { + Slog.d(TAG, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")"); + } + + final int N = mWindows.size(); + for (int i = 0; i < N; i++) { + sendDragStartedLw(mWindows.get(i), touchX, touchY, mDataDescription); + } + } + + /* helper - send a caller-provided event, presumed to be DRAG_STARTED, if the + * designated window is potentially a drop recipient. There are race situations + * around DRAG_ENDED broadcast, so we make sure that once we've declared that + * the drag has ended, we never send out another DRAG_STARTED for this drag action. + * + * This method clones the 'event' parameter if it's being delivered to the same + * process, so it's safe for the caller to call recycle() on the event afterwards. + */ + private void sendDragStartedLw(WindowState newWin, float touchX, float touchY, + ClipDescription desc) { + // Don't actually send the event if the drag is supposed to be pinned + // to the originating window but 'newWin' is not that window. + if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) { + final IBinder winBinder = newWin.mClient.asBinder(); + if (winBinder != mLocalWin) { + if (DEBUG_DRAG) { + Slog.d(TAG, "Not dispatching local DRAG_STARTED to " + newWin); + } + return; + } + } + + if (mDragInProgress && newWin.isPotentialDragTarget()) { + DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED, + touchX - newWin.mFrame.left, touchY - newWin.mFrame.top, + null, desc, null, false); + try { + newWin.mClient.dispatchDragEvent(event); + // track each window that we've notified that the drag is starting + mNotifiedWindows.add(newWin); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to drag-start window " + newWin); + } finally { + // if the callee was local, the dispatch has already recycled the event + if (Process.myPid() != newWin.mSession.mPid) { + event.recycle(); + } + } + } + } + + /* helper - construct and send a DRAG_STARTED event only if the window has not + * previously been notified, i.e. it became visible after the drag operation + * was begun. This is a rare case. + */ + private void sendDragStartedIfNeededLw(WindowState newWin) { + if (mDragInProgress) { + // If we have sent the drag-started, we needn't do so again + for (WindowState ws : mNotifiedWindows) { + if (ws == newWin) { + return; + } + } + if (DEBUG_DRAG) { + Slog.d(TAG, "need to send DRAG_STARTED to new window " + newWin); + } + sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription); + } + } + + void broadcastDragEndedLw() { + if (DEBUG_DRAG) { + Slog.d(TAG, "broadcasting DRAG_ENDED"); + } + DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, + 0, 0, null, null, null, mDragResult); + for (WindowState ws: mNotifiedWindows) { + try { + ws.mClient.dispatchDragEvent(evt); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to drag-end window " + ws); + } + } + mNotifiedWindows.clear(); + mDragInProgress = false; + evt.recycle(); + } + + void endDragLw() { + mDragState.broadcastDragEndedLw(); + + // stop intercepting input + mDragState.unregister(); + mInputMonitor.setUpdateInputWindowsNeededLw(); + mInputMonitor.updateInputWindowsLw(); + + // free our resources and drop all the object references + mDragState.reset(); + mDragState = null; + + if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-drag rotation"); + boolean changed = setRotationUncheckedLocked( + WindowManagerPolicy.USE_LAST_ROTATION, 0, false); + if (changed) { + sendNewConfiguration(); + } + } + + void notifyMoveLw(float x, float y) { + final int myPid = Process.myPid(); + + // Move the surface to the given touch + if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION notifyMoveLw"); + Surface.openTransaction(); + try { + mSurface.setPosition((int)(x - mThumbOffsetX), (int)(y - mThumbOffsetY)); + } finally { + Surface.closeTransaction(); + if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION notifyMoveLw"); + } + + // Tell the affected window + WindowState touchedWin = getTouchedWinAtPointLw(x, y); + if (touchedWin == null) { + if (DEBUG_DRAG) Slog.d(TAG, "No touched win at x=" + x + " y=" + y); + return; + } + if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) { + final IBinder touchedBinder = touchedWin.mClient.asBinder(); + if (touchedBinder != mLocalWin) { + // This drag is pinned only to the originating window, but the drag + // point is outside that window. Pretend it's over empty space. + touchedWin = null; + } + } + try { + // have we dragged over a new window? + if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) { + if (DEBUG_DRAG) { + Slog.d(TAG, "sending DRAG_EXITED to " + mTargetWindow); + } + // force DRAG_EXITED_EVENT if appropriate + DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_EXITED, + x - mTargetWindow.mFrame.left, y - mTargetWindow.mFrame.top, + null, null, null, false); + mTargetWindow.mClient.dispatchDragEvent(evt); + if (myPid != mTargetWindow.mSession.mPid) { + evt.recycle(); + } + } + if (touchedWin != null) { + if (false && DEBUG_DRAG) { + Slog.d(TAG, "sending DRAG_LOCATION to " + touchedWin); + } + DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_LOCATION, + x - touchedWin.mFrame.left, y - touchedWin.mFrame.top, + null, null, null, false); + touchedWin.mClient.dispatchDragEvent(evt); + if (myPid != touchedWin.mSession.mPid) { + evt.recycle(); + } + } + } catch (RemoteException e) { + Slog.w(TAG, "can't send drag notification to windows"); + } + mTargetWindow = touchedWin; + } + + // Tell the drop target about the data. Returns 'true' if we can immediately + // dispatch the global drag-ended message, 'false' if we need to wait for a + // result from the recipient. + boolean notifyDropLw(float x, float y) { + WindowState touchedWin = getTouchedWinAtPointLw(x, y); + if (touchedWin == null) { + // "drop" outside a valid window -- no recipient to apply a + // timeout to, and we can send the drag-ended message immediately. + mDragResult = false; + return true; + } + + if (DEBUG_DRAG) { + Slog.d(TAG, "sending DROP to " + touchedWin); + } + final int myPid = Process.myPid(); + final IBinder token = touchedWin.mClient.asBinder(); + DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DROP, + x - touchedWin.mFrame.left, y - touchedWin.mFrame.top, + null, null, mData, false); + try { + touchedWin.mClient.dispatchDragEvent(evt); + + // 5 second timeout for this window to respond to the drop + mH.removeMessages(H.DRAG_END_TIMEOUT, token); + Message msg = mH.obtainMessage(H.DRAG_END_TIMEOUT, token); + mH.sendMessageDelayed(msg, 5000); + } catch (RemoteException e) { + Slog.w(TAG, "can't send drop notification to win " + touchedWin); + return true; + } finally { + if (myPid != touchedWin.mSession.mPid) { + evt.recycle(); + } + } + mToken = token; + return false; + } + + // Find the visible, touch-deliverable window under the given point + private WindowState getTouchedWinAtPointLw(float xf, float yf) { + WindowState touchedWin = null; + final int x = (int) xf; + final int y = (int) yf; + final ArrayList<WindowState> windows = mWindows; + final int N = windows.size(); + for (int i = N - 1; i >= 0; i--) { + WindowState child = windows.get(i); + final int flags = child.mAttrs.flags; + if (!child.isVisibleLw()) { + // not visible == don't tell about drags + continue; + } + if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) { + // not touchable == don't tell about drags + continue; + } + + child.getTouchableRegion(mTmpRegion); + + final int touchFlags = flags & + (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); + if (mTmpRegion.contains(x, y) || touchFlags == 0) { + // Found it + touchedWin = child; + break; + } + } + + return touchedWin; + } + } + + DragState mDragState = null; + private final InputHandler mDragInputHandler = new BaseInputHandler() { + @Override + public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) { + boolean handled = false; + try { + if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 + && mDragState != null) { + boolean endDrag = false; + final float newX = event.getRawX(); + final float newY = event.getRawY(); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: { + if (DEBUG_DRAG) { + Slog.w(TAG, "Unexpected ACTION_DOWN in drag layer"); + } + } break; + + case MotionEvent.ACTION_MOVE: { + synchronized (mWindowMap) { + // move the surface and tell the involved window(s) where we are + mDragState.notifyMoveLw(newX, newY); + } + } break; + + case MotionEvent.ACTION_UP: { + if (DEBUG_DRAG) Slog.d(TAG, "Got UP on move channel; dropping at " + + newX + "," + newY); + synchronized (mWindowMap) { + endDrag = mDragState.notifyDropLw(newX, newY); + } + } break; + + case MotionEvent.ACTION_CANCEL: { + if (DEBUG_DRAG) Slog.d(TAG, "Drag cancelled!"); + endDrag = true; + } break; + } + + if (endDrag) { + if (DEBUG_DRAG) Slog.d(TAG, "Drag ended; tearing down state"); + // tell all the windows that the drag has ended + synchronized (mWindowMap) { + mDragState.endDragLw(); + } + } + + handled = true; + } + } catch (Exception e) { + Slog.e(TAG, "Exception caught by drag handleMotion", e); + } finally { + finishedCallback.finished(handled); + } + } + }; + + /** * Whether the UI is currently running in touch mode (not showing * navigational focus because the user is directly pressing the screen). */ @@ -555,6 +954,11 @@ public class WindowManagerService extends IWindowManager.Stub notifyAll(); } + // For debug builds, log event loop stalls to dropbox for analysis. + if (StrictMode.conditionallyEnableDebugLogging()) { + Slog.i(TAG, "Enabled StrictMode logging for WMThread's Looper"); + } + Looper.loop(); } } @@ -592,6 +996,11 @@ public class WindowManagerService extends IWindowManager.Stub notifyAll(); } + // For debug builds, log event loop stalls to dropbox for analysis. + if (StrictMode.conditionallyEnableDebugLogging()) { + Slog.i(TAG, "Enabled StrictMode for PolicyThread's Looper"); + } + Looper.loop(); } } @@ -665,7 +1074,7 @@ public class WindowManagerService extends IWindowManager.Stub private void placeWindowAfter(WindowState pos, WindowState window) { final int i = mWindows.indexOf(pos); - if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v( + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v( TAG, "Adding window " + window + " at " + (i+1) + " of " + mWindows.size() + " (after " + pos + ")"); mWindows.add(i+1, window); @@ -674,7 +1083,7 @@ public class WindowManagerService extends IWindowManager.Stub private void placeWindowBefore(WindowState pos, WindowState window) { final int i = mWindows.indexOf(pos); - if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v( + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v( TAG, "Adding window " + window + " at " + i + " of " + mWindows.size() + " (before " + pos + ")"); mWindows.add(i, window); @@ -732,9 +1141,10 @@ public class WindowManagerService extends IWindowManager.Stub //apptoken note that the window could be a floating window //that was created later or a window at the top of the list of //windows associated with this token. - if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v( - TAG, "Adding window " + win + " at " - + (newIdx+1) + " of " + N); + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) { + Slog.v(TAG, "Adding window " + win + " at " + + (newIdx+1) + " of " + N); + } localmWindows.add(newIdx+1, win); mWindowsChanged = true; } @@ -813,9 +1223,10 @@ public class WindowManagerService extends IWindowManager.Stub break; } } - if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v( - TAG, "Adding window " + win + " at " - + i + " of " + N); + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) { + Slog.v(TAG, "Adding window " + win + " at " + + i + " of " + N); + } localmWindows.add(i, win); mWindowsChanged = true; } @@ -831,13 +1242,14 @@ public class WindowManagerService extends IWindowManager.Stub } } if (i < 0) i = 0; - if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v( + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v( TAG, "Adding window " + win + " at " + i + " of " + N); localmWindows.add(i, win); mWindowsChanged = true; } if (addToToken) { + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); token.windows.add(tokenWindowsPos, win); } @@ -860,6 +1272,7 @@ public class WindowManagerService extends IWindowManager.Stub // in the same sublayer. if (wSublayer >= sublayer) { if (addToToken) { + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); token.windows.add(i, win); } placeWindowBefore( @@ -871,6 +1284,7 @@ public class WindowManagerService extends IWindowManager.Stub // in the same sublayer. if (wSublayer > sublayer) { if (addToToken) { + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); token.windows.add(i, win); } placeWindowBefore(w, win); @@ -880,6 +1294,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (i >= NA) { if (addToToken) { + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); token.windows.add(win); } if (sublayer < 0) { @@ -939,7 +1354,20 @@ public class WindowManagerService extends IWindowManager.Stub } } - mUpcomingInputMethodTarget = w; + // Now, a special case -- if the last target's window is in the + // process of exiting, and is above the new target, keep on the + // last target to avoid flicker. Consider for example a Dialog with + // the IME shown: when the Dialog is dismissed, we want to keep + // the IME above it until it is completely gone so it doesn't drop + // behind the dialog or its full-screen scrim. + if (mInputMethodTarget != null && w != null + && mInputMethodTarget.isDisplayedLw() + && mInputMethodTarget.mExiting) { + if (mInputMethodTarget.mAnimLayer > w.mAnimLayer) { + w = mInputMethodTarget; + i = localmWindows.indexOf(w); + } + } if (DEBUG_INPUT_METHOD) Slog.v(TAG, "Desired input method target=" + w + " willMove=" + willMove); @@ -1041,7 +1469,7 @@ public class WindowManagerService extends IWindowManager.Stub int pos = findDesiredInputMethodWindowIndexLocked(true); if (pos >= 0) { win.mTargetAppToken = mInputMethodTarget.mAppToken; - if (DEBUG_WINDOW_MOVEMENT) Slog.v( + if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v( TAG, "Adding input method window " + win + " at " + pos); mWindows.add(pos, win); mWindowsChanged = true; @@ -1228,7 +1656,7 @@ public class WindowManagerService extends IWindowManager.Stub } imPos = tmpRemoveWindowLocked(imPos, imWin); if (DEBUG_INPUT_METHOD) { - Slog.v(TAG, "List after moving with new pos " + imPos + ":"); + Slog.v(TAG, "List after removing with new pos " + imPos + ":"); logWindowList(" "); } imWin.mTargetAppToken = mInputMethodTarget.mAppToken; @@ -1305,6 +1733,7 @@ public class WindowManagerService extends IWindowManager.Stub int foundI = 0; WindowState topCurW = null; int topCurI = 0; + int windowDetachedI = -1; int i = N; while (i > 0) { i--; @@ -1317,13 +1746,12 @@ public class WindowManagerService extends IWindowManager.Stub continue; } topCurW = null; - if (w.mAppToken != null) { + if (w != mWindowDetachedWallpaper && w.mAppToken != null) { // If this window's app token is hidden and not animating, // it is of no interest to us. if (w.mAppToken.hidden && w.mAppToken.animation == null) { if (DEBUG_WALLPAPER) Slog.v(TAG, - "Skipping hidden or animating token: " + w); - topCurW = null; + "Skipping not hidden or animating token: " + w); continue; } } @@ -1348,9 +1776,18 @@ public class WindowManagerService extends IWindowManager.Stub continue; } break; + } else if (w == mWindowDetachedWallpaper) { + windowDetachedI = i; } } + if (foundW == null && windowDetachedI >= 0) { + if (DEBUG_WALLPAPER) Slog.v(TAG, + "Found animating detached wallpaper activity: #" + i + "=" + w); + foundW = w; + foundI = windowDetachedI; + } + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { // If we are currently waiting for an app transition, and either // the current target or the next target are involved with it, @@ -1482,6 +1919,7 @@ public class WindowManagerService extends IWindowManager.Stub WindowState wb = localmWindows.get(foundI-1); if (wb.mBaseLayer < maxLayer && wb.mAttachedWindow != foundW && + wb.mAttachedWindow != foundW.mAttachedWindow && (wb.mAttrs.type != TYPE_APPLICATION_STARTING || wb.mToken != foundW.mToken)) { // This window is not related to the previous one in any @@ -1581,9 +2019,10 @@ public class WindowManagerService extends IWindowManager.Stub } // Now stick it in. - if (DEBUG_WALLPAPER || DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, - "Moving wallpaper " + wallpaper - + " from " + oldIndex + " to " + foundI); + if (DEBUG_WALLPAPER || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) { + Slog.v(TAG, "Moving wallpaper " + wallpaper + + " from " + oldIndex + " to " + foundI); + } localmWindows.add(foundI, wallpaper); mWindowsChanged = true; @@ -1788,18 +2227,11 @@ public class WindowManagerService extends IWindowManager.Stub boolean reportNewConfig = false; WindowState attachedWindow = null; WindowState win = null; + long origId; synchronized(mWindowMap) { - // Instantiating a Display requires talking with the simulator, - // so don't do it until we know the system is mostly up and - // running. if (mDisplay == null) { - WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); - mDisplay = wm.getDefaultDisplay(); - mInitialDisplayWidth = mDisplay.getWidth(); - mInitialDisplayHeight = mDisplay.getHeight(); - mInputManager.setDisplaySize(0, mInitialDisplayWidth, mInitialDisplayHeight); - reportNewConfig = true; + throw new IllegalStateException("Display has not been initialialized"); } if (mWindowMap.containsKey(client.asBinder())) { @@ -1898,18 +2330,17 @@ public class WindowManagerService extends IWindowManager.Stub win.mInputChannel = inputChannels[0]; inputChannels[1].transferToBinderOutParameter(outInputChannel); - mInputManager.registerInputChannel(win.mInputChannel); + mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle); } // From now on, no exceptions or errors allowed! res = WindowManagerImpl.ADD_OKAY; - final long origId = Binder.clearCallingIdentity(); + origId = Binder.clearCallingIdentity(); if (addToken) { mTokenMap.put(attrs.token, token); - mTokenList.add(token); } win.attach(); mWindowMap.put(client.asBinder(), win); @@ -1953,7 +2384,8 @@ public class WindowManagerService extends IWindowManager.Stub boolean focusChanged = false; if (win.canReceiveKeys()) { - focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS); + focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS, + false /*updateInputWindows*/); if (focusChanged) { imMayMove = false; } @@ -1970,26 +2402,23 @@ public class WindowManagerService extends IWindowManager.Stub //dump(); if (focusChanged) { - finishUpdateFocusedWindowAfterAssignLayersLocked(); + finishUpdateFocusedWindowAfterAssignLayersLocked(false /*updateInputWindows*/); } - + mInputMonitor.updateInputWindowsLw(); + if (localLOGV) Slog.v( TAG, "New client " + client.asBinder() + ": window=" + win); - if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked()) { + if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) { reportNewConfig = true; } } - // sendNewConfiguration() checks caller permissions so we must call it with - // privilege. updateOrientationFromAppTokens() clears and resets the caller - // identity anyway, so it's safe to just clear & restore around this whole - // block. - final long origId = Binder.clearCallingIdentity(); if (reportNewConfig) { sendNewConfiguration(); } + Binder.restoreCallingIdentity(origId); return res; @@ -2052,8 +2481,10 @@ public class WindowManagerService extends IWindowManager.Stub win.mExiting = true; win.mRemoveOnExit = true; mLayoutNeeded = true; - updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES); + updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/); performLayoutAndPlaceSurfacesLocked(); + mInputMonitor.updateInputWindowsLw(); if (win.mAppToken != null) { win.mAppToken.updateReportedVisibilityLocked(); } @@ -2068,14 +2499,26 @@ public class WindowManagerService extends IWindowManager.Stub // So just update orientation if needed. if (wasVisible && computeForcedAppOrientationLocked() != mForcedAppOrientation - && updateOrientationFromAppTokensLocked()) { + && updateOrientationFromAppTokensLocked(false)) { mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); } - updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL); + updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/); Binder.restoreCallingIdentity(origId); } private void removeWindowInnerLocked(Session session, WindowState win) { + if (win.mRemoved) { + // Nothing to do. + return; + } + + for (int i=win.mChildWindows.size()-1; i>=0; i--) { + WindowState cwin = win.mChildWindows.get(i); + Slog.w(TAG, "Force-removing child win " + cwin + " from container " + + win); + removeWindowInnerLocked(cwin.mSession, cwin); + } + win.mRemoved = true; if (mInputMethodTarget == win) { @@ -2091,8 +2534,10 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.removeWindowLw(win); win.removeLocked(); + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "removeWindowInnerLocked: " + win); mWindowMap.remove(win.mClient.asBinder()); mWindows.remove(win); + mPendingRemove.remove(win); mWindowsChanged = true; if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Final remove of window: " + win); @@ -2104,6 +2549,7 @@ public class WindowManagerService extends IWindowManager.Stub final WindowToken token = win.mToken; final AppWindowToken atoken = win.mAppToken; + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing " + win + " from " + token); token.windows.remove(win); if (atoken != null) { atoken.allAppWindows.remove(win); @@ -2114,7 +2560,6 @@ public class WindowManagerService extends IWindowManager.Stub if (token.windows.size() == 0) { if (!token.explicit) { mTokenMap.remove(token.token); - mTokenList.remove(token); } else if (atoken != null) { atoken.firstWindowDrawn = false; } @@ -2155,6 +2600,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + mInputMonitor.setUpdateInputWindowsNeededLw(); mInputMonitor.updateInputWindowsLw(); } @@ -2174,15 +2620,17 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { WindowState w = windowForClientLocked(session, client, false); if ((w != null) && (w.mSurface != null)) { - if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION"); + if (SHOW_TRANSACTIONS) Slog.i(TAG, + ">>> OPEN TRANSACTION setTransparentRegion"); Surface.openTransaction(); try { if (SHOW_TRANSACTIONS) logSurface(w, "transparentRegionHint=" + region, null); w.mSurface.setTransparentRegionHint(region); } finally { - if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION"); Surface.closeTransaction(); + if (SHOW_TRANSACTIONS) Slog.i(TAG, + "<<< CLOSE TRANSACTION setTransparentRegion"); } } } @@ -2193,7 +2641,7 @@ public class WindowManagerService extends IWindowManager.Stub void setInsetsWindow(Session session, IWindow client, int touchableInsets, Rect contentInsets, - Rect visibleInsets) { + Rect visibleInsets, Region touchableRegion) { long origId = Binder.clearCallingIdentity(); try { synchronized (mWindowMap) { @@ -2202,6 +2650,7 @@ public class WindowManagerService extends IWindowManager.Stub w.mGivenInsetsPending = false; w.mGivenContentInsets.set(contentInsets); w.mGivenVisibleInsets.set(visibleInsets); + w.mGivenTouchableRegion.set(touchableRegion); w.mTouchableInsets = touchableInsets; mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); @@ -2346,7 +2795,10 @@ public class WindowManagerService extends IWindowManager.Stub displayed = !win.isVisibleLw(); if (win.mExiting) { win.mExiting = false; - win.mAnimation = null; + if (win.mAnimation != null) { + win.mAnimation.cancel(); + win.mAnimation = null; + } } if (win.mDestroying) { win.mDestroying = false; @@ -2399,6 +2851,7 @@ public class WindowManagerService extends IWindowManager.Stub outSurface.release(); } } catch (Exception e) { + mInputMonitor.setUpdateInputWindowsNeededLw(); mInputMonitor.updateInputWindowsLw(); Slog.w(TAG, "Exception thrown when creating surface for client " @@ -2486,7 +2939,8 @@ public class WindowManagerService extends IWindowManager.Stub if (focusMayChange) { //System.out.println("Focus may change: " + win.mAttrs.getTitle()); - if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) { + if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/)) { imMayMove = false; } //System.out.println("Relayout " + win + ": focus=" + mCurrentFocus); @@ -2517,7 +2971,7 @@ public class WindowManagerService extends IWindowManager.Stub if (assignLayers) { assignLayersLocked(); } - configChanged = updateOrientationFromAppTokensLocked(); + configChanged = updateOrientationFromAppTokensLocked(false); performLayoutAndPlaceSurfacesLocked(); if (displayed && win.mIsWallpaper) { updateWallpaperOffsetLocked(win, mDisplay.getWidth(), @@ -2542,6 +2996,7 @@ public class WindowManagerService extends IWindowManager.Stub inTouchMode = mInTouchMode; + mInputMonitor.setUpdateInputWindowsNeededLw(); mInputMonitor.updateInputWindowsLw(); } @@ -2571,7 +3026,7 @@ public class WindowManagerService extends IWindowManager.Stub } private AttributeCache.Entry getCachedAnimations(WindowManager.LayoutParams lp) { - if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: params package=" + if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg=" + (lp != null ? lp.packageName : null) + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null)); if (lp != null && lp.windowAnimations != 0) { @@ -2592,7 +3047,7 @@ public class WindowManagerService extends IWindowManager.Stub } private AttributeCache.Entry getCachedAnimations(String packageName, int resId) { - if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: params package=" + if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: package=" + packageName + " resId=0x" + Integer.toHexString(resId)); if (packageName != null) { if ((resId&0xFF000000) == 0x01000000) { @@ -2874,7 +3329,6 @@ public class WindowManagerService extends IWindowManager.Stub } wtoken = new WindowToken(token, type, true); mTokenMap.put(token, wtoken); - mTokenList.add(wtoken); if (type == TYPE_WALLPAPER) { mWallpaperTokens.add(wtoken); } @@ -2890,7 +3344,6 @@ public class WindowManagerService extends IWindowManager.Stub final long origId = Binder.clearCallingIdentity(); synchronized(mWindowMap) { WindowToken wtoken = mTokenMap.remove(token); - mTokenList.remove(wtoken); if (wtoken != null) { boolean delayed = false; if (!wtoken.hidden) { @@ -2916,7 +3369,8 @@ public class WindowManagerService extends IWindowManager.Stub if (changed) { mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); - updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL); + updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, + false /*updateInputWindows*/); } if (delayed) { @@ -2926,6 +3380,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + mInputMonitor.setUpdateInputWindowsNeededLw(); mInputMonitor.updateInputWindowsLw(); } else { Slog.w(TAG, "Attempted to remove non-existing token: " + token); @@ -2966,10 +3421,9 @@ public class WindowManagerService extends IWindowManager.Stub wtoken.groupId = groupId; wtoken.appFullscreen = fullscreen; wtoken.requestedOrientation = requestedOrientation; + if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "addAppToken: " + wtoken); mAppTokens.add(addPos, wtoken); - if (localLOGV) Slog.v(TAG, "Adding new app token: " + wtoken); mTokenMap.put(token.asBinder(), wtoken); - mTokenList.add(wtoken); // Application tokens start out hidden. wtoken.hidden = true; @@ -3086,7 +3540,7 @@ public class WindowManagerService extends IWindowManager.Stub long ident = Binder.clearCallingIdentity(); synchronized(mWindowMap) { - if (updateOrientationFromAppTokensLocked()) { + if (updateOrientationFromAppTokensLocked(false)) { if (freezeThisOneIfNeeded != null) { AppWindowToken wtoken = findAppWindowToken( freezeThisOneIfNeeded); @@ -3108,7 +3562,7 @@ public class WindowManagerService extends IWindowManager.Stub if (currentConfig.diff(mTempConfiguration) != 0) { mWaitingForConfig = true; mLayoutNeeded = true; - startFreezingDisplayLocked(); + startFreezingDisplayLocked(false); config = new Configuration(mTempConfiguration); } } @@ -3133,8 +3587,8 @@ public class WindowManagerService extends IWindowManager.Stub * @see android.view.IWindowManager#updateOrientationFromAppTokens( * android.os.IBinder) */ - boolean updateOrientationFromAppTokensLocked() { - if (mDisplayFrozen) { + boolean updateOrientationFromAppTokensLocked(boolean inTransaction) { + if (mDisplayFrozen || mOpeningApps.size() > 0 || mClosingApps.size() > 0) { // If the display is frozen, some activities may be in the middle // of restarting, and thus have removed their old window. If the // window has the flag to hide the lock screen, then the lock screen @@ -3154,7 +3608,8 @@ public class WindowManagerService extends IWindowManager.Stub //action like disabling/enabling sensors etc., mPolicy.setCurrentOrientationLw(req); if (setRotationUncheckedLocked(WindowManagerPolicy.USE_LAST_ROTATION, - mLastRotationFlags | Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE)) { + mLastRotationFlags | Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE, + inTransaction)) { changed = true; } } @@ -3245,13 +3700,13 @@ public class WindowManagerService extends IWindowManager.Stub if (moveFocusNow && changed) { final long origId = Binder.clearCallingIdentity(); - updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL); + updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/); Binder.restoreCallingIdentity(origId); } } } - public void prepareAppTransition(int transit) { + public void prepareAppTransition(int transit, boolean alwaysKeepCurrent) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "prepareAppTransition()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); @@ -3265,14 +3720,16 @@ public class WindowManagerService extends IWindowManager.Stub if (mNextAppTransition == WindowManagerPolicy.TRANSIT_UNSET || mNextAppTransition == WindowManagerPolicy.TRANSIT_NONE) { mNextAppTransition = transit; - } else if (transit == WindowManagerPolicy.TRANSIT_TASK_OPEN - && mNextAppTransition == WindowManagerPolicy.TRANSIT_TASK_CLOSE) { - // Opening a new task always supersedes a close for the anim. - mNextAppTransition = transit; - } else if (transit == WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN - && mNextAppTransition == WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE) { - // Opening a new activity always supersedes a close for the anim. - mNextAppTransition = transit; + } else if (!alwaysKeepCurrent) { + if (transit == WindowManagerPolicy.TRANSIT_TASK_OPEN + && mNextAppTransition == WindowManagerPolicy.TRANSIT_TASK_CLOSE) { + // Opening a new task always supersedes a close for the anim. + mNextAppTransition = transit; + } else if (transit == WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN + && mNextAppTransition == WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE) { + // Opening a new activity always supersedes a close for the anim. + mNextAppTransition = transit; + } } mAppTransitionReady = false; mAppTransitionTimeout = false; @@ -3322,7 +3779,7 @@ public class WindowManagerService extends IWindowManager.Stub public void setAppStartingWindow(IBinder token, String pkg, int theme, CharSequence nonLocalizedLabel, int labelRes, int icon, - IBinder transferFrom, boolean createIfNeeded) { + int windowFlags, IBinder transferFrom, boolean createIfNeeded) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "setAppStartingIcon()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); @@ -3378,10 +3835,12 @@ public class WindowManagerService extends IWindowManager.Stub startingWindow.mToken = wtoken; startingWindow.mRootToken = wtoken; startingWindow.mAppToken = wtoken; - if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, + if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing starting window: " + startingWindow); mWindows.remove(startingWindow); mWindowsChanged = true; + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing starting " + startingWindow + + " from " + ttoken); ttoken.windows.remove(startingWindow); ttoken.allAppWindows.remove(startingWindow); addWindowToListInOrderLocked(startingWindow, true); @@ -3416,7 +3875,8 @@ public class WindowManagerService extends IWindowManager.Stub ttoken.updateLayers(); } - updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES); + updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + true /*updateInputWindows*/); mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); Binder.restoreCallingIdentity(origId); @@ -3470,7 +3930,7 @@ public class WindowManagerService extends IWindowManager.Stub mStartingIconInTransition = true; wtoken.startingData = new StartingData( pkg, theme, nonLocalizedLabel, - labelRes, icon); + labelRes, icon, windowFlags); Message m = mH.obtainMessage(H.ADD_STARTING, wtoken); // Note: we really want to do sendMessageAtFrontOfQueue() because we // want to process the message ASAP, before any other queued @@ -3576,12 +4036,13 @@ public class WindowManagerService extends IWindowManager.Stub if (changed) { mLayoutNeeded = true; + mInputMonitor.setUpdateInputWindowsNeededLw(); if (performLayout) { - updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES); + updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/); performLayoutAndPlaceSurfacesLocked(); - } else { - mInputMonitor.updateInputWindowsLw(); } + mInputMonitor.updateInputWindowsLw(); } } @@ -3727,7 +4188,7 @@ public class WindowManagerService extends IWindowManager.Stub wtoken.freezingScreen = true; mAppsFreezingScreen++; if (mAppsFreezingScreen == 1) { - startFreezingDisplayLocked(); + startFreezingDisplayLocked(false); mH.removeMessages(H.APP_FREEZE_TIMEOUT); mH.sendMessageDelayed(mH.obtainMessage(H.APP_FREEZE_TIMEOUT), 5000); @@ -3796,7 +4257,6 @@ public class WindowManagerService extends IWindowManager.Stub final long origId = Binder.clearCallingIdentity(); synchronized(mWindowMap) { WindowToken basewtoken = mTokenMap.remove(token); - mTokenList.remove(basewtoken); if (basewtoken != null && (wtoken=basewtoken.appWindowToken) != null) { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Removing app token: " + wtoken); delayed = setTokenVisibilityLocked(wtoken, null, false, WindowManagerPolicy.TRANSIT_UNSET, true); @@ -3816,6 +4276,8 @@ public class WindowManagerService extends IWindowManager.Stub + " animating=" + wtoken.animating); if (delayed) { // set the token aside because it has an active animation to be finished + if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, + "removeAppToken make exiting: " + wtoken); mExitingAppTokens.add(wtoken); } else { // Make sure there is no animation running on this token, @@ -3824,11 +4286,9 @@ public class WindowManagerService extends IWindowManager.Stub wtoken.animation = null; wtoken.animating = false; } + if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, + "removeAppToken: " + wtoken); mAppTokens.remove(wtoken); - if (mLastEnterAnimToken == wtoken) { - mLastEnterAnimToken = null; - mLastEnterAnimParams = null; - } wtoken.removed = true; if (wtoken.startingData != null) { startingToken = wtoken; @@ -3837,7 +4297,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mFocusedApp == wtoken) { if (DEBUG_FOCUS) Slog.v(TAG, "Removing focused app token:" + wtoken); mFocusedApp = null; - updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL); + updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/); mInputMonitor.setFocusedAppLw(null); } } else { @@ -3954,18 +4414,21 @@ public class WindowManagerService extends IWindowManager.Stub if (!added && cwin.mSubLayer >= 0) { if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding child window at " + index + ": " + cwin); + win.mRebuilding = false; mWindows.add(index, win); index++; added = true; } if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding window at " + index + ": " + cwin); + cwin.mRebuilding = false; mWindows.add(index, cwin); index++; } if (!added) { if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding window at " + index + ": " + win); + win.mRebuilding = false; mWindows.add(index, win); index++; } @@ -3991,6 +4454,9 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_REORDER) Slog.v(TAG, "Initial app tokens:"); if (DEBUG_REORDER) dumpAppTokensLocked(); final AppWindowToken wtoken = findAppWindowToken(token); + if (DEBUG_TOKEN_MOVEMENT || DEBUG_REORDER) Slog.v(TAG, + "Start moving token " + wtoken + " initially at " + + mAppTokens.indexOf(wtoken)); if (wtoken == null || !mAppTokens.remove(wtoken)) { Slog.w(TAG, "Attempting to reorder token that doesn't exist: " + token + " (" + wtoken + ")"); @@ -3998,6 +4464,7 @@ public class WindowManagerService extends IWindowManager.Stub } mAppTokens.add(index, wtoken); if (DEBUG_REORDER) Slog.v(TAG, "Moved " + token + " to " + index + ":"); + else if (DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "Moved " + token + " to " + index); if (DEBUG_REORDER) dumpAppTokensLocked(); final long origId = Binder.clearCallingIdentity(); @@ -4009,9 +4476,11 @@ public class WindowManagerService extends IWindowManager.Stub reAddAppWindowsLocked(findWindowOffsetLocked(index), wtoken); if (DEBUG_REORDER) Slog.v(TAG, "Final window list:"); if (DEBUG_REORDER) dumpWindowsLocked(); - updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES); + updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/); mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); + mInputMonitor.updateInputWindowsLw(); } Binder.restoreCallingIdentity(origId); } @@ -4025,6 +4494,8 @@ public class WindowManagerService extends IWindowManager.Stub for (int i=0; i<N; i++) { IBinder token = tokens.get(i); final AppWindowToken wtoken = findAppWindowToken(token); + if (DEBUG_REORDER || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, + "Temporarily removing " + wtoken + " from " + mAppTokens.indexOf(wtoken)); if (!mAppTokens.remove(wtoken)) { Slog.w(TAG, "Attempting to reorder token that doesn't exist: " + token + " (" + wtoken + ")"); @@ -4046,11 +4517,13 @@ public class WindowManagerService extends IWindowManager.Stub pos = reAddAppWindowsLocked(pos, wtoken); if (updateFocusAndLayout) { - if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) { + if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/)) { assignLayersLocked(); } mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); + mInputMonitor.updateInputWindowsLw(); } } @@ -4076,11 +4549,13 @@ public class WindowManagerService extends IWindowManager.Stub } } - if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) { + if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/)) { assignLayersLocked(); } mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); + mInputMonitor.updateInputWindowsLw(); //dump(); } @@ -4098,6 +4573,8 @@ public class WindowManagerService extends IWindowManager.Stub for (int i=0; i<N; i++) { AppWindowToken wt = findAppWindowToken(tokens.get(i)); if (wt != null) { + if (DEBUG_TOKEN_MOVEMENT || DEBUG_REORDER) Slog.v(TAG, + "Adding next to top: " + wt); mAppTokens.add(wt); if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { mToTopApps.remove(wt); @@ -4130,6 +4607,8 @@ public class WindowManagerService extends IWindowManager.Stub for (int i=0; i<N; i++) { AppWindowToken wt = findAppWindowToken(tokens.get(i)); if (wt != null) { + if (DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, + "Adding next to bottom: " + wt + " at " + pos); mAppTokens.add(pos, wt); if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { mToTopApps.remove(wt); @@ -4435,8 +4914,7 @@ public class WindowManagerService extends IWindowManager.Stub final int N = mWindows.size(); for (int i=0; i<N; i++) { WindowState w = mWindows.get(i); - if (w.isVisibleLw() && !w.mObscured - && (w.mOrientationChanging || !w.isDrawnLw())) { + if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) { return; } } @@ -4477,6 +4955,164 @@ public class WindowManagerService extends IWindowManager.Stub } } + // TODO: more accounting of which pid(s) turned it on, keep count, + // only allow disables from pids which have count on, etc. + public void showStrictModeViolation(boolean on) { + int pid = Binder.getCallingPid(); + synchronized(mWindowMap) { + // Ignoring requests to enable the red border from clients + // which aren't on screen. (e.g. Broadcast Receivers in + // the background..) + if (on) { + boolean isVisible = false; + for (WindowState ws : mWindows) { + if (ws.mSession.mPid == pid && ws.isVisibleLw()) { + isVisible = true; + break; + } + } + if (!isVisible) { + return; + } + } + + if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION showStrictModeViolation"); + Surface.openTransaction(); + try { + if (mStrictModeFlash == null) { + mStrictModeFlash = new StrictModeFlash(mDisplay, mFxSession); + } + mStrictModeFlash.setVisibility(on); + } finally { + Surface.closeTransaction(); + if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION showStrictModeViolation"); + } + } + } + + public void setStrictModeVisualIndicatorPreference(String value) { + SystemProperties.set(StrictMode.VISUAL_PROPERTY, value); + } + + public Bitmap screenshotApplications(IBinder appToken, int maxWidth, int maxHeight) { + if (!checkCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER, + "screenshotApplications()")) { + throw new SecurityException("Requires READ_FRAME_BUFFER permission"); + } + + Bitmap rawss; + + int maxLayer = 0; + boolean foundApp; + final Rect frame = new Rect(); + + float scale; + int sw, sh, dw, dh; + int rot; + + synchronized(mWindowMap) { + long ident = Binder.clearCallingIdentity(); + + int aboveAppLayer = mPolicy.windowTypeToLayerLw( + WindowManager.LayoutParams.TYPE_APPLICATION) * TYPE_LAYER_MULTIPLIER + + TYPE_LAYER_OFFSET; + aboveAppLayer += TYPE_LAYER_MULTIPLIER; + + // Figure out the part of the screen that is actually the app. + for (int i=0; i<mWindows.size(); i++) { + WindowState ws = mWindows.get(i); + if (ws.mSurface == null) { + continue; + } + if (ws.mLayer >= aboveAppLayer) { + break; + } + if (appToken != null && (ws.mAppToken == null + || ws.mAppToken.token != appToken)) { + continue; + } + if (maxLayer < ws.mAnimLayer) { + maxLayer = ws.mAnimLayer; + } + final Rect wf = ws.mFrame; + final Rect cr = ws.mContentInsets; + int left = wf.left + cr.left; + int top = wf.top + cr.top; + int right = wf.right - cr.right; + int bottom = wf.bottom - cr.bottom; + frame.union(left, top, right, bottom); + } + Binder.restoreCallingIdentity(ident); + + if (frame.isEmpty() || maxLayer == 0) { + return null; + } + + // The screenshot API does not apply the current screen rotation. + rot = mDisplay.getRotation(); + int fw = frame.width(); + int fh = frame.height(); + + // First try reducing to fit in x dimension. + scale = maxWidth/(float)fw; + sw = maxWidth; + sh = (int)(fh*scale); + if (sh > maxHeight) { + // y dimension became too long; constrain by that. + scale = maxHeight/(float)fh; + sw = (int)(fw*scale); + sh = maxHeight; + } + + // The screen shot will contain the entire screen. + dw = (int)(mDisplay.getWidth()*scale); + dh = (int)(mDisplay.getHeight()*scale); + if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) { + int tmp = dw; + dw = dh; + dh = tmp; + rot = (rot == Surface.ROTATION_90) ? Surface.ROTATION_270 : Surface.ROTATION_90; + } + rawss = Surface.screenshot(dw, dh, 0, maxLayer); + } + + if (rawss == null) { + Log.w(TAG, "Failure taking screenshot for (" + dw + "x" + dh + + ") to layer " + maxLayer); + return null; + } + + Bitmap bm = Bitmap.createBitmap(sw, sh, rawss.getConfig()); + Matrix matrix = new Matrix(); + ScreenRotationAnimation.createRotationMatrix(rot, dw, dh, matrix); + matrix.postTranslate(-(int)(frame.left*scale), -(int)(frame.top*scale)); + Canvas canvas = new Canvas(bm); + canvas.drawBitmap(rawss, matrix, null); + + rawss.recycle(); + return bm; + } + + public void freezeRotation() { + if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION, + "setRotation()")) { + throw new SecurityException("Requires SET_ORIENTATION permission"); + } + + mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_LOCKED, mRotation); + setRotationUnchecked(WindowManagerPolicy.USE_LAST_ROTATION, false, 0); + } + + public void thawRotation() { + if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION, + "setRotation()")) { + throw new SecurityException("Requires SET_ORIENTATION permission"); + } + + mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_FREE, 0); + setRotationUnchecked(WindowManagerPolicy.USE_LAST_ROTATION, false, 0); + } + public void setRotation(int rotation, boolean alwaysSendConfiguration, int animFlags) { if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION, @@ -4495,7 +5131,7 @@ public class WindowManagerService extends IWindowManager.Stub long origId = Binder.clearCallingIdentity(); boolean changed; synchronized(mWindowMap) { - changed = setRotationUncheckedLocked(rotation, animFlags); + changed = setRotationUncheckedLocked(rotation, animFlags, false); } if (changed || alwaysSendConfiguration) { @@ -4513,14 +5149,31 @@ public class WindowManagerService extends IWindowManager.Stub * Returns null if the rotation has been changed. In this case YOU * MUST CALL setNewConfiguration() TO UNFREEZE THE SCREEN. */ - public boolean setRotationUncheckedLocked(int rotation, int animFlags) { + public boolean setRotationUncheckedLocked(int rotation, int animFlags, boolean inTransaction) { + if (mDragState != null || mScreenRotationAnimation != null) { + // Potential rotation during a drag. Don't do the rotation now, but make + // a note to perform the rotation later. + if (DEBUG_ORIENTATION) Slog.v(TAG, "Deferring rotation."); + if (rotation != WindowManagerPolicy.USE_LAST_ROTATION) { + mDeferredRotation = rotation; + mDeferredRotationAnimFlags = animFlags; + } + return false; + } + boolean changed; if (rotation == WindowManagerPolicy.USE_LAST_ROTATION) { + if (mDeferredRotation != WindowManagerPolicy.USE_LAST_ROTATION) { + rotation = mDeferredRotation; + mRequestedRotation = rotation; + mLastRotationFlags = mDeferredRotationAnimFlags; + } rotation = mRequestedRotation; } else { mRequestedRotation = rotation; mLastRotationFlags = animFlags; } + mDeferredRotation = WindowManagerPolicy.USE_LAST_ROTATION; if (DEBUG_ORIENTATION) Slog.v(TAG, "Overwriting rotation value from " + rotation); rotation = mPolicy.rotationForOrientationLw(mForcedAppOrientation, mRotation, mDisplayEnabled); @@ -4540,11 +5193,33 @@ public class WindowManagerService extends IWindowManager.Stub 2000); mWaitingForConfig = true; mLayoutNeeded = true; - startFreezingDisplayLocked(); + startFreezingDisplayLocked(inTransaction); Slog.i(TAG, "Setting rotation to " + rotation + ", animFlags=" + animFlags); mInputManager.setDisplayOrientation(0, rotation); if (mDisplayEnabled) { - Surface.setOrientation(0, rotation, animFlags); + if (CUSTOM_SCREEN_ROTATION) { + Surface.freezeDisplay(0); + if (!inTransaction) { + if (SHOW_TRANSACTIONS) Slog.i(TAG, + ">>> OPEN TRANSACTION setRotationUnchecked"); + Surface.openTransaction(); + } + try { + if (mScreenRotationAnimation != null) { + mScreenRotationAnimation.setRotation(rotation); + } + } finally { + if (!inTransaction) { + Surface.closeTransaction(); + if (SHOW_TRANSACTIONS) Slog.i(TAG, + "<<< CLOSE TRANSACTION setRotationUnchecked"); + } + } + Surface.setOrientation(0, rotation, animFlags); + Surface.unfreezeDisplay(0); + } else { + Surface.setOrientation(0, rotation, animFlags); + } } for (int i=mWindows.size()-1; i>=0; i--) { WindowState w = mWindows.get(i); @@ -4958,7 +5633,13 @@ public class WindowManagerService extends IWindowManager.Stub public Configuration computeNewConfiguration() { synchronized (mWindowMap) { - return computeNewConfigurationLocked(); + Configuration config = computeNewConfigurationLocked(); + if (config == null && mWaitingForConfig) { + // Nothing changed but we are waiting for something... stop that! + mWaitingForConfig = false; + performLayoutAndPlaceSurfacesLocked(); + } + return config; } } @@ -5054,7 +5735,59 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.adjustConfigurationLw(config); return true; } - + + // ------------------------------------------------------------- + // Drag and drop + // ------------------------------------------------------------- + + IBinder prepareDragSurface(IWindow window, SurfaceSession session, + int flags, int width, int height, Surface outSurface) { + if (DEBUG_DRAG) { + Slog.d(TAG, "prepare drag surface: w=" + width + " h=" + height + + " flags=" + Integer.toHexString(flags) + " win=" + window + + " asbinder=" + window.asBinder()); + } + + final int callerPid = Binder.getCallingPid(); + final long origId = Binder.clearCallingIdentity(); + IBinder token = null; + + try { + synchronized (mWindowMap) { + try { + if (mDragState == null) { + Surface surface = new Surface(session, callerPid, "drag surface", 0, + width, height, PixelFormat.TRANSLUCENT, Surface.HIDDEN); + outSurface.copyFrom(surface); + final IBinder winBinder = window.asBinder(); + token = new Binder(); + // TODO: preserve flags param in DragState + mDragState = new DragState(token, surface, 0, winBinder); + mDragState.mSurface = surface; + token = mDragState.mToken = new Binder(); + + // 5 second timeout for this window to actually begin the drag + mH.removeMessages(H.DRAG_START_TIMEOUT, winBinder); + Message msg = mH.obtainMessage(H.DRAG_START_TIMEOUT, winBinder); + mH.sendMessageDelayed(msg, 5000); + } else { + Slog.w(TAG, "Drag already in progress"); + } + } catch (Surface.OutOfResourcesException e) { + Slog.e(TAG, "Can't allocate drag surface w=" + width + " h=" + height, e); + if (mDragState != null) { + mDragState.reset(); + mDragState = null; + } + } + } + } finally { + Binder.restoreCallingIdentity(origId); + } + + return token; + } + // ------------------------------------------------------------- // Input Events and Focus Management // ------------------------------------------------------------- @@ -5074,23 +5807,31 @@ public class WindowManagerService extends IWindowManager.Stub // When true, input dispatch proceeds normally. Otherwise all events are dropped. private boolean mInputDispatchEnabled = true; + // When true, need to call updateInputWindowsLw(). + private boolean mUpdateInputWindowsNeeded = true; + // Temporary list of windows information to provide to the input dispatcher. private InputWindowList mTempInputWindows = new InputWindowList(); // Temporary input application object to provide to the input dispatcher. private InputApplication mTempInputApplication = new InputApplication(); + // Set to true when the first input device configuration change notification + // is received to indicate that the input devices are ready. + private final Object mInputDevicesReadyMonitor = new Object(); + private boolean mInputDevicesReady; + /* Notifies the window manager about a broken input channel. * * Called by the InputManager. */ - public void notifyInputChannelBroken(InputChannel inputChannel) { + public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) { + if (inputWindowHandle == null) { + return; + } + synchronized (mWindowMap) { - WindowState windowState = getWindowStateForInputChannelLocked(inputChannel); - if (windowState == null) { - return; // irrelevant - } - + WindowState windowState = (WindowState) inputWindowHandle.windowState; Slog.i(TAG, "WINDOW DIED " + windowState); removeWindowLocked(windowState.mSession, windowState); } @@ -5101,11 +5842,12 @@ public class WindowManagerService extends IWindowManager.Stub * * Called by the InputManager. */ - public long notifyANR(Object token, InputChannel inputChannel) { + public long notifyANR(InputApplicationHandle inputApplicationHandle, + InputWindowHandle inputWindowHandle) { AppWindowToken appWindowToken = null; - if (inputChannel != null) { + if (inputWindowHandle != null) { synchronized (mWindowMap) { - WindowState windowState = getWindowStateForInputChannelLocked(inputChannel); + WindowState windowState = (WindowState) inputWindowHandle.windowState; if (windowState != null) { Slog.i(TAG, "Input event dispatching timed out sending to " + windowState.mAttrs.getTitle()); @@ -5114,8 +5856,8 @@ public class WindowManagerService extends IWindowManager.Stub } } - if (appWindowToken == null && token != null) { - appWindowToken = (AppWindowToken) token; + if (appWindowToken == null && inputApplicationHandle != null) { + appWindowToken = inputApplicationHandle.appWindowToken; Slog.i(TAG, "Input event dispatching timed out sending to application " + appWindowToken.stringName); } @@ -5135,33 +5877,60 @@ public class WindowManagerService extends IWindowManager.Stub } return 0; // abort dispatching } - - private WindowState getWindowStateForInputChannel(InputChannel inputChannel) { - synchronized (mWindowMap) { - return getWindowStateForInputChannelLocked(inputChannel); - } + + private void addDragInputWindowLw(InputWindowList windowList) { + final InputWindow inputWindow = windowList.add(); + inputWindow.inputChannel = mDragState.mServerChannel; + inputWindow.name = "drag"; + inputWindow.layoutParamsFlags = 0; + inputWindow.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; + inputWindow.dispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; + inputWindow.visible = true; + inputWindow.canReceiveKeys = false; + inputWindow.hasFocus = true; + inputWindow.hasWallpaper = false; + inputWindow.paused = false; + inputWindow.layer = mDragState.getDragLayerLw(); + inputWindow.ownerPid = Process.myPid(); + inputWindow.ownerUid = Process.myUid(); + + // The drag window covers the entire display + inputWindow.frameLeft = 0; + inputWindow.frameTop = 0; + inputWindow.frameRight = mDisplay.getWidth(); + inputWindow.frameBottom = mDisplay.getHeight(); + + // The drag window cannot receive new touches. + inputWindow.touchableRegion.setEmpty(); } - - private WindowState getWindowStateForInputChannelLocked(InputChannel inputChannel) { - int windowCount = mWindows.size(); - for (int i = 0; i < windowCount; i++) { - WindowState windowState = mWindows.get(i); - if (windowState.mInputChannel == inputChannel) { - return windowState; - } - } - - return null; + + public void setUpdateInputWindowsNeededLw() { + mUpdateInputWindowsNeeded = true; } - + /* Updates the cached window information provided to the input dispatcher. */ public void updateInputWindowsLw() { + if (!mUpdateInputWindowsNeeded) { + return; + } + mUpdateInputWindowsNeeded = false; + // Populate the input window list with information about all of the windows that // could potentially receive input. // As an optimization, we could try to prune the list of windows but this turns // out to be difficult because only the native code knows for sure which window // currently has touch focus. final ArrayList<WindowState> windows = mWindows; + + // If there's a drag in flight, provide a pseudowindow to catch drag input + final boolean inDrag = (mDragState != null); + if (inDrag) { + if (DEBUG_DRAG) { + Log.d(TAG, "Inserting drag window"); + } + addDragInputWindowLw(mTempInputWindows); + } + final int N = windows.size(); for (int i = N - 1; i >= 0; i--) { final WindowState child = windows.get(i); @@ -5177,9 +5946,16 @@ public class WindowManagerService extends IWindowManager.Stub final boolean isVisible = child.isVisibleLw(); final boolean hasWallpaper = (child == mWallpaperTarget) && (type != WindowManager.LayoutParams.TYPE_KEYGUARD); - + + // If there's a drag in progress and 'child' is a potential drop target, + // make sure it's been told about the drag + if (inDrag && isVisible) { + mDragState.sendDragStartedIfNeededLw(child); + } + // Add a window to our list of input windows. final InputWindow inputWindow = mTempInputWindows.add(); + inputWindow.inputWindowHandle = child.mInputWindowHandle; inputWindow.inputChannel = child.mInputChannel; inputWindow.name = child.toString(); inputWindow.layoutParamsFlags = flags; @@ -5199,40 +5975,8 @@ public class WindowManagerService extends IWindowManager.Stub inputWindow.frameTop = frame.top; inputWindow.frameRight = frame.right; inputWindow.frameBottom = frame.bottom; - - final Rect visibleFrame = child.mVisibleFrame; - inputWindow.visibleFrameLeft = visibleFrame.left; - inputWindow.visibleFrameTop = visibleFrame.top; - inputWindow.visibleFrameRight = visibleFrame.right; - inputWindow.visibleFrameBottom = visibleFrame.bottom; - - switch (child.mTouchableInsets) { - default: - case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME: - inputWindow.touchableAreaLeft = frame.left; - inputWindow.touchableAreaTop = frame.top; - inputWindow.touchableAreaRight = frame.right; - inputWindow.touchableAreaBottom = frame.bottom; - break; - - case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: { - Rect inset = child.mGivenContentInsets; - inputWindow.touchableAreaLeft = frame.left + inset.left; - inputWindow.touchableAreaTop = frame.top + inset.top; - inputWindow.touchableAreaRight = frame.right - inset.right; - inputWindow.touchableAreaBottom = frame.bottom - inset.bottom; - break; - } - - case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: { - Rect inset = child.mGivenVisibleInsets; - inputWindow.touchableAreaLeft = frame.left + inset.left; - inputWindow.touchableAreaTop = frame.top + inset.top; - inputWindow.touchableAreaRight = frame.right - inset.right; - inputWindow.touchableAreaBottom = frame.bottom - inset.bottom; - break; - } - } + + child.getTouchableRegion(inputWindow.touchableRegion); } // Send windows to native code. @@ -5242,7 +5986,32 @@ public class WindowManagerService extends IWindowManager.Stub // Also avoids keeping InputChannel objects referenced unnecessarily. mTempInputWindows.clear(); } - + + /* Notifies that the input device configuration has changed. */ + public void notifyConfigurationChanged() { + sendNewConfiguration(); + + synchronized (mInputDevicesReadyMonitor) { + if (!mInputDevicesReady) { + mInputDevicesReady = true; + mInputDevicesReadyMonitor.notifyAll(); + } + } + } + + /* Waits until the built-in input devices have been configured. */ + public boolean waitForInputDevicesReady(long timeoutMillis) { + synchronized (mInputDevicesReadyMonitor) { + if (!mInputDevicesReady) { + try { + mInputDevicesReadyMonitor.wait(timeoutMillis); + } catch (InterruptedException ex) { + } + } + return mInputDevicesReady; + } + } + /* Notifies that the lid switch changed state. */ public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen); @@ -5250,26 +6019,31 @@ public class WindowManagerService extends IWindowManager.Stub /* Provides an opportunity for the window manager policy to intercept early key * processing as soon as the key has been read from the device. */ - public int interceptKeyBeforeQueueing(long whenNanos, int action, int flags, - int keyCode, int scanCode, int policyFlags, boolean isScreenOn) { - return mPolicy.interceptKeyBeforeQueueing(whenNanos, action, flags, - keyCode, scanCode, policyFlags, isScreenOn); + public int interceptKeyBeforeQueueing( + KeyEvent event, int policyFlags, boolean isScreenOn) { + return mPolicy.interceptKeyBeforeQueueing(event, policyFlags, isScreenOn); } /* Provides an opportunity for the window manager policy to process a key before * ordinary dispatch. */ - public boolean interceptKeyBeforeDispatching(InputChannel focus, - int action, int flags, int keyCode, int scanCode, int metaState, int repeatCount, - int policyFlags) { - WindowState windowState = getWindowStateForInputChannel(focus); - return mPolicy.interceptKeyBeforeDispatching(windowState, action, flags, - keyCode, scanCode, metaState, repeatCount, policyFlags); + public boolean interceptKeyBeforeDispatching( + InputWindowHandle focus, KeyEvent event, int policyFlags) { + WindowState windowState = focus != null ? (WindowState) focus.windowState : null; + return mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags); + } + + /* Provides an opportunity for the window manager policy to process a key that + * the application did not handle. */ + public KeyEvent dispatchUnhandledKey( + InputWindowHandle focus, KeyEvent event, int policyFlags) { + WindowState windowState = focus != null ? (WindowState) focus.windowState : null; + return mPolicy.dispatchUnhandledKey(windowState, event, policyFlags); } /* Called when the current input focus changes. * Layer assignment is assumed to be complete by the time this is called. */ - public void setInputFocusLw(WindowState newWindow) { + public void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) { if (DEBUG_INPUT) { Slog.d(TAG, "Input focus has changed to " + newWindow); } @@ -5281,9 +6055,13 @@ public class WindowManagerService extends IWindowManager.Stub // forgets to resume. newWindow.mToken.paused = false; } - + mInputFocus = newWindow; - updateInputWindowsLw(); + setUpdateInputWindowsNeededLw(); + + if (updateInputWindows) { + updateInputWindowsLw(); + } } } @@ -5292,12 +6070,14 @@ public class WindowManagerService extends IWindowManager.Stub if (newApp == null) { mInputManager.setFocusedApplication(null); } else { + mTempInputApplication.inputApplicationHandle = newApp.mInputApplicationHandle; mTempInputApplication.name = newApp.toString(); mTempInputApplication.dispatchingTimeoutNanos = newApp.inputDispatchingTimeoutNanos; - mTempInputApplication.token = newApp; - + mInputManager.setFocusedApplication(mTempInputApplication); + + mTempInputApplication.recycle(); } } @@ -5308,6 +6088,7 @@ public class WindowManagerService extends IWindowManager.Stub } window.paused = true; + setUpdateInputWindowsNeededLw(); updateInputWindowsLw(); } } @@ -5319,6 +6100,7 @@ public class WindowManagerService extends IWindowManager.Stub } window.paused = false; + setUpdateInputWindowsNeededLw(); updateInputWindowsLw(); } } @@ -5421,6 +6203,7 @@ public class WindowManagerService extends IWindowManager.Stub int deviceId = ev.getDeviceId(); int scancode = ev.getScanCode(); int source = ev.getSource(); + int flags = ev.getFlags(); if (source == InputDevice.SOURCE_UNKNOWN) { source = InputDevice.SOURCE_KEYBOARD; @@ -5430,7 +6213,7 @@ public class WindowManagerService extends IWindowManager.Stub if (downTime == 0) downTime = eventTime; KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount, metaState, - deviceId, scancode, KeyEvent.FLAG_FROM_SYSTEM, source); + deviceId, scancode, flags | KeyEvent.FLAG_FROM_SYSTEM, source); final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); @@ -5556,11 +6339,35 @@ public class WindowManagerService extends IWindowManager.Stub } public boolean detectSafeMode() { + if (!mInputMonitor.waitForInputDevicesReady( + INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS)) { + Slog.w(TAG, "Devices still not ready after waiting " + + INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS + + " milliseconds before attempting to detect safe mode."); + } + mSafeMode = mPolicy.detectSafeMode(); return mSafeMode; } public void systemReady() { + synchronized(mWindowMap) { + if (mDisplay != null) { + throw new IllegalStateException("Display already initialized"); + } + WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); + mDisplay = wm.getDefaultDisplay(); + mInitialDisplayWidth = mDisplay.getWidth(); + mInitialDisplayHeight = mDisplay.getHeight(); + mInputManager.setDisplaySize(0, Display.unmapDisplaySize(mInitialDisplayWidth), + Display.unmapDisplaySize(mInitialDisplayHeight)); + } + + try { + mActivityManager.updateConfiguration(null); + } catch (RemoteException e) { + } + mPolicy.systemReady(); } @@ -5685,9 +6492,9 @@ public class WindowManagerService extends IWindowManager.Stub } public void setInsets(IWindow window, int touchableInsets, - Rect contentInsets, Rect visibleInsets) { + Rect contentInsets, Rect visibleInsets, Region touchableArea) { setInsetsWindow(this, window, touchableInsets, contentInsets, - visibleInsets); + visibleInsets, touchableArea); } public void getDisplayFrame(IWindow window, Rect outDisplayFrame) { @@ -5726,6 +6533,134 @@ public class WindowManagerService extends IWindowManager.Stub } } + /* Drag/drop */ + public IBinder prepareDrag(IWindow window, int flags, + int width, int height, Surface outSurface) { + return prepareDragSurface(window, mSurfaceSession, flags, + width, height, outSurface); + } + + public boolean performDrag(IWindow window, IBinder dragToken, + float touchX, float touchY, float thumbCenterX, float thumbCenterY, + ClipData data) { + if (DEBUG_DRAG) { + Slog.d(TAG, "perform drag: win=" + window + " data=" + data); + } + + synchronized (mWindowMap) { + if (mDragState == null) { + Slog.w(TAG, "No drag prepared"); + throw new IllegalStateException("performDrag() without prepareDrag()"); + } + + if (dragToken != mDragState.mToken) { + Slog.w(TAG, "Performing mismatched drag"); + throw new IllegalStateException("performDrag() does not match prepareDrag()"); + } + + WindowState callingWin = windowForClientLocked(null, window, false); + if (callingWin == null) { + Slog.w(TAG, "Bad requesting window " + window); + return false; // !!! TODO: throw here? + } + + // !!! TODO: if input is not still focused on the initiating window, fail + // the drag initiation (e.g. an alarm window popped up just as the application + // called performDrag() + + mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder()); + + // !!! TODO: extract the current touch (x, y) in screen coordinates. That + // will let us eliminate the (touchX,touchY) parameters from the API. + + // !!! FIXME: put all this heavy stuff onto the mH looper, as well as + // the actual drag event dispatch stuff in the dragstate + + mDragState.register(); + mInputMonitor.setUpdateInputWindowsNeededLw(); + mInputMonitor.updateInputWindowsLw(); + if (!mInputManager.transferTouchFocus(callingWin.mInputChannel, + mDragState.mServerChannel)) { + Slog.e(TAG, "Unable to transfer touch focus"); + mDragState.unregister(); + mDragState = null; + mInputMonitor.setUpdateInputWindowsNeededLw(); + mInputMonitor.updateInputWindowsLw(); + return false; + } + + mDragState.mData = data; + mDragState.mCurrentX = touchX; + mDragState.mCurrentY = touchY; + mDragState.broadcastDragStartedLw(touchX, touchY); + + // remember the thumb offsets for later + mDragState.mThumbOffsetX = thumbCenterX; + mDragState.mThumbOffsetY = thumbCenterY; + + // Make the surface visible at the proper location + final Surface surface = mDragState.mSurface; + if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION performDrag"); + Surface.openTransaction(); + try { + surface.setPosition((int)(touchX - thumbCenterX), + (int)(touchY - thumbCenterY)); + surface.setAlpha(.7071f); + surface.setLayer(mDragState.getDragLayerLw()); + surface.show(); + } finally { + Surface.closeTransaction(); + if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION performDrag"); + } + } + + return true; // success! + } + + public void reportDropResult(IWindow window, boolean consumed) { + IBinder token = window.asBinder(); + if (DEBUG_DRAG) { + Slog.d(TAG, "Drop result=" + consumed + " reported by " + token); + } + + synchronized (mWindowMap) { + long ident = Binder.clearCallingIdentity(); + try { + if (mDragState == null || mDragState.mToken != token) { + Slog.w(TAG, "Invalid drop-result claim by " + window); + throw new IllegalStateException("reportDropResult() by non-recipient"); + } + + // The right window has responded, even if it's no longer around, + // so be sure to halt the timeout even if the later WindowState + // lookup fails. + mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder()); + WindowState callingWin = windowForClientLocked(null, window, false); + if (callingWin == null) { + Slog.w(TAG, "Bad result-reporting window " + window); + return; // !!! TODO: throw here? + } + + mDragState.mDragResult = consumed; + mDragState.endDragLw(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + public void dragRecipientEntered(IWindow window) { + if (DEBUG_DRAG) { + Slog.d(TAG, "Drag into new candidate view @ " + window.asBinder()); + } + } + + public void dragRecipientExited(IWindow window) { + if (DEBUG_DRAG) { + Slog.d(TAG, "Drag from old candidate view @ " + window.asBinder()); + } + } + public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) { synchronized(mWindowMap) { long ident = Binder.clearCallingIdentity(); @@ -5900,6 +6835,11 @@ public class WindowManagerService extends IWindowManager.Stub final Rect mGivenVisibleInsets = new Rect(); /** + * This is the given touchable area relative to the window frame, or null if none. + */ + final Region mGivenTouchableRegion = new Region(); + + /** * Flag indicating whether the touchable region should be adjusted by * the visible insets; if false the area outside the visible insets is * NOT touchable, so we must use those to adjust the frame during hit @@ -5908,6 +6848,7 @@ public class WindowManagerService extends IWindowManager.Stub int mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; // Current transformation being applied. + boolean mHaveMatrix; float mDsDx=1, mDtDx=0, mDsDy=0, mDtDy=1; float mLastDsDx=1, mLastDtDx=0, mLastDsDy=0, mLastDtDy=1; float mHScale=1, mVScale=1; @@ -5921,8 +6862,11 @@ public class WindowManagerService extends IWindowManager.Stub final Rect mContainingFrame = new Rect(); final Rect mDisplayFrame = new Rect(); final Rect mContentFrame = new Rect(); + final Rect mParentFrame = new Rect(); final Rect mVisibleFrame = new Rect(); + boolean mContentChanged; + float mShownAlpha = 1; float mAlpha = 1; float mLastAlpha = 1; @@ -5994,13 +6938,18 @@ public class WindowManagerService extends IWindowManager.Stub // Is this window now (or just being) removed? boolean mRemoved; + // Temp for keeping track of windows that have been removed when + // rebuilding window list. + boolean mRebuilding; + // For debugging, this is the last information given to the surface flinger. boolean mSurfaceShown; int mSurfaceX, mSurfaceY, mSurfaceW, mSurfaceH; int mSurfaceLayer; float mSurfaceAlpha; - // Input channel + // Input channel and input window handle used by the input dispatcher. + InputWindowHandle mInputWindowHandle; InputChannel mInputChannel; // Used to improve performance of toString() @@ -6045,6 +6994,7 @@ public class WindowManagerService extends IWindowManager.Stub + TYPE_LAYER_OFFSET; mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type); mAttachedWindow = attachedWindow; + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + mAttachedWindow); mAttachedWindow.mChildWindows.add(this); mLayoutAttached = mAttrs.type != WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; @@ -6092,6 +7042,8 @@ public class WindowManagerService extends IWindowManager.Stub mLayer = 0; mAnimLayer = 0; mLastLayer = 0; + mInputWindowHandle = new InputWindowHandle( + mAppToken != null ? mAppToken.mInputApplicationHandle : null, this); } void attach() { @@ -6129,6 +7081,13 @@ public class WindowManagerService extends IWindowManager.Stub h = mAttrs.height== mAttrs.MATCH_PARENT ? ph : mRequestedHeight; } + if (!mParentFrame.equals(pf)) { + //Slog.i(TAG, "Window " + this + " content frame from " + mParentFrame + // + " to " + pf); + mParentFrame.set(pf); + mContentChanged = true; + } + final Rect content = mContentFrame; content.set(cf); @@ -6260,6 +7219,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mAnimation != null) { mAnimating = true; mLocalAnimating = false; + mAnimation.cancel(); mAnimation = null; } } @@ -6276,9 +7236,6 @@ public class WindowManagerService extends IWindowManager.Stub } int flags = 0; - if (mAttrs.memoryType == MEMORY_TYPE_PUSH_BUFFERS) { - flags |= Surface.PUSH_BUFFERS; - } if ((mAttrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) { flags |= Surface.SECURE; @@ -6312,10 +7269,16 @@ public class WindowManagerService extends IWindowManager.Stub mSurfaceW = w; mSurfaceH = h; try { + final boolean isHwAccelerated = (mAttrs.flags & + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0; + final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : mAttrs.format; + if (isHwAccelerated && mAttrs.format == PixelFormat.OPAQUE) { + flags |= Surface.OPAQUE; + } mSurface = new Surface( mSession.mSurfaceSession, mSession.mPid, mAttrs.getTitle().toString(), - 0, w, h, mAttrs.format, flags); + 0, w, h, format, flags); if (SHOW_TRANSACTIONS) Slog.i(TAG, " CREATE SURFACE " + mSurface + " IN SESSION " + mSession.mSurfaceSession @@ -6337,9 +7300,8 @@ public class WindowManagerService extends IWindowManager.Stub + ", set left=" + mFrame.left + " top=" + mFrame.top + ", animLayer=" + mAnimLayer); if (SHOW_TRANSACTIONS) { - Slog.i(TAG, ">>> OPEN TRANSACTION"); - if (SHOW_TRANSACTIONS) logSurface(this, - "CREATE pos=(" + mFrame.left + "," + mFrame.top + ") (" + + Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked"); + logSurface(this, "CREATE pos=(" + mFrame.left + "," + mFrame.top + ") (" + mFrame.width() + "x" + mFrame.height() + "), layer=" + mAnimLayer + " HIDE", null); } @@ -6364,8 +7326,8 @@ public class WindowManagerService extends IWindowManager.Stub } mLastHidden = true; } finally { - if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION"); Surface.closeTransaction(); + if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION createSurfaceLocked"); } if (localLOGV) Slog.v( TAG, "Created surface " + this); @@ -6525,6 +7487,7 @@ public class WindowManagerService extends IWindowManager.Stub // starting window, so there is no need for it to also // be doing its own stuff. if (mAnimation != null) { + mAnimation.cancel(); mAnimation = null; // Make sure we clean up the animation. mAnimating = true; @@ -6570,7 +7533,11 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ANIM) Slog.v( TAG, "Finished animation in " + this + " @ " + currentTime); - mAnimation = null; + + if (mAnimation != null) { + mAnimation.cancel(); + mAnimation = null; + } //WindowManagerService.this.dump(); } mHasLocalTransformation = false; @@ -6599,6 +7566,7 @@ public class WindowManagerService extends IWindowManager.Stub // clear it and make sure we run the cleanup code. mAnimating = true; mLocalAnimating = true; + mAnimation.cancel(); mAnimation = null; } @@ -6613,7 +7581,10 @@ public class WindowManagerService extends IWindowManager.Stub mAnimating = false; mLocalAnimating = false; - mAnimation = null; + if (mAnimation != null) { + mAnimation.cancel(); + mAnimation = null; + } mAnimLayer = mLayer; if (mIsImWindow) { mAnimLayer += mInputMethodAnimLayerAdjustment; @@ -6743,8 +7714,10 @@ public class WindowManagerService extends IWindowManager.Stub } } + final boolean screenAnimation = mScreenRotationAnimation != null + && mScreenRotationAnimation.isAnimating(); if (selfTransformation || attachedTransformation != null - || appTransformation != null) { + || appTransformation != null || screenAnimation) { // cache often used attributes locally final Rect frame = mFrame; final float tmpFloats[] = mTmpFloats; @@ -6762,6 +7735,10 @@ public class WindowManagerService extends IWindowManager.Stub if (appTransformation != null) { tmpMatrix.postConcat(appTransformation.getMatrix()); } + if (screenAnimation) { + tmpMatrix.postConcat( + mScreenRotationAnimation.getEnterTransformation().getMatrix()); + } // "convert" it into SurfaceFlinger's format // (a 2x2 matrix + an offset) @@ -6769,10 +7746,11 @@ public class WindowManagerService extends IWindowManager.Stub // since it is already included in the transformation. //Slog.i(TAG, "Transform: " + matrix); + mHaveMatrix = true; tmpMatrix.getValues(tmpFloats); mDsDx = tmpFloats[Matrix.MSCALE_X]; - mDtDx = tmpFloats[Matrix.MSKEW_X]; - mDsDy = tmpFloats[Matrix.MSKEW_Y]; + mDtDx = tmpFloats[Matrix.MSKEW_Y]; + mDsDy = tmpFloats[Matrix.MSKEW_X]; mDtDy = tmpFloats[Matrix.MSCALE_Y]; int x = (int)tmpFloats[Matrix.MTRANS_X] + mXOffset; int y = (int)tmpFloats[Matrix.MTRANS_Y] + mYOffset; @@ -6800,6 +7778,10 @@ public class WindowManagerService extends IWindowManager.Stub if (appTransformation != null) { mShownAlpha *= appTransformation.getAlpha(); } + if (screenAnimation) { + mShownAlpha *= + mScreenRotationAnimation.getEnterTransformation().getAlpha(); + } } else { //Slog.i(TAG, "Not applying alpha transform"); } @@ -6816,6 +7798,7 @@ public class WindowManagerService extends IWindowManager.Stub mShownFrame.offset(mXOffset, mYOffset); } mShownAlpha = mAlpha; + mHaveMatrix = false; mDsDx = 1; mDtDx = 0; mDsDy = 0; @@ -6846,7 +7829,7 @@ public class WindowManagerService extends IWindowManager.Stub final AppWindowToken atoken = mAppToken; return mSurface != null && !mAttachedHidden && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested) - && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending)) + && !mDrawPending && !mCommitDrawPending && !mExiting && !mDestroying; } @@ -6872,6 +7855,15 @@ public class WindowManagerService extends IWindowManager.Stub } /** + * Can this window possibly be a drag/drop target? The test here is + * a combination of the above "visible now" with the check that the + * Input Manager uses when discarding windows from input consideration. + */ + boolean isPotentialDragTarget() { + return isVisibleNow() && (mInputChannel != null) && !mRemoved; + } + + /** * Same as isVisible(), but we also count it as visible between the * call to IWindowSession.add() and the first relayout(). */ @@ -6950,14 +7942,12 @@ public class WindowManagerService extends IWindowManager.Stub /** * Returns true if the window has a surface that it has drawn a - * complete UI in to. Note that this returns true if the orientation - * is changing even if the window hasn't redrawn because we don't want - * to stop things from executing during that time. + * complete UI in to. */ public boolean isDrawnLw() { final AppWindowToken atoken = mAppToken; return mSurface != null && !mDestroying - && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending)); + && !mDrawPending && !mCommitDrawPending; } /** @@ -6972,6 +7962,19 @@ public class WindowManagerService extends IWindowManager.Stub && !mDrawPending && !mCommitDrawPending; } + /** + * Return whether this window is wanting to have a translation + * animation applied to it for an in-progress move. (Only makes + * sense to call from performLayoutAndPlaceSurfacesLockedInner().) + */ + boolean shouldAnimateMove() { + return mContentChanged && !mExiting && !mLastHidden && !mDisplayFrozen + && (mFrame.top != mLastFrame.top + || mFrame.left != mLastFrame.left) + && (mAttachedWindow == null || !mAttachedWindow.shouldAnimateMove()) + && mPolicy.isScreenOn(); + } + boolean needsBackgroundFiller(int screenWidth, int screenHeight) { return // only if the application is requesting compatible window @@ -6996,6 +7999,7 @@ public class WindowManagerService extends IWindowManager.Stub disposeInputChannel(); if (mAttachedWindow != null) { + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing " + this + " from " + mAttachedWindow); mAttachedWindow.mChildWindows.remove(this); } destroySurfaceLocked(); @@ -7118,6 +8122,36 @@ public class WindowManagerService extends IWindowManager.Stub return true; } + public void getTouchableRegion(Region outRegion) { + final Rect frame = mFrame; + switch (mTouchableInsets) { + default: + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME: + outRegion.set(frame); + break; + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: { + final Rect inset = mGivenContentInsets; + outRegion.set( + frame.left + inset.left, frame.top + inset.top, + frame.right - inset.right, frame.bottom - inset.bottom); + break; + } + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: { + final Rect inset = mGivenVisibleInsets; + outRegion.set( + frame.left + inset.left, frame.top + inset.top, + frame.right - inset.right, frame.bottom - inset.bottom); + break; + } + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION: { + final Region givenTouchableRegion = mGivenTouchableRegion; + outRegion.set(givenTouchableRegion); + outRegion.translate(frame.left, frame.top); + break; + } + } + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("mSession="); pw.print(mSession); pw.print(" mClient="); pw.println(mClient.asBinder()); @@ -7198,6 +8232,8 @@ public class WindowManagerService extends IWindowManager.Stub pw.println(); pw.print(prefix); pw.print("mContainingFrame="); mContainingFrame.printShortString(pw); + pw.print(" mParentFrame="); + mParentFrame.printShortString(pw); pw.print(" mDisplayFrame="); mDisplayFrame.printShortString(pw); pw.println(); @@ -7209,11 +8245,6 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mVisibleInsets="); mVisibleInsets.printShortString(pw); pw.print(" last="); mLastVisibleInsets.printShortString(pw); pw.println(); - if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) { - pw.print(prefix); pw.print("mShownAlpha="); pw.print(mShownAlpha); - pw.print(" mAlpha="); pw.print(mAlpha); - pw.print(" mLastAlpha="); pw.println(mLastAlpha); - } if (mAnimating || mLocalAnimating || mAnimationIsEntrance || mAnimation != null) { pw.print(prefix); pw.print("mAnimating="); pw.print(mAnimating); @@ -7228,6 +8259,17 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" "); mTransformation.printShortString(pw); pw.println(); } + if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) { + pw.print(prefix); pw.print("mShownAlpha="); pw.print(mShownAlpha); + pw.print(" mAlpha="); pw.print(mAlpha); + pw.print(" mLastAlpha="); pw.println(mLastAlpha); + } + if (mHaveMatrix) { + pw.print(prefix); pw.print("mDsDx="); pw.print(mDsDx); + pw.print(" mDtDx="); pw.print(mDtDx); + pw.print(" mDsDy="); pw.print(mDsDy); + pw.print(" mDtDy="); pw.println(mDtDy); + } pw.print(prefix); pw.print("mDrawPending="); pw.print(mDrawPending); pw.print(" mCommitDrawPending="); pw.print(mCommitDrawPending); pw.print(" mReadyToShow="); pw.print(mReadyToShow); @@ -7421,11 +8463,15 @@ public class WindowManagerService extends IWindowManager.Stub boolean startingMoved; boolean firstWindowDrawn; + // Input application handle used by the input dispatcher. + InputApplicationHandle mInputApplicationHandle; + AppWindowToken(IApplicationToken _token) { super(_token.asBinder(), WindowManager.LayoutParams.TYPE_APPLICATION, true); appWindowToken = this; appToken = _token; + mInputApplicationHandle = new InputApplicationHandle(this); } public void setAnimation(Animation anim) { @@ -7601,7 +8647,8 @@ public class WindowManagerService extends IWindowManager.Stub WindowState win = allAppWindows.get(i); if (win == startingWindow || win.mAppFreezing || win.mViewVisibility != View.VISIBLE - || win.mAttrs.type == TYPE_APPLICATION_STARTING) { + || win.mAttrs.type == TYPE_APPLICATION_STARTING + || win.mDestroying) { continue; } if (DEBUG_VISIBILITY) { @@ -7692,14 +8739,14 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(prefix); pw.print("animating="); pw.print(animating); pw.print(" animation="); pw.println(animation); } - if (animLayerAdjustment != 0) { - pw.print(prefix); pw.print("animLayerAdjustment="); pw.println(animLayerAdjustment); - } if (hasTransformation) { - pw.print(prefix); pw.print("hasTransformation="); pw.print(hasTransformation); - pw.print(" transformation="); transformation.printShortString(pw); + pw.print(prefix); pw.print("XForm: "); + transformation.printShortString(pw); pw.println(); } + if (animLayerAdjustment != 0) { + pw.print(prefix); pw.print("animLayerAdjustment="); pw.println(animLayerAdjustment); + } if (startingData != null || removed || firstWindowDrawn) { pw.print(prefix); pw.print("startingData="); pw.print(startingData); pw.print(" removed="); pw.print(removed); @@ -7751,14 +8798,16 @@ public class WindowManagerService extends IWindowManager.Stub final CharSequence nonLocalizedLabel; final int labelRes; final int icon; + final int windowFlags; StartingData(String _pkg, int _theme, CharSequence _nonLocalizedLabel, - int _labelRes, int _icon) { + int _labelRes, int _icon, int _windowFlags) { pkg = _pkg; theme = _theme; nonLocalizedLabel = _nonLocalizedLabel; labelRes = _labelRes; icon = _icon; + windowFlags = _windowFlags; } } @@ -7779,6 +8828,8 @@ public class WindowManagerService extends IWindowManager.Stub public static final int APP_FREEZE_TIMEOUT = 17; public static final int SEND_NEW_CONFIGURATION = 18; public static final int REPORT_WINDOWS_CHANGE = 19; + public static final int DRAG_START_TIMEOUT = 20; + public static final int DRAG_END_TIMEOUT = 21; private Session mLastReportedHold; @@ -7831,6 +8882,8 @@ public class WindowManagerService extends IWindowManager.Stub // Ignore if process has died. } } + + mPolicy.focusChanged(lastFocus, newFocus); } } break; @@ -7877,7 +8930,7 @@ public class WindowManagerService extends IWindowManager.Stub view = mPolicy.addStartingWindow( wtoken.token, sd.pkg, sd.theme, sd.nonLocalizedLabel, sd.labelRes, - sd.icon); + sd.icon, sd.windowFlags); } catch (Exception e) { Slog.w(TAG, "Exception when adding starting window", e); } @@ -8121,6 +9174,36 @@ public class WindowManagerService extends IWindowManager.Stub break; } + case DRAG_START_TIMEOUT: { + IBinder win = (IBinder)msg.obj; + if (DEBUG_DRAG) { + Slog.w(TAG, "Timeout starting drag by win " + win); + } + synchronized (mWindowMap) { + // !!! TODO: ANR the app that has failed to start the drag in time + if (mDragState != null) { + mDragState.unregister(); + mInputMonitor.setUpdateInputWindowsNeededLw(); + mInputMonitor.updateInputWindowsLw(); + mDragState.reset(); + mDragState = null; + } + } + break; + } + + case DRAG_END_TIMEOUT: { + IBinder win = (IBinder)msg.obj; + if (DEBUG_DRAG) { + Slog.w(TAG, "Timeout ending drag to win " + win); + } + synchronized (mWindowMap) { + // !!! TODO: ANR the drag-receiving app + mDragState.mDragResult = false; + mDragState.endDragLw(); + } + break; + } } } } @@ -8199,12 +9282,18 @@ public class WindowManagerService extends IWindowManager.Stub int lastWallpaper = -1; int numRemoved = 0; + if (mRebuildTmp.length < NW) { + mRebuildTmp = new WindowState[NW+10]; + } + // First remove all existing app windows. i=0; while (i < NW) { WindowState w = mWindows.get(i); if (w.mAppToken != null) { WindowState win = mWindows.remove(i); + win.mRebuilding = true; + mRebuildTmp[numRemoved] = win; mWindowsChanged = true; if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Rebuild removing window: " + win); @@ -8242,6 +9331,21 @@ public class WindowManagerService extends IWindowManager.Stub if (i != numRemoved) { Slog.w(TAG, "Rebuild removed " + numRemoved + " windows but added " + i); + for (i=0; i<numRemoved; i++) { + WindowState ws = mRebuildTmp[i]; + if (ws.mRebuilding) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + ws.dump(pw, ""); + pw.flush(); + Slog.w(TAG, "This window was lost: " + ws); + Slog.w(TAG, sw.toString()); + } + } + Slog.w(TAG, "Current app token list:"); + dumpAppTokensLocked(); + Slog.w(TAG, "Final window list:"); + dumpWindowsLocked(); } } @@ -8251,6 +9355,12 @@ public class WindowManagerService extends IWindowManager.Stub int curLayer = 0; int i; + if (DEBUG_LAYERS) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Log.v(TAG, "Assigning layers", here); + } + for (i=0; i<N; i++) { WindowState w = mWindows.get(i); if (w.mBaseLayer == curBaseLayer || w.mIsImWindow @@ -8302,38 +9412,46 @@ public class WindowManagerService extends IWindowManager.Stub return; } + mInLayout = true; boolean recoveringMemory = false; - if (mForceRemoves != null) { - recoveringMemory = true; - // Wait a little it for things to settle down, and off we go. - for (int i=0; i<mForceRemoves.size(); i++) { - WindowState ws = mForceRemoves.get(i); - Slog.i(TAG, "Force removing: " + ws); - removeWindowInnerLocked(ws.mSession, ws); - } - mForceRemoves = null; - Slog.w(TAG, "Due to memory failure, waiting a bit for next layout"); - Object tmp = new Object(); - synchronized (tmp) { - try { - tmp.wait(250); - } catch (InterruptedException e) { + + try { + if (mForceRemoves != null) { + recoveringMemory = true; + // Wait a little bit for things to settle down, and off we go. + for (int i=0; i<mForceRemoves.size(); i++) { + WindowState ws = mForceRemoves.get(i); + Slog.i(TAG, "Force removing: " + ws); + removeWindowInnerLocked(ws.mSession, ws); + } + mForceRemoves = null; + Slog.w(TAG, "Due to memory failure, waiting a bit for next layout"); + Object tmp = new Object(); + synchronized (tmp) { + try { + tmp.wait(250); + } catch (InterruptedException e) { + } } } + } catch (RuntimeException e) { + Slog.e(TAG, "Unhandled exception while force removing for memory", e); } - - mInLayout = true; + try { performLayoutAndPlaceSurfacesLockedInner(recoveringMemory); - int i = mPendingRemove.size()-1; - if (i >= 0) { - while (i >= 0) { - WindowState w = mPendingRemove.get(i); - removeWindowInnerLocked(w.mSession, w); - i--; + int N = mPendingRemove.size(); + if (N > 0) { + if (mPendingRemoveTmp.length < N) { + mPendingRemoveTmp = new WindowState[N+10]; } + mPendingRemove.toArray(mPendingRemoveTmp); mPendingRemove.clear(); + for (int i=0; i<N; i++) { + WindowState w = mPendingRemoveTmp[i]; + removeWindowInnerLocked(w.mSession, w); + } mInLayout = false; assignLayersLocked(); @@ -8356,7 +9474,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - private final int performLayoutLockedInner() { + private final int performLayoutLockedInner(boolean initial, boolean updateInputWindows) { if (!mLayoutNeeded) { return 0; } @@ -8390,16 +9508,16 @@ public class WindowManagerService extends IWindowManager.Stub final AppWindowToken atoken = win.mAppToken; final boolean gone = win.mViewVisibility == View.GONE || !win.mRelayoutCalled - || win.mRootToken.hidden + || (atoken == null && win.mRootToken.hidden) || (atoken != null && atoken.hiddenRequested) || win.mAttachedHidden || win.mExiting || win.mDestroying; - if (!win.mLayoutAttached) { - if (DEBUG_LAYOUT) Slog.v(TAG, "First pass " + win + if (DEBUG_LAYOUT && !win.mLayoutAttached) { + Slog.v(TAG, "First pass " + win + ": gone=" + gone + " mHaveFrame=" + win.mHaveFrame + " mLayoutAttached=" + win.mLayoutAttached); - if (DEBUG_LAYOUT && gone) Slog.v(TAG, " (mViewVisibility=" + if (gone) Slog.v(TAG, " (mViewVisibility=" + win.mViewVisibility + " mRelayoutCalled=" + win.mRelayoutCalled + " hidden=" + win.mRootToken.hidden + " hiddenRequested=" @@ -8414,6 +9532,10 @@ public class WindowManagerService extends IWindowManager.Stub // just don't display"). if (!gone || !win.mHaveFrame) { if (!win.mLayoutAttached) { + if (initial) { + //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial"); + win.mContentChanged = false; + } mPolicy.layoutWindowLw(win, win.mAttrs, null); win.mLayoutSeq = seq; if (DEBUG_LAYOUT) Slog.v(TAG, "-> mFrame=" @@ -8433,18 +9555,22 @@ public class WindowManagerService extends IWindowManager.Stub for (i = topAttached; i >= 0; i--) { WindowState win = mWindows.get(i); - // If this view is GONE, then skip it -- keep the current - // frame, and let the caller know so they can ignore it - // if they want. (We do the normal layout for INVISIBLE - // windows, since that means "perform layout as normal, - // just don't display"). if (win.mLayoutAttached) { if (DEBUG_LAYOUT) Slog.v(TAG, "Second pass " + win + " mHaveFrame=" + win.mHaveFrame + " mViewVisibility=" + win.mViewVisibility + " mRelayoutCalled=" + win.mRelayoutCalled); + // If this view is GONE, then skip it -- keep the current + // frame, and let the caller know so they can ignore it + // if they want. (We do the normal layout for INVISIBLE + // windows, since that means "perform layout as normal, + // just don't display"). if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled) || !win.mHaveFrame) { + if (initial) { + //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial"); + win.mContentChanged = false; + } mPolicy.layoutWindowLw(win, win.mAttrs, win.mAttachedWindow); win.mLayoutSeq = seq; if (DEBUG_LAYOUT) Slog.v(TAG, "-> mFrame=" @@ -8456,13 +9582,22 @@ public class WindowManagerService extends IWindowManager.Stub } // Window frames may have changed. Tell the input dispatcher about it. - mInputMonitor.updateInputWindowsLw(); + mInputMonitor.setUpdateInputWindowsNeededLw(); + if (updateInputWindows) { + mInputMonitor.updateInputWindowsLw(); + } return mPolicy.finishLayoutLw(); } + // "Something has changed! Let's make it correct now." private final void performLayoutAndPlaceSurfacesLockedInner( boolean recoveringMemory) { + if (mDisplay == null) { + Slog.i(TAG, "skipping performLayoutAndPlaceSurfacesLockedInner with no mDisplay"); + return; + } + final long currentTime = SystemClock.uptimeMillis(); final int dw = mDisplay.getWidth(); final int dh = mDisplay.getHeight(); @@ -8471,7 +9606,8 @@ public class WindowManagerService extends IWindowManager.Stub if (mFocusMayChange) { mFocusMayChange = false; - updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES); + updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/); } // Initialize state of exiting tokens. @@ -8491,13 +9627,14 @@ public class WindowManagerService extends IWindowManager.Stub boolean focusDisplayed = false; boolean animating = false; boolean createWatermark = false; + boolean updateRotation = false; if (mFxSession == null) { mFxSession = new SurfaceSession(); createWatermark = true; } - if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION"); + if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces"); Surface.openTransaction(); @@ -8507,6 +9644,9 @@ public class WindowManagerService extends IWindowManager.Stub if (mWatermark != null) { mWatermark.positionSurface(dw, dh); } + if (mStrictModeFlash != null) { + mStrictModeFlash.positionSurface(dw, dh); + } try { boolean wallpaperForceHidingChanged = false; @@ -8532,7 +9672,7 @@ public class WindowManagerService extends IWindowManager.Stub } if ((changes&WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) { if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout"); - if (updateOrientationFromAppTokensLocked()) { + if (updateOrientationFromAppTokensLocked(true)) { mLayoutNeeded = true; mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); } @@ -8544,7 +9684,7 @@ public class WindowManagerService extends IWindowManager.Stub // FIRST LOOP: Perform a layout, if needed. if (repeats < 4) { - changes = performLayoutLockedInner(); + changes = performLayoutLockedInner(repeats == 0, false /*updateInputWindows*/); if (changes != 0) { continue; } @@ -8579,9 +9719,21 @@ public class WindowManagerService extends IWindowManager.Stub animating = tokensAnimating; + if (mScreenRotationAnimation != null) { + if (mScreenRotationAnimation.isAnimating()) { + if (mScreenRotationAnimation.stepAnimation(currentTime)) { + animating = true; + } else { + mScreenRotationAnimation = null; + updateRotation = true; + } + } + } + boolean tokenMayBeDrawn = false; boolean wallpaperMayChange = false; boolean forceHiding = false; + WindowState windowDetachedWallpaper = null; mPolicy.beginAnimationLw(dw, dh); @@ -8593,7 +9745,7 @@ public class WindowManagerService extends IWindowManager.Stub final WindowManager.LayoutParams attrs = w.mAttrs; if (w.mSurface != null) { - // Execute animation. + // Take care of the window being ready to display. if (w.commitFinishDrawingLocked(currentTime)) { if ((w.mAttrs.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { @@ -8603,19 +9755,56 @@ public class WindowManagerService extends IWindowManager.Stub } } - boolean wasAnimating = w.mAnimating; - if (w.stepAnimationLocked(currentTime, dw, dh)) { + final boolean wasAnimating = w.mAnimating; + + int animDw = dw; + int animDh = dh; + + // If the window has moved due to its containing + // content frame changing, then we'd like to animate + // it. The checks here are ordered by what is least + // likely to be true first. + if (w.shouldAnimateMove()) { + // Frame has moved, containing content frame + // has also moved, and we're not currently animating... + // let's do something. + Animation a = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.window_move_from_decor); + w.setAnimation(a); + animDw = w.mLastFrame.left - w.mFrame.left; + animDh = w.mLastFrame.top - w.mFrame.top; + } + + // Execute animation. + final boolean nowAnimating = w.stepAnimationLocked(currentTime, + animDw, animDh); + + // If this window is animating, make a note that we have + // an animating window and take care of a request to run + // a detached wallpaper animation. + if (nowAnimating) { + if (w.mAnimation != null && w.mAnimation.getDetachWallpaper()) { + windowDetachedWallpaper = w; + } animating = true; - //w.dump(" "); } + + // If this window's app token is running a detached wallpaper + // animation, make a note so we can ensure the wallpaper is + // displayed behind it. + if (w.mAppToken != null && w.mAppToken.animation != null + && w.mAppToken.animation.getDetachWallpaper()) { + windowDetachedWallpaper = w; + } + if (wasAnimating && !w.mAnimating && mWallpaperTarget == w) { wallpaperMayChange = true; } if (mPolicy.doesForceHide(w, attrs)) { - if (!wasAnimating && animating) { + if (!wasAnimating && nowAnimating) { if (DEBUG_VISIBILITY) Slog.v(TAG, - "Animation done that could impact force hide: " + "Animation started that could impact force hide: " + w); wallpaperForceHidingChanged = true; mFocusMayChange = true; @@ -8712,12 +9901,9 @@ public class WindowManagerService extends IWindowManager.Stub if (tokenMayBeDrawn) { // See if any windows have been drawn, so they (and others // associated with them) can now be shown. - final int NT = mTokenList.size(); + final int NT = mAppTokens.size(); for (i=0; i<NT; i++) { - AppWindowToken wtoken = mTokenList.get(i).appWindowToken; - if (wtoken == null) { - continue; - } + AppWindowToken wtoken = mAppTokens.get(i); if (wtoken.freezingScreen) { int numInteresting = wtoken.numInterestingWindows; if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) { @@ -8814,8 +10000,8 @@ public class WindowManagerService extends IWindowManager.Stub // The top-most window will supply the layout params, // and we will determine it below. LayoutParams animLp = null; - AppWindowToken animToken = null; int bestAnimLayer = -1; + boolean fullscreenAnim = false; if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New wallpaper target=" + mWallpaperTarget @@ -8857,11 +10043,18 @@ public class WindowManagerService extends IWindowManager.Stub // window, we will always use its anim. if ((ws.mAttrs.flags&FLAG_COMPATIBLE_WINDOW) != 0) { animLp = ws.mAttrs; - animToken = ws.mAppToken; bestAnimLayer = Integer.MAX_VALUE; - } else if (ws.mLayer > bestAnimLayer) { + } else if (!fullscreenAnim || ws.mLayer > bestAnimLayer) { + animLp = ws.mAttrs; + bestAnimLayer = ws.mLayer; + } + fullscreenAnim = true; + } + } else if (!fullscreenAnim) { + WindowState ws = wtoken.findMainWindow(); + if (ws != null) { + if (ws.mLayer > bestAnimLayer) { animLp = ws.mAttrs; - animToken = ws.mAppToken; bestAnimLayer = ws.mLayer; } } @@ -8899,15 +10092,6 @@ public class WindowManagerService extends IWindowManager.Stub "New transit into wallpaper: " + transit); } - if ((transit&WindowManagerPolicy.TRANSIT_ENTER_MASK) != 0) { - mLastEnterAnimToken = animToken; - mLastEnterAnimParams = animLp; - } else if (mLastEnterAnimParams != null) { - animLp = mLastEnterAnimParams; - mLastEnterAnimToken = null; - mLastEnterAnimParams = null; - } - // If all closing windows are obscured, then there is // no need to do an animation. This is the case, for // example, when this transition is being done behind @@ -8952,12 +10136,14 @@ public class WindowManagerService extends IWindowManager.Stub // This has changed the visibility of windows, so perform // a new layout to get them all up-to-date. - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; + changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT + | WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; mLayoutNeeded = true; if (!moveInputMethodWindowsIfNeededLocked(true)) { assignLayersLocked(); } - updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES); + updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, + false /*updateInputWindows*/); mFocusMayChange = false; } } @@ -9039,6 +10225,14 @@ public class WindowManagerService extends IWindowManager.Stub } } + if (mWindowDetachedWallpaper != windowDetachedWallpaper) { + if (DEBUG_WALLPAPER) Slog.v(TAG, + "Detached wallpaper changed from " + mWindowDetachedWallpaper + + windowDetachedWallpaper); + mWindowDetachedWallpaper = windowDetachedWallpaper; + wallpaperMayChange = true; + } + if (wallpaperMayChange) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper may change! Adjusting"); @@ -9058,7 +10252,8 @@ public class WindowManagerService extends IWindowManager.Stub if (mFocusMayChange) { mFocusMayChange = false; - if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES)) { + if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, + false /*updateInputWindows*/)) { changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; adjResult = 0; } @@ -9070,8 +10265,6 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: changes=0x" + Integer.toHexString(changes)); - - mInputMonitor.updateInputWindowsLw(); } while (changes != 0); // THIRD LOOP: Update the surfaces of all windows. @@ -9115,11 +10308,8 @@ public class WindowManagerService extends IWindowManager.Stub + ": new=" + w.mShownFrame + ", old=" + w.mLastShownFrame); - boolean resize; int width, height; if ((w.mAttrs.flags & w.mAttrs.FLAG_SCALED) != 0) { - resize = w.mLastRequestedWidth != w.mRequestedWidth || - w.mLastRequestedHeight != w.mRequestedHeight; // for a scaled surface, we just want to use // the requested size. width = w.mRequestedWidth; @@ -9127,58 +10317,61 @@ public class WindowManagerService extends IWindowManager.Stub w.mLastRequestedWidth = width; w.mLastRequestedHeight = height; w.mLastShownFrame.set(w.mShownFrame); - try { - if (SHOW_TRANSACTIONS) logSurface(w, - "POS " + w.mShownFrame.left - + ", " + w.mShownFrame.top, null); - w.mSurfaceX = w.mShownFrame.left; - w.mSurfaceY = w.mShownFrame.top; - w.mSurface.setPosition(w.mShownFrame.left, w.mShownFrame.top); - } catch (RuntimeException e) { - Slog.w(TAG, "Error positioning surface in " + w, e); - if (!recoveringMemory) { - reclaimSomeSurfaceMemoryLocked(w, "position"); - } - } } else { - resize = !w.mLastShownFrame.equals(w.mShownFrame); width = w.mShownFrame.width(); height = w.mShownFrame.height(); w.mLastShownFrame.set(w.mShownFrame); } - if (resize) { - if (width < 1) width = 1; - if (height < 1) height = 1; - if (w.mSurface != null) { + if (w.mSurface != null) { + if (w.mSurfaceX != w.mShownFrame.left + || w.mSurfaceY != w.mShownFrame.top) { try { if (SHOW_TRANSACTIONS) logSurface(w, - "POS " + w.mShownFrame.left + "," - + w.mShownFrame.top + " SIZE " - + w.mShownFrame.width() + "x" + "POS " + w.mShownFrame.left + + ", " + w.mShownFrame.top, null); + w.mSurfaceX = w.mShownFrame.left; + w.mSurfaceY = w.mShownFrame.top; + w.mSurface.setPosition(w.mShownFrame.left, w.mShownFrame.top); + } catch (RuntimeException e) { + Slog.w(TAG, "Error positioning surface of " + w + + " pos=(" + w.mShownFrame.left + + "," + w.mShownFrame.top + ")", e); + if (!recoveringMemory) { + reclaimSomeSurfaceMemoryLocked(w, "position"); + } + } + } + + if (width < 1) { + width = 1; + } + if (height < 1) { + height = 1; + } + + if (w.mSurfaceW != width || w.mSurfaceH != height) { + try { + if (SHOW_TRANSACTIONS) logSurface(w, + "SIZE " + w.mShownFrame.width() + "x" + w.mShownFrame.height(), null); w.mSurfaceResized = true; w.mSurfaceW = width; w.mSurfaceH = height; w.mSurface.setSize(width, height); - w.mSurfaceX = w.mShownFrame.left; - w.mSurfaceY = w.mShownFrame.top; - w.mSurface.setPosition(w.mShownFrame.left, - w.mShownFrame.top); } catch (RuntimeException e) { // If something goes wrong with the surface (such // as running out of memory), don't take down the // entire system. - Slog.e(TAG, "Failure updating surface of " + w - + "size=(" + width + "x" + height - + "), pos=(" + w.mShownFrame.left - + "," + w.mShownFrame.top + ")", e); + Slog.e(TAG, "Error resizing surface of " + w + + " size=(" + width + "x" + height + ")", e); if (!recoveringMemory) { reclaimSomeSurfaceMemoryLocked(w, "size"); } } } } + if (!w.mAppFreezing && w.mLayoutSeq == mLayoutSeq) { w.mContentInsetsChanged = !w.mLastContentInsets.equals(w.mContentInsets); @@ -9195,11 +10388,21 @@ public class WindowManagerService extends IWindowManager.Stub if (localLOGV) Slog.v(TAG, "Resizing " + w + ": configChanged=" + configChanged + " last=" + w.mLastFrame + " frame=" + w.mFrame); - if (!w.mLastFrame.equals(w.mFrame) + boolean frameChanged = !w.mLastFrame.equals(w.mFrame); + if (frameChanged || w.mContentInsetsChanged || w.mVisibleInsetsChanged || w.mSurfaceResized || configChanged) { + if (DEBUG_RESIZE || DEBUG_ORIENTATION) { + Slog.v(TAG, "Resize reasons: " + + "frameChanged=" + frameChanged + + " contentInsetsChanged=" + w.mContentInsetsChanged + + " visibleInsetsChanged=" + w.mVisibleInsetsChanged + + " surfaceResized=" + w.mSurfaceResized + + " configChanged=" + configChanged); + } + w.mLastFrame.set(w.mFrame); w.mLastContentInsets.set(w.mContentInsets); w.mLastVisibleInsets.set(w.mVisibleInsets); @@ -9251,12 +10454,6 @@ public class WindowManagerService extends IWindowManager.Stub if (w.mAttachedHidden || !w.isReadyForDisplay()) { if (!w.mLastHidden) { //dump(); - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Window hiding: waitingToShow=" - + w.mRootToken.waitingToShow + " polvis=" - + w.mPolicyVisibility + " atthid=" - + w.mAttachedHidden + " tokhid=" - + w.mRootToken.hidden + " vis=" - + w.mViewVisibility); w.mLastHidden = true; if (SHOW_TRANSACTIONS) logSurface(w, "HIDE (performLayout)", null); @@ -9368,6 +10565,11 @@ public class WindowManagerService extends IWindowManager.Stub w.mOrientationChanging = false; } + if (w.mContentChanged) { + //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing"); + w.mContentChanged = false; + } + final boolean canBeSeen = w.isDisplayedLw(); if (someoneLosingFocus && w == mCurrentFocus && canBeSeen) { @@ -9445,7 +10647,8 @@ public class WindowManagerService extends IWindowManager.Stub mDimAnimator = new DimAnimator(mFxSession); } mDimAnimator.show(dw, dh); - mDimAnimator.updateParameters(w, currentTime); + mDimAnimator.updateParameters(mContext.getResources(), + w, currentTime); } } if ((attrFlags&FLAG_BLUR_BEHIND) != 0) { @@ -9521,16 +10724,14 @@ public class WindowManagerService extends IWindowManager.Stub } mBlurShown = false; } - - if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION"); } catch (RuntimeException e) { Slog.e(TAG, "Unhandled exception in Window Manager", e); } - mInputMonitor.updateInputWindowsLw(); - Surface.closeTransaction(); + if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces"); + if (mWatermark != null) { mWatermark.drawIfNeeded(); } @@ -9620,12 +10821,10 @@ public class WindowManagerService extends IWindowManager.Stub // soon as their animations are complete token.animation = null; token.animating = false; + if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, + "performLayout: App token exiting now removed" + token); mAppTokens.remove(token); mExitingAppTokens.remove(i); - if (mLastEnterAnimToken == token) { - mLastEnterAnimToken = null; - mLastEnterAnimParams = null; - } } } @@ -9657,30 +10856,28 @@ public class WindowManagerService extends IWindowManager.Stub } else if (animating) { requestAnimationLocked(currentTime+(1000/60)-SystemClock.uptimeMillis()); } - + + // Finally update all input windows now that the window changes have stabilized. + mInputMonitor.setUpdateInputWindowsNeededLw(); mInputMonitor.updateInputWindowsLw(); - - if (DEBUG_FREEZE) Slog.v(TAG, "Layout: mDisplayFrozen=" + mDisplayFrozen - + " holdScreen=" + holdScreen); - if (!mDisplayFrozen) { - setHoldScreenLocked(holdScreen != null); - if (screenBrightness < 0 || screenBrightness > 1.0f) { - mPowerManager.setScreenBrightnessOverride(-1); - } else { - mPowerManager.setScreenBrightnessOverride((int) - (screenBrightness * Power.BRIGHTNESS_ON)); - } - if (buttonBrightness < 0 || buttonBrightness > 1.0f) { - mPowerManager.setButtonBrightnessOverride(-1); - } else { - mPowerManager.setButtonBrightnessOverride((int) - (buttonBrightness * Power.BRIGHTNESS_ON)); - } - if (holdScreen != mHoldingScreenOn) { - mHoldingScreenOn = holdScreen; - Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, holdScreen); - mH.sendMessage(m); - } + + setHoldScreenLocked(holdScreen != null); + if (screenBrightness < 0 || screenBrightness > 1.0f) { + mPowerManager.setScreenBrightnessOverride(-1); + } else { + mPowerManager.setScreenBrightnessOverride((int) + (screenBrightness * Power.BRIGHTNESS_ON)); + } + if (buttonBrightness < 0 || buttonBrightness > 1.0f) { + mPowerManager.setButtonBrightnessOverride(-1); + } else { + mPowerManager.setButtonBrightnessOverride((int) + (buttonBrightness * Power.BRIGHTNESS_ON)); + } + if (holdScreen != mHoldingScreenOn) { + mHoldingScreenOn = holdScreen; + Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, holdScreen); + mH.sendMessage(m); } if (mTurnOnScreen) { @@ -9690,6 +10887,15 @@ public class WindowManagerService extends IWindowManager.Stub mTurnOnScreen = false; } + if (updateRotation) { + if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation"); + boolean changed = setRotationUncheckedLocked( + WindowManagerPolicy.USE_LAST_ROTATION, 0, false); + if (changed) { + sendNewConfiguration(); + } + } + // Check to see if we are now in a state where the screen should // be enabled, because the window obscured flags have changed. enableScreenIfNeededLocked(); @@ -9739,7 +10945,7 @@ public class WindowManagerService extends IWindowManager.Stub } return true; } catch (RuntimeException e) { - Slog.w(TAG, "Failure showing surface " + win.mSurface + " in " + win); + Slog.w(TAG, "Failure showing surface " + win.mSurface + " in " + win, e); } reclaimSomeSurfaceMemoryLocked(win, "show"); @@ -9774,6 +10980,7 @@ public class WindowManagerService extends IWindowManager.Stub + " token=" + win.mToken + " pid=" + ws.mSession.mPid + " uid=" + ws.mSession.mUid); + if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null); ws.mSurface.destroy(); ws.mSurfaceShown = false; ws.mSurface = null; @@ -9781,10 +10988,11 @@ public class WindowManagerService extends IWindowManager.Stub i--; N--; leakedSurface = true; - } else if (win.mAppToken != null && win.mAppToken.clientHidden) { + } else if (ws.mAppToken != null && ws.mAppToken.clientHidden) { Slog.w(TAG, "LEAKED SURFACE (app token hidden): " + ws + " surface=" + ws.mSurface + " token=" + win.mAppToken); + if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null); ws.mSurface.destroy(); ws.mSurfaceShown = false; ws.mSurface = null; @@ -9822,6 +11030,7 @@ public class WindowManagerService extends IWindowManager.Stub // surface and ask the app to request another one. Slog.w(TAG, "Looks like we have reclaimed some memory, clearing surface for retry."); if (surface != null) { + if (SHOW_TRANSACTIONS) logSurface(win, "RECOVER DESTROY", null); surface.destroy(); win.mSurfaceShown = false; win.mSurface = null; @@ -9837,7 +11046,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - private boolean updateFocusedWindowLocked(int mode) { + private boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) { WindowState newFocus = computeFocusedWindowLocked(); if (mCurrentFocus != newFocus) { // This check makes sure that we don't already have the focus @@ -9858,7 +11067,7 @@ public class WindowManagerService extends IWindowManager.Stub mLayoutNeeded = true; } if (mode == UPDATE_FOCUS_PLACING_SURFACES) { - performLayoutLockedInner(); + performLayoutLockedInner(true /*initial*/, updateInputWindows); } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) { // Client will do the layout, but we need to assign layers // for handleNewWindowLocked() below. @@ -9869,15 +11078,15 @@ public class WindowManagerService extends IWindowManager.Stub if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) { // If we defer assigning layers, then the caller is responsible for // doing this part. - finishUpdateFocusedWindowAfterAssignLayersLocked(); + finishUpdateFocusedWindowAfterAssignLayersLocked(updateInputWindows); } return true; } return false; } - private void finishUpdateFocusedWindowAfterAssignLayersLocked() { - mInputMonitor.setInputFocusLw(mCurrentFocus); + private void finishUpdateFocusedWindowAfterAssignLayersLocked(boolean updateInputWindows) { + mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows); } private WindowState computeFocusedWindowLocked() { @@ -9949,7 +11158,7 @@ public class WindowManagerService extends IWindowManager.Stub return result; } - private void startFreezingDisplayLocked() { + private void startFreezingDisplayLocked(boolean inTransaction) { if (mDisplayFrozen) { return; } @@ -9969,8 +11178,6 @@ public class WindowManagerService extends IWindowManager.Stub mFreezeGcPending = now; } - if (DEBUG_FREEZE) Slog.v(TAG, "*** FREEZING DISPLAY", new RuntimeException()); - mDisplayFrozen = true; mInputMonitor.freezeInputDispatchingLw(); @@ -9985,7 +11192,19 @@ public class WindowManagerService extends IWindowManager.Stub File file = new File("/data/system/frozen"); Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024); } - Surface.freezeDisplay(0); + + if (CUSTOM_SCREEN_ROTATION) { + if (mScreenRotationAnimation != null && mScreenRotationAnimation.isAnimating()) { + mScreenRotationAnimation.kill(); + mScreenRotationAnimation = null; + } + if (mScreenRotationAnimation == null) { + mScreenRotationAnimation = new ScreenRotationAnimation(mContext, + mDisplay, mFxSession, inTransaction); + } + } else { + Surface.freezeDisplay(0); + } } private void stopFreezingDisplayLocked() { @@ -9997,14 +11216,27 @@ public class WindowManagerService extends IWindowManager.Stub return; } - if (DEBUG_FREEZE) Slog.v(TAG, "*** UNFREEZING DISPLAY", new RuntimeException()); - mDisplayFrozen = false; mH.removeMessages(H.APP_FREEZE_TIMEOUT); if (PROFILE_ORIENTATION) { Debug.stopMethodTracing(); } - Surface.unfreezeDisplay(0); + + boolean updateRotation = false; + + if (CUSTOM_SCREEN_ROTATION) { + if (mScreenRotationAnimation != null) { + if (mScreenRotationAnimation.dismiss(MAX_ANIMATION_DURATION, + mTransitionAnimationScale)) { + requestAnimationLocked(0); + } else { + mScreenRotationAnimation = null; + updateRotation = true; + } + } + } else { + Surface.unfreezeDisplay(0); + } mInputMonitor.thawInputDispatchingLw(); @@ -10012,7 +11244,7 @@ public class WindowManagerService extends IWindowManager.Stub // to avoid inconsistent states. However, something interesting // could have actually changed during that time so re-evaluate it // now to catch that. - if (updateOrientationFromAppTokensLocked()) { + if (updateOrientationFromAppTokensLocked(false)) { mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); } @@ -10026,6 +11258,15 @@ public class WindowManagerService extends IWindowManager.Stub 2000); mScreenFrozenLock.release(); + + if (updateRotation) { + if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation"); + boolean changed = setRotationUncheckedLocked( + WindowManagerPolicy.USE_LAST_ROTATION, 0, false); + if (changed) { + sendNewConfiguration(); + } + } } static int getPropertyInt(String[] tokens, int index, int defUnits, int defDps, @@ -10047,7 +11288,7 @@ public class WindowManagerService extends IWindowManager.Stub return val; } - class Watermark { + static class Watermark { final String[] mTokens; final String mText; final Paint mTextPaint; @@ -10063,9 +11304,9 @@ public class WindowManagerService extends IWindowManager.Stub int mLastDH; boolean mDrawNeeded; - Watermark(SurfaceSession session, String[] tokens) { + Watermark(Display display, SurfaceSession session, String[] tokens) { final DisplayMetrics dm = new DisplayMetrics(); - mDisplay.getMetrics(dm); + display.getMetrics(dm); if (false) { Log.i(TAG, "*********************** WATERMARK"); @@ -10159,6 +11400,8 @@ public class WindowManagerService extends IWindowManager.Stub } catch (OutOfResourcesException e) { } if (c != null) { + c.drawColor(0, PorterDuff.Mode.CLEAR); + int deltaX = mDeltaX; int deltaY = mDeltaY; @@ -10201,7 +11444,7 @@ public class WindowManagerService extends IWindowManager.Stub if (line != null) { String[] toks = line.split("%"); if (toks != null && toks.length > 0) { - mWatermark = new Watermark(mFxSession, toks); + mWatermark = new Watermark(mDisplay, mFxSession, toks); } } } catch (FileNotFoundException e) { @@ -10217,6 +11460,23 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void statusBarVisibilityChanged(int visibility) { + synchronized (mWindowMap) { + final int N = mWindows.size(); + for (int i = 0; i < N; i++) { + WindowState ws = mWindows.get(i); + try { + if (ws.getAttrs().hasSystemUiListeners) { + ws.mClient.dispatchSystemUiVisibilityChanged(visibility); + } + } catch (RemoteException e) { + // so sorry + } + } + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission("android.permission.DUMP") != PackageManager.PERMISSION_GRANTED) { @@ -10315,14 +11575,6 @@ public class WindowManagerService extends IWindowManager.Stub token.dump(pw, " "); } } - if (mTokenList.size() > 0) { - pw.println(" "); - pw.println(" Window token list:"); - for (int i=0; i<mTokenList.size(); i++) { - pw.print(" #"); pw.print(i); pw.print(": "); - pw.println(mTokenList.get(i)); - } - } if (mWallpaperTokens.size() > 0) { pw.println(" "); pw.println(" Wallpaper tokens:"); @@ -10382,6 +11634,9 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mLowerWallpaperTarget="); pw.println(mLowerWallpaperTarget); pw.print(" mUpperWallpaperTarget="); pw.println(mUpperWallpaperTarget); } + if (mWindowDetachedWallpaper != null) { + pw.print(" mWindowDetachedWallpaper="); pw.println(mWindowDetachedWallpaper); + } pw.print(" mCurConfiguration="); pw.println(this.mCurConfiguration); pw.print(" mInTouchMode="); pw.print(mInTouchMode); pw.print(" mLayoutSeq="); pw.println(mLayoutSeq); @@ -10407,6 +11662,8 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mRotation="); pw.print(mRotation); pw.print(", mForcedAppOrientation="); pw.print(mForcedAppOrientation); pw.print(", mRequestedRotation="); pw.println(mRequestedRotation); + pw.print(" mDeferredRotation="); pw.print(mDeferredRotation); + pw.print(", mDeferredRotationAnimFlags="); pw.print(mDeferredRotationAnimFlags); pw.print(" mAnimationPending="); pw.print(mAnimationPending); pw.print(" mWindowAnimationScale="); pw.print(mWindowAnimationScale); pw.print(" mTransitionWindowAnimationScale="); pw.println(mTransitionAnimationScale); @@ -10425,10 +11682,6 @@ public class WindowManagerService extends IWindowManager.Stub } pw.print(" mStartingIconInTransition="); pw.print(mStartingIconInTransition); pw.print(", mSkipAppTransitionAnimation="); pw.println(mSkipAppTransitionAnimation); - if (mLastEnterAnimToken != null || mLastEnterAnimToken != null) { - pw.print(" mLastEnterAnimToken="); pw.print(mLastEnterAnimToken); - pw.print(", mLastEnterAnimParams="); pw.println(mLastEnterAnimParams); - } if (mOpeningApps.size() > 0) { pw.print(" mOpeningApps="); pw.println(mOpeningApps); } @@ -10441,8 +11694,14 @@ public class WindowManagerService extends IWindowManager.Stub if (mToBottomApps.size() > 0) { pw.print(" mToBottomApps="); pw.println(mToBottomApps); } - pw.print(" DisplayWidth="); pw.print(mDisplay.getWidth()); - pw.print(" DisplayHeight="); pw.println(mDisplay.getHeight()); + if (mDisplay != null) { + pw.print(" DisplayWidth="); pw.print(mDisplay.getWidth()); + pw.print(" DisplayHeight="); pw.println(mDisplay.getHeight()); + } else { + pw.println(" NO DISPLAY"); + } + pw.println(" Policy:"); + mPolicy.dump(" ", fd, pw, args); } } @@ -10510,7 +11769,7 @@ public class WindowManagerService extends IWindowManager.Stub * Set's the dim surface's layer and update dim parameters that will be used in * {@link updateSurface} after all windows are examined. */ - void updateParameters(WindowState w, long currentTime) { + void updateParameters(Resources res, WindowState w, long currentTime) { mDimSurface.setLayer(w.mAnimLayer-1); final float target = w.mExiting ? 0 : w.mAttrs.dimAmount; @@ -10524,11 +11783,15 @@ public class WindowManagerService extends IWindowManager.Stub ? w.mAnimation.computeDurationHint() : DEFAULT_DIM_DURATION; if (target > mDimTargetAlpha) { - // This is happening behind the activity UI, - // so we can make it run a little longer to - // give a stronger impression without disrupting - // the user. - duration *= DIM_DURATION_MULTIPLIER; + TypedValue tv = new TypedValue(); + res.getValue(com.android.internal.R.fraction.config_dimBehindFadeDuration, + tv, true); + if (tv.type == TypedValue.TYPE_FRACTION) { + duration = (long)tv.getFraction((float)duration, (float)duration); + } else if (tv.type >= TypedValue.TYPE_FIRST_INT + && tv.type <= TypedValue.TYPE_LAST_INT) { + duration = tv.data; + } } if (duration < 1) { // Don't divide by zero diff --git a/services/java/com/android/server/WiredAccessoryObserver.java b/services/java/com/android/server/WiredAccessoryObserver.java new file mode 100644 index 0000000..e45c368 --- /dev/null +++ b/services/java/com/android/server/WiredAccessoryObserver.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.ActivityManagerNative; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.Message; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.os.UEventObserver; +import android.util.Slog; +import android.media.AudioManager; +import android.util.Log; + +import java.io.FileReader; +import java.io.FileNotFoundException; + +/** + * <p>WiredAccessoryObserver monitors for a wired headset on the main board or dock. + */ +class WiredAccessoryObserver extends UEventObserver { + private static final String TAG = WiredAccessoryObserver.class.getSimpleName(); + private static final boolean LOG = true; + private static final int MAX_AUDIO_PORTS = 3; /* h2w, USB Audio & hdmi */ + private static final String uEventInfo[][] = { {"DEVPATH=/devices/virtual/switch/h2w", + "/sys/class/switch/h2w/state", + "/sys/class/switch/h2w/name"}, + {"DEVPATH=/devices/virtual/switch/usb_audio", + "/sys/class/switch/usb_audio/state", + "/sys/class/switch/usb_audio/name"}, + {"DEVPATH=/devices/virtual/switch/hdmi", + "/sys/class/switch/hdmi/state", + "/sys/class/switch/hdmi/name"} }; + + private static final int BIT_HEADSET = (1 << 0); + private static final int BIT_HEADSET_NO_MIC = (1 << 1); + private static final int BIT_USB_HEADSET_ANLG = (1 << 2); + private static final int BIT_USB_HEADSET_DGTL = (1 << 3); + private static final int BIT_HDMI_AUDIO = (1 << 4); + private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC| + BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL| + BIT_HDMI_AUDIO); + private static final int HEADSETS_WITH_MIC = BIT_HEADSET; + + private int mHeadsetState; + private int mPrevHeadsetState; + private String mHeadsetName; + private int switchState; + + private final Context mContext; + private final WakeLock mWakeLock; // held while there is a pending route change + + public WiredAccessoryObserver(Context context) { + mContext = context; + PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryObserver"); + mWakeLock.setReferenceCounted(false); + + context.registerReceiver(new BootCompletedReceiver(), + new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); + } + + private final class BootCompletedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + // At any given time accessories could be inserted + // one on the board, one on the dock and one on HDMI: + // observe three UEVENTs + init(); // set initial status + for (int i = 0; i < MAX_AUDIO_PORTS; i++) { + startObserving(uEventInfo[i][0]); + } + } + } + + @Override + public void onUEvent(UEventObserver.UEvent event) { + if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString()); + + try { + String name = event.get("SWITCH_NAME"); + int state = Integer.parseInt(event.get("SWITCH_STATE")); + updateState(name, state); + } catch (NumberFormatException e) { + Slog.e(TAG, "Could not parse switch state from event " + event); + } + } + + private synchronized final void updateState(String name, int state) + { + if (name.equals("usb_audio")) { + if (state == 1) { + switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC| + BIT_USB_HEADSET_DGTL|BIT_HDMI_AUDIO)) | + (state << 2)); + } else if (state == 2) { + switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC| + BIT_USB_HEADSET_ANLG|BIT_HDMI_AUDIO)) | + (state << 3)); + } else switchState = (mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|BIT_HDMI_AUDIO)); + } + else if (name.equals("hdmi")) { + switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC| + BIT_USB_HEADSET_DGTL|BIT_USB_HEADSET_ANLG)) | + (state << 4)); + } + else { + switchState = ((mHeadsetState & (BIT_HDMI_AUDIO|BIT_USB_HEADSET_ANLG| + BIT_USB_HEADSET_DGTL)) | + state); + } + update(name, switchState); + } + + private synchronized final void init() { + char[] buffer = new char[1024]; + + String newName = mHeadsetName; + int newState = mHeadsetState; + mPrevHeadsetState = mHeadsetState; + + if (LOG) Slog.v(TAG, "init()"); + + for (int i = 0; i < MAX_AUDIO_PORTS; i++) { + try { + FileReader file = new FileReader(uEventInfo[i][1]); + int len = file.read(buffer, 0, 1024); + file.close(); + newState = Integer.valueOf((new String(buffer, 0, len)).trim()); + + file = new FileReader(uEventInfo[i][2]); + len = file.read(buffer, 0, 1024); + file.close(); + newName = new String(buffer, 0, len).trim(); + + if (newState > 0) { + updateState(newName, newState); + } + + } catch (FileNotFoundException e) { + Slog.w(TAG, "This kernel does not have wired headset support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + } + } + + private synchronized final void update(String newName, int newState) { + // Retain only relevant bits + int headsetState = newState & SUPPORTED_HEADSETS; + int newOrOld = headsetState | mHeadsetState; + int delay = 0; + int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG; + int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL; + int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC); + boolean h2wStateChange = true; + boolean usbStateChange = true; + // reject all suspect transitions: only accept state changes from: + // - a: 0 heaset to 1 headset + // - b: 1 headset to 0 headset + if (LOG) Slog.v(TAG, "newState = "+newState+", headsetState = "+headsetState+"," + + "mHeadsetState = "+mHeadsetState); + if (mHeadsetState == headsetState || ((h2w_headset & (h2w_headset - 1)) != 0)) { + Log.e(TAG, "unsetting h2w flag"); + h2wStateChange = false; + } + // - c: 0 usb headset to 1 usb headset + // - d: 1 usb headset to 0 usb headset + if ((usb_headset_anlg >> 2) == 1 && (usb_headset_dgtl >> 3) == 1) { + Log.e(TAG, "unsetting usb flag"); + usbStateChange = false; + } + if (!h2wStateChange && !usbStateChange) { + Log.e(TAG, "invalid transition, returning ..."); + return; + } + + mHeadsetName = newName; + mPrevHeadsetState = mHeadsetState; + mHeadsetState = headsetState; + + if (headsetState == 0) { + Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY); + mContext.sendBroadcast(intent); + // It can take hundreds of ms flush the audio pipeline after + // apps pause audio playback, but audio route changes are + // immediate, so delay the route change by 1000ms. + // This could be improved once the audio sub-system provides an + // interface to clear the audio pipeline. + delay = 1000; + } else { + // Insert the same delay for headset connection so that the connection event is not + // broadcast before the disconnection event in case of fast removal/insertion + if (mHandler.hasMessages(0)) { + delay = 1000; + } + } + mWakeLock.acquire(); + mHandler.sendMessageDelayed(mHandler.obtainMessage(0, + mHeadsetState, + mPrevHeadsetState, + mHeadsetName), + delay); + } + + private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) { + int allHeadsets = SUPPORTED_HEADSETS; + for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) { + if ((curHeadset & allHeadsets) != 0) { + sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName); + allHeadsets &= ~curHeadset; + } + } + } + + private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) { + if ((headsetState & headset) != (prevHeadsetState & headset)) { + + int state = 0; + if ((headsetState & headset) != 0) { + state = 1; + } + if((headset == BIT_USB_HEADSET_ANLG) || (headset == BIT_USB_HEADSET_DGTL) || + (headset == BIT_HDMI_AUDIO)) { + Intent intent; + + // Pack up the values and broadcast them to everyone + if (headset == BIT_USB_HEADSET_ANLG) { + intent = new Intent(Intent.ACTION_USB_ANLG_HEADSET_PLUG); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra("state", state); + intent.putExtra("name", headsetName); + ActivityManagerNative.broadcastStickyIntent(intent, null); + } else if (headset == BIT_USB_HEADSET_DGTL) { + intent = new Intent(Intent.ACTION_USB_DGTL_HEADSET_PLUG); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra("state", state); + intent.putExtra("name", headsetName); + ActivityManagerNative.broadcastStickyIntent(intent, null); + } else if (headset == BIT_HDMI_AUDIO) { + intent = new Intent(Intent.ACTION_HDMI_AUDIO_PLUG); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra("state", state); + intent.putExtra("name", headsetName); + ActivityManagerNative.broadcastStickyIntent(intent, null); + } + + if (LOG) Slog.v(TAG, "Intent.ACTION_USB_HEADSET_PLUG: state: "+state+" name: "+headsetName); + // TODO: Should we require a permission? + } + if((headset == BIT_HEADSET) || (headset == BIT_HEADSET_NO_MIC)) { + + // Pack up the values and broadcast them to everyone + Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + //int state = 0; + int microphone = 0; + + if ((headset & HEADSETS_WITH_MIC) != 0) { + microphone = 1; + } + + intent.putExtra("state", state); + intent.putExtra("name", headsetName); + intent.putExtra("microphone", microphone); + + if (LOG) Slog.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone); + // TODO: Should we require a permission? + ActivityManagerNative.broadcastStickyIntent(intent, null); + } + } + } + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + sendIntents(msg.arg1, msg.arg2, (String)msg.obj); + mWakeLock.release(); + } + }; +} diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index ddd99f4..6bb19b0 100755 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -55,6 +55,7 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.IIntentReceiver; @@ -75,6 +76,8 @@ import android.content.pm.ServiceInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.net.Proxy; +import android.net.ProxyProperties; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -553,7 +556,7 @@ public final class ActivityManagerService extends ActivityManagerNative = new HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>>(); /** - * Fingerprints (String.hashCode()) of stack traces that we've + * Fingerprints (hashCode()) of stack traces that we've * already logged DropBox entries for. Guarded by itself. If * something (rogue user app) forces this over * MAX_DUP_SUPPRESSED_STACKS entries, the contents are cleared. @@ -958,6 +961,8 @@ public final class ActivityManagerService extends ActivityManagerNative static final int CANCEL_HEAVY_NOTIFICATION_MSG = 25; static final int SHOW_STRICT_MODE_VIOLATION_MSG = 26; static final int CHECK_EXCESSIVE_WAKE_LOCKS_MSG = 27; + static final int CLEAR_DNS_CACHE = 28; + static final int UPDATE_HTTP_PROXY = 29; AlertDialog mUidAlert; @@ -1109,6 +1114,44 @@ public final class ActivityManagerService extends ActivityManagerNative } } } break; + case CLEAR_DNS_CACHE: { + synchronized (ActivityManagerService.this) { + for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { + ProcessRecord r = mLruProcesses.get(i); + if (r.thread != null) { + try { + r.thread.clearDnsCache(); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to clear dns cache for: " + r.info.processName); + } + } + } + } + } break; + case UPDATE_HTTP_PROXY: { + ProxyProperties proxy = (ProxyProperties)msg.obj; + String host = ""; + String port = ""; + String exclList = ""; + if (proxy != null) { + host = proxy.getHost(); + port = Integer.toString(proxy.getPort()); + exclList = proxy.getExclusionList(); + } + synchronized (ActivityManagerService.this) { + for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { + ProcessRecord r = mLruProcesses.get(i); + if (r.thread != null) { + try { + r.thread.setHttpProxy(host, port, exclList); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to update http proxy for: " + + r.info.processName); + } + } + } + } + } break; case SHOW_UID_ERROR_MSG: { // XXX This is a temporary dialog, no need to localize. AlertDialog d = new BaseErrorDialog(mContext); @@ -1116,7 +1159,7 @@ public final class ActivityManagerService extends ActivityManagerNative d.setCancelable(false); d.setTitle("System UIDs Inconsistent"); d.setMessage("UIDs on the system are inconsistent, you need to wipe your data partition or your device will be unstable."); - d.setButton("I'm Feeling Lucky", + d.setButton(DialogInterface.BUTTON_POSITIVE, "I'm Feeling Lucky", mHandler.obtainMessage(IM_FEELING_LUCKY_MSG)); mUidAlert = d; d.show(); @@ -1281,6 +1324,7 @@ public final class ActivityManagerService extends ActivityManagerNative ActivityThread at = ActivityThread.systemMain(); mSystemThread = at; Context context = at.getSystemContext(); + context.setTheme(android.R.style.Theme_Holo); m.mContext = context; m.mFactoryTest = factoryTest; m.mMainStack = new ActivityStack(m, context, true); @@ -1333,6 +1377,11 @@ public final class ActivityManagerService extends ActivityManagerNative } } + // For debug builds, log event loop stalls to dropbox for analysis. + if (StrictMode.conditionallyEnableDebugLogging()) { + Slog.i(TAG, "Enabled StrictMode logging for AThread's Looper"); + } + Looper.loop(); } } @@ -1977,7 +2026,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app == null || app.instrumentationClass == null) { intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); mMainStack.startActivityLocked(null, intent, null, null, 0, aInfo, - null, null, 0, 0, 0, false, false); + null, null, 0, 0, 0, false, false, null); } } @@ -2033,7 +2082,7 @@ public final class ActivityManagerService extends ActivityManagerNative intent.setComponent(new ComponentName( ri.activityInfo.packageName, ri.activityInfo.name)); mMainStack.startActivityLocked(null, intent, null, null, 0, ri.activityInfo, - null, null, 0, 0, 0, false, false); + null, null, 0, 0, 0, false, false, null); } } } @@ -2072,13 +2121,13 @@ public final class ActivityManagerService extends ActivityManagerNative } mPendingActivityLaunches.clear(); } - + public final int startActivity(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) { - return mMainStack.startActivityMayWait(caller, intent, resolvedType, + return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, null, null); } @@ -2089,7 +2138,7 @@ public final class ActivityManagerService extends ActivityManagerNative String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) { WaitResult res = new WaitResult(); - mMainStack.startActivityMayWait(caller, intent, resolvedType, + mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, res, null); return res; @@ -2100,12 +2149,12 @@ public final class ActivityManagerService extends ActivityManagerNative int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, Configuration config) { - return mMainStack.startActivityMayWait(caller, intent, resolvedType, + return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, null, config); } - public int startActivityIntentSender(IApplicationThread caller, + public int startActivityIntentSender(IApplicationThread caller, IntentSender intent, Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flagsMask, int flagsValues) { @@ -2218,7 +2267,7 @@ public final class ActivityManagerService extends ActivityManagerNative // those are not yet exposed to user code, so there is no need. int res = mMainStack.startActivityLocked(r.app.thread, intent, r.resolvedType, null, 0, aInfo, resultTo, resultWho, - requestCode, -1, r.launchedFromUid, false, false); + requestCode, -1, r.launchedFromUid, false, false, null); Binder.restoreCallingIdentity(origId); r.finishing = wasFinishing; @@ -2240,43 +2289,37 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException( "startActivityInPackage only available to the system"); } - - final boolean componentSpecified = intent.getComponent() != null; - - // Don't modify the client's object! - intent = new Intent(intent); - // Collect information about the target of the Intent. - ActivityInfo aInfo; - try { - ResolveInfo rInfo = - AppGlobals.getPackageManager().resolveIntent( - intent, resolvedType, - PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS); - aInfo = rInfo != null ? rInfo.activityInfo : null; - } catch (RemoteException e) { - aInfo = null; - } + return mMainStack.startActivityMayWait(null, uid, intent, resolvedType, + null, 0, resultTo, resultWho, requestCode, onlyIfNeeded, false, null, null); + } - if (aInfo != null) { - // Store the found target back into the intent, because now that - // we have it we never want to do this again. For example, if the - // user navigates back to this point in the history, we should - // always restart the exact same activity. - intent.setComponent(new ComponentName( - aInfo.applicationInfo.packageName, aInfo.name)); - } + public final int startActivities(IApplicationThread caller, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + return mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo); + } - synchronized(this) { - return mMainStack.startActivityLocked(null, intent, resolvedType, - null, 0, aInfo, resultTo, resultWho, requestCode, -1, uid, - onlyIfNeeded, componentSpecified); + public final int startActivitiesInPackage(int uid, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + + // This is so super not safe, that only the system (or okay root) + // can do it. + final int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != Process.myUid()) { + throw new SecurityException( + "startActivityInPackage only available to the system"); } + + return mMainStack.startActivities(null, uid, intents, resolvedTypes, resultTo); } final void addRecentTaskLocked(TaskRecord task) { - // Remove any existing entries that are the same kind of task. int N = mRecentTasks.size(); + // Quick case: check if the top-most recent task is the same. + if (N > 0 && mRecentTasks.get(0) == task) { + return; + } + // Remove any existing entries that are the same kind of task. for (int i=0; i<N; i++) { TaskRecord tr = mRecentTasks.get(i); if ((task.affinity != null && task.affinity.equals(tr.affinity)) @@ -2450,6 +2493,10 @@ public final class ActivityManagerService extends ActivityManagerNative } if (proc.thread != null) { + if (proc.pid == Process.myPid()) { + Log.w(TAG, "crashApplication: trying to crash self!"); + return; + } long ident = Binder.clearCallingIdentity(); try { proc.thread.scheduleCrash(message); @@ -2565,6 +2612,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (localLOGV) Slog.v( TAG, "Removing this entry! frozen=" + r.haveState + " finishing=" + r.finishing); + r.makeFinishing(); mMainStack.mHistory.remove(i); r.inHistory = false; @@ -3012,7 +3060,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (uid == pkgUid || checkComponentPermission( android.Manifest.permission.CLEAR_APP_USER_DATA, - pid, uid, -1) + pid, uid, -1, true) == PackageManager.PERMISSION_GRANTED) { forceStopPackageLocked(packageName, pkgUid); } else { @@ -3678,10 +3726,12 @@ public final class ActivityManagerService extends ActivityManagerNative String[] pkgs = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); if (pkgs != null) { for (String pkg : pkgs) { - if (forceStopPackageLocked(pkg, -1, false, false, false)) { - setResultCode(Activity.RESULT_OK); - return; - } + synchronized (ActivityManagerService.this) { + if (forceStopPackageLocked(pkg, -1, false, false, false)) { + setResultCode(Activity.RESULT_OK); + return; + } + } } } } @@ -3735,22 +3785,22 @@ public final class ActivityManagerService extends ActivityManagerNative } } - public final void activityPaused(IBinder token, Bundle icicle) { - // Refuse possible leaked file descriptors - if (icicle != null && icicle.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Bundle"); - } - + public final void activityPaused(IBinder token) { final long origId = Binder.clearCallingIdentity(); - mMainStack.activityPaused(token, icicle, false); + mMainStack.activityPaused(token, false); Binder.restoreCallingIdentity(origId); } - public final void activityStopped(IBinder token, Bitmap thumbnail, + public final void activityStopped(IBinder token, Bundle icicle, Bitmap thumbnail, 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(); @@ -3759,8 +3809,18 @@ public final class ActivityManagerService extends ActivityManagerNative int index = mMainStack.indexOfTokenLocked(token); if (index >= 0) { r = (ActivityRecord)mMainStack.mHistory.get(index); - r.thumbnail = thumbnail; + r.icicle = icicle; + r.haveState = true; + if (thumbnail != null) { + r.thumbnail = thumbnail; + if (r.task != null) { + r.task.lastThumbnail = r.thumbnail; + } + } r.description = description; + if (r.task != null) { + r.task.lastDescription = r.description; + } r.stopped = true; r.state = ActivityState.STOPPED; if (!r.finishing) { @@ -3835,16 +3895,30 @@ public final class ActivityManagerService extends ActivityManagerNative public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, - int requestCode, Intent intent, String resolvedType, int flags) { + int requestCode, Intent[] intents, String[] resolvedTypes, int flags) { // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors() == true) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - - if (type == INTENT_SENDER_BROADCAST) { - if ((intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { + if (intents != null) { + if (intents.length < 1) { + throw new IllegalArgumentException("Intents array length must be >= 1"); + } + for (int i=0; i<intents.length; i++) { + Intent intent = intents[i]; + if (intent == null) { + throw new IllegalArgumentException("Null intent at index " + i); + } + if (intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + if (type == INTENT_SENDER_BROADCAST && + (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { + throw new IllegalArgumentException( + "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); + } + intents[i] = new Intent(intent); + } + if (resolvedTypes != null && resolvedTypes.length != intents.length) { throw new IllegalArgumentException( - "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); + "Intent array length does not match resolvedTypes length"); } } @@ -3867,7 +3941,7 @@ public final class ActivityManagerService extends ActivityManagerNative } return getIntentSenderLocked(type, packageName, callingUid, - token, resultWho, requestCode, intent, resolvedType, flags); + token, resultWho, requestCode, intents, resolvedTypes, flags); } catch (RemoteException e) { throw new SecurityException(e); @@ -3877,7 +3951,7 @@ public final class ActivityManagerService extends ActivityManagerNative IIntentSender getIntentSenderLocked(int type, String packageName, int callingUid, IBinder token, String resultWho, - int requestCode, Intent intent, String resolvedType, int flags) { + int requestCode, Intent[] intents, String[] resolvedTypes, int flags) { ActivityRecord activity = null; if (type == INTENT_SENDER_ACTIVITY_RESULT) { int index = mMainStack.indexOfTokenLocked(token); @@ -3898,14 +3972,24 @@ public final class ActivityManagerService extends ActivityManagerNative PendingIntentRecord.Key key = new PendingIntentRecord.Key( type, packageName, activity, resultWho, - requestCode, intent, resolvedType, flags); + requestCode, intents, resolvedTypes, flags); WeakReference<PendingIntentRecord> ref; ref = mIntentSenderRecords.get(key); PendingIntentRecord rec = ref != null ? ref.get() : null; if (rec != null) { if (!cancelCurrent) { if (updateCurrent) { - rec.key.requestIntent.replaceExtras(intent); + if (rec.key.requestIntent != null) { + rec.key.requestIntent.replaceExtras(intents != null ? intents[0] : null); + } + if (intents != null) { + intents[intents.length-1] = rec.key.requestIntent; + rec.key.allIntents = intents; + rec.key.allResolvedTypes = resolvedTypes; + } else { + rec.key.allIntents = null; + rec.key.allResolvedTypes = null; + } } return rec; } @@ -4067,7 +4151,7 @@ public final class ActivityManagerService extends ActivityManagerNative * This can be called with or without the global lock held. */ int checkComponentPermission(String permission, int pid, int uid, - int reqUid) { + int owningUid, boolean exported) { // We might be performing an operation on behalf of an indirect binder // invocation, e.g. via {@link #openContentUri}. Check and adjust the // client identity accordingly before proceeding. @@ -4084,9 +4168,14 @@ public final class ActivityManagerService extends ActivityManagerNative !Process.supportsProcesses()) { return PackageManager.PERMISSION_GRANTED; } - // If the target requires a specific UID, always fail for others. - if (reqUid >= 0 && uid != reqUid) { - Slog.w(TAG, "Permission denied: checkComponentPermission() reqUid=" + reqUid); + // If there is a uid that owns whatever is being accessed, it has + // blanket access to it regardless of the permissions it requires. + if (owningUid >= 0 && uid == owningUid) { + return PackageManager.PERMISSION_GRANTED; + } + // If the target is not exported, then nobody else can get to it. + if (!exported) { + Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid); return PackageManager.PERMISSION_DENIED; } if (permission == null) { @@ -4115,7 +4204,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (permission == null) { return PackageManager.PERMISSION_DENIED; } - return checkComponentPermission(permission, pid, uid, -1); + return checkComponentPermission(permission, pid, uid, -1, true); } /** @@ -4265,8 +4354,10 @@ public final class ActivityManagerService extends ActivityManagerNative return -1; } - if (DEBUG_URI_PERMISSION) Slog.v(TAG, - "Checking grant " + targetPkg + " permission to " + uri); + if (targetPkg != null) { + if (DEBUG_URI_PERMISSION) Slog.v(TAG, + "Checking grant " + targetPkg + " permission to " + uri); + } final IPackageManager pm = AppGlobals.getPackageManager(); @@ -4295,23 +4386,45 @@ public final class ActivityManagerService extends ActivityManagerNative } int targetUid; - try { - targetUid = pm.getPackageUid(targetPkg); - if (targetUid < 0) { - if (DEBUG_URI_PERMISSION) Slog.v(TAG, - "Can't grant URI permission no uid for: " + targetPkg); + if (targetPkg != null) { + try { + targetUid = pm.getPackageUid(targetPkg); + if (targetUid < 0) { + if (DEBUG_URI_PERMISSION) Slog.v(TAG, + "Can't grant URI permission no uid for: " + targetPkg); + return -1; + } + } catch (RemoteException ex) { return -1; } - } catch (RemoteException ex) { - return -1; + } else { + targetUid = -1; } - // First... does the target actually need this permission? - if (checkHoldingPermissionsLocked(pm, pi, uri, 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); - return -1; + if (targetUid >= 0) { + // First... does the target actually need this permission? + if (checkHoldingPermissionsLocked(pm, pi, uri, 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); + return -1; + } + } else { + // First... there is no target package, so can anyone access it? + boolean allowed = pi.exported; + if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) { + if (pi.readPermission != null) { + allowed = false; + } + } + if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) { + if (pi.writePermission != null) { + allowed = false; + } + } + if (allowed) { + return -1; + } } // Second... is the provider allowing granting of URI permissions? @@ -4341,16 +4454,25 @@ public final class ActivityManagerService extends ActivityManagerNative // Third... does the caller itself have permission to access // this uri? - if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) { - if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) { - throw new SecurityException("Uid " + callingUid - + " does not have permission to uri " + uri); + if (callingUid != Process.myUid()) { + if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) { + if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) { + throw new SecurityException("Uid " + callingUid + + " does not have permission to uri " + uri); + } } } return targetUid; } + public int checkGrantUriPermission(int callingUid, String targetPkg, + Uri uri, int modeFlags) { + synchronized(this) { + return checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags); + } + } + void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) { modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION @@ -4393,6 +4515,10 @@ public final class ActivityManagerService extends ActivityManagerNative void grantUriPermissionLocked(int callingUid, String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) { + if (targetPkg == null) { + throw new NullPointerException("targetPkg"); + } + int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags); if (targetUid < 0) { return; @@ -4411,6 +4537,10 @@ public final class ActivityManagerService extends ActivityManagerNative + " from " + intent + "; flags=0x" + Integer.toHexString(intent != null ? intent.getFlags() : 0)); + if (targetPkg == null) { + throw new NullPointerException("targetPkg"); + } + if (intent == null) { return -1; } @@ -4712,6 +4842,11 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } + final boolean canReadFb = (flags&ActivityManager.TASKS_GET_THUMBNAILS) != 0 + && checkCallingPermission( + android.Manifest.permission.READ_FRAME_BUFFER) + == PackageManager.PERMISSION_GRANTED; + int pos = mMainStack.mHistory.size()-1; ActivityRecord next = pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null; @@ -4756,7 +4891,13 @@ public final class ActivityManagerService extends ActivityManagerNative ci.id = curTask.taskId; ci.baseActivity = r.intent.getComponent(); ci.topActivity = top.intent.getComponent(); - ci.thumbnail = top.thumbnail; + if (canReadFb) { + if (top.thumbnail != null) { + ci.thumbnail = top.thumbnail; + } else if (top.state == ActivityState.RESUMED) { + ci.thumbnail = top.stack.screenshotActivities(top); + } + } ci.description = topDescription; ci.numActivities = numActivities; ci.numRunning = numRunning; @@ -4825,8 +4966,15 @@ public final class ActivityManagerService extends ActivityManagerNative enforceCallingPermission(android.Manifest.permission.GET_TASKS, "getRecentTasks()"); + final boolean canReadFb = (flags&ActivityManager.TASKS_GET_THUMBNAILS) != 0 + && checkCallingPermission( + android.Manifest.permission.READ_FRAME_BUFFER) + == PackageManager.PERMISSION_GRANTED; + IPackageManager pm = AppGlobals.getPackageManager(); + ActivityRecord resumed = mMainStack.mResumedActivity; + final int N = mRecentTasks.size(); ArrayList<ActivityManager.RecentTaskInfo> res = new ArrayList<ActivityManager.RecentTaskInfo>( @@ -4844,6 +4992,15 @@ public final class ActivityManagerService extends ActivityManagerNative tr.intent != null ? tr.intent : tr.affinityIntent); rti.origActivity = tr.origActivity; + if (canReadFb) { + if (resumed != null && resumed.task == tr) { + rti.thumbnail = resumed.stack.screenshotActivities(resumed); + } else { + rti.thumbnail = tr.lastThumbnail; + } + } + rti.description = tr.lastDescription; + if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0) { // Check whether this activity is currently available. try { @@ -4910,7 +5067,7 @@ public final class ActivityManagerService extends ActivityManagerNative /** * TODO: Add mController hook */ - public void moveTaskToFront(int task) { + public void moveTaskToFront(int task, int flags) { enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, "moveTaskToFront()"); @@ -4925,6 +5082,11 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=0; i<N; i++) { TaskRecord tr = mRecentTasks.get(i); if (tr.taskId == task) { + if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) { + // Caller wants the home activity moved with it. To accomplish this, + // we'll just move the home task to the top first. + mMainStack.moveHomeToFrontLocked(); + } mMainStack.moveTaskToFrontLocked(tr, null); return; } @@ -4932,6 +5094,11 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { ActivityRecord hr = (ActivityRecord)mMainStack.mHistory.get(i); if (hr.task.taskId == task) { + if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) { + // Caller wants the home activity moved with it. To accomplish this, + // we'll just move the home task to the top first. + mMainStack.moveHomeToFrontLocked(); + } mMainStack.moveTaskToFrontLocked(hr.task, null); return; } @@ -5160,12 +5327,12 @@ public final class ActivityManagerService extends ActivityManagerNative final int callingPid = (r != null) ? r.pid : Binder.getCallingPid(); final int callingUid = (r != null) ? r.info.uid : Binder.getCallingUid(); if (checkComponentPermission(cpi.readPermission, callingPid, callingUid, - cpi.exported ? -1 : cpi.applicationInfo.uid) + cpi.applicationInfo.uid, cpi.exported) == PackageManager.PERMISSION_GRANTED) { return null; } if (checkComponentPermission(cpi.writePermission, callingPid, callingUid, - cpi.exported ? -1 : cpi.applicationInfo.uid) + cpi.applicationInfo.uid, cpi.exported) == PackageManager.PERMISSION_GRANTED) { return null; } @@ -5177,12 +5344,12 @@ public final class ActivityManagerService extends ActivityManagerNative i--; PathPermission pp = pps[i]; if (checkComponentPermission(pp.getReadPermission(), callingPid, callingUid, - cpi.exported ? -1 : cpi.applicationInfo.uid) + cpi.applicationInfo.uid, cpi.exported) == PackageManager.PERMISSION_GRANTED) { return null; } if (checkComponentPermission(pp.getWritePermission(), callingPid, callingUid, - cpi.exported ? -1 : cpi.applicationInfo.uid) + cpi.applicationInfo.uid, cpi.exported) == PackageManager.PERMISSION_GRANTED) { return null; } @@ -5198,10 +5365,18 @@ public final class ActivityManagerService extends ActivityManagerNative } } - String msg = "Permission Denial: opening provider " + cpi.name - + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid - + ", uid=" + callingUid + ") requires " - + cpi.readPermission + " or " + cpi.writePermission; + String msg; + if (!cpi.exported) { + msg = "Permission Denial: opening provider " + cpi.name + + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid + + ", uid=" + callingUid + ") that is not exported from uid " + + cpi.applicationInfo.uid; + } else { + msg = "Permission Denial: opening provider " + cpi.name + + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid + + ", uid=" + callingUid + ") requires " + + cpi.readPermission + " or " + cpi.writePermission; + } Slog.w(TAG, msg); return msg; } @@ -5683,16 +5858,18 @@ public final class ActivityManagerService extends ActivityManagerNative return pfd; } + // Actually is sleeping or shutting down or whatever else in the future + // is an inactive state. + public boolean isSleeping() { + return mSleeping || mShuttingDown; + } + public void goingToSleep() { synchronized(this) { mSleeping = true; mWindowManager.setEventDispatching(false); - if (mMainStack.mResumedActivity != null) { - mMainStack.pauseIfSleepingLocked(); - } else { - Slog.w(TAG, "goingToSleep with no resumed activity!"); - } + mMainStack.stopIfSleepingLocked(); // Initialize the wake times of all processes. checkExcessivePowerUsageLocked(false); @@ -5716,7 +5893,7 @@ public final class ActivityManagerService extends ActivityManagerNative mWindowManager.setEventDispatching(false); if (mMainStack.mResumedActivity != null) { - mMainStack.pauseIfSleepingLocked(); + mMainStack.stopIfSleepingLocked(); final long endTime = System.currentTimeMillis() + timeout; while (mMainStack.mResumedActivity != null || mMainStack.mPausingActivity != null) { @@ -5740,13 +5917,30 @@ public final class ActivityManagerService extends ActivityManagerNative return timedout; } + public final void activitySlept(IBinder token) { + if (localLOGV) Slog.v( + TAG, "Activity slept: token=" + token); + + ActivityRecord r = null; + + final long origId = Binder.clearCallingIdentity(); + + synchronized (this) { + int index = mMainStack.indexOfTokenLocked(token); + if (index >= 0) { + r = (ActivityRecord)mMainStack.mHistory.get(index); + mMainStack.activitySleptLocked(r); + } + } + + Binder.restoreCallingIdentity(origId); + } + public void wakingUp() { synchronized(this) { - if (mMainStack.mGoingToSleep.isHeld()) { - mMainStack.mGoingToSleep.release(); - } mWindowManager.setEventDispatching(true); mSleeping = false; + mMainStack.awakeFromSleepingLocked(); mMainStack.resumeTopActivityLocked(null); } } @@ -5791,7 +5985,7 @@ public final class ActivityManagerService extends ActivityManagerNative final int perm = checkComponentPermission( android.Manifest.permission.STOP_APP_SWITCHES, callingPid, - callingUid, -1); + callingUid, -1, true); if (perm == PackageManager.PERMISSION_GRANTED) { return true; } @@ -5875,6 +6069,35 @@ public final class ActivityManagerService extends ActivityManagerNative } } + public void setImmersive(IBinder token, boolean immersive) { + synchronized(this) { + int index = (token != null) ? mMainStack.indexOfTokenLocked(token) : -1; + if (index < 0) { + throw new IllegalArgumentException(); + } + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); + r.immersive = immersive; + } + } + + public boolean isImmersive(IBinder token) { + synchronized (this) { + int index = (token != null) ? mMainStack.indexOfTokenLocked(token) : -1; + if (index < 0) { + throw new IllegalArgumentException(); + } + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); + return r.immersive; + } + } + + public boolean isTopActivityImmersive() { + synchronized (this) { + ActivityRecord r = mMainStack.topRunningActivityLocked(null); + return (r != null) ? r.immersive : false; + } + } + public final void enterSafeMode() { synchronized(this) { // It only makes sense to do this before the system is ready @@ -5884,23 +6107,25 @@ public final class ActivityManagerService extends ActivityManagerNative AppGlobals.getPackageManager().enterSafeMode(); } catch (RemoteException e) { } - - View v = LayoutInflater.from(mContext).inflate( - com.android.internal.R.layout.safe_mode, null); - WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); - lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; - lp.width = WindowManager.LayoutParams.WRAP_CONTENT; - lp.height = WindowManager.LayoutParams.WRAP_CONTENT; - lp.gravity = Gravity.BOTTOM | Gravity.LEFT; - lp.format = v.getBackground().getOpacity(); - lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; - ((WindowManager)mContext.getSystemService( - Context.WINDOW_SERVICE)).addView(v, lp); } } } + public final void showSafeModeOverlay() { + View v = LayoutInflater.from(mContext).inflate( + com.android.internal.R.layout.safe_mode, null); + WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); + lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; + lp.width = WindowManager.LayoutParams.WRAP_CONTENT; + lp.height = WindowManager.LayoutParams.WRAP_CONTENT; + lp.gravity = Gravity.BOTTOM | Gravity.LEFT; + lp.format = v.getBackground().getOpacity(); + lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + ((WindowManager)mContext.getSystemService( + Context.WINDOW_SERVICE)).addView(v, lp); + } + public void noteWakeupAlarm(IIntentSender sender) { if (!(sender instanceof PendingIntentRecord)) { return; @@ -6372,7 +6597,6 @@ public final class ActivityManagerService extends ActivityManagerNative + " has crashed too many times: killing!"); EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH, app.info.processName, app.info.uid); - killServicesLocked(app, false); for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (r.app == app) { @@ -6382,6 +6606,10 @@ public final class ActivityManagerService extends ActivityManagerNative } } if (!app.persistent) { + // Don't let services in this process be restarted and potentially + // annoy the user repeatedly. Unless it is persistent, since those + // processes run critical code. + killServicesLocked(app, false); // We don't want to start this process again until the user // explicitly does so... but for persistent process, we really // need to keep it running. If a persistent process is actually @@ -6405,7 +6633,7 @@ public final class ActivityManagerService extends ActivityManagerNative int index = mMainStack.indexOfTokenLocked(r); r.stack.finishActivityLocked(r, index, Activity.RESULT_CANCELED, null, "crashed"); - // Also terminate an activities below it that aren't yet + // Also terminate any activities below it that aren't yet // stopped, to avoid a situation where one will get // re-start our crashing activity once it gets resumed again. index--; @@ -6414,7 +6642,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (r.state == ActivityState.RESUMED || r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { - if (!r.isHomeActivity) { + if (!r.isHomeActivity || mHomeProcess != r.app) { Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); r.stack.finishActivityLocked(r, index, @@ -6435,7 +6663,28 @@ public final class ActivityManagerService extends ActivityManagerNative sr.crashCount++; } } - + + // If the crashing process is what we consider to be the "home process" and it has been + // replaced by a third-party app, clear the package preferred activities from packages + // with a home activity running in the process to prevent a repeatedly crashing app + // from blocking the user to manually clear the list. + if (app == mHomeProcess && mHomeProcess.activities.size() > 0 + && (mHomeProcess.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + Iterator it = mHomeProcess.activities.iterator(); + while (it.hasNext()) { + ActivityRecord r = (ActivityRecord)it.next(); + if (r.isHomeActivity) { + Log.i(TAG, "Clearing package preferred activities from " + r.packageName); + try { + ActivityThread.getPackageManager() + .clearPackagePreferredActivities(r.packageName); + } catch (RemoteException c) { + // pm is in same process, this will never happen. + } + } + } + } + mProcessCrashTimes.put(app.info.processName, app.info.uid, now); return true; } @@ -6501,7 +6750,7 @@ public final class ActivityManagerService extends ActivityManagerNative ProcessRecord r = findAppProcess(app); if ((violationMask & StrictMode.PENALTY_DROPBOX) != 0) { - Integer stackFingerprint = info.crashInfo.stackTrace.hashCode(); + Integer stackFingerprint = info.hashCode(); boolean logIt = true; synchronized (mAlreadyLoggedViolatedStacks) { if (mAlreadyLoggedViolatedStacks.contains(stackFingerprint)) { @@ -6578,9 +6827,23 @@ public final class ActivityManagerService extends ActivityManagerNative if (info.violationNumThisLoop != 0) { sb.append("Loop-Violation-Number: ").append(info.violationNumThisLoop).append("\n"); } - if (info != null && info.durationMillis != -1) { + if (info.numAnimationsRunning != 0) { + sb.append("Animations-Running: ").append(info.numAnimationsRunning).append("\n"); + } + if (info.broadcastIntentAction != null) { + sb.append("Broadcast-Intent-Action: ").append(info.broadcastIntentAction).append("\n"); + } + if (info.durationMillis != -1) { sb.append("Duration-Millis: ").append(info.durationMillis).append("\n"); } + if (info.numInstances != -1) { + sb.append("Instance-Count: ").append(info.numInstances).append("\n"); + } + if (info.tags != null) { + for (String tag : info.tags) { + sb.append("Span-Tag: ").append(tag).append("\n"); + } + } sb.append("\n"); if (info.crashInfo != null && info.crashInfo.stackTrace != null) { sb.append(info.crashInfo.stackTrace); @@ -6789,6 +7052,9 @@ public final class ActivityManagerService extends ActivityManagerNative sb.append("Subject: ").append(subject).append("\n"); } sb.append("Build: ").append(Build.FINGERPRINT).append("\n"); + if (Debug.isDebuggerConnected()) { + sb.append("Debugger: Connected\n"); + } sb.append("\n"); // Do the rest in a worker thread to avoid blocking the caller on I/O @@ -7142,6 +7408,9 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" prov[iders]: content provider state"); pw.println(" s[ervices]: service state"); pw.println(" service [name]: service client-side state"); + pw.println(" cmd may also be a component name (com.foo/.myApp),"); + pw.println(" a partial substring in a component name, or an"); + pw.println(" ActivityRecord hex object identifier."); return; } else { pw.println("Unknown argument: " + opt + "; use -h for help"); @@ -7183,13 +7452,21 @@ public final class ActivityManagerService extends ActivityManagerNative } return; } else if ("service".equals(cmd)) { - dumpService(fd, pw, args, opti, dumpAll); + dumpService(fd, pw, args, opti); return; } else if ("services".equals(cmd) || "s".equals(cmd)) { synchronized (this) { dumpServicesLocked(fd, pw, args, opti, true); } return; + } else { + // Dumping a single activity? + if (dumpActivity(fd, pw, cmd, args, opti, dumpAll)) { + return; + } + pw.println("Bad activity command, or no activities match: " + cmd); + pw.println("Use -h for help."); + return; } } @@ -7262,6 +7539,11 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" Activities waiting to stop:"); dumpHistoryList(pw, mMainStack.mStoppingActivities, " ", "Stop", false); } + if (mMainStack.mGoingToSleepActivities.size() > 0) { + pw.println(" "); + pw.println(" Activities waiting to sleep:"); + dumpHistoryList(pw, mMainStack.mGoingToSleepActivities, " ", "Sleep", false); + } if (mMainStack.mFinishingActivities.size() > 0) { pw.println(" "); pw.println(" Activities waiting to finish:"); @@ -7273,6 +7555,7 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" mResumedActivity: " + mMainStack.mResumedActivity); pw.println(" mFocusedActivity: " + mFocusedActivity); pw.println(" mLastPausedActivity: " + mMainStack.mLastPausedActivity); + pw.println(" mSleepTimeout: " + mMainStack.mSleepTimeout); if (dumpAll && mRecentTasks.size() > 0) { pw.println(" "); @@ -7536,8 +7819,7 @@ public final class ActivityManagerService extends ActivityManagerNative * - the first arg isn't the flattened component name of an existing service: * dump all services whose component contains the first arg as a substring */ - protected void dumpService(FileDescriptor fd, PrintWriter pw, String[] args, - int opti, boolean dumpAll) { + protected void dumpService(FileDescriptor fd, PrintWriter pw, String[] args, int opti) { String[] newArgs; String componentNameString; ServiceRecord r; @@ -7557,7 +7839,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (r != null) { - dumpService(fd, pw, r, newArgs, dumpAll); + dumpService(fd, pw, r, newArgs); } else { ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); synchronized (this) { @@ -7569,7 +7851,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } for (int i=0; i<services.size(); i++) { - dumpService(fd, pw, services.get(i), newArgs, dumpAll); + dumpService(fd, pw, services.get(i), newArgs); } } } @@ -7578,16 +7860,10 @@ public final class ActivityManagerService extends ActivityManagerNative * Invokes IApplicationThread.dumpService() on the thread of the specified service if * there is a thread associated with the service. */ - private void dumpService(FileDescriptor fd, PrintWriter pw, ServiceRecord r, String[] args, - boolean dumpAll) { - pw.println(" Service " + r.name.flattenToString()); - if (dumpAll) { - synchronized (this) { - pw.print(" * "); pw.println(r); - r.dump(pw, " "); - } - pw.println(""); - } + private void dumpService(FileDescriptor fd, PrintWriter pw, ServiceRecord r, String[] args) { + pw.println("------------------------------------------------------------" + + "-------------------"); + pw.println("APP SERVICE: " + r.name.flattenToString()); if (r.app != null && r.app.thread != null) { try { // flush anything that is already in the PrintWriter since the thread is going @@ -7602,6 +7878,96 @@ public final class ActivityManagerService extends ActivityManagerNative } } + /** + * There are three things that cmd can be: + * - a flattened component name that matched an existing activity + * - the cmd arg isn't the flattened component name of an existing activity: + * dump all activity whose component contains the cmd as a substring + * - A hex number of the ActivityRecord object instance. + */ + protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args, + int opti, boolean dumpAll) { + String[] newArgs; + ComponentName componentName = ComponentName.unflattenFromString(name); + int objectId = 0; + if (componentName == null) { + // Not a '/' separated full component name; maybe an object ID? + try { + objectId = Integer.parseInt(name, 16); + name = null; + componentName = null; + } catch (RuntimeException e) { + } + } + newArgs = new String[args.length - opti]; + if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti); + + ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>(); + synchronized (this) { + for (ActivityRecord r1 : (ArrayList<ActivityRecord>)mMainStack.mHistory) { + if (componentName != null) { + if (r1.intent.getComponent().equals(componentName)) { + activities.add(r1); + } + } else if (name != null) { + if (r1.intent.getComponent().flattenToString().contains(name)) { + activities.add(r1); + } + } else if (System.identityHashCode(r1) == objectId) { + activities.add(r1); + } + } + } + + if (activities.size() <= 0) { + return false; + } + + TaskRecord lastTask = null; + for (int i=activities.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)activities.get(i); + if (lastTask != r.task) { + lastTask = r.task; + pw.print("* Task "); pw.print(lastTask.affinity); + pw.print(" id="); pw.println(lastTask.taskId); + if (dumpAll) { + lastTask.dump(pw, " "); + } + } + dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll); + } + return true; + } + + /** + * Invokes IApplicationThread.dumpActivity() on the thread of the specified activity if + * there is a thread associated with the activity. + */ + private void dumpActivity(String prefix, FileDescriptor fd, PrintWriter pw, + ActivityRecord r, String[] args, boolean dumpAll) { + synchronized (this) { + pw.print(prefix); pw.print("* Activity "); + pw.print(Integer.toHexString(System.identityHashCode(r))); + pw.print(" "); pw.print(r.shortComponentName); pw.print(" pid="); + if (r.app != null) pw.println(r.app.pid); + else pw.println("(not running)"); + if (dumpAll) { + r.dump(pw, prefix + " "); + } + } + if (r.app != null && r.app.thread != null) { + try { + // flush anything that is already in the PrintWriter since the thread is going + // to write to the file descriptor directly + pw.flush(); + r.app.thread.dumpActivity(fd, r, prefix + " ", args); + pw.flush(); + } catch (RemoteException e) { + pw.println("got a RemoteException while dumping the activity"); + } + } + } + boolean dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll) { boolean needSep = false; @@ -8158,7 +8524,8 @@ public final class ActivityManagerService extends ActivityManagerNative } } - if (sr.crashCount >= 2) { + if (sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags + &ApplicationInfo.FLAG_PERSISTENT) == 0) { Slog.w(TAG, "Service crashed " + sr.crashCount + " times, stopping: " + sr); EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH, @@ -8566,8 +8933,16 @@ public final class ActivityManagerService extends ActivityManagerNative int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); if (checkComponentPermission(r.permission, - callingPid, callingUid, r.exported ? -1 : r.appInfo.uid) + callingPid, callingUid, r.appInfo.uid, r.exported) != PackageManager.PERMISSION_GRANTED) { + if (!r.exported) { + Slog.w(TAG, "Permission Denial: Accessing service " + r.name + + " from pid=" + callingPid + + ", uid=" + callingUid + + " that is not exported from uid " + r.appInfo.uid); + return new ServiceLookupResult(null, "not exported from uid " + + r.appInfo.uid); + } Slog.w(TAG, "Permission Denial: Accessing service " + r.name + " from pid=" + callingPid + ", uid=" + callingUid @@ -8649,11 +9024,19 @@ public final class ActivityManagerService extends ActivityManagerNative } if (r != null) { if (checkComponentPermission(r.permission, - callingPid, callingUid, r.exported ? -1 : r.appInfo.uid) + callingPid, callingUid, r.appInfo.uid, r.exported) != PackageManager.PERMISSION_GRANTED) { + if (!r.exported) { + Slog.w(TAG, "Permission Denial: Accessing service " + r.name + + " from pid=" + callingPid + + ", uid=" + callingUid + + " that is not exported from uid " + r.appInfo.uid); + return new ServiceLookupResult(null, "not exported from uid " + + r.appInfo.uid); + } Slog.w(TAG, "Permission Denial: Accessing service " + r.name - + " from pid=" + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid() + + " from pid=" + callingPid + + ", uid=" + callingUid + " requires " + r.permission); return new ServiceLookupResult(null, r.permission); } @@ -8820,6 +9203,11 @@ public final class ActivityManagerService extends ActivityManagerNative long minDuration = SERVICE_RESTART_DURATION; long resetTime = SERVICE_RESET_RUN_DURATION; + if ((r.serviceInfo.applicationInfo.flags + &ApplicationInfo.FLAG_PERSISTENT) != 0) { + minDuration /= 4; + } + // Any delivered but not yet finished starts should be put back // on the pending list. final int N = r.deliveredStarts.size(); @@ -8859,9 +9247,16 @@ public final class ActivityManagerService extends ActivityManagerNative r.restartCount = 1; r.restartDelay = minDuration; } else { - r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR; - if (r.restartDelay < minDuration) { - r.restartDelay = minDuration; + if ((r.serviceInfo.applicationInfo.flags + &ApplicationInfo.FLAG_PERSISTENT) != 0) { + // Services in peristent processes will restart much more + // quickly, since they are pretty important. (Think SystemUI). + r.restartDelay += minDuration/2; + } else { + r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR; + if (r.restartDelay < minDuration) { + r.restartDelay = minDuration; + } } } } @@ -10141,7 +10536,7 @@ public final class ActivityManagerService extends ActivityManagerNative || uidRemoved) { if (checkComponentPermission( android.Manifest.permission.BROADCAST_PACKAGE_REMOVED, - callingPid, callingUid, -1) + callingPid, callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) { if (uidRemoved) { final Bundle intentExtras = intent.getExtras(); @@ -10200,6 +10595,15 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler.sendEmptyMessage(UPDATE_TIME_ZONE); } + if (intent.ACTION_CLEAR_DNS_CACHE.equals(intent.getAction())) { + mHandler.sendEmptyMessage(CLEAR_DNS_CACHE); + } + + if (Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) { + ProxyProperties proxy = intent.getParcelableExtra("proxy"); + mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY, proxy)); + } + /* * Prevent non-system code (defined here to be non-persistent * processes) from sending protected broadcasts. @@ -10800,7 +11204,7 @@ public final class ActivityManagerService extends ActivityManagerNative boolean skip = false; if (filter.requiredPermission != null) { int perm = checkComponentPermission(filter.requiredPermission, - r.callingPid, r.callingUid, -1); + r.callingPid, r.callingUid, -1, true); if (perm != PackageManager.PERMISSION_GRANTED) { Slog.w(TAG, "Permission Denial: broadcasting " + r.intent.toString() @@ -10813,7 +11217,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (r.requiredPermission != null) { int perm = checkComponentPermission(r.requiredPermission, - filter.receiverList.pid, filter.receiverList.uid, -1); + filter.receiverList.pid, filter.receiverList.uid, -1, true); if (perm != PackageManager.PERMISSION_GRANTED) { Slog.w(TAG, "Permission Denial: receiving " + r.intent.toString() @@ -11079,17 +11483,26 @@ public final class ActivityManagerService extends ActivityManagerNative boolean skip = false; int perm = checkComponentPermission(info.activityInfo.permission, - r.callingPid, r.callingUid, - info.activityInfo.exported - ? -1 : info.activityInfo.applicationInfo.uid); + r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid, + info.activityInfo.exported); if (perm != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission Denial: broadcasting " - + r.intent.toString() - + " from " + r.callerPackage + " (pid=" + r.callingPid - + ", uid=" + r.callingUid + ")" - + " requires " + info.activityInfo.permission - + " due to receiver " + info.activityInfo.packageName - + "/" + info.activityInfo.name); + if (!info.activityInfo.exported) { + Slog.w(TAG, "Permission Denial: broadcasting " + + r.intent.toString() + + " from " + r.callerPackage + " (pid=" + r.callingPid + + ", uid=" + r.callingUid + ")" + + " is not exported from uid " + info.activityInfo.applicationInfo.uid + + " due to receiver " + info.activityInfo.packageName + + "/" + info.activityInfo.name); + } else { + Slog.w(TAG, "Permission Denial: broadcasting " + + r.intent.toString() + + " from " + r.callerPackage + " (pid=" + r.callingPid + + ", uid=" + r.callingUid + ")" + + " requires " + info.activityInfo.permission + + " due to receiver " + info.activityInfo.packageName + + "/" + info.activityInfo.name); + } skip = true; } if (r.callingUid != Process.SYSTEM_UID && @@ -11434,14 +11847,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (starting != null) { kept = mMainStack.ensureActivityConfigurationLocked(starting, changes); - if (kept) { - // If this didn't result in the starting activity being - // destroyed, then we need to make sure at this point that all - // other activities are made visible. - if (DEBUG_SWITCH) Slog.i(TAG, "Config didn't destroy " + starting - + ", ensuring others are correct."); - mMainStack.ensureActivitiesVisibleLocked(starting, changes); - } + // And we need to make sure at this point that all other activities + // are made visible with the correct configuration. + mMainStack.ensureActivitiesVisibleLocked(starting, changes); } if (values != null && mWindowManager != null) { @@ -11550,7 +11958,7 @@ public final class ActivityManagerService extends ActivityManagerNative } else if (app == mHeavyWeightProcess) { // We don't want to kill the current heavy-weight process. adj = HEAVY_WEIGHT_APP_ADJ; - schedGroup = Process.THREAD_GROUP_DEFAULT; + schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; app.adjType = "heavy"; } else if (app == mHomeProcess) { // This process is hosting what we currently consider to be the @@ -11566,13 +11974,19 @@ public final class ActivityManagerService extends ActivityManagerNative app.adjType = "bg-activities"; N = app.activities.size(); for (int j=0; j<N; j++) { - if (app.activities.get(j).visible) { + ActivityRecord r = app.activities.get(j); + if (r.visible) { // This app has a visible activity! app.hidden = false; adj = VISIBLE_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "visible"; break; + } else if (r.state == ActivityState.PAUSING + || r.state == ActivityState.PAUSED + || r.state == ActivityState.STOPPING) { + adj = PERCEPTIBLE_APP_ADJ; + app.adjType = "stopping"; } } } else { @@ -12475,7 +12889,69 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + + public boolean dumpHeap(String process, boolean managed, + String path, ParcelFileDescriptor fd) throws RemoteException { + + try { + synchronized (this) { + // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to + // its own permission (same as profileControl). + if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires permission " + + android.Manifest.permission.SET_ACTIVITY_WATCHER); + } + + if (fd == null) { + throw new IllegalArgumentException("null fd"); + } + + ProcessRecord proc = null; + try { + int pid = Integer.parseInt(process); + synchronized (mPidsSelfLocked) { + proc = mPidsSelfLocked.get(pid); + } + } catch (NumberFormatException e) { + } + + if (proc == null) { + HashMap<String, SparseArray<ProcessRecord>> all + = mProcessNames.getMap(); + SparseArray<ProcessRecord> procs = all.get(process); + if (procs != null && procs.size() > 0) { + proc = procs.valueAt(0); + } + } + + if (proc == null || proc.thread == null) { + throw new IllegalArgumentException("Unknown process: " + process); + } + + boolean isSecure = "1".equals(SystemProperties.get(SYSTEM_SECURE, "0")); + if (isSecure) { + if ((proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) { + throw new SecurityException("Process not debuggable: " + proc); + } + } + + proc.thread.dumpHeap(managed, path, fd); + fd = null; + return true; + } + } catch (RemoteException e) { + throw new IllegalStateException("Process disappeared"); + } finally { + if (fd != null) { + try { + fd.close(); + } catch (IOException e) { + } + } + } + } + /** In this method we try to acquire our lock to make sure that we have not deadlocked */ public void monitor() { synchronized (this) { } diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index 4d773e4..0fb30ff 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -26,6 +26,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.os.Build; import android.os.Bundle; import android.os.Message; import android.os.Process; @@ -36,6 +37,7 @@ import android.util.Log; import android.util.Slog; import android.util.TimeUtils; import android.view.IApplicationToken; +import android.view.WindowManager; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -58,7 +60,8 @@ class ActivityRecord extends IApplicationToken.Stub { final String processName; // process where this component wants to run final String taskAffinity; // as per ActivityInfo.taskAffinity final boolean stateNotNeeded; // As per ActivityInfo.flags - final boolean fullscreen; // covers the full screen? + final boolean fullscreen; // covers the full screen? + final boolean noDisplay; // activity is not displayed? final boolean componentSpecified; // did caller specifiy an explicit component? final boolean isHomeActivity; // do we consider this to be a home activity? final String baseDir; // where activity source (resources etc) located @@ -68,6 +71,8 @@ class ActivityRecord extends IApplicationToken.Stub { int labelRes; // the label information from the package mgr. int icon; // resource identifier of activity's icon. int theme; // resource identifier of activity's theme. + int realTheme; // actual theme resource we will use, never 0. + int windowFlags; // custom window flags for preview window. TaskRecord task; // the task this is in. long launchTime; // when we starting launching this activity long startTime; // last time this activity was started @@ -98,12 +103,14 @@ class ActivityRecord extends IApplicationToken.Stub { boolean inHistory; // are we in the history stack? int launchMode; // the launch mode activity attribute. boolean visible; // does this activity's window need to be shown? + 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. + boolean immersive; // immersive mode (don't interrupt if possible) String stringName; // for caching of toString(). @@ -159,12 +166,15 @@ class ActivityRecord extends IApplicationToken.Stub { pw.print(" finishing="); pw.println(finishing); pw.print(prefix); pw.print("keysPaused="); pw.print(keysPaused); pw.print(" inHistory="); pw.print(inHistory); - pw.print(" launchMode="); pw.println(launchMode); - pw.print(prefix); pw.print("fullscreen="); pw.print(fullscreen); pw.print(" visible="); pw.print(visible); - pw.print(" frozenBeforeDestroy="); pw.print(frozenBeforeDestroy); - pw.print(" thumbnailNeeded="); pw.print(thumbnailNeeded); + pw.print(" sleeping="); pw.print(sleeping); pw.print(" idle="); pw.println(idle); + pw.print(prefix); pw.print("fullscreen="); pw.print(fullscreen); + pw.print(" noDisplay="); pw.print(noDisplay); + 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.println(thumbnailNeeded); if (launchTime != 0 || startTime != 0) { pw.print(prefix); pw.print("launchTime="); TimeUtils.formatDuration(launchTime, pw); pw.print(" startTime="); @@ -242,6 +252,16 @@ class ActivityRecord extends IApplicationToken.Stub { } icon = aInfo.getIconResource(); theme = aInfo.getThemeResource(); + realTheme = theme; + if (realTheme == 0) { + realTheme = aInfo.applicationInfo.targetSdkVersion + < Build.VERSION_CODES.HONEYCOMB + ? android.R.style.Theme + : android.R.style.Theme_Holo; + } + if ((aInfo.flags&ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) { + windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + } if ((aInfo.flags&ActivityInfo.FLAG_MULTIPROCESS) != 0 && _caller != null && (aInfo.applicationInfo.uid == Process.SYSTEM_UID @@ -259,12 +279,13 @@ class ActivityRecord extends IApplicationToken.Stub { launchMode = aInfo.launchMode; AttributeCache.Entry ent = AttributeCache.instance().get(packageName, - theme != 0 ? theme : android.R.style.Theme, - com.android.internal.R.styleable.Window); + realTheme, com.android.internal.R.styleable.Window); fullscreen = ent != null && !ent.array.getBoolean( com.android.internal.R.styleable.Window_windowIsFloating, false) && !ent.array.getBoolean( com.android.internal.R.styleable.Window_windowIsTranslucent, false); + noDisplay = ent != null && ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowNoDisplay, false); if (!_componentSpecified || _launchedFromUid == Process.myUid() || _launchedFromUid == 0) { @@ -289,6 +310,8 @@ class ActivityRecord extends IApplicationToken.Stub { } else { isHomeActivity = false; } + + immersive = (aInfo.flags & ActivityInfo.FLAG_IMMERSIVE) != 0; } else { realActivity = null; taskAffinity = null; @@ -299,7 +322,18 @@ class ActivityRecord extends IApplicationToken.Stub { processName = null; packageName = null; fullscreen = true; + noDisplay = false; isHomeActivity = false; + immersive = false; + } + } + + void makeFinishing() { + if (!finishing) { + finishing = true; + if (task != null) { + task.numActivities--; + } } } @@ -403,7 +437,7 @@ class ActivityRecord extends IApplicationToken.Stub { // an application, and that application is not blocked or unresponding. // In any other case, we can't count on getting the screen unfrozen, // so it is best to leave as-is. - return app == null || (!app.crashing && !app.notResponding); + return app != null && !app.crashing && !app.notResponding; } public void startFreezingScreenLocked(ProcessRecord app, int configChanges) { @@ -570,14 +604,32 @@ class ActivityRecord extends IApplicationToken.Stub { public boolean isInterestingToUserLocked() { return visible || nowVisible || state == ActivityState.PAUSING || state == ActivityState.RESUMED; - } + } + + public void setSleeping(boolean _sleeping) { + if (sleeping == _sleeping) { + return; + } + if (app != null && app.thread != null) { + try { + app.thread.scheduleSleeping(this, _sleeping); + if (sleeping && !stack.mGoingToSleepActivities.contains(this)) { + stack.mGoingToSleepActivities.add(this); + } + sleeping = _sleeping; + } catch (RemoteException e) { + Slog.w(ActivityStack.TAG, "Exception thrown when sleeping: " + + intent.getComponent(), e); + } + } + } public String toString() { if (stringName != null) { return stringName; } StringBuilder sb = new StringBuilder(128); - sb.append("HistoryRecord{"); + sb.append("ActivityRecord{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(' '); sb.append(intent.getComponent().flattenToShortString()); diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index f52d322..2040cbd 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -46,9 +46,10 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Bitmap; import android.net.Uri; import android.os.Binder; -import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -91,6 +92,9 @@ public class ActivityStack { // next activity. static final int PAUSE_TIMEOUT = 500; + // How long we can hold the sleep wake lock before giving up. + static final int SLEEP_TIMEOUT = 5*1000; + // How long we can hold the launch wake lock before giving up. static final int LAUNCH_TIMEOUT = 10*1000; @@ -99,8 +103,8 @@ public class ActivityStack { static final int DESTROY_TIMEOUT = 10*1000; // How long until we reset a task when the user returns to it. Currently - // 30 minutes. - static final long ACTIVITY_INACTIVE_RESET_TIME = 1000*60*30; + // disabled. + static final long ACTIVITY_INACTIVE_RESET_TIME = 0; // How long between activity launches that we consider safe to not warn // the user about an unexpected activity being launched on top. @@ -157,6 +161,12 @@ public class ActivityStack { = new ArrayList<ActivityRecord>(); /** + * List of activities that are in the process of going to sleep. + */ + final ArrayList<ActivityRecord> mGoingToSleepActivities + = new ArrayList<ActivityRecord>(); + + /** * Animations that for the current transition have requested not to * be considered for the transition animation. */ @@ -237,6 +247,15 @@ public class ActivityStack { long mInitialStartTime = 0; + /** + * Set when we have taken too long waiting to go to sleep. + */ + boolean mSleepTimeout = false; + + int mThumbnailWidth = -1; + int mThumbnailHeight = -1; + + static final int SLEEP_TIMEOUT_MSG = 8; static final int PAUSE_TIMEOUT_MSG = 9; static final int IDLE_TIMEOUT_MSG = 10; static final int IDLE_NOW_MSG = 11; @@ -251,12 +270,19 @@ public class ActivityStack { public void handleMessage(Message msg) { switch (msg.what) { + case SLEEP_TIMEOUT_MSG: { + if (mService.isSleeping()) { + Slog.w(TAG, "Sleep timeout! Sleeping now."); + mSleepTimeout = true; + checkReadyForSleepLocked(); + } + } break; case PAUSE_TIMEOUT_MSG: { IBinder token = (IBinder)msg.obj; // We don't at this point know if the activity is fullscreen, // so we need to be conservative and assume it isn't. Slog.w(TAG, "Activity pause timeout for " + token); - activityPaused(token, null, true); + activityPaused(token, true); } break; case IDLE_TIMEOUT_MSG: { if (mService.mDidDexOpt) { @@ -510,6 +536,7 @@ public class ActivityStack { mService.mHomeProcess = app; } mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName()); + r.sleeping = false; app.thread.scheduleLaunchActivity(new Intent(r.intent), r, System.identityHashCode(r), r.info, r.icicle, results, newIntents, !andResume, @@ -564,13 +591,14 @@ public class ActivityStack { // As part of the process of launching, ActivityThread also performs // a resume. r.state = ActivityState.RESUMED; - r.icicle = null; - r.haveState = false; r.stopped = false; mResumedActivity = r; r.task.touchActiveTime(); + if (mMainStack) { + mService.addRecentTaskLocked(r.task); + } completeResumeLocked(r); - pauseIfSleepingLocked(); + checkReadyForSleepLocked(); } else { // This activity is not starting in the resumed state... which // should look like we asked it to pause+stop (but remain visible), @@ -580,6 +608,9 @@ public class ActivityStack { r.stopped = true; } + r.icicle = null; + r.haveState = false; + // Launch the new version setup screen if needed. We do this -after- // launching the initial activity (that is, home), so that it can have // a chance to initialize itself while in the background, making the @@ -623,8 +654,8 @@ public class ActivityStack { "activity", r.intent.getComponent(), false); } - void pauseIfSleepingLocked() { - if (mService.mSleeping || mService.mShuttingDown) { + void stopIfSleepingLocked() { + if (mService.isSleeping()) { if (!mGoingToSleep.isHeld()) { mGoingToSleep.acquire(); if (mLaunchingActivity.isHeld()) { @@ -632,18 +663,113 @@ public class ActivityStack { mService.mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); } } + mHandler.removeMessages(SLEEP_TIMEOUT_MSG); + Message msg = mHandler.obtainMessage(SLEEP_TIMEOUT_MSG); + mHandler.sendMessageDelayed(msg, SLEEP_TIMEOUT); + checkReadyForSleepLocked(); + } + } - // If we are not currently pausing an activity, get the current - // one to pause. If we are pausing one, we will just let that stuff - // run and release the wake lock when all done. - if (mPausingActivity == null) { - if (DEBUG_PAUSE) Slog.v(TAG, "Sleep needs to pause..."); + void awakeFromSleepingLocked() { + mHandler.removeMessages(SLEEP_TIMEOUT_MSG); + mSleepTimeout = false; + if (mGoingToSleep.isHeld()) { + mGoingToSleep.release(); + } + // Ensure activities are no longer sleeping. + for (int i=mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + r.setSleeping(false); + } + mGoingToSleepActivities.clear(); + } + + void activitySleptLocked(ActivityRecord r) { + mGoingToSleepActivities.remove(r); + checkReadyForSleepLocked(); + } + + void checkReadyForSleepLocked() { + if (!mService.isSleeping()) { + // Do not care. + return; + } + + if (!mSleepTimeout) { + if (mResumedActivity != null) { + // Still have something resumed; can't sleep until it is paused. + if (DEBUG_PAUSE) Slog.v(TAG, "Sleep needs to pause " + mResumedActivity); if (DEBUG_USER_LEAVING) Slog.v(TAG, "Sleep => pause with userLeaving=false"); startPausingLocked(false, true); + return; + } + if (mPausingActivity != null) { + // Still waiting for something to pause; can't sleep yet. + if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still waiting to pause " + mPausingActivity); + return; + } + + if (mStoppingActivities.size() > 0) { + // Still need to tell some activities to stop; can't sleep yet. + if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to stop " + + mStoppingActivities.size() + " activities"); + Message msg = Message.obtain(); + msg.what = IDLE_NOW_MSG; + mHandler.sendMessage(msg); + return; } + + ensureActivitiesVisibleLocked(null, 0); + + // Make sure any stopped but visible activities are now sleeping. + // This ensures that the activity's onStop() is called. + for (int i=mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (r.state == ActivityState.STOPPING || r.state == ActivityState.STOPPED) { + r.setSleeping(true); + } + } + + if (mGoingToSleepActivities.size() > 0) { + // Still need to tell some activities to sleep; can't sleep yet. + if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to sleep " + + mGoingToSleepActivities.size() + " activities"); + return; + } + } + + mHandler.removeMessages(SLEEP_TIMEOUT_MSG); + + if (mGoingToSleep.isHeld()) { + mGoingToSleep.release(); + } + if (mService.mShuttingDown) { + mService.notifyAll(); } + } + public final Bitmap screenshotActivities(ActivityRecord who) { + if (who.noDisplay) { + return null; + } + + Resources res = mService.mContext.getResources(); + int w = mThumbnailWidth; + int h = mThumbnailHeight; + if (w < 0) { + mThumbnailWidth = w = + res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width); + mThumbnailHeight = h = + res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height); + } + + if (w > 0) { + return mService.mWindowManager.screenshotApplications(who, w, h); + } + return null; + } + private final void startPausingLocked(boolean userLeaving, boolean uiSleeping) { if (mPausingActivity != null) { RuntimeException e = new RuntimeException(); @@ -663,6 +789,10 @@ public class ActivityStack { mLastPausedActivity = prev; prev.state = ActivityState.PAUSING; prev.task.touchActiveTime(); + prev.thumbnail = screenshotActivities(prev); + if (prev.task != null) { + prev.task.lastThumbnail = prev.thumbnail; + } mService.updateCpuStats(); @@ -726,10 +856,9 @@ public class ActivityStack { } } - final void activityPaused(IBinder token, Bundle icicle, boolean timeout) { + final void activityPaused(IBinder token, boolean timeout) { if (DEBUG_PAUSE) Slog.v( - TAG, "Activity paused: token=" + token + ", icicle=" + icicle - + ", timeout=" + timeout); + TAG, "Activity paused: token=" + token + ", timeout=" + timeout); ActivityRecord r = null; @@ -737,10 +866,6 @@ public class ActivityStack { int index = indexOfTokenLocked(token); if (index >= 0) { r = (ActivityRecord)mHistory.get(index); - if (!timeout) { - r.icicle = icicle; - r.haveState = true; - } mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); if (mPausingActivity == r) { r.state = ActivityState.PAUSED; @@ -789,6 +914,8 @@ public class ActivityStack { Message msg = Message.obtain(); msg.what = IDLE_NOW_MSG; mHandler.sendMessage(msg); + } else { + checkReadyForSleepLocked(); } } } else { @@ -798,15 +925,10 @@ public class ActivityStack { mPausingActivity = null; } - if (!mService.mSleeping && !mService.mShuttingDown) { + if (!mService.isSleeping()) { resumeTopActivityLocked(prev); } else { - if (mGoingToSleep.isHeld()) { - mGoingToSleep.release(); - } - if (mService.mShuttingDown) { - mService.notifyAll(); - } + checkReadyForSleepLocked(); } if (prev != null) { @@ -961,6 +1083,7 @@ public class ActivityStack { TAG, "Making visible and scheduling visibility: " + r); try { mService.mWindowManager.setAppVisibility(r, true); + r.sleeping = false; r.app.thread.scheduleWindowVisibility(r, true); r.stopFreezingScreenLocked(false); } catch (Exception e) { @@ -1090,6 +1213,8 @@ public class ActivityStack { // The activity may be waiting for stop, but that is no longer // appropriate for it. mStoppingActivities.remove(next); + mGoingToSleepActivities.remove(next); + next.sleeping = false; mWaitingVisibleActivities.remove(next); if (DEBUG_SWITCH) Slog.v(TAG, "Resuming " + next); @@ -1176,11 +1301,12 @@ public class ActivityStack { if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare close transition: prev=" + prev); if (mNoAnimActivities.contains(prev)) { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_NONE, false); } else { mService.mWindowManager.prepareAppTransition(prev.task == next.task ? WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE - : WindowManagerPolicy.TRANSIT_TASK_CLOSE); + : WindowManagerPolicy.TRANSIT_TASK_CLOSE, false); } mService.mWindowManager.setAppWillBeHidden(prev); mService.mWindowManager.setAppVisibility(prev, false); @@ -1188,11 +1314,12 @@ public class ActivityStack { if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: prev=" + prev); if (mNoAnimActivities.contains(next)) { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_NONE, false); } else { mService.mWindowManager.prepareAppTransition(prev.task == next.task ? WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN - : WindowManagerPolicy.TRANSIT_TASK_OPEN); + : WindowManagerPolicy.TRANSIT_TASK_OPEN, false); } } if (false) { @@ -1203,9 +1330,11 @@ public class ActivityStack { if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: no previous"); if (mNoAnimActivities.contains(next)) { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_NONE, false); } else { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN, false); } } @@ -1223,6 +1352,9 @@ public class ActivityStack { next.state = ActivityState.RESUMED; mResumedActivity = next; next.task.touchActiveTime(); + if (mMainStack) { + mService.addRecentTaskLocked(next.task); + } mService.updateLruProcessLocked(next.app, true, true); updateLRUListLocked(next); @@ -1284,10 +1416,11 @@ public class ActivityStack { System.identityHashCode(next), next.task.taskId, next.shortComponentName); + next.sleeping = false; next.app.thread.scheduleResumeActivity(next, mService.isNextTransitionForward()); - pauseIfSleepingLocked(); + checkReadyForSleepLocked(); } catch (Exception e) { // Whoops, need to restart this activity! @@ -1301,7 +1434,8 @@ public class ActivityStack { mService.mWindowManager.setAppStartingWindow( next, next.packageName, next.theme, next.nonLocalizedLabel, - next.labelRes, next.icon, null, true); + next.labelRes, next.icon, next.windowFlags, + null, true); } } startSpecificActivityLocked(next, true, false); @@ -1336,7 +1470,8 @@ public class ActivityStack { mService.mWindowManager.setAppStartingWindow( next, next.packageName, next.theme, next.nonLocalizedLabel, - next.labelRes, next.icon, null, true); + next.labelRes, next.icon, next.windowFlags, + null, true); } if (DEBUG_SWITCH) Slog.v(TAG, "Restarting: " + next); } @@ -1347,7 +1482,7 @@ public class ActivityStack { } private final void startActivityLocked(ActivityRecord r, boolean newTask, - boolean doResume) { + boolean doResume, boolean keepCurTransition) { final int NH = mHistory.size(); int addPos = -1; @@ -1418,16 +1553,17 @@ public class ActivityStack { if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: starting " + r); if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_NONE, keepCurTransition); mNoAnimActivities.add(r); } else if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { mService.mWindowManager.prepareAppTransition( - WindowManagerPolicy.TRANSIT_TASK_OPEN); + WindowManagerPolicy.TRANSIT_TASK_OPEN, keepCurTransition); mNoAnimActivities.remove(r); } else { mService.mWindowManager.prepareAppTransition(newTask ? WindowManagerPolicy.TRANSIT_TASK_OPEN - : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN, keepCurTransition); mNoAnimActivities.remove(r); } mService.mWindowManager.addAppToken( @@ -1460,7 +1596,7 @@ public class ActivityStack { } mService.mWindowManager.setAppStartingWindow( r, r.packageName, r.theme, r.nonLocalizedLabel, - r.labelRes, r.icon, prev, showStartingIcon); + r.labelRes, r.icon, r.windowFlags, prev, showStartingIcon); } } else { // If this is the first activity, don't do any fancy animations, @@ -1485,7 +1621,8 @@ public class ActivityStack { ActivityRecord newActivity) { boolean forceReset = (newActivity.info.flags &ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0; - if (taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) { + if (ACTIVITY_INACTIVE_RESET_TIME > 0 + && taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) { if ((newActivity.info.flags &ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) { forceReset = true; @@ -1571,8 +1708,7 @@ public class ActivityStack { if (mService.mCurTask <= 0) { mService.mCurTask = 1; } - target.task = new TaskRecord(mService.mCurTask, target.info, null, - (target.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); + target.task = new TaskRecord(mService.mCurTask, target.info, null); target.task.affinityIntent = target.intent; if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + " out to new task " + target.task); @@ -1609,9 +1745,6 @@ public class ActivityStack { taskTopI = -1; } replyChainEnd = -1; - if (mMainStack) { - mService.addRecentTaskLocked(target.task); - } } else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) { // If the activity should just be removed -- either @@ -1774,11 +1907,11 @@ public class ActivityStack { * activities on top of it and return the instance. * * @param newR Description of the new activity being started. - * @return Returns the old activity that should be continue to be used, + * @return Returns the old activity that should be continued to be used, * or null if none was found. */ private final ActivityRecord performClearTaskLocked(int taskId, - ActivityRecord newR, int launchFlags, boolean doClear) { + ActivityRecord newR, int launchFlags) { int i = mHistory.size(); // First find the requested task. @@ -1804,17 +1937,18 @@ public class ActivityStack { if (r.realActivity.equals(newR.realActivity)) { // Here it is! Now finish everything in front... ActivityRecord ret = r; - if (doClear) { - while (i < (mHistory.size()-1)) { - i++; - r = (ActivityRecord)mHistory.get(i); - if (r.finishing) { - continue; - } - if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "clear")) { - i--; - } + while (i < (mHistory.size()-1)) { + i++; + r = (ActivityRecord)mHistory.get(i); + if (r.task.taskId != taskId) { + break; + } + if (r.finishing) { + continue; + } + if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, + null, "clear")) { + i--; } } @@ -1841,6 +1975,51 @@ public class ActivityStack { } /** + * Completely remove all activities associated with an existing task. + */ + private final void performClearTaskLocked(int taskId) { + int i = mHistory.size(); + + // First find the requested task. + while (i > 0) { + i--; + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (r.task.taskId == taskId) { + i++; + break; + } + } + + // Now clear it. + while (i > 0) { + i--; + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (r.finishing) { + continue; + } + if (r.task.taskId != taskId) { + // We hit the bottom. Now finish it all... + while (i < (mHistory.size()-1)) { + i++; + r = (ActivityRecord)mHistory.get(i); + if (r.task.taskId != taskId) { + // Whoops hit the end. + return; + } + if (r.finishing) { + continue; + } + if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, + null, "clear")) { + i--; + } + } + return; + } + } + } + + /** * Find the activity in the history stack within the given task. Returns * the index within the history at which it's found, or < 0 if not found. */ @@ -1880,7 +2059,7 @@ public class ActivityStack { int grantedMode, ActivityInfo aInfo, IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, boolean onlyIfNeeded, - boolean componentSpecified) { + boolean componentSpecified, ActivityRecord[] outActivity) { int err = START_SUCCESS; @@ -1958,17 +2137,25 @@ public class ActivityStack { } final int perm = mService.checkComponentPermission(aInfo.permission, callingPid, - callingUid, aInfo.exported ? -1 : aInfo.applicationInfo.uid); + callingUid, aInfo.applicationInfo.uid, aInfo.exported); if (perm != PackageManager.PERMISSION_GRANTED) { if (resultRecord != null) { sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, Activity.RESULT_CANCELED, null); } - String msg = "Permission Denial: starting " + intent.toString() - + " from " + callerApp + " (pid=" + callingPid - + ", uid=" + callingUid + ")" - + " requires " + aInfo.permission; + String msg; + if (!aInfo.exported) { + msg = "Permission Denial: starting " + intent.toString() + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ")" + + " not exported from uid " + aInfo.applicationInfo.uid; + } else { + msg = "Permission Denial: starting " + intent.toString() + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ")" + + " requires " + aInfo.permission; + } Slog.w(TAG, msg); throw new SecurityException(msg); } @@ -2002,6 +2189,9 @@ public class ActivityStack { ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, requestCode, componentSpecified); + if (outActivity != null) { + outActivity[0] = r; + } if (mMainStack) { if (mResumedActivity == null @@ -2036,6 +2226,16 @@ public class ActivityStack { grantedUriPermissions, grantedMode, onlyIfNeeded, true); } + final void moveHomeToFrontFromLaunchLocked(int launchFlags) { + if ((launchFlags & + (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) + == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) { + // Caller wants to appear on home activity, so before starting + // their own activity we will bring home to the front. + moveHomeToFrontLocked(); + } + } + final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord, Uri[] grantedUriPermissions, int grantedMode, boolean onlyIfNeeded, boolean doResume) { @@ -2109,6 +2309,7 @@ public class ActivityStack { } boolean addingToTask = false; + TaskRecord reuseTask = null; if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK @@ -2139,13 +2340,14 @@ public class ActivityStack { // being started, which means not bringing it to the front // if the caller is not itself in the front. ActivityRecord curTop = topRunningNonDelayedActivityLocked(notTop); - if (curTop.task != taskTop.task) { + if (curTop != null && curTop.task != taskTop.task) { r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); boolean callerAtFront = sourceRecord == null || curTop.task == sourceRecord.task; if (callerAtFront) { // We really do want to push this one into the // user's face, right now. + moveHomeToFrontFromLaunchLocked(launchFlags); moveTaskToFrontLocked(taskTop.task, r); } } @@ -2164,7 +2366,16 @@ public class ActivityStack { } return START_RETURN_INTENT_TO_CALLER; } - if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 + if ((launchFlags & + (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) + == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) { + // The caller has requested to completely replace any + // existing task with its new activity. Well that should + // not be too hard... + reuseTask = taskTop.task; + performClearTaskLocked(taskTop.task.taskId); + reuseTask.setIntent(r.intent, r.info); + } else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { // In this situation we want to remove all activities @@ -2172,7 +2383,7 @@ public class ActivityStack { // cases this means we are resetting the task to its // initial state. ActivityRecord top = performClearTaskLocked( - taskTop.task.taskId, r, launchFlags, true); + taskTop.task.taskId, r, launchFlags); if (top != null) { if (top.frontOfTask) { // Activity aliases may mean we use different @@ -2233,7 +2444,7 @@ public class ActivityStack { // for now we'll just drop it. taskTop.task.setIntent(r.intent, r.info); } - if (!addingToTask) { + if (!addingToTask && reuseTask == null) { // We didn't do anything... but it was needed (a.k.a., client // don't use that intent!) And for paranoia, make // sure we have correctly resumed the top activity. @@ -2292,23 +2503,25 @@ public class ActivityStack { } boolean newTask = false; + boolean keepCurTransition = false; // Should this be considered a new task? if (r.resultTo == null && !addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { - // todo: should do better management of integers. - mService.mCurTask++; - if (mService.mCurTask <= 0) { - mService.mCurTask = 1; + if (reuseTask == null) { + // todo: should do better management of integers. + mService.mCurTask++; + if (mService.mCurTask <= 0) { + mService.mCurTask = 1; + } + r.task = new TaskRecord(mService.mCurTask, r.info, intent); + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + + " in new task " + r.task); + } else { + r.task = reuseTask; } - r.task = new TaskRecord(mService.mCurTask, r.info, intent, - (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); - if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r - + " in new task " + r.task); newTask = true; - if (mMainStack) { - mService.addRecentTaskLocked(r.task); - } + moveHomeToFrontFromLaunchLocked(launchFlags); } else if (sourceRecord != null) { if (!addingToTask && @@ -2317,7 +2530,8 @@ public class ActivityStack { // task, but the caller has asked to clear that task if the // activity is already running. ActivityRecord top = performClearTaskLocked( - sourceRecord.task.taskId, r, launchFlags, true); + sourceRecord.task.taskId, r, launchFlags); + keepCurTransition = true; if (top != null) { logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); top.deliverNewIntentLocked(callingUid, r.intent); @@ -2359,9 +2573,8 @@ public class ActivityStack { ActivityRecord prev = N > 0 ? (ActivityRecord)mHistory.get(N-1) : null; r.task = prev != null - ? prev.task - : new TaskRecord(mService.mCurTask, r.info, intent, - (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); + ? prev.task + : new TaskRecord(mService.mCurTask, r.info, intent); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new guessed " + r.task); } @@ -2380,25 +2593,11 @@ public class ActivityStack { EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.task.taskId); } logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task); - startActivityLocked(r, newTask, doResume); + startActivityLocked(r, newTask, doResume, keepCurTransition); return START_SUCCESS; } - final int startActivityMayWait(IApplicationThread caller, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded, - boolean debug, WaitResult outResult, Configuration config) { - // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - - boolean componentSpecified = intent.getComponent() != null; - - // Don't modify the client's object! - intent = new Intent(intent); - + ActivityInfo resolveActivity(Intent intent, String resolvedType, boolean debug) { // Collect information about the target of the Intent. ActivityInfo aInfo; try { @@ -2427,11 +2626,32 @@ public class ActivityStack { } } } + return aInfo; + } + + final int startActivityMayWait(IApplicationThread caller, int callingUid, + Intent intent, String resolvedType, Uri[] grantedUriPermissions, + int grantedMode, IBinder resultTo, + String resultWho, int requestCode, boolean onlyIfNeeded, + boolean debug, WaitResult outResult, Configuration config) { + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + boolean componentSpecified = intent.getComponent() != null; + + // Don't modify the client's object! + intent = new Intent(intent); + + // Collect information about the target of the Intent. + ActivityInfo aInfo = resolveActivity(intent, resolvedType, debug); synchronized (mService) { int callingPid; - int callingUid; - if (caller == null) { + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { callingPid = Binder.getCallingPid(); callingUid = Binder.getCallingUid(); } else { @@ -2470,8 +2690,8 @@ public class ActivityStack { IIntentSender target = mService.getIntentSenderLocked( IActivityManager.INTENT_SENDER_ACTIVITY, "android", - realCallingUid, null, null, 0, intent, - resolvedType, PendingIntent.FLAG_CANCEL_CURRENT + realCallingUid, null, null, 0, new Intent[] { intent }, + new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); Intent newIntent = new Intent(); @@ -2516,7 +2736,7 @@ public class ActivityStack { int res = startActivityLocked(caller, intent, resolvedType, grantedUriPermissions, grantedMode, aInfo, resultTo, resultWho, requestCode, callingPid, callingUid, - onlyIfNeeded, componentSpecified); + onlyIfNeeded, componentSpecified, null); if (mConfigWillChange && mMainStack) { // If the caller also wants to switch to a new configuration, @@ -2567,6 +2787,75 @@ public class ActivityStack { } } + final int startActivities(IApplicationThread caller, int callingUid, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + if (intents == null) { + throw new NullPointerException("intents is null"); + } + if (resolvedTypes == null) { + throw new NullPointerException("resolvedTypes is null"); + } + if (intents.length != resolvedTypes.length) { + throw new IllegalArgumentException("intents are length different than resolvedTypes"); + } + + ActivityRecord[] outActivity = new ActivityRecord[1]; + + int callingPid; + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { + callingPid = Binder.getCallingPid(); + callingUid = Binder.getCallingUid(); + } else { + callingPid = callingUid = -1; + } + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mService) { + + for (int i=0; i<intents.length; i++) { + Intent intent = intents[i]; + if (intent == null) { + continue; + } + + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + boolean componentSpecified = intent.getComponent() != null; + + // Don't modify the client's object! + intent = new Intent(intent); + + // Collect information about the target of the Intent. + ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], false); + + if (mMainStack && aInfo != null && (aInfo.applicationInfo.flags + & ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { + throw new IllegalArgumentException( + "FLAG_CANT_SAVE_STATE not supported here"); + } + + int res = startActivityLocked(caller, intent, resolvedTypes[i], + null, 0, aInfo, resultTo, null, -1, callingPid, callingUid, + false, componentSpecified, outActivity); + if (res < 0) { + return res; + } + + resultTo = outActivity[0]; + } + } + } finally { + Binder.restoreCallingIdentity(origId); + } + + return IActivityManager.START_SUCCESS; + } + void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, long thisTime, long totalTime) { for (int i=mWaitingActivityLaunched.size()-1; i>=0; i--) { @@ -2644,6 +2933,9 @@ public class ActivityStack { mService.mWindowManager.setAppVisibility(r, false); } r.app.thread.scheduleStopActivity(r, r.visible, r.configChangeFlags); + if (mService.isSleeping()) { + r.setSleeping(true); + } } catch (Exception e) { // Maybe just ignore exceptions here... if the process // has crashed, our death notification will clean things @@ -2687,7 +2979,7 @@ public class ActivityStack { mService.mWindowManager.setAppVisibility(s, false); } } - if (!s.waitingVisible && remove) { + if ((!s.waitingVisible || mService.isSleeping()) && remove) { if (localLOGV) Slog.v(TAG, "Ready to stop: " + s); if (stops == null) { stops = new ArrayList<ActivityRecord>(); @@ -2891,7 +3183,7 @@ public class ActivityStack { return false; } - r.finishing = true; + r.makeFinishing(); EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY, System.identityHashCode(r), r.task.taskId, r.shortComponentName, reason); @@ -2958,7 +3250,7 @@ public class ActivityStack { "Prepare close transition: finishing " + r); mService.mWindowManager.prepareAppTransition(endTask ? WindowManagerPolicy.TRANSIT_TASK_CLOSE - : WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE); + : WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE, false); // Tell window manager to prepare for this one to be removed. mService.mWindowManager.setAppVisibility(r, false); @@ -3011,6 +3303,8 @@ public class ActivityStack { Message msg = Message.obtain(); msg.what = IDLE_NOW_MSG; mHandler.sendMessage(msg); + } else { + checkReadyForSleepLocked(); } } r.state = ActivityState.STOPPING; @@ -3020,6 +3314,7 @@ public class ActivityStack { // make sure the record is cleaned out of other places. mStoppingActivities.remove(r); + mGoingToSleepActivities.remove(r); mWaitingVisibleActivities.remove(r); if (mResumedActivity == r) { mResumedActivity = null; @@ -3096,6 +3391,7 @@ public class ActivityStack { private final void removeActivityFromHistoryLocked(ActivityRecord r) { if (r.state != ActivityState.DESTROYED) { + r.makeFinishing(); mHistory.remove(r); r.inHistory = false; r.state = ActivityState.DESTROYED; @@ -3246,10 +3542,30 @@ public class ActivityStack { void removeHistoryRecordsForAppLocked(ProcessRecord app) { removeHistoryRecordsForAppLocked(mLRUActivities, app); removeHistoryRecordsForAppLocked(mStoppingActivities, app); + removeHistoryRecordsForAppLocked(mGoingToSleepActivities, app); removeHistoryRecordsForAppLocked(mWaitingVisibleActivities, app); removeHistoryRecordsForAppLocked(mFinishingActivities, app); } + /** + * Move the current home activity's task (if one exists) to the front + * of the stack. + */ + final void moveHomeToFrontLocked() { + TaskRecord homeTask = null; + for (int i=mHistory.size()-1; i>=0; i--) { + ActivityRecord hr = (ActivityRecord)mHistory.get(i); + if (hr.isHomeActivity) { + homeTask = hr.task; + break; + } + } + if (homeTask != null) { + moveTaskToFrontLocked(homeTask, null); + } + } + + final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason) { if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr); @@ -3274,17 +3590,12 @@ public class ActivityStack { ActivityRecord r = (ActivityRecord)mHistory.get(pos); if (localLOGV) Slog.v( TAG, "At " + pos + " ckp " + r.task + ": " + r); - boolean first = true; if (r.task.taskId == task) { if (localLOGV) Slog.v(TAG, "Removing and adding at " + top); mHistory.remove(pos); mHistory.add(top, r); moved.add(0, r); top--; - if (first && mMainStack) { - mService.addRecentTaskLocked(r.task); - first = false; - } } pos--; } @@ -3293,13 +3604,15 @@ public class ActivityStack { "Prepare to front transition: task=" + tr); if (reason != null && (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_NONE, false); ActivityRecord r = topRunningActivityLocked(null); if (r != null) { mNoAnimActivities.add(r); } } else { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_FRONT); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_TASK_TO_FRONT, false); } mService.mWindowManager.moveAppTokensToTop(moved); @@ -3378,13 +3691,15 @@ public class ActivityStack { if (reason != null && (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_NONE, false); ActivityRecord r = topRunningActivityLocked(null); if (r != null) { mNoAnimActivities.add(r); } } else { - mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_BACK); + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_TASK_TO_BACK, false); } mService.mWindowManager.moveAppTokensToBottom(moved); if (VALIDATE_TOKENS) { diff --git a/services/java/com/android/server/am/AppWaitingForDebuggerDialog.java b/services/java/com/android/server/am/AppWaitingForDebuggerDialog.java index 8e9818d..9fb48b3 100644 --- a/services/java/com/android/server/am/AppWaitingForDebuggerDialog.java +++ b/services/java/com/android/server/am/AppWaitingForDebuggerDialog.java @@ -17,6 +17,7 @@ package com.android.server.am; import android.content.Context; +import android.content.DialogInterface; import android.os.Handler; import android.os.Message; @@ -49,7 +50,7 @@ class AppWaitingForDebuggerDialog extends BaseErrorDialog { text.append(" is waiting for the debugger to attach."); setMessage(text.toString()); - setButton("Force Close", mHandler.obtainMessage(1, app)); + setButton(DialogInterface.BUTTON_POSITIVE, "Force Close", mHandler.obtainMessage(1, app)); setTitle("Waiting For Debugger"); getWindow().setTitle("Waiting For Debugger: " + app.info.processName); } diff --git a/services/java/com/android/server/am/BaseErrorDialog.java b/services/java/com/android/server/am/BaseErrorDialog.java index 03e3272..d1e89bc 100644 --- a/services/java/com/android/server/am/BaseErrorDialog.java +++ b/services/java/com/android/server/am/BaseErrorDialog.java @@ -34,7 +34,7 @@ class BaseErrorDialog extends AlertDialog { getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); getWindow().setTitle("Error Dialog"); - setIcon(R.drawable.ic_dialog_alert); + setIconAttribute(R.attr.alertDialogIcon); } public void onStart() { diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index 367c4cf..963a691 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -16,7 +16,9 @@ package com.android.server.am; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.Binder; @@ -46,6 +48,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub { final BatteryStatsImpl mStats; Context mContext; + private boolean mBluetoothPendingStats; + private BluetoothHeadset mBluetoothHeadset; BatteryStatsService(String filename) { mStats = new BatteryStatsImpl(filename); @@ -287,16 +291,43 @@ public final class BatteryStatsService extends IBatteryStats.Stub { public void noteBluetoothOn() { enforceCallingPermission(); - BluetoothHeadset headset = new BluetoothHeadset(mContext, null); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, + BluetoothProfile.HEADSET); + } synchronized (mStats) { - mStats.noteBluetoothOnLocked(); - mStats.setBtHeadset(headset); + if (mBluetoothHeadset != null) { + mStats.noteBluetoothOnLocked(); + mStats.setBtHeadset(mBluetoothHeadset); + } else { + mBluetoothPendingStats = true; + } } } - + + private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = + new BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mBluetoothHeadset = (BluetoothHeadset) proxy; + synchronized (mStats) { + if (mBluetoothPendingStats) { + mStats.noteBluetoothOnLocked(); + mStats.setBtHeadset(mBluetoothHeadset); + mBluetoothPendingStats = false; + } + } + } + + public void onServiceDisconnected(int profile) { + mBluetoothHeadset = null; + } + }; + public void noteBluetoothOff() { enforceCallingPermission(); synchronized (mStats) { + mBluetoothPendingStats = false; mStats.noteBluetoothOffLocked(); } } diff --git a/services/java/com/android/server/am/FactoryErrorDialog.java b/services/java/com/android/server/am/FactoryErrorDialog.java index 2e25474..b19bb5c 100644 --- a/services/java/com/android/server/am/FactoryErrorDialog.java +++ b/services/java/com/android/server/am/FactoryErrorDialog.java @@ -17,6 +17,7 @@ package com.android.server.am; import android.content.Context; +import android.content.DialogInterface; import android.os.Handler; import android.os.Message; @@ -26,7 +27,8 @@ class FactoryErrorDialog extends BaseErrorDialog { setCancelable(false); setTitle(context.getText(com.android.internal.R.string.factorytest_failed)); setMessage(msg); - setButton(context.getText(com.android.internal.R.string.factorytest_reboot), + setButton(DialogInterface.BUTTON_POSITIVE, + context.getText(com.android.internal.R.string.factorytest_reboot), mHandler.obtainMessage(0)); getWindow().setTitle("Factory Error"); } diff --git a/services/java/com/android/server/am/LaunchWarningWindow.java b/services/java/com/android/server/am/LaunchWarningWindow.java index 4130e33..1114a31 100644 --- a/services/java/com/android/server/am/LaunchWarningWindow.java +++ b/services/java/com/android/server/am/LaunchWarningWindow.java @@ -4,6 +4,7 @@ import com.android.internal.R; import android.app.Dialog; import android.content.Context; +import android.util.TypedValue; import android.view.Window; import android.view.WindowManager; import android.widget.ImageView; @@ -20,8 +21,11 @@ public class LaunchWarningWindow extends Dialog { setContentView(R.layout.launch_warning); setTitle(context.getText(R.string.launch_warning_title)); - getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, - R.drawable.ic_dialog_alert); + + TypedValue out = new TypedValue(); + getContext().getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true); + getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, out.resourceId); + ImageView icon = (ImageView)findViewById(R.id.replace_app_icon); icon.setImageDrawable(next.info.applicationInfo.loadIcon(context.getPackageManager())); TextView text = (TextView)findViewById(R.id.replace_message); diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index 7a85eb8..ee6e420 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -47,20 +47,24 @@ class PendingIntentRecord extends IIntentSender.Stub { final int requestCode; final Intent requestIntent; final String requestResolvedType; + Intent[] allIntents; + String[] allResolvedTypes; final int flags; final int hashCode; private static final int ODD_PRIME_NUMBER = 37; Key(int _t, String _p, ActivityRecord _a, String _w, - int _r, Intent _i, String _it, int _f) { + int _r, Intent[] _i, String[] _it, int _f) { type = _t; packageName = _p; activity = _a; who = _w; requestCode = _r; - requestIntent = _i; - requestResolvedType = _it; + requestIntent = _i != null ? _i[_i.length-1] : null; + requestResolvedType = _it != null ? _it[_it.length-1] : null; + allIntents = _i; + allResolvedTypes = _it; flags = _f; int hash = 23; @@ -72,11 +76,11 @@ class PendingIntentRecord extends IIntentSender.Stub { if (_a != null) { hash = (ODD_PRIME_NUMBER*hash) + _a.hashCode(); } - if (_i != null) { - hash = (ODD_PRIME_NUMBER*hash) + _i.filterHashCode(); + if (requestIntent != null) { + hash = (ODD_PRIME_NUMBER*hash) + requestIntent.filterHashCode(); } - if (_it != null) { - hash = (ODD_PRIME_NUMBER*hash) + _it.hashCode(); + if (requestResolvedType != null) { + hash = (ODD_PRIME_NUMBER*hash) + requestResolvedType.hashCode(); } hash = (ODD_PRIME_NUMBER*hash) + _p.hashCode(); hash = (ODD_PRIME_NUMBER*hash) + _t; @@ -209,9 +213,24 @@ class PendingIntentRecord extends IIntentSender.Stub { switch (key.type) { case IActivityManager.INTENT_SENDER_ACTIVITY: try { - owner.startActivityInPackage(uid, - finalIntent, resolvedType, - resultTo, resultWho, requestCode, false); + if (key.allIntents != null && key.allIntents.length > 1) { + Intent[] allIntents = new Intent[key.allIntents.length]; + String[] allResolvedTypes = new String[key.allIntents.length]; + System.arraycopy(key.allIntents, 0, allIntents, 0, + key.allIntents.length); + if (key.allResolvedTypes != null) { + System.arraycopy(key.allResolvedTypes, 0, allResolvedTypes, 0, + key.allResolvedTypes.length); + } + allIntents[allIntents.length-1] = finalIntent; + allResolvedTypes[allResolvedTypes.length-1] = resolvedType; + owner.startActivitiesInPackage(uid, allIntents, + allResolvedTypes, resultTo); + } else { + owner.startActivityInPackage(uid, + finalIntent, resolvedType, + resultTo, resultWho, requestCode, false); + } } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Unable to send startActivity intent", e); diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java index bcb8f54..86cec42 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -19,14 +19,13 @@ package com.android.server.am; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; -import android.os.SystemClock; +import android.graphics.Bitmap; import java.io.PrintWriter; class TaskRecord { final int taskId; // Unique identifier for this task. final String affinity; // The affinity name for this task, or null. - final boolean clearOnBackground; // As per the original activity. 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. @@ -35,14 +34,14 @@ class TaskRecord { long lastActiveTime; // Last time this task was active, including sleep. boolean rootWasReset; // True if the intent at the root of the task had // the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag. + Bitmap lastThumbnail; // Last thumbnail captured for this task. + CharSequence lastDescription; // Last description captured for this task. String stringName; // caching of toString() result. - TaskRecord(int _taskId, ActivityInfo info, Intent _intent, - boolean _clearOnBackground) { + TaskRecord(int _taskId, ActivityInfo info, Intent _intent) { taskId = _taskId; affinity = info.taskAffinity; - clearOnBackground = _clearOnBackground; setIntent(_intent, info); } @@ -86,9 +85,8 @@ class TaskRecord { } void dump(PrintWriter pw, String prefix) { - if (clearOnBackground || numActivities != 0 || rootWasReset) { - pw.print(prefix); pw.print("clearOnBackground="); pw.print(clearOnBackground); - pw.print(" numActivities="); pw.print(numActivities); + if (numActivities != 0 || rootWasReset) { + pw.print(prefix); pw.print("numActivities="); pw.print(numActivities); pw.print(" rootWasReset="); pw.println(rootWasReset); } if (affinity != null) { diff --git a/services/java/com/android/server/am/UriPermissionOwner.java b/services/java/com/android/server/am/UriPermissionOwner.java index 99c82e6..68a2e0f 100644 --- a/services/java/com/android/server/am/UriPermissionOwner.java +++ b/services/java/com/android/server/am/UriPermissionOwner.java @@ -45,7 +45,7 @@ class UriPermissionOwner { } Binder getExternalTokenLocked() { - if (externalToken != null) { + if (externalToken == null) { externalToken = new ExternalToken(); } return externalToken; diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index a73a4ce..dd9db9a 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -19,20 +19,19 @@ package com.android.server.connectivity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.bluetooth.BluetoothPan; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; -import android.hardware.Usb; +import android.hardware.UsbManager; import android.net.ConnectivityManager; import android.net.InterfaceConfiguration; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; import android.net.NetworkInfo; -import android.net.NetworkUtils; import android.os.Binder; import android.os.Environment; import android.os.Handler; @@ -52,8 +51,10 @@ import com.android.internal.util.HierarchicalStateMachine; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.net.InetAddress; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedList; import java.util.Set; /** * @hide @@ -66,7 +67,8 @@ import java.util.Set; public class Tethering extends INetworkManagementEventObserver.Stub { private Context mContext; - private final String TAG = "Tethering"; + private final static String TAG = "Tethering"; + private final static boolean DEBUG = true; private boolean mBooted = false; //used to remember if we got connected before boot finished @@ -75,6 +77,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { // TODO - remove both of these - should be part of interface inspection/selection stuff private String[] mTetherableUsbRegexs; private String[] mTetherableWifiRegexs; + private String[] mTetherableBluetoothRegexs; private String[] mUpstreamIfaceRegexs; private Looper mLooper; @@ -87,17 +90,31 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129"; private static final String USB_NETMASK = "255.255.255.0"; - // FYI - the default wifi is 192.168.43.1 and 255.255.255.0 + // USB is 192.168.42.1 and 255.255.255.0 + // Wifi is 192.168.43.1 and 255.255.255.0 + // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1 + // with 255.255.255.0 private String[] mDhcpRange; private static final String DHCP_DEFAULT_RANGE1_START = "192.168.42.2"; private static final String DHCP_DEFAULT_RANGE1_STOP = "192.168.42.254"; private static final String DHCP_DEFAULT_RANGE2_START = "192.168.43.2"; private static final String DHCP_DEFAULT_RANGE2_STOP = "192.168.43.254"; + private static final String DHCP_DEFAULT_RANGE3_START = "192.168.44.2"; + private static final String DHCP_DEFAULT_RANGE3_STOP = "192.168.44.254"; + private static final String DHCP_DEFAULT_RANGE4_START = "192.168.45.2"; + private static final String DHCP_DEFAULT_RANGE4_STOP = "192.168.45.254"; + private static final String DHCP_DEFAULT_RANGE5_START = "192.168.46.2"; + private static final String DHCP_DEFAULT_RANGE5_STOP = "192.168.46.254"; + private static final String DHCP_DEFAULT_RANGE6_START = "192.168.47.2"; + private static final String DHCP_DEFAULT_RANGE6_STOP = "192.168.47.254"; + private static final String DHCP_DEFAULT_RANGE7_START = "192.168.48.2"; + private static final String DHCP_DEFAULT_RANGE7_STOP = "192.168.48.254"; + private String[] mDnsServers; private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8"; - private static final String DNS_DEFAULT_SERVER2 = "4.2.2.2"; + private static final String DNS_DEFAULT_SERVER2 = "8.8.4.4"; // resampled each time we turn on tethering - used as cache for settings/config-val private boolean mDunRequired; // configuration info - must use DUN apn on 3g @@ -111,14 +128,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private boolean mUsbMassStorageOff; // track the status of USB Mass Storage private boolean mUsbConnected; // track the status of USB connection - // mUsbHandler message - static final int USB_STATE_CHANGE = 1; - static final int USB_DISCONNECTED = 0; - static final int USB_CONNECTED = 1; - - // Time to delay before processing USB disconnect events - static final long USB_DISCONNECT_DELAY = 1000; - public Tethering(Context context, Looper looper) { mContext = context; mLooper = looper; @@ -143,7 +152,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mStateReceiver = new StateReceiver(); IntentFilter filter = new IntentFilter(); - filter.addAction(Usb.ACTION_USB_STATE); + filter.addAction(UsbManager.ACTION_USB_STATE); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(Intent.ACTION_BOOT_COMPLETED); mContext.registerReceiver(mStateReceiver, filter); @@ -160,11 +169,21 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mDhcpRange = context.getResources().getStringArray( com.android.internal.R.array.config_tether_dhcp_range); if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) { - mDhcpRange = new String[4]; + mDhcpRange = new String[14]; mDhcpRange[0] = DHCP_DEFAULT_RANGE1_START; mDhcpRange[1] = DHCP_DEFAULT_RANGE1_STOP; mDhcpRange[2] = DHCP_DEFAULT_RANGE2_START; mDhcpRange[3] = DHCP_DEFAULT_RANGE2_STOP; + mDhcpRange[4] = DHCP_DEFAULT_RANGE3_START; + mDhcpRange[5] = DHCP_DEFAULT_RANGE3_STOP; + mDhcpRange[6] = DHCP_DEFAULT_RANGE4_START; + mDhcpRange[7] = DHCP_DEFAULT_RANGE4_STOP; + mDhcpRange[8] = DHCP_DEFAULT_RANGE5_START; + mDhcpRange[9] = DHCP_DEFAULT_RANGE5_STOP; + mDhcpRange[10] = DHCP_DEFAULT_RANGE6_START; + mDhcpRange[11] = DHCP_DEFAULT_RANGE6_STOP; + mDhcpRange[12] = DHCP_DEFAULT_RANGE7_START; + mDhcpRange[13] = DHCP_DEFAULT_RANGE7_STOP; } mDunRequired = false; // resample when we turn on @@ -172,6 +191,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { com.android.internal.R.array.config_tether_usb_regexs); mTetherableWifiRegexs = context.getResources().getStringArray( com.android.internal.R.array.config_tether_wifi_regexs); + mTetherableBluetoothRegexs = context.getResources().getStringArray( + com.android.internal.R.array.config_tether_bluetooth_regexs); mUpstreamIfaceRegexs = context.getResources().getStringArray( com.android.internal.R.array.config_tether_upstream_regexs); @@ -182,7 +203,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } public void interfaceLinkStatusChanged(String iface, boolean link) { - Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link); + if (DEBUG) Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link); boolean found = false; boolean usb = false; if (isWifi(iface)) { @@ -190,6 +211,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } else if (isUsb(iface)) { found = true; usb = true; + } else if (isBluetooth(iface)) { + found = true; } if (found == false) return; @@ -224,6 +247,12 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return false; } + public boolean isBluetooth(String iface) { + for (String regex : mTetherableBluetoothRegexs) { + if (iface.matches(regex)) return true; + } + return false; + } public void interfaceAdded(String iface) { IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); @@ -236,29 +265,34 @@ public class Tethering extends INetworkManagementEventObserver.Stub { found = true; usb = true; } + if (isBluetooth(iface)) { + found = true; + } if (found == false) { - Log.d(TAG, iface + " is not a tetherable iface, ignoring"); + if (DEBUG) Log.d(TAG, iface + " is not a tetherable iface, ignoring"); return; } synchronized (mIfaces) { TetherInterfaceSM sm = mIfaces.get(iface); if (sm != null) { - Log.e(TAG, "active iface (" + iface + ") reported as added, ignoring"); + if (DEBUG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring"); return; } sm = new TetherInterfaceSM(iface, mLooper, usb); mIfaces.put(iface, sm); sm.start(); } - Log.d(TAG, "interfaceAdded :" + iface); + if (DEBUG) Log.d(TAG, "interfaceAdded :" + iface); } public void interfaceRemoved(String iface) { synchronized (mIfaces) { TetherInterfaceSM sm = mIfaces.get(iface); if (sm == null) { - Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring"); + if (DEBUG) { + Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring"); + } return; } sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN); @@ -329,13 +363,14 @@ public class Tethering extends INetworkManagementEventObserver.Stub { boolean wifiTethered = false; boolean usbTethered = false; + boolean bluetoothTethered = false; synchronized (mIfaces) { Set ifaces = mIfaces.keySet(); for (Object iface : ifaces) { TetherInterfaceSM sm = mIfaces.get(iface); if (sm != null) { - if(sm.isErrored()) { + if (sm.isErrored()) { erroredList.add((String)iface); } else if (sm.isAvailable()) { availableList.add((String)iface); @@ -344,6 +379,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { usbTethered = true; } else if (isWifi((String)iface)) { wifiTethered = true; + } else if (isBluetooth((String)iface)) { + bluetoothTethered = true; } activeList.add((String)iface); } @@ -358,17 +395,25 @@ public class Tethering extends INetworkManagementEventObserver.Stub { broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ERRORED_TETHER, erroredList); mContext.sendStickyBroadcast(broadcast); - Log.d(TAG, "sendTetherStateChangedBroadcast " + availableList.size() + ", " + - activeList.size() + ", " + erroredList.size()); + if (DEBUG) { + Log.d(TAG, "sendTetherStateChangedBroadcast " + availableList.size() + ", " + + activeList.size() + ", " + erroredList.size()); + } if (usbTethered) { - if (wifiTethered) { + if (wifiTethered || bluetoothTethered) { showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general); } else { showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_usb); } } else if (wifiTethered) { - showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_wifi); + if (bluetoothTethered) { + showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general); + } else { + showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_wifi); + } + } else if (bluetoothTethered) { + showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_bluetooth); } else { clearTetheredNotification(); } @@ -399,7 +444,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { CharSequence message = r.getText(com.android.internal.R.string. tethered_notification_message); - if(mTetheredNotification == null) { + if (mTetheredNotification == null) { mTetheredNotification = new Notification(); mTetheredNotification.when = 0; } @@ -429,25 +474,12 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } - private Handler mUsbHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - mUsbConnected = (msg.arg1 == USB_CONNECTED); - updateUsbStatus(); - } - }; - private class StateReceiver extends BroadcastReceiver { public void onReceive(Context content, Intent intent) { String action = intent.getAction(); - if (action.equals(Usb.ACTION_USB_STATE)) { - // process connect events immediately, but delay handling disconnects - // to debounce USB configuration changes - boolean connected = intent.getExtras().getBoolean(Usb.USB_CONNECTED); - Message msg = Message.obtain(mUsbHandler, USB_STATE_CHANGE, - (connected ? USB_CONNECTED : USB_DISCONNECTED), 0); - mUsbHandler.removeMessages(USB_STATE_CHANGE); - mUsbHandler.sendMessageDelayed(msg, connected ? 0 : USB_DISCONNECT_DELAY); + if (action.equals(UsbManager.ACTION_USB_STATE)) { + mUsbConnected = intent.getExtras().getBoolean(UsbManager.USB_CONNECTED); + updateUsbStatus(); } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) { mUsbMassStorageOff = false; updateUsbStatus(); @@ -456,6 +488,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mUsbMassStorageOff = true; updateUsbStatus(); } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + if (DEBUG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION"); mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED); } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { mBooted = true; @@ -486,9 +519,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } - // toggled when we enter/leave the fully teathered state + // toggled when we enter/leave the fully tethered state private boolean enableUsbRndis(boolean enabled) { - Log.d(TAG, "enableUsbRndis(" + enabled + ")"); + if (DEBUG) Log.d(TAG, "enableUsbRndis(" + enabled + ")"); IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); @@ -513,7 +546,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { // configured when we start tethering and unconfig'd on error or conclusion private boolean configureUsbIface(boolean enabled) { - Log.d(TAG, "configureUsbIface(" + enabled + ")"); + if (DEBUG) Log.d(TAG, "configureUsbIface(" + enabled + ")"); IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); @@ -532,16 +565,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { try { ifcg = service.getInterfaceConfig(iface); if (ifcg != null) { - String[] addr = USB_NEAR_IFACE_ADDR.split("\\."); - ifcg.ipAddr = (Integer.parseInt(addr[0]) << 24) + - (Integer.parseInt(addr[1]) << 16) + - (Integer.parseInt(addr[2]) << 8) + - (Integer.parseInt(addr[3])); - addr = USB_NETMASK.split("\\."); - ifcg.netmask = (Integer.parseInt(addr[0]) << 24) + - (Integer.parseInt(addr[1]) << 16) + - (Integer.parseInt(addr[2]) << 8) + - (Integer.parseInt(addr[3])); + ifcg.addr = InetAddress.getByName(USB_NEAR_IFACE_ADDR); + ifcg.mask = InetAddress.getByName(USB_NETMASK); if (enabled) { ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up"); } else { @@ -569,6 +594,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return mTetherableWifiRegexs; } + public String[] getTetherableBluetoothRegexs() { + return mTetherableBluetoothRegexs; + } + public String[] getUpstreamIfaceRegexs() { return mUpstreamIfaceRegexs; } @@ -760,7 +789,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { @Override public boolean processMessage(Message message) { - Log.d(TAG, "InitialState.processMessage what=" + message.what); + if (DEBUG) Log.d(TAG, "InitialState.processMessage what=" + message.what); boolean retValue = true; switch (message.what) { case CMD_TETHER_REQUESTED: @@ -801,7 +830,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } @Override public boolean processMessage(Message message) { - Log.d(TAG, "StartingState.processMessage what=" + message.what); + if (DEBUG) Log.d(TAG, "StartingState.processMessage what=" + message.what); boolean retValue = true; switch (message.what) { // maybe a parent class? @@ -853,7 +882,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return; } if (mUsb) Tethering.this.enableUsbRndis(true); - Log.d(TAG, "Tethered " + mIfaceName); + if (DEBUG) Log.d(TAG, "Tethered " + mIfaceName); setAvailable(false); setTethered(true); sendTetherStateChangedBroadcast(); @@ -864,7 +893,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } @Override public boolean processMessage(Message message) { - Log.d(TAG, "TetheredState.processMessage what=" + message.what); + if (DEBUG) Log.d(TAG, "TetheredState.processMessage what=" + message.what); boolean retValue = true; boolean error = false; switch (message.what) { @@ -907,13 +936,18 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } else if (message.what == CMD_INTERFACE_DOWN) { transitionTo(mUnavailableState); } - Log.d(TAG, "Untethered " + mIfaceName); + if (DEBUG) Log.d(TAG, "Untethered " + mIfaceName); break; case CMD_TETHER_CONNECTION_CHANGED: String newUpstreamIfaceName = (String)(message.obj); b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); service = INetworkManagementService.Stub.asInterface(b); - + if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) || + (mMyUpstreamIfaceName != null && + mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) { + if (DEBUG) Log.d(TAG, "Connection changed noop - dropping"); + break; + } if (mMyUpstreamIfaceName != null) { try { service.disableNat(mIfaceName, mMyUpstreamIfaceName); @@ -980,7 +1014,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { ConnectivityManager.TETHER_ERROR_MASTER_ERROR); break; } - Log.d(TAG, "Tether lost upstream connection " + mIfaceName); + if (DEBUG) Log.d(TAG, "Tether lost upstream connection " + mIfaceName); sendTetherStateChangedBroadcast(); if (mUsb) { if (!Tethering.this.configureUsbIface(false)) { @@ -1056,7 +1090,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private ArrayList mNotifyList; - private boolean mConnectionRequested = false; + private int mCurrentConnectionSequence; + private boolean mMobileReserved = false; private String mUpstreamIfaceName = null; @@ -1095,43 +1130,47 @@ public class Tethering extends INetworkManagementEventObserver.Stub { public boolean processMessage(Message m) { return false; } - protected int turnOnMobileConnection() { + protected boolean turnOnMobileConnection() { + boolean retValue = true; + if (mMobileReserved) return retValue; IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); - int retValue = Phone.APN_REQUEST_FAILED; + int result = Phone.APN_REQUEST_FAILED; try { - retValue = service.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, - (mDunRequired ? Phone.FEATURE_ENABLE_DUN : Phone.FEATURE_ENABLE_HIPRI), - new Binder()); + result = service.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, + (mDunRequired ? Phone.FEATURE_ENABLE_DUN_ALWAYS : + Phone.FEATURE_ENABLE_HIPRI), new Binder()); } catch (Exception e) { } - switch (retValue) { + switch (result) { case Phone.APN_ALREADY_ACTIVE: case Phone.APN_REQUEST_STARTED: - sendMessageDelayed(CMD_CELL_CONNECTION_RENEW, CELL_CONNECTION_RENEW_MS); - mConnectionRequested = true; + mMobileReserved = true; + Message m = obtainMessage(CMD_CELL_CONNECTION_RENEW); + m.arg1 = ++mCurrentConnectionSequence; + sendMessageDelayed(m, CELL_CONNECTION_RENEW_MS); break; case Phone.APN_REQUEST_FAILED: default: - mConnectionRequested = false; + retValue = false; break; } return retValue; } protected boolean turnOffMobileConnection() { - if (mConnectionRequested) { + if (mMobileReserved) { IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); try { service.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, - (mDunRequired? Phone.FEATURE_ENABLE_DUN : + (mDunRequired? Phone.FEATURE_ENABLE_DUN_ALWAYS : Phone.FEATURE_ENABLE_HIPRI)); } catch (Exception e) { return false; } - mConnectionRequested = false; + mMobileReserved = false; } return true; } @@ -1194,32 +1233,35 @@ public class Tethering extends INetworkManagementEventObserver.Stub { for (String iface : ifaces) { for (String regex : mUpstreamIfaceRegexs) { if (iface.matches(regex)) { - // verify it is up! + // verify it is active InterfaceConfiguration ifcg = null; try { ifcg = service.getInterfaceConfig(iface); + if (ifcg.isActive()) { + return iface; + } } catch (Exception e) { Log.e(TAG, "Error getting iface config :" + e); // ignore - try next continue; } - if (ifcg.interfaceFlags.contains("up")) { - return iface; - } } } } return null; } + protected void chooseUpstreamType(boolean tryCell) { // decide if the current upstream is good or not and if not // do something about it (start up DUN if required or HiPri if not) String iface = findActiveUpstreamIface(); IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b); - mConnectionRequested = false; - Log.d(TAG, "chooseUpstreamType(" + tryCell + "), dunRequired =" - + mDunRequired + ", iface=" + iface); + mMobileReserved = false; + if (DEBUG) { + Log.d(TAG, "chooseUpstreamType(" + tryCell + "), dunRequired =" + + mDunRequired + ", iface=" + iface); + } if (iface != null) { try { if (mDunRequired) { @@ -1227,7 +1269,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { NetworkInfo info = cm.getNetworkInfo( ConnectivityManager.TYPE_MOBILE_DUN); if (info.isConnected()) { - Log.d(TAG, "setting dun ifacename =" + iface); + if (DEBUG) Log.d(TAG, "setting dun ifacename =" + iface); // even if we're already connected - it may be somebody else's // refcount, so add our own turnOnMobileConnection(); @@ -1239,11 +1281,11 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } } else { - Log.d(TAG, "checking if hipri brought us this connection"); + if (DEBUG) Log.d(TAG, "checking if hipri brought us this connection"); NetworkInfo info = cm.getNetworkInfo( ConnectivityManager.TYPE_MOBILE_HIPRI); if (info.isConnected()) { - Log.d(TAG, "yes - hipri in use"); + if (DEBUG) Log.d(TAG, "yes - hipri in use"); // even if we're already connected - it may be sombody else's // refcount, so add our own turnOnMobileConnection(); @@ -1256,16 +1298,19 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } // may have been set to null in the if above if (iface == null ) { + boolean success = false; if (tryCell == TRY_TO_SETUP_MOBILE_CONNECTION) { - turnOnMobileConnection(); + success = turnOnMobileConnection(); + } + if (!success) { + // wait for things to settle and retry + sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS); } - // wait for things to settle and retry - sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS); } notifyTetheredOfNewUpstreamIface(iface); } protected void notifyTetheredOfNewUpstreamIface(String ifaceName) { - Log.d(TAG, "notifying tethered with iface =" + ifaceName); + if (DEBUG) Log.d(TAG, "notifying tethered with iface =" + ifaceName); mUpstreamIfaceName = ifaceName; for (Object o : mNotifyList) { TetherInterfaceSM sm = (TetherInterfaceSM)o; @@ -1278,23 +1323,23 @@ public class Tethering extends INetworkManagementEventObserver.Stub { class InitialState extends TetherMasterUtilState { @Override public void enter() { - mConnectionRequested = false; + mMobileReserved = false; } @Override public boolean processMessage(Message message) { - Log.d(TAG, "MasterInitialState.processMessage what=" + message.what); + if (DEBUG) Log.d(TAG, "MasterInitialState.processMessage what=" + message.what); boolean retValue = true; switch (message.what) { case CMD_TETHER_MODE_REQUESTED: mDunRequired = isDunRequired(); TetherInterfaceSM who = (TetherInterfaceSM)message.obj; - Log.d(TAG, "Tether Mode requested by " + who.toString()); + if (DEBUG) Log.d(TAG, "Tether Mode requested by " + who.toString()); mNotifyList.add(who); transitionTo(mTetherModeAliveState); break; case CMD_TETHER_MODE_UNREQUESTED: who = (TetherInterfaceSM)message.obj; - Log.d(TAG, "Tether Mode unrequested by " + who.toString()); + if (DEBUG) Log.d(TAG, "Tether Mode unrequested by " + who.toString()); int index = mNotifyList.indexOf(who); if (index != -1) { mNotifyList.remove(who); @@ -1309,10 +1354,11 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } class TetherModeAliveState extends TetherMasterUtilState { - boolean mTryCell = WAIT_FOR_NETWORK_TO_SETTLE; + boolean mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE; @Override public void enter() { - mTryCell = WAIT_FOR_NETWORK_TO_SETTLE; // first pass lets just see what we have. + mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE; // better try something first pass + // or crazy tests cases will fail chooseUpstreamType(mTryCell); mTryCell = !mTryCell; turnOnMasterTetherSettings(); // may transition us out @@ -1324,7 +1370,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } @Override public boolean processMessage(Message message) { - Log.d(TAG, "TetherModeAliveState.processMessage what=" + message.what); + if (DEBUG) Log.d(TAG, "TetherModeAliveState.processMessage what=" + message.what); boolean retValue = true; switch (message.what) { case CMD_TETHER_MODE_REQUESTED: @@ -1351,9 +1397,12 @@ public class Tethering extends INetworkManagementEventObserver.Stub { case CMD_CELL_CONNECTION_RENEW: // make sure we're still using a requested connection - may have found // wifi or something since then. - if (mConnectionRequested) { - Log.d(TAG, "renewing mobile connection - requeuing for another " + - CELL_CONNECTION_RENEW_MS + "ms"); + if (mCurrentConnectionSequence == message.arg1) { + if (DEBUG) { + Log.d(TAG, "renewing mobile connection - requeuing for another " + + CELL_CONNECTION_RENEW_MS + "ms"); + } + mMobileReserved = false; // need to renew it turnOnMobileConnection(); } break; diff --git a/services/java/com/android/server/location/ComprehensiveCountryDetector.java b/services/java/com/android/server/location/ComprehensiveCountryDetector.java new file mode 100755 index 0000000..e9ce3ce --- /dev/null +++ b/services/java/com/android/server/location/ComprehensiveCountryDetector.java @@ -0,0 +1,359 @@ +/* + * 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.location; + +import android.content.Context; +import android.location.Country; +import android.location.CountryListener; +import android.location.Geocoder; +import android.provider.Settings; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Slog; + +import java.util.Locale; +import java.util.Timer; +import java.util.TimerTask; + +/** + * This class is used to detect the country where the user is. The sources of + * country are queried in order of reliability, like + * <ul> + * <li>Mobile network</li> + * <li>Location</li> + * <li>SIM's country</li> + * <li>Phone's locale</li> + * </ul> + * <p> + * Call the {@link #detectCountry()} to get the available country immediately. + * <p> + * To be notified of the future country change, using the + * {@link #setCountryListener(CountryListener)} + * <p> + * Using the {@link #stop()} to stop listening to the country change. + * <p> + * The country information will be refreshed every + * {@link #LOCATION_REFRESH_INTERVAL} once the location based country is used. + * + * @hide + */ +public class ComprehensiveCountryDetector extends CountryDetectorBase { + + private final static String TAG = "ComprehensiveCountryDetector"; + + /** + * The refresh interval when the location based country was used + */ + private final static long LOCATION_REFRESH_INTERVAL = 1000 * 60 * 60 * 24; // 1 day + + protected CountryDetectorBase mLocationBasedCountryDetector; + protected Timer mLocationRefreshTimer; + + private final int mPhoneType; + private Country mCountry; + private TelephonyManager mTelephonyManager; + private Country mCountryFromLocation; + private boolean mStopped = false; + private ServiceState mLastState; + + private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onServiceStateChanged(ServiceState serviceState) { + // TODO: Find out how often we will be notified, if this method is called too + // many times, let's consider querying the network. + Slog.d(TAG, "onServiceStateChanged"); + // We only care the state change + if (mLastState == null || mLastState.getState() != serviceState.getState()) { + detectCountry(true, true); + mLastState = new ServiceState(serviceState); + } + } + }; + + /** + * The listener for receiving the notification from LocationBasedCountryDetector. + */ + private CountryListener mLocationBasedCountryDetectionListener = new CountryListener() { + public void onCountryDetected(Country country) { + mCountryFromLocation = country; + // Don't start the LocationBasedCountryDetector. + detectCountry(true, false); + stopLocationBasedDetector(); + } + }; + + public ComprehensiveCountryDetector(Context context) { + super(context); + mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + mPhoneType = mTelephonyManager.getPhoneType(); + } + + @Override + public Country detectCountry() { + // Don't start the LocationBasedCountryDetector if we have been stopped. + return detectCountry(false, !mStopped); + } + + @Override + public void stop() { + Slog.i(TAG, "Stop the detector."); + cancelLocationRefresh(); + removePhoneStateListener(); + stopLocationBasedDetector(); + mListener = null; + mStopped = true; + } + + /** + * Get the country from different sources in order of the reliability. + */ + private Country getCountry() { + Country result = null; + result = getNetworkBasedCountry(); + if (result == null) { + result = getLastKnownLocationBasedCountry(); + } + if (result == null) { + result = getSimBasedCountry(); + } + if (result == null) { + result = getLocaleCountry(); + } + return result; + } + + /** + * @return the country from the mobile network. + */ + protected Country getNetworkBasedCountry() { + String countryIso = null; + // TODO: The document says the result may be unreliable on CDMA networks. Shall we use + // it on CDMA phone? We may test the Android primarily used countries. + if (mPhoneType == TelephonyManager.PHONE_TYPE_GSM) { + countryIso = mTelephonyManager.getNetworkCountryIso(); + if (!TextUtils.isEmpty(countryIso)) { + return new Country(countryIso, Country.COUNTRY_SOURCE_NETWORK); + } + } + return null; + } + + /** + * @return the cached location based country. + */ + protected Country getLastKnownLocationBasedCountry() { + return mCountryFromLocation; + } + + /** + * @return the country from SIM card + */ + protected Country getSimBasedCountry() { + String countryIso = null; + countryIso = mTelephonyManager.getSimCountryIso(); + if (!TextUtils.isEmpty(countryIso)) { + return new Country(countryIso, Country.COUNTRY_SOURCE_SIM); + } + return null; + } + + /** + * @return the country from the system's locale. + */ + protected Country getLocaleCountry() { + Locale defaultLocale = Locale.getDefault(); + if (defaultLocale != null) { + return new Country(defaultLocale.getCountry(), Country.COUNTRY_SOURCE_LOCALE); + } else { + return null; + } + } + + /** + * @param notifyChange indicates whether the listener should be notified the change of the + * country + * @param startLocationBasedDetection indicates whether the LocationBasedCountryDetector could + * be started if the current country source is less reliable than the location. + * @return the current available UserCountry + */ + private Country detectCountry(boolean notifyChange, boolean startLocationBasedDetection) { + Country country = getCountry(); + runAfterDetectionAsync(mCountry != null ? new Country(mCountry) : mCountry, country, + notifyChange, startLocationBasedDetection); + mCountry = country; + return mCountry; + } + + /** + * Run the tasks in the service's thread. + */ + protected void runAfterDetectionAsync(final Country country, final Country detectedCountry, + final boolean notifyChange, final boolean startLocationBasedDetection) { + mHandler.post(new Runnable() { + public void run() { + runAfterDetection( + country, detectedCountry, notifyChange, startLocationBasedDetection); + } + }); + } + + @Override + public void setCountryListener(CountryListener listener) { + CountryListener prevListener = mListener; + mListener = listener; + if (mListener == null) { + // Stop listening all services + removePhoneStateListener(); + stopLocationBasedDetector(); + cancelLocationRefresh(); + } else if (prevListener == null) { + addPhoneStateListener(); + detectCountry(false, true); + } + } + + void runAfterDetection(final Country country, final Country detectedCountry, + final boolean notifyChange, final boolean startLocationBasedDetection) { + if (notifyChange) { + notifyIfCountryChanged(country, detectedCountry); + } + if (startLocationBasedDetection && (detectedCountry == null + || detectedCountry.getSource() > Country.COUNTRY_SOURCE_LOCATION) + && isAirplaneModeOff() && mListener != null && isGeoCoderImplemented()) { + // Start finding location when the source is less reliable than the + // location and the airplane mode is off (as geocoder will not + // work). + // TODO : Shall we give up starting the detector within a + // period of time? + startLocationBasedDetector(mLocationBasedCountryDetectionListener); + } + if (detectedCountry == null + || detectedCountry.getSource() >= Country.COUNTRY_SOURCE_LOCATION) { + // Schedule the location refresh if the country source is + // not more reliable than the location or no country is + // found. + // TODO: Listen to the preference change of GPS, Wifi etc, + // and start detecting the country. + scheduleLocationRefresh(); + } else { + // Cancel the location refresh once the current source is + // more reliable than the location. + cancelLocationRefresh(); + stopLocationBasedDetector(); + } + } + + /** + * Find the country from LocationProvider. + */ + private synchronized void startLocationBasedDetector(CountryListener listener) { + if (mLocationBasedCountryDetector != null) { + return; + } + mLocationBasedCountryDetector = createLocationBasedCountryDetector(); + mLocationBasedCountryDetector.setCountryListener(listener); + mLocationBasedCountryDetector.detectCountry(); + } + + private synchronized void stopLocationBasedDetector() { + if (mLocationBasedCountryDetector != null) { + mLocationBasedCountryDetector.stop(); + mLocationBasedCountryDetector = null; + } + } + + protected CountryDetectorBase createLocationBasedCountryDetector() { + return new LocationBasedCountryDetector(mContext); + } + + protected boolean isAirplaneModeOff() { + return Settings.System.getInt( + mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 0; + } + + /** + * Notify the country change. + */ + private void notifyIfCountryChanged(final Country country, final Country detectedCountry) { + if (detectedCountry != null && mListener != null + && (country == null || !country.equals(detectedCountry))) { + Slog.d(TAG, + "The country was changed from " + country != null ? country.getCountryIso() : + country + " to " + detectedCountry.getCountryIso()); + notifyListener(detectedCountry); + } + } + + /** + * Schedule the next location refresh. We will do nothing if the scheduled task exists. + */ + private synchronized void scheduleLocationRefresh() { + if (mLocationRefreshTimer != null) return; + mLocationRefreshTimer = new Timer(); + mLocationRefreshTimer.schedule(new TimerTask() { + @Override + public void run() { + mLocationRefreshTimer = null; + detectCountry(false, true); + } + }, LOCATION_REFRESH_INTERVAL); + } + + /** + * Cancel the scheduled refresh task if it exists + */ + private synchronized void cancelLocationRefresh() { + if (mLocationRefreshTimer != null) { + mLocationRefreshTimer.cancel(); + mLocationRefreshTimer = null; + } + } + + protected synchronized void addPhoneStateListener() { + if (mPhoneStateListener == null && mPhoneType == TelephonyManager.PHONE_TYPE_GSM) { + mLastState = null; + mPhoneStateListener = new PhoneStateListener() { + @Override + public void onServiceStateChanged(ServiceState serviceState) { + // TODO: Find out how often we will be notified, if this + // method is called too + // many times, let's consider querying the network. + Slog.d(TAG, "onServiceStateChanged"); + // We only care the state change + if (mLastState == null || mLastState.getState() != serviceState.getState()) { + detectCountry(true, true); + mLastState = new ServiceState(serviceState); + } + } + }; + mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); + } + } + + protected synchronized void removePhoneStateListener() { + if (mPhoneStateListener != null) { + mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); + mPhoneStateListener = null; + } + } + + protected boolean isGeoCoderImplemented() { + return Geocoder.isPresent(); + } +} diff --git a/services/java/com/android/server/location/CountryDetectorBase.java b/services/java/com/android/server/location/CountryDetectorBase.java new file mode 100644 index 0000000..8326ef9 --- /dev/null +++ b/services/java/com/android/server/location/CountryDetectorBase.java @@ -0,0 +1,72 @@ +/* + * 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.location; + +import android.content.Context; +import android.location.Country; +import android.location.CountryListener; +import android.os.Handler; + +/** + * This class defines the methods need to be implemented by the country + * detector. + * <p> + * Calling {@link #detectCountry} to start detecting the country. The country + * could be returned immediately if it is available. + * + * @hide + */ +public abstract class CountryDetectorBase { + protected final Handler mHandler; + protected final Context mContext; + protected CountryListener mListener; + protected Country mDetectedCountry; + + public CountryDetectorBase(Context ctx) { + mContext = ctx; + mHandler = new Handler(); + } + + /** + * Start detecting the country that the user is in. + * + * @return the country if it is available immediately, otherwise null should + * be returned. + */ + public abstract Country detectCountry(); + + /** + * Register a listener to receive the notification when the country is detected or changed. + * <p> + * The previous listener will be replaced if it exists. + */ + public void setCountryListener(CountryListener listener) { + mListener = listener; + } + + /** + * Stop detecting the country. The detector should release all system services and be ready to + * be freed + */ + public abstract void stop(); + + protected void notifyListener(Country country) { + if (mListener != null) { + mListener.onCountryDetected(country); + } + } +} diff --git a/services/java/com/android/server/location/LocationBasedCountryDetector.java b/services/java/com/android/server/location/LocationBasedCountryDetector.java new file mode 100755 index 0000000..139f05d --- /dev/null +++ b/services/java/com/android/server/location/LocationBasedCountryDetector.java @@ -0,0 +1,235 @@ +/* + * 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.location; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import android.content.Context; +import android.location.Address; +import android.location.Country; +import android.location.Geocoder; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.util.Slog; + +/** + * This class detects which country the user currently is in through the enabled + * location providers and the GeoCoder + * <p> + * Use {@link #detectCountry} to start querying. If the location can not be + * resolved within the given time, the last known location will be used to get + * the user country through the GeoCoder. The IllegalStateException will be + * thrown if there is a ongoing query. + * <p> + * The current query can be stopped by {@link #stop()} + * + * @hide + */ +public class LocationBasedCountryDetector extends CountryDetectorBase { + private final static String TAG = "LocationBasedCountryDetector"; + private final static long QUERY_LOCATION_TIMEOUT = 1000 * 60 * 5; // 5 mins + + /** + * Used for canceling location query + */ + protected Timer mTimer; + + /** + * The thread to query the country from the GeoCoder. + */ + protected Thread mQueryThread; + protected List<LocationListener> mLocationListeners; + + private LocationManager mLocationManager; + private List<String> mEnabledProviders; + + public LocationBasedCountryDetector(Context ctx) { + super(ctx); + mLocationManager = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE); + } + + /** + * @return the ISO 3166-1 two letters country code from the location + */ + protected String getCountryFromLocation(Location location) { + String country = null; + Geocoder geoCoder = new Geocoder(mContext); + try { + List<Address> addresses = geoCoder.getFromLocation( + location.getLatitude(), location.getLongitude(), 1); + if (addresses != null && addresses.size() > 0) { + country = addresses.get(0).getCountryCode(); + } + } catch (IOException e) { + Slog.w(TAG, "Exception occurs when getting country from location"); + } + return country; + } + + /** + * Register the listeners with the location providers + */ + protected void registerEnabledProviders(List<LocationListener> listeners) { + int total = listeners.size(); + for (int i = 0; i< total; i++) { + mLocationManager.requestLocationUpdates( + mEnabledProviders.get(i), 0, 0, listeners.get(i)); + } + } + + /** + * Unregister the listeners with the location providers + */ + protected void unregisterProviders(List<LocationListener> listeners) { + for (LocationListener listener : listeners) { + mLocationManager.removeUpdates(listener); + } + } + + /** + * @return the last known location from all providers + */ + protected Location getLastKnownLocation() { + List<String> providers = mLocationManager.getAllProviders(); + Location bestLocation = null; + for (String provider : providers) { + Location lastKnownLocation = mLocationManager.getLastKnownLocation(provider); + if (lastKnownLocation != null) { + if (bestLocation == null || bestLocation.getTime() < lastKnownLocation.getTime()) { + bestLocation = lastKnownLocation; + } + } + } + return bestLocation; + } + + /** + * @return the timeout for querying the location. + */ + protected long getQueryLocationTimeout() { + return QUERY_LOCATION_TIMEOUT; + } + + /** + * @return the total number of enabled location providers + */ + protected int getTotalEnabledProviders() { + if (mEnabledProviders == null) { + mEnabledProviders = mLocationManager.getProviders(true); + } + return mEnabledProviders.size(); + } + + /** + * Start detecting the country. + * <p> + * Queries the location from all location providers, then starts a thread to query the + * country from GeoCoder. + */ + @Override + public synchronized Country detectCountry() { + if (mLocationListeners != null) { + throw new IllegalStateException(); + } + // Request the location from all enabled providers. + int totalProviders = getTotalEnabledProviders(); + if (totalProviders > 0) { + mLocationListeners = new ArrayList<LocationListener>(totalProviders); + for (int i = 0; i < totalProviders; i++) { + LocationListener listener = new LocationListener () { + public void onLocationChanged(Location location) { + if (location != null) { + LocationBasedCountryDetector.this.stop(); + queryCountryCode(location); + } + } + public void onProviderDisabled(String provider) { + } + public void onProviderEnabled(String provider) { + } + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; + mLocationListeners.add(listener); + } + registerEnabledProviders(mLocationListeners); + mTimer = new Timer(); + mTimer.schedule(new TimerTask() { + @Override + public void run() { + mTimer = null; + LocationBasedCountryDetector.this.stop(); + // Looks like no provider could provide the location, let's try the last + // known location. + queryCountryCode(getLastKnownLocation()); + } + }, getQueryLocationTimeout()); + } else { + // There is no provider enabled. + queryCountryCode(getLastKnownLocation()); + } + return mDetectedCountry; + } + + /** + * Stop the current query without notifying the listener. + */ + @Override + public synchronized void stop() { + if (mLocationListeners != null) { + unregisterProviders(mLocationListeners); + mLocationListeners = null; + } + if (mTimer != null) { + mTimer.cancel(); + mTimer = null; + } + } + + /** + * Start a new thread to query the country from Geocoder. + */ + private synchronized void queryCountryCode(final Location location) { + if (location == null) { + notifyListener(null); + return; + } + if (mQueryThread != null) return; + mQueryThread = new Thread(new Runnable() { + public void run() { + String countryIso = null; + if (location != null) { + countryIso = getCountryFromLocation(location); + } + if (countryIso != null) { + mDetectedCountry = new Country(countryIso, Country.COUNTRY_SOURCE_LOCATION); + } else { + mDetectedCountry = null; + } + notifyListener(mDetectedCountry); + mQueryThread = null; + } + }); + mQueryThread.start(); + } +} |