diff options
author | Jean-Baptiste Queru <jbq@google.com> | 2009-11-12 18:45:53 -0800 |
---|---|---|
committer | Jean-Baptiste Queru <jbq@google.com> | 2009-11-13 13:53:39 -0800 |
commit | 9db3d07b9620b4269ab33f78604a36327e536ce1 (patch) | |
tree | 41e294f34b9695187af098cd42167489fb0c8fb0 /services/java/com/android/server | |
parent | 6c63ee4fc4acae4bbbbd2a49e0a68206221f0de0 (diff) | |
download | frameworks_base-9db3d07b9620b4269ab33f78604a36327e536ce1.zip frameworks_base-9db3d07b9620b4269ab33f78604a36327e536ce1.tar.gz frameworks_base-9db3d07b9620b4269ab33f78604a36327e536ce1.tar.bz2 |
eclair snapshot
Diffstat (limited to 'services/java/com/android/server')
44 files changed, 9700 insertions, 2845 deletions
diff --git a/services/java/com/android/server/AccessibilityManagerService.java b/services/java/com/android/server/AccessibilityManagerService.java index 55007ba..f67a7ae 100644 --- a/services/java/com/android/server/AccessibilityManagerService.java +++ b/services/java/com/android/server/AccessibilityManagerService.java @@ -25,6 +25,7 @@ import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceConnection; import android.accessibilityservice.IEventListener; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -615,6 +616,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mId = sIdCounter++; mComponentName = componentName; mIntent = new Intent().setComponent(mComponentName); + mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, + com.android.internal.R.string.accessibility_binding_label); + mIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( + mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0)); } /** diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index 5439f8b..6bf7102 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -51,6 +51,7 @@ 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; @@ -105,6 +106,7 @@ class AppWidgetService extends IAppWidgetService.Stub } Context mContext; + Locale mLocale; PackageManager mPackageManager; AlarmManager mAlarmManager; ArrayList<Provider> mInstalledProviders = new ArrayList<Provider>(); @@ -131,6 +133,11 @@ class AppWidgetService extends IAppWidgetService.Stub mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); + // Register for configuration changes so we can update the names + // of the widgets when the locale changes. + mContext.registerReceiver(mBroadcastReceiver, + new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED), null, null); + // Register for broadcasts about package install, etc., so we can // update the provider list. IntentFilter filter = new IntentFilter(); @@ -473,8 +480,10 @@ class AppWidgetService extends IAppWidgetService.Stub public void stopListening(int hostId) { synchronized (mAppWidgetIds) { Host host = lookupHostLocked(getCallingUid(), hostId); - host.callbacks = null; - pruneHostLocked(host); + if (host != null) { + host.callbacks = null; + pruneHostLocked(host); + } } } @@ -814,7 +823,10 @@ class AppWidgetService extends IAppWidgetService.Stub temp.delete(); } - writeStateToFileLocked(temp); + if (!writeStateToFileLocked(temp)) { + Log.w(TAG, "Failed to persist new settings"); + return; + } //noinspection ResultOfMethodCallIgnored real.delete(); @@ -822,7 +834,7 @@ class AppWidgetService extends IAppWidgetService.Stub temp.renameTo(real); } - void writeStateToFileLocked(File file) { + boolean writeStateToFileLocked(File file) { FileOutputStream stream = null; int N; @@ -875,6 +887,7 @@ class AppWidgetService extends IAppWidgetService.Stub out.endDocument(); stream.close(); + return true; } catch (IOException e) { try { if (stream != null) { @@ -887,6 +900,7 @@ class AppWidgetService extends IAppWidgetService.Stub //noinspection ResultOfMethodCallIgnored file.delete(); } + return false; } } @@ -1039,6 +1053,22 @@ class AppWidgetService extends IAppWidgetService.Stub //Log.d(TAG, "received " + action); if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { sendInitialBroadcasts(); + } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { + Locale revised = Locale.getDefault(); + if (revised == null || mLocale == null || + !(revised.equals(mLocale))) { + mLocale = revised; + + synchronized (mAppWidgetIds) { + int N = mInstalledProviders.size(); + for (int i=N-1; i>=0; i--) { + Provider p = mInstalledProviders.get(i); + String pkgName = p.info.provider.getPackageName(); + updateProvidersForPackageLocked(pkgName); + } + saveStateLocked(); + } + } } else { Uri uri = intent.getData(); if (uri == null) { diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index 6e28515..c3b591e 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -46,6 +46,8 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; +import android.os.SystemClock; +import android.util.EventLog; import android.util.Log; import android.util.SparseArray; @@ -54,6 +56,7 @@ import android.backup.IRestoreObserver; import android.backup.IRestoreSession; import android.backup.RestoreSet; +import com.android.internal.backup.BackupConstants; import com.android.internal.backup.LocalTransport; import com.android.internal.backup.IBackupTransport; @@ -63,6 +66,7 @@ import com.android.server.PackageManagerBackupAgent.Metadata; import java.io.EOFException; import java.io.File; import java.io.FileDescriptor; +import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.RandomAccessFile; @@ -72,24 +76,47 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Random; class BackupManagerService extends IBackupManager.Stub { private static final String TAG = "BackupManagerService"; - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; // How often we perform a backup pass. Privileged external callers can // trigger an immediate pass. private static final long BACKUP_INTERVAL = AlarmManager.INTERVAL_HOUR; + // Random variation in backup scheduling time to avoid server load spikes + private static final int FUZZ_MILLIS = 5 * 60 * 1000; + // The amount of time between the initial provisioning of the device and // the first backup pass. private static final long FIRST_BACKUP_INTERVAL = 12 * AlarmManager.INTERVAL_HOUR; - private static final String RUN_BACKUP_ACTION = "_backup_run_"; + private static final String RUN_BACKUP_ACTION = "android.backup.intent.RUN"; + private static final String RUN_INITIALIZE_ACTION = "android.backup.intent.INIT"; + private static final String RUN_CLEAR_ACTION = "android.backup.intent.CLEAR"; private static final int MSG_RUN_BACKUP = 1; private static final int MSG_RUN_FULL_BACKUP = 2; private static final int MSG_RUN_RESTORE = 3; private static final int MSG_RUN_CLEAR = 4; + private static final int MSG_RUN_INITIALIZE = 5; + + // Event tags -- see system/core/logcat/event-log-tags + private static final int BACKUP_DATA_CHANGED_EVENT = 2820; + private static final int BACKUP_START_EVENT = 2821; + private static final int BACKUP_TRANSPORT_FAILURE_EVENT = 2822; + private static final int BACKUP_AGENT_FAILURE_EVENT = 2823; + private static final int BACKUP_PACKAGE_EVENT = 2824; + private static final int BACKUP_SUCCESS_EVENT = 2825; + private static final int BACKUP_RESET_EVENT = 2826; + private static final int BACKUP_INITIALIZE_EVENT = 2827; + + private static final int RESTORE_START_EVENT = 2830; + private static final int RESTORE_TRANSPORT_FAILURE_EVENT = 2831; + private static final int RESTORE_AGENT_FAILURE_EVENT = 2832; + private static final int RESTORE_PACKAGE_EVENT = 2833; + private static final int RESTORE_SUCCESS_EVENT = 2834; // Timeout interval for deciding that a bind or clear-data has taken too long static final long TIMEOUT_INTERVAL = 10 * 1000; @@ -100,18 +127,17 @@ class BackupManagerService extends IBackupManager.Stub { private PowerManager mPowerManager; private AlarmManager mAlarmManager; - private boolean mEnabled; // access to this is synchronized on 'this' - private boolean mProvisioned; - private PowerManager.WakeLock mWakelock; - private final BackupHandler mBackupHandler = new BackupHandler(); - private PendingIntent mRunBackupIntent; - private BroadcastReceiver mRunBackupReceiver; - private IntentFilter mRunBackupFilter; + boolean mEnabled; // access to this is synchronized on 'this' + boolean mProvisioned; + PowerManager.WakeLock mWakelock; + final BackupHandler mBackupHandler = new BackupHandler(); + PendingIntent mRunBackupIntent, mRunInitIntent; + BroadcastReceiver mRunBackupReceiver, mRunInitReceiver; // map UIDs to the set of backup client services within that UID's app set - private final SparseArray<HashSet<ApplicationInfo>> mBackupParticipants + final SparseArray<HashSet<ApplicationInfo>> mBackupParticipants = new SparseArray<HashSet<ApplicationInfo>>(); // set of backup services that have pending changes - private class BackupRequest { + class BackupRequest { public ApplicationInfo appInfo; public boolean fullBackup; @@ -125,35 +151,38 @@ class BackupManagerService extends IBackupManager.Stub { } } // Backups that we haven't started yet. - private HashMap<ApplicationInfo,BackupRequest> mPendingBackups + HashMap<ApplicationInfo,BackupRequest> mPendingBackups = new HashMap<ApplicationInfo,BackupRequest>(); // Pseudoname that we use for the Package Manager metadata "package" - private static final String PACKAGE_MANAGER_SENTINEL = "@pm@"; + static final String PACKAGE_MANAGER_SENTINEL = "@pm@"; // locking around the pending-backup management - private final Object mQueueLock = new Object(); + final Object mQueueLock = new Object(); // The thread performing the sequence of queued backups binds to each app's agent // in succession. Bind notifications are asynchronously delivered through the // Activity Manager; use this lock object to signal when a requested binding has // completed. - private final Object mAgentConnectLock = new Object(); - private IBackupAgent mConnectedAgent; - private volatile boolean mConnecting; + final Object mAgentConnectLock = new Object(); + IBackupAgent mConnectedAgent; + volatile boolean mConnecting; + volatile boolean mBackupOrRestoreInProgress = false; + volatile long mLastBackupPass; + volatile long mNextBackupPass; - // A similar synchronicity mechanism around clearing apps' data for restore - private final Object mClearDataLock = new Object(); - private volatile boolean mClearingData; + // A similar synchronization mechanism around clearing apps' data for restore + final Object mClearDataLock = new Object(); + volatile boolean mClearingData; // Transport bookkeeping - private final HashMap<String,IBackupTransport> mTransports + final HashMap<String,IBackupTransport> mTransports = new HashMap<String,IBackupTransport>(); - private String mCurrentTransport; - private IBackupTransport mLocalTransport, mGoogleTransport; - private RestoreSession mActiveRestoreSession; + String mCurrentTransport; + IBackupTransport mLocalTransport, mGoogleTransport; + RestoreSession mActiveRestoreSession; - private class RestoreParams { + class RestoreParams { public IBackupTransport transport; public IRestoreObserver observer; public long token; @@ -165,7 +194,7 @@ class BackupManagerService extends IBackupManager.Stub { } } - private class ClearParams { + class ClearParams { public IBackupTransport transport; public PackageInfo packageInfo; @@ -176,11 +205,19 @@ class BackupManagerService extends IBackupManager.Stub { } // Where we keep our journal files and other bookkeeping - private File mBaseStateDir; - private File mDataDir; - private File mJournalDir; - private File mJournal; - private RandomAccessFile mJournalStream; + File mBaseStateDir; + File mDataDir; + File mJournalDir; + File mJournal; + + // Keep a log of all the apps we've ever backed up + private File mEverStored; + HashSet<String> mEverStoredApps = new HashSet<String>(); + + // Persistently track the need to do a full init + static final String INIT_SENTINEL_FILE_NAME = "_need_init_"; + HashSet<String> mPendingInits = new HashSet<String>(); // transport names + volatile boolean mInitInProgress = false; public BackupManagerService(Context context) { mContext = context; @@ -193,27 +230,39 @@ class BackupManagerService extends IBackupManager.Stub { // Set up our bookkeeping boolean areEnabled = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.BACKUP_ENABLED, 0) != 0; - // !!! TODO: mProvisioned needs to default to 0, not 1. mProvisioned = Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.BACKUP_PROVISIONED, 1) != 0; + Settings.Secure.BACKUP_PROVISIONED, 0) != 0; mBaseStateDir = new File(Environment.getDataDirectory(), "backup"); mDataDir = Environment.getDownloadCacheDirectory(); + // Alarm receivers for scheduled backups & initialization operations mRunBackupReceiver = new RunBackupReceiver(); - mRunBackupFilter = new IntentFilter(); - mRunBackupFilter.addAction(RUN_BACKUP_ACTION); - context.registerReceiver(mRunBackupReceiver, mRunBackupFilter); + IntentFilter filter = new IntentFilter(); + filter.addAction(RUN_BACKUP_ACTION); + context.registerReceiver(mRunBackupReceiver, filter, + android.Manifest.permission.BACKUP, null); + + mRunInitReceiver = new RunInitializeReceiver(); + filter = new IntentFilter(); + filter.addAction(RUN_INITIALIZE_ACTION); + context.registerReceiver(mRunInitReceiver, filter, + android.Manifest.permission.BACKUP, null); Intent backupIntent = new Intent(RUN_BACKUP_ACTION); - // !!! TODO: restrict delivery to our receiver; the naive setClass() doesn't seem to work - //backupIntent.setClass(context, mRunBackupReceiver.getClass()); backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mRunBackupIntent = PendingIntent.getBroadcast(context, MSG_RUN_BACKUP, backupIntent, 0); + Intent initIntent = new Intent(RUN_INITIALIZE_ACTION); + backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mRunInitIntent = PendingIntent.getBroadcast(context, MSG_RUN_INITIALIZE, initIntent, 0); + // Set up the backup-request journaling mJournalDir = new File(mBaseStateDir, "pending"); mJournalDir.mkdirs(); // creates mBaseStateDir along the way - makeJournalLocked(); // okay because no other threads are running yet + mJournal = null; // will be created on first use + + // Set up the various sorts of package tracking we do + initPackageTracking(); // Build our mapping of uid to backup client services. This implicitly // schedules a backup pass on the Package Manager metadata the first @@ -249,14 +298,6 @@ class BackupManagerService extends IBackupManager.Stub { // leftover journal files into the pending backup set parseLeftoverJournals(); - // Register for broadcasts about package install, etc., so we can - // update the provider list. - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_PACKAGE_ADDED); - filter.addAction(Intent.ACTION_PACKAGE_REMOVED); - filter.addDataScheme("package"); - mContext.registerReceiver(mBroadcastReceiver, filter); - // Power management mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "backup"); @@ -267,55 +308,206 @@ class BackupManagerService extends IBackupManager.Stub { private class RunBackupReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { if (RUN_BACKUP_ACTION.equals(intent.getAction())) { - if (DEBUG) Log.v(TAG, "Running a backup pass"); + synchronized (mQueueLock) { + if (mPendingInits.size() > 0) { + // If there are pending init operations, we process those + // and then settle into the usual periodic backup schedule. + if (DEBUG) Log.v(TAG, "Init pending at scheduled backup"); + try { + mAlarmManager.cancel(mRunInitIntent); + mRunInitIntent.send(); + } catch (PendingIntent.CanceledException ce) { + Log.e(TAG, "Run init intent cancelled"); + // can't really do more than bail here + } + } else { + // Don't run backups now if we're disabled, not yet + // fully set up, in the middle of a backup already, + // or racing with an initialize pass. + if (mEnabled && mProvisioned + && !mBackupOrRestoreInProgress && !mInitInProgress) { + if (DEBUG) Log.v(TAG, "Running a backup pass"); + mBackupOrRestoreInProgress = true; + + // Acquire the wakelock and pass it to the backup thread. it will + // be released once backup concludes. + mWakelock.acquire(); + + Message msg = mBackupHandler.obtainMessage(MSG_RUN_BACKUP); + mBackupHandler.sendMessage(msg); + } else { + Log.w(TAG, "Backup pass but e=" + mEnabled + " p=" + mProvisioned + + " b=" + mBackupOrRestoreInProgress + " i=" + mInitInProgress); + } + } + } + } + } + } + private class RunInitializeReceiver extends BroadcastReceiver { + public void onReceive(Context context, Intent intent) { + if (RUN_INITIALIZE_ACTION.equals(intent.getAction())) { synchronized (mQueueLock) { - // acquire a wakelock and pass it to the backup thread. it will - // be released once backup concludes. + if (DEBUG) Log.v(TAG, "Running a device init"); + mInitInProgress = true; + + // Acquire the wakelock and pass it to the init thread. it will + // be released once init concludes. mWakelock.acquire(); - Message msg = mBackupHandler.obtainMessage(MSG_RUN_BACKUP); + Message msg = mBackupHandler.obtainMessage(MSG_RUN_INITIALIZE); mBackupHandler.sendMessage(msg); } } } } - private void makeJournalLocked() { - try { - mJournal = File.createTempFile("journal", null, mJournalDir); - mJournalStream = new RandomAccessFile(mJournal, "rwd"); - } catch (IOException e) { - Log.e(TAG, "Unable to write backup journals"); - mJournal = null; - mJournalStream = null; + private void initPackageTracking() { + if (DEBUG) Log.v(TAG, "Initializing package tracking"); + + // Keep a log of what apps we've ever backed up. Because we might have + // rebooted in the middle of an operation that was removing something from + // this log, we sanity-check its contents here and reconstruct it. + mEverStored = new File(mBaseStateDir, "processed"); + File tempProcessedFile = new File(mBaseStateDir, "processed.new"); + + // If we were in the middle of removing something from the ever-backed-up + // file, there might be a transient "processed.new" file still present. + // Ignore it -- we'll validate "processed" against the current package set. + if (tempProcessedFile.exists()) { + tempProcessedFile.delete(); } + + // If there are previous contents, parse them out then start a new + // file to continue the recordkeeping. + if (mEverStored.exists()) { + RandomAccessFile temp = null; + RandomAccessFile in = null; + + try { + temp = new RandomAccessFile(tempProcessedFile, "rws"); + in = new RandomAccessFile(mEverStored, "r"); + + while (true) { + PackageInfo info; + String pkg = in.readUTF(); + try { + info = mPackageManager.getPackageInfo(pkg, 0); + mEverStoredApps.add(pkg); + temp.writeUTF(pkg); + if (DEBUG) Log.v(TAG, " + " + pkg); + } catch (NameNotFoundException e) { + // nope, this package was uninstalled; don't include it + if (DEBUG) Log.v(TAG, " - " + pkg); + } + } + } catch (EOFException e) { + // Once we've rewritten the backup history log, atomically replace the + // old one with the new one then reopen the file for continuing use. + if (!tempProcessedFile.renameTo(mEverStored)) { + Log.e(TAG, "Error renaming " + tempProcessedFile + " to " + mEverStored); + } + } catch (IOException e) { + Log.e(TAG, "Error in processed file", e); + } finally { + try { if (temp != null) temp.close(); } catch (IOException e) {} + try { if (in != null) in.close(); } catch (IOException e) {} + } + } + + // Register for broadcasts about package install, etc., so we can + // update the provider list. + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addDataScheme("package"); + mContext.registerReceiver(mBroadcastReceiver, filter); } private void parseLeftoverJournals() { - if (mJournal != null) { - File[] allJournals = mJournalDir.listFiles(); - for (File f : allJournals) { - if (f.compareTo(mJournal) != 0) { - // This isn't the current journal, so it must be a leftover. Read - // out the package names mentioned there and schedule them for - // backup. - try { - Log.i(TAG, "Found stale backup journal, scheduling:"); - RandomAccessFile in = new RandomAccessFile(f, "r"); - while (true) { - String packageName = in.readUTF(); - Log.i(TAG, " + " + packageName); - dataChanged(packageName); - } - } catch (EOFException e) { - // no more data; we're done - } catch (Exception e) { - // can't read it or other error; just skip it - } finally { - // close/delete the file - f.delete(); + for (File f : mJournalDir.listFiles()) { + if (mJournal == null || f.compareTo(mJournal) != 0) { + // This isn't the current journal, so it must be a leftover. Read + // out the package names mentioned there and schedule them for + // backup. + RandomAccessFile in = null; + try { + Log.i(TAG, "Found stale backup journal, scheduling:"); + in = new RandomAccessFile(f, "r"); + while (true) { + String packageName = in.readUTF(); + Log.i(TAG, " + " + packageName); + dataChanged(packageName); } + } catch (EOFException e) { + // no more data; we're done + } catch (Exception e) { + Log.e(TAG, "Can't read " + f, e); + } finally { + // close/delete the file + try { if (in != null) in.close(); } catch (IOException e) {} + f.delete(); + } + } + } + } + + // Maintain persistent state around whether need to do an initialize operation. + // Must be called with the queue lock held. + void recordInitPendingLocked(boolean isPending, String transportName) { + if (DEBUG) Log.i(TAG, "recordInitPendingLocked: " + isPending + + " on transport " + transportName); + try { + IBackupTransport transport = getTransport(transportName); + String transportDirName = transport.transportDirName(); + File stateDir = new File(mBaseStateDir, transportDirName); + File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME); + + if (isPending) { + // We need an init before we can proceed with sending backup data. + // Record that with an entry in our set of pending inits, as well as + // journaling it via creation of a sentinel file. + mPendingInits.add(transportName); + try { + (new FileOutputStream(initPendingFile)).close(); + } catch (IOException ioe) { + // Something is badly wrong with our permissions; just try to move on + } + } else { + // No more initialization needed; wipe the journal and reset our state. + initPendingFile.delete(); + mPendingInits.remove(transportName); + } + } catch (RemoteException e) { + // can't happen; the transport is local + } + } + + // Reset all of our bookkeeping, in response to having been told that + // the backend data has been wiped [due to idle expiry, for example], + // so we must re-upload all saved settings. + void resetBackupState(File stateFileDir) { + synchronized (mQueueLock) { + // Wipe the "what we've ever backed up" tracking + mEverStoredApps.clear(); + mEverStored.delete(); + + // Remove all the state files + for (File sf : stateFileDir.listFiles()) { + // ... but don't touch the needs-init sentinel + if (!sf.getName().equals(INIT_SENTINEL_FILE_NAME)) { + sf.delete(); + } + } + + // Enqueue a new backup of every participant + int N = mBackupParticipants.size(); + for (int i=0; i<N; i++) { + int uid = mBackupParticipants.keyAt(i); + HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i); + for (ApplicationInfo app: participants) { + dataChanged(app.packageName); } } } @@ -327,6 +519,29 @@ class BackupManagerService extends IBackupManager.Stub { if (DEBUG) Log.v(TAG, "Registering transport " + name + " = " + transport); mTransports.put(name, transport); } + + // If the init sentinel file exists, we need to be sure to perform the init + // as soon as practical. We also create the state directory at registration + // time to ensure it's present from the outset. + try { + String transportName = transport.transportDirName(); + File stateDir = new File(mBaseStateDir, transportName); + stateDir.mkdirs(); + + File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME); + if (initSentinel.exists()) { + synchronized (mQueueLock) { + mPendingInits.add(transportName); + + // TODO: pick a better starting time than now + 1 minute + long delay = 1000 * 60; // one minute, in milliseconds + mAlarmManager.set(AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + delay, mRunInitIntent); + } + } + } catch (RemoteException e) { + // can't happen, the transport is local + } } // ----- Track installation/removal of packages ----- @@ -392,16 +607,21 @@ class BackupManagerService extends IBackupManager.Stub { switch (msg.what) { case MSG_RUN_BACKUP: { + mLastBackupPass = System.currentTimeMillis(); + mNextBackupPass = mLastBackupPass + BACKUP_INTERVAL; + IBackupTransport transport = getTransport(mCurrentTransport); if (transport == null) { Log.v(TAG, "Backup requested but no transport available"); + synchronized (mQueueLock) { + mBackupOrRestoreInProgress = false; + } mWakelock.release(); break; } // snapshot the pending-backup set and work on that ArrayList<BackupRequest> queue = new ArrayList<BackupRequest>(); - File oldJournal = mJournal; synchronized (mQueueLock) { // Do we have any work to do? if (mPendingBackups.size() > 0) { @@ -412,14 +632,8 @@ class BackupManagerService extends IBackupManager.Stub { mPendingBackups.clear(); // Start a new backup-queue journal file too - if (mJournalStream != null) { - try { - mJournalStream.close(); - } catch (IOException e) { - // don't need to do anything - } - makeJournalLocked(); - } + File oldJournal = mJournal; + mJournal = null; // At this point, we have started a new journal file, and the old // file identity is being passed to the backup processing thread. @@ -429,6 +643,9 @@ class BackupManagerService extends IBackupManager.Stub { (new PerformBackupThread(transport, queue, oldJournal)).start(); } else { Log.v(TAG, "Backup requested but nothing pending"); + synchronized (mQueueLock) { + mBackupOrRestoreInProgress = false; + } mWakelock.release(); } } @@ -453,6 +670,20 @@ class BackupManagerService extends IBackupManager.Stub { (new PerformClearThread(params.transport, params.packageInfo)).start(); break; } + + case MSG_RUN_INITIALIZE: + { + HashSet<String> queue; + + // Snapshot the pending-init queue and work on that + synchronized (mQueueLock) { + queue = new HashSet<String>(mPendingInits); + mPendingInits.clear(); + } + + (new PerformInitializeThread(queue)).start(); + break; + } } } } @@ -472,7 +703,12 @@ class BackupManagerService extends IBackupManager.Stub { Log.v(TAG, "Adding " + targetPkgs.size() + " backup participants:"); for (PackageInfo p : targetPkgs) { Log.v(TAG, " " + p + " agent=" + p.applicationInfo.backupAgentName - + " uid=" + p.applicationInfo.uid); + + " uid=" + p.applicationInfo.uid + + " killAfterRestore=" + + (((p.applicationInfo.flags & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0) ? "true" : "false") + + " restoreNeedsApplication=" + + (((p.applicationInfo.flags & ApplicationInfo.FLAG_RESTORE_NEEDS_APPLICATION) != 0) ? "true" : "false") + ); } } @@ -485,6 +721,13 @@ class BackupManagerService extends IBackupManager.Stub { mBackupParticipants.put(uid, set); } set.add(pkg.applicationInfo); + + // If we've never seen this app before, schedule a backup for it + if (!mEverStoredApps.contains(pkg.packageName)) { + if (DEBUG) Log.i(TAG, "New app " + pkg.packageName + + " never backed up; scheduling"); + dataChanged(pkg.packageName); + } } } } @@ -528,6 +771,7 @@ class BackupManagerService extends IBackupManager.Stub { for (ApplicationInfo entry: set) { if (entry.packageName.equals(pkg.packageName)) { set.remove(entry); + removeEverBackedUp(pkg.packageName); break; } } @@ -540,15 +784,28 @@ class BackupManagerService extends IBackupManager.Stub { } // Returns the set of all applications that define an android:backupAgent attribute - private List<PackageInfo> allAgentPackages() { + List<PackageInfo> allAgentPackages() { // !!! TODO: cache this and regenerate only when necessary int flags = PackageManager.GET_SIGNATURES; List<PackageInfo> packages = mPackageManager.getInstalledPackages(flags); int N = packages.size(); for (int a = N-1; a >= 0; a--) { - ApplicationInfo app = packages.get(a).applicationInfo; - if (((app.flags&ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) - || app.backupAgentName == null) { + PackageInfo pkg = packages.get(a); + try { + ApplicationInfo app = pkg.applicationInfo; + if (((app.flags&ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) + || app.backupAgentName == null + || (mPackageManager.checkPermission(android.Manifest.permission.BACKUP_DATA, + pkg.packageName) != PackageManager.PERMISSION_GRANTED)) { + packages.remove(a); + } + else { + // we will need the shared library path, so look that up and store it here + app = mPackageManager.getApplicationInfo(pkg.packageName, + PackageManager.GET_SHARED_LIBRARY_FILES); + pkg.applicationInfo.sharedLibraryFiles = app.sharedLibraryFiles; + } + } catch (NameNotFoundException e) { packages.remove(a); } } @@ -570,6 +827,64 @@ class BackupManagerService extends IBackupManager.Stub { addPackageParticipantsLockedInner(packageName, allApps); } + // Called from the backup thread: record that the given app has been successfully + // backed up at least once + void logBackupComplete(String packageName) { + if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) return; + + synchronized (mEverStoredApps) { + if (!mEverStoredApps.add(packageName)) return; + + RandomAccessFile out = null; + try { + out = new RandomAccessFile(mEverStored, "rws"); + out.seek(out.length()); + out.writeUTF(packageName); + } catch (IOException e) { + Log.e(TAG, "Can't log backup of " + packageName + " to " + mEverStored); + } finally { + try { if (out != null) out.close(); } catch (IOException e) {} + } + } + } + + // Remove our awareness of having ever backed up the given package + void removeEverBackedUp(String packageName) { + if (DEBUG) Log.v(TAG, "Removing backed-up knowledge of " + packageName + ", new set:"); + + synchronized (mEverStoredApps) { + // Rewrite the file and rename to overwrite. If we reboot in the middle, + // we'll recognize on initialization time that the package no longer + // exists and fix it up then. + File tempKnownFile = new File(mBaseStateDir, "processed.new"); + RandomAccessFile known = null; + try { + known = new RandomAccessFile(tempKnownFile, "rws"); + mEverStoredApps.remove(packageName); + for (String s : mEverStoredApps) { + known.writeUTF(s); + if (DEBUG) Log.v(TAG, " " + s); + } + known.close(); + known = null; + if (!tempKnownFile.renameTo(mEverStored)) { + throw new IOException("Can't rename " + tempKnownFile + " to " + mEverStored); + } + } catch (IOException e) { + // Bad: we couldn't create the new copy. For safety's sake we + // abandon the whole process and remove all what's-backed-up + // state entirely, meaning we'll force a backup pass for every + // participant on the next boot or [re]install. + Log.w(TAG, "Error rewriting " + mEverStored, e); + mEverStoredApps.clear(); + tempKnownFile.delete(); + mEverStored.delete(); + } finally { + try { if (known != null) known.close(); } catch (IOException e) {} + } + } + } + // Return the given transport private IBackupTransport getTransport(String transportName) { synchronized (mTransports) { @@ -637,6 +952,14 @@ class BackupManagerService extends IBackupManager.Stub { synchronized(mClearDataLock) { mClearingData = true; + /* This is causing some critical processes to be killed during setup. + Temporarily revert this change until we find a better solution. + try { + mActivityManager.clearApplicationUserData(packageName, observer); + } catch (RemoteException e) { + // can't happen because the activity manager is in this process + } + */ mPackageManager.clearApplicationUserData(packageName, observer); // only wait 10 seconds for the clear data to happen @@ -653,8 +976,7 @@ class BackupManagerService extends IBackupManager.Stub { } class ClearDataObserver extends IPackageDataObserver.Stub { - public void onRemoveCompleted(String packageName, boolean succeeded) - throws android.os.RemoteException { + public void onRemoveCompleted(String packageName, boolean succeeded) { synchronized(mClearDataLock) { mClearingData = false; mClearDataLock.notifyAll(); @@ -682,54 +1004,129 @@ class BackupManagerService extends IBackupManager.Stub { } catch (RemoteException e) { // can't happen; the transport is local } - mStateDir.mkdirs(); } @Override public void run() { + int status = BackupConstants.TRANSPORT_OK; + long startRealtime = SystemClock.elapsedRealtime(); if (DEBUG) Log.v(TAG, "Beginning backup of " + mQueue.size() + " targets"); // Backups run at background priority Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - // The package manager doesn't have a proper <application> etc, but since - // it's running here in the system process we can just set up its agent - // directly and use a synthetic BackupRequest. We always run this pass - // because it's cheap and this way we guarantee that we don't get out of - // step even if we're selecting among various transports at run time. - PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent( - mPackageManager, allAgentPackages()); - BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false); - pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL; - processOneBackup(pmRequest, - IBackupAgent.Stub.asInterface(pmAgent.onBind()), - mTransport); - - // Now run all the backups in our queue - doQueuedBackups(mTransport); - - // Finally, tear down the transport try { - if (!mTransport.finishBackup()) { - // STOPSHIP TODO: handle errors - Log.e(TAG, "Backup failure in finishBackup()"); + EventLog.writeEvent(BACKUP_START_EVENT, mTransport.transportDirName()); + + // If we haven't stored package manager metadata yet, we must init the transport. + File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL); + if (status == BackupConstants.TRANSPORT_OK && pmState.length() <= 0) { + Log.i(TAG, "Initializing (wiping) backup state and transport storage"); + resetBackupState(mStateDir); // Just to make sure. + status = mTransport.initializeDevice(); + if (status == BackupConstants.TRANSPORT_OK) { + EventLog.writeEvent(BACKUP_INITIALIZE_EVENT); + } else { + EventLog.writeEvent(BACKUP_TRANSPORT_FAILURE_EVENT, "(initialize)"); + Log.e(TAG, "Transport error in initializeDevice()"); + } } - } catch (RemoteException e) { - Log.e(TAG, "Error in finishBackup()", e); - } - if (!mJournal.delete()) { - Log.e(TAG, "Unable to remove backup journal file " + mJournal.getAbsolutePath()); - } + // The package manager doesn't have a proper <application> etc, but since + // it's running here in the system process we can just set up its agent + // directly and use a synthetic BackupRequest. We always run this pass + // because it's cheap and this way we guarantee that we don't get out of + // step even if we're selecting among various transports at run time. + if (status == BackupConstants.TRANSPORT_OK) { + PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent( + mPackageManager, allAgentPackages()); + BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false); + pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL; + status = processOneBackup(pmRequest, + IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport); + } + + if (status == BackupConstants.TRANSPORT_OK) { + // Now run all the backups in our queue + status = doQueuedBackups(mTransport); + } - // Only once we're entirely finished do we release the wakelock - mWakelock.release(); + if (status == BackupConstants.TRANSPORT_OK) { + // Tell the transport to finish everything it has buffered + status = mTransport.finishBackup(); + if (status == BackupConstants.TRANSPORT_OK) { + int millis = (int) (SystemClock.elapsedRealtime() - startRealtime); + EventLog.writeEvent(BACKUP_SUCCESS_EVENT, mQueue.size(), millis); + } else { + EventLog.writeEvent(BACKUP_TRANSPORT_FAILURE_EVENT, "(finish)"); + Log.e(TAG, "Transport error in finishBackup()"); + } + } + + if (status == BackupConstants.TRANSPORT_NOT_INITIALIZED) { + // The backend reports that our dataset has been wiped. We need to + // reset all of our bookkeeping and instead run a new backup pass for + // everything. This must come after mBackupOrRestoreInProgress is cleared. + EventLog.writeEvent(BACKUP_RESET_EVENT, mTransport.transportDirName()); + resetBackupState(mStateDir); + } + } catch (Exception e) { + Log.e(TAG, "Error in backup thread", e); + status = BackupConstants.TRANSPORT_ERROR; + } finally { + // If things went wrong, we need to re-stage the apps we had expected + // to be backing up in this pass. This journals the package names in + // the current active pending-backup file, not in the we are holding + // here in mJournal. + if (status != BackupConstants.TRANSPORT_OK) { + Log.w(TAG, "Backup pass unsuccessful, restaging"); + for (BackupRequest req : mQueue) { + dataChanged(req.appInfo.packageName); + } + + // We also want to reset the backup schedule based on whatever + // the transport suggests by way of retry/backoff time. + try { + startBackupAlarmsLocked(mTransport.requestBackupTime()); + } catch (RemoteException e) { /* cannot happen */ } + } + + // Either backup was successful, in which case we of course do not need + // this pass's journal any more; or it failed, in which case we just + // re-enqueued all of these packages in the current active journal. + // Either way, we no longer need this pass's journal. + if (mJournal != null && !mJournal.delete()) { + Log.e(TAG, "Unable to remove backup journal file " + mJournal); + } + + // Only once we're entirely finished do we indicate our completion + // and release the wakelock + synchronized (mQueueLock) { + mBackupOrRestoreInProgress = false; + } + + if (status == BackupConstants.TRANSPORT_NOT_INITIALIZED) { + // This must come after mBackupOrRestoreInProgress is cleared. + backupNow(); + } + + mWakelock.release(); + } } - private void doQueuedBackups(IBackupTransport transport) { + private int doQueuedBackups(IBackupTransport transport) { for (BackupRequest request : mQueue) { Log.d(TAG, "starting agent for backup of " + request); + // Don't run backup, even if requested, if the target app does not have + // the requisite permission + if (mPackageManager.checkPermission(android.Manifest.permission.BACKUP_DATA, + request.appInfo.packageName) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "Skipping backup of unprivileged package " + + request.appInfo.packageName); + continue; + } + IBackupAgent agent = null; int mode = (request.fullBackup) ? IApplicationThread.BACKUP_MODE_FULL @@ -737,31 +1134,40 @@ class BackupManagerService extends IBackupManager.Stub { try { agent = bindToAgentSynchronous(request.appInfo, mode); if (agent != null) { - processOneBackup(request, agent, transport); + int result = processOneBackup(request, agent, transport); + if (result != BackupConstants.TRANSPORT_OK) return result; } - - // unbind even on timeout, just in case - mActivityManager.unbindBackupAgent(request.appInfo); } catch (SecurityException ex) { // Try for the next one. Log.d(TAG, "error in bind/backup", ex); - } catch (RemoteException e) { - Log.v(TAG, "bind/backup threw"); - e.printStackTrace(); + } finally { + try { // unbind even on timeout, just in case + mActivityManager.unbindBackupAgent(request.appInfo); + } catch (RemoteException e) {} } - } + + return BackupConstants.TRANSPORT_OK; } - void processOneBackup(BackupRequest request, IBackupAgent agent, IBackupTransport transport) { + private int processOneBackup(BackupRequest request, IBackupAgent agent, + IBackupTransport transport) { final String packageName = request.appInfo.packageName; - Log.d(TAG, "processOneBackup doBackup() on " + packageName); + if (DEBUG) Log.d(TAG, "processOneBackup doBackup() on " + packageName); + + File savedStateName = new File(mStateDir, packageName); + File backupDataName = new File(mDataDir, packageName + ".data"); + File newStateName = new File(mStateDir, packageName + ".new"); + ParcelFileDescriptor savedState = null; + ParcelFileDescriptor backupData = null; + ParcelFileDescriptor newState = null; + + PackageInfo packInfo; try { // Look up the package info & signatures. This is first so that if it // throws an exception, there's no file setup yet that would need to // be unraveled. - PackageInfo packInfo; if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) { // The metadata 'package' is synthetic packInfo = new PackageInfo(); @@ -771,78 +1177,103 @@ class BackupManagerService extends IBackupManager.Stub { PackageManager.GET_SIGNATURES); } - // !!! TODO: get the state file dir from the transport - File savedStateName = new File(mStateDir, packageName); - File backupDataName = new File(mDataDir, packageName + ".data"); - File newStateName = new File(mStateDir, packageName + ".new"); - // In a full backup, we pass a null ParcelFileDescriptor as // the saved-state "file" - ParcelFileDescriptor savedState = (request.fullBackup) ? null - : ParcelFileDescriptor.open(savedStateName, + if (!request.fullBackup) { + savedState = ParcelFileDescriptor.open(savedStateName, ParcelFileDescriptor.MODE_READ_ONLY | - ParcelFileDescriptor.MODE_CREATE); + ParcelFileDescriptor.MODE_CREATE); // Make an empty file if necessary + } - backupDataName.delete(); - ParcelFileDescriptor backupData = - ParcelFileDescriptor.open(backupDataName, - ParcelFileDescriptor.MODE_READ_WRITE | - ParcelFileDescriptor.MODE_CREATE); + backupData = ParcelFileDescriptor.open(backupDataName, + ParcelFileDescriptor.MODE_READ_WRITE | + ParcelFileDescriptor.MODE_CREATE | + ParcelFileDescriptor.MODE_TRUNCATE); - newStateName.delete(); - ParcelFileDescriptor newState = - ParcelFileDescriptor.open(newStateName, - ParcelFileDescriptor.MODE_READ_WRITE | - ParcelFileDescriptor.MODE_CREATE); + newState = ParcelFileDescriptor.open(newStateName, + ParcelFileDescriptor.MODE_READ_WRITE | + ParcelFileDescriptor.MODE_CREATE | + ParcelFileDescriptor.MODE_TRUNCATE); // Run the target's backup pass - boolean success = false; - try { - agent.doBackup(savedState, backupData, newState); - success = true; - } finally { - if (savedState != null) { - savedState.close(); + agent.doBackup(savedState, backupData, newState); + logBackupComplete(packageName); + if (DEBUG) Log.v(TAG, "doBackup() success"); + } catch (Exception e) { + Log.e(TAG, "Error backing up " + packageName, e); + EventLog.writeEvent(BACKUP_AGENT_FAILURE_EVENT, packageName, e.toString()); + backupDataName.delete(); + newStateName.delete(); + return BackupConstants.TRANSPORT_ERROR; + } finally { + try { if (savedState != null) savedState.close(); } catch (IOException e) {} + try { if (backupData != null) backupData.close(); } catch (IOException e) {} + try { if (newState != null) newState.close(); } catch (IOException e) {} + savedState = backupData = newState = null; + } + + // Now propagate the newly-backed-up data to the transport + int result = BackupConstants.TRANSPORT_OK; + try { + int size = (int) backupDataName.length(); + if (size > 0) { + if (result == BackupConstants.TRANSPORT_OK) { + backupData = ParcelFileDescriptor.open(backupDataName, + ParcelFileDescriptor.MODE_READ_ONLY); + result = transport.performBackup(packInfo, backupData); } - backupData.close(); - newState.close(); - } - - // Now propagate the newly-backed-up data to the transport - if (success) { - if (DEBUG) Log.v(TAG, "doBackup() success"); - if (backupDataName.length() > 0) { - backupData = - ParcelFileDescriptor.open(backupDataName, - ParcelFileDescriptor.MODE_READ_ONLY); - if (!transport.performBackup(packInfo, backupData)) { - // STOPSHIP TODO: handle errors - Log.e(TAG, "Backup failure in performBackup()"); - } - } else { - if (DEBUG) { - Log.i(TAG, "no backup data written; not calling transport"); - } + + // TODO - We call finishBackup() for each application backed up, because + // we need to know now whether it succeeded or failed. Instead, we should + // hold off on finishBackup() until the end, which implies holding off on + // renaming *all* the output state files (see below) until that happens. + + if (result == BackupConstants.TRANSPORT_OK) { + result = transport.finishBackup(); } + } else { + if (DEBUG) Log.i(TAG, "no backup data written; not calling transport"); + } - // After successful transport, delete the now-stale data - // and juggle the files so that next time we supply the agent - // with the new state file it just created. + // After successful transport, delete the now-stale data + // and juggle the files so that next time we supply the agent + // with the new state file it just created. + if (result == BackupConstants.TRANSPORT_OK) { backupDataName.delete(); newStateName.renameTo(savedStateName); + EventLog.writeEvent(BACKUP_PACKAGE_EVENT, packageName, size); + } else { + EventLog.writeEvent(BACKUP_TRANSPORT_FAILURE_EVENT, packageName); } } catch (Exception e) { - Log.e(TAG, "Error backing up " + packageName, e); + Log.e(TAG, "Transport error backing up " + packageName, e); + EventLog.writeEvent(BACKUP_TRANSPORT_FAILURE_EVENT, packageName); + result = BackupConstants.TRANSPORT_ERROR; + } finally { + try { if (backupData != null) backupData.close(); } catch (IOException e) {} } + + return result; } } // ----- Restore handling ----- - private boolean signaturesMatch(Signature[] storedSigs, Signature[] deviceSigs) { + private boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) { + // If the target resides on the system partition, we allow it to restore + // data from the like-named package in a restore set even if the signatures + // do not match. (Unlike general applications, those flashed to the system + // partition will be signed with the device's platform certificate, so on + // different phones the same system app will have different signatures.) + if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + if (DEBUG) Log.v(TAG, "System app " + target.packageName + " - skipping sig check"); + return true; + } + // Allow unsigned apps, but not signed on one device and unsigned on the other // !!! TODO: is this the right policy? + Signature[] deviceSigs = target.signatures; if (DEBUG) Log.v(TAG, "signaturesMatch(): stored=" + storedSigs + " device=" + deviceSigs); if ((storedSigs == null || storedSigs.length == 0) @@ -878,7 +1309,6 @@ class BackupManagerService extends IBackupManager.Stub { private IBackupTransport mTransport; private IRestoreObserver mObserver; private long mToken; - private RestoreSet mImage; private File mStateDir; class RestoreRequest { @@ -903,19 +1333,19 @@ class BackupManagerService extends IBackupManager.Stub { } catch (RemoteException e) { // can't happen; the transport is local } - mStateDir.mkdirs(); } @Override public void run() { + long startRealtime = SystemClock.elapsedRealtime(); if (DEBUG) Log.v(TAG, "Beginning restore process mTransport=" + mTransport - + " mObserver=" + mObserver + " mToken=" + mToken); + + " mObserver=" + mObserver + " mToken=" + Long.toHexString(mToken)); /** * Restore sequence: * * 1. get the restore set description for our identity * 2. for each app in the restore set: - * 3.a. if it's restorable on this device, add it to the restore queue + * 2.a. if it's restorable on this device, add it to the restore queue * 3. for each app in the restore queue: * 3.a. clear the app data * 3.b. get the restore data for the app from the transport @@ -923,25 +1353,18 @@ class BackupManagerService extends IBackupManager.Stub { * 3.d. agent.doRestore() with the data from the server * 3.e. unbind the agent [and kill the app?] * 4. shut down the transport + * + * On errors, we try our best to recover and move on to the next + * application, but if necessary we abort the whole operation -- + * the user is waiting, after al. */ int error = -1; // assume error // build the set of apps to restore try { - RestoreSet[] images = mTransport.getAvailableRestoreSets(); - if (images == null) { - // STOPSHIP TODO: Handle the failure somehow? - Log.e(TAG, "Error getting restore sets"); - return; - } - - if (images.length == 0) { - Log.i(TAG, "No restore sets available"); - return; - } - - mImage = images[0]; + // TODO: Log this before getAvailableRestoreSets, somehow + EventLog.writeEvent(RESTORE_START_EVENT, mTransport.transportDirName(), mToken); // Get the list of all packages which have backup enabled. // (Include the Package Manager metadata pseudo-package first.) @@ -965,23 +1388,28 @@ class BackupManagerService extends IBackupManager.Stub { } } - if (!mTransport.startRestore(mToken, restorePackages.toArray(new PackageInfo[0]))) { - // STOPSHIP TODO: Handle the failure somehow? + if (mTransport.startRestore(mToken, restorePackages.toArray(new PackageInfo[0])) != + BackupConstants.TRANSPORT_OK) { Log.e(TAG, "Error starting restore operation"); + EventLog.writeEvent(RESTORE_TRANSPORT_FAILURE_EVENT); return; } String packageName = mTransport.nextRestorePackage(); if (packageName == null) { - // STOPSHIP TODO: Handle the failure somehow? Log.e(TAG, "Error getting first restore package"); + EventLog.writeEvent(RESTORE_TRANSPORT_FAILURE_EVENT); return; } else if (packageName.equals("")) { Log.i(TAG, "No restore data available"); + int millis = (int) (SystemClock.elapsedRealtime() - startRealtime); + EventLog.writeEvent(RESTORE_SUCCESS_EVENT, 0, millis); return; } else if (!packageName.equals(PACKAGE_MANAGER_SENTINEL)) { Log.e(TAG, "Expected restore data for \"" + PACKAGE_MANAGER_SENTINEL + "\", found only \"" + packageName + "\""); + EventLog.writeEvent(RESTORE_AGENT_FAILURE_EVENT, PACKAGE_MANAGER_SENTINEL, + "Package manager data missing"); return; } @@ -994,23 +1422,25 @@ class BackupManagerService extends IBackupManager.Stub { // signature/version verification etc, so we simply do not proceed with // the restore operation. if (!pmAgent.hasMetadata()) { - Log.i(TAG, "No restore metadata available, so not restoring settings"); + Log.e(TAG, "No restore metadata available, so not restoring settings"); + EventLog.writeEvent(RESTORE_AGENT_FAILURE_EVENT, PACKAGE_MANAGER_SENTINEL, + "Package manager restore metadata missing"); return; } int count = 0; for (;;) { packageName = mTransport.nextRestorePackage(); + if (packageName == null) { - // STOPSHIP TODO: Handle the failure somehow? Log.e(TAG, "Error getting next restore package"); + EventLog.writeEvent(RESTORE_TRANSPORT_FAILURE_EVENT); return; } else if (packageName.equals("")) { break; } if (mObserver != null) { - ++count; try { mObserver.onUpdate(count); } catch (RemoteException e) { @@ -1022,21 +1452,34 @@ class BackupManagerService extends IBackupManager.Stub { Metadata metaInfo = pmAgent.getRestoredMetadata(packageName); if (metaInfo == null) { Log.e(TAG, "Missing metadata for " + packageName); + EventLog.writeEvent(RESTORE_AGENT_FAILURE_EVENT, packageName, + "Package metadata missing"); + continue; + } + + PackageInfo packageInfo; + try { + int flags = PackageManager.GET_SIGNATURES; + packageInfo = mPackageManager.getPackageInfo(packageName, flags); + } catch (NameNotFoundException e) { + Log.e(TAG, "Invalid package restoring data", e); + EventLog.writeEvent(RESTORE_AGENT_FAILURE_EVENT, packageName, + "Package missing on device"); continue; } - int flags = PackageManager.GET_SIGNATURES; - PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, flags); if (metaInfo.versionCode > packageInfo.versionCode) { - Log.w(TAG, "Package " + packageName - + " restore version [" + metaInfo.versionCode - + "] is too new for installed version [" - + packageInfo.versionCode + "]"); + String message = "Version " + metaInfo.versionCode + + " > installed version " + packageInfo.versionCode; + Log.w(TAG, "Package " + packageName + ": " + message); + EventLog.writeEvent(RESTORE_AGENT_FAILURE_EVENT, packageName, message); continue; } - if (!signaturesMatch(metaInfo.signatures, packageInfo.signatures)) { + if (!signaturesMatch(metaInfo.signatures, packageInfo)) { Log.w(TAG, "Signature mismatch restoring " + packageName); + EventLog.writeEvent(RESTORE_AGENT_FAILURE_EVENT, packageName, + "Signature mismatch"); continue; } @@ -1045,41 +1488,66 @@ class BackupManagerService extends IBackupManager.Stub { + "] is compatible with installed version [" + packageInfo.versionCode + "]"); - // Now perform the actual restore + // Now perform the actual restore: first clear the app's data + // if appropriate clearApplicationDataSynchronous(packageName); + + // Then set up and bind the agent (with a restricted Application object + // unless the application says otherwise) + boolean useRealApp = (packageInfo.applicationInfo.flags + & ApplicationInfo.FLAG_RESTORE_NEEDS_APPLICATION) != 0; + if (DEBUG && useRealApp) { + Log.v(TAG, "agent requires real Application subclass for restore"); + } IBackupAgent agent = bindToAgentSynchronous( packageInfo.applicationInfo, - IApplicationThread.BACKUP_MODE_RESTORE); + (useRealApp ? IApplicationThread.BACKUP_MODE_INCREMENTAL + : IApplicationThread.BACKUP_MODE_RESTORE)); if (agent == null) { Log.w(TAG, "Can't find backup agent for " + packageName); + EventLog.writeEvent(RESTORE_AGENT_FAILURE_EVENT, packageName, + "Restore agent missing"); continue; } + // And then finally run the restore on this agent try { processOneRestore(packageInfo, metaInfo.versionCode, agent); + ++count; } finally { - // unbind even on timeout or failure, just in case + // unbind and tidy up even on timeout or failure, just in case mActivityManager.unbindBackupAgent(packageInfo.applicationInfo); + + // The agent was probably running with a stub Application object, + // which isn't a valid run mode for the main app logic. Shut + // down the app so that next time it's launched, it gets the + // usual full initialization. + if ((packageInfo.applicationInfo.flags + & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0) { + if (DEBUG) Log.d(TAG, "Restore complete, killing host process of " + + packageInfo.applicationInfo.processName); + mActivityManager.killApplicationProcess( + packageInfo.applicationInfo.processName, + packageInfo.applicationInfo.uid); + } } } // if we get this far, report success to the observer error = 0; - } catch (NameNotFoundException e) { - // STOPSHIP TODO: Handle the failure somehow? - Log.e(TAG, "Invalid paackage restoring data", e); - } catch (RemoteException e) { - // STOPSHIP TODO: Handle the failure somehow? - Log.e(TAG, "Error restoring data", e); + int millis = (int) (SystemClock.elapsedRealtime() - startRealtime); + EventLog.writeEvent(RESTORE_SUCCESS_EVENT, count, millis); + } catch (Exception e) { + Log.e(TAG, "Error in restore thread", e); } finally { + if (DEBUG) Log.d(TAG, "finishing restore mObserver=" + mObserver); + try { mTransport.finishRestore(); } catch (RemoteException e) { Log.e(TAG, "Error finishing restore", e); } - Log.d(TAG, "finishing restore mObserver=" + mObserver); - if (mObserver != null) { try { mObserver.restoreFinished(error); @@ -1089,6 +1557,9 @@ class BackupManagerService extends IBackupManager.Stub { } // done; we can finally release the wakelock + synchronized (mQueueLock) { + mBackupOrRestoreInProgress = false; + } mWakelock.release(); } } @@ -1098,51 +1569,78 @@ class BackupManagerService extends IBackupManager.Stub { // !!! TODO: actually run the restore through mTransport final String packageName = app.packageName; - Log.d(TAG, "processOneRestore packageName=" + packageName); + if (DEBUG) Log.d(TAG, "processOneRestore packageName=" + packageName); + + // Don't restore to unprivileged packages + if (mPackageManager.checkPermission(android.Manifest.permission.BACKUP_DATA, + packageName) != PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Skipping restore of unprivileged package " + packageName); + } // !!! TODO: get the dirs from the transport File backupDataName = new File(mDataDir, packageName + ".restore"); - backupDataName.delete(); + File newStateName = new File(mStateDir, packageName + ".new"); + File savedStateName = new File(mStateDir, packageName); + + ParcelFileDescriptor backupData = null; + ParcelFileDescriptor newState = null; + try { - ParcelFileDescriptor backupData = - ParcelFileDescriptor.open(backupDataName, + // Run the transport's restore pass + backupData = ParcelFileDescriptor.open(backupDataName, ParcelFileDescriptor.MODE_READ_WRITE | - ParcelFileDescriptor.MODE_CREATE); + ParcelFileDescriptor.MODE_CREATE | + ParcelFileDescriptor.MODE_TRUNCATE); - // Run the transport's restore pass - // Run the target's backup pass - try { - if (!mTransport.getRestoreData(backupData)) { - // STOPSHIP TODO: Handle this error somehow? - Log.e(TAG, "Error getting restore data for " + packageName); - return; - } - } finally { - backupData.close(); + if (mTransport.getRestoreData(backupData) != BackupConstants.TRANSPORT_OK) { + Log.e(TAG, "Error getting restore data for " + packageName); + EventLog.writeEvent(RESTORE_TRANSPORT_FAILURE_EVENT); + return; } // Okay, we have the data. Now have the agent do the restore. - File newStateName = new File(mStateDir, packageName + ".new"); - ParcelFileDescriptor newState = - ParcelFileDescriptor.open(newStateName, - ParcelFileDescriptor.MODE_READ_WRITE | - ParcelFileDescriptor.MODE_CREATE); - + backupData.close(); backupData = ParcelFileDescriptor.open(backupDataName, ParcelFileDescriptor.MODE_READ_ONLY); - try { - agent.doRestore(backupData, appVersionCode, newState); - } finally { - newState.close(); - backupData.close(); - } + newState = ParcelFileDescriptor.open(newStateName, + ParcelFileDescriptor.MODE_READ_WRITE | + ParcelFileDescriptor.MODE_CREATE | + ParcelFileDescriptor.MODE_TRUNCATE); + + agent.doRestore(backupData, appVersionCode, newState); // if everything went okay, remember the recorded state now - File savedStateName = new File(mStateDir, packageName); - newStateName.renameTo(savedStateName); + // + // !!! TODO: the restored data should be migrated on the server + // side into the current dataset. In that case the new state file + // we just created would reflect the data already extant in the + // backend, so there'd be nothing more to do. Until that happens, + // however, we need to make sure that we record the data to the + // current backend dataset. (Yes, this means shipping the data over + // the wire in both directions. That's bad, but consistency comes + // first, then efficiency.) Once we introduce server-side data + // migration to the newly-restored device's dataset, we will change + // the following from a discard of the newly-written state to the + // "correct" operation of renaming into the canonical state blob. + newStateName.delete(); // TODO: remove; see above comment + //newStateName.renameTo(savedStateName); // TODO: replace with this + + int size = (int) backupDataName.length(); + EventLog.writeEvent(RESTORE_PACKAGE_EVENT, packageName, size); } catch (Exception e) { Log.e(TAG, "Error restoring data for " + packageName, e); + EventLog.writeEvent(RESTORE_AGENT_FAILURE_EVENT, packageName, e.toString()); + + // If the agent fails restore, it might have put the app's data + // into an incoherent state. For consistency we wipe its data + // again in this case before propagating the exception + clearApplicationDataSynchronous(packageName); + } finally { + backupDataName.delete(); + try { if (backupData != null) backupData.close(); } catch (IOException e) {} + try { if (newState != null) newState.close(); } catch (IOException e) {} + backupData = newState = null; } } } @@ -1165,17 +1663,88 @@ class BackupManagerService extends IBackupManager.Stub { stateFile.delete(); // Tell the transport to remove all the persistent storage for the app + // TODO - need to handle failures mTransport.clearBackupData(mPackage); } catch (RemoteException e) { // can't happen; the transport is local } finally { try { + // TODO - need to handle failures mTransport.finishBackup(); } catch (RemoteException e) { // can't happen; the transport is local } // Last but not least, release the cpu + synchronized (mQueueLock) { + mBackupOrRestoreInProgress = false; + } + mWakelock.release(); + } + } + } + + class PerformInitializeThread extends Thread { + HashSet<String> mQueue; + + PerformInitializeThread(HashSet<String> transportNames) { + mQueue = transportNames; + } + + @Override + public void run() { + try { + for (String transportName : mQueue) { + IBackupTransport transport = getTransport(transportName); + if (transport == null) { + Log.e(TAG, "Requested init for " + transportName + " but not found"); + continue; + } + + Log.i(TAG, "Initializing (wiping) backup transport storage: " + transportName); + EventLog.writeEvent(BACKUP_START_EVENT, transport.transportDirName()); + long startRealtime = SystemClock.elapsedRealtime(); + int status = transport.initializeDevice(); + + if (status == BackupConstants.TRANSPORT_OK) { + status = transport.finishBackup(); + } + + // Okay, the wipe really happened. Clean up our local bookkeeping. + if (status == BackupConstants.TRANSPORT_OK) { + Log.i(TAG, "Device init successful"); + int millis = (int) (SystemClock.elapsedRealtime() - startRealtime); + EventLog.writeEvent(BACKUP_INITIALIZE_EVENT); + resetBackupState(new File(mBaseStateDir, transport.transportDirName())); + EventLog.writeEvent(BACKUP_SUCCESS_EVENT, 0, millis); + synchronized (mQueueLock) { + recordInitPendingLocked(false, transportName); + } + } else { + // If this didn't work, requeue this one and try again + // after a suitable interval + Log.e(TAG, "Transport error in initializeDevice()"); + EventLog.writeEvent(BACKUP_TRANSPORT_FAILURE_EVENT, "(initialize)"); + synchronized (mQueueLock) { + recordInitPendingLocked(true, transportName); + } + // do this via another alarm to make sure of the wakelock states + long delay = transport.requestBackupTime(); + if (DEBUG) Log.w(TAG, "init failed on " + + transportName + " resched in " + delay); + mAlarmManager.set(AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + delay, mRunInitIntent); + } + } + } catch (RemoteException e) { + // can't happen; the transports are local + } catch (Exception e) { + Log.e(TAG, "Unexpected error performing init", e); + } finally { + // Done; indicate that we're finished and release the wakelock + synchronized (mQueueLock) { + mInitInProgress = false; + } mWakelock.release(); } } @@ -1184,10 +1753,11 @@ class BackupManagerService extends IBackupManager.Stub { // ----- IBackupManager binder interface ----- - public void dataChanged(String packageName) throws RemoteException { + public void dataChanged(String packageName) { // Record that we need a backup pass for the caller. Since multiple callers // may share a uid, we need to note all candidates within that uid and schedule // a backup pass for each of them. + EventLog.writeEvent(BACKUP_DATA_CHANGED_EVENT, packageName); // If the caller does not hold the BACKUP permission, it can only request a // backup of its own data. @@ -1235,19 +1805,23 @@ class BackupManagerService extends IBackupManager.Stub { } } } else { - Log.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"); + Log.w(TAG, "dataChanged but no participant pkg='" + packageName + "'" + + " uid=" + Binder.getCallingUid()); } } private void writeToJournalLocked(String str) { - if (mJournalStream != null) { - try { - mJournalStream.writeUTF(str); - } catch (IOException e) { - Log.e(TAG, "Error writing to backup journal"); - mJournalStream = null; - mJournal = null; - } + RandomAccessFile out = null; + try { + if (mJournal == null) mJournal = File.createTempFile("journal", null, mJournalDir); + out = new RandomAccessFile(mJournal, "rws"); + out.seek(out.length()); + out.writeUTF(str); + } catch (IOException e) { + Log.e(TAG, "Can't write " + str + " to backup journal", e); + mJournal = null; + } finally { + try { if (out != null) out.close(); } catch (IOException e) {} } } @@ -1288,10 +1862,12 @@ class BackupManagerService extends IBackupManager.Stub { if (DEBUG) Log.v(TAG, "Found the app - running clear process"); // found it; fire off the clear request synchronized (mQueueLock) { + long oldId = Binder.clearCallingIdentity(); mWakelock.acquire(); Message msg = mBackupHandler.obtainMessage(MSG_RUN_CLEAR, new ClearParams(getTransport(mCurrentTransport), info)); mBackupHandler.sendMessage(msg); + Binder.restoreCallingIdentity(oldId); } break; } @@ -1300,13 +1876,16 @@ class BackupManagerService extends IBackupManager.Stub { // Run a backup pass immediately for any applications that have declared // that they have pending updates. - public void backupNow() throws RemoteException { - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "backupNow"); + public void backupNow() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "backupNow"); if (DEBUG) Log.v(TAG, "Scheduling immediate backup pass"); synchronized (mQueueLock) { + // Because the alarms we are using can jitter, and we want an *immediate* + // backup pass to happen, we restart the timer beginning with "next time," + // then manually fire the backup trigger intent ourselves. + startBackupAlarmsLocked(BACKUP_INTERVAL); try { - if (DEBUG) Log.v(TAG, "sending immediate backup broadcast"); mRunBackupIntent.send(); } catch (PendingIntent.CanceledException e) { // should never happen @@ -1320,6 +1899,8 @@ class BackupManagerService extends IBackupManager.Stub { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "setBackupEnabled"); + Log.i(TAG, "Backup enabled => " + enable); + boolean wasEnabled = mEnabled; synchronized (this) { Settings.Secure.putInt(mContext.getContentResolver(), @@ -1333,7 +1914,27 @@ class BackupManagerService extends IBackupManager.Stub { startBackupAlarmsLocked(BACKUP_INTERVAL); } else if (!enable) { // No longer enabled, so stop running backups + if (DEBUG) Log.i(TAG, "Opting out of backup"); + mAlarmManager.cancel(mRunBackupIntent); + + // This also constitutes an opt-out, so we wipe any data for + // this device from the backend. We start that process with + // an alarm in order to guarantee wakelock states. + if (wasEnabled && mProvisioned) { + // NOTE: we currently flush every registered transport, not just + // the currently-active one. + HashSet<String> allTransports; + synchronized (mTransports) { + allTransports = new HashSet<String>(mTransports.keySet()); + } + // build the set of transports for which we are posting an init + for (String transport : allTransports) { + recordInitPendingLocked(true, transport); + } + mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), + mRunInitIntent); + } } } } @@ -1363,20 +1964,27 @@ class BackupManagerService extends IBackupManager.Stub { } private void startBackupAlarmsLocked(long delayBeforeFirstBackup) { - long when = System.currentTimeMillis() + delayBeforeFirstBackup; - mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, when, - BACKUP_INTERVAL, mRunBackupIntent); + // We used to use setInexactRepeating(), but that may be linked to + // backups running at :00 more often than not, creating load spikes. + // Schedule at an exact time for now, and also add a bit of "fuzz". + + Random random = new Random(); + long when = System.currentTimeMillis() + delayBeforeFirstBackup + + random.nextInt(FUZZ_MILLIS); + mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, when, + BACKUP_INTERVAL + random.nextInt(FUZZ_MILLIS), mRunBackupIntent); + mNextBackupPass = when; } // Report whether the backup mechanism is currently enabled public boolean isBackupEnabled() { - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "isBackupEnabled"); + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "isBackupEnabled"); return mEnabled; // no need to synchronize just to read it } // Report the name of the currently active transport public String getCurrentTransport() { - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "getCurrentTransport"); Log.v(TAG, "... getCurrentTransport() returning " + mCurrentTransport); return mCurrentTransport; @@ -1405,7 +2013,7 @@ class BackupManagerService extends IBackupManager.Stub { // name is not one of the available transports, no action is taken and the method // returns null. public String selectBackupTransport(String transport) { - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "selectBackupTransport"); + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "selectBackupTransport"); synchronized (mTransports) { String prevTransport = null; @@ -1459,7 +2067,7 @@ class BackupManagerService extends IBackupManager.Stub { // Hand off a restore session public IRestoreSession beginRestoreSession(String transport) { - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "beginRestoreSession"); + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "beginRestoreSession"); synchronized(this) { if (mActiveRestoreSession != null) { @@ -1484,55 +2092,80 @@ class BackupManagerService extends IBackupManager.Stub { } // --- Binder interface --- - public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException { - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, + public synchronized RestoreSet[] getAvailableRestoreSets() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "getAvailableRestoreSets"); try { - synchronized(this) { - if (mRestoreSets == null) { + if (mRestoreTransport == null) { + Log.w(TAG, "Null transport getting restore sets"); + return null; + } + if (mRestoreSets == null) { // valid transport; do the one-time fetch mRestoreSets = mRestoreTransport.getAvailableRestoreSets(); + if (mRestoreSets == null) EventLog.writeEvent(RESTORE_TRANSPORT_FAILURE_EVENT); } return mRestoreSets; - } - } catch (RuntimeException e) { - Log.d(TAG, "getAvailableRestoreSets exception"); - e.printStackTrace(); - throw e; + } catch (Exception e) { + Log.e(TAG, "Error in getAvailableRestoreSets", e); + return null; } } - public int performRestore(long token, IRestoreObserver observer) - throws android.os.RemoteException { - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "performRestore"); + public synchronized int performRestore(long token, IRestoreObserver observer) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, + "performRestore"); + + if (DEBUG) Log.d(TAG, "performRestore token=" + Long.toHexString(token) + + " observer=" + observer); - Log.d(TAG, "performRestore token=" + token + " observer=" + observer); + if (mRestoreTransport == null || mRestoreSets == null) { + Log.e(TAG, "Ignoring performRestore() with no restore set"); + return -1; + } + + synchronized (mQueueLock) { + if (mBackupOrRestoreInProgress) { + Log.e(TAG, "Backup pass in progress, restore aborted"); + return -1; + } - if (mRestoreSets != null) { for (int i = 0; i < mRestoreSets.length; i++) { if (token == mRestoreSets[i].token) { + long oldId = Binder.clearCallingIdentity(); + // Suppress backups until the restore operation is finished + mBackupOrRestoreInProgress = true; mWakelock.acquire(); Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); msg.obj = new RestoreParams(mRestoreTransport, observer, token); mBackupHandler.sendMessage(msg); + Binder.restoreCallingIdentity(oldId); return 0; } } - } else { - if (DEBUG) Log.v(TAG, "No current restore set, not doing restore"); } + + Log.w(TAG, "Restore token " + Long.toHexString(token) + " not found"); return -1; } - public void endRestoreSession() throws android.os.RemoteException { - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, + public synchronized void endRestoreSession() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "endRestoreSession"); - Log.d(TAG, "endRestoreSession"); + if (DEBUG) Log.d(TAG, "endRestoreSession"); + + synchronized (this) { + try { + if (mRestoreTransport != null) mRestoreTransport.finishRestore(); + } catch (Exception e) { + Log.e(TAG, "Error in finishRestore", e); + } finally { + mRestoreTransport = null; + } + } - mRestoreTransport.finishRestore(); - mRestoreTransport = null; - synchronized(BackupManagerService.this) { + synchronized (BackupManagerService.this) { if (BackupManagerService.this.mActiveRestoreSession == this) { BackupManagerService.this.mActiveRestoreSession = null; } else { @@ -1546,32 +2179,55 @@ class BackupManagerService extends IBackupManager.Stub { @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { synchronized (mQueueLock) { - long oldId = Binder.clearCallingIdentity(); - pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled") - + " / " + (!mProvisioned ? "not " : "") + "provisioned"); + + " / " + (!mProvisioned ? "not " : "") + "provisioned / " + + (!mBackupOrRestoreInProgress ? "not " : "") + "in progress / " + + (this.mPendingInits.size() == 0 ? "not " : "") + "pending init / " + + (!mInitInProgress ? "not " : "") + "initializing"); + pw.println("Last backup pass: " + mLastBackupPass + + " (now = " + System.currentTimeMillis() + ')'); + pw.println(" next scheduled: " + mNextBackupPass); + pw.println("Available transports:"); for (String t : listAllTransports()) { - String pad = (t.equals(mCurrentTransport)) ? " * " : " "; - pw.println(pad + t); + pw.println((t.equals(mCurrentTransport) ? " * " : " ") + t); + try { + File dir = new File(mBaseStateDir, getTransport(t).transportDirName()); + for (File f : dir.listFiles()) { + pw.println(" " + f.getName() + " - " + f.length() + " state bytes"); + } + } catch (RemoteException e) { + Log.e(TAG, "Error in transportDirName()", e); + pw.println(" Error: " + e); + } } + + pw.println("Pending init: " + mPendingInits.size()); + for (String s : mPendingInits) { + pw.println(" " + s); + } + int N = mBackupParticipants.size(); - pw.println("Participants: " + N); + pw.println("Participants:"); for (int i=0; i<N; i++) { int uid = mBackupParticipants.keyAt(i); pw.print(" uid: "); pw.println(uid); HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i); for (ApplicationInfo app: participants) { - pw.println(" " + app.toString()); + pw.println(" " + app.packageName); } } - pw.println("Pending: " + mPendingBackups.size()); + + pw.println("Ever backed up: " + mEverStoredApps.size()); + for (String pkg : mEverStoredApps) { + pw.println(" " + pkg); + } + + pw.println("Pending backup: " + mPendingBackups.size()); for (BackupRequest req : mPendingBackups.values()) { pw.println(" " + req); } - - Binder.restoreCallingIdentity(oldId); } } } diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java index 90d8c9d..bb36936 100644 --- a/services/java/com/android/server/BatteryService.java +++ b/services/java/com/android/server/BatteryService.java @@ -26,7 +26,6 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.os.BatteryManager; import android.os.Binder; -import android.os.Debug; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -45,7 +44,6 @@ import java.io.IOException; import java.io.PrintWriter; - /** * <p>BatteryService monitors the charging status, and charge level of the device * battery. When these values change this service broadcasts the new values @@ -113,18 +111,27 @@ class BatteryService extends Binder { private int mLastBatteryVoltage; private int mLastBatteryTemperature; private boolean mLastBatteryLevelCritical; - + + private int mLowBatteryWarningLevel; + private int mLowBatteryCloseWarningLevel; + private int mPlugType; private int mLastPlugType = -1; // Extra state so we can detect first run private long mDischargeStartTime; private int mDischargeStartLevel; + private boolean mSentLowBatteryBroadcast = false; public BatteryService(Context context) { mContext = context; mBatteryStats = BatteryStatsService.getService(); + 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"); // set initial status @@ -171,6 +178,22 @@ class BatteryService extends Binder { return mBatteryLevel; } + void systemReady() { + // check our power situation now that it is safe to display the shutdown dialog. + shutdownIfNoPower(); + } + + private final void shutdownIfNoPower() { + // shut down gracefully if our battery is critically low and we are not powered. + // wait until the system has booted before attempting to display the shutdown dialog. + if (mBatteryLevel == 0 && !isPowered() && ActivityManagerNative.isSystemReady()) { + Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN); + intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } + } + private native void native_update(); private synchronized final void update() { @@ -178,7 +201,9 @@ class BatteryService extends Binder { boolean logOutlier = false; long dischargeDuration = 0; - + + shutdownIfNoPower(); + mBatteryLevelCritical = mBatteryLevel <= CRITICAL_BATTERY_LEVEL; if (mAcOnline) { mPlugType = BatteryManager.BATTERY_PLUGGED_AC; @@ -247,18 +272,49 @@ class BatteryService extends Binder { logOutlier = true; } + final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE; + final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE; + + /* The ACTION_BATTERY_LOW broadcast is sent in these situations: + * - is just un-plugged (previously was plugged) and battery level is + * less than or equal to WARNING, or + * - is not plugged and battery level falls to WARNING boundary + * (becomes <= mLowBatteryWarningLevel). + */ + final boolean sendBatteryLow = !plugged + && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN + && mBatteryLevel <= mLowBatteryWarningLevel + && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel); + + sendIntent(); + // Separate broadcast is sent for power connected / not connected // since the standard intent will not wake any applications and some // applications may want to have smart behavior based on this. + Intent statusIntent = new Intent(); + statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); if (mPlugType != 0 && mLastPlugType == 0) { - Intent intent = new Intent(Intent.ACTION_POWER_CONNECTED); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent); + statusIntent.setAction(Intent.ACTION_POWER_CONNECTED); + mContext.sendBroadcast(statusIntent); } else if (mPlugType == 0 && mLastPlugType != 0) { - Intent intent = new Intent(Intent.ACTION_POWER_DISCONNECTED); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent); + statusIntent.setAction(Intent.ACTION_POWER_DISCONNECTED); + mContext.sendBroadcast(statusIntent); + } + + if (sendBatteryLow) { + mSentLowBatteryBroadcast = true; + statusIntent.setAction(Intent.ACTION_BATTERY_LOW); + mContext.sendBroadcast(statusIntent); + } else if (mSentLowBatteryBroadcast && mLastBatteryLevel >= mLowBatteryCloseWarningLevel) { + mSentLowBatteryBroadcast = false; + statusIntent.setAction(Intent.ACTION_BATTERY_OKAY); + mContext.sendBroadcast(statusIntent); + } + + // This needs to be done after sendIntent() so that we get the lastest battery stats. + if (logOutlier && dischargeDuration != 0) { + logOutlier(dischargeDuration); } mLastBatteryStatus = mBatteryStatus; @@ -269,13 +325,6 @@ class BatteryService extends Binder { mLastBatteryVoltage = mBatteryVoltage; mLastBatteryTemperature = mBatteryTemperature; mLastBatteryLevelCritical = mBatteryLevelCritical; - - sendIntent(); - - // This needs to be done after sendIntent() so that we get the lastest battery stats. - if (logOutlier && dischargeDuration != 0) { - logOutlier(dischargeDuration); - } } } @@ -291,16 +340,16 @@ class BatteryService extends Binder { int icon = getIcon(mBatteryLevel); - intent.putExtra("status", mBatteryStatus); - intent.putExtra("health", mBatteryHealth); - intent.putExtra("present", mBatteryPresent); - intent.putExtra("level", mBatteryLevel); - intent.putExtra("scale", BATTERY_SCALE); - intent.putExtra("icon-small", icon); - intent.putExtra("plugged", mPlugType); - intent.putExtra("voltage", mBatteryVoltage); - intent.putExtra("temperature", mBatteryTemperature); - intent.putExtra("technology", mBatteryTechnology); + intent.putExtra(BatteryManager.EXTRA_STATUS, mBatteryStatus); + intent.putExtra(BatteryManager.EXTRA_HEALTH, mBatteryHealth); + intent.putExtra(BatteryManager.EXTRA_PRESENT, mBatteryPresent); + intent.putExtra(BatteryManager.EXTRA_LEVEL, mBatteryLevel); + intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE); + intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon); + intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType); + intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryVoltage); + intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryTemperature); + intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryTechnology); if (false) { Log.d(TAG, "updateBattery level:" + mBatteryLevel + diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 493bd09..78215b0 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -30,51 +30,136 @@ import android.net.NetworkStateTracker; import android.net.wifi.WifiStateTracker; import android.os.Binder; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.provider.Settings; +import android.text.TextUtils; import android.util.EventLog; import android.util.Log; +import com.android.internal.telephony.Phone; + import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; /** * @hide */ public class ConnectivityService extends IConnectivityManager.Stub { - private static final boolean DBG = false; + private static final boolean DBG = true; private static final String TAG = "ConnectivityService"; // Event log tags (must be in sync with event-log-tags) private static final int EVENTLOG_CONNECTIVITY_STATE_CHANGED = 50020; + // how long to wait before switching back to a radio's default network + private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000; + // system property that can override the above value + private static final String NETWORK_RESTORE_DELAY_PROP_NAME = + "android.telephony.apn-restore"; + /** * Sometimes we want to refer to the individual network state * trackers separately, and sometimes we just want to treat them * abstractly. */ private NetworkStateTracker mNetTrackers[]; - private WifiStateTracker mWifiStateTracker; - private MobileDataStateTracker mMobileDataStateTracker; + + /** + * A per Net list of the PID's that requested access to the net + * used both as a refcount and for per-PID DNS selection + */ + private List mNetRequestersPids[]; + private WifiWatchdogService mWifiWatchdogService; + // priority order of the nettrackers + // (excluding dynamically set mNetworkPreference) + // TODO - move mNetworkTypePreference into this + private int[] mPriorityList; + private Context mContext; private int mNetworkPreference; - private NetworkStateTracker mActiveNetwork; + private int mActiveDefaultNetwork = -1; private int mNumDnsEntries; - private static int sDnsChangeCounter; private boolean mTestMode; private static ConnectivityService sServiceInstance; + private Handler mHandler; + + // list of DeathRecipients used to make sure features are turned off when + // a process dies + private List mFeatureUsers; + + private boolean mSystemReady; + private ArrayList<Intent> mDeferredBroadcasts; + + private class NetworkAttributes { + /** + * Class for holding settings read from resources. + */ + public String mName; + public int mType; + public int mRadio; + public int mPriority; + public NetworkAttributes(String init) { + String fragments[] = init.split(","); + mName = fragments[0].toLowerCase(); + if (fragments[1].toLowerCase().equals("wifi")) { + mRadio = ConnectivityManager.TYPE_WIFI; + } else { + mRadio = ConnectivityManager.TYPE_MOBILE; + } + if (mName.equals("default")) { + mType = mRadio; + } else if (mName.equals("mms")) { + mType = ConnectivityManager.TYPE_MOBILE_MMS; + } else if (mName.equals("supl")) { + mType = ConnectivityManager.TYPE_MOBILE_SUPL; + } else if (mName.equals("dun")) { + mType = ConnectivityManager.TYPE_MOBILE_DUN; + } else if (mName.equals("hipri")) { + mType = ConnectivityManager.TYPE_MOBILE_HIPRI; + } + mPriority = Integer.parseInt(fragments[2]); + } + public boolean isDefault() { + return (mType == mRadio); + } + } + NetworkAttributes[] mNetAttributes; + + private class RadioAttributes { + public String mName; + public int mPriority; + public int mSimultaneity; + public int mType; + public RadioAttributes(String init) { + String fragments[] = init.split(","); + mName = fragments[0].toLowerCase(); + mPriority = Integer.parseInt(fragments[1]); + mSimultaneity = Integer.parseInt(fragments[2]); + if (mName.equals("wifi")) { + mType = ConnectivityManager.TYPE_WIFI; + } else { + mType = ConnectivityManager.TYPE_MOBILE; + } + } + } + RadioAttributes[] mRadioAttributes; + private static class ConnectivityThread extends Thread { private Context mContext; - + private ConnectivityThread(Context context) { super("ConnectivityThread"); mContext = context; @@ -89,11 +174,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { } Looper.loop(); } - + public static ConnectivityService getServiceInstance(Context context) { ConnectivityThread thread = new ConnectivityThread(context); thread.start(); - + synchronized (thread) { while (sServiceInstance == null) { try { @@ -101,27 +186,71 @@ public class ConnectivityService extends IConnectivityManager.Stub { thread.wait(); } catch (InterruptedException ignore) { Log.e(TAG, - "Unexpected InterruptedException while waiting for ConnectivityService thread"); + "Unexpected InterruptedException while waiting"+ + " for ConnectivityService thread"); } } } - + return sServiceInstance; } } - + public static ConnectivityService getInstance(Context context) { return ConnectivityThread.getServiceInstance(context); } - + private ConnectivityService(Context context) { if (DBG) Log.v(TAG, "ConnectivityService starting up"); mContext = context; - mNetTrackers = new NetworkStateTracker[2]; - Handler handler = new MyHandler(); - + mNetTrackers = new NetworkStateTracker[ + ConnectivityManager.MAX_NETWORK_TYPE+1]; + mHandler = new MyHandler(); + mNetworkPreference = getPersistedNetworkPreference(); - + + // Load device network attributes from resources + mNetAttributes = new NetworkAttributes[ + ConnectivityManager.MAX_NETWORK_TYPE+1]; + mRadioAttributes = new RadioAttributes[ + ConnectivityManager.MAX_RADIO_TYPE+1]; + String[] naStrings = context.getResources().getStringArray( + com.android.internal.R.array.networkAttributes); + // TODO - what if the setting has gaps/unknown types? + for (String a : naStrings) { + NetworkAttributes n = new NetworkAttributes(a); + mNetAttributes[n.mType] = n; + } + String[] raStrings = context.getResources().getStringArray( + com.android.internal.R.array.radioAttributes); + for (String a : raStrings) { + RadioAttributes r = new RadioAttributes(a); + mRadioAttributes[r.mType] = r; + } + + // high priority first + mPriorityList = new int[naStrings.length]; + { + int priority = 0; //lowest + int nextPos = naStrings.length-1; + while (nextPos>-1) { + for (int i = 0; i < mNetAttributes.length; i++) { + if(mNetAttributes[i].mPriority == priority) { + mPriorityList[nextPos--] = i; + } + } + priority++; + } + } + + mNetRequestersPids = + new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE+1]; + for (int i=0; i<=ConnectivityManager.MAX_NETWORK_TYPE; i++) { + mNetRequestersPids[i] = new ArrayList(); + } + + mFeatureUsers = new ArrayList(); + /* * Create the network state trackers for Wi-Fi and mobile * data. Maybe this could be done with a factory class, @@ -130,15 +259,36 @@ public class ConnectivityService extends IConnectivityManager.Stub { * to change very often. */ if (DBG) Log.v(TAG, "Starting Wifi Service."); - mWifiStateTracker = new WifiStateTracker(context, handler); - WifiService wifiService = new WifiService(context, mWifiStateTracker); + WifiStateTracker wst = new WifiStateTracker(context, mHandler); + WifiService wifiService = new WifiService(context, wst); ServiceManager.addService(Context.WIFI_SERVICE, wifiService); - mNetTrackers[ConnectivityManager.TYPE_WIFI] = mWifiStateTracker; + mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst; + + mNetTrackers[ConnectivityManager.TYPE_MOBILE] = + new MobileDataStateTracker(context, mHandler, + ConnectivityManager.TYPE_MOBILE, Phone.APN_TYPE_DEFAULT, + "MOBILE"); + + mNetTrackers[ConnectivityManager.TYPE_MOBILE_MMS] = + new MobileDataStateTracker(context, mHandler, + ConnectivityManager.TYPE_MOBILE_MMS, Phone.APN_TYPE_MMS, + "MOBILE_MMS"); + + mNetTrackers[ConnectivityManager.TYPE_MOBILE_SUPL] = + new MobileDataStateTracker(context, mHandler, + ConnectivityManager.TYPE_MOBILE_SUPL, Phone.APN_TYPE_SUPL, + "MOBILE_SUPL"); + + mNetTrackers[ConnectivityManager.TYPE_MOBILE_DUN] = + new MobileDataStateTracker(context, mHandler, + ConnectivityManager.TYPE_MOBILE_DUN, Phone.APN_TYPE_DUN, + "MOBILE_DUN"); + + mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI] = + new MobileDataStateTracker(context, mHandler, + ConnectivityManager.TYPE_MOBILE_HIPRI, Phone.APN_TYPE_HIPRI, + "MOBILE_HIPRI"); - mMobileDataStateTracker = new MobileDataStateTracker(context, handler); - mNetTrackers[ConnectivityManager.TYPE_MOBILE] = mMobileDataStateTracker; - - mActiveNetwork = null; mNumDnsEntries = 0; mTestMode = SystemProperties.get("cm.test.mode").equals("true") @@ -148,16 +298,17 @@ public class ConnectivityService extends IConnectivityManager.Stub { t.startMonitoring(); // Constructing this starts it too - mWifiWatchdogService = new WifiWatchdogService(context, mWifiStateTracker); + mWifiWatchdogService = new WifiWatchdogService(context, wst); } /** - * Sets the preferred network. + * Sets the preferred network. * @param preference the new preference */ public synchronized void setNetworkPreference(int preference) { enforceChangePermission(); - if (ConnectivityManager.isNetworkTypeValid(preference)) { + if (ConnectivityManager.isNetworkTypeValid(preference) && + mNetAttributes[preference].isDefault()) { if (mNetworkPreference != preference) { persistNetworkPreference(preference); mNetworkPreference = preference; @@ -173,9 +324,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void persistNetworkPreference(int networkPreference) { final ContentResolver cr = mContext.getContentResolver(); - Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE, networkPreference); + Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE, + networkPreference); } - + private int getPersistedNetworkPreference() { final ContentResolver cr = mContext.getContentResolver(); @@ -187,31 +339,30 @@ public class ConnectivityService extends IConnectivityManager.Stub { return ConnectivityManager.DEFAULT_NETWORK_PREFERENCE; } - + /** - * Make the state of network connectivity conform to the preference settings. + * Make the state of network connectivity conform to the preference settings * In this method, we only tear down a non-preferred network. Establishing * a connection to the preferred network is taken care of when we handle * the disconnect event from the non-preferred network * (see {@link #handleDisconnect(NetworkInfo)}). */ private void enforcePreference() { - if (mActiveNetwork == null) + if (mNetTrackers[mNetworkPreference].getNetworkInfo().isConnected()) return; - for (NetworkStateTracker t : mNetTrackers) { - if (t == mActiveNetwork) { - int netType = t.getNetworkInfo().getType(); - int otherNetType = ((netType == ConnectivityManager.TYPE_WIFI) ? - ConnectivityManager.TYPE_MOBILE : - ConnectivityManager.TYPE_WIFI); - - if (t.getNetworkInfo().getType() != mNetworkPreference) { - NetworkStateTracker otherTracker = mNetTrackers[otherNetType]; - if (otherTracker.isAvailable()) { - teardown(t); - } + if (!mNetTrackers[mNetworkPreference].isAvailable()) + return; + + for (int t=0; t <= ConnectivityManager.MAX_RADIO_TYPE; t++) { + if (t != mNetworkPreference && + mNetTrackers[t].getNetworkInfo().isConnected()) { + if (DBG) { + Log.d(TAG, "tearing down " + + mNetTrackers[t].getNetworkInfo() + + " in enforcePreference"); } + teardown(mNetTrackers[t]); } } } @@ -229,13 +380,21 @@ public class ConnectivityService extends IConnectivityManager.Stub { * Return NetworkInfo for the active (i.e., connected) network interface. * It is assumed that at most one network is active at a time. If more * than one is active, it is indeterminate which will be returned. - * @return the info for the active network, or {@code null} if none is active + * @return the info for the active network, or {@code null} if none is + * active */ public NetworkInfo getActiveNetworkInfo() { enforceAccessPermission(); - for (NetworkStateTracker t : mNetTrackers) { + for (int type=0; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) { + if (!mNetAttributes[type].isDefault()) { + continue; + } + NetworkStateTracker t = mNetTrackers[type]; NetworkInfo info = t.getNetworkInfo(); if (info.isConnected()) { + if (DBG && type != mActiveDefaultNetwork) Log.e(TAG, + "connected default network is not " + + "mActiveDefaultNetwork!"); return info; } } @@ -280,36 +439,253 @@ public class ConnectivityService extends IConnectivityManager.Stub { return tracker != null && tracker.setRadio(turnOn); } - public int startUsingNetworkFeature(int networkType, String feature) { + /** + * Used to notice when the calling process dies so we can self-expire + * + * Also used to know if the process has cleaned up after itself when + * our auto-expire timer goes off. The timer has a link to an object. + * + */ + private class FeatureUser implements IBinder.DeathRecipient { + int mNetworkType; + String mFeature; + IBinder mBinder; + int mPid; + int mUid; + + FeatureUser(int type, String feature, IBinder binder) { + super(); + mNetworkType = type; + mFeature = feature; + mBinder = binder; + mPid = getCallingPid(); + mUid = getCallingUid(); + + try { + mBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + binderDied(); + } + } + + void unlinkDeathRecipient() { + mBinder.unlinkToDeath(this, 0); + } + + public void binderDied() { + Log.d(TAG, "ConnectivityService FeatureUser binderDied(" + + mNetworkType + ", " + mFeature + ", " + mBinder); + stopUsingNetworkFeature(this, false); + } + + public void expire() { + Log.d(TAG, "ConnectivityService FeatureUser expire(" + + mNetworkType + ", " + mFeature + ", " + mBinder); + stopUsingNetworkFeature(this, false); + } + } + + // javadoc from interface + public int startUsingNetworkFeature(int networkType, String feature, + IBinder binder) { + if (DBG) { + Log.d(TAG, "startUsingNetworkFeature for net " + networkType + + ": " + feature); + } enforceChangePermission(); if (!ConnectivityManager.isNetworkTypeValid(networkType)) { - return -1; + return Phone.APN_REQUEST_FAILED; } - NetworkStateTracker tracker = mNetTrackers[networkType]; - if (tracker != null) { - return tracker.startUsingNetworkFeature(feature, getCallingPid(), getCallingUid()); + + FeatureUser f = new FeatureUser(networkType, feature, binder); + + // TODO - move this into the MobileDataStateTracker + int usedNetworkType = networkType; + if(networkType == ConnectivityManager.TYPE_MOBILE) { + 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)) { + usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN; + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) { + usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI; + } } - return -1; + NetworkStateTracker network = mNetTrackers[usedNetworkType]; + if (network != null) { + if (usedNetworkType != networkType) { + Integer currentPid = new Integer(getCallingPid()); + + NetworkStateTracker radio = mNetTrackers[networkType]; + NetworkInfo ni = network.getNetworkInfo(); + + if (ni.isAvailable() == false) { + if (DBG) Log.d(TAG, "special network not available"); + return Phone.APN_TYPE_NOT_AVAILABLE; + } + + 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( + NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK, + f), getRestoreDefaultNetworkDelay()); + + + if ((ni.isConnectedOrConnecting() == true) && + !network.isTeardownRequested()) { + if (ni.isConnected() == true) { + // add the pid-specific dns + handleDnsConfigurationChange(); + if (DBG) Log.d(TAG, "special network already active"); + return Phone.APN_ALREADY_ACTIVE; + } + if (DBG) Log.d(TAG, "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) Log.d(TAG, "reconnecting to special network"); + network.reconnect(); + return Phone.APN_REQUEST_STARTED; + } else { + synchronized(this) { + mFeatureUsers.add(f); + } + mHandler.sendMessageDelayed(mHandler.obtainMessage( + NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK, + f), getRestoreDefaultNetworkDelay()); + + return network.startUsingNetworkFeature(feature, + getCallingPid(), getCallingUid()); + } + } + return Phone.APN_TYPE_NOT_AVAILABLE; } + // javadoc from interface public int stopUsingNetworkFeature(int networkType, String feature) { + int pid = getCallingPid(); + int uid = getCallingUid(); + + FeatureUser u = null; + boolean found = false; + + synchronized(this) { + for (int i = 0; i < mFeatureUsers.size() ; i++) { + u = (FeatureUser)mFeatureUsers.get(i); + if (uid == u.mUid && pid == u.mPid && + networkType == u.mNetworkType && + TextUtils.equals(feature, u.mFeature)) { + found = true; + break; + } + } + } + if (found && u != null) { + // stop regardless of how many other time this proc had called start + return stopUsingNetworkFeature(u, true); + } else { + // none found! + return 1; + } + } + + private int stopUsingNetworkFeature(FeatureUser u, boolean ignoreDups) { + int networkType = u.mNetworkType; + String feature = u.mFeature; + int pid = u.mPid; + int uid = u.mUid; + + NetworkStateTracker tracker = null; + boolean callTeardown = false; // used to carry our decision outside of sync block + + if (DBG) { + Log.d(TAG, "stopUsingNetworkFeature for net " + networkType + + ": " + feature); + } enforceChangePermission(); if (!ConnectivityManager.isNetworkTypeValid(networkType)) { return -1; } - NetworkStateTracker tracker = mNetTrackers[networkType]; - if (tracker != null) { - return tracker.stopUsingNetworkFeature(feature, getCallingPid(), getCallingUid()); + + // need to link the mFeatureUsers list with the mNetRequestersPids state in this + // sync block + synchronized(this) { + // check if this process still has an outstanding start request + if (!mFeatureUsers.contains(u)) { + return 1; + } + u.unlinkDeathRecipient(); + mFeatureUsers.remove(mFeatureUsers.indexOf(u)); + // If we care about duplicate requests, check for that here. + // + // This is done to support the extension of a request - the app + // can request we start the network feature again and renew the + // auto-shutoff delay. Normal "stop" calls from the app though + // do not pay attention to duplicate requests - in effect the + // API does not refcount and a single stop will counter multiple starts. + if (ignoreDups == false) { + for (int i = 0; i < mFeatureUsers.size() ; i++) { + FeatureUser x = (FeatureUser)mFeatureUsers.get(i); + if (x.mUid == u.mUid && x.mPid == u.mPid && + x.mNetworkType == u.mNetworkType && + TextUtils.equals(x.mFeature, u.mFeature)) { + return 1; + } + } + } + + // TODO - move to MobileDataStateTracker + int usedNetworkType = networkType; + if (networkType == ConnectivityManager.TYPE_MOBILE) { + 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)) { + usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN; + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) { + usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI; + } + } + tracker = mNetTrackers[usedNetworkType]; + if(usedNetworkType != networkType) { + Integer currentPid = new Integer(pid); + reassessPidDns(pid, true); + mNetRequestersPids[usedNetworkType].remove(currentPid); + if (mNetRequestersPids[usedNetworkType].size() != 0) { + if (DBG) Log.d(TAG, "not tearing down special network - " + + "others still using it"); + return 1; + } + callTeardown = true; + } + } + + if (callTeardown) { + tracker.teardown(); + return 1; + } else { + // do it the old fashioned way + return tracker.stopUsingNetworkFeature(feature, pid, uid); } - return -1; } /** * 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 + * @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 requestRouteToHost(int networkType, int hostAddress) { @@ -318,19 +694,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { return false; } NetworkStateTracker tracker = mNetTrackers[networkType]; - /* - * If there's only one connected network, and it's the one requested, - * then we don't have to do anything - the requested route already - * exists. If it's not the requested network, then it's not possible - * to establish the requested route. Finally, if there is more than - * one connected network, then we must insert an entry in the routing - * table. - */ - if (getNumConnectedNetworks() > 1) { - return tracker.requestRouteToHost(hostAddress); - } else { - return tracker.getNetworkInfo().getType() == networkType; + + if (!tracker.getNetworkInfo().isConnected() || tracker.isTeardownRequested()) { + if (DBG) { + Log.d(TAG, "requestRouteToHost on down network (" + networkType + " - dropped"); + } + return false; } + return tracker.requestRouteToHost(hostAddress); } /** @@ -340,7 +711,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { return Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.BACKGROUND_DATA, 1) == 1; } - + /** * @see ConnectivityManager#setBackgroundDataSetting(boolean) */ @@ -348,22 +719,24 @@ public class ConnectivityService extends IConnectivityManager.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_BACKGROUND_DATA_SETTING, "ConnectivityService"); - + if (getBackgroundDataSetting() == allowBackgroundDataUsage) return; Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.BACKGROUND_DATA, allowBackgroundDataUsage ? 1 : 0); - + Settings.Secure.BACKGROUND_DATA, + allowBackgroundDataUsage ? 1 : 0); + Intent broadcast = new Intent( ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED); mContext.sendBroadcast(broadcast); - } - + } + private int getNumConnectedNetworks() { int numConnectedNets = 0; for (NetworkStateTracker nt : mNetTrackers) { - if (nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { + if (nt.getNetworkInfo().isConnected() && + !nt.isTeardownRequested()) { ++numConnectedNets; } } @@ -371,64 +744,46 @@ public class ConnectivityService extends IConnectivityManager.Stub { } private void enforceAccessPermission() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NETWORK_STATE, - "ConnectivityService"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_NETWORK_STATE, + "ConnectivityService"); } private void enforceChangePermission() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_NETWORK_STATE, - "ConnectivityService"); - + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CHANGE_NETWORK_STATE, + "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 broadcast. - * But first, we check whether it might be possible to connect to a different - * network. + * 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 + * broadcast. But first, we check whether it might be possible to connect + * to a different network. * @param info the {@code NetworkInfo} for the network */ private void handleDisconnect(NetworkInfo info) { - if (DBG) Log.v(TAG, "Handle DISCONNECT for " + info.getTypeName()); + int prevNetType = info.getType(); - mNetTrackers[info.getType()].setTeardownRequested(false); + mNetTrackers[prevNetType].setTeardownRequested(false); /* * If the disconnected network is not the active one, then don't report * this as a loss of connectivity. What probably happened is that we're * getting the disconnect for a network that we explicitly disabled * in accordance with network preference policies. */ - if (mActiveNetwork == null || info.getType() != mActiveNetwork.getNetworkInfo().getType()) - return; - - NetworkStateTracker newNet; - if (info.getType() == ConnectivityManager.TYPE_MOBILE) { - newNet = mWifiStateTracker; - } else /* info().getType() == TYPE_WIFI */ { - newNet = mMobileDataStateTracker; - } - - /** - * 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. - */ - NetworkInfo switchTo = null; - if (newNet.isAvailable()) { - mActiveNetwork = newNet; - switchTo = newNet.getNetworkInfo(); - switchTo.setFailover(true); - if (!switchTo.isConnectedOrConnecting()) { - newNet.reconnect(); + if (!mNetAttributes[prevNetType].isDefault()) { + List pids = mNetRequestersPids[prevNetType]; + for (int i = 0; i<pids.size(); i++) { + Integer pid = (Integer)pids.get(i); + // will remove them because the net's no longer connected + // need to do this now as only now do we know the pids and + // can properly null things that are no longer referenced. + reassessPidDns(pid.intValue(), false); } - } else { - newNet.reconnect(); } - boolean otherNetworkConnected = false; Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); if (info.isFailover()) { @@ -439,31 +794,92 @@ public class ConnectivityService extends IConnectivityManager.Stub { intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason()); } if (info.getExtraInfo() != null) { - intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo()); + intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, + info.getExtraInfo()); } - if (switchTo != null) { - otherNetworkConnected = switchTo.isConnected(); - if (DBG) { - if (otherNetworkConnected) { - Log.v(TAG, "Switching to already connected " + switchTo.getTypeName()); + + /* + * If this is a default network, check if other defaults are available + * or active + */ + NetworkStateTracker newNet = null; + if (mNetAttributes[prevNetType].isDefault()) { + if (mActiveDefaultNetwork == prevNetType) { + mActiveDefaultNetwork = -1; + } + + int newType = -1; + int newPriority = -1; + for (int checkType=0; checkType <= + ConnectivityManager.MAX_NETWORK_TYPE; checkType++) { + if (checkType == prevNetType) { + 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 (mRadioAttributes[mNetAttributes[checkType].mRadio]. + mPriority > newPriority) { + newType = checkType; + newPriority = mRadioAttributes[mNetAttributes[newType]. + mRadio].mPriority; + } + } + } + + 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()) { + Log.v(TAG, "Switching to already connected " + + switchTo.getTypeName()); + } else { + Log.v(TAG, "Attempting to switch to " + + switchTo.getTypeName()); + } + } + intent.putExtra(ConnectivityManager. + EXTRA_OTHER_NETWORK_INFO, switchTo); } else { - Log.v(TAG, "Attempting to switch to " + switchTo.getTypeName()); + intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, + true); + newNet.reconnect(); } + } else { + intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, + true); } - intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo); - } else { - intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); } - if (DBG) Log.v(TAG, "Sending DISCONNECT bcast for " + info.getTypeName() + - (switchTo == null ? "" : " other=" + switchTo.getTypeName())); - mContext.sendStickyBroadcast(intent); + // do this before we broadcast the change + handleConnectivityChange(); + + sendStickyBroadcast(intent); /* - * If the failover network is already connected, then immediately send out - * a followup broadcast indicating successful failover + * If the failover network is already connected, then immediately send + * out a followup broadcast indicating successful failover */ - if (switchTo != null && otherNetworkConnected) - sendConnectedBroadcast(switchTo); + if (newNet != null && newNet.getNetworkInfo().isConnected()) + sendConnectedBroadcast(newNet.getNetworkInfo()); } private void sendConnectedBroadcast(NetworkInfo info) { @@ -477,9 +893,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason()); } if (info.getExtraInfo() != null) { - intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo()); + intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, + info.getExtraInfo()); } - mContext.sendStickyBroadcast(intent); + sendStickyBroadcast(intent); } /** @@ -488,106 +905,124 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private void handleConnectionFailure(NetworkInfo info) { mNetTrackers[info.getType()].setTeardownRequested(false); - if (getActiveNetworkInfo() == null) { - String reason = info.getReason(); - String extraInfo = info.getExtraInfo(); - if (DBG) { - String reasonText; - if (reason == null) { - reasonText = "."; - } else { - reasonText = " (" + reason + ")."; - } - Log.v(TAG, "Attempt to connect to " + info.getTypeName() + " failed" + reasonText); + String reason = info.getReason(); + String extraInfo = info.getExtraInfo(); + + if (DBG) { + String reasonText; + if (reason == null) { + reasonText = "."; + } else { + reasonText = " (" + reason + ")."; } - - Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); - intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + Log.v(TAG, "Attempt to connect to " + info.getTypeName() + + " failed" + reasonText); + } + + Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + if (getActiveNetworkInfo() == null) { intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); - if (reason != null) { - intent.putExtra(ConnectivityManager.EXTRA_REASON, reason); - } - if (extraInfo != null) { - intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, extraInfo); + } + if (reason != null) { + intent.putExtra(ConnectivityManager.EXTRA_REASON, reason); + } + if (extraInfo != null) { + intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, extraInfo); + } + if (info.isFailover()) { + intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); + info.setFailover(false); + } + sendStickyBroadcast(intent); + } + + private void sendStickyBroadcast(Intent intent) { + synchronized(this) { + if (mSystemReady) { + mContext.sendStickyBroadcast(intent); + } else { + if (mDeferredBroadcasts == null) { + mDeferredBroadcasts = new ArrayList<Intent>(); + } + mDeferredBroadcasts.add(intent); } - if (info.isFailover()) { - intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); - info.setFailover(false); + } + } + + void systemReady() { + synchronized(this) { + mSystemReady = true; + if (mDeferredBroadcasts != null) { + int count = mDeferredBroadcasts.size(); + for (int i = 0; i < count; i++) { + mContext.sendStickyBroadcast(mDeferredBroadcasts.get(i)); + } + mDeferredBroadcasts = null; } - mContext.sendStickyBroadcast(intent); } } private void handleConnect(NetworkInfo info) { - if (DBG) Log.v(TAG, "Handle CONNECT for " + info.getTypeName()); + int type = info.getType(); // snapshot isFailover, because sendConnectedBroadcast() resets it boolean isFailover = info.isFailover(); - NetworkStateTracker thisNet = mNetTrackers[info.getType()]; - NetworkStateTracker deadnet = null; - NetworkStateTracker otherNet; - if (info.getType() == ConnectivityManager.TYPE_MOBILE) { - otherNet = mWifiStateTracker; - } else /* info().getType() == TYPE_WIFI */ { - otherNet = mMobileDataStateTracker; - } - /* - * Check policy to see whether we are connected to a non-preferred - * network that now needs to be torn down. - */ - NetworkInfo wifiInfo = mWifiStateTracker.getNetworkInfo(); - NetworkInfo mobileInfo = mMobileDataStateTracker.getNetworkInfo(); - if (wifiInfo.isConnected() && mobileInfo.isConnected()) { - if (mNetworkPreference == ConnectivityManager.TYPE_WIFI) - deadnet = mMobileDataStateTracker; - else - deadnet = mWifiStateTracker; - } - - boolean toredown = false; - thisNet.setTeardownRequested(false); - if (!mTestMode && deadnet != null) { - if (DBG) Log.v(TAG, "Policy requires " + - deadnet.getNetworkInfo().getTypeName() + " teardown"); - toredown = teardown(deadnet); - if (DBG && !toredown) { - Log.d(TAG, "Network declined teardown request"); + NetworkStateTracker thisNet = mNetTrackers[type]; + + // if this is a default net and other default is running + // kill the one not preferred + if (mNetAttributes[type].isDefault()) { + if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) { + if ((type != mNetworkPreference && + mNetAttributes[mActiveDefaultNetwork].mPriority > + mNetAttributes[type].mPriority) || + mNetworkPreference == mActiveDefaultNetwork) { + // don't accept this one + if (DBG) Log.v(TAG, "Not broadcasting CONNECT_ACTION " + + "to torn down network " + info.getTypeName()); + teardown(thisNet); + return; + } else { + // tear down the other + NetworkStateTracker otherNet = + mNetTrackers[mActiveDefaultNetwork]; + if (DBG) Log.v(TAG, "Policy requires " + + otherNet.getNetworkInfo().getTypeName() + + " teardown"); + if (!teardown(otherNet)) { + Log.e(TAG, "Network declined teardown request"); + return; + } + if (isFailover) { + otherNet.releaseWakeLock(); + } + } } + mActiveDefaultNetwork = type; } - - /* - * Note that if toredown is true, deadnet cannot be null, so there is - * no danger of a null pointer exception here.. - */ - if (!toredown || deadnet.getNetworkInfo().getType() != info.getType()) { - mActiveNetwork = thisNet; - if (DBG) Log.v(TAG, "Sending CONNECT bcast for " + info.getTypeName()); - thisNet.updateNetworkSettings(); - sendConnectedBroadcast(info); - if (isFailover) { - otherNet.releaseWakeLock(); - } - } else { - if (DBG) Log.v(TAG, "Not broadcasting CONNECT_ACTION to torn down network " + - info.getTypeName()); - } + thisNet.setTeardownRequested(false); + thisNet.updateNetworkSettings(); + handleConnectivityChange(); + sendConnectedBroadcast(info); } private void handleScanResultsAvailable(NetworkInfo info) { int networkType = info.getType(); if (networkType != ConnectivityManager.TYPE_WIFI) { - if (DBG) Log.v(TAG, "Got ScanResultsAvailable for " + info.getTypeName() + " network." - + " Don't know how to handle."); + if (DBG) Log.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) { + private void handleNotificationChange(boolean visible, int id, + Notification notification) { NotificationManager notificationManager = (NotificationManager) mContext .getSystemService(Context.NOTIFICATION_SERVICE); - + if (visible) { notificationManager.notify(id, notification); } else { @@ -605,79 +1040,173 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private void handleConnectivityChange() { /* + * If a non-default network is enabled, add the host routes that + * will allow it's DNS servers to be accessed. Only * If both mobile and wifi are enabled, add the host routes that * will allow MMS traffic to pass on the mobile network. But * remove the default route for the mobile network, so that there * will be only one default route, to ensure that all traffic * except MMS will travel via Wi-Fi. */ - int numConnectedNets = handleConfigurationChange(); - if (numConnectedNets > 1) { - mMobileDataStateTracker.addPrivateRoutes(); - mMobileDataStateTracker.removeDefaultRoute(); - } else if (mMobileDataStateTracker.getNetworkInfo().isConnected()) { - mMobileDataStateTracker.removePrivateRoutes(); - mMobileDataStateTracker.restoreDefaultRoute(); + handleDnsConfigurationChange(); + + for (int netType : mPriorityList) { + if (mNetTrackers[netType].getNetworkInfo().isConnected()) { + if (mNetAttributes[netType].isDefault()) { + mNetTrackers[netType].addDefaultRoute(); + } else { + mNetTrackers[netType].addPrivateDnsRoutes(); + } + } else { + if (mNetAttributes[netType].isDefault()) { + mNetTrackers[netType].removeDefaultRoute(); + } else { + mNetTrackers[netType].removePrivateDnsRoutes(); + } + } + } + } + + /** + * Adjust the per-process dns entries (net.dns<x>.<pid>) based + * on the highest priority active net which this process requested. + * If there aren't any, clear it out + */ + private void reassessPidDns(int myPid, boolean doBump) + { + if (DBG) Log.d(TAG, "reassessPidDns for pid " + myPid); + for(int i : mPriorityList) { + if (mNetAttributes[i].isDefault()) { + continue; + } + NetworkStateTracker nt = mNetTrackers[i]; + if (nt.getNetworkInfo().isConnected() && + !nt.isTeardownRequested()) { + 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); + if (doBump) { + bumpDns(); + } + return; + } + } + } + } + // nothing found - delete + for (int i = 1; ; i++) { + String prop = "net.dns" + i + "." + myPid; + if (SystemProperties.get(prop).length() == 0) { + if (doBump) { + bumpDns(); + } + return; + } + SystemProperties.set(prop, ""); } } - private int handleConfigurationChange() { + private void writePidDns(String[] dnsList, 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); + } + } + } + + private void bumpDns() { /* - * Set DNS properties. Always put Wi-Fi entries at the front of - * the list if it is active. + * Bump the property that tells the name resolver library to reread + * the DNS server list from the properties. */ - int index = 1; - String lastDns = ""; - int numConnectedNets = 0; - int incrValue = ConnectivityManager.TYPE_MOBILE - ConnectivityManager.TYPE_WIFI; - int stopValue = ConnectivityManager.TYPE_MOBILE + incrValue; + String propVal = SystemProperties.get("net.dnschange"); + int n = 0; + if (propVal.length() != 0) { + try { + n = Integer.parseInt(propVal); + } catch (NumberFormatException e) {} + } + SystemProperties.set("net.dnschange", "" + (n+1)); + } - for (int netType = ConnectivityManager.TYPE_WIFI; netType != stopValue; netType += incrValue) { + private void handleDnsConfigurationChange() { + // add default net's dns entries + for (int x = mPriorityList.length-1; x>= 0; x--) { + int netType = mPriorityList[x]; NetworkStateTracker nt = mNetTrackers[netType]; - if (nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { - ++numConnectedNets; + if (nt != null && nt.getNetworkInfo().isConnected() && + !nt.isTeardownRequested()) { String[] dnsList = nt.getNameServers(); - for (int i = 0; i < dnsList.length && dnsList[i] != null; i++) { - // skip duplicate entries - if (!dnsList[i].equals(lastDns)) { - SystemProperties.set("net.dns" + index++, dnsList[i]); - lastDns = dnsList[i]; + if (mNetAttributes[netType].isDefault()) { + int j = 1; + for (String dns : dnsList) { + if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) { + if (DBG) { + Log.d(TAG, "adding dns " + dns + " for " + + nt.getNetworkInfo().getTypeName()); + } + SystemProperties.set("net.dns" + j++, dns); + } + } + for (int k=j ; k<mNumDnsEntries; k++) { + if (DBG) Log.d(TAG, "erasing net.dns" + k); + SystemProperties.set("net.dns" + k, ""); + } + mNumDnsEntries = j; + } else { + // set per-pid dns for attached secondary nets + List pids = mNetRequestersPids[netType]; + for (int y=0; y< pids.size(); y++) { + Integer pid = (Integer)pids.get(y); + writePidDns(dnsList, pid.intValue()); } } } } - // Null out any DNS properties that are no longer used - for (int i = index; i <= mNumDnsEntries; i++) { - SystemProperties.set("net.dns" + i, ""); + + bumpDns(); + } + + private int getRestoreDefaultNetworkDelay() { + String restoreDefaultNetworkDelayStr = SystemProperties.get( + NETWORK_RESTORE_DELAY_PROP_NAME); + if(restoreDefaultNetworkDelayStr != null && + restoreDefaultNetworkDelayStr.length() != 0) { + try { + return Integer.valueOf(restoreDefaultNetworkDelayStr); + } catch (NumberFormatException e) { + } } - mNumDnsEntries = index - 1; - // Notify the name resolver library of the change - SystemProperties.set("net.dnschange", String.valueOf(sDnsChangeCounter++)); - return numConnectedNets; + return RESTORE_DEFAULT_NETWORK_DELAY; } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { - pw.println("Permission Denial: can't dump ConnectivityService from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); + pw.println("Permission Denial: can't dump ConnectivityService " + + "from from pid=" + Binder.getCallingPid() + ", uid=" + + Binder.getCallingUid()); return; } - if (mActiveNetwork == null) { - pw.println("No active network"); - } else { - pw.println("Active network: " + mActiveNetwork.getNetworkInfo().getTypeName()); - } pw.println(); for (NetworkStateTracker nst : mNetTrackers) { + if (nst.getNetworkInfo().isConnected()) { + pw.println("Active network: " + nst.getNetworkInfo(). + getTypeName()); + } pw.println(nst.getNetworkInfo()); pw.println(nst); pw.println(); } } + // must be stateless - things change under us. private class MyHandler extends Handler { @Override public void handleMessage(Message msg) { @@ -685,46 +1214,54 @@ public class ConnectivityService extends IConnectivityManager.Stub { switch (msg.what) { case NetworkStateTracker.EVENT_STATE_CHANGED: info = (NetworkInfo) msg.obj; - if (DBG) Log.v(TAG, "ConnectivityChange for " + info.getTypeName() + ": " + + if (DBG) Log.d(TAG, "ConnectivityChange for " + + info.getTypeName() + ": " + info.getState() + "/" + info.getDetailedState()); // Connectivity state changed: // [31-13] Reserved for future use - // [12-9] Network subtype (for mobile network, as defined by TelephonyManager) - // [8-3] Detailed state ordinal (as defined by NetworkInfo.DetailedState) + // [12-9] Network subtype (for mobile network, as defined + // by TelephonyManager) + // [8-3] Detailed state ordinal (as defined by + // NetworkInfo.DetailedState) // [2-0] Network type (as defined by ConnectivityManager) int eventLogParam = (info.getType() & 0x7) | ((info.getDetailedState().ordinal() & 0x3f) << 3) | (info.getSubtype() << 9); - EventLog.writeEvent(EVENTLOG_CONNECTIVITY_STATE_CHANGED, eventLogParam); - - if (info.getDetailedState() == NetworkInfo.DetailedState.FAILED) { + EventLog.writeEvent(EVENTLOG_CONNECTIVITY_STATE_CHANGED, + eventLogParam); + + if (info.getDetailedState() == + NetworkInfo.DetailedState.FAILED) { handleConnectionFailure(info); - } else if (info.getState() == NetworkInfo.State.DISCONNECTED) { + } else if (info.getState() == + NetworkInfo.State.DISCONNECTED) { handleDisconnect(info); } else if (info.getState() == NetworkInfo.State.SUSPENDED) { // TODO: need to think this over. - // the logic here is, handle SUSPENDED the same as DISCONNECTED. The - // only difference being we are broadcasting an intent with NetworkInfo - // that's suspended. This allows the applications an opportunity to - // handle DISCONNECTED and SUSPENDED differently, or not. + // the logic here is, handle SUSPENDED the same as + // DISCONNECTED. The only difference being we are + // broadcasting an intent with NetworkInfo that's + // suspended. This allows the applications an + // opportunity to handle DISCONNECTED and SUSPENDED + // differently, or not. handleDisconnect(info); } else if (info.getState() == NetworkInfo.State.CONNECTED) { handleConnect(info); } - handleConnectivityChange(); 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); + handleNotificationChange(msg.arg1 == 1, msg.arg2, + (Notification) msg.obj); case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: - handleConfigurationChange(); + handleDnsConfigurationChange(); break; case NetworkStateTracker.EVENT_ROAMING_CHANGED: @@ -734,6 +1271,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: // fill me in break; + case NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK: + FeatureUser u = (FeatureUser)msg.obj; + u.expire(); + break; } } } diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java index 52e09ca..57af029 100644 --- a/services/java/com/android/server/DeviceStorageMonitorService.java +++ b/services/java/com/android/server/DeviceStorageMonitorService.java @@ -43,8 +43,8 @@ import android.provider.Settings; /** * This class implements a service to monitor the amount of disk storage space * on the device. If the free storage on device is less than a tunable threshold value - * (default is 10%. this value is a gservices parameter) a low memory notification is - * displayed to alert the user. If the user clicks on the low memory notification the + * (default is 10%. this value is a gservices parameter) a low memory notification is + * displayed to alert the user. If the user clicks on the low memory notification the * Application Manager application gets launched to let the user free storage space. * Event log events: * A low memory event with the free storage on device in bytes is logged to the event log @@ -68,32 +68,35 @@ class DeviceStorageMonitorService extends Binder { private static final int EVENT_LOG_FREE_STORAGE_LEFT = 2746; 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; - private long mFreeMem; + private long mFreeMem; // on /data private long mLastReportedFreeMem; private long mLastReportedFreeMemTime; private boolean mLowMemFlag=false; private Context mContext; private ContentResolver mContentResolver; - long mBlkSize; - long mTotalMemory; - StatFs mFileStats; - private static final String DATA_PATH="/data"; - long mThreadStartTime = -1; - boolean mClearSucceeded = false; - boolean mClearingCache; + private long mTotalMemory; // on /data + private StatFs mDataFileStats; + private StatFs mSystemFileStats; + private StatFs mCacheFileStats; + private static final String DATA_PATH = "/data"; + private static final String SYSTEM_PATH = "/system"; + private static final String CACHE_PATH = "/cache"; + private long mThreadStartTime = -1; + private boolean mClearSucceeded = false; + private boolean mClearingCache; private Intent mStorageLowIntent; private Intent mStorageOkIntent; private CachePackageDataObserver mClearCacheObserver; private static final int _TRUE = 1; private static final int _FALSE = 0; - + /** * This string is used for ServiceManager access to this class. */ static final String SERVICE = "devicestoragemonitor"; - + /** - * Handler that checks the amount of disk space on the device and sends a + * Handler that checks the amount of disk space on the device and sends a * notification if the device runs low on disk space */ Handler mHandler = new Handler() { @@ -107,7 +110,7 @@ class DeviceStorageMonitorService extends Binder { checkMemory(msg.arg1 == _TRUE); } }; - + class CachePackageDataObserver extends IPackageDataObserver.Stub { public void onRemoveCompleted(String packageName, boolean succeeded) { mClearSucceeded = succeeded; @@ -115,12 +118,17 @@ class DeviceStorageMonitorService extends Binder { if(localLOGV) Log.i(TAG, " Clear succeeded:"+mClearSucceeded +", mClearingCache:"+mClearingCache+" Forcing memory check"); postCheckMemoryMsg(false, 0); - } + } } - + private final void restatDataDir() { - mFileStats.restat(DATA_PATH); - mFreeMem = mFileStats.getAvailableBlocks()*mBlkSize; + try { + mDataFileStats.restat(DATA_PATH); + mFreeMem = (long) mDataFileStats.getAvailableBlocks() * + mDataFileStats.getBlockSize(); + } catch (IllegalArgumentException e) { + // use the old value of mFreeMem + } // Allow freemem to be overridden by debug.freemem for testing String debugFreeMem = SystemProperties.get("debug.freemem"); if (!"".equals(debugFreeMem)) { @@ -132,10 +140,27 @@ class DeviceStorageMonitorService extends Binder { DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES)*60*1000; //log the amount of free memory in event log long currTime = SystemClock.elapsedRealtime(); - if((mLastReportedFreeMemTime == 0) || - (currTime-mLastReportedFreeMemTime) >= freeMemLogInterval) { + if((mLastReportedFreeMemTime == 0) || + (currTime-mLastReportedFreeMemTime) >= freeMemLogInterval) { mLastReportedFreeMemTime = currTime; - EventLog.writeEvent(EVENT_LOG_FREE_STORAGE_LEFT, mFreeMem); + long mFreeSystem = -1, mFreeCache = -1; + try { + mSystemFileStats.restat(SYSTEM_PATH); + mFreeSystem = (long) mSystemFileStats.getAvailableBlocks() * + mSystemFileStats.getBlockSize(); + } catch (IllegalArgumentException e) { + // ignore; report -1 + } + try { + mCacheFileStats.restat(CACHE_PATH); + mFreeCache = (long) mCacheFileStats.getAvailableBlocks() * + mCacheFileStats.getBlockSize(); + } catch (IllegalArgumentException e) { + // ignore; report -1 + } + mCacheFileStats.restat(CACHE_PATH); + EventLog.writeEvent(EVENT_LOG_FREE_STORAGE_LEFT, + mFreeMem, mFreeSystem, mFreeCache); } // Read the reporting threshold from Gservices long threshold = Gservices.getLong(mContentResolver, @@ -148,7 +173,7 @@ class DeviceStorageMonitorService extends Binder { EventLog.writeEvent(EVENT_LOG_STORAGE_BELOW_THRESHOLD, mFreeMem); } } - + private final void clearCache() { if (mClearCacheObserver == null) { // Lazy instantiation @@ -165,10 +190,10 @@ class DeviceStorageMonitorService extends Binder { mClearSucceeded = false; } } - + private final void checkMemory(boolean checkCache) { - //if the thread that was started to clear cache is still running do nothing till its - //finished clearing cache. Ideally this flag could be modified by clearCache + //if the thread that was started to clear cache is still running do nothing till its + //finished clearing cache. Ideally this flag could be modified by clearCache // and should be accessed via a lock but even if it does this test will fail now and //hopefully the next time this flag will be set to the correct value. if(mClearingCache) { @@ -177,11 +202,11 @@ class DeviceStorageMonitorService extends Binder { long diffTime = System.currentTimeMillis() - mThreadStartTime; if(diffTime > (10*60*1000)) { Log.w(TAG, "Thread that clears cache file seems to run for ever"); - } + } } else { restatDataDir(); if (localLOGV) Log.v(TAG, "freeMemory="+mFreeMem); - + //post intent to NotificationManager to display icon if necessary long memThreshold = getMemThreshold(); if (mFreeMem < memThreshold) { @@ -214,7 +239,7 @@ class DeviceStorageMonitorService extends Binder { //keep posting messages to itself periodically postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL); } - + private void postCheckMemoryMsg(boolean clearCache, long delay) { // Remove queued messages mHandler.removeMessages(DEVICE_MEMORY_WHAT); @@ -222,16 +247,16 @@ class DeviceStorageMonitorService extends Binder { clearCache ?_TRUE : _FALSE, 0), delay); } - + /* - * just query settings to retrieve the memory threshold. + * just query settings to retrieve the memory threshold. * Preferred this over using a ContentObserver since Settings.Gservices caches the value * any way */ private long getMemThreshold() { int value = Settings.Gservices.getInt( - mContentResolver, - Settings.Gservices.SYS_STORAGE_THRESHOLD_PERCENTAGE, + mContentResolver, + Settings.Gservices.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE); if(localLOGV) Log.v(TAG, "Threshold Percentage="+value); //evaluate threshold value @@ -247,16 +272,17 @@ class DeviceStorageMonitorService extends Binder { mContext = context; mContentResolver = mContext.getContentResolver(); //create StatFs object - mFileStats = new StatFs(DATA_PATH); - //initialize block size - mBlkSize = mFileStats.getBlockSize(); + mDataFileStats = new StatFs(DATA_PATH); + mSystemFileStats = new StatFs(SYSTEM_PATH); + mCacheFileStats = new StatFs(CACHE_PATH); //initialize total storage on device - mTotalMemory = ((long)mFileStats.getBlockCount()*mBlkSize)/100L; + mTotalMemory = ((long)mDataFileStats.getBlockCount() * + mDataFileStats.getBlockSize())/100L; mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW); mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK); checkMemory(true); } - + /** * This method sends a notification to NotificationManager to display @@ -271,7 +297,7 @@ class DeviceStorageMonitorService extends Binder { Intent lowMemIntent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE); lowMemIntent.putExtra("memory", mFreeMem); lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - NotificationManager mNotificationMgr = + NotificationManager mNotificationMgr = (NotificationManager)mContext.getSystemService( Context.NOTIFICATION_SERVICE); CharSequence title = mContext.getText( @@ -302,7 +328,7 @@ class DeviceStorageMonitorService extends Binder { mContext.removeStickyBroadcast(mStorageLowIntent); mContext.sendBroadcast(mStorageOkIntent); } - + public void updateMemory() { int callingUid = getCallingUid(); if(callingUid != Process.SYSTEM_UID) { diff --git a/services/java/com/android/server/DockObserver.java b/services/java/com/android/server/DockObserver.java new file mode 100644 index 0000000..f089de1 --- /dev/null +++ b/services/java/com/android/server/DockObserver.java @@ -0,0 +1,184 @@ +/* + * 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.Activity; +import android.app.KeyguardManager; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.os.UEventObserver; +import android.provider.Settings; +import android.util.Log; + +import com.android.internal.widget.LockPatternUtils; + +import java.io.FileReader; +import java.io.FileNotFoundException; + +/** + * <p>DockObserver monitors for a docking station. + */ +class DockObserver extends UEventObserver { + private static final String TAG = DockObserver.class.getSimpleName(); + private static final boolean LOG = false; + + private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock"; + private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state"; + + private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + private boolean mSystemReady; + + private final Context mContext; + + private PowerManagerService mPowerManager; + + private KeyguardManager.KeyguardLock mKeyguardLock; + private boolean mKeyguardDisabled; + private LockPatternUtils mLockPatternUtils; + + // The broadcast receiver which receives the result of the ordered broadcast sent when + // the dock state changes. The original ordered broadcast is sent with an initial result + // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g., + // to RESULT_CANCELED, then the intent to start a dock app will not be sent. + private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (getResultCode() != Activity.RESULT_OK) { + return; + } + + // Launch a dock activity + String category; + switch (mDockState) { + case Intent.EXTRA_DOCK_STATE_CAR: + category = Intent.CATEGORY_CAR_DOCK; + break; + case Intent.EXTRA_DOCK_STATE_DESK: + category = Intent.CATEGORY_DESK_DOCK; + break; + default: + category = null; + break; + } + if (category != null) { + intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(category); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + try { + mContext.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.w(TAG, e.getCause()); + } + } + } + }; + + public DockObserver(Context context, PowerManagerService pm) { + mContext = context; + mPowerManager = pm; + mLockPatternUtils = new LockPatternUtils(context.getContentResolver()); + init(); // set initial status + startObserving(DOCK_UEVENT_MATCH); + } + + @Override + public void onUEvent(UEventObserver.UEvent event) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Dock UEVENT: " + event.toString()); + } + + synchronized (this) { + try { + int newState = Integer.parseInt(event.get("SWITCH_STATE")); + if (newState != mDockState) { + mDockState = newState; + if (mSystemReady) { + update(); + } + } + } catch (NumberFormatException e) { + Log.e(TAG, "Could not parse switch state from event " + event); + } + } + } + + private final void init() { + char[] buffer = new char[1024]; + + try { + FileReader file = new FileReader(DOCK_STATE_PATH); + int len = file.read(buffer, 0, 1024); + mDockState = Integer.valueOf((new String(buffer, 0, len)).trim()); + + } catch (FileNotFoundException e) { + Log.w(TAG, "This kernel does not have dock station support"); + } catch (Exception e) { + Log.e(TAG, "" , e); + } + } + + void systemReady() { + synchronized (this) { + KeyguardManager keyguardManager = + (KeyguardManager)mContext.getSystemService(Context.KEYGUARD_SERVICE); + mKeyguardLock = keyguardManager.newKeyguardLock(TAG); + + // don't bother broadcasting undocked here + if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { + update(); + } + mSystemReady = true; + } + } + + private final void update() { + mHandler.sendEmptyMessage(0); + } + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + synchronized (this) { + Log.i(TAG, "Dock state changed: " + mDockState); + if (Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { + Log.i(TAG, "Device not provisioned, skipping dock broadcast"); + return; + } + // Pack up the values and broadcast them to everyone + mPowerManager.userActivityWithForce(SystemClock.uptimeMillis(), false, true); + Intent intent = new Intent(Intent.ACTION_DOCK_EVENT); + intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState); + + // Send the ordered broadcast; the result receiver will receive after all + // broadcasts have been sent. If any broadcast receiver changes the result + // code from the initial value of RESULT_OK, then the result receiver will + // not launch the corresponding dock application. This gives apps a chance + // to override the behavior and stay in their app even when the device is + // placed into a dock. + mContext.sendStickyOrderedBroadcast( + intent, mResultReceiver, null, Activity.RESULT_OK, null, null); + } + } + }; +} diff --git a/services/java/com/android/server/HardwareService.java b/services/java/com/android/server/HardwareService.java index 5bc9b5f..88074c2 100755 --- a/services/java/com/android/server/HardwareService.java +++ b/services/java/com/android/server/HardwareService.java @@ -37,6 +37,9 @@ import android.os.Binder; import android.os.SystemClock; import android.util.Log; +import java.util.LinkedList; +import java.util.ListIterator; + public class HardwareService extends IHardwareService.Stub { private static final String TAG = "HardwareService"; @@ -49,10 +52,74 @@ public class HardwareService extends IHardwareService.Stub { static final int LIGHT_FLASH_NONE = 0; static final int LIGHT_FLASH_TIMED = 1; + static final int LIGHT_FLASH_HARDWARE = 2; + + /** + * Light brightness is managed by a user setting. + */ + static final int BRIGHTNESS_MODE_USER = 0; + + /** + * Light brightness is managed by a light sensor. + */ + static final int BRIGHTNESS_MODE_SENSOR = 1; + + private final LinkedList<Vibration> mVibrations; + private Vibration mCurrentVibration; private boolean mAttentionLightOn; private boolean mPulsing; + private class Vibration implements IBinder.DeathRecipient { + private final IBinder mToken; + private final long mTimeout; + private final long mStartTime; + private final long[] mPattern; + private final int mRepeat; + + Vibration(IBinder token, long millis) { + this(token, millis, null, 0); + } + + Vibration(IBinder token, long[] pattern, int repeat) { + this(token, 0, pattern, repeat); + } + + private Vibration(IBinder token, long millis, long[] pattern, + int repeat) { + mToken = token; + mTimeout = millis; + mStartTime = SystemClock.uptimeMillis(); + mPattern = pattern; + mRepeat = repeat; + } + + public void binderDied() { + synchronized (mVibrations) { + mVibrations.remove(this); + if (this == mCurrentVibration) { + doCancelVibrateLocked(); + startNextVibrationLocked(); + } + } + } + + public boolean hasLongerTimeout(long millis) { + if (mTimeout == 0) { + // This is a pattern, return false to play the simple + // vibration. + return false; + } + if ((mStartTime + mTimeout) + < (SystemClock.uptimeMillis() + millis)) { + // If this vibration will end before the time passed in, let + // the new vibration play. + return false; + } + return true; + } + } + HardwareService(Context context) { // Reset the hardware to a default state, in case this is a runtime // restart instead of a fresh boot. @@ -66,8 +133,10 @@ public class HardwareService extends IHardwareService.Stub { mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mWakeLock.setReferenceCounted(true); + mVibrations = new LinkedList<Vibration>(); + mBatteryStats = BatteryStatsService.getService(); - + IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); context.registerReceiver(mIntentReceiver, filter); @@ -78,13 +147,27 @@ public class HardwareService extends IHardwareService.Stub { super.finalize(); } - public void vibrate(long milliseconds) { + public void vibrate(long milliseconds, IBinder token) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires VIBRATE permission"); } - doCancelVibrate(); - vibratorOn(milliseconds); + // We're running in the system server so we cannot crash. Check for a + // timeout of 0 or negative. This will ensure that a vibration has + // either a timeout of > 0 or a non-null pattern. + if (milliseconds <= 0 || (mCurrentVibration != null + && mCurrentVibration.hasLongerTimeout(milliseconds))) { + // Ignore this vibration since the current vibration will play for + // longer than milliseconds. + return; + } + Vibration vib = new Vibration(token, milliseconds); + synchronized (mVibrations) { + removeVibrationLocked(token); + doCancelVibrateLocked(); + mCurrentVibration = vib; + startVibrationLocked(vib); + } } private boolean isAll0(long[] pattern) { @@ -121,34 +204,25 @@ public class HardwareService extends IHardwareService.Stub { return; } - synchronized (this) { - Death death = new Death(token); - try { - token.linkToDeath(death, 0); - } catch (RemoteException e) { - return; - } - - Thread oldThread = mThread; - - if (oldThread != null) { - // stop the old one - synchronized (mThread) { - mThread.mDone = true; - mThread.notify(); - } - } + Vibration vib = new Vibration(token, pattern, repeat); + try { + token.linkToDeath(vib, 0); + } catch (RemoteException e) { + return; + } - if (mDeath != null) { - mToken.unlinkToDeath(mDeath, 0); + synchronized (mVibrations) { + removeVibrationLocked(token); + doCancelVibrateLocked(); + if (repeat >= 0) { + mVibrations.addFirst(vib); + startNextVibrationLocked(); + } else { + // A negative repeat means that this pattern is not meant + // to repeat. Treat it like a simple vibration. + mCurrentVibration = vib; + startVibrationLocked(vib); } - - mDeath = death; - mToken = token; - - // start the new thread - mThread = new VibrateThread(pattern, repeat); - mThread.start(); } } finally { @@ -156,7 +230,7 @@ public class HardwareService extends IHardwareService.Stub { } } - public void cancelVibrate() { + public void cancelVibrate(IBinder token) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.VIBRATE, "cancelVibrate"); @@ -164,21 +238,27 @@ public class HardwareService extends IHardwareService.Stub { // so wakelock calls will succeed long identity = Binder.clearCallingIdentity(); try { - doCancelVibrate(); + synchronized (mVibrations) { + final Vibration vib = removeVibrationLocked(token); + if (vib == mCurrentVibration) { + doCancelVibrateLocked(); + startNextVibrationLocked(); + } + } } finally { Binder.restoreCallingIdentity(identity); } } - + public boolean getFlashlightEnabled() { return Hardware.getFlashlightEnabled(); } - + public void setFlashlightEnabled(boolean on) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FLASHLIGHT) + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FLASHLIGHT) != PackageManager.PERMISSION_GRANTED && - mContext.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST) + mContext.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires FLASHLIGHT or HARDWARE_TEST permission"); } @@ -186,60 +266,40 @@ public class HardwareService extends IHardwareService.Stub { } public void enableCameraFlash(int milliseconds) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.CAMERA) + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED && - mContext.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST) + mContext.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires CAMERA or HARDWARE_TEST permission"); } Hardware.enableCameraFlash(milliseconds); } - public void setBacklights(int brightness) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires HARDWARE_TEST permission"); - } - // Don't let applications turn the screen all the way off - brightness = Math.max(brightness, Power.BRIGHTNESS_DIM); - setLightBrightness_UNCHECKED(LIGHT_ID_BACKLIGHT, brightness); - setLightBrightness_UNCHECKED(LIGHT_ID_KEYBOARD, brightness); - setLightBrightness_UNCHECKED(LIGHT_ID_BUTTONS, brightness); - long identity = Binder.clearCallingIdentity(); - try { - mBatteryStats.noteScreenBrightness(brightness); - } catch (RemoteException e) { - Log.w(TAG, "RemoteException calling noteScreenBrightness on BatteryStatsService", e); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - void setLightOff_UNCHECKED(int light) { - setLight_native(mNativePointer, light, 0, LIGHT_FLASH_NONE, 0, 0); + setLight_native(mNativePointer, light, 0, LIGHT_FLASH_NONE, 0, 0, 0); } - void setLightBrightness_UNCHECKED(int light, int brightness) { + void setLightBrightness_UNCHECKED(int light, int brightness, int brightnessMode) { int b = brightness & 0x000000ff; b = 0xff000000 | (b << 16) | (b << 8) | b; - setLight_native(mNativePointer, light, b, LIGHT_FLASH_NONE, 0, 0); + setLight_native(mNativePointer, light, b, LIGHT_FLASH_NONE, 0, 0, brightnessMode); } void setLightColor_UNCHECKED(int light, int color) { - setLight_native(mNativePointer, light, color, LIGHT_FLASH_NONE, 0, 0); + setLight_native(mNativePointer, light, color, LIGHT_FLASH_NONE, 0, 0, 0); } void setLightFlashing_UNCHECKED(int light, int color, int mode, int onMS, int offMS) { - setLight_native(mNativePointer, light, color, mode, onMS, offMS); + setLight_native(mNativePointer, light, color, mode, onMS, offMS, 0); } - public void setAttentionLight(boolean on) { + public void setAttentionLight(boolean on, int color) { // Not worthy of a permission. We shouldn't have a flashlight permission. synchronized (this) { mAttentionLightOn = on; mPulsing = false; - setLight_native(mNativePointer, LIGHT_ID_ATTENTION, on ? 0xffffffff : 0, - LIGHT_FLASH_NONE, 0, 0); + setLight_native(mNativePointer, LIGHT_ID_ATTENTION, color, + LIGHT_FLASH_HARDWARE, on ? 3 : 0, 0, 0); } } @@ -253,8 +313,8 @@ public class HardwareService extends IHardwareService.Stub { } if (!mAttentionLightOn && !mPulsing) { mPulsing = true; - setLight_native(mNativePointer, LIGHT_ID_ATTENTION, 0xff101010, - LIGHT_FLASH_NONE, 0, 0); + setLight_native(mNativePointer, LIGHT_ID_ATTENTION, 0x00ffffff, + LIGHT_FLASH_HARDWARE, 7, 0, 0); mH.sendMessageDelayed(Message.obtain(mH, 1), 3000); } } @@ -271,33 +331,80 @@ public class HardwareService extends IHardwareService.Stub { mPulsing = false; setLight_native(mNativePointer, LIGHT_ID_ATTENTION, mAttentionLightOn ? 0xffffffff : 0, - LIGHT_FLASH_NONE, 0, 0); + LIGHT_FLASH_NONE, 0, 0, 0); } } } }; - private void doCancelVibrate() { - synchronized (this) { - if (mThread != null) { - synchronized (mThread) { - mThread.mDone = true; - mThread.notify(); - } - mThread = null; + private final Runnable mVibrationRunnable = new Runnable() { + public void run() { + synchronized (mVibrations) { + doCancelVibrateLocked(); + startNextVibrationLocked(); + } + } + }; + + // Lock held on mVibrations + private void doCancelVibrateLocked() { + if (mThread != null) { + synchronized (mThread) { + mThread.mDone = true; + mThread.notify(); } - vibratorOff(); + mThread = null; } + vibratorOff(); + mH.removeCallbacks(mVibrationRunnable); + } + + // Lock held on mVibrations + private void startNextVibrationLocked() { + if (mVibrations.size() <= 0) { + return; + } + mCurrentVibration = mVibrations.getFirst(); + startVibrationLocked(mCurrentVibration); + } + + // Lock held on mVibrations + private void startVibrationLocked(final Vibration vib) { + if (vib.mTimeout != 0) { + vibratorOn(vib.mTimeout); + mH.postDelayed(mVibrationRunnable, vib.mTimeout); + } else { + // mThread better be null here. doCancelVibrate should always be + // called before startNextVibrationLocked or startVibrationLocked. + mThread = new VibrateThread(vib); + mThread.start(); + } + } + + // Lock held on mVibrations + private Vibration removeVibrationLocked(IBinder token) { + ListIterator<Vibration> iter = mVibrations.listIterator(0); + while (iter.hasNext()) { + Vibration vib = iter.next(); + if (vib.mToken == token) { + iter.remove(); + return vib; + } + } + // We might be looking for a simple vibration which is only stored in + // mCurrentVibration. + if (mCurrentVibration != null && mCurrentVibration.mToken == token) { + return mCurrentVibration; + } + return null; } private class VibrateThread extends Thread { - long[] mPattern; - int mRepeat; + final Vibration mVibration; boolean mDone; - - VibrateThread(long[] pattern, int repeat) { - mPattern = pattern; - mRepeat = repeat; + + VibrateThread(Vibration vib) { + mVibration = vib; mWakeLock.acquire(); } @@ -323,12 +430,13 @@ public class HardwareService extends IHardwareService.Stub { Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY); synchronized (this) { int index = 0; - long[] pattern = mPattern; + long[] pattern = mVibration.mPattern; int len = pattern.length; + int repeat = mVibration.mRepeat; long duration = 0; while (!mDone) { - // add off-time duration to any accumulated on-time duration + // add off-time duration to any accumulated on-time duration if (index < len) { duration += pattern[index++]; } @@ -347,68 +455,53 @@ public class HardwareService extends IHardwareService.Stub { HardwareService.this.vibratorOn(duration); } } else { - if (mRepeat < 0) { + if (repeat < 0) { break; } else { - index = mRepeat; + index = repeat; duration = 0; } } } - if (mDone) { - // make sure vibrator is off if we were cancelled. - // otherwise, it will turn off automatically - // when the last timeout expires. - HardwareService.this.vibratorOff(); - } mWakeLock.release(); } - synchronized (HardwareService.this) { + synchronized (mVibrations) { if (mThread == this) { mThread = null; } - } - } - }; - - private class Death implements IBinder.DeathRecipient { - IBinder mMe; - - Death(IBinder me) { - mMe = me; - } - - public void binderDied() { - synchronized (HardwareService.this) { - if (mMe == mToken) { - doCancelVibrate(); + if (!mDone) { + // If this vibration finished naturally, start the next + // vibration. + mVibrations.remove(mVibration); + startNextVibrationLocked(); } } } - } + }; BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { - doCancelVibrate(); + synchronized (mVibrations) { + doCancelVibrateLocked(); + mVibrations.clear(); + } } } }; - + private static native int init_native(); private static native void finalize_native(int ptr); private static native void setLight_native(int ptr, int light, int color, int mode, - int onMS, int offMS); + int onMS, int offMS, int brightnessMode); private final Context mContext; private final PowerManager.WakeLock mWakeLock; private final IBatteryStats mBatteryStats; - + volatile VibrateThread mThread; - volatile Death mDeath; - volatile IBinder mToken; private int mNativePointer; diff --git a/services/java/com/android/server/HeadsetObserver.java b/services/java/com/android/server/HeadsetObserver.java index 9b0a2d4..bee3108 100644 --- a/services/java/com/android/server/HeadsetObserver.java +++ b/services/java/com/android/server/HeadsetObserver.java @@ -41,10 +41,16 @@ class HeadsetObserver extends UEventObserver { 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 BIT_TTY = (1 << 2); + private static final int BIT_FM_HEADSET = (1 << 3); + private static final int BIT_FM_SPEAKER = (1 << 4); + private int mHeadsetState; + private int mPrevHeadsetState; private String mHeadsetName; - private boolean mAudioRouteNeedsUpdate; - private AudioManager mAudioManager; + private boolean mPendingIntent; private final Context mContext; private final WakeLock mWakeLock; // held while there is a pending route change @@ -76,6 +82,7 @@ class HeadsetObserver extends UEventObserver { String newName = mHeadsetName; int newState = mHeadsetState; + mPrevHeadsetState = mHeadsetState; try { FileReader file = new FileReader(HEADSET_STATE_PATH); int len = file.read(buffer, 0, 1024); @@ -91,20 +98,25 @@ class HeadsetObserver extends UEventObserver { Log.e(TAG, "" , e); } - mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); update(newName, newState); } private synchronized final void update(String newName, int newState) { if (newName != mHeadsetName || newState != mHeadsetState) { - boolean isUnplug = (newState == 0 && mHeadsetState == 1); + boolean isUnplug = false; + if ( (mHeadsetState & BIT_HEADSET) > 0 || (mHeadsetState & BIT_HEADSET_NO_MIC) > 0) { + if ((newState & BIT_HEADSET) == 0 && (newState & BIT_HEADSET_NO_MIC) == 0) + isUnplug = true; + } mHeadsetName = newName; + mPrevHeadsetState = mHeadsetState; mHeadsetState = newState; - mAudioRouteNeedsUpdate = true; - - sendIntent(isUnplug); + mPendingIntent = true; if (isUnplug) { + 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. @@ -113,12 +125,13 @@ class HeadsetObserver extends UEventObserver { mWakeLock.acquire(); mHandler.sendEmptyMessageDelayed(0, 1000); } else { - updateAudioRoute(); + sendIntent(); + mPendingIntent = false; } } } - private synchronized final void sendIntent(boolean isUnplug) { + private synchronized final void sendIntent() { // Pack up the values and broadcast them to everyone Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); @@ -128,24 +141,15 @@ class HeadsetObserver extends UEventObserver { // TODO: Should we require a permission? ActivityManagerNative.broadcastStickyIntent(intent, null); - - if (isUnplug) { - intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY); - mContext.sendBroadcast(intent); - } - } - - private synchronized final void updateAudioRoute() { - if (mAudioRouteNeedsUpdate) { - mAudioManager.setWiredHeadsetOn(mHeadsetState == 1); - mAudioRouteNeedsUpdate = false; - } } private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { - updateAudioRoute(); + if (mPendingIntent) { + sendIntent(); + mPendingIntent = false; + } mWakeLock.release(); } }; diff --git a/services/java/com/android/server/InputDevice.java b/services/java/com/android/server/InputDevice.java index 7b8a2a4..6eb6242 100644 --- a/services/java/com/android/server/InputDevice.java +++ b/services/java/com/android/server/InputDevice.java @@ -23,9 +23,15 @@ import android.view.Surface; import android.view.WindowManagerPolicy; public class InputDevice { + static final boolean DEBUG_POINTERS = false; + static final boolean DEBUG_HACKS = false; + /** Amount that trackball needs to move in order to generate a key event. */ static final int TRACKBALL_MOVEMENT_THRESHOLD = 6; + /** Maximum number of pointers we will track and report. */ + static final int MAX_POINTERS = 10; + final int id; final int classes; final String name; @@ -34,9 +40,13 @@ public class InputDevice { final AbsoluteInfo absPressure; final AbsoluteInfo absSize; - long mDownTime = 0; + long mKeyDownTime = 0; int mMetaKeysState = 0; + // For use by KeyInputQueue for keeping track of the current touch + // data in the old non-multi-touch protocol. + final int[] curTouchVals = new int[MotionEvent.NUM_SAMPLE_DATA * 2]; + final MotionState mAbs = new MotionState(0, 0); final MotionState mRel = new MotionState(TRACKBALL_MOVEMENT_THRESHOLD, TRACKBALL_MOVEMENT_THRESHOLD); @@ -48,146 +58,707 @@ public class InputDevice { float yMoveScale; MotionEvent currentMove = null; boolean changed = false; - boolean down = false; - boolean lastDown = false; - long downTime = 0; - int x = 0; - int y = 0; - int pressure = 1; - int size = 0; + long mDownTime = 0; + + // The currently assigned pointer IDs, corresponding to the last data. + int[] mPointerIds = new int[MAX_POINTERS]; + + // This is the last generated pointer data, ordered to match + // mPointerIds. + boolean mSkipLastPointers; + int mLastNumPointers = 0; + final int[] mLastData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS]; + + // This is the next set of pointer data being generated. It is not + // in any known order, and will be propagated in to mLastData + // as part of mapping it to the appropriate pointer IDs. + // Note that we have one extra sample of data here, to help clients + // avoid doing bounds checking. + int mNextNumPointers = 0; + final int[] mNextData = new int[(MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS) + + MotionEvent.NUM_SAMPLE_DATA]; + + // Used to determine whether we dropped bad data, to avoid doing + // it repeatedly. + final boolean[] mDroppedBadPoint = new boolean[MAX_POINTERS]; + + // Used to perform averaging of reported coordinates, to smooth + // the data and filter out transients during a release. + static final int HISTORY_SIZE = 5; + int[] mHistoryDataStart = new int[MAX_POINTERS]; + int[] mHistoryDataEnd = new int[MAX_POINTERS]; + final int[] mHistoryData = new int[(MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS) + * HISTORY_SIZE]; + final int[] mAveragedData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS]; + + // Temporary data structures for doing the pointer ID mapping. + final int[] mLast2Next = new int[MAX_POINTERS]; + final int[] mNext2Last = new int[MAX_POINTERS]; + final long[] mNext2LastDistance = new long[MAX_POINTERS]; + + // Temporary data structure for generating the final motion data. + final float[] mReportData = new float[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS]; + + // This is not used here, but can be used by callers for state tracking. + int mAddingPointerOffset = 0; + final boolean[] mDown = new boolean[MAX_POINTERS]; MotionState(int mx, int my) { xPrecision = mx; yPrecision = my; xMoveScale = mx != 0 ? (1.0f/mx) : 1.0f; yMoveScale = my != 0 ? (1.0f/my) : 1.0f; + for (int i=0; i<MAX_POINTERS; i++) { + mPointerIds[i] = i; + } } - MotionEvent generateMotion(InputDevice device, long curTime, - boolean isAbs, Display display, int orientation, - int metaState) { - if (!changed) { - return null; + /** + * Special hack for devices that have bad screen data: if one of the + * points has moved more than a screen height from the last position, + * then drop it. + */ + void dropBadPoint(InputDevice dev) { + // We should always have absY, but let's be paranoid. + if (dev.absY == null) { + return; + } + // Don't do anything if a finger is going down or up. We run + // here before assigning pointer IDs, so there isn't a good + // way to do per-finger matching. + if (mNextNumPointers != mLastNumPointers) { + return; } - float scaledX = x; - float scaledY = y; - float temp; - float scaledPressure = 1.0f; - float scaledSize = 0; - int edgeFlags = 0; - if (isAbs) { - int w = display.getWidth()-1; - int h = display.getHeight()-1; - if (orientation == Surface.ROTATION_90 - || orientation == Surface.ROTATION_270) { - int tmp = w; - w = h; - h = tmp; - } - if (device.absX != null) { - scaledX = ((scaledX-device.absX.minValue) - / device.absX.range) * w; - } - if (device.absY != null) { - scaledY = ((scaledY-device.absY.minValue) - / device.absY.range) * h; - } - if (device.absPressure != null) { - scaledPressure = - ((pressure-device.absPressure.minValue) - / (float)device.absPressure.range); - } - if (device.absSize != null) { - scaledSize = - ((size-device.absSize.minValue) - / (float)device.absSize.range); + // We consider a single movement across more than a 7/16 of + // the long size of the screen to be bad. This was a magic value + // determined by looking at the maximum distance it is feasible + // to actually move in one sample. + final int maxDy = ((dev.absY.maxValue-dev.absY.minValue)*7)/16; + + // Look through all new points and see if any are farther than + // acceptable from all previous points. + for (int i=mNextNumPointers-1; i>=0; i--) { + final int ioff = i * MotionEvent.NUM_SAMPLE_DATA; + //final int x = mNextData[ioff + MotionEvent.SAMPLE_X]; + final int y = mNextData[ioff + MotionEvent.SAMPLE_Y]; + if (DEBUG_HACKS) Log.v("InputDevice", "Looking at next point #" + i + ": y=" + y); + boolean dropped = false; + if (!mDroppedBadPoint[i] && mLastNumPointers > 0) { + dropped = true; + int closestDy = -1; + int closestY = -1; + // We will drop this new point if it is sufficiently + // far away from -all- last points. + for (int j=mLastNumPointers-1; j>=0; j--) { + final int joff = j * MotionEvent.NUM_SAMPLE_DATA; + //int dx = x - mLastData[joff + MotionEvent.SAMPLE_X]; + int dy = y - mLastData[joff + MotionEvent.SAMPLE_Y]; + //if (dx < 0) dx = -dx; + if (dy < 0) dy = -dy; + if (DEBUG_HACKS) Log.v("InputDevice", "Comparing with last point #" + j + + ": y=" + mLastData[joff] + " dy=" + dy); + if (dy < maxDy) { + dropped = false; + break; + } else if (closestDy < 0 || dy < closestDy) { + closestDy = dy; + closestY = mLastData[joff + MotionEvent.SAMPLE_Y]; + } + } + if (dropped) { + dropped = true; + Log.i("InputDevice", "Dropping bad point #" + i + + ": newY=" + y + " closestDy=" + closestDy + + " maxDy=" + maxDy); + mNextData[ioff + MotionEvent.SAMPLE_Y] = closestY; + break; + } } - switch (orientation) { - case Surface.ROTATION_90: - temp = scaledX; - scaledX = scaledY; - scaledY = w-temp; + mDroppedBadPoint[i] = dropped; + } + } + + /** + * Special hack for devices that have bad screen data: aggregate and + * compute averages of the coordinate data, to reduce the amount of + * jitter seen by applications. + */ + int[] generateAveragedData(int upOrDownPointer, int lastNumPointers, + int nextNumPointers) { + final int numPointers = mLastNumPointers; + final int[] rawData = mLastData; + if (DEBUG_HACKS) Log.v("InputDevice", "lastNumPointers=" + lastNumPointers + + " nextNumPointers=" + nextNumPointers + + " numPointers=" + numPointers); + for (int i=0; i<numPointers; i++) { + final int ioff = i * MotionEvent.NUM_SAMPLE_DATA; + // We keep the average data in offsets based on the pointer + // ID, so we don't need to move it around as fingers are + // pressed and released. + final int p = mPointerIds[i]; + final int poff = p * MotionEvent.NUM_SAMPLE_DATA * HISTORY_SIZE; + if (i == upOrDownPointer && lastNumPointers != nextNumPointers) { + if (lastNumPointers < nextNumPointers) { + // This pointer is going down. Clear its history + // and start fresh. + if (DEBUG_HACKS) Log.v("InputDevice", "Pointer down @ index " + + upOrDownPointer + " id " + mPointerIds[i]); + mHistoryDataStart[i] = 0; + mHistoryDataEnd[i] = 0; + System.arraycopy(rawData, ioff, mHistoryData, poff, + MotionEvent.NUM_SAMPLE_DATA); + System.arraycopy(rawData, ioff, mAveragedData, ioff, + MotionEvent.NUM_SAMPLE_DATA); + continue; + } else { + // The pointer is going up. Just fall through to + // recompute the last averaged point (and don't add + // it as a new point to include in the average). + if (DEBUG_HACKS) Log.v("InputDevice", "Pointer up @ index " + + upOrDownPointer + " id " + mPointerIds[i]); + } + } else { + int end = mHistoryDataEnd[i]; + int eoff = poff + (end*MotionEvent.NUM_SAMPLE_DATA); + int oldX = mHistoryData[eoff + MotionEvent.SAMPLE_X]; + int oldY = mHistoryData[eoff + MotionEvent.SAMPLE_Y]; + int newX = rawData[ioff + MotionEvent.SAMPLE_X]; + int newY = rawData[ioff + MotionEvent.SAMPLE_Y]; + int dx = newX-oldX; + int dy = newY-oldY; + int delta = dx*dx + dy*dy; + if (DEBUG_HACKS) Log.v("InputDevice", "Delta from last: " + delta); + if (delta >= (75*75)) { + // Magic number, if moving farther than this, turn + // off filtering to avoid lag in response. + mHistoryDataStart[i] = 0; + mHistoryDataEnd[i] = 0; + System.arraycopy(rawData, ioff, mHistoryData, poff, + MotionEvent.NUM_SAMPLE_DATA); + System.arraycopy(rawData, ioff, mAveragedData, ioff, + MotionEvent.NUM_SAMPLE_DATA); + continue; + } else { + end++; + if (end >= HISTORY_SIZE) { + end -= HISTORY_SIZE; + } + mHistoryDataEnd[i] = end; + int noff = poff + (end*MotionEvent.NUM_SAMPLE_DATA); + mHistoryData[noff + MotionEvent.SAMPLE_X] = newX; + mHistoryData[noff + MotionEvent.SAMPLE_Y] = newY; + mHistoryData[noff + MotionEvent.SAMPLE_PRESSURE] + = rawData[ioff + MotionEvent.SAMPLE_PRESSURE]; + int start = mHistoryDataStart[i]; + if (end == start) { + start++; + if (start >= HISTORY_SIZE) { + start -= HISTORY_SIZE; + } + mHistoryDataStart[i] = start; + } + } + } + + // Now compute the average. + int start = mHistoryDataStart[i]; + int end = mHistoryDataEnd[i]; + int x=0, y=0; + int totalPressure = 0; + while (start != end) { + int soff = poff + (start*MotionEvent.NUM_SAMPLE_DATA); + int pressure = mHistoryData[soff + MotionEvent.SAMPLE_PRESSURE]; + if (pressure <= 0) pressure = 1; + x += mHistoryData[soff + MotionEvent.SAMPLE_X] * pressure; + y += mHistoryData[soff + MotionEvent.SAMPLE_Y] * pressure; + totalPressure += pressure; + start++; + if (start >= HISTORY_SIZE) start = 0; + } + int eoff = poff + (end*MotionEvent.NUM_SAMPLE_DATA); + int pressure = mHistoryData[eoff + MotionEvent.SAMPLE_PRESSURE]; + if (pressure <= 0) pressure = 1; + x += mHistoryData[eoff + MotionEvent.SAMPLE_X] * pressure; + y += mHistoryData[eoff + MotionEvent.SAMPLE_Y] * pressure; + totalPressure += pressure; + x /= totalPressure; + y /= totalPressure; + if (DEBUG_HACKS) Log.v("InputDevice", "Averaging " + totalPressure + + " weight: (" + x + "," + y + ")"); + mAveragedData[ioff + MotionEvent.SAMPLE_X] = x; + mAveragedData[ioff + MotionEvent.SAMPLE_Y] = y; + } + return mAveragedData; + } + + private boolean assignPointer(int nextIndex, boolean allowOverlap) { + final int lastNumPointers = mLastNumPointers; + final int[] next2Last = mNext2Last; + final long[] next2LastDistance = mNext2LastDistance; + final int[] last2Next = mLast2Next; + final int[] lastData = mLastData; + final int[] nextData = mNextData; + final int id = nextIndex * MotionEvent.NUM_SAMPLE_DATA; + + if (DEBUG_POINTERS) Log.v("InputDevice", "assignPointer: nextIndex=" + + nextIndex + " dataOff=" + id); + final int x1 = nextData[id + MotionEvent.SAMPLE_X]; + final int y1 = nextData[id + MotionEvent.SAMPLE_Y]; + + long bestDistance = -1; + int bestIndex = -1; + for (int j=0; j<lastNumPointers; j++) { + if (!allowOverlap && last2Next[j] < 0) { + continue; + } + final int jd = j * MotionEvent.NUM_SAMPLE_DATA; + final int xd = lastData[jd + MotionEvent.SAMPLE_X] - x1; + final int yd = lastData[jd + MotionEvent.SAMPLE_Y] - y1; + final long distance = xd*(long)xd + yd*(long)yd; + if (j == 0 || distance < bestDistance) { + bestDistance = distance; + bestIndex = j; + } + } + + if (DEBUG_POINTERS) Log.v("InputDevice", "New index " + nextIndex + + " best old index=" + bestIndex + " (distance=" + + bestDistance + ")"); + next2Last[nextIndex] = bestIndex; + next2LastDistance[nextIndex] = bestDistance; + + if (bestIndex < 0) { + return true; + } + + if (last2Next[bestIndex] == -1) { + last2Next[bestIndex] = nextIndex; + return false; + } + + if (DEBUG_POINTERS) Log.v("InputDevice", "Old index " + bestIndex + + " has multiple best new pointers!"); + + last2Next[bestIndex] = -2; + return true; + } + + private int updatePointerIdentifiers() { + final int[] lastData = mLastData; + final int[] nextData = mNextData; + final int nextNumPointers = mNextNumPointers; + final int lastNumPointers = mLastNumPointers; + + if (nextNumPointers == 1 && lastNumPointers == 1) { + System.arraycopy(nextData, 0, lastData, 0, + MotionEvent.NUM_SAMPLE_DATA); + return -1; + } + + // Clear our old state. + final int[] last2Next = mLast2Next; + for (int i=0; i<lastNumPointers; i++) { + last2Next[i] = -1; + } + + if (DEBUG_POINTERS) Log.v("InputDevice", + "Update pointers: lastNumPointers=" + lastNumPointers + + " nextNumPointers=" + nextNumPointers); + + // Figure out the closes new points to the previous points. + final int[] next2Last = mNext2Last; + final long[] next2LastDistance = mNext2LastDistance; + boolean conflicts = false; + for (int i=0; i<nextNumPointers; i++) { + conflicts |= assignPointer(i, true); + } + + // Resolve ambiguities in pointer mappings, when two or more + // new pointer locations find their best previous location is + // the same. + if (conflicts) { + if (DEBUG_POINTERS) Log.v("InputDevice", "Resolving conflicts"); + + for (int i=0; i<lastNumPointers; i++) { + if (last2Next[i] != -2) { + continue; + } + + // Note that this algorithm is far from perfect. Ideally + // we should do something like the one described at + // http://portal.acm.org/citation.cfm?id=997856 + + if (DEBUG_POINTERS) Log.v("InputDevice", + "Resolving last index #" + i); + + int numFound; + do { + numFound = 0; + long worstDistance = 0; + int worstJ = -1; + for (int j=0; j<nextNumPointers; j++) { + if (next2Last[j] != i) { + continue; + } + numFound++; + if (worstDistance < next2LastDistance[j]) { + worstDistance = next2LastDistance[j]; + worstJ = j; + } + } + + if (worstJ >= 0) { + if (DEBUG_POINTERS) Log.v("InputDevice", + "Worst new pointer: " + worstJ + + " (distance=" + worstDistance + ")"); + if (assignPointer(worstJ, false)) { + // In this case there is no last pointer + // remaining for this new one! + next2Last[worstJ] = -1; + } + } + } while (numFound > 2); + } + } + + int retIndex = -1; + + if (lastNumPointers < nextNumPointers) { + // We have one or more new pointers that are down. Create a + // new pointer identifier for one of them. + if (DEBUG_POINTERS) Log.v("InputDevice", "Adding new pointer"); + int nextId = 0; + int i=0; + while (i < lastNumPointers) { + if (mPointerIds[i] > nextId) { + // Found a hole, insert the pointer here. + if (DEBUG_POINTERS) Log.v("InputDevice", + "Inserting new pointer at hole " + i); + System.arraycopy(mPointerIds, i, mPointerIds, + i+1, lastNumPointers-i); + System.arraycopy(lastData, i*MotionEvent.NUM_SAMPLE_DATA, + lastData, (i+1)*MotionEvent.NUM_SAMPLE_DATA, + (lastNumPointers-i)*MotionEvent.NUM_SAMPLE_DATA); break; - case Surface.ROTATION_180: - scaledX = w-scaledX; - scaledY = h-scaledY; + } + i++; + nextId++; + } + + if (DEBUG_POINTERS) Log.v("InputDevice", + "New pointer id " + nextId + " at index " + i); + + mLastNumPointers++; + retIndex = i; + mPointerIds[i] = nextId; + + // And assign this identifier to the first new pointer. + for (int j=0; j<nextNumPointers; j++) { + if (next2Last[j] < 0) { + if (DEBUG_POINTERS) Log.v("InputDevice", + "Assigning new id to new pointer index " + j); + next2Last[j] = i; break; - case Surface.ROTATION_270: - temp = scaledX; - scaledX = h-scaledY; - scaledY = temp; + } + } + } + + // Propagate all of the current data into the appropriate + // location in the old data to match the pointer ID that was + // assigned to it. + for (int i=0; i<nextNumPointers; i++) { + int lastIndex = next2Last[i]; + if (lastIndex >= 0) { + if (DEBUG_POINTERS) Log.v("InputDevice", + "Copying next pointer index " + i + + " to last index " + lastIndex); + System.arraycopy(nextData, i*MotionEvent.NUM_SAMPLE_DATA, + lastData, lastIndex*MotionEvent.NUM_SAMPLE_DATA, + MotionEvent.NUM_SAMPLE_DATA); + } + } + + if (lastNumPointers > nextNumPointers) { + // One or more pointers has gone up. Find the first one, + // and adjust accordingly. + if (DEBUG_POINTERS) Log.v("InputDevice", "Removing old pointer"); + for (int i=0; i<lastNumPointers; i++) { + if (last2Next[i] == -1) { + if (DEBUG_POINTERS) Log.v("InputDevice", + "Removing old pointer at index " + i); + retIndex = i; break; + } } - - if (scaledX == 0) { - edgeFlags += MotionEvent.EDGE_LEFT; - } else if (scaledX == display.getWidth() - 1.0f) { - edgeFlags += MotionEvent.EDGE_RIGHT; + } + + return retIndex; + } + + void removeOldPointer(int index) { + final int lastNumPointers = mLastNumPointers; + if (index >= 0 && index < lastNumPointers) { + System.arraycopy(mPointerIds, index+1, mPointerIds, + index, lastNumPointers-index-1); + System.arraycopy(mLastData, (index+1)*MotionEvent.NUM_SAMPLE_DATA, + mLastData, (index)*MotionEvent.NUM_SAMPLE_DATA, + (lastNumPointers-index-1)*MotionEvent.NUM_SAMPLE_DATA); + mLastNumPointers--; + } + } + + MotionEvent generateAbsMotion(InputDevice device, long curTime, + long curTimeNano, Display display, int orientation, + int metaState) { + + if (mSkipLastPointers) { + mSkipLastPointers = false; + mLastNumPointers = 0; + } + + if (mNextNumPointers <= 0 && mLastNumPointers <= 0) { + return null; + } + + final int lastNumPointers = mLastNumPointers; + final int nextNumPointers = mNextNumPointers; + if (mNextNumPointers > MAX_POINTERS) { + Log.w("InputDevice", "Number of pointers " + mNextNumPointers + + " exceeded maximum of " + MAX_POINTERS); + mNextNumPointers = MAX_POINTERS; + } + + int upOrDownPointer = updatePointerIdentifiers(); + + final float[] reportData = mReportData; + final int[] rawData; + if (KeyInputQueue.BAD_TOUCH_HACK) { + rawData = generateAveragedData(upOrDownPointer, lastNumPointers, + nextNumPointers); + } else { + rawData = mLastData; + } + + final int numPointers = mLastNumPointers; + + if (DEBUG_POINTERS) Log.v("InputDevice", "Processing " + + numPointers + " pointers (going from " + lastNumPointers + + " to " + nextNumPointers + ")"); + + for (int i=0; i<numPointers; i++) { + final int pos = i * MotionEvent.NUM_SAMPLE_DATA; + reportData[pos + MotionEvent.SAMPLE_X] = rawData[pos + MotionEvent.SAMPLE_X]; + reportData[pos + MotionEvent.SAMPLE_Y] = rawData[pos + MotionEvent.SAMPLE_Y]; + reportData[pos + MotionEvent.SAMPLE_PRESSURE] = rawData[pos + MotionEvent.SAMPLE_PRESSURE]; + reportData[pos + MotionEvent.SAMPLE_SIZE] = rawData[pos + MotionEvent.SAMPLE_SIZE]; + } + + int action; + int edgeFlags = 0; + if (nextNumPointers != lastNumPointers) { + if (nextNumPointers > lastNumPointers) { + if (lastNumPointers == 0) { + action = MotionEvent.ACTION_DOWN; + mDownTime = curTime; + } else { + action = MotionEvent.ACTION_POINTER_DOWN + | (upOrDownPointer << MotionEvent.ACTION_POINTER_ID_SHIFT); + } + } else { + if (numPointers == 1) { + action = MotionEvent.ACTION_UP; + } else { + action = MotionEvent.ACTION_POINTER_UP + | (upOrDownPointer << MotionEvent.ACTION_POINTER_ID_SHIFT); + } } - - if (scaledY == 0) { - edgeFlags += MotionEvent.EDGE_TOP; - } else if (scaledY == display.getHeight() - 1.0f) { - edgeFlags += MotionEvent.EDGE_BOTTOM; + currentMove = null; + } else { + action = MotionEvent.ACTION_MOVE; + } + + final int dispW = display.getWidth()-1; + final int dispH = display.getHeight()-1; + int w = dispW; + int h = dispH; + if (orientation == Surface.ROTATION_90 + || orientation == Surface.ROTATION_270) { + int tmp = w; + w = h; + h = tmp; + } + + final AbsoluteInfo absX = device.absX; + final AbsoluteInfo absY = device.absY; + final AbsoluteInfo absPressure = device.absPressure; + final AbsoluteInfo absSize = device.absSize; + for (int i=0; i<numPointers; i++) { + final int j = i * MotionEvent.NUM_SAMPLE_DATA; + + if (absX != null) { + reportData[j + MotionEvent.SAMPLE_X] = + ((reportData[j + MotionEvent.SAMPLE_X]-absX.minValue) + / absX.range) * w; + } + if (absY != null) { + reportData[j + MotionEvent.SAMPLE_Y] = + ((reportData[j + MotionEvent.SAMPLE_Y]-absY.minValue) + / absY.range) * h; + } + if (absPressure != null) { + reportData[j + MotionEvent.SAMPLE_PRESSURE] = + ((reportData[j + MotionEvent.SAMPLE_PRESSURE]-absPressure.minValue) + / (float)absPressure.range); + } + if (absSize != null) { + reportData[j + MotionEvent.SAMPLE_SIZE] = + ((reportData[j + MotionEvent.SAMPLE_SIZE]-absSize.minValue) + / (float)absSize.range); } - } else { - scaledX *= xMoveScale; - scaledY *= yMoveScale; switch (orientation) { - case Surface.ROTATION_90: - temp = scaledX; - scaledX = scaledY; - scaledY = -temp; + case Surface.ROTATION_90: { + final float temp = reportData[j + MotionEvent.SAMPLE_X]; + reportData[j + MotionEvent.SAMPLE_X] = reportData[j + MotionEvent.SAMPLE_Y]; + reportData[j + MotionEvent.SAMPLE_Y] = w-temp; break; - case Surface.ROTATION_180: - scaledX = -scaledX; - scaledY = -scaledY; + } + case Surface.ROTATION_180: { + reportData[j + MotionEvent.SAMPLE_X] = w-reportData[j + MotionEvent.SAMPLE_X]; + reportData[j + MotionEvent.SAMPLE_Y] = h-reportData[j + MotionEvent.SAMPLE_Y]; break; - case Surface.ROTATION_270: - temp = scaledX; - scaledX = -scaledY; - scaledY = temp; + } + case Surface.ROTATION_270: { + final float temp = reportData[j + MotionEvent.SAMPLE_X]; + reportData[j + MotionEvent.SAMPLE_X] = h-reportData[j + MotionEvent.SAMPLE_Y]; + reportData[j + MotionEvent.SAMPLE_Y] = temp; break; + } + } + } + + // We only consider the first pointer when computing the edge + // flags, since they are global to the event. + if (action == MotionEvent.ACTION_DOWN) { + if (reportData[MotionEvent.SAMPLE_X] <= 0) { + edgeFlags |= MotionEvent.EDGE_LEFT; + } else if (reportData[MotionEvent.SAMPLE_X] >= dispW) { + edgeFlags |= MotionEvent.EDGE_RIGHT; + } + if (reportData[MotionEvent.SAMPLE_Y] <= 0) { + edgeFlags |= MotionEvent.EDGE_TOP; + } else if (reportData[MotionEvent.SAMPLE_Y] >= dispH) { + edgeFlags |= MotionEvent.EDGE_BOTTOM; } } - changed = false; - if (down != lastDown) { - int action; - lastDown = down; - if (down) { + if (currentMove != null) { + if (false) Log.i("InputDevice", "Adding batch x=" + + reportData[MotionEvent.SAMPLE_X] + + " y=" + reportData[MotionEvent.SAMPLE_Y] + + " to " + currentMove); + currentMove.addBatch(curTime, reportData, metaState); + if (WindowManagerPolicy.WATCH_POINTER) { + Log.i("KeyInputQueue", "Updating: " + currentMove); + } + return null; + } + + MotionEvent me = MotionEvent.obtainNano(mDownTime, curTime, + curTimeNano, action, numPointers, mPointerIds, reportData, + metaState, xPrecision, yPrecision, device.id, edgeFlags); + if (action == MotionEvent.ACTION_MOVE) { + currentMove = me; + } + + if (nextNumPointers < lastNumPointers) { + removeOldPointer(upOrDownPointer); + } + + return me; + } + + boolean hasMore() { + return mLastNumPointers != mNextNumPointers; + } + + void finish() { + mNextNumPointers = mAddingPointerOffset = 0; + mNextData[MotionEvent.SAMPLE_PRESSURE] = 0; + } + + MotionEvent generateRelMotion(InputDevice device, long curTime, + long curTimeNano, int orientation, int metaState) { + + final float[] scaled = mReportData; + + // For now we only support 1 pointer with relative motions. + scaled[MotionEvent.SAMPLE_X] = mNextData[MotionEvent.SAMPLE_X]; + scaled[MotionEvent.SAMPLE_Y] = mNextData[MotionEvent.SAMPLE_Y]; + scaled[MotionEvent.SAMPLE_PRESSURE] = 1.0f; + scaled[MotionEvent.SAMPLE_SIZE] = 0; + int edgeFlags = 0; + + int action; + if (mNextNumPointers != mLastNumPointers) { + mNextData[MotionEvent.SAMPLE_X] = + mNextData[MotionEvent.SAMPLE_Y] = 0; + if (mNextNumPointers > 0 && mLastNumPointers == 0) { action = MotionEvent.ACTION_DOWN; - downTime = curTime; - } else { + mDownTime = curTime; + } else if (mNextNumPointers == 0) { action = MotionEvent.ACTION_UP; + } else { + action = MotionEvent.ACTION_MOVE; } + mLastNumPointers = mNextNumPointers; currentMove = null; - if (!isAbs) { - x = y = 0; - } - return MotionEvent.obtain(downTime, curTime, action, - scaledX, scaledY, scaledPressure, scaledSize, metaState, - xPrecision, yPrecision, device.id, edgeFlags); } else { - if (currentMove != null) { - if (false) Log.i("InputDevice", "Adding batch x=" + scaledX - + " y=" + scaledY + " to " + currentMove); - currentMove.addBatch(curTime, scaledX, scaledY, - scaledPressure, scaledSize, metaState); - if (WindowManagerPolicy.WATCH_POINTER) { - Log.i("KeyInputQueue", "Updating: " + currentMove); - } - return null; + action = MotionEvent.ACTION_MOVE; + } + + scaled[MotionEvent.SAMPLE_X] *= xMoveScale; + scaled[MotionEvent.SAMPLE_Y] *= yMoveScale; + switch (orientation) { + case Surface.ROTATION_90: { + final float temp = scaled[MotionEvent.SAMPLE_X]; + scaled[MotionEvent.SAMPLE_X] = scaled[MotionEvent.SAMPLE_Y]; + scaled[MotionEvent.SAMPLE_Y] = -temp; + break; + } + case Surface.ROTATION_180: { + scaled[MotionEvent.SAMPLE_X] = -scaled[MotionEvent.SAMPLE_X]; + scaled[MotionEvent.SAMPLE_Y] = -scaled[MotionEvent.SAMPLE_Y]; + break; + } + case Surface.ROTATION_270: { + final float temp = scaled[MotionEvent.SAMPLE_X]; + scaled[MotionEvent.SAMPLE_X] = -scaled[MotionEvent.SAMPLE_Y]; + scaled[MotionEvent.SAMPLE_Y] = temp; + break; } - MotionEvent me = MotionEvent.obtain(downTime, curTime, - MotionEvent.ACTION_MOVE, scaledX, scaledY, - scaledPressure, scaledSize, metaState, - xPrecision, yPrecision, device.id, edgeFlags); + } + + if (currentMove != null) { + if (false) Log.i("InputDevice", "Adding batch x=" + + scaled[MotionEvent.SAMPLE_X] + + " y=" + scaled[MotionEvent.SAMPLE_Y] + + " to " + currentMove); + currentMove.addBatch(curTime, scaled, metaState); + if (WindowManagerPolicy.WATCH_POINTER) { + Log.i("KeyInputQueue", "Updating: " + currentMove); + } + return null; + } + + MotionEvent me = MotionEvent.obtainNano(mDownTime, curTime, + curTimeNano, action, 1, mPointerIds, scaled, metaState, + xPrecision, yPrecision, device.id, edgeFlags); + if (action == MotionEvent.ACTION_MOVE) { currentMove = me; - return me; } + return me; } } diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 4198154..e2e0ba9 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -32,6 +32,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.app.ActivityManagerNative; import android.app.AlertDialog; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -40,6 +41,7 @@ import android.content.IntentFilter; import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; @@ -180,6 +182,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub = new HashMap<IBinder, ClientState>(); /** + * Set once the system is ready to run third party code. + */ + boolean mSystemReady; + + /** * Id of the currently selected input method. */ String mCurMethodId; @@ -360,16 +367,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Uh oh, current input method is no longer around! // Pick another one... Log.i(TAG, "Current input method removed: " + curInputMethodId); - List<InputMethodInfo> enabled = getEnabledInputMethodListLocked(); - if (enabled != null && enabled.size() > 0) { - changed = true; - curIm = enabled.get(0); - curInputMethodId = curIm.getId(); - Log.i(TAG, "Switching to: " + curInputMethodId); - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, - curInputMethodId); - } else if (curIm != null) { + if (!chooseNewDefaultIME()) { changed = true; curIm = null; curInputMethodId = ""; @@ -383,16 +381,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } else if (curIm == null) { // We currently don't have a default input method... is // one now available? - List<InputMethodInfo> enabled = getEnabledInputMethodListLocked(); - if (enabled != null && enabled.size() > 0) { - changed = true; - curIm = enabled.get(0); - curInputMethodId = curIm.getId(); - Log.i(TAG, "New default input method: " + curInputMethodId); - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, - curInputMethodId); - } + changed = chooseNewDefaultIME(); } if (changed) { @@ -508,6 +497,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } public void systemReady() { + synchronized (mMethodMap) { + if (!mSystemReady) { + mSystemReady = true; + try { + startInputInnerLocked(); + } catch (RuntimeException e) { + Log.w(TAG, "Unexpected exception", e); + } + } + } } public List<InputMethodInfo> getInputMethodList() { @@ -727,6 +726,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + return startInputInnerLocked(); + } + + InputBindResult startInputInnerLocked() { + if (mCurMethodId == null) { + return mNoBinding; + } + + if (!mSystemReady) { + // If the system is not yet ready, we shouldn't be running third + // party code. + return new InputBindResult(null, mCurMethodId, mCurSeq); + } + InputMethodInfo info = mMethodMap.get(mCurMethodId); if (info == null) { throw new IllegalArgumentException("Unknown id: " + mCurMethodId); @@ -736,6 +749,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE); mCurIntent.setComponent(info.getComponent()); + mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, + com.android.internal.R.string.input_method_binding_label); + mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( + mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0)); if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE)) { mLastBindTime = SystemClock.uptimeMillis(); mHaveConnection = true; @@ -777,17 +794,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub synchronized (mMethodMap) { if (mCurIntent != null && name.equals(mCurIntent.getComponent())) { mCurMethod = IInputMethod.Stub.asInterface(service); + if (mCurToken == null) { + Log.w(TAG, "Service connected without a token!"); + unbindCurrentMethodLocked(false); + return; + } + if (DEBUG) Log.v(TAG, "Initiating attach with token: " + mCurToken); + executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( + MSG_ATTACH_TOKEN, mCurMethod, mCurToken)); if (mCurClient != null) { - if (DEBUG) Log.v(TAG, "Initiating attach with token: " + mCurToken); + if (DEBUG) Log.v(TAG, "Creating first session while with client " + + mCurClient); executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( - MSG_ATTACH_TOKEN, mCurMethod, mCurToken)); - if (mCurClient != null) { - if (DEBUG) Log.v(TAG, "Creating first session while with client " - + mCurClient); - executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( - MSG_CREATE_SESSION, mCurMethod, - new MethodCallback(mCurMethod))); - } + MSG_CREATE_SESSION, mCurMethod, + new MethodCallback(mCurMethod))); } } } @@ -977,6 +997,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mShowExplicitlyRequested = true; mShowForced = true; } + + if (!mSystemReady) { + return false; + } + boolean res = false; if (mCurMethod != null) { executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO( @@ -1327,6 +1352,23 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return false; } + private boolean isSystemIme(InputMethodInfo inputMethod) { + return (inputMethod.getServiceInfo().applicationInfo.flags + & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + private boolean chooseNewDefaultIME() { + List<InputMethodInfo> enabled = getEnabledInputMethodListLocked(); + if (enabled != null && enabled.size() > 0) { + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.DEFAULT_INPUT_METHOD, + enabled.get(0).getId()); + return true; + } + + return false; + } + void buildInputMethodListLocked(ArrayList<InputMethodInfo> list, HashMap<String, InputMethodInfo> map) { list.clear(); @@ -1357,6 +1399,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub list.add(p); map.put(p.getId(), p); + // System IMEs are enabled by default + if (isSystemIme(p)) { + setInputMethodEnabled(p.getId(), true); + } + if (DEBUG) { Log.d(TAG, "Found a third-party input method " + p); } @@ -1367,6 +1414,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Log.w(TAG, "Unable to load input method " + compName, e); } } + + String defaultIme = Settings.Secure.getString(mContext + .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); + if (!map.containsKey(defaultIme)) { + if (chooseNewDefaultIME()) { + updateFromSettingsLocked(); + } + } } // ---------------------------------------------------------------------- @@ -1612,7 +1667,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + " mShowExplicitlyRequested=" + mShowExplicitlyRequested + " mShowForced=" + mShowForced + " mInputShown=" + mInputShown); - p.println(" mScreenOn=" + mScreenOn); + p.println(" mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn); } if (client != null) { diff --git a/services/java/com/android/server/JournaledFile.java b/services/java/com/android/server/JournaledFile.java new file mode 100644 index 0000000..3d1f52d --- /dev/null +++ b/services/java/com/android/server/JournaledFile.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import java.io.File; +import java.io.IOException; + +public class JournaledFile { + File mReal; + File mTemp; + boolean mWriting; + + public JournaledFile(File real, File temp) { + mReal = real; + mTemp = temp; + } + + /** Returns the file for you to read. + * @more + * Prefers the real file. If it doesn't exist, uses the temp one, and then copies + * it to the real one. If there is both a real file and a temp one, assumes that the + * temp one isn't fully written and deletes it. + */ + public File chooseForRead() { + File result; + if (mReal.exists()) { + result = mReal; + if (mTemp.exists()) { + mTemp.delete(); + } + } else if (mTemp.exists()) { + result = mTemp; + mTemp.renameTo(mReal); + } else { + return mReal; + } + return result; + } + + /** + * Returns a file for you to write. + * @more + * If a write is already happening, throws. In other words, you must provide your + * own locking. + * <p> + * Call {@link #commit} to commit the changes, or {@link #rollback} to forget the changes. + */ + public File chooseForWrite() { + if (mWriting) { + throw new IllegalStateException("uncommitted write already in progress"); + } + if (!mReal.exists()) { + // If the real one doesn't exist, it's either because this is the first time + // or because something went wrong while copying them. In this case, we can't + // trust anything that's in temp. In order to have the chooseForRead code not + // use the temporary one until it's fully written, create an empty file + // for real, which will we'll shortly delete. + try { + mReal.createNewFile(); + } catch (IOException e) { + // Ignore + } + } + + if (mTemp.exists()) { + mTemp.delete(); + } + mWriting = true; + return mTemp; + } + + /** + * Commit changes. + */ + public void commit() { + if (!mWriting) { + throw new IllegalStateException("no file to commit"); + } + mWriting = false; + mTemp.renameTo(mReal); + } + + /** + * Roll back changes. + */ + public void rollback() { + if (!mWriting) { + throw new IllegalStateException("no file to roll back"); + } + mWriting = false; + mTemp.delete(); + } +} diff --git a/services/java/com/android/server/KeyInputQueue.java b/services/java/com/android/server/KeyInputQueue.java index 411cd6b..d68ccfa 100644 --- a/services/java/com/android/server/KeyInputQueue.java +++ b/services/java/com/android/server/KeyInputQueue.java @@ -18,10 +18,13 @@ package com.android.server; import android.content.Context; import android.content.res.Configuration; -import android.os.SystemClock; +import android.os.Environment; +import android.os.LatencyTimer; import android.os.PowerManager; +import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; +import android.util.Xml; import android.view.Display; import android.view.KeyEvent; import android.view.MotionEvent; @@ -29,10 +32,38 @@ import android.view.RawInputEvent; import android.view.Surface; import android.view.WindowManagerPolicy; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; + public abstract class KeyInputQueue { static final String TAG = "KeyInputQueue"; - SparseArray<InputDevice> mDevices = new SparseArray<InputDevice>(); + static final boolean DEBUG = false; + static final boolean DEBUG_VIRTUAL_KEYS = false; + static final boolean DEBUG_POINTERS = false; + + /** + * Turn on some hacks we have to improve the touch interaction with a + * certain device whose screen currently is not all that good. + */ + static final boolean BAD_TOUCH_HACK = true; + + private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; + + final SparseArray<InputDevice> mDevices = new SparseArray<InputDevice>(); + final SparseArray<InputDevice> mIgnoredDevices = new SparseArray<InputDevice>(); + final ArrayList<VirtualKey> mVirtualKeys = new ArrayList<VirtualKey>(); + final HapticFeedbackCallback mHapticFeedbackCallback; int mGlobalMetaState = 0; boolean mHaveGlobalMetaState = false; @@ -43,10 +74,14 @@ public abstract class KeyInputQueue { int mCacheCount; Display mDisplay = null; + int mDisplayWidth; + int mDisplayHeight; int mOrientation = Surface.ROTATION_0; int[] mKeyRotationMap = null; + VirtualKey mPressedVirtualKey = null; + PowerManager.WakeLock mWakeLock; static final int[] KEY_90_MAP = new int[] { @@ -73,14 +108,21 @@ public abstract class KeyInputQueue { public static final int FILTER_REMOVE = 0; public static final int FILTER_KEEP = 1; public static final int FILTER_ABORT = -1; - + + private static final boolean MEASURE_LATENCY = false; + private LatencyTimer lt; + public interface FilterCallback { int filterEvent(QueuedEvent ev); } + public interface HapticFeedbackCallback { + void virtualKeyFeedback(KeyEvent event); + } + static class QueuedEvent { InputDevice inputDevice; - long when; + long whenNano; int flags; // From the raw event int classType; // One of the class constants in InputEvent Object event; @@ -88,7 +130,7 @@ public abstract class KeyInputQueue { void copyFrom(QueuedEvent that) { this.inputDevice = that.inputDevice; - this.when = that.when; + this.whenNano = that.whenNano; this.flags = that.flags; this.classType = that.classType; this.event = that.event; @@ -106,7 +148,144 @@ public abstract class KeyInputQueue { QueuedEvent next; } - KeyInputQueue(Context context) { + /** + * A key that exists as a part of the touch-screen, outside of the normal + * display area of the screen. + */ + static class VirtualKey { + int scancode; + int centerx; + int centery; + int width; + int height; + + int hitLeft; + int hitTop; + int hitRight; + int hitBottom; + + InputDevice lastDevice; + int lastKeycode; + + boolean checkHit(int x, int y) { + return (x >= hitLeft && x <= hitRight + && y >= hitTop && y <= hitBottom); + } + + void computeHitRect(InputDevice dev, int dw, int dh) { + if (dev == lastDevice) { + return; + } + + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "computeHitRect for " + scancode + + ": dev=" + dev + " absX=" + dev.absX + " absY=" + dev.absY); + + lastDevice = dev; + + int minx = dev.absX.minValue; + int maxx = dev.absX.maxValue; + + int halfw = width/2; + int left = centerx - halfw; + int right = centerx + halfw; + hitLeft = minx + ((left*maxx-minx)/dw); + hitRight = minx + ((right*maxx-minx)/dw); + + int miny = dev.absY.minValue; + int maxy = dev.absY.maxValue; + + int halfh = height/2; + int top = centery - halfh; + int bottom = centery + halfh; + hitTop = miny + ((top*maxy-miny)/dh); + hitBottom = miny + ((bottom*maxy-miny)/dh); + } + } + + private void readVirtualKeys(String deviceName) { + 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) Log.v(TAG, "***** VIRTUAL KEYS: " + it); + final int N = it.length-6; + for (int i=0; i<=N; i+=6) { + if (!"0x01".equals(it[i])) { + Log.w(TAG, "Unknown virtual key type at elem #" + i + + ": " + it[i]); + continue; + } + try { + VirtualKey sb = new VirtualKey(); + sb.scancode = Integer.parseInt(it[i+1]); + sb.centerx = Integer.parseInt(it[i+2]); + sb.centery = Integer.parseInt(it[i+3]); + sb.width = Integer.parseInt(it[i+4]); + sb.height = Integer.parseInt(it[i+5]); + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Virtual key " + + sb.scancode + ": center=" + sb.centerx + "," + + sb.centery + " size=" + sb.width + "x" + + sb.height); + mVirtualKeys.add(sb); + } catch (NumberFormatException e) { + Log.w(TAG, "Bad number at region " + i + " in: " + + str, e); + } + } + } + br.close(); + } catch (FileNotFoundException e) { + Log.i(TAG, "No virtual keys found"); + } catch (IOException e) { + Log.w(TAG, "Error reading virtual keys", e); + } + } + + private void readExcludedDevices() { + // Read partner-provided list of excluded input devices + XmlPullParser parser = null; + // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system". + File confFile = new File(Environment.getRootDirectory(), EXCLUDED_DEVICES_PATH); + FileReader confreader = null; + try { + confreader = new FileReader(confFile); + parser = Xml.newPullParser(); + parser.setInput(confreader); + XmlUtils.beginDocument(parser, "devices"); + + while (true) { + XmlUtils.nextElement(parser); + if (!"device".equals(parser.getName())) { + break; + } + String name = parser.getAttributeValue(null, "name"); + if (name != null) { + if (DEBUG) Log.v(TAG, "addExcludedDevice " + name); + addExcludedDevice(name); + } + } + } catch (FileNotFoundException e) { + // It's ok if the file does not exist. + } catch (Exception e) { + Log.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e); + } finally { + try { if (confreader != null) confreader.close(); } catch (IOException e) { } + } + } + + KeyInputQueue(Context context, HapticFeedbackCallback hapticFeedbackCallback) { + if (MEASURE_LATENCY) { + lt = new LatencyTimer(100, 1000); + } + + mHapticFeedbackCallback = hapticFeedbackCallback; + + readExcludedDevices(); + PowerManager pm = (PowerManager)context.getSystemService( Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, @@ -123,6 +302,12 @@ public abstract class KeyInputQueue { public void setDisplay(Display display) { mDisplay = display; + + // We assume at this point that the display dimensions reflect the + // natural, unrotated display. We will perform hit tests for soft + // buttons based on that display. + mDisplayWidth = display.getWidth(); + mDisplayHeight = display.getHeight(); } public void getInputConfiguration(Configuration config) { @@ -149,22 +334,76 @@ public abstract class KeyInputQueue { config.navigation = Configuration.NAVIGATION_TRACKBALL; //Log.i("foo", "***** HAVE TRACKBALL!"); + } else if ((d.classes&RawInputEvent.CLASS_DPAD) != 0) { + config.navigation + = Configuration.NAVIGATION_DPAD; + //Log.i("foo", "***** HAVE DPAD!"); } } } } } + public int getScancodeState(int code) { + synchronized (mFirst) { + VirtualKey vk = mPressedVirtualKey; + if (vk != null) { + if (vk.scancode == code) { + return 2; + } + } + return nativeGetScancodeState(code); + } + } + + public int getScancodeState(int deviceId, int code) { + synchronized (mFirst) { + VirtualKey vk = mPressedVirtualKey; + if (vk != null) { + if (vk.scancode == code) { + return 2; + } + } + return nativeGetScancodeState(deviceId, code); + } + } + + public int getKeycodeState(int code) { + synchronized (mFirst) { + VirtualKey vk = mPressedVirtualKey; + if (vk != null) { + if (vk.lastKeycode == code) { + return 2; + } + } + return nativeGetKeycodeState(code); + } + } + + public int getKeycodeState(int deviceId, int code) { + synchronized (mFirst) { + VirtualKey vk = mPressedVirtualKey; + if (vk != null) { + if (vk.lastKeycode == code) { + return 2; + } + } + return nativeGetKeycodeState(deviceId, code); + } + } + public static native String getDeviceName(int deviceId); public static native int getDeviceClasses(int deviceId); + public static native void addExcludedDevice(String deviceName); public static native boolean getAbsoluteInfo(int deviceId, int axis, InputDevice.AbsoluteInfo outInfo); public static native int getSwitchState(int sw); public static native int getSwitchState(int deviceId, int sw); - public static native int getScancodeState(int sw); - public static native int getScancodeState(int deviceId, int sw); - public static native int getKeycodeState(int sw); - public static native int getKeycodeState(int deviceId, int sw); + public static native int nativeGetScancodeState(int code); + public static native int nativeGetScancodeState(int deviceId, int code); + public static native int nativeGetKeycodeState(int code); + public static native int nativeGetKeycodeState(int deviceId, int code); + public static native int scancodeToKeycode(int deviceId, int scancode); public static native boolean hasKeys(int[] keycodes, boolean[] keyExists); public static KeyEvent newKeyEvent(InputDevice device, long downTime, @@ -181,12 +420,13 @@ public abstract class KeyInputQueue { Thread mThread = new Thread("InputDeviceReader") { public void run() { + if (DEBUG) Log.v(TAG, "InputDeviceReader.run()"); android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY); - try { - RawInputEvent ev = new RawInputEvent(); - while (true) { + RawInputEvent ev = new RawInputEvent(); + while (true) { + try { InputDevice di; // block, doesn't release the monitor @@ -207,23 +447,50 @@ public abstract class KeyInputQueue { if (ev.type == RawInputEvent.EV_DEVICE_ADDED) { synchronized (mFirst) { di = newInputDevice(ev.deviceId); - mDevices.put(ev.deviceId, di); - configChanged = true; + if (di.classes != 0) { + // If this device is some kind of input class, + // we care about it. + mDevices.put(ev.deviceId, di); + if ((di.classes & RawInputEvent.CLASS_TOUCHSCREEN) != 0) { + readVirtualKeys(di.name); + } + // The configuration may have changed because + // of this device. + configChanged = true; + } else { + // We won't do anything with this device. + mIgnoredDevices.put(ev.deviceId, di); + Log.i(TAG, "Ignoring non-input device: id=0x" + + Integer.toHexString(di.id) + + ", name=" + di.name); + } } } else if (ev.type == RawInputEvent.EV_DEVICE_REMOVED) { synchronized (mFirst) { - Log.i(TAG, "Device removed: id=0x" - + Integer.toHexString(ev.deviceId)); + if (false) { + Log.i(TAG, "Device removed: id=0x" + + Integer.toHexString(ev.deviceId)); + } di = mDevices.get(ev.deviceId); if (di != null) { mDevices.delete(ev.deviceId); + // The configuration may have changed because + // of this device. configChanged = true; + } else if ((di=mIgnoredDevices.get(ev.deviceId)) != null) { + mIgnoredDevices.remove(ev.deviceId); } else { - Log.w(TAG, "Bad device id: " + ev.deviceId); + Log.w(TAG, "Removing bad device id: " + + Integer.toHexString(ev.deviceId)); + continue; } } } else { di = getInputDevice(ev.deviceId); + if (di == null) { + // This may be some junk from an ignored device. + continue; + } // first crack at it send = preprocessEvent(di, ev); @@ -235,13 +502,9 @@ public abstract class KeyInputQueue { } } - if (di == null) { - continue; - } - if (configChanged) { synchronized (mFirst) { - addLocked(di, SystemClock.uptimeMillis(), 0, + addLocked(di, System.nanoTime(), 0, RawInputEvent.CLASS_CONFIGURATION_CHANGED, null); } @@ -256,6 +519,7 @@ public abstract class KeyInputQueue { // timebase as SystemClock.uptimeMillis(). //curTime = gotOne ? ev.when : SystemClock.uptimeMillis(); final long curTime = SystemClock.uptimeMillis(); + final long curTimeNano = System.nanoTime(); //Log.i(TAG, "curTime=" + curTime + ", systemClock=" + SystemClock.uptimeMillis()); final int classes = di.classes; @@ -271,95 +535,376 @@ public abstract class KeyInputQueue { boolean down; if (ev.value != 0) { down = true; - di.mDownTime = curTime; + di.mKeyDownTime = curTime; } else { down = false; } int keycode = rotateKeyCodeLocked(ev.keycode); - addLocked(di, curTime, ev.flags, + addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, - newKeyEvent(di, di.mDownTime, curTime, down, + newKeyEvent(di, di.mKeyDownTime, curTime, down, keycode, 0, scancode, ((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0) ? KeyEvent.FLAG_WOKE_HERE : 0)); + } else if (ev.type == RawInputEvent.EV_KEY) { + // Single touch protocol: touch going down or up. if (ev.scancode == RawInputEvent.BTN_TOUCH && - (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { + (classes&(RawInputEvent.CLASS_TOUCHSCREEN + |RawInputEvent.CLASS_TOUCHSCREEN_MT)) + == RawInputEvent.CLASS_TOUCHSCREEN) { di.mAbs.changed = true; - di.mAbs.down = ev.value != 0; - } - if (ev.scancode == RawInputEvent.BTN_MOUSE && + di.mAbs.mDown[0] = ev.value != 0; + + // Trackball (mouse) protocol: press down or up. + } else if (ev.scancode == RawInputEvent.BTN_MOUSE && (classes&RawInputEvent.CLASS_TRACKBALL) != 0) { di.mRel.changed = true; - di.mRel.down = ev.value != 0; + di.mRel.mNextNumPointers = ev.value != 0 ? 1 : 0; send = true; } + // Process position events from multitouch protocol. + } else if (ev.type == RawInputEvent.EV_ABS && + (classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) { + if (ev.scancode == RawInputEvent.ABS_MT_TOUCH_MAJOR) { + di.mAbs.changed = true; + di.mAbs.mNextData[di.mAbs.mAddingPointerOffset + + MotionEvent.SAMPLE_PRESSURE] = ev.value; + } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_X) { + di.mAbs.changed = true; + di.mAbs.mNextData[di.mAbs.mAddingPointerOffset + + MotionEvent.SAMPLE_X] = ev.value; + if (DEBUG_POINTERS) Log.v(TAG, "MT @" + + di.mAbs.mAddingPointerOffset + + " X:" + ev.value); + } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_Y) { + di.mAbs.changed = true; + di.mAbs.mNextData[di.mAbs.mAddingPointerOffset + + MotionEvent.SAMPLE_Y] = ev.value; + if (DEBUG_POINTERS) Log.v(TAG, "MT @" + + di.mAbs.mAddingPointerOffset + + " Y:" + ev.value); + } else if (ev.scancode == RawInputEvent.ABS_MT_WIDTH_MAJOR) { + di.mAbs.changed = true; + di.mAbs.mNextData[di.mAbs.mAddingPointerOffset + + MotionEvent.SAMPLE_SIZE] = ev.value; + } + + // Process position events from single touch protocol. } else if (ev.type == RawInputEvent.EV_ABS && (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { if (ev.scancode == RawInputEvent.ABS_X) { di.mAbs.changed = true; - di.mAbs.x = ev.value; + di.curTouchVals[MotionEvent.SAMPLE_X] = ev.value; } else if (ev.scancode == RawInputEvent.ABS_Y) { di.mAbs.changed = true; - di.mAbs.y = ev.value; + di.curTouchVals[MotionEvent.SAMPLE_Y] = ev.value; } else if (ev.scancode == RawInputEvent.ABS_PRESSURE) { di.mAbs.changed = true; - di.mAbs.pressure = ev.value; + di.curTouchVals[MotionEvent.SAMPLE_PRESSURE] = ev.value; + di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA + + MotionEvent.SAMPLE_PRESSURE] = ev.value; } else if (ev.scancode == RawInputEvent.ABS_TOOL_WIDTH) { di.mAbs.changed = true; - di.mAbs.size = ev.value; + di.curTouchVals[MotionEvent.SAMPLE_SIZE] = ev.value; + di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA + + MotionEvent.SAMPLE_SIZE] = ev.value; } + // Process movement events from trackball (mouse) protocol. } else if (ev.type == RawInputEvent.EV_REL && (classes&RawInputEvent.CLASS_TRACKBALL) != 0) { // Add this relative movement into our totals. if (ev.scancode == RawInputEvent.REL_X) { di.mRel.changed = true; - di.mRel.x += ev.value; + di.mRel.mNextData[MotionEvent.SAMPLE_X] += ev.value; } else if (ev.scancode == RawInputEvent.REL_Y) { di.mRel.changed = true; - di.mRel.y += ev.value; + di.mRel.mNextData[MotionEvent.SAMPLE_Y] += ev.value; } } - if (send || ev.type == RawInputEvent.EV_SYN) { + // Handle multitouch protocol sync: tells us that the + // driver has returned all data for -one- of the pointers + // that is currently down. + if (ev.type == RawInputEvent.EV_SYN + && ev.scancode == RawInputEvent.SYN_MT_REPORT + && di.mAbs != null) { + di.mAbs.changed = true; + if (di.mAbs.mNextData[MotionEvent.SAMPLE_PRESSURE] > 0) { + // If the value is <= 0, the pointer is not + // down, so keep it in the count. + + if (di.mAbs.mNextData[di.mAbs.mAddingPointerOffset + + MotionEvent.SAMPLE_PRESSURE] != 0) { + final int num = di.mAbs.mNextNumPointers+1; + di.mAbs.mNextNumPointers = num; + if (DEBUG_POINTERS) Log.v(TAG, + "MT_REPORT: now have " + num + " pointers"); + final int newOffset = (num <= InputDevice.MAX_POINTERS) + ? (num * MotionEvent.NUM_SAMPLE_DATA) + : (InputDevice.MAX_POINTERS * + MotionEvent.NUM_SAMPLE_DATA); + di.mAbs.mAddingPointerOffset = newOffset; + di.mAbs.mNextData[newOffset + + MotionEvent.SAMPLE_PRESSURE] = 0; + } else { + if (DEBUG_POINTERS) Log.v(TAG, "MT_REPORT: no pointer"); + } + } + + // Handle general event sync: all data for the current + // event update has been delivered. + } else if (send || (ev.type == RawInputEvent.EV_SYN + && ev.scancode == RawInputEvent.SYN_REPORT)) { if (mDisplay != null) { if (!mHaveGlobalMetaState) { computeGlobalMetaStateLocked(); } MotionEvent me; - me = di.mAbs.generateMotion(di, curTime, true, - mDisplay, mOrientation, mGlobalMetaState); - if (false) Log.v(TAG, "Absolute: x=" + di.mAbs.x - + " y=" + di.mAbs.y + " ev=" + me); - if (me != null) { - if (WindowManagerPolicy.WATCH_POINTER) { - Log.i(TAG, "Enqueueing: " + me); + + InputDevice.MotionState ms = di.mAbs; + if (ms.changed) { + ms.changed = false; + + if ((classes&(RawInputEvent.CLASS_TOUCHSCREEN + |RawInputEvent.CLASS_TOUCHSCREEN_MT)) + == RawInputEvent.CLASS_TOUCHSCREEN) { + ms.mNextNumPointers = 0; + if (ms.mDown[0]) { + System.arraycopy(di.curTouchVals, 0, + ms.mNextData, 0, + MotionEvent.NUM_SAMPLE_DATA); + ms.mNextNumPointers++; + } } - addLocked(di, curTime, ev.flags, - RawInputEvent.CLASS_TOUCHSCREEN, me); + + if (BAD_TOUCH_HACK) { + ms.dropBadPoint(di); + } + + boolean doMotion = !monitorVirtualKey(di, + ev, curTime, curTimeNano); + + if (doMotion && ms.mNextNumPointers > 0 + && (ms.mLastNumPointers == 0 + || ms.mSkipLastPointers)) { + doMotion = !generateVirtualKeyDown(di, + ev, curTime, curTimeNano); + } + + if (doMotion) { + // XXX Need to be able to generate + // multiple events here, for example + // if two fingers change up/down state + // at the same time. + do { + me = ms.generateAbsMotion(di, curTime, + curTimeNano, mDisplay, + mOrientation, mGlobalMetaState); + if (DEBUG_POINTERS) Log.v(TAG, "Absolute: x=" + + di.mAbs.mNextData[MotionEvent.SAMPLE_X] + + " y=" + + di.mAbs.mNextData[MotionEvent.SAMPLE_Y] + + " ev=" + me); + if (me != null) { + if (WindowManagerPolicy.WATCH_POINTER) { + Log.i(TAG, "Enqueueing: " + me); + } + addLocked(di, curTimeNano, ev.flags, + RawInputEvent.CLASS_TOUCHSCREEN, me); + } + } while (ms.hasMore()); + } else { + // We are consuming movement in the + // virtual key area... but still + // propagate this to the previous + // data for comparisons. + int num = ms.mNextNumPointers; + if (num > InputDevice.MAX_POINTERS) { + num = InputDevice.MAX_POINTERS; + } + System.arraycopy(ms.mNextData, 0, + ms.mLastData, 0, + num * MotionEvent.NUM_SAMPLE_DATA); + ms.mLastNumPointers = num; + ms.mSkipLastPointers = true; + } + + ms.finish(); } - me = di.mRel.generateMotion(di, curTime, false, - mDisplay, mOrientation, mGlobalMetaState); - if (false) Log.v(TAG, "Relative: x=" + di.mRel.x - + " y=" + di.mRel.y + " ev=" + me); - if (me != null) { - addLocked(di, curTime, ev.flags, - RawInputEvent.CLASS_TRACKBALL, me); + + ms = di.mRel; + if (ms.changed) { + ms.changed = false; + + me = ms.generateRelMotion(di, curTime, + curTimeNano, + mOrientation, mGlobalMetaState); + if (false) Log.v(TAG, "Relative: x=" + + di.mRel.mNextData[MotionEvent.SAMPLE_X] + + " y=" + + di.mRel.mNextData[MotionEvent.SAMPLE_Y] + + " ev=" + me); + if (me != null) { + addLocked(di, curTimeNano, ev.flags, + RawInputEvent.CLASS_TRACKBALL, me); + } + + ms.finish(); } } } } + + } catch (RuntimeException exc) { + Log.e(TAG, "InputReaderThread uncaught exception", exc); } } - catch (RuntimeException exc) { - Log.e(TAG, "InputReaderThread uncaught exception", exc); - } } }; + private boolean isInsideDisplay(InputDevice dev) { + final InputDevice.AbsoluteInfo absx = dev.absX; + final InputDevice.AbsoluteInfo absy = dev.absY; + final InputDevice.MotionState absm = dev.mAbs; + if (absx == null || absy == null || absm == null) { + return true; + } + + if (absm.mNextData[MotionEvent.SAMPLE_X] >= absx.minValue + && absm.mNextData[MotionEvent.SAMPLE_X] <= absx.maxValue + && absm.mNextData[MotionEvent.SAMPLE_Y] >= absy.minValue + && absm.mNextData[MotionEvent.SAMPLE_Y] <= absy.maxValue) { + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Input (" + + absm.mNextData[MotionEvent.SAMPLE_X] + + "," + absm.mNextData[MotionEvent.SAMPLE_Y] + + ") inside of display"); + return true; + } + + return false; + } + + private VirtualKey findVirtualKey(InputDevice dev) { + final int N = mVirtualKeys.size(); + if (N <= 0) { + return null; + } + + final InputDevice.MotionState absm = dev.mAbs; + for (int i=0; i<N; i++) { + VirtualKey sb = mVirtualKeys.get(i); + sb.computeHitRect(dev, mDisplayWidth, mDisplayHeight); + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Hit test (" + + absm.mNextData[MotionEvent.SAMPLE_X] + "," + + absm.mNextData[MotionEvent.SAMPLE_Y] + ") in code " + + sb.scancode + " - (" + sb.hitLeft + + "," + sb.hitTop + ")-(" + sb.hitRight + "," + + sb.hitBottom + ")"); + if (sb.checkHit(absm.mNextData[MotionEvent.SAMPLE_X], + absm.mNextData[MotionEvent.SAMPLE_Y])) { + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Hit!"); + return sb; + } + } + + return null; + } + + private boolean generateVirtualKeyDown(InputDevice di, RawInputEvent ev, + long curTime, long curTimeNano) { + if (isInsideDisplay(di)) { + // Didn't consume event. + return false; + } + + + VirtualKey vk = findVirtualKey(di); + if (vk != null) { + final InputDevice.MotionState ms = di.mAbs; + mPressedVirtualKey = vk; + vk.lastKeycode = scancodeToKeycode(di.id, vk.scancode); + ms.mLastNumPointers = ms.mNextNumPointers; + di.mKeyDownTime = curTime; + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, + "Generate key down for: " + vk.scancode + + " (keycode=" + vk.lastKeycode + ")"); + KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, true, + vk.lastKeycode, 0, vk.scancode, + KeyEvent.FLAG_VIRTUAL_HARD_KEY); + mHapticFeedbackCallback.virtualKeyFeedback(event); + addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, + event); + } + + // We always consume the event, even if we didn't + // generate a key event. There are two reasons for + // this: to avoid spurious touches when holding + // the edges of the device near the touchscreen, + // and to avoid reporting events if there are virtual + // keys on the touchscreen outside of the display + // area. + // Note that for all of this we are only looking at the + // first pointer, since what we are handling here is the + // first pointer going down, and this is the coordinate + // that will be used to dispatch the event. + if (false) { + final InputDevice.AbsoluteInfo absx = di.absX; + final InputDevice.AbsoluteInfo absy = di.absY; + final InputDevice.MotionState absm = di.mAbs; + Log.v(TAG, "Rejecting (" + + absm.mNextData[MotionEvent.SAMPLE_X] + "," + + absm.mNextData[MotionEvent.SAMPLE_Y] + "): outside of (" + + absx.minValue + "," + absy.minValue + + ")-(" + absx.maxValue + "," + + absx.maxValue + ")"); + } + return true; + } + + private boolean monitorVirtualKey(InputDevice di, RawInputEvent ev, + long curTime, long curTimeNano) { + VirtualKey vk = mPressedVirtualKey; + if (vk == null) { + return false; + } + + final InputDevice.MotionState ms = di.mAbs; + if (ms.mNextNumPointers <= 0) { + mPressedVirtualKey = null; + ms.mLastNumPointers = 0; + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Generate key up for: " + vk.scancode); + KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, false, + vk.lastKeycode, 0, vk.scancode, + KeyEvent.FLAG_VIRTUAL_HARD_KEY); + mHapticFeedbackCallback.virtualKeyFeedback(event); + addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, + event); + return true; + + } else if (isInsideDisplay(di)) { + // Whoops the pointer has moved into + // the display area! Cancel the + // virtual key and start a pointer + // motion. + mPressedVirtualKey = null; + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Cancel key up for: " + vk.scancode); + KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, false, + vk.lastKeycode, 0, vk.scancode, + KeyEvent.FLAG_CANCELED | KeyEvent.FLAG_VIRTUAL_HARD_KEY); + mHapticFeedbackCallback.virtualKeyFeedback(event); + addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, + event); + ms.mLastNumPointers = 0; + return false; + } + + return true; + } + /** * Returns a new meta state for the given keys and old state. */ @@ -497,6 +1042,29 @@ public abstract class KeyInputQueue { } } + /** + * Return true if the queue has an up event pending that corresponds + * to the same key as the given key event. + */ + boolean hasKeyUpEvent(KeyEvent origEvent) { + synchronized (mFirst) { + final int keyCode = origEvent.getKeyCode(); + QueuedEvent cur = mLast.prev; + while (cur.prev != null) { + if (cur.classType == RawInputEvent.CLASS_KEYBOARD) { + KeyEvent ke = (KeyEvent)cur.event; + if (ke.getAction() == KeyEvent.ACTION_UP + && ke.getKeyCode() == keyCode) { + return true; + } + } + cur = cur.prev; + } + } + + return false; + } + void recycleEvent(QueuedEvent ev) { synchronized (mFirst) { //Log.i(TAG, "Recycle event: " + ev); @@ -506,8 +1074,8 @@ public abstract class KeyInputQueue { if (ev.event == ev.inputDevice.mRel.currentMove) { if (false) Log.i(TAG, "Detach rel " + ev.event); ev.inputDevice.mRel.currentMove = null; - ev.inputDevice.mRel.x = 0; - ev.inputDevice.mRel.y = 0; + ev.inputDevice.mRel.mNextData[MotionEvent.SAMPLE_X] = 0; + ev.inputDevice.mRel.mNextData[MotionEvent.SAMPLE_Y] = 0; } recycleLocked(ev); } @@ -530,7 +1098,7 @@ public abstract class KeyInputQueue { } } - private QueuedEvent obtainLocked(InputDevice device, long when, + private QueuedEvent obtainLocked(InputDevice device, long whenNano, int flags, int classType, Object event) { QueuedEvent ev; if (mCacheCount == 0) { @@ -542,7 +1110,7 @@ public abstract class KeyInputQueue { mCacheCount--; } ev.inputDevice = device; - ev.when = when; + ev.whenNano = whenNano; ev.flags = flags; ev.classType = classType; ev.event = event; @@ -561,13 +1129,13 @@ public abstract class KeyInputQueue { } } - private void addLocked(InputDevice device, long when, int flags, + private void addLocked(InputDevice device, long whenNano, int flags, int classType, Object event) { boolean poke = mFirst.next == mLast; - QueuedEvent ev = obtainLocked(device, when, flags, classType, event); + QueuedEvent ev = obtainLocked(device, whenNano, flags, classType, event); QueuedEvent p = mLast.prev; - while (p != mFirst && ev.when < p.when) { + while (p != mFirst && ev.whenNano < p.whenNano) { p = p.prev; } @@ -578,31 +1146,48 @@ public abstract class KeyInputQueue { ev.inQueue = true; if (poke) { + long time; + if (MEASURE_LATENCY) { + time = System.nanoTime(); + } mFirst.notify(); mWakeLock.acquire(); + if (MEASURE_LATENCY) { + lt.sample("1 addLocked-queued event ", System.nanoTime() - time); + } } } private InputDevice newInputDevice(int deviceId) { int classes = getDeviceClasses(deviceId); String name = getDeviceName(deviceId); - Log.i(TAG, "Device added: id=0x" + Integer.toHexString(deviceId) - + ", name=" + name - + ", classes=" + Integer.toHexString(classes)); - InputDevice.AbsoluteInfo absX; - InputDevice.AbsoluteInfo absY; - InputDevice.AbsoluteInfo absPressure; - InputDevice.AbsoluteInfo absSize; - if ((classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { - absX = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_X, "X"); - absY = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_Y, "Y"); - absPressure = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_PRESSURE, "Pressure"); - absSize = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_TOOL_WIDTH, "Size"); - } else { - absX = null; - absY = null; - absPressure = null; - absSize = null; + InputDevice.AbsoluteInfo absX = null; + InputDevice.AbsoluteInfo absY = null; + InputDevice.AbsoluteInfo absPressure = null; + InputDevice.AbsoluteInfo absSize = null; + if (classes != 0) { + Log.i(TAG, "Device added: id=0x" + Integer.toHexString(deviceId) + + ", name=" + name + + ", classes=" + Integer.toHexString(classes)); + if ((classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) { + absX = loadAbsoluteInfo(deviceId, + RawInputEvent.ABS_MT_POSITION_X, "X"); + absY = loadAbsoluteInfo(deviceId, + RawInputEvent.ABS_MT_POSITION_Y, "Y"); + absPressure = loadAbsoluteInfo(deviceId, + RawInputEvent.ABS_MT_TOUCH_MAJOR, "Pressure"); + absSize = loadAbsoluteInfo(deviceId, + RawInputEvent.ABS_MT_WIDTH_MAJOR, "Size"); + } else if ((classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { + absX = loadAbsoluteInfo(deviceId, + RawInputEvent.ABS_X, "X"); + absY = loadAbsoluteInfo(deviceId, + RawInputEvent.ABS_Y, "Y"); + absPressure = loadAbsoluteInfo(deviceId, + RawInputEvent.ABS_PRESSURE, "Pressure"); + absSize = loadAbsoluteInfo(deviceId, + RawInputEvent.ABS_TOOL_WIDTH, "Size"); + } } return new InputDevice(deviceId, classes, name, absX, absY, absPressure, absSize); diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 3f268c9..bbb43d7 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -16,12 +16,7 @@ package com.android.server; -import java.io.BufferedReader; -import java.io.File; import java.io.FileDescriptor; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; @@ -31,7 +26,6 @@ import java.util.Map; import java.util.Observable; import java.util.Observer; import java.util.Set; -import java.util.regex.Pattern; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -49,10 +43,12 @@ import android.location.IGpsStatusProvider; import android.location.ILocationListener; import android.location.ILocationManager; import android.location.ILocationProvider; +import android.location.INetInitiatedListener; import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -63,7 +59,6 @@ import android.os.Message; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; -import android.os.SystemClock; import android.provider.Settings; import android.util.Log; import android.util.PrintWriterPrinter; @@ -71,6 +66,7 @@ import android.util.PrintWriterPrinter; import com.android.internal.location.GpsLocationProvider; import com.android.internal.location.LocationProviderProxy; import com.android.internal.location.MockProvider; +import com.android.internal.location.GpsNetInitiatedHandler; /** * The service class that manages LocationProviders and issues location @@ -82,17 +78,9 @@ public class LocationManagerService extends ILocationManager.Stub implements Run private static final String TAG = "LocationManagerService"; private static final boolean LOCAL_LOGV = false; - // Minimum time interval between last known location writes, in milliseconds. - private static final long MIN_LAST_KNOWN_LOCATION_TIME = 60L * 1000L; - - // Max time to hold wake lock for, in milliseconds. - private static final long MAX_TIME_FOR_WAKE_LOCK = 60 * 1000L; - // The last time a location was written, by provider name. private HashMap<String,Long> mLastWriteTime = new HashMap<String,Long>(); - private static final Pattern PATTERN_COMMA = Pattern.compile(","); - private static final String ACCESS_FINE_LOCATION = android.Manifest.permission.ACCESS_FINE_LOCATION; private static final String ACCESS_COARSE_LOCATION = @@ -118,6 +106,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run private final Context mContext; private IGeocodeProvider mGeocodeProvider; private IGpsStatusProvider mGpsStatusProvider; + private INetInitiatedListener mNetInitiatedListener; private LocationWorkerHandler mLocationHandler; // Cache the real providers for use in addTestProvider() and removeTestProvider() @@ -394,7 +383,12 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } public void locationCallbackFinished(ILocationListener listener) { - Receiver receiver = getReceiver(listener); + //Do not use getReceiver here as that will add the ILocationListener to + //the receiver list if it is not found. If it is not found then the + //LocationListener was removed when it had a pending broadcast and should + //not be added back. + IBinder binder = listener.asBinder(); + Receiver receiver = mReceivers.get(binder); if (receiver != null) { synchronized (receiver) { // so wakelock calls will succeed @@ -413,97 +407,6 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - private Location readLastKnownLocationLocked(String provider) { - Location location = null; - String s = null; - try { - File f = new File(LocationManager.SYSTEM_DIR + "/location." - + provider); - if (!f.exists()) { - return null; - } - BufferedReader reader = new BufferedReader(new FileReader(f), 256); - s = reader.readLine(); - } catch (IOException e) { - Log.w(TAG, "Unable to read last known location", e); - } - - if (s == null) { - return null; - } - try { - String[] tokens = PATTERN_COMMA.split(s); - int idx = 0; - long time = Long.parseLong(tokens[idx++]); - double latitude = Double.parseDouble(tokens[idx++]); - double longitude = Double.parseDouble(tokens[idx++]); - double altitude = Double.parseDouble(tokens[idx++]); - float bearing = Float.parseFloat(tokens[idx++]); - float speed = Float.parseFloat(tokens[idx++]); - - location = new Location(provider); - location.setTime(time); - location.setLatitude(latitude); - location.setLongitude(longitude); - location.setAltitude(altitude); - location.setBearing(bearing); - location.setSpeed(speed); - } catch (NumberFormatException nfe) { - Log.e(TAG, "NumberFormatException reading last known location", nfe); - return null; - } - - return location; - } - - private void writeLastKnownLocationLocked(String provider, - Location location) { - long now = SystemClock.elapsedRealtime(); - Long last = mLastWriteTime.get(provider); - if ((last != null) - && (now - last.longValue() < MIN_LAST_KNOWN_LOCATION_TIME)) { - return; - } - mLastWriteTime.put(provider, now); - - StringBuilder sb = new StringBuilder(100); - sb.append(location.getTime()); - sb.append(','); - sb.append(location.getLatitude()); - sb.append(','); - sb.append(location.getLongitude()); - sb.append(','); - sb.append(location.getAltitude()); - sb.append(','); - sb.append(location.getBearing()); - sb.append(','); - sb.append(location.getSpeed()); - - FileWriter writer = null; - try { - File d = new File(LocationManager.SYSTEM_DIR); - if (!d.exists()) { - if (!d.mkdirs()) { - Log.w(TAG, "Unable to create directory to write location"); - return; - } - } - File f = new File(LocationManager.SYSTEM_DIR + "/location." + provider); - writer = new FileWriter(f); - writer.write(sb.toString()); - } catch (IOException e) { - Log.w(TAG, "Unable to write location", e); - } finally { - if (writer != null) { - try { - writer.close(); - } catch (IOException e) { - Log.w(TAG, "Exception closing file", e); - } - } - } - } - private void addProvider(LocationProviderProxy provider) { mProviders.add(provider); mProvidersByName.put(provider.getName(), provider); @@ -541,6 +444,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // Create a gps location provider GpsLocationProvider provider = new GpsLocationProvider(mContext, this); mGpsStatusProvider = provider.getGpsStatusProvider(); + mNetInitiatedListener = provider.getNetInitiatedListener(); LocationProviderProxy proxy = new LocationProviderProxy(LocationManager.GPS_PROVIDER, provider); addProvider(proxy); mGpsLocationProvider = proxy; @@ -626,7 +530,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } // notify provider of current network state - proxy.updateNetworkState(mNetworkState); + proxy.updateNetworkState(mNetworkState, null); } } @@ -850,7 +754,9 @@ public class LocationManagerService extends ILocationManager.Stub implements Run */ void disposeLocked() { ArrayList<UpdateRecord> records = mRecordsByProvider.get(this.mProvider); - records.remove(this); + if (records != null) { + records.remove(this); + } } @Override @@ -869,15 +775,6 @@ public class LocationManagerService extends ILocationManager.Stub implements Run mLastFixBroadcast.dump(new PrintWriterPrinter(pw), prefix + " "); pw.println(prefix + "mLastStatusBroadcast=" + mLastStatusBroadcast); } - - /** - * Calls dispose(). - */ - @Override protected void finalize() { - synchronized (mLock) { - disposeLocked(); - } - } } private Receiver getReceiver(ILocationListener listener) { @@ -1030,6 +927,12 @@ public class LocationManagerService extends ILocationManager.Stub implements Run try { if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) { receiver.getListener().asBinder().unlinkToDeath(receiver, 0); + synchronized(receiver) { + if(receiver.mPendingBroadcasts > 0) { + decrementPendingBroadcasts(); + receiver.mPendingBroadcasts = 0; + } + } } // Record which providers were associated with this listener @@ -1108,6 +1011,11 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } public boolean sendExtraCommand(String provider, String command, Bundle extras) { + if (provider == null) { + // throw NullPointerException to remain compatible with previous implementation + throw new NullPointerException(); + } + // first check for permission to the provider checkPermissionsSafe(provider); // and check for ACCESS_LOCATION_EXTRA_COMMANDS @@ -1118,7 +1026,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run synchronized (mLock) { LocationProviderProxy proxy = mProvidersByName.get(provider); - if (provider == null) { + if (proxy == null) { return false; } @@ -1126,6 +1034,22 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } + public boolean sendNiResponse(int notifId, int userResponse) + { + if (Binder.getCallingUid() != Process.myUid()) { + throw new SecurityException( + "calling sendNiResponse from outside of the system is not allowed"); + } + try { + return mNetInitiatedListener.sendNiResponse(notifId, userResponse); + } + catch (RemoteException e) + { + Log.e(TAG, "RemoteException in LocationManagerService.sendNiResponse"); + return false; + } + } + class ProximityAlert { final int mUid; final double mLatitude; @@ -1157,13 +1081,13 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return mIntent; } - boolean isInProximity(double latitude, double longitude) { + boolean isInProximity(double latitude, double longitude, float accuracy) { Location loc = new Location(""); loc.setLatitude(latitude); loc.setLongitude(longitude); double radius = loc.distanceTo(mLocation); - return radius <= mRadius; + return radius <= Math.max(mRadius,accuracy); } @Override @@ -1203,6 +1127,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run long now = System.currentTimeMillis(); double latitude = loc.getLatitude(); double longitude = loc.getLongitude(); + float accuracy = loc.getAccuracy(); ArrayList<PendingIntent> intentsToRemove = null; for (ProximityAlert alert : mProximityAlerts.values()) { @@ -1212,7 +1137,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if ((expiration == -1) || (now <= expiration)) { boolean entered = mProximitiesEntered.contains(alert); boolean inProximity = - alert.isInProximity(latitude, longitude); + alert.isInProximity(latitude, longitude, accuracy); if (!entered && inProximity) { if (LOCAL_LOGV) { Log.v(TAG, "Entered alert"); @@ -1487,16 +1412,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return null; } - Location location = mLastKnownLocation.get(provider); - if (location == null) { - // Get the persistent last known location for the provider - location = readLastKnownLocationLocked(provider); - if (location != null) { - mLastKnownLocation.put(provider, location); - } - } - - return location; + return mLastKnownLocation.get(provider); } private static boolean shouldBroadcastSafe(Location loc, Location lastLoc, UpdateRecord record) { @@ -1541,7 +1457,6 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } else { lastLocation.set(location); } - writeLastKnownLocationLocked(provider, location); // Fetch latest status update time long newStatusUpdateTime = p.getStatusUpdateTime(); @@ -1686,13 +1601,15 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } else { mNetworkState = LocationProvider.TEMPORARILY_UNAVAILABLE; } + NetworkInfo info = + (NetworkInfo)intent.getExtra(ConnectivityManager.EXTRA_NETWORK_INFO); // Notify location providers of current network state synchronized (mLock) { for (int i = mProviders.size() - 1; i >= 0; i--) { LocationProviderProxy provider = mProviders.get(i); if (provider.requiresNetwork()) { - provider.updateNetworkState(mNetworkState); + provider.updateNetworkState(mNetworkState, info); } } } @@ -1792,6 +1709,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) { checkMockPermissionsSafe(); + long identity = Binder.clearCallingIdentity(); synchronized (mLock) { MockProvider provider = new MockProvider(name, this, requiresNetwork, requiresSatellite, @@ -1814,6 +1732,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run mLastKnownLocation.put(name, null); updateProvidersLocked(); } + Binder.restoreCallingIdentity(identity); } public void removeTestProvider(String provider) { @@ -1823,6 +1742,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (mockProvider == null) { throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); } + long identity = Binder.clearCallingIdentity(); removeProvider(mProvidersByName.get(provider)); mMockProviders.remove(mockProvider); // reinstall real provider if we were mocking GPS or network provider @@ -1835,6 +1755,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } mLastKnownLocation.put(provider, null); updateProvidersLocked(); + Binder.restoreCallingIdentity(identity); } } @@ -1870,6 +1791,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (mockProvider == null) { throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); } + long identity = Binder.clearCallingIdentity(); if (enabled) { mockProvider.enable(); mEnabledProviders.add(provider); @@ -1880,6 +1802,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run mDisabledProviders.add(provider); } updateProvidersLocked(); + Binder.restoreCallingIdentity(identity); } } @@ -1890,9 +1813,11 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (mockProvider == null) { throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); } + long identity = Binder.clearCallingIdentity(); mEnabledProviders.remove(provider); mDisabledProviders.remove(provider); updateProvidersLocked(); + Binder.restoreCallingIdentity(identity); } } diff --git a/services/java/com/android/server/MasterClearReceiver.java b/services/java/com/android/server/MasterClearReceiver.java index 5a42e76..3c366da 100644 --- a/services/java/com/android/server/MasterClearReceiver.java +++ b/services/java/com/android/server/MasterClearReceiver.java @@ -30,8 +30,8 @@ public class MasterClearReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals("android.intent.action.GTALK_DATA_MESSAGE_RECEIVED")) { - if (!intent.getBooleanExtra("from_trusted_server", false)) { + if (intent.getAction().equals(Intent.ACTION_REMOTE_INTENT)) { + if (!intent.getBooleanExtra("android.intent.extra.from_trusted_server", false)) { Log.w(TAG, "Ignoring master clear request -- not from trusted server."); return; } diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index f81c519..204389e 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -231,6 +231,7 @@ class MountService extends IMountService.Stub { if (getMassStorageConnected() && !suppressIfConnected) { Intent intent = new Intent(); intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); setUsbStorageNotification( com.android.internal.R.string.usb_storage_notification_title, @@ -295,7 +296,7 @@ class MountService extends IMountService.Stub { setMediaStorageNotification( com.android.internal.R.string.ext_media_nomedia_notification_title, com.android.internal.R.string.ext_media_nomedia_notification_message, - com.android.internal.R.drawable.stat_sys_no_sim, + com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, null); handlePossibleExplicitUnmountBroadcast(path); @@ -312,7 +313,7 @@ class MountService extends IMountService.Stub { setMediaStorageNotification( com.android.internal.R.string.ext_media_safe_unmount_notification_title, com.android.internal.R.string.ext_media_safe_unmount_notification_message, - com.android.internal.R.drawable.stat_notify_sim_toolkit, + com.android.internal.R.drawable.stat_notify_sdcard, true, true, null); mShowSafeUnmountNotificationWhenUnmounted = false; } else { @@ -332,7 +333,7 @@ class MountService extends IMountService.Stub { setMediaStorageNotification( com.android.internal.R.string.ext_media_checking_notification_title, com.android.internal.R.string.ext_media_checking_notification_message, - com.android.internal.R.drawable.stat_notify_sim_toolkit, + com.android.internal.R.drawable.stat_notify_sdcard_prepare, true, false, null); updateUsbMassStorageNotification(true, false); @@ -352,7 +353,7 @@ class MountService extends IMountService.Stub { setMediaStorageNotification(com.android.internal.R.string.ext_media_nofs_notification_title, com.android.internal.R.string.ext_media_nofs_notification_message, - com.android.internal.R.drawable.stat_sys_no_sim, + com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi); updateUsbMassStorageNotification(false, false); intent = new Intent(Intent.ACTION_MEDIA_NOFS, @@ -420,7 +421,7 @@ class MountService extends IMountService.Stub { setMediaStorageNotification(com.android.internal.R.string.ext_media_unmountable_notification_title, com.android.internal.R.string.ext_media_unmountable_notification_message, - com.android.internal.R.drawable.stat_sys_no_sim, + com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi); updateUsbMassStorageNotification(false, false); diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index aac7124..ff23a13 100644..100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -30,11 +30,11 @@ import android.app.PendingIntent; import android.app.StatusBarManager; import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.ContentQueryMap; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; @@ -48,6 +48,7 @@ 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; import android.os.Vibrator; @@ -96,12 +97,13 @@ class NotificationManagerService extends INotificationManager.Stub private Vibrator mVibrator = new Vibrator(); // adb - private int mBatteryPlugged; + private boolean mUsbConnected; private boolean mAdbEnabled = false; private boolean mAdbNotificationShown = false; private Notification mAdbNotification; - private ArrayList<NotificationRecord> mNotificationList; + private final ArrayList<NotificationRecord> mNotificationList = + new ArrayList<NotificationRecord>(); private ArrayList<ToastRecord> mToastQueue; @@ -150,20 +152,22 @@ class NotificationManagerService extends INotificationManager.Stub private static final class NotificationRecord { - String pkg; - int id; + final String pkg; + final String tag; + final int id; ITransientNotification callback; int duration; - Notification notification; + final Notification notification; IBinder statusBarKey; - NotificationRecord(String pkg, int id, Notification notification) + NotificationRecord(String pkg, String tag, int id, Notification notification) { this.pkg = pkg; + this.tag = tag; this.id = id; this.notification = notification; } - + void dump(PrintWriter pw, String prefix, Context baseContext) { pw.println(prefix + this); pw.println(prefix + " icon=0x" + Integer.toHexString(notification.icon) @@ -187,7 +191,8 @@ class NotificationManagerService extends INotificationManager.Stub return "NotificationRecord{" + Integer.toHexString(System.identityHashCode(this)) + " pkg=" + pkg - + " id=" + Integer.toHexString(id) + "}"; + + " id=" + Integer.toHexString(id) + + " tag=" + tag + "}"; } } @@ -256,8 +261,9 @@ class NotificationManagerService extends INotificationManager.Stub cancelAll(); } - public void onNotificationClick(String pkg, int id) { - cancelNotification(pkg, id, Notification.FLAG_AUTO_CANCEL); + public void onNotificationClick(String pkg, String tag, int id) { + cancelNotification(pkg, tag, id, Notification.FLAG_AUTO_CANCEL, + Notification.FLAG_FOREGROUND_SERVICE); } public void onPanelRevealed() { @@ -310,8 +316,11 @@ class NotificationManagerService extends INotificationManager.Stub mBatteryFull = batteryFull; updateLights(); } - - mBatteryPlugged = intent.getIntExtra("plugged", 0); + } else if (action.equals(Intent.ACTION_UMS_CONNECTED)) { + mUsbConnected = true; + updateAdbNotification(); + } else if (action.equals(Intent.ACTION_UMS_DISCONNECTED)) { + mUsbConnected = false; updateAdbNotification(); } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED) || action.equals(Intent.ACTION_PACKAGE_RESTARTED)) { @@ -323,7 +332,7 @@ class NotificationManagerService extends INotificationManager.Stub if (pkgName == null) { return; } - cancelAllNotifications(pkgName); + cancelAllNotificationsInt(pkgName, 0, 0); } } }; @@ -363,7 +372,6 @@ class NotificationManagerService extends INotificationManager.Stub mSound = new AsyncPlayer(TAG); mSound.setUsesWakeLock(context); mToastQueue = new ArrayList<ToastRecord>(); - mNotificationList = new ArrayList<NotificationRecord>(); mHandler = new WorkerHandler(); mStatusBarService = statusBar; statusBar.setNotificationCallbacks(mNotificationCallbacks); @@ -380,6 +388,8 @@ class NotificationManagerService extends INotificationManager.Stub // register for battery changed notifications IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(Intent.ACTION_UMS_CONNECTED); + filter.addAction(Intent.ACTION_UMS_DISCONNECTED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); mContext.registerReceiver(mIntentReceiver, filter); @@ -575,6 +585,14 @@ class NotificationManagerService extends INotificationManager.Stub // ============================================================================ public void enqueueNotification(String pkg, int id, Notification notification, int[] idOut) { + enqueueNotificationWithTag(pkg, null /* tag */, id, notification, idOut); + } + + public void enqueueNotificationWithTag(String pkg, String tag, int id, + Notification notification, int[] idOut) + { + checkIncomingCall(pkg); + // This conditional is a dirty hack to limit the logging done on // behalf of the download manager without affecting other apps. if (!pkg.equals("com.android.providers.downloads") @@ -598,16 +616,29 @@ class NotificationManagerService extends INotificationManager.Stub } synchronized (mNotificationList) { - NotificationRecord r = new NotificationRecord(pkg, id, notification); + NotificationRecord r = new NotificationRecord(pkg, tag, id, notification); NotificationRecord old = null; - int index = indexOfNotificationLocked(pkg, id); + int index = indexOfNotificationLocked(pkg, tag, id); if (index < 0) { mNotificationList.add(r); } else { old = mNotificationList.remove(index); mNotificationList.add(index, r); + // Make sure we don't lose the foreground service state. + if (old != null) { + notification.flags |= + old.notification.flags&Notification.FLAG_FOREGROUND_SERVICE; + } } + + // Ensure if this is a foreground service that the proper additional + // flags are set. + if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) { + notification.flags |= Notification.FLAG_ONGOING_EVENT + | Notification.FLAG_NO_CLEAR; + } + if (notification.icon != 0) { IconData icon = IconData.makeIcon(null, pkg, notification.icon, notification.iconLevel, @@ -622,17 +653,18 @@ class NotificationManagerService extends INotificationManager.Stub } NotificationData n = new NotificationData(); - n.id = id; - n.pkg = pkg; - n.when = notification.when; - n.tickerText = truncatedTicker; - n.ongoingEvent = (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0; - if (!n.ongoingEvent && (notification.flags & Notification.FLAG_NO_CLEAR) == 0) { - n.clearable = true; - } - n.contentView = notification.contentView; - n.contentIntent = notification.contentIntent; - n.deleteIntent = notification.deleteIntent; + n.pkg = pkg; + n.tag = tag; + n.id = id; + n.when = notification.when; + n.tickerText = truncatedTicker; + n.ongoingEvent = (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0; + if (!n.ongoingEvent && (notification.flags & Notification.FLAG_NO_CLEAR) == 0) { + n.clearable = true; + } + n.contentView = notification.contentView; + n.contentIntent = notification.contentIntent; + n.deleteIntent = notification.deleteIntent; if (old != null && old.statusBarKey != null) { r.statusBarKey = old.statusBarKey; long identity = Binder.clearCallingIdentity(); @@ -802,21 +834,24 @@ class NotificationManagerService extends INotificationManager.Stub } /** - * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}. + * Cancels a notification ONLY if it has all of the {@code mustHaveFlags} + * and none of the {@code mustNotHaveFlags}. */ - private void cancelNotification(String pkg, int id, int mustHaveFlags) { + private void cancelNotification(String pkg, String tag, int id, int mustHaveFlags, + int mustNotHaveFlags) { EventLog.writeEvent(EVENT_LOG_CANCEL, pkg, id, mustHaveFlags); synchronized (mNotificationList) { - NotificationRecord r = null; - - int index = indexOfNotificationLocked(pkg, id); + int index = indexOfNotificationLocked(pkg, tag, id); if (index >= 0) { - r = mNotificationList.get(index); + NotificationRecord r = mNotificationList.get(index); if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) { return; } + if ((r.notification.flags & mustNotHaveFlags) != 0) { + return; + } mNotificationList.remove(index); @@ -830,7 +865,8 @@ class NotificationManagerService extends INotificationManager.Stub * Cancels all notifications from a given package that have all of the * {@code mustHaveFlags}. */ - private void cancelAllNotificationsInt(String pkg, int mustHaveFlags) { + void cancelAllNotificationsInt(String pkg, int mustHaveFlags, + int mustNotHaveFlags) { EventLog.writeEvent(EVENT_LOG_CANCEL_ALL, pkg, mustHaveFlags); synchronized (mNotificationList) { @@ -841,6 +877,9 @@ class NotificationManagerService extends INotificationManager.Stub if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) { continue; } + if ((r.notification.flags & mustNotHaveFlags) != 0) { + continue; + } if (!r.pkg.equals(pkg)) { continue; } @@ -855,17 +894,44 @@ class NotificationManagerService extends INotificationManager.Stub } - public void cancelNotification(String pkg, int id) - { - cancelNotification(pkg, id, 0); + public void cancelNotification(String pkg, int id) { + cancelNotificationWithTag(pkg, null /* tag */, id); } - public void cancelAllNotifications(String pkg) - { - cancelAllNotificationsInt(pkg, 0); + public void cancelNotificationWithTag(String pkg, String tag, int id) { + checkIncomingCall(pkg); + // 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); + } + + public void cancelAllNotifications(String pkg) { + checkIncomingCall(pkg); + + // Calling from user space, don't allow the canceling of actively + // running foreground services. + cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE); } - public void cancelAll() { + void checkIncomingCall(String pkg) { + int uid = Binder.getCallingUid(); + if (uid == Process.SYSTEM_UID || uid == 0) { + return; + } + try { + ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo( + pkg, 0); + if (ai.uid != uid) { + throw new SecurityException("Calling uid " + uid + " gave package" + + pkg + " which is owned by uid " + ai.uid); + } + } catch (PackageManager.NameNotFoundException e) { + throw new SecurityException("Unknown package " + pkg); + } + } + + void cancelAll() { synchronized (mNotificationList) { final int N = mNotificationList.size(); for (int i=N-1; i>=0; i--) { @@ -902,8 +968,15 @@ class NotificationManagerService extends INotificationManager.Stub { // Battery low always shows, other states only show if charging. if (mBatteryLow) { - mHardware.setLightFlashing_UNCHECKED(HardwareService.LIGHT_ID_BATTERY, BATTERY_LOW_ARGB, - HardwareService.LIGHT_FLASH_TIMED, BATTERY_BLINK_ON, BATTERY_BLINK_OFF); + if (mBatteryCharging) { + mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY, + BATTERY_LOW_ARGB); + } else { + // Flash when battery is low and not charging + mHardware.setLightFlashing_UNCHECKED(HardwareService.LIGHT_ID_BATTERY, + BATTERY_LOW_ARGB, HardwareService.LIGHT_FLASH_TIMED, + BATTERY_BLINK_ON, BATTERY_BLINK_OFF); + } } else if (mBatteryCharging) { if (mBatteryFull) { mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY, @@ -937,12 +1010,21 @@ class NotificationManagerService extends INotificationManager.Stub } // lock on mNotificationList - private int indexOfNotificationLocked(String pkg, int id) + private int indexOfNotificationLocked(String pkg, String tag, int id) { ArrayList<NotificationRecord> list = mNotificationList; final int len = list.size(); for (int i=0; i<len; i++) { NotificationRecord r = list.get(i); + if (tag == null) { + if (r.tag != null) { + continue; + } + } else { + if (!tag.equals(r.tag)) { + continue; + } + } if (r.id == id && r.pkg.equals(pkg)) { return i; } @@ -954,7 +1036,7 @@ class NotificationManagerService extends INotificationManager.Stub // security feature that we don't want people customizing the platform // to accidentally lose. private void updateAdbNotification() { - if (mAdbEnabled && mBatteryPlugged == BatteryManager.BATTERY_PLUGGED_USB) { + if (mAdbEnabled && mUsbConnected) { if ("0".equals(SystemProperties.get("persist.adb.notify"))) { return; } diff --git a/services/java/com/android/server/PackageManagerBackupAgent.java b/services/java/com/android/server/PackageManagerBackupAgent.java index 786f423..dbd1826 100644 --- a/services/java/com/android/server/PackageManagerBackupAgent.java +++ b/services/java/com/android/server/PackageManagerBackupAgent.java @@ -51,7 +51,7 @@ import java.util.List; */ public class PackageManagerBackupAgent extends BackupAgent { private static final String TAG = "PMBA"; - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; // key under which we store global metadata (individual app metadata // is stored using the package name as a key) @@ -195,7 +195,6 @@ public class PackageManagerBackupAgent extends BackupAgent { byte[] sigs = flattenSignatureArray(info.signatures); - // !!! TODO: take out this debugging if (DEBUG) { Log.v(TAG, "+ metadata for " + packName + " version=" + info.versionCode @@ -214,7 +213,6 @@ public class PackageManagerBackupAgent extends BackupAgent { // mentioned in the saved state file, but appear to no longer be present // on the device. Write a deletion entity for them. for (String app : mExisting) { - // !!! TODO: take out this msg if (DEBUG) Log.v(TAG, "- removing metadata for deleted pkg " + app); try { data.writeEntityHeader(app, -1); @@ -266,7 +264,6 @@ public class PackageManagerBackupAgent extends BackupAgent { mStoredSdkVersion = storedSdkVersion; mStoredIncrementalVersion = in.readUTF(); mHasMetadata = true; - // !!! TODO: remove this debugging output if (DEBUG) { Log.i(TAG, "Restore set version " + storedSystemVersion + " is compatible with OS version " + Build.VERSION.SDK_INT @@ -277,7 +274,6 @@ public class PackageManagerBackupAgent extends BackupAgent { // it's a file metadata record int versionCode = in.readInt(); Signature[] sigs = unflattenSignatureArray(in); -// !!! TODO: take out this debugging if (DEBUG) { Log.i(TAG, " restored metadata for " + key + " dataSize=" + dataSize @@ -326,7 +322,14 @@ public class PackageManagerBackupAgent extends BackupAgent { try { int num = in.readInt(); - Log.v(TAG, " ... unflatten read " + num); + if (DEBUG) Log.v(TAG, " ... unflatten read " + num); + + // Sensical? + if (num > 20) { + Log.e(TAG, "Suspiciously large sig count in restore data; aborting"); + throw new IllegalStateException("Bad restore state"); + } + sigs = new Signature[num]; for (int i = 0; i < num; i++) { int len = in.readInt(); @@ -340,7 +343,7 @@ public class PackageManagerBackupAgent extends BackupAgent { Log.w(TAG, "Empty signature block found"); } } catch (IOException e) { - Log.d(TAG, "Unable to unflatten sigs"); + Log.e(TAG, "Unable to unflatten sigs"); return null; } diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 89f854e..a83459e 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -35,6 +35,7 @@ import android.content.IntentSender.SendIntentException; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; +import android.content.pm.FeatureInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageInstallObserver; @@ -61,6 +62,8 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; import android.os.Parcel; import android.os.RemoteException; import android.os.Environment; @@ -88,6 +91,7 @@ import java.io.InputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; @@ -108,6 +112,7 @@ class PackageManagerService extends IPackageManager.Stub { private static final boolean MULTIPLE_APPLICATION_UIDS = true; private static final int RADIO_UID = Process.PHONE_UID; + private static final int LOG_UID = Process.LOG_UID; private static final int FIRST_APPLICATION_UID = Process.FIRST_APPLICATION_UID; private static final int MAX_APPLICATION_UIDS = 1000; @@ -138,7 +143,7 @@ class PackageManagerService extends IPackageManager.Stub { final HandlerThread mHandlerThread = new HandlerThread("PackageManager", Process.THREAD_PRIORITY_BACKGROUND); - final Handler mHandler; + final PackageHandler mHandler; final int mSdkVersion = Build.VERSION.SDK_INT; final String mSdkCodename = "REL".equals(Build.VERSION.CODENAME) @@ -173,6 +178,7 @@ class PackageManagerService extends IPackageManager.Stub { final File mFrameworkDir; final File mSystemAppDir; final File mAppInstallDir; + final File mDalvikCacheDir; // Directory containing the private parts (e.g. code and non-resource assets) of forward-locked // apps. @@ -221,6 +227,14 @@ class PackageManagerService extends IPackageManager.Stub { // etc/permissions.xml file. final HashMap<String, String> mSharedLibraries = new HashMap<String, String>(); + // Temporary for building the final shared libraries for an .apk. + String[] mTmpSharedLibraries = null; + + // These are the features this devices supports that were read from the + // etc/permissions.xml file. + final HashMap<String, FeatureInfo> mAvailableFeatures = + new HashMap<String, FeatureInfo>(); + // All available activities, for your resolving pleasure. final ActivityIntentResolver mActivities = new ActivityIntentResolver(); @@ -262,6 +276,49 @@ class PackageManagerService extends IPackageManager.Stub { ComponentName mResolveComponentName; PackageParser.Package mPlatformPackage; + // Set of pending broadcasts for aggregating enable/disable of components. + final HashMap<String, String> mPendingBroadcasts = new HashMap<String, String>(); + static final int SEND_PENDING_BROADCAST = 1; + // Delay time in millisecs + static final int BROADCAST_DELAY = 10 * 1000; + + class PackageHandler extends Handler { + PackageHandler(Looper looper) { + super(looper); + } + public void handleMessage(Message msg) { + switch (msg.what) { + case SEND_PENDING_BROADCAST : { + int size = 0; + String broadcastList[]; + HashMap<String, String> tmpMap; + int uids[]; + synchronized (mPackages) { + size = mPendingBroadcasts.size(); + if (size <= 0) { + // Nothing to be done. Just return + return; + } + broadcastList = new String[size]; + mPendingBroadcasts.keySet().toArray(broadcastList); + tmpMap = new HashMap<String, String>(mPendingBroadcasts); + uids = new int[size]; + for (int i = 0; i < size; i++) { + PackageSetting ps = mSettings.mPackages.get(mPendingBroadcasts.get(broadcastList[i])); + uids[i] = (ps != null) ? ps.userId : -1; + } + mPendingBroadcasts.clear(); + } + // Send broadcasts + for (int i = 0; i < size; i++) { + String className = broadcastList[i]; + sendPackageChangedBroadcast(className, true, tmpMap.get(className), uids[i]); + } + break; + } + } + } + } public static final IPackageManager main(Context context, boolean factoryTest) { PackageManagerService m = new PackageManagerService(context, factoryTest); ServiceManager.addService("package", m); @@ -309,6 +366,10 @@ class PackageManagerService extends IPackageManager.Stub { MULTIPLE_APPLICATION_UIDS ? RADIO_UID : FIRST_APPLICATION_UID, ApplicationInfo.FLAG_SYSTEM); + mSettings.addSharedUserLP("android.uid.log", + MULTIPLE_APPLICATION_UIDS + ? LOG_UID : FIRST_APPLICATION_UID, + ApplicationInfo.FLAG_SYSTEM); String separateProcesses = SystemProperties.get("debug.separate_processes"); if (separateProcesses != null && separateProcesses.length() > 0) { @@ -345,7 +406,7 @@ class PackageManagerService extends IPackageManager.Stub { synchronized (mInstallLock) { synchronized (mPackages) { mHandlerThread.start(); - mHandler = new Handler(mHandlerThread.getLooper()); + mHandler = new PackageHandler(mHandlerThread.getLooper()); File dataDir = Environment.getDataDirectory(); mAppDataDir = new File(dataDir, "data"); @@ -378,8 +439,11 @@ class PackageManagerService extends IPackageManager.Stub { final HashSet<String> libFiles = new HashSet<String>(); mFrameworkDir = new File(Environment.getRootDirectory(), "framework"); + mDalvikCacheDir = new File(dataDir, "dalvik-cache"); if (mInstaller != null) { + boolean didDexOpt = false; + /** * Out of paranoia, ensure that everything in the boot class * path has been dexed. @@ -392,6 +456,7 @@ class PackageManagerService extends IPackageManager.Stub { if (dalvik.system.DexFile.isDexOptNeeded(paths[i])) { libFiles.add(paths[i]); mInstaller.dexopt(paths[i], Process.SYSTEM_UID, true); + didDexOpt = true; } } catch (FileNotFoundException e) { Log.w(TAG, "Boot class path not found: " + paths[i]); @@ -414,6 +479,7 @@ class PackageManagerService extends IPackageManager.Stub { if (dalvik.system.DexFile.isDexOptNeeded(lib)) { libFiles.add(lib); mInstaller.dexopt(lib, Process.SYSTEM_UID, true); + didDexOpt = true; } } catch (FileNotFoundException e) { Log.w(TAG, "Library not found: " + lib); @@ -433,7 +499,7 @@ class PackageManagerService extends IPackageManager.Stub { * run from a non-root shell. */ String[] frameworkFiles = mFrameworkDir.list(); - if (frameworkFiles != null && mInstaller != null) { + if (frameworkFiles != null) { for (int i=0; i<frameworkFiles.length; i++) { File libPath = new File(mFrameworkDir, frameworkFiles[i]); String path = libPath.getPath(); @@ -448,6 +514,7 @@ class PackageManagerService extends IPackageManager.Stub { try { if (dalvik.system.DexFile.isDexOptNeeded(path)) { mInstaller.dexopt(path, Process.SYSTEM_UID, true); + didDexOpt = true; } } catch (FileNotFoundException e) { Log.w(TAG, "Jar not found: " + path); @@ -456,6 +523,25 @@ class PackageManagerService extends IPackageManager.Stub { } } } + + if (didDexOpt) { + // If we had to do a dexopt of one of the previous + // things, then something on the system has changed. + // Consider this significant, and wipe away all other + // existing dexopt files to ensure we don't leave any + // dangling around. + String[] files = mDalvikCacheDir.list(); + if (files != null) { + for (int i=0; i<files.length; i++) { + String fn = files[i]; + if (fn.startsWith("data@app@") + || fn.startsWith("data@app-private@")) { + Log.i(TAG, "Pruning dalvik file: " + fn); + (new File(mDalvikCacheDir, fn)).delete(); + } + } + } + } } mFrameworkInstallObserver = new AppDirObserver( @@ -581,6 +667,27 @@ class PackageManagerService extends IPackageManager.Stub { final File permFile = new File(Environment.getRootDirectory(), "etc/permissions/platform.xml"); readPermissionsFromXml(permFile); + + StringBuilder sb = new StringBuilder(128); + sb.append("Libs:"); + Iterator<String> it = mSharedLibraries.keySet().iterator(); + while (it.hasNext()) { + sb.append(' '); + String name = it.next(); + sb.append(name); + sb.append(':'); + sb.append(mSharedLibraries.get(name)); + } + Log.i(TAG, sb.toString()); + + sb.setLength(0); + sb.append("Features:"); + it = mAvailableFeatures.keySet().iterator(); + while (it.hasNext()) { + sb.append(' '); + sb.append(it.next()); + } + Log.i(TAG, sb.toString()); } private void readPermissionsFromXml(File permFile) { @@ -670,8 +777,22 @@ class PackageManagerService extends IPackageManager.Stub { Log.w(TAG, "<library> without file at " + parser.getPositionDescription()); } else { - Log.i(TAG, "Got library " + lname + " in " + lfile); - this.mSharedLibraries.put(lname, lfile); + //Log.i(TAG, "Got library " + lname + " in " + lfile); + mSharedLibraries.put(lname, lfile); + } + XmlUtils.skipCurrentTag(parser); + continue; + + } else if ("feature".equals(name)) { + String fname = parser.getAttributeValue(null, "name"); + if (fname == null) { + Log.w(TAG, "<feature> without name at " + + parser.getPositionDescription()); + } else { + //Log.i(TAG, "Got feature " + fname); + FeatureInfo fi = new FeatureInfo(); + fi.name = fname; + mAvailableFeatures.put(fname, fi); } XmlUtils.skipCurrentTag(parser); continue; @@ -751,6 +872,10 @@ class PackageManagerService extends IPackageManager.Stub { } PackageInfo generatePackageInfo(PackageParser.Package p, int flags) { + if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { + // The package has been uninstalled but has retained data and resources. + return PackageParser.generatePackageInfo(p, null, flags); + } final PackageSetting ps = (PackageSetting)p.mExtras; if (ps == null) { return null; @@ -1001,16 +1126,40 @@ class PackageManagerService extends IPackageManager.Stub { Set<String> libSet; synchronized (mPackages) { libSet = mSharedLibraries.keySet(); + int size = libSet.size(); + if (size > 0) { + String[] libs = new String[size]; + libSet.toArray(libs); + return libs; + } } - int size = libSet.size(); - if (size > 0) { - String[] libs = new String[size]; - libSet.toArray(libs); - return libs; + return null; + } + + public FeatureInfo[] getSystemAvailableFeatures() { + Collection<FeatureInfo> featSet; + synchronized (mPackages) { + featSet = mAvailableFeatures.values(); + int size = featSet.size(); + if (size > 0) { + FeatureInfo[] features = new FeatureInfo[size+1]; + featSet.toArray(features); + FeatureInfo fi = new FeatureInfo(); + fi.reqGlEsVersion = SystemProperties.getInt("ro.opengles.version", + FeatureInfo.GL_ES_VERSION_UNDEFINED); + features[size] = fi; + return features; + } } return null; } + public boolean hasSystemFeature(String name) { + synchronized (mPackages) { + return mAvailableFeatures.containsKey(name); + } + } + public int checkPermission(String permName, String pkgName) { synchronized (mPackages) { PackageParser.Package p = mPackages.get(pkgName); @@ -1138,25 +1287,57 @@ class PackageManagerService extends IPackageManager.Stub { || p2 == null || p2.mExtras == null) { return PackageManager.SIGNATURE_UNKNOWN_PACKAGE; } - return checkSignaturesLP(p1, p2); + return checkSignaturesLP(p1.mSignatures, p2.mSignatures); + } + } + + public int checkUidSignatures(int uid1, int uid2) { + synchronized (mPackages) { + Signature[] s1; + Signature[] s2; + Object obj = mSettings.getUserIdLP(uid1); + if (obj != null) { + if (obj instanceof SharedUserSetting) { + s1 = ((SharedUserSetting)obj).signatures.mSignatures; + } else if (obj instanceof PackageSetting) { + s1 = ((PackageSetting)obj).signatures.mSignatures; + } else { + return PackageManager.SIGNATURE_UNKNOWN_PACKAGE; + } + } else { + return PackageManager.SIGNATURE_UNKNOWN_PACKAGE; + } + obj = mSettings.getUserIdLP(uid2); + if (obj != null) { + if (obj instanceof SharedUserSetting) { + s2 = ((SharedUserSetting)obj).signatures.mSignatures; + } else if (obj instanceof PackageSetting) { + s2 = ((PackageSetting)obj).signatures.mSignatures; + } else { + return PackageManager.SIGNATURE_UNKNOWN_PACKAGE; + } + } else { + return PackageManager.SIGNATURE_UNKNOWN_PACKAGE; + } + return checkSignaturesLP(s1, s2); } } - int checkSignaturesLP(PackageParser.Package p1, PackageParser.Package p2) { - if (p1.mSignatures == null) { - return p2.mSignatures == null + int checkSignaturesLP(Signature[] s1, Signature[] s2) { + if (s1 == null) { + return s2 == null ? PackageManager.SIGNATURE_NEITHER_SIGNED : PackageManager.SIGNATURE_FIRST_NOT_SIGNED; } - if (p2.mSignatures == null) { + if (s2 == null) { return PackageManager.SIGNATURE_SECOND_NOT_SIGNED; } - final int N1 = p1.mSignatures.length; - final int N2 = p2.mSignatures.length; + final int N1 = s1.length; + final int N2 = s2.length; for (int i=0; i<N1; i++) { boolean match = false; for (int j=0; j<N2; j++) { - if (p1.mSignatures[i].equals(p2.mSignatures[j])) { + if (s1[i].equals(s2[j])) { match = true; break; } @@ -1677,6 +1858,9 @@ class PackageManagerService extends IPackageManager.Stub { } } + /** + * @deprecated + */ public void querySyncProviders(List outNames, List outInfo) { synchronized (mPackages) { Iterator<Map.Entry<String, PackageParser.Provider>> i @@ -1817,7 +2001,6 @@ class PackageManagerService extends IPackageManager.Stub { parseFlags |= mDefParseFlags; PackageParser pp = new PackageParser(scanFile.getPath()); pp.setSeparateProcesses(mSeparateProcesses); - pp.setSdkVersion(mSdkVersion, mSdkCodename); final PackageParser.Package pkg = pp.parsePackage(scanFile, destCodeFile.getAbsolutePath(), mMetrics, parseFlags); if (pkg == null) { @@ -1841,27 +2024,25 @@ class PackageManagerService extends IPackageManager.Stub { } if ((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) { // Check for updated system applications here - if (updatedPkg != null) { - if ((ps != null) && (!ps.codePath.getPath().equals(scanFile.getPath()))) { - if (pkg.mVersionCode <= ps.versionCode) { - // The system package has been updated and the code path does not match - // Ignore entry. Just return - Log.w(TAG, "Package:" + pkg.packageName + - " has been updated. Ignoring the one from path:"+scanFile); - mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE; - return null; - } else { - // Delete the older apk pointed to by ps - // At this point, its safely assumed that package installation for - // apps in system partition will go through. If not there won't be a working - // version of the app - synchronized (mPackages) { - // Just remove the loaded entries from package lists. - mPackages.remove(ps.name); - } - deletePackageResourcesLI(ps.name, ps.codePathString, ps.resourcePathString); - mSettings.enableSystemPackageLP(ps.name); + if ((ps != null) && (!ps.codePath.equals(scanFile))) { + if (pkg.mVersionCode < ps.versionCode) { + // The system package has been updated and the code path does not match + // Ignore entry. Just return + Log.w(TAG, "Package:" + pkg.packageName + + " has been updated. Ignoring the one from path:"+scanFile); + mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE; + return null; + } else { + // Delete the older apk pointed to by ps + // At this point, its safely assumed that package installation for + // apps in system partition will go through. If not there won't be a working + // version of the app + synchronized (mPackages) { + // Just remove the loaded entries from package lists. + mPackages.remove(ps.name); } + deletePackageResourcesLI(ps.name, ps.codePathString, ps.resourcePathString); + mSettings.enableSystemPackageLP(ps.name); } } } @@ -1871,7 +2052,7 @@ class PackageManagerService extends IPackageManager.Stub { scanMode |= SCAN_FORWARD_LOCKED; } File resFile = destResourceFile; - if ((scanMode & SCAN_FORWARD_LOCKED) != 0) { + if (ps != null && ((scanMode & SCAN_FORWARD_LOCKED) != 0)) { resFile = getFwdLockedResource(ps.name); } // Note that we invoke the following method only if we are about to unpack an application @@ -2030,17 +2211,62 @@ class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { // Check all shared libraries and map to their actual file path. - if (pkg.usesLibraryFiles != null) { - for (int i=0; i<pkg.usesLibraryFiles.length; i++) { - String file = mSharedLibraries.get(pkg.usesLibraryFiles[i]); + if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) { + if (mTmpSharedLibraries == null || + mTmpSharedLibraries.length < mSharedLibraries.size()) { + mTmpSharedLibraries = new String[mSharedLibraries.size()]; + } + int num = 0; + int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0; + for (int i=0; i<N; i++) { + String file = mSharedLibraries.get(pkg.usesLibraries.get(i)); if (file == null) { Log.e(TAG, "Package " + pkg.packageName + " requires unavailable shared library " - + pkg.usesLibraryFiles[i] + "; ignoring!"); + + pkg.usesLibraries.get(i) + "; failing!"); mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY; return null; } - pkg.usesLibraryFiles[i] = file; + mTmpSharedLibraries[num] = file; + num++; + } + N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0; + for (int i=0; i<N; i++) { + String file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i)); + if (file == null) { + Log.w(TAG, "Package " + pkg.packageName + + " desires unavailable shared library " + + pkg.usesOptionalLibraries.get(i) + "; ignoring!"); + } else { + mTmpSharedLibraries[num] = file; + num++; + } + } + if (num > 0) { + pkg.usesLibraryFiles = new String[num]; + 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 (fi.name != null) { + if (mAvailableFeatures.get(fi.name) == null) { + Log.e(TAG, "Package " + pkg.packageName + + " requires unavailable feature " + + fi.name + "; failing!"); + mLastScanError = PackageManager.INSTALL_FAILED_MISSING_FEATURE; + return null; + } + } + } } } @@ -2904,9 +3130,9 @@ class PackageManagerService extends IPackageManager.Stub { allowed = true; } else if (p.info.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE || p.info.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) { - allowed = (checkSignaturesLP(p.owner, pkg) + allowed = (checkSignaturesLP(p.owner.mSignatures, pkg.mSignatures) == PackageManager.SIGNATURE_MATCH) - || (checkSignaturesLP(mPlatformPackage, pkg) + || (checkSignaturesLP(mPlatformPackage.mSignatures, pkg.mSignatures) == PackageManager.SIGNATURE_MATCH); if (p.info.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) { if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) { @@ -3439,9 +3665,18 @@ class PackageManagerService extends IPackageManager.Stub { mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this); - PackageInstalledInfo res; - synchronized (mInstallLock) { - res = installPackageLI(packageURI, flags, true, installerPackageName); + // Result object to be returned + PackageInstalledInfo res = new PackageInstalledInfo(); + res.returnCode = PackageManager.INSTALL_SUCCEEDED; + res.uid = -1; + res.pkg = null; + res.removedInfo = new PackageRemovedInfo(); + // Make a temporary copy of file from given packageURI + File tmpPackageFile = copyTempInstallFile(packageURI, res); + if (tmpPackageFile != null) { + synchronized (mInstallLock) { + installPackageLI(packageURI, flags, true, installerPackageName, tmpPackageFile, res); + } } if (observer != null) { try { @@ -3553,7 +3788,8 @@ class PackageManagerService extends IPackageManager.Stub { // First find the old package info and check signatures synchronized(mPackages) { oldPackage = mPackages.get(pkgName); - if(checkSignaturesLP(pkg, oldPackage) != PackageManager.SIGNATURE_MATCH) { + if(checkSignaturesLP(pkg.mSignatures, oldPackage.mSignatures) + != PackageManager.SIGNATURE_MATCH) { res.returnCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; return; } @@ -3626,14 +3862,13 @@ class PackageManagerService extends IPackageManager.Stub { final ApplicationInfo deletedPackageAppInfo = deletedPackage.applicationInfo; final ApplicationInfo installedPackageAppInfo = newPackage.applicationInfo; - if (!deletedPackageAppInfo.sourceDir - .equals(installedPackageAppInfo.sourceDir)) { - new File(deletedPackageAppInfo.sourceDir).delete(); - } - if (!deletedPackageAppInfo.publicSourceDir - .equals(installedPackageAppInfo.publicSourceDir)) { - new File(deletedPackageAppInfo.publicSourceDir).delete(); - } + deletePackageResourcesLI(pkgName, + !deletedPackageAppInfo.sourceDir + .equals(installedPackageAppInfo.sourceDir) + ? deletedPackageAppInfo.sourceDir : null, + !deletedPackageAppInfo.publicSourceDir + .equals(installedPackageAppInfo.publicSourceDir) + ? deletedPackageAppInfo.publicSourceDir : null); //update signature on the new package setting //this should always succeed, since we checked the //signature earlier. @@ -3655,11 +3890,30 @@ class PackageManagerService extends IPackageManager.Stub { // Since we failed to install the new package we need to restore the old // package that we deleted. if(deletedPkg) { + File restoreFile = new File(deletedPackage.mPath); + if (restoreFile == null) { + Log.e(TAG, "Failed allocating storage when restoring pkg : " + pkgName); + return; + } + File restoreTmpFile = createTempPackageFile(); + if (restoreTmpFile == null) { + Log.e(TAG, "Failed creating temp file when restoring pkg : " + pkgName); + return; + } + if (!FileUtils.copyFile(restoreFile, restoreTmpFile)) { + Log.e(TAG, "Failed copying temp file when restoring pkg : " + pkgName); + return; + } + PackageInstalledInfo restoreRes = new PackageInstalledInfo(); + restoreRes.removedInfo = new PackageRemovedInfo(); installPackageLI( - Uri.fromFile(new File(deletedPackage.mPath)), + Uri.fromFile(restoreFile), isForwardLocked(deletedPackage) ? PackageManager.INSTALL_FORWARD_LOCK - : 0, false, oldInstallerPackageName); + : 0, false, oldInstallerPackageName, restoreTmpFile, restoreRes); + if (restoreRes.returnCode != PackageManager.INSTALL_SUCCEEDED) { + Log.e(TAG, "Failed restoring pkg : " + pkgName + " after failed upgrade"); + } } } } @@ -3822,50 +4076,36 @@ class PackageManagerService extends IPackageManager.Stub { return new File(mAppInstallDir, publicZipFileName); } - private PackageInstalledInfo installPackageLI(Uri pPackageURI, - int pFlags, boolean newInstall, String installerPackageName) { - File tmpPackageFile = null; - String pkgName = null; - boolean forwardLocked = false; - boolean replacingExistingPackage = false; - // Result object to be returned - PackageInstalledInfo res = new PackageInstalledInfo(); - res.returnCode = PackageManager.INSTALL_SUCCEEDED; - res.uid = -1; - res.pkg = null; - res.removedInfo = new PackageRemovedInfo(); + private File copyTempInstallFile(Uri pPackageURI, + PackageInstalledInfo res) { + File tmpPackageFile = createTempPackageFile(); + int retCode = PackageManager.INSTALL_SUCCEEDED; + if (tmpPackageFile == null) { + res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + return null; + } - main_flow: try { - tmpPackageFile = createTempPackageFile(); - if (tmpPackageFile == null) { - res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - break main_flow; + if (pPackageURI.getScheme().equals("file")) { + final File srcPackageFile = new File(pPackageURI.getPath()); + // We copy the source package file to a temp file and then rename it to the + // destination file in order to eliminate a window where the package directory + // scanner notices the new package file but it's not completely copied yet. + if (!FileUtils.copyFile(srcPackageFile, tmpPackageFile)) { + Log.e(TAG, "Couldn't copy package file to temp file."); + retCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; } - tmpPackageFile.deleteOnExit(); // paranoia - if (pPackageURI.getScheme().equals("file")) { - final File srcPackageFile = new File(pPackageURI.getPath()); - // We copy the source package file to a temp file and then rename it to the - // destination file in order to eliminate a window where the package directory - // scanner notices the new package file but it's not completely copied yet. - if (!FileUtils.copyFile(srcPackageFile, tmpPackageFile)) { - Log.e(TAG, "Couldn't copy package file to temp file."); - res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - break main_flow; - } - } else if (pPackageURI.getScheme().equals("content")) { - ParcelFileDescriptor fd; - try { - fd = mContext.getContentResolver().openFileDescriptor(pPackageURI, "r"); - } catch (FileNotFoundException e) { - Log.e(TAG, "Couldn't open file descriptor from download service."); - res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - break main_flow; - } - if (fd == null) { - Log.e(TAG, "Couldn't open file descriptor from download service (null)."); - res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - break main_flow; - } + } else if (pPackageURI.getScheme().equals("content")) { + ParcelFileDescriptor fd = null; + try { + fd = mContext.getContentResolver().openFileDescriptor(pPackageURI, "r"); + } catch (FileNotFoundException e) { + Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e); + retCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + } + if (fd == null) { + Log.e(TAG, "Couldn't open file descriptor from download service (null)."); + retCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + } else { if (Config.LOGV) { Log.v(TAG, "Opened file descriptor from download service."); } @@ -3876,14 +4116,34 @@ class PackageManagerService extends IPackageManager.Stub { // scanner notices the new package file but it's not completely copied yet. if (!FileUtils.copyToFile(dlStream, tmpPackageFile)) { Log.e(TAG, "Couldn't copy package stream to temp file."); - res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - break main_flow; + retCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; } - } else { - Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); - res.returnCode = PackageManager.INSTALL_FAILED_INVALID_URI; - break main_flow; } + } else { + Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); + retCode = PackageManager.INSTALL_FAILED_INVALID_URI; + } + + res.returnCode = retCode; + if (retCode != PackageManager.INSTALL_SUCCEEDED) { + if (tmpPackageFile != null && tmpPackageFile.exists()) { + tmpPackageFile.delete(); + } + return null; + } + return tmpPackageFile; + } + + private void installPackageLI(Uri pPackageURI, + int pFlags, boolean newInstall, String installerPackageName, + File tmpPackageFile, PackageInstalledInfo res) { + String pkgName = null; + boolean forwardLocked = false; + boolean replacingExistingPackage = false; + // Result object to be returned + res.returnCode = PackageManager.INSTALL_SUCCEEDED; + + main_flow: try { pkgName = PackageParser.parsePackageName( tmpPackageFile.getAbsolutePath(), 0); if (pkgName == null) { @@ -3911,7 +4171,6 @@ class PackageManagerService extends IPackageManager.Stub { parseFlags |= mDefParseFlags; PackageParser pp = new PackageParser(tmpPackageFile.getPath()); pp.setSeparateProcesses(mSeparateProcesses); - pp.setSdkVersion(mSdkVersion, mSdkCodename); final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile, destPackageFile.getAbsolutePath(), mMetrics, parseFlags); if (pkg == null) { @@ -3955,7 +4214,6 @@ class PackageManagerService extends IPackageManager.Stub { tmpPackageFile.delete(); } } - return res; } private int setPermissionsLI(String pkgName, @@ -4292,22 +4550,30 @@ class PackageManagerService extends IPackageManager.Stub { private void deletePackageResourcesLI(String packageName, String sourceDir, String publicSourceDir) { - File sourceFile = new File(sourceDir); - if (!sourceFile.exists()) { - Log.w(TAG, "Package source " + sourceDir + " does not exist."); - } - // Delete application's code and resources - sourceFile.delete(); - final File publicSourceFile = new File(publicSourceDir); - if (publicSourceFile.exists()) { - publicSourceFile.delete(); + if (sourceDir != null) { + File sourceFile = new File(sourceDir); + if (!sourceFile.exists()) { + Log.w(TAG, "Package source " + sourceDir + " does not exist."); + } + // Delete application's code and resources + sourceFile.delete(); + if (mInstaller != null) { + int retCode = mInstaller.rmdex(sourceFile.toString()); + if (retCode < 0) { + Log.w(TAG, "Couldn't remove dex file for package: " + + packageName + " at location " + + sourceFile.toString() + ", retcode=" + retCode); + // we don't consider this to be a failure of the core package deletion + } + } } - if (mInstaller != null) { - int retCode = mInstaller.rmdex(sourceFile.toString()); - if (retCode < 0) { - Log.w(TAG, "Couldn't remove dex file for package: " - + packageName + " at location " + sourceFile.toString() + ", retcode=" + retCode); - // we don't consider this to be a failure of the core package deletion + if (publicSourceDir != null && !publicSourceDir.equals(sourceDir)) { + final File publicSourceFile = new File(publicSourceDir); + if (!publicSourceFile.exists()) { + Log.w(TAG, "Package public source " + publicSourceFile + " does not exist."); + } + if (publicSourceFile.exists()) { + publicSourceFile.delete(); } } } @@ -4743,7 +5009,7 @@ class PackageManagerService extends IPackageManager.Stub { } private void setEnabledSetting( - final String packageNameStr, String classNameStr, int newState, final int flags) { + final String packageName, String className, int newState, final int flags) { if (!(newState == COMPONENT_ENABLED_STATE_DEFAULT || newState == COMPONENT_ENABLED_STATE_ENABLED || newState == COMPONENT_ENABLED_STATE_DISABLED)) { @@ -4755,17 +5021,20 @@ class PackageManagerService extends IPackageManager.Stub { final int permission = mContext.checkCallingPermission( android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE); final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); + boolean sendNow = false; + boolean isApp = (className == null); + String key = isApp ? packageName : className; int packageUid = -1; synchronized (mPackages) { - pkgSetting = mSettings.mPackages.get(packageNameStr); + pkgSetting = mSettings.mPackages.get(packageName); if (pkgSetting == null) { - if (classNameStr == null) { + if (className == null) { throw new IllegalArgumentException( - "Unknown package: " + packageNameStr); + "Unknown package: " + packageName); } throw new IllegalArgumentException( - "Unknown component: " + packageNameStr - + "/" + classNameStr); + "Unknown component: " + packageName + + "/" + className); } if (!allowedByPermission && (uid != pkgSetting.userId)) { throw new SecurityException( @@ -4773,41 +5042,67 @@ class PackageManagerService extends IPackageManager.Stub { + Binder.getCallingPid() + ", uid=" + uid + ", package uid=" + pkgSetting.userId); } - packageUid = pkgSetting.userId; - if (classNameStr == null) { + if (className == null) { // We're dealing with an application/package level state change pkgSetting.enabled = newState; } else { // We're dealing with a component level state change switch (newState) { case COMPONENT_ENABLED_STATE_ENABLED: - pkgSetting.enableComponentLP(classNameStr); + pkgSetting.enableComponentLP(className); break; case COMPONENT_ENABLED_STATE_DISABLED: - pkgSetting.disableComponentLP(classNameStr); + pkgSetting.disableComponentLP(className); break; case COMPONENT_ENABLED_STATE_DEFAULT: - pkgSetting.restoreComponentLP(classNameStr); + pkgSetting.restoreComponentLP(className); break; default: Log.e(TAG, "Invalid new component state: " + newState); + return; } } mSettings.writeLP(); + packageUid = pkgSetting.userId; + if ((flags&PackageManager.DONT_KILL_APP) == 0) { + sendNow = true; + // Purge entry from pending broadcast list if another one exists already + // since we are sending one right away. + if (mPendingBroadcasts.get(key) != null) { + mPendingBroadcasts.remove(key); + // Can ignore empty list since its handled in the handler anyway + } + } else { + if (mPendingBroadcasts.get(key) == null) { + mPendingBroadcasts.put(key, packageName); + } + if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) { + // Schedule a message + mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, BROADCAST_DELAY); + } + } } - + long callingId = Binder.clearCallingIdentity(); try { - Bundle extras = new Bundle(2); - extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, - (flags&PackageManager.DONT_KILL_APP) != 0); - extras.putInt(Intent.EXTRA_UID, packageUid); - sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageNameStr, extras); + if (sendNow) { + sendPackageChangedBroadcast(packageName, + (flags&PackageManager.DONT_KILL_APP) != 0, key, packageUid); + } } finally { Binder.restoreCallingIdentity(callingId); } } + private void sendPackageChangedBroadcast(String packageName, + boolean killFlag, String componentName, int packageUid) { + Bundle extras = new Bundle(2); + extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentName); + extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag); + extras.putInt(Intent.EXTRA_UID, packageUid); + sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras); + } + public String getInstallerPackageName(String packageName) { synchronized (mPackages) { PackageSetting pkg = mSettings.mPackages.get(packageName); @@ -5029,6 +5324,15 @@ class PackageManagerService extends IPackageManager.Stub { pw.println("Settings parse messages:"); pw.println(mSettings.mReadMessages.toString()); } + + synchronized (mProviders) { + pw.println(" "); + pw.println("Registered ContentProviders:"); + for (PackageParser.Provider p : mProviders.values()) { + pw.println(" ["); pw.println(p.info.authority); pw.println("]: "); + pw.println(p.toString()); + } + } } static final class BasePermission { @@ -5475,7 +5779,7 @@ class PackageManagerService extends IPackageManager.Stub { } static class GrantedPermissions { - final int pkgFlags; + int pkgFlags; HashSet<String> grantedPermissions = new HashSet<String>(); int[] gids; @@ -5893,10 +6197,10 @@ class PackageManagerService extends IPackageManager.Stub { // Let the app continue with previous uid if code path changes. reportSettingsProblem(Log.WARN, "Package " + name + " codePath changed from " + p.codePath - + " to " + codePath + "; Retaining data and using new code from " + - codePath); + + " to " + codePath + "; Retaining data and using new"); } - } else if (p.sharedUser != sharedUser) { + } + if (p.sharedUser != sharedUser) { reportSettingsProblem(Log.WARN, "Package " + name + " shared user changed from " + (p.sharedUser != null ? p.sharedUser.name : "<nothing>") @@ -5904,6 +6208,13 @@ class PackageManagerService extends IPackageManager.Stub { + (sharedUser != null ? sharedUser.name : "<nothing>") + "; replacing with new"); p = null; + } else { + if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0) { + // If what we are scanning is a system package, then + // make it so, regardless of whether it was previously + // installed only in the data partition. + p.pkgFlags |= ApplicationInfo.FLAG_SYSTEM; + } } } if (p == null) { @@ -5964,14 +6275,14 @@ class PackageManagerService extends IPackageManager.Stub { // Update code path if needed if (!codePath.toString().equalsIgnoreCase(p.codePathString)) { Log.w(TAG, "Code path for pkg : " + p.pkg.packageName + - " changing form " + p.codePathString + " to " + codePath); + " changing from " + p.codePathString + " to " + codePath); p.codePath = codePath; p.codePathString = codePath.toString(); } //Update resource path if needed if (!resourcePath.toString().equalsIgnoreCase(p.resourcePathString)) { Log.w(TAG, "Resource path for pkg : " + p.pkg.packageName + - " changing form " + p.resourcePathString + " to " + resourcePath); + " changing from " + p.resourcePathString + " to " + resourcePath); p.resourcePath = resourcePath; p.resourcePathString = resourcePath.toString(); } @@ -6033,7 +6344,9 @@ class PackageManagerService extends IPackageManager.Stub { continue; } for (PackageSetting pkg:sus.packages) { - if (pkg.pkg.requestedPermissions.contains(eachPerm)) { + if (pkg.pkg != null && + !pkg.pkg.packageName.equalsIgnoreCase(deletedPs.pkg.packageName) && + pkg.pkg.requestedPermissions.contains(eachPerm)) { used = true; break; } @@ -6048,7 +6361,9 @@ class PackageManagerService extends IPackageManager.Stub { int newGids[] = globalGids; for (String eachPerm : sus.grantedPermissions) { BasePermission bp = mPermissions.get(eachPerm); - newGids = appendInts(newGids, bp.gids); + if (bp != null) { + newGids = appendInts(newGids, bp.gids); + } } sus.gids = newGids; } @@ -6129,10 +6444,18 @@ class PackageManagerService extends IPackageManager.Stub { // Keep the old settings around until we know the new ones have // been successfully written. if (mSettingsFilename.exists()) { - if (mBackupSettingsFilename.exists()) { - mBackupSettingsFilename.delete(); + // Presence of backup settings file indicates that we failed + // to persist settings earlier. So preserve the older + // backup for future reference since the current settings + // might have been corrupted. + if (!mBackupSettingsFilename.exists()) { + if (!mSettingsFilename.renameTo(mBackupSettingsFilename)) { + Log.w(TAG, "Unable to backup package manager settings, current changes will be lost at reboot"); + return; + } + } else { + Log.w(TAG, "Preserving older settings backup"); } - mSettingsFilename.renameTo(mBackupSettingsFilename); } mPastSignatures.clear(); @@ -6217,15 +6540,19 @@ class PackageManagerService extends IPackageManager.Stub { |FileUtils.S_IRGRP|FileUtils.S_IWGRP |FileUtils.S_IROTH, -1, -1); + return; } catch(XmlPullParserException e) { Log.w(TAG, "Unable to write package manager settings, current changes will be lost at reboot", e); - } catch(java.io.IOException e) { Log.w(TAG, "Unable to write package manager settings, current changes will be lost at reboot", e); - } - + // Clean up partially written file + if (mSettingsFilename.exists()) { + if (!mSettingsFilename.delete()) { + Log.i(TAG, "Failed to clean up mangled file: " + mSettingsFilename); + } + } //Debug.stopMethodTracing(); } @@ -6394,6 +6721,13 @@ class PackageManagerService extends IPackageManager.Stub { str = new FileInputStream(mBackupSettingsFilename); mReadMessages.append("Reading from backup settings file\n"); Log.i(TAG, "Reading from backup settings file!"); + if (mSettingsFilename.exists()) { + // If both the backup and settings file exist, we + // ignore the settings since it might have been + // corrupted. + Log.w(TAG, "Cleaning up settings file " + mSettingsFilename); + mSettingsFilename.delete(); + } } catch (java.io.IOException e) { // We'll try for the normal settings file. } diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index 79d78ad..e1425d4 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -28,7 +28,12 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.database.Cursor; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; import android.os.BatteryStats; import android.os.Binder; import android.os.Handler; @@ -48,6 +53,8 @@ import android.util.Log; import android.view.WindowManagerPolicy; import static android.provider.Settings.System.DIM_SCREEN; import static android.provider.Settings.System.SCREEN_BRIGHTNESS; +import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE; +import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; import static android.provider.Settings.System.STAY_ON_WHILE_PLUGGED_IN; @@ -58,7 +65,8 @@ import java.util.HashMap; import java.util.Observable; import java.util.Observer; -class PowerManagerService extends IPowerManager.Stub implements LocalPowerManager, Watchdog.Monitor { +class PowerManagerService extends IPowerManager.Stub + implements LocalPowerManager, Watchdog.Monitor { private static final String TAG = "PowerManagerService"; static final String PARTIAL_NAME = "PowerManagerService"; @@ -72,7 +80,8 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage private static final int LOCK_MASK = PowerManager.PARTIAL_WAKE_LOCK | PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.SCREEN_BRIGHT_WAKE_LOCK - | PowerManager.FULL_WAKE_LOCK; + | PowerManager.FULL_WAKE_LOCK + | PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK; // time since last state: time since last event: // The short keylight delay comes from Gservices; this is the default. @@ -81,6 +90,15 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage private static final int LONG_KEYLIGHT_DELAY = 6000; // t+6 sec private static final int LONG_DIM_TIME = 7000; // t+N-5 sec + // How long to wait to debounce light sensor changes. + private static final int LIGHT_SENSOR_DELAY = 2000; + + // For debouncing the proximity sensor. + private static final int PROXIMITY_SENSOR_DELAY = 1000; + + // trigger proximity if distance is less than 5 cm + private static final float PROXIMITY_THRESHOLD = 5.0f; + // Cached Gservices settings; see updateGservicesValues() private int mShortKeylightDelay = SHORT_KEYLIGHT_DELAY_DEFAULT; @@ -116,6 +134,8 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage static final boolean ANIMATE_KEYBOARD_LIGHTS = false; static final int ANIM_STEPS = 60/4; + // Slower animation for autobrightness changes + static final int AUTOBRIGHTNESS_ANIM_STEPS = 60; // These magic numbers are the initial state of the LEDs at boot. Ideally // we should read them from the driver, but our current hardware returns 0 @@ -143,6 +163,11 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage private int mUserState; private boolean mKeyboardVisible = false; private boolean mUserActivityAllowed = true; + private int mProximityWakeLockCount = 0; + private boolean mProximitySensorEnabled = false; + private boolean mProximitySensorActive = false; + private int mProximityPendingValue = -1; // -1 == nothing, 0 == inactive, 1 == active + private long mLastProximityEventTime; private int mTotalDelaySetting; private int mKeylightDelay; private int mDimDelay; @@ -175,16 +200,34 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage private IActivityManager mActivityService; private IBatteryStats mBatteryStats; private BatteryService mBatteryService; + private SensorManager mSensorManager; + private Sensor mProximitySensor; + private Sensor mLightSensor; + private boolean mLightSensorEnabled; + private float mLightSensorValue = -1; + private float mLightSensorPendingValue = -1; + private int mLightSensorBrightness = -1; private boolean mDimScreen = true; private long mNextTimeout; private volatile int mPokey = 0; private volatile boolean mPokeAwakeOnSet = false; private volatile boolean mInitComplete = false; private HashMap<IBinder,PokeLock> mPokeLocks = new HashMap<IBinder,PokeLock>(); + // mScreenOnTime and mScreenOnStartTime are used for computing total time screen + // has been on since boot private long mScreenOnTime; private long mScreenOnStartTime; + // mLastScreenOnTime is the time the screen was last turned on + private long mLastScreenOnTime; private boolean mPreventScreenOn; private int mScreenBrightnessOverride = -1; + private boolean mUseSoftwareAutoBrightness; + private boolean mAutoBrightessEnabled; + private int[] mAutoBrightnessLevels; + private int[] mLcdBacklightValues; + private int[] mButtonBacklightValues; + private int[] mKeyboardBacklightValues; + private int mLightSensorWarmupTime; // Used when logging number and duration of touch-down cycles private long mTotalTouchDownTime; @@ -193,6 +236,8 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage // could be either static or controllable at runtime private static final boolean mSpew = false; + private static final boolean mDebugProximitySensor = (true || mSpew); + private static final boolean mDebugLightSensor = (false || mSpew); /* static PrintStream mLog; @@ -290,10 +335,7 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage // temporarily set mUserActivityAllowed to true so this will work // even when the keyguard is on. synchronized (mLocks) { - boolean savedActivityAllowed = mUserActivityAllowed; - mUserActivityAllowed = true; - userActivity(SystemClock.uptimeMillis(), false); - mUserActivityAllowed = savedActivityAllowed; + forceUserActivityLocked(); } } } @@ -333,6 +375,9 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage // DIM_SCREEN //mDimScreen = getInt(DIM_SCREEN) != 0; + // SCREEN_BRIGHTNESS_MODE + setScreenBrightnessMode(getInt(SCREEN_BRIGHTNESS_MODE)); + // recalculate everything setScreenOffTimeoutsLocked(); } @@ -404,12 +449,32 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF); mScreenOffIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - ContentResolver resolver = mContext.getContentResolver(); + Resources resources = mContext.getResources(); + + // read settings for auto-brightness + mUseSoftwareAutoBrightness = resources.getBoolean( + com.android.internal.R.bool.config_automatic_brightness_available); + if (mUseSoftwareAutoBrightness) { + mAutoBrightnessLevels = resources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLevels); + mLcdBacklightValues = resources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); + mButtonBacklightValues = resources.getIntArray( + com.android.internal.R.array.config_autoBrightnessButtonBacklightValues); + mKeyboardBacklightValues = resources.getIntArray( + com.android.internal.R.array.config_autoBrightnessKeyboardBacklightValues); + mLightSensorWarmupTime = resources.getInteger( + com.android.internal.R.integer.config_lightSensorWarmupTime); + } + + ContentResolver resolver = mContext.getContentResolver(); Cursor settingsCursor = resolver.query(Settings.System.CONTENT_URI, null, "(" + Settings.System.NAME + "=?) or (" + Settings.System.NAME + "=?) or (" + + Settings.System.NAME + "=?) or (" + Settings.System.NAME + "=?)", - new String[]{STAY_ON_WHILE_PLUGGED_IN, SCREEN_OFF_TIMEOUT, DIM_SCREEN}, + new String[]{STAY_ON_WHILE_PLUGGED_IN, SCREEN_OFF_TIMEOUT, DIM_SCREEN, + SCREEN_BRIGHTNESS_MODE}, null); mSettings = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, mHandler); SettingsObserver settingsObserver = new SettingsObserver(); @@ -430,9 +495,14 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage // And explicitly do the initial update of our cached settings updateGservicesValues(); - // turn everything on - setPowerState(ALL_BRIGHT); - + if (mUseSoftwareAutoBrightness) { + // turn the screen on + setPowerState(SCREEN_BRIGHT); + } else { + // turn everything on + setPowerState(ALL_BRIGHT); + } + synchronized (mHandlerThread) { mInitComplete = true; mHandlerThread.notifyAll(); @@ -527,7 +597,11 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage switch (wl.flags & LOCK_MASK) { case PowerManager.FULL_WAKE_LOCK: - wl.minState = (mKeyboardVisible ? ALL_BRIGHT : SCREEN_BUTTON_BRIGHT); + if (mUseSoftwareAutoBrightness) { + wl.minState = SCREEN_BRIGHT; + } else { + wl.minState = (mKeyboardVisible ? ALL_BRIGHT : SCREEN_BUTTON_BRIGHT); + } break; case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: wl.minState = SCREEN_BRIGHT; @@ -536,6 +610,7 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage wl.minState = SCREEN_DIM; break; case PowerManager.PARTIAL_WAKE_LOCK: + case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: break; default: // just log and bail. we're in the server, so don't @@ -583,6 +658,11 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage } } Power.acquireWakeLock(Power.PARTIAL_WAKE_LOCK,PARTIAL_NAME); + } else if ((flags & LOCK_MASK) == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) { + mProximityWakeLockCount++; + if (mProximityWakeLockCount == 1) { + enableProximityLockLocked(); + } } if (newlock) { acquireUid = wl.uid; @@ -639,6 +719,18 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage if (LOG_PARTIAL_WL) EventLog.writeEvent(LOG_POWER_PARTIAL_WAKE_STATE, 0, wl.tag); Power.releaseWakeLock(PARTIAL_NAME); } + } else if ((wl.flags & LOCK_MASK) == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) { + mProximityWakeLockCount--; + if (mProximityWakeLockCount == 0) { + if (mProximitySensorActive) { + // wait for proximity sensor to go negative before disabling sensor + if (mDebugProximitySensor) { + Log.d(TAG, "waiting for proximity sensor to go negative"); + } + } else { + disableProximityLockLocked(); + } + } } // Unlink the lock from the binder. wl.binder.unlinkToDeath(wl, 0); @@ -745,15 +837,17 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage switch (type) { case PowerManager.FULL_WAKE_LOCK: - return "FULL_WAKE_LOCK "; + return "FULL_WAKE_LOCK "; case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: - return "SCREEN_BRIGHT_WAKE_LOCK"; + return "SCREEN_BRIGHT_WAKE_LOCK "; case PowerManager.SCREEN_DIM_WAKE_LOCK: - return "SCREEN_DIM_WAKE_LOCK "; + return "SCREEN_DIM_WAKE_LOCK "; case PowerManager.PARTIAL_WAKE_LOCK: - return "PARTIAL_WAKE_LOCK "; + return "PARTIAL_WAKE_LOCK "; + case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: + return "PROXIMITY_SCREEN_OFF_WAKE_LOCK"; default: - return "??? "; + return "??? "; } } @@ -808,10 +902,21 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage pw.println(" mPreventScreenOn=" + mPreventScreenOn + " mScreenBrightnessOverride=" + mScreenBrightnessOverride); pw.println(" mTotalDelaySetting=" + mTotalDelaySetting); + pw.println(" mLastScreenOnTime=" + mLastScreenOnTime); pw.println(" mBroadcastWakeLock=" + mBroadcastWakeLock); pw.println(" mStayOnWhilePluggedInScreenDimLock=" + mStayOnWhilePluggedInScreenDimLock); pw.println(" mStayOnWhilePluggedInPartialLock=" + mStayOnWhilePluggedInPartialLock); pw.println(" mPreventScreenOnPartialLock=" + mPreventScreenOnPartialLock); + pw.println(" mProximityWakeLockCount=" + mProximityWakeLockCount); + pw.println(" mProximitySensorEnabled=" + mProximitySensorEnabled); + pw.println(" mProximitySensorActive=" + mProximitySensorActive); + pw.println(" mProximityPendingValue=" + mProximityPendingValue); + pw.println(" mLastProximityEventTime=" + mLastProximityEventTime); + pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); + pw.println(" mLightSensorValue=" + mLightSensorValue); + pw.println(" mLightSensorPendingValue=" + mLightSensorPendingValue); + pw.println(" mUseSoftwareAutoBrightness=" + mUseSoftwareAutoBrightness); + pw.println(" mAutoBrightessEnabled=" + mAutoBrightessEnabled); mScreenBrightness.dump(pw, " mScreenBrightness: "); mKeyboardBrightness.dump(pw, " mKeyboardBrightness: "); mButtonBrightness.dump(pw, " mButtonBrightness: "); @@ -1136,7 +1241,7 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage // Finally, set the flag that prevents the screen from turning on. // (Below, in setPowerState(), we'll check mPreventScreenOn and - // we *won't* call Power.setScreenState(true) if it's set.) + // we *won't* call setScreenStateLocked(true) if it's set.) mPreventScreenOn = true; } else { // (Re)enable the screen. @@ -1154,9 +1259,9 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage Log.d(TAG, "preventScreenOn: turning on after a prior preventScreenOn(true)!"); } - int err = Power.setScreenState(true); + int err = setScreenStateLocked(true); if (err != 0) { - Log.w(TAG, "preventScreenOn: error from Power.setScreenState(): " + err); + Log.w(TAG, "preventScreenOn: error from setScreenStateLocked(): " + err); } } @@ -1211,6 +1316,30 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage } }; + private int setScreenStateLocked(boolean on) { + int err = Power.setScreenState(on); + if (err == 0) { + mLastScreenOnTime = (on ? SystemClock.elapsedRealtime() : 0); + if (mUseSoftwareAutoBrightness) { + enableLightSensor(on); + if (!on) { + // make sure button and key backlights are off too + int brightnessMode = (mUseSoftwareAutoBrightness + ? HardwareService.BRIGHTNESS_MODE_SENSOR + : HardwareService.BRIGHTNESS_MODE_USER); + mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BUTTONS, 0, + brightnessMode); + mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_KEYBOARD, 0, + brightnessMode); + // clear current value so we will update based on the new conditions + // when the sensor is reenabled. + mLightSensorValue = -1; + } + } + } + return err; + } + private void setPowerState(int state) { setPowerState(state, false, false); @@ -1230,6 +1359,10 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage if (noChangeLights) { newState = (newState & ~LIGHTS_MASK) | (mPowerState & LIGHTS_MASK); } + if (mProximitySensorActive) { + // don't turn on the screen when the proximity sensor lock is held + newState = (newState & ~SCREEN_BRIGHT); + } if (batteryIsLow()) { newState |= BATTERY_LOW_BIT; @@ -1239,15 +1372,15 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage if (newState == mPowerState) { return; } - - if (!mDoneBooting) { + + if (!mDoneBooting && !mUseSoftwareAutoBrightness) { newState |= ALL_BRIGHT; } boolean oldScreenOn = (mPowerState & SCREEN_ON_BIT) != 0; boolean newScreenOn = (newState & SCREEN_ON_BIT) != 0; - if (mSpew) { + if (mPowerState != newState) { Log.d(TAG, "setPowerState: mPowerState=" + mPowerState + " newState=" + newState + " noChangeLights=" + noChangeLights); Log.d(TAG, " oldKeyboardBright=" + ((mPowerState & KEYBOARD_BRIGHT_BIT) != 0) @@ -1295,7 +1428,7 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage reallyTurnScreenOn = false; } if (reallyTurnScreenOn) { - err = Power.setScreenState(true); + err = setScreenStateLocked(true); long identity = Binder.clearCallingIdentity(); try { mBatteryStats.noteScreenBrightness( @@ -1307,7 +1440,7 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage Binder.restoreCallingIdentity(identity); } } else { - Power.setScreenState(false); + setScreenStateLocked(false); // But continue as if we really did turn the screen on... err = 0; } @@ -1323,6 +1456,8 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage sendNotificationLocked(true, -1); } } else { + // cancel light sensor task + mHandler.removeCallbacks(mAutoBrightnessTask); mScreenOffTime = SystemClock.elapsedRealtime(); long identity = Binder.clearCallingIdentity(); try { @@ -1352,7 +1487,7 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage EventLog.writeEvent(LOG_POWER_SCREEN_STATE, 0, becauseOfUser ? 1 : 0, mTotalTouchDownTime, mTouchCycles); mLastTouchDown = 0; - int err = Power.setScreenState(false); + int err = setScreenStateLocked(false); if (mScreenOnStartTime != 0) { mScreenOnTime += SystemClock.elapsedRealtime() - mScreenOnStartTime; mScreenOnStartTime = 0; @@ -1499,9 +1634,10 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage } finally { Binder.restoreCallingIdentity(identity); } - mScreenBrightness.setTargetLocked(brightness, - steps, INITIAL_SCREEN_BRIGHTNESS, nominalCurrentValue); - startAnimation = true; + if (mScreenBrightness.setTargetLocked(brightness, + steps, INITIAL_SCREEN_BRIGHTNESS, nominalCurrentValue)) { + startAnimation = true; + } } else { if ((newState & SCREEN_BRIGHT_BIT) == 0) { // dim or turn off backlight, depending on if the screen is on @@ -1549,14 +1685,23 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage } private void setLightBrightness(int mask, int value) { + int brightnessMode = (mAutoBrightessEnabled + ? HardwareService.BRIGHTNESS_MODE_SENSOR + : HardwareService.BRIGHTNESS_MODE_USER); if ((mask & SCREEN_BRIGHT_BIT) != 0) { - mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BACKLIGHT, value); + mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BACKLIGHT, value, + brightnessMode); } + brightnessMode = (mUseSoftwareAutoBrightness + ? HardwareService.BRIGHTNESS_MODE_SENSOR + : HardwareService.BRIGHTNESS_MODE_USER); if ((mask & BUTTON_BRIGHT_BIT) != 0) { - mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BUTTONS, value); + mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BUTTONS, value, + brightnessMode); } if ((mask & KEYBOARD_BRIGHT_BIT) != 0) { - mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_KEYBOARD, value); + mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_KEYBOARD, value, + brightnessMode); } } @@ -1580,11 +1725,13 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage + " delta=" + delta); } - void setTargetLocked(int target, int stepsToTarget, int initialValue, + boolean setTargetLocked(int target, int stepsToTarget, int initialValue, int nominalCurrentValue) { if (!initialized) { initialized = true; curValue = (float)initialValue; + } else if (targetValue == target) { + return false; } targetValue = target; delta = (targetValue - @@ -1598,6 +1745,7 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage + noticeMe); } animating = true; + return true; } boolean stepLocked() { @@ -1657,6 +1805,9 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage try { if (mScreenBrightnessOverride >= 0) { return mScreenBrightnessOverride; + } else if (mLightSensorBrightness >= 0 && mUseSoftwareAutoBrightness + && mAutoBrightessEnabled) { + return mLightSensorBrightness; } final int brightness = Settings.System.getInt(mContext.getContentResolver(), SCREEN_BRIGHTNESS); @@ -1667,18 +1818,31 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage } } - boolean screenIsOn() { + public boolean isScreenOn() { synchronized (mLocks) { return (mPowerState & SCREEN_ON_BIT) != 0; } } - boolean screenIsBright() { + boolean isScreenBright() { synchronized (mLocks) { return (mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT; } } + private boolean isScreenTurningOffLocked() { + return (mScreenBrightness.animating && mScreenBrightness.targetValue == 0); + } + + private void forceUserActivityLocked() { + // cancel animation so userActivity will succeed + mScreenBrightness.animating = false; + boolean savedActivityAllowed = mUserActivityAllowed; + mUserActivityAllowed = true; + userActivity(SystemClock.uptimeMillis(), false); + mUserActivityAllowed = savedActivityAllowed; + } + public void userActivityWithForce(long time, boolean noChangeLights, boolean force) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); userActivity(time, noChangeLights, OTHER_EVENT, force); @@ -1712,7 +1876,6 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage return; } - if (false) { if (((mPokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0)) { Log.d(TAG, "userActivity !!!");//, new RuntimeException()); @@ -1726,13 +1889,21 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage Log.d(TAG, "userActivity mLastEventTime=" + mLastEventTime + " time=" + time + " mUserActivityAllowed=" + mUserActivityAllowed + " mUserState=0x" + Integer.toHexString(mUserState) - + " mWakeLockState=0x" + Integer.toHexString(mWakeLockState)); + + " mWakeLockState=0x" + Integer.toHexString(mWakeLockState) + + " mProximitySensorActive=" + mProximitySensorActive + + " force=" + force); + } + // ignore user activity if we are in the process of turning off the screen + if (isScreenTurningOffLocked()) { + Log.d(TAG, "ignoring user activity while turning off screen"); + return; } if (mLastEventTime <= time || force) { mLastEventTime = time; - if (mUserActivityAllowed || force) { - // Only turn on button backlights if a button was pressed. - if (eventType == BUTTON_EVENT) { + if ((mUserActivityAllowed && !mProximitySensorActive) || force) { + // Only turn on button backlights if a button was pressed + // and auto brightness is disabled + if (eventType == BUTTON_EVENT && !mUseSoftwareAutoBrightness) { mUserState = (mKeyboardVisible ? ALL_BRIGHT : SCREEN_BUTTON_BRIGHT); } else { // don't clear button/keyboard backlights when the screen is touched. @@ -1757,6 +1928,122 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage } } + private int getAutoBrightnessValue(int sensorValue, int[] values) { + try { + int i; + for (i = 0; i < mAutoBrightnessLevels.length; i++) { + if (sensorValue < mAutoBrightnessLevels[i]) { + break; + } + } + return values[i]; + } catch (Exception e) { + // guard against null pointer or index out of bounds errors + Log.e(TAG, "getAutoBrightnessValue", e); + return 255; + } + } + + private Runnable mProximityTask = new Runnable() { + public void run() { + synchronized (mLocks) { + if (mProximityPendingValue != -1) { + proximityChangedLocked(mProximityPendingValue == 1); + mProximityPendingValue = -1; + } + } + } + }; + + private Runnable mAutoBrightnessTask = new Runnable() { + public void run() { + synchronized (mLocks) { + int value = (int)mLightSensorPendingValue; + if (value >= 0) { + mLightSensorPendingValue = -1; + lightSensorChangedLocked(value); + } + } + } + }; + + private void lightSensorChangedLocked(int value) { + if (mDebugLightSensor) { + Log.d(TAG, "lightSensorChangedLocked " + value); + } + + if (mLightSensorValue != value) { + mLightSensorValue = value; + if ((mPowerState & BATTERY_LOW_BIT) == 0) { + int lcdValue = getAutoBrightnessValue(value, mLcdBacklightValues); + int buttonValue = getAutoBrightnessValue(value, mButtonBacklightValues); + int keyboardValue; + if (mKeyboardVisible) { + keyboardValue = getAutoBrightnessValue(value, mKeyboardBacklightValues); + } else { + keyboardValue = 0; + } + mLightSensorBrightness = lcdValue; + + if (mDebugLightSensor) { + Log.d(TAG, "lcdValue " + lcdValue); + Log.d(TAG, "buttonValue " + buttonValue); + Log.d(TAG, "keyboardValue " + keyboardValue); + } + + boolean startAnimation = false; + if (mAutoBrightessEnabled && mScreenBrightnessOverride < 0) { + if (ANIMATE_SCREEN_LIGHTS) { + if (mScreenBrightness.setTargetLocked(lcdValue, + AUTOBRIGHTNESS_ANIM_STEPS, INITIAL_SCREEN_BRIGHTNESS, + (int)mScreenBrightness.curValue)) { + startAnimation = true; + } + } else { + int brightnessMode = (mAutoBrightessEnabled + ? HardwareService.BRIGHTNESS_MODE_SENSOR + : HardwareService.BRIGHTNESS_MODE_USER); + mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BACKLIGHT, + lcdValue, brightnessMode); + } + } + if (ANIMATE_BUTTON_LIGHTS) { + if (mButtonBrightness.setTargetLocked(buttonValue, + AUTOBRIGHTNESS_ANIM_STEPS, INITIAL_BUTTON_BRIGHTNESS, + (int)mButtonBrightness.curValue)) { + startAnimation = true; + } + } else { + int brightnessMode = (mUseSoftwareAutoBrightness + ? HardwareService.BRIGHTNESS_MODE_SENSOR + : HardwareService.BRIGHTNESS_MODE_USER); + mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BUTTONS, + buttonValue, brightnessMode); + } + if (ANIMATE_KEYBOARD_LIGHTS) { + if (mKeyboardBrightness.setTargetLocked(keyboardValue, + AUTOBRIGHTNESS_ANIM_STEPS, INITIAL_BUTTON_BRIGHTNESS, + (int)mKeyboardBrightness.curValue)) { + startAnimation = true; + } + } else { + int brightnessMode = (mUseSoftwareAutoBrightness + ? HardwareService.BRIGHTNESS_MODE_SENSOR + : HardwareService.BRIGHTNESS_MODE_USER); + mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_KEYBOARD, + keyboardValue, brightnessMode); + } + if (startAnimation) { + if (mDebugLightSensor) { + Log.i(TAG, "lightSensorChangedLocked scheduling light animator"); + } + mHandler.removeCallbacks(mLightAnimator); + mHandler.post(mLightAnimator); + } + } + } + } + /** * The user requested that we go to sleep (probably with the power button). * This overrides all wake locks that are held. @@ -1816,16 +2103,60 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage } public void setKeyboardVisibility(boolean visible) { - mKeyboardVisible = visible; + synchronized (mLocks) { + if (mSpew) { + Log.d(TAG, "setKeyboardVisibility: " + visible); + } + if (mKeyboardVisible != visible) { + mKeyboardVisible = visible; + // don't signal user activity if the screen is off; other code + // will take care of turning on due to a true change to the lid + // switch and synchronized with the lock screen. + if ((mPowerState & SCREEN_ON_BIT) != 0) { + if (mUseSoftwareAutoBrightness) { + // force recompute of backlight values + if (mLightSensorValue >= 0) { + int value = (int)mLightSensorValue; + mLightSensorValue = -1; + lightSensorChangedLocked(value); + } + } + userActivity(SystemClock.uptimeMillis(), false, BUTTON_EVENT, true); + } + } + } } /** * When the keyguard is up, it manages the power state, and userActivity doesn't do anything. + * When disabling user activity we also reset user power state so the keyguard can reset its + * short screen timeout when keyguard is unhidden. */ public void enableUserActivity(boolean enabled) { + if (mSpew) { + Log.d(TAG, "enableUserActivity " + enabled); + } synchronized (mLocks) { mUserActivityAllowed = enabled; - mLastEventTime = SystemClock.uptimeMillis(); // we might need to pass this in + if (!enabled) { + // cancel timeout and clear mUserState so the keyguard can set a short timeout + setTimeoutLocked(SystemClock.uptimeMillis(), 0); + } + } + } + + 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); + } + } } } @@ -1975,6 +2306,14 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage } void systemReady() { + mSensorManager = new SensorManager(mHandlerThread.getLooper()); + mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); + // don't bother with the light sensor if auto brightness is handled in hardware + if (mUseSoftwareAutoBrightness) { + mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); + enableLightSensor(true); + } + synchronized (mLocks) { Log.d(TAG, "system ready!"); mDoneBooting = true; @@ -1996,4 +2335,206 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage public void monitor() { synchronized (mLocks) { } } + + public int getSupportedWakeLockFlags() { + int result = PowerManager.PARTIAL_WAKE_LOCK + | PowerManager.FULL_WAKE_LOCK + | PowerManager.SCREEN_DIM_WAKE_LOCK; + + if (mProximitySensor != null) { + result |= PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK; + } + + return result; + } + + public void setBacklightBrightness(int brightness) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + // Don't let applications turn the screen all the way off + brightness = Math.max(brightness, Power.BRIGHTNESS_DIM); + mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BACKLIGHT, brightness, + HardwareService.BRIGHTNESS_MODE_USER); + mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_KEYBOARD, + (mKeyboardVisible ? brightness : 0), HardwareService.BRIGHTNESS_MODE_USER); + mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BUTTONS, brightness, + HardwareService.BRIGHTNESS_MODE_USER); + long identity = Binder.clearCallingIdentity(); + try { + mBatteryStats.noteScreenBrightness(brightness); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException calling noteScreenBrightness on BatteryStatsService", e); + } finally { + Binder.restoreCallingIdentity(identity); + } + + // update our animation state + if (ANIMATE_SCREEN_LIGHTS) { + mScreenBrightness.curValue = brightness; + mScreenBrightness.animating = false; + mScreenBrightness.targetValue = -1; + } + if (ANIMATE_KEYBOARD_LIGHTS) { + mKeyboardBrightness.curValue = brightness; + mKeyboardBrightness.animating = false; + mKeyboardBrightness.targetValue = -1; + } + if (ANIMATE_BUTTON_LIGHTS) { + mButtonBrightness.curValue = brightness; + mButtonBrightness.animating = false; + mButtonBrightness.targetValue = -1; + } + } + + private void enableProximityLockLocked() { + if (mDebugProximitySensor) { + Log.d(TAG, "enableProximityLockLocked"); + } + if (!mProximitySensorEnabled) { + // clear calling identity so sensor manager battery stats are accurate + long identity = Binder.clearCallingIdentity(); + try { + mSensorManager.registerListener(mProximityListener, mProximitySensor, + SensorManager.SENSOR_DELAY_NORMAL); + mProximitySensorEnabled = true; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + private void disableProximityLockLocked() { + if (mDebugProximitySensor) { + Log.d(TAG, "disableProximityLockLocked"); + } + if (mProximitySensorEnabled) { + // clear calling identity so sensor manager battery stats are accurate + long identity = Binder.clearCallingIdentity(); + try { + mSensorManager.unregisterListener(mProximityListener); + mHandler.removeCallbacks(mProximityTask); + mProximitySensorEnabled = false; + } finally { + Binder.restoreCallingIdentity(identity); + } + if (mProximitySensorActive) { + mProximitySensorActive = false; + forceUserActivityLocked(); + } + } + } + + private void proximityChangedLocked(boolean active) { + if (mDebugProximitySensor) { + Log.d(TAG, "proximityChangedLocked, active: " + active); + } + if (!mProximitySensorEnabled) { + Log.d(TAG, "Ignoring proximity change after sensor is disabled"); + return; + } + if (active) { + goToSleepLocked(SystemClock.uptimeMillis()); + mProximitySensorActive = true; + } else { + // proximity sensor negative events trigger as user activity. + // temporarily set mUserActivityAllowed to true so this will work + // even when the keyguard is on. + mProximitySensorActive = false; + forceUserActivityLocked(); + + if (mProximityWakeLockCount == 0) { + // disable sensor if we have no listeners left after proximity negative + disableProximityLockLocked(); + } + } + } + + private void enableLightSensor(boolean enable) { + if (mDebugLightSensor) { + Log.d(TAG, "enableLightSensor " + enable); + } + if (mSensorManager != null && mLightSensorEnabled != enable) { + mLightSensorEnabled = enable; + // clear calling identity so sensor manager battery stats are accurate + long identity = Binder.clearCallingIdentity(); + try { + if (enable) { + mSensorManager.registerListener(mLightListener, mLightSensor, + SensorManager.SENSOR_DELAY_NORMAL); + } else { + mSensorManager.unregisterListener(mLightListener); + mHandler.removeCallbacks(mAutoBrightnessTask); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + SensorEventListener mProximityListener = new SensorEventListener() { + public void onSensorChanged(SensorEvent event) { + long milliseconds = SystemClock.elapsedRealtime(); + synchronized (mLocks) { + float distance = event.values[0]; + long timeSinceLastEvent = milliseconds - mLastProximityEventTime; + mLastProximityEventTime = milliseconds; + mHandler.removeCallbacks(mProximityTask); + + // compare against getMaximumRange to support sensors that only return 0 or 1 + boolean active = (distance >= 0.0 && distance < PROXIMITY_THRESHOLD && + distance < mProximitySensor.getMaximumRange()); + + if (mDebugProximitySensor) { + Log.d(TAG, "mProximityListener.onSensorChanged active: " + active); + } + if (timeSinceLastEvent < PROXIMITY_SENSOR_DELAY) { + // enforce delaying atleast PROXIMITY_SENSOR_DELAY before processing + mProximityPendingValue = (active ? 1 : 0); + mHandler.postDelayed(mProximityTask, PROXIMITY_SENSOR_DELAY - timeSinceLastEvent); + } else { + // process the value immediately + mProximityPendingValue = -1; + proximityChangedLocked(active); + } + } + } + + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // ignore + } + }; + + SensorEventListener mLightListener = new SensorEventListener() { + public void onSensorChanged(SensorEvent event) { + synchronized (mLocks) { + // ignore light sensor while screen is turning off + if (isScreenTurningOffLocked()) { + return; + } + + int value = (int)event.values[0]; + long milliseconds = SystemClock.elapsedRealtime(); + if (mDebugLightSensor) { + Log.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 { + // delay processing to debounce the sensor + mLightSensorPendingValue = value; + mHandler.postDelayed(mAutoBrightnessTask, LIGHT_SENSOR_DELAY); + } + } else { + mLightSensorPendingValue = -1; + } + } + } + + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // ignore + } + }; } diff --git a/services/java/com/android/server/ProcessStats.java b/services/java/com/android/server/ProcessStats.java index af80e20..ac3b723 100644 --- a/services/java/com/android/server/ProcessStats.java +++ b/services/java/com/android/server/ProcessStats.java @@ -30,6 +30,7 @@ import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.StringTokenizer; public class ProcessStats { private static final String TAG = "ProcessStats"; @@ -138,7 +139,22 @@ public class ProcessStats { private boolean mFirst = true; private byte[] mBuffer = new byte[256]; - + + /** + * The time in microseconds that the CPU has been running at each speed. + */ + private long[] mCpuSpeedTimes; + + /** + * The relative time in microseconds that the CPU has been running at each speed. + */ + private long[] mRelCpuSpeedTimes; + + /** + * The different speeds that the CPU can be running at. + */ + private long[] mCpuSpeeds; + public static class Stats { public final int pid; final String statFile; @@ -460,6 +476,70 @@ public class ProcessStats { return 0; } + /** + * Returns the times spent at each CPU speed, since the last call to this method. If this + * is the first time, it will return 1 for each value. + * @return relative times spent at different speed steps. + */ + public long[] getLastCpuSpeedTimes() { + if (mCpuSpeedTimes == null) { + mCpuSpeedTimes = getCpuSpeedTimes(null); + mRelCpuSpeedTimes = new long[mCpuSpeedTimes.length]; + for (int i = 0; i < mCpuSpeedTimes.length; i++) { + mRelCpuSpeedTimes[i] = 1; // Initialize + } + } else { + getCpuSpeedTimes(mRelCpuSpeedTimes); + for (int i = 0; i < mCpuSpeedTimes.length; i++) { + long temp = mRelCpuSpeedTimes[i]; + mRelCpuSpeedTimes[i] -= mCpuSpeedTimes[i]; + mCpuSpeedTimes[i] = temp; + } + } + return mRelCpuSpeedTimes; + } + + private long[] getCpuSpeedTimes(long[] out) { + long[] tempTimes = out; + long[] tempSpeeds = mCpuSpeeds; + final int MAX_SPEEDS = 20; + if (out == null) { + tempTimes = new long[MAX_SPEEDS]; // Hopefully no more than that + tempSpeeds = new long[MAX_SPEEDS]; + } + int speed = 0; + String file = readFile("/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state", '\0'); + // Note: file may be null on kernels without cpufreq (i.e. the emulator's) + if (file != null) { + StringTokenizer st = new StringTokenizer(file, "\n "); + while (st.hasMoreElements()) { + String token = st.nextToken(); + try { + long val = Long.parseLong(token); + tempSpeeds[speed] = val; + token = st.nextToken(); + val = Long.parseLong(token); + tempTimes[speed] = val; + speed++; + if (speed == MAX_SPEEDS) break; // No more + if (localLOGV && out == null) { + Log.v(TAG, "First time : Speed/Time = " + tempSpeeds[speed - 1] + + "\t" + tempTimes[speed - 1]); + } + } catch (NumberFormatException nfe) { + Log.i(TAG, "Unable to parse time_in_state"); + } + } + } + if (out == null) { + out = new long[speed]; + mCpuSpeeds = new long[speed]; + System.arraycopy(tempSpeeds, 0, mCpuSpeeds, 0, speed); + System.arraycopy(tempTimes, 0, out, 0, speed); + } + return out; + } + final public int getLastUserTime() { return mRelUserTime; } diff --git a/services/java/com/android/server/RandomBlock.java b/services/java/com/android/server/RandomBlock.java index 4ac1c6e..f7847ec 100644 --- a/services/java/com/android/server/RandomBlock.java +++ b/services/java/com/android/server/RandomBlock.java @@ -32,13 +32,14 @@ import java.io.RandomAccessFile; class RandomBlock { private static final String TAG = "RandomBlock"; + private static final boolean DEBUG = false; private static final int BLOCK_SIZE = 4096; private byte[] block = new byte[BLOCK_SIZE]; private RandomBlock() { } static RandomBlock fromFile(String filename) throws IOException { - Log.v(TAG, "reading from file " + filename); + if (DEBUG) Log.v(TAG, "reading from file " + filename); InputStream stream = null; try { stream = new FileInputStream(filename); @@ -62,7 +63,7 @@ class RandomBlock { } void toFile(String filename) throws IOException { - Log.v(TAG, "writing to file " + filename); + if (DEBUG) Log.v(TAG, "writing to file " + filename); RandomAccessFile out = null; try { out = new RandomAccessFile(filename, "rws"); diff --git a/services/java/com/android/server/SensorService.java b/services/java/com/android/server/SensorService.java index ceef39f..4dfeb9d 100644 --- a/services/java/com/android/server/SensorService.java +++ b/services/java/com/android/server/SensorService.java @@ -84,12 +84,16 @@ class SensorService extends ISensorService.Stub { if (hasSensor(sensor)) { removeSensor(sensor); try { - deactivateIfUnused(sensor); + deactivateIfUnusedLocked(sensor); } catch (RemoteException e) { Log.w(TAG, "RemoteException in binderDied"); } } } + if (mListeners.size() == 0) { + _sensors_control_wake(); + _sensors_control_close(); + } mListeners.notify(); } } @@ -102,9 +106,12 @@ class SensorService extends ISensorService.Stub { } public Bundle getDataChannel() throws RemoteException { - return _sensors_control_open(); + // synchronize so we do not require sensor HAL to be thread-safe. + synchronized(mListeners) { + return _sensors_control_open(); + } } - + public boolean enableSensor(IBinder binder, String name, int sensor, int enable) throws RemoteException { if (localLOGV) Log.d(TAG, "enableSensor " + name + "(#" + sensor + ") " + enable); @@ -163,7 +170,7 @@ class SensorService extends ISensorService.Stub { l.addSensor(sensor, enable); } else { l.removeSensor(sensor); - deactivateIfUnused(sensor); + deactivateIfUnusedLocked(sensor); if (l.mSensors == 0) { mListeners.remove(l); binder.unlinkToDeath(l, 0); @@ -173,12 +180,13 @@ class SensorService extends ISensorService.Stub { if (mListeners.size() == 0) { _sensors_control_wake(); + _sensors_control_close(); } } return true; } - void deactivateIfUnused(int sensor) throws RemoteException { + private void deactivateIfUnusedLocked(int sensor) throws RemoteException { int size = mListeners.size(); for (int i=0 ; i<size ; i++) { if (mListeners.get(i).hasSensor(sensor)) @@ -187,10 +195,11 @@ class SensorService extends ISensorService.Stub { _sensors_control_activate(sensor, false); } - ArrayList<Listener> mListeners = new ArrayList<Listener>(); + private ArrayList<Listener> mListeners = new ArrayList<Listener>(); private static native int _sensors_control_init(); private static native Bundle _sensors_control_open(); + private static native int _sensors_control_close(); private static native boolean _sensors_control_activate(int sensor, boolean activate); private static native int _sensors_control_set_delay(int ms); private static native int _sensors_control_wake(); diff --git a/services/java/com/android/server/ShutdownActivity.java b/services/java/com/android/server/ShutdownActivity.java new file mode 100644 index 0000000..7f0e90d --- /dev/null +++ b/services/java/com/android/server/ShutdownActivity.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +import com.android.internal.app.ShutdownThread; + +public class ShutdownActivity extends Activity { + + private static final String TAG = "ShutdownActivity"; + private boolean mConfirm; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mConfirm = getIntent().getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false); + Log.i(TAG, "onCreate(): confirm=" + mConfirm); + + Handler h = new Handler(); + h.post(new Runnable() { + public void run() { + ShutdownThread.shutdown(ShutdownActivity.this, mConfirm); + } + }); + } +} diff --git a/services/java/com/android/server/SystemBackupAgent.java b/services/java/com/android/server/SystemBackupAgent.java new file mode 100644 index 0000000..8903ebd --- /dev/null +++ b/services/java/com/android/server/SystemBackupAgent.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.backup.AbsoluteFileBackupHelper; +import android.backup.BackupDataInput; +import android.backup.BackupDataInputStream; +import android.backup.BackupDataOutput; +import android.backup.BackupHelper; +import android.backup.BackupHelperAgent; +import android.content.Context; +import android.os.ParcelFileDescriptor; +import android.os.ServiceManager; +import android.os.SystemService; +import android.util.Log; + +import java.io.File; +import java.io.IOException; + +/** + * Backup agent for various system-managed data, currently just the system wallpaper + */ +public class SystemBackupAgent extends BackupHelperAgent { + private static final String TAG = "SystemBackupAgent"; + + // These paths must match what the WallpaperManagerService uses + private static final String WALLPAPER_IMAGE = "/data/data/com.android.settings/files/wallpaper"; + private static final String WALLPAPER_INFO = "/data/system/wallpaper_info.xml"; + + @Override + public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState) throws IOException { + // We only back up the data under the current "wallpaper" schema with metadata + addHelper("wallpaper", new AbsoluteFileBackupHelper(SystemBackupAgent.this, + new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO })); + super.onBackup(oldState, data, newState); + } + + @Override + 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, + new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO })); + addHelper("system_files", new AbsoluteFileBackupHelper(SystemBackupAgent.this, + new String[] { WALLPAPER_IMAGE })); + + boolean success = false; + try { + super.onRestore(data, appVersionCode, newState); + + WallpaperManagerService wallpaper = (WallpaperManagerService)ServiceManager.getService( + Context.WALLPAPER_SERVICE); + wallpaper.settingsRestored(); + } catch (IOException ex) { + // If there was a failure, delete everything for the wallpaper, this is too aggresive, + // but this is hopefully a rare failure. + Log.d(TAG, "restore failed", ex); + (new File(WALLPAPER_IMAGE)).delete(); + (new File(WALLPAPER_INFO)).delete(); + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index b2848ac..b8cf844 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -18,10 +18,12 @@ package com.android.server; import com.android.server.am.ActivityManagerService; import com.android.server.status.StatusBarService; +import com.android.internal.os.SamplingProfilerIntegration; import dalvik.system.VMRuntime; import android.app.ActivityManagerNative; +import android.bluetooth.BluetoothAdapter; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentService; @@ -31,23 +33,22 @@ import android.content.pm.IPackageManager; import android.database.ContentObserver; import android.database.Cursor; import android.media.AudioService; -import android.os.Looper; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.os.SystemProperties; +import android.os.*; import android.provider.Contacts.People; import android.provider.Settings; import android.server.BluetoothA2dpService; -import android.server.BluetoothDeviceService; +import android.server.BluetoothService; import android.server.search.SearchManagerService; import android.util.EventLog; import android.util.Log; +import android.accounts.AccountManagerService; + +import java.util.Timer; +import java.util.TimerTask; class ServerThread extends Thread { private static final String TAG = "SystemServer"; private final static boolean INCLUDE_DEMO = false; - private final static boolean INCLUDE_BACKUP = false; private static final int LOG_BOOT_PROGRESS_SYSTEM_RUN = 3010; @@ -84,31 +85,34 @@ class ServerThread extends Thread { HardwareService hardware = null; PowerManagerService power = null; + BatteryService battery = null; + ConnectivityService connectivity = null; IPackageManager pm = null; Context context = null; WindowManagerService wm = null; - BluetoothDeviceService bluetooth = null; + BluetoothService bluetooth = null; BluetoothA2dpService bluetoothA2dp = null; HeadsetObserver headset = null; + DockObserver dock = null; // Critical services... try { - Log.i(TAG, "Starting Entropy Service."); + Log.i(TAG, "Entropy Service"); ServiceManager.addService("entropy", new EntropyService()); - Log.i(TAG, "Starting Power Manager."); + Log.i(TAG, "Power Manager"); power = new PowerManagerService(); ServiceManager.addService(Context.POWER_SERVICE, power); - Log.i(TAG, "Starting Activity Manager."); + Log.i(TAG, "Activity Manager"); context = ActivityManagerService.main(factoryTest); - Log.i(TAG, "Starting telephony registry"); + Log.i(TAG, "Telephony Registry"); ServiceManager.addService("telephony.registry", new TelephonyRegistry(context)); AttributeCache.init(context); - Log.i(TAG, "Starting Package Manager."); + Log.i(TAG, "Package Manager"); pm = PackageManagerService.main(context, factoryTest != SystemServer.FACTORY_TEST_OFF); @@ -116,18 +120,27 @@ class ServerThread extends Thread { mContentResolver = context.getContentResolver(); - Log.i(TAG, "Starting Content Manager."); + // The AccountManager must come before the ContentService + try { + Log.i(TAG, "Account Manager"); + ServiceManager.addService(Context.ACCOUNT_SERVICE, + new AccountManagerService(context)); + } catch (Throwable e) { + Log.e(TAG, "Failure starting Account Manager", e); + } + + Log.i(TAG, "Content Manager"); ContentService.main(context, factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL); - Log.i(TAG, "Starting System Content Providers."); + Log.i(TAG, "System Content Providers"); ActivityManagerService.installSystemProviders(); - Log.i(TAG, "Starting Battery Service."); - BatteryService battery = new BatteryService(context); + Log.i(TAG, "Battery Service"); + battery = new BatteryService(context); ServiceManager.addService("battery", battery); - Log.i(TAG, "Starting Hardware Service."); + Log.i(TAG, "Hardware Service"); hardware = new HardwareService(context); ServiceManager.addService("hardware", hardware); @@ -135,18 +148,19 @@ class ServerThread extends Thread { // hardware service, content providers and the battery service. power.init(context, hardware, ActivityManagerService.getDefault(), battery); - Log.i(TAG, "Starting Alarm Manager."); + Log.i(TAG, "Alarm Manager"); AlarmManagerService alarm = new AlarmManagerService(context); ServiceManager.addService(Context.ALARM_SERVICE, alarm); + Log.i(TAG, "Init Watchdog"); Watchdog.getInstance().init(context, battery, power, alarm, ActivityManagerService.self()); // Sensor Service is needed by Window Manager, so this goes first - Log.i(TAG, "Starting Sensor Service."); + Log.i(TAG, "Sensor Service"); ServiceManager.addService(Context.SENSOR_SERVICE, new SensorService(context)); - Log.i(TAG, "Starting Window Manager."); + Log.i(TAG, "Window Manager"); wm = WindowManagerService.main(context, power, factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL); ServiceManager.addService(Context.WINDOW_SERVICE, wm); @@ -159,16 +173,16 @@ class ServerThread extends Thread { // support Bluetooth - see bug 988521 if (SystemProperties.get("ro.kernel.qemu").equals("1")) { Log.i(TAG, "Registering null Bluetooth Service (emulator)"); - ServiceManager.addService(Context.BLUETOOTH_SERVICE, null); + ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, null); } else if (factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) { Log.i(TAG, "Registering null Bluetooth Service (factory test)"); - ServiceManager.addService(Context.BLUETOOTH_SERVICE, null); + ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, null); } else { - Log.i(TAG, "Starting Bluetooth Service."); - bluetooth = new BluetoothDeviceService(context); - bluetooth.init(); - ServiceManager.addService(Context.BLUETOOTH_SERVICE, bluetooth); - bluetoothA2dp = new BluetoothA2dpService(context); + Log.i(TAG, "Bluetooth Service"); + bluetooth = new BluetoothService(context); + ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, bluetooth); + bluetooth.initAfterRegistration(); + bluetoothA2dp = new BluetoothA2dpService(context, bluetooth); ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE, bluetoothA2dp); @@ -187,10 +201,11 @@ class ServerThread extends Thread { InputMethodManagerService imm = null; AppWidgetService appWidget = null; NotificationManagerService notification = null; + WallpaperManagerService wallpaper = null; if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { try { - Log.i(TAG, "Starting Status Bar Service."); + Log.i(TAG, "Status Bar"); statusBar = new StatusBarService(context); ServiceManager.addService("statusbar", statusBar); } catch (Throwable e) { @@ -198,14 +213,14 @@ class ServerThread extends Thread { } try { - Log.i(TAG, "Starting Clipboard Service."); + Log.i(TAG, "Clipboard Service"); ServiceManager.addService("clipboard", new ClipboardService(context)); } catch (Throwable e) { Log.e(TAG, "Failure starting Clipboard Service", e); } try { - Log.i(TAG, "Starting Input Method Service."); + Log.i(TAG, "Input Method Service"); imm = new InputMethodManagerService(context, statusBar); ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm); } catch (Throwable e) { @@ -213,22 +228,22 @@ class ServerThread extends Thread { } try { - Log.i(TAG, "Starting NetStat Service."); + Log.i(TAG, "NetStat Service"); ServiceManager.addService("netstat", new NetStatService(context)); } catch (Throwable e) { Log.e(TAG, "Failure starting NetStat Service", e); } try { - Log.i(TAG, "Starting Connectivity Service."); - ServiceManager.addService(Context.CONNECTIVITY_SERVICE, - ConnectivityService.getInstance(context)); + Log.i(TAG, "Connectivity Service"); + connectivity = ConnectivityService.getInstance(context); + ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity); } catch (Throwable e) { Log.e(TAG, "Failure starting Connectivity Service", e); } try { - Log.i(TAG, "Starting Accessibility Manager."); + Log.i(TAG, "Accessibility Manager"); ServiceManager.addService(Context.ACCESSIBILITY_SERVICE, new AccessibilityManagerService(context)); } catch (Throwable e) { @@ -236,7 +251,7 @@ class ServerThread extends Thread { } try { - Log.i(TAG, "Starting Notification Manager."); + Log.i(TAG, "Notification Manager"); notification = new NotificationManagerService(context, statusBar, hardware); ServiceManager.addService(Context.NOTIFICATION_SERVICE, notification); } catch (Throwable e) { @@ -245,14 +260,14 @@ class ServerThread extends Thread { try { // MountService must start after NotificationManagerService - Log.i(TAG, "Starting Mount Service."); + Log.i(TAG, "Mount Service"); ServiceManager.addService("mount", new MountService(context)); } catch (Throwable e) { Log.e(TAG, "Failure starting Mount Service", e); } try { - Log.i(TAG, "Starting DeviceStorageMonitor service"); + Log.i(TAG, "Device Storage Monitor"); ServiceManager.addService(DeviceStorageMonitorService.SERVICE, new DeviceStorageMonitorService(context)); } catch (Throwable e) { @@ -260,14 +275,14 @@ class ServerThread extends Thread { } try { - Log.i(TAG, "Starting Location Manager."); + Log.i(TAG, "Location Manager"); ServiceManager.addService(Context.LOCATION_SERVICE, new LocationManagerService(context)); } catch (Throwable e) { Log.e(TAG, "Failure starting Location Manager", e); } try { - Log.i(TAG, "Starting Search Service."); + Log.i(TAG, "Search Service"); ServiceManager.addService( Context.SEARCH_SERVICE, new SearchManagerService(context) ); } catch (Throwable e) { Log.e(TAG, "Failure starting Search Service", e); @@ -279,7 +294,7 @@ class ServerThread extends Thread { } try { - Log.i(TAG, "Starting Checkin Service."); + Log.i(TAG, "Checkin Service"); Intent intent = new Intent().setComponent(new ComponentName( "com.google.android.server.checkin", "com.google.android.server.checkin.CheckinService")); @@ -292,21 +307,22 @@ class ServerThread extends Thread { } try { - Log.i(TAG, "Starting Wallpaper Service"); - ServiceManager.addService(Context.WALLPAPER_SERVICE, new WallpaperService(context)); + Log.i(TAG, "Wallpaper Service"); + wallpaper = new WallpaperManagerService(context); + ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper); } catch (Throwable e) { Log.e(TAG, "Failure starting Wallpaper Service", e); } try { - Log.i(TAG, "Starting Audio Service"); + Log.i(TAG, "Audio Service"); ServiceManager.addService(Context.AUDIO_SERVICE, new AudioService(context)); } catch (Throwable e) { Log.e(TAG, "Failure starting Audio Service", e); } try { - Log.i(TAG, "Starting HeadsetObserver"); + Log.i(TAG, "Headset Observer"); // Listen for wired headset changes headset = new HeadsetObserver(context); } catch (Throwable e) { @@ -314,16 +330,22 @@ class ServerThread extends Thread { } try { - if (INCLUDE_BACKUP) { - Log.i(TAG, "Starting Backup Service"); - ServiceManager.addService(Context.BACKUP_SERVICE, new BackupManagerService(context)); - } + Log.i(TAG, "Dock Observer"); + // Listen for dock station changes + dock = new DockObserver(context, power); + } catch (Throwable e) { + Log.e(TAG, "Failure starting DockObserver", e); + } + + try { + Log.i(TAG, "Backup Service"); + ServiceManager.addService(Context.BACKUP_SERVICE, new BackupManagerService(context)); } catch (Throwable e) { Log.e(TAG, "Failure starting Backup Service", e); } try { - Log.i(TAG, "Starting AppWidget Service"); + Log.i(TAG, "AppWidget Service"); appWidget = new AppWidgetService(context); ServiceManager.addService(Context.APPWIDGET_SERVICE, appWidget); } catch (Throwable e) { @@ -345,8 +367,17 @@ class ServerThread extends Thread { mContentResolver.registerContentObserver(Settings.Secure.getUriFor(Settings.Secure.ADB_ENABLED), false, new AdbSettingsObserver()); + // Before things start rolling, be sure we have decided whether + // we are in safe mode. + final boolean safeMode = wm.detectSafeMode(); + if (safeMode) { + try { + ActivityManagerNative.getDefault().enterSafeMode(); + } catch (RemoteException e) { + } + } + // It is now time to start up the app processes... - boolean safeMode = wm.detectSafeMode(); if (notification != null) { notification.systemReady(); @@ -355,27 +386,45 @@ class ServerThread extends Thread { if (statusBar != null) { statusBar.systemReady(); } - if (imm != null) { - imm.systemReady(); - } wm.systemReady(); power.systemReady(); try { pm.systemReady(); } catch (RemoteException e) { } - if (appWidget != null) { - appWidget.systemReady(safeMode); - } - - // After making the following code, third party code may be running... - try { - ActivityManagerNative.getDefault().systemReady(); - } catch (RemoteException e) { - } - - Watchdog.getInstance().start(); + // These are needed to propagate to the runnable below. + final BatteryService batteryF = battery; + final ConnectivityService connectivityF = connectivity; + final DockObserver dockF = dock; + final AppWidgetService appWidgetF = appWidget; + final WallpaperManagerService wallpaperF = wallpaper; + final InputMethodManagerService immF = imm; + + // 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 + // where third party code can really run (but before it has actually + // started launching the initial applications), for us to complete our + // initialization. + ((ActivityManagerService)ActivityManagerNative.getDefault()) + .systemReady(new Runnable() { + public void run() { + Log.i(TAG, "Making services ready"); + + if (batteryF != null) batteryF.systemReady(); + if (connectivityF != null) connectivityF.systemReady(); + if (dockF != null) dockF.systemReady(); + Watchdog.getInstance().start(); + + // It is now okay to let the various system services start their + // third party code... + + if (appWidgetF != null) appWidgetF.systemReady(safeMode); + if (wallpaperF != null) wallpaperF.systemReady(); + if (immF != null) immF.systemReady(); + } + }); + Looper.loop(); Log.d(TAG, "System ServerThread is exiting!"); } @@ -417,19 +466,33 @@ public class SystemServer public static final int FACTORY_TEST_OFF = 0; public static final int FACTORY_TEST_LOW_LEVEL = 1; public static final int FACTORY_TEST_HIGH_LEVEL = 2; - - /** - * This method is called from Zygote to initialize the system. This will cause the native + + static Timer timer; + static final long SNAPSHOT_INTERVAL = 60 * 60 * 1000; // 1hr + + /** + * This method is called from Zygote to initialize the system. This will cause the native * services (SurfaceFlinger, AudioFlinger, etc..) to be started. After that it will call back * up into init2() to start the Android services. - */ + */ native public static void init1(String[] args); public static void main(String[] args) { + if (SamplingProfilerIntegration.isEnabled()) { + SamplingProfilerIntegration.start(); + timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + SamplingProfilerIntegration.writeSnapshot("system_server"); + } + }, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL); + } + // 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 9f2856c..47cb6ad 100644 --- a/services/java/com/android/server/TelephonyRegistry.java +++ b/services/java/com/android/server/TelephonyRegistry.java @@ -88,10 +88,14 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private String mDataConnectionApn = ""; + private String[] mDataConnectionApnTypes = null; + private String mDataConnectionInterfaceName = ""; private Bundle mCellLocation = new Bundle(); + private int mDataConnectionNetworkType; + static final int PHONE_STATE_PERMISSION_MASK = PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR | PhoneStateListener.LISTEN_CALL_STATE | @@ -107,7 +111,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { // handler before they get to app code. TelephonyRegistry(Context context) { - CellLocation.getEmpty().fillInNotifierBundle(mCellLocation); + CellLocation location = CellLocation.getEmpty(); + + // Note that location can be null for non-phone builds like + // like the generic one. + if (location != null) { + location.fillInNotifierBundle(mCellLocation); + } mContext = context; mBatteryStats = BatteryStatsService.getService(); } @@ -179,7 +189,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } if ((events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) { try { - r.callback.onDataConnectionStateChanged(mDataConnectionState); + r.callback.onDataConnectionStateChanged(mDataConnectionState, + mDataConnectionNetworkType); } catch (RemoteException ex) { remove(r.binder); } @@ -337,7 +348,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyDataConnection(int state, boolean isDataConnectivityPossible, - String reason, String apn, String interfaceName) { + String reason, String apn, String[] apnTypes, String interfaceName, int networkType) { if (!checkNotifyPermission("notifyDataConnection()" )) { return; } @@ -346,12 +357,14 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { 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); + r.callback.onDataConnectionStateChanged(state, networkType); } catch (RemoteException ex) { remove(r.binder); } @@ -359,7 +372,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn, - interfaceName); + apnTypes, interfaceName); } public void notifyDataConnectionFailed(String reason) { @@ -464,7 +477,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private void broadcastServiceStateChanged(ServiceState state) { long ident = Binder.clearCallingIdentity(); try { - mBatteryStats.noteAirplaneMode(state.getState() == ServiceState.STATE_POWER_OFF); + mBatteryStats.notePhoneState(state.getState()); } catch (RemoteException re) { // Can't do much } finally { @@ -517,8 +530,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mContext.sendBroadcast(intent, android.Manifest.permission.READ_PHONE_STATE); } - private void broadcastDataConnectionStateChanged(int state, boolean isDataConnectivityPossible, - String reason, String apn, String interfaceName) { + private void broadcastDataConnectionStateChanged(int state, + boolean isDataConnectivityPossible, + String reason, String apn, String[] apnTypes, String interfaceName) { // 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. @@ -531,6 +545,14 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { 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]; + } + } + intent.putExtra(Phone.DATA_APN_TYPES_KEY, types); intent.putExtra(Phone.DATA_IFACE_NAME_KEY, interfaceName); mContext.sendStickyBroadcast(intent); } diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java new file mode 100644 index 0000000..4b6049f --- /dev/null +++ b/services/java/com/android/server/WallpaperManagerService.java @@ -0,0 +1,702 @@ +/* + * 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 static android.os.FileObserver.*; +import static android.os.ParcelFileDescriptor.*; + +import android.app.IWallpaperManager; +import android.app.IWallpaperManagerCallback; +import android.app.PendingIntent; +import android.app.WallpaperInfo; +import android.backup.BackupManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.FileObserver; +import android.os.ParcelFileDescriptor; +import android.os.RemoteCallbackList; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.service.wallpaper.IWallpaperConnection; +import android.service.wallpaper.IWallpaperEngine; +import android.service.wallpaper.IWallpaperService; +import android.service.wallpaper.WallpaperService; +import android.util.Log; +import android.util.Xml; +import android.view.IWindowManager; +import android.view.WindowManager; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.util.List; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import com.android.internal.service.wallpaper.ImageWallpaper; +import com.android.internal.util.FastXmlSerializer; + +class WallpaperManagerService extends IWallpaperManager.Stub { + static final String TAG = "WallpaperService"; + static final boolean DEBUG = false; + + Object mLock = new Object(); + + /** + * Minimum time between crashes of a wallpaper service for us to consider + * restarting it vs. just reverting to the static wallpaper. + */ + static final long MIN_WALLPAPER_CRASH_TIME = 10000; + + static final File WALLPAPER_DIR = new File( + "/data/data/com.android.settings/files"); + static final String WALLPAPER = "wallpaper"; + static final File WALLPAPER_FILE = new File(WALLPAPER_DIR, WALLPAPER); + + /** + * List of callbacks registered they should each be notified + * when the wallpaper is changed. + */ + private final RemoteCallbackList<IWallpaperManagerCallback> mCallbacks + = new RemoteCallbackList<IWallpaperManagerCallback>(); + + /** + * Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks + * that the wallpaper has changed. The CREATE is triggered when there is no + * wallpaper set and is created for the first time. The CLOSE_WRITE is triggered + * everytime the wallpaper is changed. + */ + private final FileObserver mWallpaperObserver = new FileObserver( + WALLPAPER_DIR.getAbsolutePath(), CREATE | CLOSE_WRITE | DELETE | DELETE_SELF) { + @Override + public void onEvent(int event, String path) { + if (path == null) { + return; + } + synchronized (mLock) { + // changing the wallpaper means we'll need to back up the new one + long origId = Binder.clearCallingIdentity(); + BackupManager bm = new BackupManager(mContext); + bm.dataChanged(); + Binder.restoreCallingIdentity(origId); + + File changedFile = new File(WALLPAPER_DIR, path); + if (WALLPAPER_FILE.equals(changedFile)) { + notifyCallbacksLocked(); + } + } + } + }; + + final Context mContext; + final IWindowManager mIWindowManager; + + int mWidth = -1; + int mHeight = -1; + String mName = ""; + ComponentName mWallpaperComponent; + WallpaperConnection mWallpaperConnection; + long mLastDiedTime; + + class WallpaperConnection extends IWallpaperConnection.Stub + implements ServiceConnection { + final WallpaperInfo mInfo; + final Binder mToken = new Binder(); + IWallpaperService mService; + IWallpaperEngine mEngine; + + public WallpaperConnection(WallpaperInfo info) { + mInfo = info; + } + + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mLock) { + if (mWallpaperConnection == this) { + mService = IWallpaperService.Stub.asInterface(service); + attachServiceLocked(this); + // XXX should probably do saveSettingsLocked() later + // when we have an engine, but I'm not sure about + // locking there and anyway we always need to be able to + // recover if there is something wrong. + saveSettingsLocked(); + } + } + } + + public void onServiceDisconnected(ComponentName name) { + synchronized (mLock) { + mService = null; + mEngine = null; + if (mWallpaperConnection == this) { + Log.w(TAG, "Wallpaper service gone: " + mWallpaperComponent); + if ((mLastDiedTime+MIN_WALLPAPER_CRASH_TIME) + < SystemClock.uptimeMillis()) { + Log.w(TAG, "Reverting to built-in wallpaper!"); + bindWallpaperComponentLocked(null); + } + } + } + } + + public void attachEngine(IWallpaperEngine engine) { + mEngine = engine; + } + + public ParcelFileDescriptor setWallpaper(String name) { + synchronized (mLock) { + if (mWallpaperConnection == this) { + return updateWallpaperBitmapLocked(name); + } + return null; + } + } + } + + public WallpaperManagerService(Context context) { + if (DEBUG) Log.d(TAG, "WallpaperService startup"); + mContext = context; + mIWindowManager = IWindowManager.Stub.asInterface( + ServiceManager.getService(Context.WINDOW_SERVICE)); + WALLPAPER_DIR.mkdirs(); + loadSettingsLocked(); + mWallpaperObserver.startWatching(); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + mWallpaperObserver.stopWatching(); + } + + public void systemReady() { + synchronized (mLock) { + try { + bindWallpaperComponentLocked(mWallpaperComponent); + } catch (RuntimeException e) { + Log.w(TAG, "Failure starting previous wallpaper", e); + try { + bindWallpaperComponentLocked(null); + } catch (RuntimeException e2) { + Log.w(TAG, "Failure starting default wallpaper", e2); + clearWallpaperComponentLocked(); + } + } + } + } + + public void clearWallpaper() { + synchronized (mLock) { + File f = WALLPAPER_FILE; + if (f.exists()) { + f.delete(); + } + final long ident = Binder.clearCallingIdentity(); + try { + bindWallpaperComponentLocked(null); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + public void setDimensionHints(int width, int height) throws RemoteException { + checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS); + + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("width and height must be > 0"); + } + + synchronized (mLock) { + if (width != mWidth || height != mHeight) { + mWidth = width; + mHeight = height; + saveSettingsLocked(); + if (mWallpaperConnection != null) { + if (mWallpaperConnection.mEngine != null) { + try { + mWallpaperConnection.mEngine.setDesiredSize( + width, height); + } catch (RemoteException e) { + } + notifyCallbacksLocked(); + } + } + } + } + } + + public int getWidthHint() throws RemoteException { + synchronized (mLock) { + return mWidth; + } + } + + public int getHeightHint() throws RemoteException { + synchronized (mLock) { + return mHeight; + } + } + + public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb, + Bundle outParams) { + synchronized (mLock) { + try { + if (outParams != null) { + outParams.putInt("width", mWidth); + outParams.putInt("height", mHeight); + } + mCallbacks.register(cb); + File f = WALLPAPER_FILE; + if (!f.exists()) { + return null; + } + return ParcelFileDescriptor.open(f, MODE_READ_ONLY); + } catch (FileNotFoundException e) { + /* Shouldn't happen as we check to see if the file exists */ + Log.w(TAG, "Error getting wallpaper", e); + } + return null; + } + } + + public WallpaperInfo getWallpaperInfo() { + synchronized (mLock) { + if (mWallpaperConnection != null) { + return mWallpaperConnection.mInfo; + } + return null; + } + } + + public ParcelFileDescriptor setWallpaper(String name) { + checkPermission(android.Manifest.permission.SET_WALLPAPER); + synchronized (mLock) { + final long ident = Binder.clearCallingIdentity(); + try { + ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name); + if (pfd != null) { + bindWallpaperComponentLocked(null); + saveSettingsLocked(); + } + return pfd; + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + ParcelFileDescriptor updateWallpaperBitmapLocked(String name) { + if (name == null) name = ""; + try { + ParcelFileDescriptor fd = ParcelFileDescriptor.open(WALLPAPER_FILE, + MODE_CREATE|MODE_READ_WRITE); + mName = name; + return fd; + } catch (FileNotFoundException e) { + Log.w(TAG, "Error setting wallpaper", e); + } + return null; + } + + public void setWallpaperComponent(ComponentName name) { + checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT); + synchronized (mLock) { + final long ident = Binder.clearCallingIdentity(); + try { + bindWallpaperComponentLocked(name); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + void bindWallpaperComponentLocked(ComponentName name) { + // Has the component changed? + if (mWallpaperConnection != null) { + if (mWallpaperComponent == null) { + if (name == null) { + // Still using default wallpaper. + return; + } + } else if (mWallpaperComponent.equals(name)) { + // Changing to same wallpaper. + return; + } + } + + try { + ComponentName realName = name; + if (realName == null) { + // The default component is our static image wallpaper. + realName = new ComponentName("android", + ImageWallpaper.class.getName()); + //clearWallpaperComponentLocked(); + //return; + } + ServiceInfo si = mContext.getPackageManager().getServiceInfo(realName, + PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS); + if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) { + throw new SecurityException("Selected service does not require " + + android.Manifest.permission.BIND_WALLPAPER + + ": " + realName); + } + + WallpaperInfo wi = null; + + Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); + if (name != null) { + // Make sure the selected service is actually a wallpaper service. + List<ResolveInfo> ris = mContext.getPackageManager() + .queryIntentServices(intent, PackageManager.GET_META_DATA); + for (int i=0; i<ris.size(); i++) { + ServiceInfo rsi = ris.get(i).serviceInfo; + if (rsi.name.equals(si.name) && + rsi.packageName.equals(si.packageName)) { + try { + wi = new WallpaperInfo(mContext, ris.get(i)); + } catch (XmlPullParserException e) { + throw new IllegalArgumentException(e); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + break; + } + } + if (wi == null) { + throw new SecurityException("Selected service is not a wallpaper: " + + realName); + } + } + + // Bind the service! + WallpaperConnection newConn = new WallpaperConnection(wi); + intent.setComponent(realName); + intent.putExtra(Intent.EXTRA_CLIENT_LABEL, + com.android.internal.R.string.wallpaper_binding_label); + intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( + mContext, 0, + Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER), + mContext.getText(com.android.internal.R.string.chooser_wallpaper)), + 0)); + if (!mContext.bindService(intent, newConn, + Context.BIND_AUTO_CREATE)) { + throw new IllegalArgumentException("Unable to bind service: " + + name); + } + + clearWallpaperComponentLocked(); + mWallpaperComponent = name; + mWallpaperConnection = newConn; + mLastDiedTime = SystemClock.uptimeMillis(); + try { + if (DEBUG) Log.v(TAG, "Adding window token: " + newConn.mToken); + mIWindowManager.addWindowToken(newConn.mToken, + WindowManager.LayoutParams.TYPE_WALLPAPER); + } catch (RemoteException e) { + } + + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException("Unknown component " + name); + } + } + + void clearWallpaperComponentLocked() { + mWallpaperComponent = null; + if (mWallpaperConnection != null) { + if (mWallpaperConnection.mEngine != null) { + try { + mWallpaperConnection.mEngine.destroy(); + } catch (RemoteException e) { + } + } + mContext.unbindService(mWallpaperConnection); + try { + if (DEBUG) Log.v(TAG, "Removing window token: " + + mWallpaperConnection.mToken); + mIWindowManager.removeWindowToken(mWallpaperConnection.mToken); + } catch (RemoteException e) { + } + mWallpaperConnection = null; + } + } + + void attachServiceLocked(WallpaperConnection conn) { + try { + conn.mService.attach(conn, conn.mToken, + WindowManager.LayoutParams.TYPE_WALLPAPER, false, + mWidth, mHeight); + } catch (RemoteException e) { + Log.w(TAG, "Failed attaching wallpaper; clearing", e); + bindWallpaperComponentLocked(null); + } + } + + private void notifyCallbacksLocked() { + final int n = mCallbacks.beginBroadcast(); + for (int i = 0; i < n; i++) { + try { + mCallbacks.getBroadcastItem(i).onWallpaperChanged(); + } catch (RemoteException e) { + + // The RemoteCallbackList will take care of removing + // the dead object for us. + } + } + mCallbacks.finishBroadcast(); + final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED); + mContext.sendBroadcast(intent); + } + + private void checkPermission(String permission) { + if (PackageManager.PERMISSION_GRANTED!= mContext.checkCallingOrSelfPermission(permission)) { + throw new SecurityException("Access denied to process: " + Binder.getCallingPid() + + ", must have permission " + permission); + } + } + + private static JournaledFile makeJournaledFile() { + final String base = "/data/system/wallpaper_info.xml"; + return new JournaledFile(new File(base), new File(base + ".tmp")); + } + + private void saveSettingsLocked() { + JournaledFile journal = makeJournaledFile(); + FileOutputStream stream = null; + try { + stream = new FileOutputStream(journal.chooseForWrite(), false); + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(stream, "utf-8"); + out.startDocument(null, true); + + out.startTag(null, "wp"); + out.attribute(null, "width", Integer.toString(mWidth)); + out.attribute(null, "height", Integer.toString(mHeight)); + out.attribute(null, "name", mName); + if (mWallpaperComponent != null) { + out.attribute(null, "component", + mWallpaperComponent.flattenToShortString()); + } + out.endTag(null, "wp"); + + out.endDocument(); + stream.close(); + journal.commit(); + } catch (IOException e) { + try { + if (stream != null) { + stream.close(); + } + } catch (IOException ex) { + // Ignore + } + journal.rollback(); + } + } + + private void loadSettingsLocked() { + JournaledFile journal = makeJournaledFile(); + FileInputStream stream = null; + File file = journal.chooseForRead(); + boolean success = false; + try { + stream = new FileInputStream(file); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(stream, null); + + int type; + do { + type = parser.next(); + if (type == XmlPullParser.START_TAG) { + String tag = parser.getName(); + if ("wp".equals(tag)) { + mWidth = Integer.parseInt(parser.getAttributeValue(null, "width")); + mHeight = Integer.parseInt(parser.getAttributeValue(null, "height")); + mName = parser.getAttributeValue(null, "name"); + String comp = parser.getAttributeValue(null, "component"); + mWallpaperComponent = comp != null + ? ComponentName.unflattenFromString(comp) + : null; + } + } + } while (type != XmlPullParser.END_DOCUMENT); + success = true; + } catch (NullPointerException e) { + Log.w(TAG, "failed parsing " + file + " " + e); + } catch (NumberFormatException e) { + Log.w(TAG, "failed parsing " + file + " " + e); + } catch (XmlPullParserException e) { + Log.w(TAG, "failed parsing " + file + " " + e); + } catch (IOException e) { + Log.w(TAG, "failed parsing " + file + " " + e); + } catch (IndexOutOfBoundsException e) { + Log.w(TAG, "failed parsing " + file + " " + e); + } + try { + if (stream != null) { + stream.close(); + } + } catch (IOException e) { + // Ignore + } + + if (!success) { + mWidth = -1; + mHeight = -1; + mName = ""; + } + } + + void settingsRestored() { + boolean success = false; + synchronized (mLock) { + loadSettingsLocked(); + // If there's a wallpaper name, we use that. If that can't be loaded, then we + // use the default. + if ("".equals(mName)) { + success = true; + } else { + success = restoreNamedResourceLocked(); + } + } + + if (!success) { + Log.e(TAG, "Failed to restore wallpaper: '" + mName + "'"); + mName = ""; + WALLPAPER_FILE.delete(); + } + saveSettingsLocked(); + } + + boolean restoreNamedResourceLocked() { + if (mName.length() > 4 && "res:".equals(mName.substring(0, 4))) { + String resName = mName.substring(4); + + String pkg = null; + int colon = resName.indexOf(':'); + if (colon > 0) { + pkg = resName.substring(0, colon); + } + + String ident = null; + int slash = resName.lastIndexOf('/'); + if (slash > 0) { + ident = resName.substring(slash+1); + } + + String type = null; + if (colon > 0 && slash > 0 && (slash-colon) > 1) { + type = resName.substring(colon+1, slash); + } + + if (pkg != null && ident != null && type != null) { + int resId = -1; + InputStream res = null; + FileOutputStream fos = null; + try { + Context c = mContext.createPackageContext(pkg, Context.CONTEXT_RESTRICTED); + Resources r = c.getResources(); + resId = r.getIdentifier(resName, null, null); + if (resId == 0) { + Log.e(TAG, "couldn't resolve identifier pkg=" + pkg + " type=" + type + + " ident=" + ident); + return false; + } + + res = r.openRawResource(resId); + fos = new FileOutputStream(WALLPAPER_FILE); + + byte[] buffer = new byte[32768]; + int amt; + while ((amt=res.read(buffer)) > 0) { + fos.write(buffer, 0, amt); + } + // mWallpaperObserver will notice the close and send the change broadcast + + Log.d(TAG, "Restored wallpaper: " + resName); + return true; + } catch (NameNotFoundException e) { + Log.e(TAG, "Package name " + pkg + " not found"); + } catch (Resources.NotFoundException e) { + Log.e(TAG, "Resource not found: " + resId); + } catch (IOException e) { + Log.e(TAG, "IOException while restoring wallpaper ", e); + } finally { + if (res != null) { + try { + res.close(); + } catch (IOException ex) {} + } + if (fos != null) { + try { + fos.close(); + } catch (IOException ex) {} + } + } + } + } + return false; + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + + pw.println("Permission Denial: can't dump wallpaper service from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + synchronized (mLock) { + pw.println("Current Wallpaper Service state:"); + pw.print(" mWidth="); pw.print(mWidth); + pw.print(" mHeight="); pw.println(mHeight); + pw.print(" mName="); pw.println(mName); + pw.print(" mWallpaperComponent="); pw.println(mWallpaperComponent); + if (mWallpaperConnection != null) { + WallpaperConnection conn = mWallpaperConnection; + pw.print(" Wallpaper connection "); + pw.print(conn); pw.println(":"); + pw.print(" mInfo.component="); pw.println(conn.mInfo.getComponent()); + pw.print(" mToken="); pw.println(conn.mToken); + pw.print(" mService="); pw.println(conn.mService); + pw.print(" mEngine="); pw.println(conn.mEngine); + pw.print(" mLastDiedTime="); + pw.println(mLastDiedTime - SystemClock.uptimeMillis()); + } + } + } +} diff --git a/services/java/com/android/server/WallpaperService.java b/services/java/com/android/server/WallpaperService.java deleted file mode 100644 index d921baf..0000000 --- a/services/java/com/android/server/WallpaperService.java +++ /dev/null @@ -1,203 +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 static android.os.FileObserver.*; -import static android.os.ParcelFileDescriptor.*; - -import android.app.IWallpaperService; -import android.app.IWallpaperServiceCallback; -import android.backup.BackupManager; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.os.Binder; -import android.os.RemoteException; -import android.os.FileObserver; -import android.os.ParcelFileDescriptor; -import android.os.RemoteCallbackList; -import android.util.Config; -import android.util.Log; - -import java.io.File; -import java.io.FileNotFoundException; - -class WallpaperService extends IWallpaperService.Stub { - private static final String TAG = WallpaperService.class.getSimpleName(); - - private static final File WALLPAPER_DIR = new File( - "/data/data/com.android.settings/files"); - private static final String WALLPAPER = "wallpaper"; - private static final File WALLPAPER_FILE = new File(WALLPAPER_DIR, WALLPAPER); - - private static final String PREFERENCES = "wallpaper-hints"; - - private static final String HINT_WIDTH = "hintWidth"; - private static final String HINT_HEIGHT = "hintHeight"; - - /** - * List of callbacks registered they should each be notified - * when the wallpaper is changed. - */ - private final RemoteCallbackList<IWallpaperServiceCallback> mCallbacks - = new RemoteCallbackList<IWallpaperServiceCallback>(); - - /** - * Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks - * that the wallpaper has changed. The CREATE is triggered when there is no - * wallpaper set and is created for the first time. The CLOSE_WRITE is triggered - * everytime the wallpaper is changed. - */ - private final FileObserver mWallpaperObserver = new FileObserver( - WALLPAPER_DIR.getAbsolutePath(), CREATE | CLOSE_WRITE) { - @Override - public void onEvent(int event, String path) { - if (path == null) { - return; - } - - File changedFile = new File(WALLPAPER_DIR, path); - if (WALLPAPER_FILE.equals(changedFile)) { - notifyCallbacks(); - } - } - }; - - private final Context mContext; - - private int mWidth = -1; - private int mHeight = -1; - - public WallpaperService(Context context) { - if (Config.LOGD) Log.d(TAG, "WallpaperService startup"); - mContext = context; - createFilesDir(); - mWallpaperObserver.startWatching(); - - SharedPreferences preferences = mContext.getSharedPreferences(PREFERENCES, - Context.MODE_PRIVATE); - mWidth = preferences.getInt(HINT_WIDTH, -1); - mHeight = preferences.getInt(HINT_HEIGHT, -1); - } - - @Override - protected void finalize() throws Throwable { - super.finalize(); - mWallpaperObserver.stopWatching(); - } - - public void clearWallpaper() { - File f = WALLPAPER_FILE; - if (f.exists()) { - f.delete(); - } - } - - public void setDimensionHints(int width, int height) throws RemoteException { - checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS); - - if (width <= 0 || height <= 0) { - throw new IllegalArgumentException("width and height must be > 0"); - } - - if (width != mWidth || height != mHeight) { - mWidth = width; - mHeight = height; - - SharedPreferences preferences = mContext.getSharedPreferences(PREFERENCES, - Context.MODE_PRIVATE); - - final SharedPreferences.Editor editor = preferences.edit(); - editor.putInt(HINT_WIDTH, width); - editor.putInt(HINT_HEIGHT, height); - editor.commit(); - } - } - - public int getWidthHint() throws RemoteException { - return mWidth; - } - - public int getHeightHint() throws RemoteException { - return mHeight; - } - - public ParcelFileDescriptor getWallpaper(IWallpaperServiceCallback cb) { - try { - mCallbacks.register(cb); - File f = WALLPAPER_FILE; - if (!f.exists()) { - return null; - } - return ParcelFileDescriptor.open(f, MODE_READ_ONLY); - } catch (FileNotFoundException e) { - - /* Shouldn't happen as we check to see if the file exists */ - if (Config.LOGD) Log.d(TAG, "Error getting wallpaper", e); - } - return null; - } - - public ParcelFileDescriptor setWallpaper() { - checkPermission(android.Manifest.permission.SET_WALLPAPER); - try { - ParcelFileDescriptor fd = ParcelFileDescriptor.open(WALLPAPER_FILE, - MODE_CREATE|MODE_READ_WRITE); - - // changing the wallpaper means we'll need to back up the new one - long origId = Binder.clearCallingIdentity(); - BackupManager bm = new BackupManager(mContext); - bm.dataChanged(); - Binder.restoreCallingIdentity(origId); - - return fd; - } catch (FileNotFoundException e) { - if (Config.LOGD) Log.d(TAG, "Error setting wallpaper", e); - } - return null; - } - - private void createFilesDir() { - if (!WALLPAPER_DIR.exists()) { - WALLPAPER_DIR.mkdirs(); - } - } - - private void notifyCallbacks() { - final int n = mCallbacks.beginBroadcast(); - for (int i = 0; i < n; i++) { - try { - mCallbacks.getBroadcastItem(i).onWallpaperChanged(); - } catch (RemoteException e) { - - // The RemoteCallbackList will take care of removing - // the dead object for us. - } - } - mCallbacks.finishBroadcast(); - final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED); - mContext.sendBroadcast(intent); - } - - private void checkPermission(String permission) { - if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(permission)) { - throw new SecurityException("Access denied to process: " + Binder.getCallingPid() - + ", must have permission " + permission); - } - } -} diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 394ed3a..32ad6c6 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -41,6 +41,7 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.SupplicantState; import android.net.NetworkStateTracker; import android.net.DhcpInfo; +import android.net.NetworkUtils; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; @@ -92,6 +93,9 @@ public class WifiService extends IWifiManager.Stub { private boolean mDeviceIdle; private int mPluggedType; + // true if the user enabled Wifi while in airplane mode + private boolean mAirplaneModeOverwridden; + private final LockList mLocks = new LockList(); // some wifi lock statistics private int mFullLocksAcquired; @@ -142,28 +146,6 @@ public class WifiService extends IWifiManager.Stub { private final WifiHandler mWifiHandler; /* - * Map used to keep track of hidden networks presence, which - * is needed to switch between active and passive scan modes. - * If there is at least one hidden network that is currently - * present (enabled), we want to do active scans instead of - * passive. - */ - private final Map<Integer, Boolean> mIsHiddenNetworkPresent; - /* - * The number of currently present hidden networks. When this - * counter goes from 0 to 1 or from 1 to 0, we change the - * scan mode to active or passive respectively. Initially, we - * set the counter to 0 and we increment it every time we add - * a new present (enabled) hidden network. - */ - private int mNumHiddenNetworkPresent; - /* - * Whether we change the scan mode is due to a hidden network - * (in this class, this is always the case) - */ - private final static boolean SET_DUE_TO_A_HIDDEN_NETWORK = true; - - /* * Cache of scan results objects (size is somewhat arbitrary) */ private static final int SCAN_RESULT_CACHE_SIZE = 80; @@ -195,12 +177,6 @@ public class WifiService extends IWifiManager.Stub { mWifiStateTracker.enableRssiPolling(true); mBatteryStats = BatteryStatsService.getService(); - /* - * Initialize the hidden-networks state - */ - mIsHiddenNetworkPresent = new HashMap<Integer, Boolean>(); - mNumHiddenNetworkPresent = 0; - mScanResultCache = new LinkedHashMap<String, ScanResult>( SCAN_RESULT_CACHE_SIZE, 0.75f, true) { /* @@ -246,6 +222,8 @@ public class WifiService extends IWifiManager.Stub { new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + // clear our flag indicating the user has overwridden airplane mode + mAirplaneModeOverwridden = false; updateWifiState(); } }, @@ -254,155 +232,6 @@ public class WifiService extends IWifiManager.Stub { setWifiEnabledBlocking(wifiEnabled, false, Process.myUid()); } - /** - * Initializes the hidden networks state. Must be called when we - * enable Wi-Fi. - */ - private synchronized void initializeHiddenNetworksState() { - // First, reset the state - resetHiddenNetworksState(); - - // ... then add networks that are marked as hidden - List<WifiConfiguration> networks = getConfiguredNetworks(); - if (!networks.isEmpty()) { - for (WifiConfiguration config : networks) { - if (config != null && config.hiddenSSID) { - addOrUpdateHiddenNetwork( - config.networkId, - config.status != WifiConfiguration.Status.DISABLED); - } - } - - } - } - - /** - * Resets the hidden networks state. - */ - private synchronized void resetHiddenNetworksState() { - mNumHiddenNetworkPresent = 0; - mIsHiddenNetworkPresent.clear(); - } - - /** - * Marks all but netId network as not present. - */ - private synchronized void markAllHiddenNetworksButOneAsNotPresent(int netId) { - for (Map.Entry<Integer, Boolean> entry : mIsHiddenNetworkPresent.entrySet()) { - if (entry != null) { - Integer networkId = entry.getKey(); - if (networkId != netId) { - updateNetworkIfHidden( - networkId, false); - } - } - } - } - - /** - * Updates the netId network presence status if netId is an existing - * hidden network. - */ - private synchronized void updateNetworkIfHidden(int netId, boolean present) { - if (isHiddenNetwork(netId)) { - addOrUpdateHiddenNetwork(netId, present); - } - } - - /** - * Updates the netId network presence status if netId is an existing - * hidden network. If the network does not exist, adds the network. - */ - private synchronized void addOrUpdateHiddenNetwork(int netId, boolean present) { - if (0 <= netId) { - - // If we are adding a new entry or modifying an existing one - Boolean isPresent = mIsHiddenNetworkPresent.get(netId); - if (isPresent == null || isPresent != present) { - if (present) { - incrementHiddentNetworkPresentCounter(); - } else { - // If we add a new hidden network, no need to change - // the counter (it must be 0) - if (isPresent != null) { - decrementHiddentNetworkPresentCounter(); - } - } - mIsHiddenNetworkPresent.put(netId, present); - } - } else { - Log.e(TAG, "addOrUpdateHiddenNetwork(): Invalid (negative) network id!"); - } - } - - /** - * Removes the netId network if it is hidden (being kept track of). - */ - private synchronized void removeNetworkIfHidden(int netId) { - if (isHiddenNetwork(netId)) { - removeHiddenNetwork(netId); - } - } - - /** - * Removes the netId network. For the call to be successful, the network - * must be hidden. - */ - private synchronized void removeHiddenNetwork(int netId) { - if (0 <= netId) { - Boolean isPresent = - mIsHiddenNetworkPresent.remove(netId); - if (isPresent != null) { - // If we remove an existing hidden network that is not - // present, no need to change the counter - if (isPresent) { - decrementHiddentNetworkPresentCounter(); - } - } else { - if (DBG) { - Log.d(TAG, "removeHiddenNetwork(): Removing a non-existent network!"); - } - } - } else { - Log.e(TAG, "removeHiddenNetwork(): Invalid (negative) network id!"); - } - } - - /** - * Returns true if netId is an existing hidden network. - */ - private synchronized boolean isHiddenNetwork(int netId) { - return mIsHiddenNetworkPresent.containsKey(netId); - } - - /** - * Increments the present (enabled) hidden networks counter. If the - * counter value goes from 0 to 1, changes the scan mode to active. - */ - private void incrementHiddentNetworkPresentCounter() { - ++mNumHiddenNetworkPresent; - if (1 == mNumHiddenNetworkPresent) { - // Switch the scan mode to "active" - mWifiStateTracker.setScanMode(true, SET_DUE_TO_A_HIDDEN_NETWORK); - } - } - - /** - * Decrements the present (enabled) hidden networks counter. If the - * counter goes from 1 to 0, changes the scan mode back to passive. - */ - private void decrementHiddentNetworkPresentCounter() { - if (0 < mNumHiddenNetworkPresent) { - --mNumHiddenNetworkPresent; - if (0 == mNumHiddenNetworkPresent) { - // Switch the scan mode to "passive" - mWifiStateTracker.setScanMode(false, SET_DUE_TO_A_HIDDEN_NETWORK); - } - } else { - Log.e(TAG, "Hidden-network counter invariant violation!"); - } - } - private boolean getPersistedWifiEnabled() { final ContentResolver cr = mContext.getContentResolver(); try { @@ -466,8 +295,14 @@ public class WifiService extends IWifiManager.Stub { 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()); } @@ -488,7 +323,7 @@ public class WifiService extends IWifiManager.Stub { if (mWifiState == eventualWifiState) { return true; } - if (enable && isAirplaneModeOn()) { + if (enable && isAirplaneModeOn() && !mAirplaneModeOverwridden) { return false; } @@ -522,7 +357,7 @@ public class WifiService extends IWifiManager.Stub { } // We must reset the interface before we unload the driver - mWifiStateTracker.resetInterface(); + mWifiStateTracker.resetInterface(false); if (!WifiNative.unloadDriver()) { Log.e(TAG, "Failed to unload Wi-Fi driver."); @@ -543,15 +378,6 @@ public class WifiService extends IWifiManager.Stub { } setWifiEnabledState(eventualWifiState, uid); - /* - * Initialize the hidden networks state and the number of allowed - * radio channels if Wi-Fi is being turned on. - */ - if (enable) { - mWifiStateTracker.setNumAllowedChannels(); - initializeHiddenNetworksState(); - } - return true; } @@ -843,6 +669,15 @@ public class WifiService extends IWifiManager.Stub { } } } + + for (WifiConfiguration.EnterpriseField field : + config.enterpriseFields) { + value = WifiNative.getNetworkVariableCommand(netId, + field.varName()); + if (!TextUtils.isEmpty(value)) { + field.setValue(value); + } + } } /** @@ -884,15 +719,6 @@ public class WifiService extends IWifiManager.Stub { } mNeedReconfig = mNeedReconfig || doReconfig; - /* - * If we have hidden networks, we may have to change the scan mode - */ - if (config.hiddenSSID) { - // Mark the network as present unless it is disabled - addOrUpdateHiddenNetwork( - netId, config.status != WifiConfiguration.Status.DISABLED); - } - setVariables: { /* * Note that if a networkId for a non-existent network @@ -1064,103 +890,20 @@ public class WifiService extends IWifiManager.Stub { break setVariables; } - if ((config.eap != null) && !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.eapVarName, - config.eap)) { - if (DBG) { - Log.d(TAG, config.SSID + ": failed to set eap: "+ - config.eap); - } - break setVariables; - } - - if ((config.phase2 != null) && !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.phase2VarName, - config.phase2)) { - if (DBG) { - Log.d(TAG, config.SSID + ": failed to set phase2: "+ - config.phase2); - } - break setVariables; - } - - if ((config.identity != null) && !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.identityVarName, - config.identity)) { - if (DBG) { - Log.d(TAG, config.SSID + ": failed to set identity: "+ - config.identity); - } - break setVariables; - } - - if ((config.anonymousIdentity != null) && !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.anonymousIdentityVarName, - config.anonymousIdentity)) { - if (DBG) { - Log.d(TAG, config.SSID + ": failed to set anonymousIdentity: "+ - config.anonymousIdentity); - } - break setVariables; - } - - if ((config.password != null) && !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.passwordVarName, - config.password)) { - if (DBG) { - Log.d(TAG, config.SSID + ": failed to set password: "+ - config.password); - } - break setVariables; - } - - if ((config.clientCert != null) && !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.clientCertVarName, - config.clientCert)) { - if (DBG) { - Log.d(TAG, config.SSID + ": failed to set clientCert: "+ - config.clientCert); - } - break setVariables; - } - - if ((config.caCert != null) && !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.caCertVarName, - config.caCert)) { - if (DBG) { - Log.d(TAG, config.SSID + ": failed to set caCert: "+ - config.caCert); - } - break setVariables; - } - - if ((config.privateKey != null) && !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.privateKeyVarName, - config.privateKey)) { - if (DBG) { - Log.d(TAG, config.SSID + ": failed to set privateKey: "+ - config.privateKey); - } - break setVariables; - } - - if ((config.privateKeyPasswd != null) && !WifiNative.setNetworkVariableCommand( + for (WifiConfiguration.EnterpriseField field + : config.enterpriseFields) { + String varName = field.varName(); + String value = field.value(); + if ((value != null) && !WifiNative.setNetworkVariableCommand( netId, - WifiConfiguration.privateKeyPasswdVarName, - config.privateKeyPasswd)) { - if (DBG) { - Log.d(TAG, config.SSID + ": failed to set privateKeyPasswd: "+ - config.privateKeyPasswd); + varName, + value)) { + if (DBG) { + Log.d(TAG, config.SSID + ": failed to set " + varName + + ": " + value); + } + break setVariables; } - break setVariables; } return netId; @@ -1231,11 +974,6 @@ public class WifiService extends IWifiManager.Stub { public boolean removeNetwork(int netId) { enforceChangePermission(); - /* - * If we have hidden networks, we may have to change the scan mode - */ - removeNetworkIfHidden(netId); - return mWifiStateTracker.removeNetwork(netId); } @@ -1249,18 +987,14 @@ public class WifiService extends IWifiManager.Stub { public boolean enableNetwork(int netId, boolean disableOthers) { enforceChangePermission(); - /* - * If we have hidden networks, we may have to change the scan mode - */ - synchronized(this) { - if (disableOthers) { - markAllHiddenNetworksButOneAsNotPresent(netId); - } - updateNetworkIfHidden(netId, true); - } - synchronized (mWifiStateTracker) { - return WifiNative.enableNetworkCommand(netId, disableOthers); + String ifname = mWifiStateTracker.getInterfaceName(); + NetworkUtils.enableInterface(ifname); + boolean result = WifiNative.enableNetworkCommand(netId, disableOthers); + if (!result) { + NetworkUtils.disableInterface(ifname); + } + return result; } } @@ -1273,11 +1007,6 @@ public class WifiService extends IWifiManager.Stub { public boolean disableNetwork(int netId) { enforceChangePermission(); - /* - * If we have hidden networks, we may have to change the scan mode - */ - updateNetworkIfHidden(netId, false); - synchronized (mWifiStateTracker) { return WifiNative.disableNetworkCommand(netId); } @@ -1375,45 +1104,49 @@ public class WifiService extends IWifiManager.Stub { level = 0; } - // bssid is the hash key - scanResult = mScanResultCache.get(bssid); - if (scanResult != null) { - scanResult.level = level; - } else { - /* - * 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) { + /* + * 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 = result[4]; + ssid = ""; } else { - // Here, we must have 3 fields: no flags and ssid - // set flags = ""; - ssid = ""; + 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(bssid, scanResult); + mScanResultCache.put(key, scanResult); } } } else { @@ -1479,14 +1212,17 @@ public class WifiService extends IWifiManager.Stub { * 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 is + * 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. */ - public boolean setNumAllowedChannels(int numChannels) { + public boolean setNumAllowedChannels(int numChannels, boolean persist) { + Log.i(TAG, "WifiService trying to setNumAllowed to "+numChannels+ + " with persist set to "+persist); enforceChangePermission(); /* * Validate the argument. We'd like to let the Wi-Fi driver do this, @@ -1505,9 +1241,11 @@ public class WifiService extends IWifiManager.Stub { return false; } - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS, - numChannels); + if (persist) { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS, + numChannels); + } mWifiStateTracker.setNumAllowedChannels(numChannels); return true; } @@ -1586,9 +1324,16 @@ public class WifiService extends IWifiManager.Stub { if (!shouldWifiStayAwake(stayAwakeConditions, mPluggedType)) { WifiInfo info = mWifiStateTracker.requestConnectionInfo(); if (info.getSupplicantState() != SupplicantState.COMPLETED) { - // do not keep Wifi awake when screen is off if Wifi is not associated - mDeviceIdle = true; - updateWifiState(); + // 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) + // TODO - fix the race conditions and switch back to the immediate turn-off + long triggerTime = System.currentTimeMillis() + (2*60*1000); // 2 min + Log.d(TAG, "setting ACTION_DEVICE_IDLE timer for 120,000 ms"); + mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent); + // // do not keep Wifi awake when screen is off if Wifi is not associated + // mDeviceIdle = true; + // updateWifiState(); } else { long triggerTime = System.currentTimeMillis() + idleMillis; Log.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis + "ms"); @@ -1619,10 +1364,10 @@ public class WifiService extends IWifiManager.Stub { return; } mPluggedType = pluggedType; - } else if (action.equals(BluetoothA2dp.SINK_STATE_CHANGED_ACTION)) { + } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { boolean isBluetoothPlaying = intent.getIntExtra( - BluetoothA2dp.SINK_STATE, + BluetoothA2dp.EXTRA_SINK_STATE, BluetoothA2dp.STATE_DISCONNECTED) == BluetoothA2dp.STATE_PLAYING; mWifiStateTracker.setBluetoothScanMode(isBluetoothPlaying); } else { @@ -1688,7 +1433,7 @@ public class WifiService extends IWifiManager.Stub { private void updateWifiState() { boolean wifiEnabled = getPersistedWifiEnabled(); - boolean airplaneMode = isAirplaneModeOn(); + boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden; boolean lockHeld = mLocks.hasLocks(); int strongestLockMode; boolean wifiShouldBeEnabled = wifiEnabled && !airplaneMode; @@ -1741,7 +1486,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.SINK_STATE_CHANGED_ACTION); + intentFilter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); mContext.registerReceiver(mReceiver, intentFilter); } @@ -1752,6 +1497,13 @@ public class WifiService extends IWifiManager.Stub { || airplaneModeRadios.contains(Settings.System.RADIO_WIFI); } + private boolean isAirplaneToggleable() { + String toggleableRadios = Settings.System.getString(mContext.getContentResolver(), + Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS); + return toggleableRadios != null + && toggleableRadios.contains(Settings.System.RADIO_WIFI); + } + /** * Returns true if Wi-Fi is sensitive to airplane mode, and airplane mode is * currently on. @@ -1950,8 +1702,10 @@ public class WifiService extends IWifiManager.Stub { } private boolean acquireWifiLockLocked(WifiLock wifiLock) { + Log.d(TAG, "acquireWifiLockLocked: " + wifiLock); + mLocks.addLock(wifiLock); - + int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); try { @@ -1969,7 +1723,7 @@ public class WifiService extends IWifiManager.Stub { } finally { Binder.restoreCallingIdentity(ident); } - + updateWifiState(); return true; } @@ -1983,8 +1737,11 @@ public class WifiService extends IWifiManager.Stub { private boolean releaseWifiLockLocked(IBinder lock) { boolean hadLock; - + WifiLock wifiLock = mLocks.removeLock(lock); + + Log.d(TAG, "releaseWifiLockLocked: " + wifiLock); + hadLock = (wifiLock != null); if (hadLock) { @@ -2006,7 +1763,7 @@ public class WifiService extends IWifiManager.Stub { Binder.restoreCallingIdentity(ident); } } - + // TODO - should this only happen if you hadLock? updateWifiState(); return hadLock; } @@ -2068,7 +1825,9 @@ 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. - WifiNative.stopPacketFiltering(); + synchronized (mWifiStateTracker) { + WifiNative.stopPacketFiltering(); + } } int uid = Binder.getCallingUid(); @@ -2104,7 +1863,9 @@ public class WifiService extends IWifiManager.Stub { removed.unlinkDeathRecipient(); } if (mMulticasters.size() == 0) { - WifiNative.startPacketFiltering(); + synchronized (mWifiStateTracker) { + WifiNative.startPacketFiltering(); + } } Long ident = Binder.clearCallingIdentity(); diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index d4c27b7..327cd72 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -31,15 +31,15 @@ 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_GPU; -import static android.view.WindowManager.LayoutParams.MEMORY_TYPE_HARDWARE; 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; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import com.android.internal.app.IBatteryStats; import com.android.internal.policy.PolicyManager; @@ -63,9 +63,11 @@ import android.graphics.Rect; import android.graphics.Region; import android.os.BatteryStats; import android.os.Binder; +import android.os.Bundle; import android.os.Debug; import android.os.Handler; import android.os.IBinder; +import android.os.LatencyTimer; import android.os.LocalPowerManager; import android.os.Looper; import android.os.Message; @@ -98,6 +100,7 @@ import android.view.RawInputEvent; import android.view.Surface; import android.view.SurfaceSession; import android.view.View; +import android.view.ViewConfiguration; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.WindowManagerImpl; @@ -124,7 +127,8 @@ import java.util.Iterator; import java.util.List; /** {@hide} */ -public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { +public class WindowManagerService extends IWindowManager.Stub + implements Watchdog.Monitor, KeyInputQueue.HapticFeedbackCallback { static final String TAG = "WindowManager"; static final boolean DEBUG = false; static final boolean DEBUG_FOCUS = false; @@ -133,11 +137,16 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo static final boolean DEBUG_INPUT = false; static final boolean DEBUG_INPUT_METHOD = false; static final boolean DEBUG_VISIBILITY = false; + static final boolean DEBUG_WINDOW_MOVEMENT = false; static final boolean DEBUG_ORIENTATION = 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 SHOW_TRANSACTIONS = false; + static final boolean HIDE_STACK_CRAWLS = true; + static final boolean MEASURE_LATENCY = false; + static private LatencyTimer lt; static final boolean PROFILE_ORIENTATION = false; static final boolean BLUR = true; @@ -145,9 +154,6 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo static final int LOG_WM_NO_SURFACE_MEMORY = 31000; - /** How long to wait for first key repeat, in milliseconds */ - static final int KEY_REPEAT_FIRST_DELAY = 750; - /** How long to wait for subsequent key repeats, in milliseconds */ static final int KEY_REPEAT_DELAY = 50; @@ -228,8 +234,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo mPolicy.enableKeyguard(false); } public void released() { + mPolicy.enableKeyguard(true); synchronized (mKeyguardDisabled) { - mPolicy.enableKeyguard(true); mWaitingUntilKeyguardReenabled = false; mKeyguardDisabled.notifyAll(); } @@ -297,6 +303,18 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo 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 mWindows = new ArrayList(); @@ -368,13 +386,19 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // perform or TRANSIT_NONE if we are not waiting. If we are waiting, // mOpeningApps and mClosingApps are the lists of tokens that will be // made visible or hidden at the next transition. - int mNextAppTransition = WindowManagerPolicy.TRANSIT_NONE; + int mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; + String mNextAppTransitionPackage; + int mNextAppTransitionEnter; + int mNextAppTransitionExit; boolean mAppTransitionReady = false; + boolean mAppTransitionRunning = false; boolean mAppTransitionTimeout = false; boolean mStartingIconInTransition = false; boolean mSkipAppTransitionAnimation = false; final ArrayList<AppWindowToken> mOpeningApps = new ArrayList<AppWindowToken>(); final ArrayList<AppWindowToken> mClosingApps = new ArrayList<AppWindowToken>(); + final ArrayList<AppWindowToken> mToTopApps = new ArrayList<AppWindowToken>(); + final ArrayList<AppWindowToken> mToBottomApps = new ArrayList<AppWindowToken>(); //flag to detect fat touch events boolean mFatTouch = false; @@ -395,6 +419,33 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo WindowState mInputMethodWindow = null; final ArrayList<WindowState> mInputMethodDialogs = new ArrayList<WindowState>(); + final ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>(); + + // If non-null, this is the currently visible window that is associated + // with the wallpaper. + WindowState mWallpaperTarget = null; + // If non-null, we are in the middle of animating from one wallpaper target + // to another, and this is the lower one in Z-order. + WindowState mLowerWallpaperTarget = null; + // 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; + int mWallpaperAnimLayerAdjustment; + float mLastWallpaperX = -1; + float mLastWallpaperY = -1; + float mLastWallpaperXStep = -1; + float mLastWallpaperYStep = -1; + boolean mSendingPointersToWallpaper = false; + // This is set when we are waiting for a wallpaper to tell us it is done + // changing its scroll position. + WindowState mWaitingOnWallpaper; + // The last time we had a timeout when waiting for a wallpaper. + long mLastWallpaperTimeoutTime; + // We give a wallpaper up to 150ms to finish scrolling. + static final long WALLPAPER_TIMEOUT = 150; + // Time we wait after a timeout before trying to wait again. + static final long WALLPAPER_TIMEOUT_RECOVERY = 10000; + AppWindowToken mFocusedApp = null; PowerManagerService mPowerManager; @@ -409,6 +460,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // Who is holding the screen on. Session mHoldingScreenOn; + boolean mTurnOnScreen; + /** * Whether the UI is currently running in touch mode (not showing * navigational focus because the user is directly pressing the screen). @@ -512,6 +565,10 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo private WindowManagerService(Context context, PowerManagerService pm, boolean haveInputMethods) { + if (MEASURE_LATENCY) { + lt = new LatencyTimer(100, 1000); + } + mContext = context; mHaveInputMethods = haveInputMethods; mLimitedAlphaCompositing = context.getResources().getBoolean( @@ -583,7 +640,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo private void placeWindowAfter(Object pos, WindowState window) { final int i = mWindows.indexOf(pos); - if (localLOGV || DEBUG_FOCUS) Log.v( + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Log.v( TAG, "Adding window " + window + " at " + (i+1) + " of " + mWindows.size() + " (after " + pos + ")"); mWindows.add(i+1, window); @@ -591,7 +648,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo private void placeWindowBefore(Object pos, WindowState window) { final int i = mWindows.indexOf(pos); - if (localLOGV || DEBUG_FOCUS) Log.v( + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Log.v( TAG, "Adding window " + window + " at " + i + " of " + mWindows.size() + " (before " + pos + ")"); mWindows.add(i, window); @@ -648,6 +705,9 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo //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) Log.v( + TAG, "Adding window " + win + " at " + + (newIdx+1) + " of " + N); localmWindows.add(newIdx+1, win); } } @@ -666,7 +726,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo i--; break; } - if (t.windows.size() > 0) { + + // We haven't reached the token yet; if this token + // is not going to the bottom and has windows, we can + // use it as an anchor for when we do reach the token. + if (!t.sendingToBottom && t.windows.size() > 0) { pos = t.windows.get(0); } } @@ -688,6 +752,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } placeWindowBefore(pos, win); } else { + // Continue looking down until we find the first + // token that has windows. while (i >= 0) { AppWindowToken t = mAppTokens.get(i); final int NW = t.windows.size(); @@ -721,9 +787,9 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo break; } } - if (localLOGV || DEBUG_FOCUS) Log.v( - TAG, "Adding window " + win + " at " - + i + " of " + N); + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Log.v( + TAG, "Adding window " + win + " at " + + i + " of " + N); localmWindows.add(i, win); } } @@ -738,9 +804,9 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } if (i < 0) i = 0; - if (localLOGV || DEBUG_FOCUS) Log.v( - TAG, "Adding window " + win + " at " - + i + " of " + N); + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Log.v( + TAG, "Adding window " + win + " at " + + i + " of " + N); localmWindows.add(i, win); } if (addToToken) { @@ -887,7 +953,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo + " layer=" + highestTarget.mAnimLayer + " new layer=" + w.mAnimLayer); - if (mNextAppTransition != WindowManagerPolicy.TRANSIT_NONE) { + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { // If we are currently setting up for an animation, // hold everything until we can find out what will happen. mInputMethodTargetWaitingAnim = true; @@ -910,7 +976,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (w != null) { if (willMove) { RuntimeException e = new RuntimeException(); - e.fillInStackTrace(); + if (!HIDE_STACK_CRAWLS) e.fillInStackTrace(); if (DEBUG_INPUT_METHOD) Log.w(TAG, "Moving IM target from " + mInputMethodTarget + " to " + w, e); mInputMethodTarget = w; @@ -924,7 +990,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } if (willMove) { RuntimeException e = new RuntimeException(); - e.fillInStackTrace(); + if (!HIDE_STACK_CRAWLS) e.fillInStackTrace(); if (DEBUG_INPUT_METHOD) Log.w(TAG, "Moving IM target from " + mInputMethodTarget + " to null", e); mInputMethodTarget = null; @@ -937,6 +1003,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo int pos = findDesiredInputMethodWindowIndexLocked(true); if (pos >= 0) { win.mTargetAppToken = mInputMethodTarget.mAppToken; + if (DEBUG_WINDOW_MOVEMENT) Log.v( + TAG, "Adding input method window " + win + " at " + pos); mWindows.add(pos, win); moveInputMethodDialogsLocked(pos+1); return; @@ -977,6 +1045,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo int wpos = mWindows.indexOf(win); if (wpos >= 0) { if (wpos < interestingPos) interestingPos--; + if (DEBUG_WINDOW_MOVEMENT) Log.v(TAG, "Temp removing at " + wpos + ": " + win); mWindows.remove(wpos); int NC = win.mChildWindows.size(); while (NC > 0) { @@ -985,6 +1054,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo int cpos = mWindows.indexOf(cw); if (cpos >= 0) { if (cpos < interestingPos) interestingPos--; + if (DEBUG_WINDOW_MOVEMENT) Log.v(TAG, "Temp removing child at " + + cpos + ": " + cw); mWindows.remove(cpos); } } @@ -999,6 +1070,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // this case should be rare, so it shouldn't be that big a deal. int wpos = mWindows.indexOf(win); if (wpos >= 0) { + if (DEBUG_WINDOW_MOVEMENT) Log.v(TAG, "ReAdd removing from " + wpos + + ": " + win); mWindows.remove(wpos); reAddWindowLocked(wpos, win); } @@ -1159,6 +1232,546 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true)); } + final boolean isWallpaperVisible(WindowState wallpaperTarget) { + if (DEBUG_WALLPAPER) Log.v(TAG, "Wallpaper vis: target obscured=" + + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??") + + " anim=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null) + ? wallpaperTarget.mAppToken.animation : null) + + " upper=" + mUpperWallpaperTarget + + " lower=" + mLowerWallpaperTarget); + return (wallpaperTarget != null + && (!wallpaperTarget.mObscured || (wallpaperTarget.mAppToken != null + && wallpaperTarget.mAppToken.animation != null))) + || mUpperWallpaperTarget != null + || mLowerWallpaperTarget != null; + } + + static final int ADJUST_WALLPAPER_LAYERS_CHANGED = 1<<1; + static final int ADJUST_WALLPAPER_VISIBILITY_CHANGED = 1<<2; + + int adjustWallpaperWindowsLocked() { + int changed = 0; + + final int dw = mDisplay.getWidth(); + final int dh = mDisplay.getHeight(); + + // First find top-most window that has asked to be on top of the + // wallpaper; all wallpapers go behind it. + final ArrayList localmWindows = mWindows; + int N = localmWindows.size(); + WindowState w = null; + WindowState foundW = null; + int foundI = 0; + WindowState topCurW = null; + int topCurI = 0; + int i = N; + while (i > 0) { + i--; + w = (WindowState)localmWindows.get(i); + if ((w.mAttrs.type == WindowManager.LayoutParams.TYPE_WALLPAPER)) { + if (topCurW == null) { + topCurW = w; + topCurI = i; + } + continue; + } + topCurW = null; + if (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) Log.v(TAG, + "Skipping hidden or animating token: " + w); + topCurW = null; + continue; + } + } + if (DEBUG_WALLPAPER) Log.v(TAG, "Win " + w + ": readyfordisplay=" + + w.isReadyForDisplay() + " drawpending=" + w.mDrawPending + + " commitdrawpending=" + w.mCommitDrawPending); + if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 && w.isReadyForDisplay() + && (mWallpaperTarget == w + || (!w.mDrawPending && !w.mCommitDrawPending))) { + if (DEBUG_WALLPAPER) Log.v(TAG, + "Found wallpaper activity: #" + i + "=" + w); + foundW = w; + foundI = i; + if (w == mWallpaperTarget && ((w.mAppToken != null + && w.mAppToken.animation != null) + || w.mAnimation != null)) { + // The current wallpaper target is animating, so we'll + // look behind it for another possible target and figure + // out what is going on below. + if (DEBUG_WALLPAPER) Log.v(TAG, "Win " + w + + ": token animating, looking behind."); + continue; + } + break; + } + } + + 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, + // then hold off on doing anything with the wallpaper. + // Note that we are checking here for just whether the target + // is part of an app token... which is potentially overly aggressive + // (the app token may not be involved in the transition), but good + // enough (we'll just wait until whatever transition is pending + // executes). + if (mWallpaperTarget != null && mWallpaperTarget.mAppToken != null) { + if (DEBUG_WALLPAPER) Log.v(TAG, + "Wallpaper not changing: waiting for app anim in current target"); + return 0; + } + if (foundW != null && foundW.mAppToken != null) { + if (DEBUG_WALLPAPER) Log.v(TAG, + "Wallpaper not changing: waiting for app anim in found target"); + return 0; + } + } + + if (mWallpaperTarget != foundW) { + if (DEBUG_WALLPAPER) { + Log.v(TAG, "New wallpaper target: " + foundW + + " oldTarget: " + mWallpaperTarget); + } + + mLowerWallpaperTarget = null; + mUpperWallpaperTarget = null; + + WindowState oldW = mWallpaperTarget; + mWallpaperTarget = foundW; + + // Now what is happening... if the current and new targets are + // animating, then we are in our super special mode! + if (foundW != null && oldW != null) { + boolean oldAnim = oldW.mAnimation != null + || (oldW.mAppToken != null && oldW.mAppToken.animation != null); + boolean foundAnim = foundW.mAnimation != null + || (foundW.mAppToken != null && foundW.mAppToken.animation != null); + if (DEBUG_WALLPAPER) { + Log.v(TAG, "New animation: " + foundAnim + + " old animation: " + oldAnim); + } + if (foundAnim && oldAnim) { + int oldI = localmWindows.indexOf(oldW); + if (DEBUG_WALLPAPER) { + Log.v(TAG, "New i: " + foundI + " old i: " + oldI); + } + if (oldI >= 0) { + if (DEBUG_WALLPAPER) { + Log.v(TAG, "Animating wallpapers: old#" + oldI + + "=" + oldW + "; new#" + foundI + + "=" + foundW); + } + + // Set the new target correctly. + if (foundW.mAppToken != null && foundW.mAppToken.hiddenRequested) { + if (DEBUG_WALLPAPER) { + Log.v(TAG, "Old wallpaper still the target."); + } + mWallpaperTarget = oldW; + } + + // Now set the upper and lower wallpaper targets + // correctly, and make sure that we are positioning + // the wallpaper below the lower. + if (foundI > oldI) { + // The new target is on top of the old one. + if (DEBUG_WALLPAPER) { + Log.v(TAG, "Found target above old target."); + } + mUpperWallpaperTarget = foundW; + mLowerWallpaperTarget = oldW; + foundW = oldW; + foundI = oldI; + } else { + // The new target is below the old one. + if (DEBUG_WALLPAPER) { + Log.v(TAG, "Found target below old target."); + } + mUpperWallpaperTarget = oldW; + mLowerWallpaperTarget = foundW; + } + } + } + } + + } else if (mLowerWallpaperTarget != null) { + // Is it time to stop animating? + boolean lowerAnimating = mLowerWallpaperTarget.mAnimation != null + || (mLowerWallpaperTarget.mAppToken != null + && mLowerWallpaperTarget.mAppToken.animation != null); + boolean upperAnimating = mUpperWallpaperTarget.mAnimation != null + || (mUpperWallpaperTarget.mAppToken != null + && mUpperWallpaperTarget.mAppToken.animation != null); + if (!lowerAnimating || !upperAnimating) { + if (DEBUG_WALLPAPER) { + Log.v(TAG, "No longer animating wallpaper targets!"); + } + mLowerWallpaperTarget = null; + mUpperWallpaperTarget = null; + } + } + + boolean visible = foundW != null; + if (visible) { + // The window is visible to the compositor... but is it visible + // to the user? That is what the wallpaper cares about. + visible = isWallpaperVisible(foundW); + if (DEBUG_WALLPAPER) Log.v(TAG, "Wallpaper visibility: " + visible); + + // If the wallpaper target is animating, we may need to copy + // its layer adjustment. Only do this if we are not transfering + // between two wallpaper targets. + mWallpaperAnimLayerAdjustment = + (mLowerWallpaperTarget == null && foundW.mAppToken != null) + ? foundW.mAppToken.animLayerAdjustment : 0; + + final int maxLayer = mPolicy.getMaxWallpaperLayer() + * TYPE_LAYER_MULTIPLIER + + TYPE_LAYER_OFFSET; + + // Now w is the window we are supposed to be behind... but we + // need to be sure to also be behind any of its attached windows, + // AND any starting window associated with it, AND below the + // maximum layer the policy allows for wallpapers. + while (foundI > 0) { + WindowState wb = (WindowState)localmWindows.get(foundI-1); + if (wb.mBaseLayer < maxLayer && + wb.mAttachedWindow != foundW && + (wb.mAttrs.type != TYPE_APPLICATION_STARTING || + wb.mToken != foundW.mToken)) { + // This window is not related to the previous one in any + // interesting way, so stop here. + break; + } + foundW = wb; + foundI--; + } + } else { + if (DEBUG_WALLPAPER) Log.v(TAG, "No wallpaper target"); + } + + if (foundW == null && topCurW != null) { + // There is no wallpaper target, so it goes at the bottom. + // We will assume it is the same place as last time, if known. + foundW = topCurW; + foundI = topCurI+1; + } else { + // Okay i is the position immediately above the wallpaper. Look at + // what is below it for later. + foundW = foundI > 0 ? (WindowState)localmWindows.get(foundI-1) : null; + } + + if (visible) { + if (mWallpaperTarget.mWallpaperX >= 0) { + mLastWallpaperX = mWallpaperTarget.mWallpaperX; + mLastWallpaperXStep = mWallpaperTarget.mWallpaperXStep; + } + if (mWallpaperTarget.mWallpaperY >= 0) { + mLastWallpaperY = mWallpaperTarget.mWallpaperY; + mLastWallpaperYStep = mWallpaperTarget.mWallpaperYStep; + } + } + + // Start stepping backwards from here, ensuring that our wallpaper windows + // are correctly placed. + int curTokenIndex = mWallpaperTokens.size(); + while (curTokenIndex > 0) { + curTokenIndex--; + WindowToken token = mWallpaperTokens.get(curTokenIndex); + if (token.hidden == visible) { + changed |= ADJUST_WALLPAPER_VISIBILITY_CHANGED; + token.hidden = !visible; + // Need to do a layout to ensure the wallpaper now has the + // correct size. + mLayoutNeeded = true; + } + + int curWallpaperIndex = token.windows.size(); + while (curWallpaperIndex > 0) { + curWallpaperIndex--; + WindowState wallpaper = token.windows.get(curWallpaperIndex); + + if (visible) { + updateWallpaperOffsetLocked(wallpaper, dw, dh, false); + } + + // First, make sure the client has the current visibility + // state. + if (wallpaper.mWallpaperVisible != visible) { + wallpaper.mWallpaperVisible = visible; + try { + if (DEBUG_VISIBILITY || DEBUG_WALLPAPER) Log.v(TAG, + "Setting visibility of wallpaper " + wallpaper + + ": " + visible); + wallpaper.mClient.dispatchAppVisibility(visible); + } catch (RemoteException e) { + } + } + + wallpaper.mAnimLayer = wallpaper.mLayer + mWallpaperAnimLayerAdjustment; + if (DEBUG_LAYERS || DEBUG_WALLPAPER) Log.v(TAG, "Wallpaper win " + + wallpaper + " anim layer: " + wallpaper.mAnimLayer); + + // First, if this window is at the current index, then all + // is well. + if (wallpaper == foundW) { + foundI--; + foundW = foundI > 0 + ? (WindowState)localmWindows.get(foundI-1) : null; + continue; + } + + // The window didn't match... the current wallpaper window, + // wherever it is, is in the wrong place, so make sure it is + // not in the list. + int oldIndex = localmWindows.indexOf(wallpaper); + if (oldIndex >= 0) { + if (DEBUG_WINDOW_MOVEMENT) Log.v(TAG, "Wallpaper removing at " + + oldIndex + ": " + wallpaper); + localmWindows.remove(oldIndex); + if (oldIndex < foundI) { + foundI--; + } + } + + // Now stick it in. + if (DEBUG_WALLPAPER || DEBUG_WINDOW_MOVEMENT) Log.v(TAG, + "Moving wallpaper " + wallpaper + + " from " + oldIndex + " to " + foundI); + + localmWindows.add(foundI, wallpaper); + changed |= ADJUST_WALLPAPER_LAYERS_CHANGED; + } + } + + return changed; + } + + void setWallpaperAnimLayerAdjustmentLocked(int adj) { + if (DEBUG_LAYERS || DEBUG_WALLPAPER) Log.v(TAG, + "Setting wallpaper layer adj to " + adj); + mWallpaperAnimLayerAdjustment = adj; + int curTokenIndex = mWallpaperTokens.size(); + while (curTokenIndex > 0) { + curTokenIndex--; + WindowToken token = mWallpaperTokens.get(curTokenIndex); + int curWallpaperIndex = token.windows.size(); + while (curWallpaperIndex > 0) { + curWallpaperIndex--; + WindowState wallpaper = token.windows.get(curWallpaperIndex); + wallpaper.mAnimLayer = wallpaper.mLayer + adj; + if (DEBUG_LAYERS || DEBUG_WALLPAPER) Log.v(TAG, "Wallpaper win " + + wallpaper + " anim layer: " + wallpaper.mAnimLayer); + } + } + } + + boolean updateWallpaperOffsetLocked(WindowState wallpaperWin, int dw, int dh, + boolean sync) { + boolean changed = false; + boolean rawChanged = false; + float wpx = mLastWallpaperX >= 0 ? mLastWallpaperX : 0.5f; + float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f; + int availw = wallpaperWin.mFrame.right-wallpaperWin.mFrame.left-dw; + int offset = availw > 0 ? -(int)(availw*wpx+.5f) : 0; + changed = wallpaperWin.mXOffset != offset; + if (changed) { + if (DEBUG_WALLPAPER) Log.v(TAG, "Update wallpaper " + + wallpaperWin + " x: " + offset); + wallpaperWin.mXOffset = offset; + } + if (wallpaperWin.mWallpaperX != wpx || wallpaperWin.mWallpaperXStep != wpxs) { + wallpaperWin.mWallpaperX = wpx; + wallpaperWin.mWallpaperXStep = wpxs; + rawChanged = true; + } + + float wpy = mLastWallpaperY >= 0 ? mLastWallpaperY : 0.5f; + float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f; + int availh = wallpaperWin.mFrame.bottom-wallpaperWin.mFrame.top-dh; + offset = availh > 0 ? -(int)(availh*wpy+.5f) : 0; + if (wallpaperWin.mYOffset != offset) { + if (DEBUG_WALLPAPER) Log.v(TAG, "Update wallpaper " + + wallpaperWin + " y: " + offset); + changed = true; + wallpaperWin.mYOffset = offset; + } + if (wallpaperWin.mWallpaperY != wpy || wallpaperWin.mWallpaperYStep != wpys) { + wallpaperWin.mWallpaperY = wpy; + wallpaperWin.mWallpaperYStep = wpys; + rawChanged = true; + } + + if (rawChanged) { + try { + if (DEBUG_WALLPAPER) Log.v(TAG, "Report new wp offset " + + wallpaperWin + " x=" + wallpaperWin.mWallpaperX + + " y=" + wallpaperWin.mWallpaperY); + if (sync) { + mWaitingOnWallpaper = wallpaperWin; + } + wallpaperWin.mClient.dispatchWallpaperOffsets( + wallpaperWin.mWallpaperX, wallpaperWin.mWallpaperY, + wallpaperWin.mWallpaperXStep, wallpaperWin.mWallpaperYStep, sync); + if (sync) { + if (mWaitingOnWallpaper != null) { + long start = SystemClock.uptimeMillis(); + if ((mLastWallpaperTimeoutTime+WALLPAPER_TIMEOUT_RECOVERY) + < start) { + try { + if (DEBUG_WALLPAPER) Log.v(TAG, + "Waiting for offset complete..."); + mWindowMap.wait(WALLPAPER_TIMEOUT); + } catch (InterruptedException e) { + } + if (DEBUG_WALLPAPER) Log.v(TAG, "Offset complete!"); + if ((start+WALLPAPER_TIMEOUT) + < SystemClock.uptimeMillis()) { + Log.i(TAG, "Timeout waiting for wallpaper to offset: " + + wallpaperWin); + mLastWallpaperTimeoutTime = start; + } + } + mWaitingOnWallpaper = null; + } + } + } catch (RemoteException e) { + } + } + + return changed; + } + + void wallpaperOffsetsComplete(IBinder window) { + synchronized (mWindowMap) { + if (mWaitingOnWallpaper != null && + mWaitingOnWallpaper.mClient.asBinder() == window) { + mWaitingOnWallpaper = null; + mWindowMap.notifyAll(); + } + } + } + + boolean updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) { + final int dw = mDisplay.getWidth(); + final int dh = mDisplay.getHeight(); + + boolean changed = false; + + WindowState target = mWallpaperTarget; + if (target != null) { + if (target.mWallpaperX >= 0) { + mLastWallpaperX = target.mWallpaperX; + } else if (changingTarget.mWallpaperX >= 0) { + mLastWallpaperX = changingTarget.mWallpaperX; + } + if (target.mWallpaperY >= 0) { + mLastWallpaperY = target.mWallpaperY; + } else if (changingTarget.mWallpaperY >= 0) { + mLastWallpaperY = changingTarget.mWallpaperY; + } + } + + int curTokenIndex = mWallpaperTokens.size(); + while (curTokenIndex > 0) { + curTokenIndex--; + WindowToken token = mWallpaperTokens.get(curTokenIndex); + int curWallpaperIndex = token.windows.size(); + while (curWallpaperIndex > 0) { + curWallpaperIndex--; + WindowState wallpaper = token.windows.get(curWallpaperIndex); + if (updateWallpaperOffsetLocked(wallpaper, dw, dh, sync)) { + wallpaper.computeShownFrameLocked(); + changed = true; + // We only want to be synchronous with one wallpaper. + sync = false; + } + } + } + + return changed; + } + + void updateWallpaperVisibilityLocked() { + final boolean visible = isWallpaperVisible(mWallpaperTarget); + final int dw = mDisplay.getWidth(); + final int dh = mDisplay.getHeight(); + + int curTokenIndex = mWallpaperTokens.size(); + while (curTokenIndex > 0) { + curTokenIndex--; + WindowToken token = mWallpaperTokens.get(curTokenIndex); + if (token.hidden == visible) { + token.hidden = !visible; + // Need to do a layout to ensure the wallpaper now has the + // correct size. + mLayoutNeeded = true; + } + + int curWallpaperIndex = token.windows.size(); + while (curWallpaperIndex > 0) { + curWallpaperIndex--; + WindowState wallpaper = token.windows.get(curWallpaperIndex); + if (visible) { + updateWallpaperOffsetLocked(wallpaper, dw, dh, false); + } + + if (wallpaper.mWallpaperVisible != visible) { + wallpaper.mWallpaperVisible = visible; + try { + if (DEBUG_VISIBILITY || DEBUG_WALLPAPER) Log.v(TAG, + "Updating visibility of wallpaper " + wallpaper + + ": " + visible); + wallpaper.mClient.dispatchAppVisibility(visible); + } catch (RemoteException e) { + } + } + } + } + } + + void sendPointerToWallpaperLocked(WindowState srcWin, + MotionEvent pointer, long eventTime) { + int curTokenIndex = mWallpaperTokens.size(); + while (curTokenIndex > 0) { + curTokenIndex--; + WindowToken token = mWallpaperTokens.get(curTokenIndex); + int curWallpaperIndex = token.windows.size(); + while (curWallpaperIndex > 0) { + curWallpaperIndex--; + WindowState wallpaper = token.windows.get(curWallpaperIndex); + if ((wallpaper.mAttrs.flags & + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) { + continue; + } + try { + MotionEvent ev = MotionEvent.obtainNoHistory(pointer); + if (srcWin != null) { + ev.offsetLocation(srcWin.mFrame.left-wallpaper.mFrame.left, + srcWin.mFrame.top-wallpaper.mFrame.top); + } else { + ev.offsetLocation(-wallpaper.mFrame.left, -wallpaper.mFrame.top); + } + switch (pointer.getAction()) { + case MotionEvent.ACTION_DOWN: + mSendingPointersToWallpaper = true; + break; + case MotionEvent.ACTION_UP: + mSendingPointersToWallpaper = false; + break; + } + wallpaper.mClient.dispatchPointer(ev, eventTime, false); + } catch (RemoteException e) { + Log.w(TAG, "Failure sending pointer to wallpaper", e); + } + } + } + } + public int addWindow(Session session, IWindow client, WindowManager.LayoutParams attrs, int viewVisibility, Rect outContentInsets) { @@ -1216,6 +1829,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo + attrs.token + ". Aborting."); return WindowManagerImpl.ADD_BAD_APP_TOKEN; } + if (attrs.type == TYPE_WALLPAPER) { + Log.w(TAG, "Attempted to add wallpaper window with unknown token " + + attrs.token + ". Aborting."); + return WindowManagerImpl.ADD_BAD_APP_TOKEN; + } token = new WindowToken(attrs.token, -1, false); addToken = true; } else if (attrs.type >= FIRST_APPLICATION_WINDOW @@ -1242,6 +1860,12 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo + attrs.token + ". Aborting."); return WindowManagerImpl.ADD_BAD_APP_TOKEN; } + } else if (attrs.type == TYPE_WALLPAPER) { + if (token.windowType != TYPE_WALLPAPER) { + Log.w(TAG, "Attempted to add wallpaper window with bad token " + + attrs.token + ". Aborting."); + return WindowManagerImpl.ADD_BAD_APP_TOKEN; + } } win = new WindowState(session, client, token, @@ -1292,6 +1916,12 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo imMayMove = false; } else { addWindowToListInOrderLocked(win, true); + if (attrs.type == TYPE_WALLPAPER) { + mLastWallpaperTimeoutTime = 0; + adjustWallpaperWindowsLocked(); + } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) { + adjustWallpaperWindowsLocked(); + } } win.mEnterAnimationPending = true; @@ -1432,6 +2062,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } private void removeWindowInnerLocked(Session session, WindowState win) { + mKeyWaiter.finishedKey(session, win.mClient, true, + KeyWaiter.RETURN_NOTHING); mKeyWaiter.releasePendingPointerLocked(win.mSession); mKeyWaiter.releasePendingTrackballLocked(win.mSession); @@ -1441,11 +2073,18 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo moveInputMethodWindowsIfNeededLocked(false); } + if (false) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Log.w(TAG, "Removing window " + win, e); + } + mPolicy.removeWindowLw(win); win.removeLocked(); mWindowMap.remove(win.mClient.asBinder()); mWindows.remove(win); + if (DEBUG_WINDOW_MOVEMENT) Log.v(TAG, "Final remove of window: " + win); if (mInputMethodWindow == win) { mInputMethodWindow = null; @@ -1490,6 +2129,13 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } + if (win.mAttrs.type == TYPE_WALLPAPER) { + mLastWallpaperTimeoutTime = 0; + adjustWallpaperWindowsLocked(); + } else if ((win.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0) { + adjustWallpaperWindowsLocked(); + } + if (!mInLayout) { assignLayersLocked(); mLayoutNeeded = true; @@ -1506,10 +2152,15 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo synchronized (mWindowMap) { WindowState w = windowForClientLocked(session, client); if ((w != null) && (w.mSurface != null)) { + if (SHOW_TRANSACTIONS) Log.i(TAG, ">>> OPEN TRANSACTION"); Surface.openTransaction(); try { + if (SHOW_TRANSACTIONS) Log.i( + TAG, " SURFACE " + w.mSurface + + ": transparentRegionHint=" + region); w.mSurface.setTransparentRegionHint(region); } finally { + if (SHOW_TRANSACTIONS) Log.i(TAG, "<<< CLOSE TRANSACTION"); Surface.closeTransaction(); } } @@ -1552,6 +2203,60 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } + public void setWindowWallpaperPositionLocked(WindowState window, float x, float y, + float xStep, float yStep) { + if (window.mWallpaperX != x || window.mWallpaperY != y) { + window.mWallpaperX = x; + window.mWallpaperY = y; + window.mWallpaperXStep = xStep; + window.mWallpaperYStep = yStep; + if (updateWallpaperOffsetLocked(window, true)) { + performLayoutAndPlaceSurfacesLocked(); + } + } + } + + void wallpaperCommandComplete(IBinder window, Bundle result) { + synchronized (mWindowMap) { + if (mWaitingOnWallpaper != null && + mWaitingOnWallpaper.mClient.asBinder() == window) { + mWaitingOnWallpaper = null; + mWindowMap.notifyAll(); + } + } + } + + public Bundle sendWindowWallpaperCommandLocked(WindowState window, + String action, int x, int y, int z, Bundle extras, boolean sync) { + if (window == mWallpaperTarget || window == mLowerWallpaperTarget + || window == mUpperWallpaperTarget) { + boolean doWait = sync; + int curTokenIndex = mWallpaperTokens.size(); + while (curTokenIndex > 0) { + curTokenIndex--; + WindowToken token = mWallpaperTokens.get(curTokenIndex); + int curWallpaperIndex = token.windows.size(); + while (curWallpaperIndex > 0) { + curWallpaperIndex--; + WindowState wallpaper = token.windows.get(curWallpaperIndex); + try { + wallpaper.mClient.dispatchWallpaperCommand(action, + x, y, z, extras, sync); + // We only want to be synchronous with one wallpaper. + sync = false; + } catch (RemoteException e) { + } + } + } + + if (doWait) { + // XXX Need to wait for result. + } + } + + return null; + } + public int relayoutWindow(Session session, IWindow client, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, boolean insetsPending, @@ -1610,6 +2315,9 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo || ((flagChanges&WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) || (!win.mRelayoutCalled); + boolean wallpaperMayMove = win.mViewVisibility != viewVisibility + && (win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0; + win.mRelayoutCalled = true; final int oldVisibility = win.mViewVisibility; win.mViewVisibility = viewVisibility; @@ -1631,6 +2339,10 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo && !win.mCommitDrawPending && !mDisplayFrozen) { applyEnterAnimationLocked(win); } + if (displayed && (win.mAttrs.flags + & WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) != 0) { + win.mTurnOnScreen = true; + } if ((attrChanges&WindowManager.LayoutParams.FORMAT_CHANGED) != 0) { // To change the format, we need to re-build the surface. win.destroySurfaceLocked(); @@ -1640,13 +2352,19 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo Surface surface = win.createSurfaceLocked(); if (surface != null) { outSurface.copyFrom(surface); + win.mReportDestroySurface = false; + win.mSurfacePendingDestroy = false; + if (SHOW_TRANSACTIONS) Log.i(TAG, + " OUT SURFACE " + outSurface + ": copied"); } else { - outSurface.clear(); + // For some reason there isn't a surface. Clear the + // caller's object so they see the same state. + outSurface.release(); } } catch (Exception e) { Log.w(TAG, "Exception thrown when creating surface for client " - + client + " (" + win.mAttrs.getTitle() + ")", - e); + + client + " (" + win.mAttrs.getTitle() + ")", + e); Binder.restoreCallingIdentity(origId); return 0; } @@ -1661,17 +2379,21 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } else { win.mEnterAnimationPending = false; if (win.mSurface != null) { + if (DEBUG_VISIBILITY) Log.i(TAG, "Relayout invis " + win + + ": mExiting=" + win.mExiting + + " mSurfacePendingDestroy=" + win.mSurfacePendingDestroy); // If we are not currently running the exit animation, we // need to see about starting one. - if (!win.mExiting) { + if (!win.mExiting || win.mSurfacePendingDestroy) { // Try starting an animation; if there isn't one, we // can destroy the surface right away. int transit = WindowManagerPolicy.TRANSIT_EXIT; if (win.getAttrs().type == TYPE_APPLICATION_STARTING) { transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE; } - if (win.isWinVisibleLw() && + if (!win.mSurfacePendingDestroy && win.isWinVisibleLw() && applyAnimationLocked(win, transit, false)) { + focusMayChange = true; win.mExiting = true; mKeyWaiter.finishedKey(session, client, true, KeyWaiter.RETURN_NOTHING); @@ -1679,6 +2401,12 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // Currently in a hide animation... turn this into // an exit. win.mExiting = true; + } else if (win == mWallpaperTarget) { + // If the wallpaper is currently behind this + // window, we need to change both of them inside + // of a transaction to avoid artifacts. + win.mExiting = true; + win.mAnimating = true; } else { if (mInputMethodWindow == win) { mInputMethodWindow = null; @@ -1687,7 +2415,23 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } } - outSurface.clear(); + + if (win.mSurface == null || (win.getAttrs().flags + & WindowManager.LayoutParams.FLAG_KEEP_SURFACE_WHILE_ANIMATING) == 0 + || win.mSurfacePendingDestroy) { + // We are being called from a local process, which + // means outSurface holds its current surface. Ensure the + // surface object is cleared, but we don't want it actually + // destroyed at this point. + win.mSurfacePendingDestroy = false; + outSurface.release(); + if (DEBUG_VISIBILITY) Log.i(TAG, "Releasing surface in: " + win); + } else if (win.mSurface != null) { + if (DEBUG_VISIBILITY) Log.i(TAG, + "Keeping surface, will report destroy: " + win); + win.mReportDestroySurface = true; + outSurface.copyFrom(win.mSurface); + } } if (focusMayChange) { @@ -1707,6 +2451,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo assignLayers = true; } } + if (wallpaperMayMove) { + if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { + assignLayers = true; + } + } mLayoutNeeded = true; win.mGivenInsetsPending = insetsPending; @@ -1715,6 +2464,10 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } newConfig = updateOrientationFromAppTokensLocked(null, null); performLayoutAndPlaceSurfacesLocked(); + if (displayed && win.mIsWallpaper) { + updateWallpaperOffsetLocked(win, mDisplay.getWidth(), + mDisplay.getHeight(), false); + } if (win.mAppToken != null) { win.mAppToken.updateReportedVisibilityLocked(); } @@ -1750,6 +2503,9 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo synchronized(mWindowMap) { WindowState win = windowForClientLocked(session, client); if (win != null && win.finishDrawingLocked()) { + if ((win.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0) { + adjustWallpaperWindowsLocked(); + } mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); } @@ -1778,6 +2534,21 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo return null; } + private AttributeCache.Entry getCachedAnimations(String packageName, int resId) { + if (DEBUG_ANIM) Log.v(TAG, "Loading animations: params package=" + + packageName + " resId=0x" + Integer.toHexString(resId)); + if (packageName != null) { + if ((resId&0xFF000000) == 0x01000000) { + packageName = "android"; + } + if (DEBUG_ANIM) Log.v(TAG, "Loading animations: picked package=" + + packageName); + return AttributeCache.instance().get(packageName, resId, + com.android.internal.R.styleable.WindowAnimation); + } + return null; + } + private void applyEnterAnimationLocked(WindowState win) { int transit = WindowManagerPolicy.TRANSIT_SHOW; if (win.mEnterAnimationPending) { @@ -1832,7 +2603,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (a != null) { if (DEBUG_ANIM) { RuntimeException e = new RuntimeException(); - e.fillInStackTrace(); + if (!HIDE_STACK_CRAWLS) e.fillInStackTrace(); Log.v(TAG, "Loaded animation " + a + " for " + win, e); } win.setAnimation(a); @@ -1861,6 +2632,22 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo return null; } + private Animation loadAnimation(String packageName, int resId) { + int anim = 0; + Context context = mContext; + if (resId >= 0) { + AttributeCache.Entry ent = getCachedAnimations(packageName, resId); + if (ent != null) { + context = ent.context; + anim = resId; + } + } + if (anim != 0) { + return AnimationUtils.loadAnimation(context, anim); + } + return null; + } + private boolean applyAnimationLocked(AppWindowToken wtoken, WindowManager.LayoutParams lp, int transit, boolean enter) { // Only apply an animation if the display isn't frozen. If it is @@ -1873,6 +2660,9 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo a = new FadeInOutAnimation(enter); if (DEBUG_ANIM) Log.v(TAG, "applying FadeInOutAnimation for a window in compatibility mode"); + } else if (mNextAppTransitionPackage != null) { + a = loadAnimation(mNextAppTransitionPackage, enter ? + mNextAppTransitionEnter : mNextAppTransitionExit); } else { int animAttr = 0; switch (transit) { @@ -1906,8 +2696,28 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo ? com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation : com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation; break; + case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_wallpaperCloseEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_wallpaperCloseExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation; + break; } - a = loadAnimation(lp, animAttr); + a = animAttr != 0 ? loadAnimation(lp, animAttr) : null; if (DEBUG_ANIM) Log.v(TAG, "applyAnimation: wtoken=" + wtoken + " anim=" + a + " animAttr=0x" + Integer.toHexString(animAttr) @@ -1916,7 +2726,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (a != null) { if (DEBUG_ANIM) { RuntimeException e = new RuntimeException(); - e.fillInStackTrace(); + if (!HIDE_STACK_CRAWLS) e.fillInStackTrace(); Log.v(TAG, "Loaded animation " + a + " for " + wtoken, e); } wtoken.setAnimation(a); @@ -2002,6 +2812,9 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo wtoken = new WindowToken(token, type, true); mTokenMap.put(token, wtoken); mTokenList.add(wtoken); + if (type == TYPE_WALLPAPER) { + mWallpaperTokens.add(wtoken); + } } } @@ -2047,6 +2860,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (delayed) { mExitingTokens.add(wtoken); + } else if (wtoken.windowType == TYPE_WALLPAPER) { + mWallpaperTokens.remove(wtoken); } } @@ -2206,10 +3021,10 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo Configuration config; synchronized(mWindowMap) { config = updateOrientationFromAppTokensLocked(currentConfig, freezeThisOneIfNeeded); - } - if (config != null) { - mLayoutNeeded = true; - performLayoutAndPlaceSurfacesLocked(); + if (config != null) { + mLayoutNeeded = true; + performLayoutAndPlaceSurfacesLocked(); + } } return config; } @@ -2259,7 +3074,6 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo mTempConfiguration.setToDefaults(); if (computeNewConfigurationLocked(mTempConfiguration)) { if (appConfig.diff(mTempConfiguration) != 0) { - Log.i(TAG, "Config changed: " + mTempConfiguration); return new Configuration(mTempConfiguration); } } @@ -2351,7 +3165,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo TAG, "Prepare app transition: transit=" + transit + " mNextAppTransition=" + mNextAppTransition); if (!mDisplayFrozen) { - if (mNextAppTransition == WindowManagerPolicy.TRANSIT_NONE) { + if (mNextAppTransition == WindowManagerPolicy.TRANSIT_UNSET + || mNextAppTransition == WindowManagerPolicy.TRANSIT_NONE) { mNextAppTransition = transit; } else if (transit == WindowManagerPolicy.TRANSIT_TASK_OPEN && mNextAppTransition == WindowManagerPolicy.TRANSIT_TASK_CLOSE) { @@ -2377,6 +3192,15 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo return mNextAppTransition; } + public void overridePendingAppTransition(String packageName, + int enterAnim, int exitAnim) { + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + mNextAppTransitionPackage = packageName; + mNextAppTransitionEnter = enterAnim; + mNextAppTransitionExit = exitAnim; + } + } + public void executeAppTransition() { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "executeAppTransition()")) { @@ -2384,9 +3208,13 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } synchronized(mWindowMap) { - if (DEBUG_APP_TRANSITIONS) Log.v( - TAG, "Execute app transition: mNextAppTransition=" + mNextAppTransition); - if (mNextAppTransition != WindowManagerPolicy.TRANSIT_NONE) { + if (DEBUG_APP_TRANSITIONS) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Log.w(TAG, "Execute app transition: mNextAppTransition=" + + mNextAppTransition, e); + } + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { mAppTransitionReady = true; final long origId = Binder.clearCallingIdentity(); performLayoutAndPlaceSurfacesLocked(); @@ -2453,11 +3281,12 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo startingWindow.mToken = wtoken; startingWindow.mRootToken = wtoken; startingWindow.mAppToken = wtoken; + if (DEBUG_WINDOW_MOVEMENT) Log.v(TAG, + "Removing starting window: " + startingWindow); mWindows.remove(startingWindow); ttoken.windows.remove(startingWindow); ttoken.allAppWindows.remove(startingWindow); addWindowToListInOrderLocked(startingWindow, true); - wtoken.allAppWindows.add(startingWindow); // Propagate other interesting state between the // tokens. If the old token is displayed, we should @@ -2519,6 +3348,27 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo return; } + // If this is a translucent or wallpaper window, then don't + // show a starting window -- the current effect (a full-screen + // opaque starting window that fades away to the real contents + // when it is ready) does not work for this. + if (theme != 0) { + AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme, + com.android.internal.R.styleable.Window); + if (ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowIsTranslucent, false)) { + return; + } + if (ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowIsFloating, false)) { + return; + } + if (ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowShowWallpaper, false)) { + return; + } + } + mStartingIconInTransition = true; wtoken.startingData = new StartingData( pkg, theme, nonLocalizedLabel, @@ -2568,7 +3418,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo boolean runningAppAnimation = false; - if (transit != WindowManagerPolicy.TRANSIT_NONE) { + if (transit != WindowManagerPolicy.TRANSIT_UNSET) { if (wtoken.animation == sDummyAnimation) { wtoken.animation = null; } @@ -2659,7 +3509,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) { RuntimeException e = new RuntimeException(); - e.fillInStackTrace(); + if (!HIDE_STACK_CRAWLS) e.fillInStackTrace(); Log.v(TAG, "setAppVisibility(" + token + ", " + visible + "): mNextAppTransition=" + mNextAppTransition + " hidden=" + wtoken.hidden @@ -2668,7 +3518,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // If we are preparing an app transition, then delay changing // the visibility of this token until we execute that transition. - if (!mDisplayFrozen && mNextAppTransition != WindowManagerPolicy.TRANSIT_NONE) { + if (!mDisplayFrozen && mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { // Already in requested state, don't do anything more. if (wtoken.hiddenRequested != visible) { return; @@ -2680,12 +3530,14 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo wtoken.setDummyAnimation(); mOpeningApps.remove(wtoken); mClosingApps.remove(wtoken); + wtoken.waitingToShow = wtoken.waitingToHide = false; wtoken.inPendingTransaction = true; if (visible) { mOpeningApps.add(wtoken); wtoken.allDrawn = false; wtoken.startingDisplayed = false; wtoken.startingMoved = false; + wtoken.waitingToShow = true; if (wtoken.clientHidden) { // In the case where we are making an app visible @@ -2699,12 +3551,13 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } else { mClosingApps.add(wtoken); + wtoken.waitingToHide = true; } return; } final long origId = Binder.clearCallingIdentity(); - setTokenVisibilityLocked(wtoken, null, visible, WindowManagerPolicy.TRANSIT_NONE, true); + setTokenVisibilityLocked(wtoken, null, visible, WindowManagerPolicy.TRANSIT_UNSET, true); wtoken.updateReportedVisibilityLocked(); Binder.restoreCallingIdentity(origId); } @@ -2748,7 +3601,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo int configChanges) { if (DEBUG_ORIENTATION) { RuntimeException e = new RuntimeException(); - e.fillInStackTrace(); + if (!HIDE_STACK_CRAWLS) e.fillInStackTrace(); Log.i(TAG, "Set freezing of " + wtoken.appToken + ": hidden=" + wtoken.hidden + " freezing=" + wtoken.freezingScreen, e); @@ -2830,13 +3683,15 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo mTokenList.remove(basewtoken); if (basewtoken != null && (wtoken=basewtoken.appWindowToken) != null) { if (DEBUG_APP_TRANSITIONS) Log.v(TAG, "Removing app token: " + wtoken); - delayed = setTokenVisibilityLocked(wtoken, null, false, WindowManagerPolicy.TRANSIT_NONE, true); + delayed = setTokenVisibilityLocked(wtoken, null, false, WindowManagerPolicy.TRANSIT_UNSET, true); wtoken.inPendingTransaction = false; mOpeningApps.remove(wtoken); + wtoken.waitingToShow = false; if (mClosingApps.contains(wtoken)) { delayed = true; - } else if (mNextAppTransition != WindowManagerPolicy.TRANSIT_NONE) { + } else if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { mClosingApps.add(wtoken); + wtoken.waitingToHide = true; delayed = true; } if (DEBUG_APP_TRANSITIONS) Log.v( @@ -2846,8 +3701,18 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (delayed) { // set the token aside because it has an active animation to be finished mExitingAppTokens.add(wtoken); + } else { + // Make sure there is no animation running on this token, + // so any windows associated with it will be removed as + // soon as their animations are complete + wtoken.animation = null; + wtoken.animating = false; } mAppTokens.remove(wtoken); + if (mLastEnterAnimToken == wtoken) { + mLastEnterAnimToken = null; + mLastEnterAnimParams = null; + } wtoken.removed = true; if (wtoken.startingData != null) { startingToken = wtoken; @@ -2881,11 +3746,15 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo final int NW = token.windows.size(); for (int i=0; i<NW; i++) { WindowState win = token.windows.get(i); + if (DEBUG_WINDOW_MOVEMENT) Log.v(TAG, "Tmp removing app window " + win); mWindows.remove(win); int j = win.mChildWindows.size(); while (j > 0) { j--; - mWindows.remove(win.mChildWindows.get(j)); + WindowState cwin = (WindowState)win.mChildWindows.get(j); + if (DEBUG_WINDOW_MOVEMENT) Log.v(TAG, + "Tmp removing child window " + cwin); + mWindows.remove(cwin); } } return NW > 0; @@ -2923,6 +3792,12 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo final AppWindowToken wtoken = mAppTokens.get(tokenPos-1); if (DEBUG_REORDER) Log.v(TAG, "Looking for lower windows @ " + tokenPos + " -- " + wtoken.token); + if (wtoken.sendingToBottom) { + if (DEBUG_REORDER) Log.v(TAG, + "Skipping token -- currently sending to bottom"); + tokenPos--; + continue; + } int i = wtoken.windows.size(); while (i > 0) { i--; @@ -2931,7 +3806,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo while (j > 0) { j--; WindowState cwin = (WindowState)win.mChildWindows.get(j); - if (cwin.mSubLayer >= 0 ) { + if (cwin.mSubLayer >= 0) { for (int pos=NW-1; pos>=0; pos--) { if (mWindows.get(pos) == cwin) { if (DEBUG_REORDER) Log.v(TAG, @@ -2960,14 +3835,20 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo for (int j=0; j<NCW; j++) { WindowState cwin = (WindowState)win.mChildWindows.get(j); if (!added && cwin.mSubLayer >= 0) { + if (DEBUG_WINDOW_MOVEMENT) Log.v(TAG, "Re-adding child window at " + + index + ": " + cwin); mWindows.add(index, win); index++; added = true; } + if (DEBUG_WINDOW_MOVEMENT) Log.v(TAG, "Re-adding window at " + + index + ": " + cwin); mWindows.add(index, cwin); index++; } if (!added) { + if (DEBUG_WINDOW_MOVEMENT) Log.v(TAG, "Re-adding window at " + + index + ": " + win); mWindows.add(index, win); index++; } @@ -3035,6 +3916,26 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } + private void moveAppWindowsLocked(AppWindowToken wtoken, int tokenPos, + boolean updateFocusAndLayout) { + // First remove all of the windows from the list. + tmpRemoveAppWindowsLocked(wtoken); + + // Where to start adding? + int pos = findWindowOffsetLocked(tokenPos); + + // And now add them back at the correct place. + pos = reAddAppWindowsLocked(pos, wtoken); + + if (updateFocusAndLayout) { + if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) { + assignLayersLocked(); + } + mLayoutNeeded = true; + performLayoutAndPlaceSurfacesLocked(); + } + } + private void moveAppWindowsLocked(List<IBinder> tokens, int tokenPos) { // First remove all of the windows from the list. final int N = tokens.size(); @@ -3057,7 +3958,9 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } - updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES); + if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) { + assignLayersLocked(); + } mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); @@ -3078,9 +3981,19 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo AppWindowToken wt = findAppWindowToken(tokens.get(i)); if (wt != null) { mAppTokens.add(wt); + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + mToTopApps.remove(wt); + mToBottomApps.remove(wt); + mToTopApps.add(wt); + wt.sendingToBottom = false; + wt.sendingToTop = true; + } } } - moveAppWindowsLocked(tokens, mAppTokens.size()); + + if (mNextAppTransition == WindowManagerPolicy.TRANSIT_UNSET) { + moveAppWindowsLocked(tokens, mAppTokens.size()); + } } Binder.restoreCallingIdentity(origId); } @@ -3100,10 +4013,20 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo AppWindowToken wt = findAppWindowToken(tokens.get(i)); if (wt != null) { mAppTokens.add(pos, wt); + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + mToTopApps.remove(wt); + mToBottomApps.remove(wt); + mToBottomApps.add(i, wt); + wt.sendingToTop = false; + wt.sendingToBottom = true; + } pos++; } } - moveAppWindowsLocked(tokens, 0); + + if (mNextAppTransition == WindowManagerPolicy.TRANSIT_UNSET) { + moveAppWindowsLocked(tokens, 0); + } } Binder.restoreCallingIdentity(origId); } @@ -3113,15 +4036,17 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // ------------------------------------------------------------- public void disableKeyguard(IBinder token, String tag) { - if (mContext.checkCallingPermission(android.Manifest.permission.DISABLE_KEYGUARD) + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires DISABLE_KEYGUARD permission"); } - mKeyguardDisabled.acquire(token, tag); + synchronized (mKeyguardDisabled) { + mKeyguardDisabled.acquire(token, tag); + } } public void reenableKeyguard(IBinder token) { - if (mContext.checkCallingPermission(android.Manifest.permission.DISABLE_KEYGUARD) + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires DISABLE_KEYGUARD permission"); } @@ -3147,7 +4072,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo * @see android.app.KeyguardManager#exitKeyguardSecurely */ public void exitKeyguardSecurely(final IOnKeyguardExitResult callback) { - if (mContext.checkCallingPermission(android.Manifest.permission.DISABLE_KEYGUARD) + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires DISABLE_KEYGUARD permission"); } @@ -3166,6 +4091,20 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo return mPolicy.inKeyguardRestrictedKeyInputMode(); } + public void closeSystemDialogs(String reason) { + synchronized(mWindowMap) { + for (int i=mWindows.size()-1; i>=0; i--) { + WindowState w = (WindowState)mWindows.get(i); + if (w.mSurface != null) { + try { + w.mClient.closeSystemDialogs(reason); + } catch (RemoteException e) { + } + } + } + } + } + static float fixScale(float scale) { if (scale < 0) scale = 0; else if (scale > 20) scale = 20; @@ -3242,7 +4181,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo "getScancodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return KeyInputQueue.getScancodeState(sw); + return mQueue.getScancodeState(sw); } public int getScancodeStateForDevice(int devid, int sw) { @@ -3250,7 +4189,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo "getScancodeStateForDevice()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return KeyInputQueue.getScancodeState(devid, sw); + return mQueue.getScancodeState(devid, sw); } public int getKeycodeState(int sw) { @@ -3258,7 +4197,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo "getKeycodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return KeyInputQueue.getKeycodeState(sw); + return mQueue.getKeycodeState(sw); } public int getKeycodeStateForDevice(int devid, int sw) { @@ -3266,7 +4205,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo "getKeycodeStateForDevice()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return KeyInputQueue.getKeycodeState(devid, sw); + return mQueue.getKeycodeState(devid, sw); } public boolean hasKeys(int[] keycodes, boolean[] keyExists) { @@ -3308,7 +4247,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo final int N = mWindows.size(); for (int i=0; i<N; i++) { WindowState w = (WindowState)mWindows.get(i); - if (w.isVisibleLw() && !w.isDisplayedLw()) { + if (w.isVisibleLw() && !w.isDrawnLw()) { return; } } @@ -3735,19 +4674,6 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (!computeNewConfigurationLocked(config)) { return null; } - Log.i(TAG, "Config changed: " + config); - long now = SystemClock.uptimeMillis(); - //Log.i(TAG, "Config changing, gc pending: " + mFreezeGcPending + ", now " + now); - if (mFreezeGcPending != 0) { - if (now > (mFreezeGcPending+1000)) { - //Log.i(TAG, "Gc! " + now + " > " + (mFreezeGcPending+1000)); - mH.removeMessages(H.FORCE_GC); - Runtime.getRuntime().gc(); - mFreezeGcPending = now; - } - } else { - mFreezeGcPending = now; - } return config; } @@ -3833,7 +4759,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo private final void wakeupIfNeeded(WindowState targetWin, int eventType) { long curTime = SystemClock.uptimeMillis(); - if (eventType == LONG_TOUCH_EVENT || eventType == CHEEK_EVENT) { + if (eventType == TOUCH_EVENT || eventType == LONG_TOUCH_EVENT || eventType == CHEEK_EVENT) { if (mLastTouchEventType == eventType && (curTime - mLastUserActivityCallTime) < MIN_TIME_BETWEEN_USERACTIVITIES) { return; @@ -3893,8 +4819,16 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (DEBUG_INPUT || WindowManagerPolicy.WATCH_POINTER) Log.v(TAG, "dispatchPointer " + ev); + if (MEASURE_LATENCY) { + lt.sample("3 Wait for last dispatch ", System.nanoTime() - qev.whenNano); + } + Object targetObj = mKeyWaiter.waitForNextEventTarget(null, qev, ev, true, false, pid, uid); + + if (MEASURE_LATENCY) { + lt.sample("3 Last dispatch finished ", System.nanoTime() - qev.whenNano); + } int action = ev.getAction(); @@ -3915,6 +4849,12 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (action != MotionEvent.ACTION_MOVE) { Log.w(TAG, "No window to dispatch pointer action " + ev.getAction()); } + synchronized (mWindowMap) { + if (mSendingPointersToWallpaper) { + Log.i(TAG, "Sending skipped pointer to wallpaper!"); + sendPointerToWallpaperLocked(null, ev, ev.getEventTime()); + } + } if (qev != null) { mQueue.recycleEvent(qev); } @@ -3922,6 +4862,12 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo return INJECT_FAILED; } if (targetObj == mKeyWaiter.CONSUMED_EVENT_TOKEN) { + synchronized (mWindowMap) { + if (mSendingPointersToWallpaper) { + Log.i(TAG, "Sending skipped pointer to wallpaper!"); + sendPointerToWallpaperLocked(null, ev, ev.getEventTime()); + } + } if (qev != null) { mQueue.recycleEvent(qev); } @@ -3932,7 +4878,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo WindowState target = (WindowState)targetObj; final long eventTime = ev.getEventTime(); - + final long eventTimeNano = ev.getEventTimeNano(); + //Log.i(TAG, "Sending " + ev + " to " + target); if (uid != 0 && uid != target.mSession.mUid) { @@ -3949,6 +4896,10 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo return INJECT_NO_PERMISSION; } } + + if (MEASURE_LATENCY) { + lt.sample("4 in dispatchPointer ", System.nanoTime() - eventTimeNano); + } if ((target.mAttrs.flags & WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES) != 0) { @@ -4032,7 +4983,24 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } + if (MEASURE_LATENCY) { + lt.sample("5 in dispatchPointer ", System.nanoTime() - eventTimeNano); + } + synchronized(mWindowMap) { + if (!target.isVisibleLw()) { + // During this motion dispatch, the target window has become + // invisible. + if (mSendingPointersToWallpaper) { + sendPointerToWallpaperLocked(null, ev, eventTime); + } + if (qev != null) { + mQueue.recycleEvent(qev); + } + ev.recycle(); + return INJECT_SUCCEEDED; + } + if (qev != null && action == MotionEvent.ACTION_MOVE) { mKeyWaiter.bindTargetWindowLocked(target, KeyWaiter.RETURN_PENDING_POINTER, qev); @@ -4047,7 +5015,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo final Rect frame = out.mFrame; oev.offsetLocation(-(float)frame.left, -(float)frame.top); try { - out.mClient.dispatchPointer(oev, eventTime); + out.mClient.dispatchPointer(oev, eventTime, false); } catch (android.os.RemoteException e) { Log.i(TAG, "WINDOW DIED during outside motion dispatch: " + out); } @@ -4057,6 +5025,13 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo mKeyWaiter.mOutsideTouchTargets = null; } } + + // If we are on top of the wallpaper, then the wallpaper also + // gets to see this movement. + if (mWallpaperTarget == target || mSendingPointersToWallpaper) { + sendPointerToWallpaperLocked(null, ev, eventTime); + } + final Rect frame = target.mFrame; ev.offsetLocation(-(float)frame.left, -(float)frame.top); mKeyWaiter.bindTargetWindowLocked(target); @@ -4069,7 +5044,16 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (DEBUG_INPUT || DEBUG_FOCUS || WindowManagerPolicy.WATCH_POINTER) { Log.v(TAG, "Delivering pointer " + qev + " to " + target); } - target.mClient.dispatchPointer(ev, eventTime); + + if (MEASURE_LATENCY) { + lt.sample("6 before svr->client ipc ", System.nanoTime() - eventTimeNano); + } + + target.mClient.dispatchPointer(ev, eventTime, true); + + if (MEASURE_LATENCY) { + lt.sample("7 after svr->client ipc ", System.nanoTime() - eventTimeNano); + } return INJECT_SUCCEEDED; } catch (android.os.RemoteException e) { Log.i(TAG, "WINDOW DIED during motion dispatch: " + target); @@ -4141,7 +5125,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } try { - focus.mClient.dispatchTrackball(ev, eventTime); + focus.mClient.dispatchTrackball(ev, eventTime, true); return INJECT_SUCCEEDED; } catch (android.os.RemoteException e) { Log.i(TAG, "WINDOW DIED during key dispatch: " + focus); @@ -4172,6 +5156,16 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo return INJECT_SUCCEEDED; } + // Okay we have finished waiting for the last event to be processed. + // First off, if this is a repeat event, check to see if there is + // a corresponding up event in the queue. If there is, we will + // just drop the repeat, because it makes no sense to repeat after + // the user has released a key. (This is especially important for + // long presses.) + if (event.getRepeatCount() > 0 && mQueue.hasKeyUpEvent(event)) { + return INJECT_SUCCEEDED; + } + WindowState focus = (WindowState)focusObj; if (DEBUG_INPUT) Log.v( @@ -4664,7 +5658,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo callingPid, callingUid) == PackageManager.PERMISSION_GRANTED) { mPolicy.interceptKeyTi(null, keycode, - nextKey.getMetaState(), down, repeatCount); + nextKey.getMetaState(), down, repeatCount, + nextKey.getFlags()); } Log.w(TAG, "Event timeout during app switch: dropping " + nextKey); @@ -4687,7 +5682,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo callingPid, callingUid) == PackageManager.PERMISSION_GRANTED) { if (mPolicy.interceptKeyTi(focus, - keycode, nextKey.getMetaState(), down, repeatCount)) { + keycode, nextKey.getMetaState(), down, repeatCount, + nextKey.getFlags())) { return CONSUMED_EVENT_TOKEN; } } @@ -4914,14 +5910,16 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo return null; } + MotionEvent res = null; + QueuedEvent qev = null; + WindowState win = null; + synchronized (this) { if (DEBUG_INPUT) Log.v( TAG, "finishedKey: client=" + client.asBinder() + ", force=" + force + ", last=" + mLastBinder + " (token=" + (mLastWin != null ? mLastWin.mToken : null) + ")"); - QueuedEvent qev = null; - WindowState win = null; if (returnWhat == RETURN_PENDING_POINTER) { qev = session.mPendingPointerMove; win = session.mPendingPointerWindow; @@ -4951,17 +5949,25 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } if (qev != null) { - MotionEvent res = (MotionEvent)qev.event; + res = (MotionEvent)qev.event; if (DEBUG_INPUT) Log.v(TAG, "Returning pending motion: " + res); mQueue.recycleEvent(qev); if (win != null && returnWhat == RETURN_PENDING_POINTER) { res.offsetLocation(-win.mFrame.left, -win.mFrame.top); } - return res; } - return null; + + if (res != null && returnWhat == RETURN_PENDING_POINTER) { + synchronized (mWindowMap) { + if (mWallpaperTarget == win || mSendingPointersToWallpaper) { + sendPointerToWallpaperLocked(win, res, res.getEventTime()); + } + } + } } + + return res; } void tickle() { @@ -5107,7 +6113,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo PowerManager.WakeLock mHoldingScreen; KeyQ() { - super(mContext); + super(mContext, WindowManagerService.this); PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); mHoldingScreen = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "KEEP_SCREEN_ON_FLAG"); @@ -5140,8 +6146,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } // XXX end hack - boolean screenIsOff = !mPowerManager.screenIsOn(); - boolean screenIsDim = !mPowerManager.screenIsBright(); + boolean screenIsOff = !mPowerManager.isScreenOn(); + boolean screenIsDim = !mPowerManager.isScreenBright(); int actions = mPolicy.interceptKeyTq(event, !screenIsOff); if ((actions & WindowManagerPolicy.ACTION_GO_TO_SLEEP) != 0) { @@ -5171,8 +6177,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } case RawInputEvent.EV_REL: { - boolean screenIsOff = !mPowerManager.screenIsOn(); - boolean screenIsDim = !mPowerManager.screenIsBright(); + boolean screenIsOff = !mPowerManager.isScreenOn(); + boolean screenIsDim = !mPowerManager.isScreenBright(); if (screenIsOff) { if (!mPolicy.isWakeRelMovementTq(event.deviceId, device.classes, event)) { @@ -5188,8 +6194,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } case RawInputEvent.EV_ABS: { - boolean screenIsOff = !mPowerManager.screenIsOn(); - boolean screenIsDim = !mPowerManager.screenIsBright(); + boolean screenIsOff = !mPowerManager.isScreenOn(); + boolean screenIsDim = !mPowerManager.isScreenBright(); if (screenIsOff) { if (!mPolicy.isWakeAbsMovementTq(event.deviceId, device.classes, event)) { @@ -5238,7 +6244,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } } - }; + } public boolean detectSafeMode() { mSafeMode = mPolicy.detectSafeMode(); @@ -5278,6 +6284,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // Last keydown time for auto-repeating keys long lastKeyTime = SystemClock.uptimeMillis(); long nextKeyTime = lastKeyTime+LONG_WAIT; + long downTime = 0; // How many successive repeats we generated int keyRepeatCount = 0; @@ -5303,9 +6310,20 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (DEBUG_INPUT && ev != null) Log.v( TAG, "Event: type=" + ev.classType + " data=" + ev.event); + if (MEASURE_LATENCY) { + lt.sample("2 got event ", System.nanoTime() - ev.whenNano); + } + + if (lastKey != null && !mPolicy.allowKeyRepeat()) { + // cancel key repeat at the request of the policy. + lastKey = null; + downTime = 0; + lastKeyTime = curTime; + nextKeyTime = curTime + LONG_WAIT; + } try { if (ev != null) { - curTime = ev.when; + curTime = SystemClock.uptimeMillis(); int eventType; if (ev.classType == RawInputEvent.CLASS_TOUCHSCREEN) { eventType = eventType((MotionEvent)ev.event); @@ -5316,31 +6334,45 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo eventType = LocalPowerManager.OTHER_EVENT; } try { - long now = SystemClock.uptimeMillis(); - - if ((now - mLastBatteryStatsCallTime) + if ((curTime - mLastBatteryStatsCallTime) >= MIN_TIME_BETWEEN_USERACTIVITIES) { - mLastBatteryStatsCallTime = now; + mLastBatteryStatsCallTime = curTime; mBatteryStats.noteInputEvent(); } } catch (RemoteException e) { // Ignore } - mPowerManager.userActivity(curTime, false, eventType, false); + + if (eventType != TOUCH_EVENT + && eventType != LONG_TOUCH_EVENT + && eventType != CHEEK_EVENT) { + mPowerManager.userActivity(curTime, false, + eventType, false); + } else if (mLastTouchEventType != eventType + || (curTime - mLastUserActivityCallTime) + >= MIN_TIME_BETWEEN_USERACTIVITIES) { + mLastUserActivityCallTime = curTime; + mLastTouchEventType = eventType; + mPowerManager.userActivity(curTime, false, + eventType, false); + } + switch (ev.classType) { case RawInputEvent.CLASS_KEYBOARD: KeyEvent ke = (KeyEvent)ev.event; if (ke.isDown()) { lastKey = ke; + downTime = curTime; keyRepeatCount = 0; lastKeyTime = curTime; nextKeyTime = lastKeyTime - + KEY_REPEAT_FIRST_DELAY; + + ViewConfiguration.getLongPressTimeout(); if (DEBUG_INPUT) Log.v( TAG, "Received key down: first repeat @ " + nextKeyTime); } else { lastKey = null; + downTime = 0; // Arbitrary long timeout. lastKeyTime = curTime; nextKeyTime = curTime + LONG_WAIT; @@ -5388,7 +6420,19 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (DEBUG_INPUT) Log.v( TAG, "Key repeat: count=" + keyRepeatCount + ", next @ " + nextKeyTime); - dispatchKey(KeyEvent.changeTimeRepeat(lastKey, curTime, keyRepeatCount), 0, 0); + KeyEvent newEvent; + if (downTime != 0 && (downTime + + ViewConfiguration.getLongPressTimeout()) + <= curTime) { + newEvent = KeyEvent.changeTimeRepeat(lastKey, + curTime, keyRepeatCount, + lastKey.getFlags() | KeyEvent.FLAG_LONG_PRESS); + downTime = 0; + } else { + newEvent = KeyEvent.changeTimeRepeat(lastKey, + curTime, keyRepeatCount); + } + dispatchKey(newEvent, 0, 0); } else { curTime = SystemClock.uptimeMillis(); @@ -5592,11 +6636,47 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } + public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) { + synchronized(mWindowMap) { + long ident = Binder.clearCallingIdentity(); + try { + setWindowWallpaperPositionLocked(windowForClientLocked(this, window), + x, y, xStep, yStep); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + public void wallpaperOffsetsComplete(IBinder window) { + WindowManagerService.this.wallpaperOffsetsComplete(window); + } + + public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, + int z, Bundle extras, boolean sync) { + synchronized(mWindowMap) { + long ident = Binder.clearCallingIdentity(); + try { + return sendWindowWallpaperCommandLocked( + windowForClientLocked(this, window), + action, x, y, z, extras, sync); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + public void wallpaperCommandComplete(IBinder window, Bundle result) { + WindowManagerService.this.wallpaperCommandComplete(window, result); + } + void windowAddedLocked() { if (mSurfaceSession == null) { if (localLOGV) Log.v( TAG, "First window added to " + this + ", creating SurfaceSession"); mSurfaceSession = new SurfaceSession(); + if (SHOW_TRANSACTIONS) Log.i( + TAG, " NEW SURFACE SESSION " + mSurfaceSession); mSessions.add(this); } mNumWindow++; @@ -5614,6 +6694,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (localLOGV) Log.v( TAG, "Last window removed from " + this + ", destroying " + mSurfaceSession); + if (SHOW_TRANSACTIONS) Log.i( + TAG, " KILL SURFACE SESSION " + mSurfaceSession); try { mSurfaceSession.kill(); } catch (Exception e) { @@ -5667,23 +6749,28 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo final int mSubLayer; final boolean mLayoutAttached; final boolean mIsImWindow; + final boolean mIsWallpaper; + final boolean mIsFloatingLayer; int mViewVisibility; boolean mPolicyVisibility = true; boolean mPolicyVisibilityAfterAnim = true; boolean mAppFreezing; Surface mSurface; + boolean mReportDestroySurface; + boolean mSurfacePendingDestroy; boolean mAttachedHidden; // is our parent window hidden? boolean mLastHidden; // was this window last hidden? + boolean mWallpaperVisible; // for wallpaper, what was last vis report? int mRequestedWidth; int mRequestedHeight; int mLastRequestedWidth; int mLastRequestedHeight; - int mReqXPos; - int mReqYPos; int mLayer; int mAnimLayer; int mLastLayer; boolean mHaveFrame; + boolean mObscured; + boolean mTurnOnScreen; WindowState mNextOutsideTouch; @@ -5764,6 +6851,20 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo boolean mHasLocalTransformation; final Transformation mTransformation = new Transformation(); + // If a window showing a wallpaper: the requested offset for the + // wallpaper; if a wallpaper window: the currently applied offset. + float mWallpaperX = -1; + float mWallpaperY = -1; + + // If a window showing a wallpaper: what fraction of the offset + // range corresponds to a full virtual screen. + float mWallpaperXStep = -1; + float mWallpaperYStep = -1; + + // Wallpaper windows: pixels offset based on above variables. + int mXOffset; + int mYOffset; + // This is set after IWindowSession.relayout() has been called at // least once for the window. It allows us to detect the situation // where we don't yet have a surface, but should have one soon, so @@ -5824,6 +6925,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo mAttachedWindow = null; mLayoutAttached = false; mIsImWindow = false; + mIsWallpaper = false; + mIsFloatingLayer = false; mBaseLayer = 0; mSubLayer = 0; return; @@ -5844,6 +6947,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; mIsImWindow = attachedWindow.mAttrs.type == TYPE_INPUT_METHOD || attachedWindow.mAttrs.type == TYPE_INPUT_METHOD_DIALOG; + mIsWallpaper = attachedWindow.mAttrs.type == TYPE_WALLPAPER; + mIsFloatingLayer = mIsImWindow || mIsWallpaper; } else { // The multiplier here is to reserve space for multiple // windows in the same type layer. @@ -5855,6 +6960,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo mLayoutAttached = false; mIsImWindow = mAttrs.type == TYPE_INPUT_METHOD || mAttrs.type == TYPE_INPUT_METHOD_DIALOG; + mIsWallpaper = mAttrs.type == TYPE_WALLPAPER; + mIsFloatingLayer = mIsImWindow || mIsWallpaper; } WindowState appWin = this; @@ -5877,8 +6984,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo mRequestedHeight = 0; mLastRequestedWidth = 0; mLastRequestedHeight = 0; - mReqXPos = 0; - mReqYPos = 0; + mXOffset = 0; + mYOffset = 0; mLayer = 0; mAnimLayer = 0; mLastLayer = 0; @@ -5926,6 +7033,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo visible.set(vf); final Rect frame = mFrame; + final int fw = frame.width(); + final int fh = frame.height(); //System.out.println("In: w=" + w + " h=" + h + " container=" + // container + " x=" + mAttrs.x + " y=" + mAttrs.y); @@ -5962,6 +7071,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo visibleInsets.right = frame.right-visible.right; visibleInsets.bottom = frame.bottom-visible.bottom; + if (mIsWallpaper && (fw != frame.width() || fh != frame.height())) { + updateWallpaperOffsetLocked(this, mDisplay.getWidth(), + mDisplay.getHeight(), false); + } + if (localLOGV) { //if ("com.google.android.youtube".equals(mAttrs.packageName) // && mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { @@ -6051,6 +7165,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo Surface createSurfaceLocked() { if (mSurface == null) { + mReportDestroySurface = false; + mSurfacePendingDestroy = false; mDrawPending = true; mCommitDrawPending = false; mReadyToShow = false; @@ -6059,11 +7175,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } int flags = 0; - if (mAttrs.memoryType == MEMORY_TYPE_HARDWARE) { - flags |= Surface.HARDWARE; - } else if (mAttrs.memoryType == MEMORY_TYPE_GPU) { - flags |= Surface.GPU; - } else if (mAttrs.memoryType == MEMORY_TYPE_PUSH_BUFFERS) { + if (mAttrs.memoryType == MEMORY_TYPE_PUSH_BUFFERS) { flags |= Surface.PUSH_BUFFERS; } @@ -6086,10 +7198,21 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo h = mRequestedHeight; } + // Something is wrong and SurfaceFlinger will not like this, + // try to revert to sane values + if (w <= 0) w = 1; + if (h <= 0) h = 1; + try { mSurface = new Surface( mSession.mSurfaceSession, mSession.mPid, 0, w, h, mAttrs.format, flags); + if (SHOW_TRANSACTIONS) Log.i(TAG, " CREATE SURFACE " + + mSurface + " IN SESSION " + + mSession.mSurfaceSession + + ": pid=" + mSession.mPid + " format=" + + mAttrs.format + " flags=0x" + + Integer.toHexString(flags)); } catch (Surface.OutOfResourcesException e) { Log.w(TAG, "OutOfResourcesException creating surface"); reclaimSomeSurfaceMemoryLocked(this, "create"); @@ -6114,10 +7237,13 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo Surface.openTransaction(); try { try { - mSurface.setPosition(mFrame.left, mFrame.top); + mSurface.setPosition(mFrame.left + mXOffset, + mFrame.top + mYOffset); mSurface.setLayer(mAnimLayer); mSurface.hide(); if ((mAttrs.flags&WindowManager.LayoutParams.FLAG_DITHER) != 0) { + if (SHOW_TRANSACTIONS) Log.i(TAG, " SURFACE " + + mSurface + ": DITHER"); mSurface.setFlags(Surface.SURFACE_DITHER, Surface.SURFACE_DITHER); } @@ -6149,34 +7275,50 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo mAppToken.startingDisplayed = false; } - if (localLOGV) Log.v( - TAG, "Window " + this - + " destroying surface " + mSurface + ", session " + mSession); if (mSurface != null) { + mDrawPending = false; + mCommitDrawPending = false; + mReadyToShow = false; + + int i = mChildWindows.size(); + while (i > 0) { + i--; + WindowState c = (WindowState)mChildWindows.get(i); + c.mAttachedHidden = true; + } + + if (mReportDestroySurface) { + mReportDestroySurface = false; + mSurfacePendingDestroy = true; + try { + mClient.dispatchGetNewSurface(); + // We'll really destroy on the next time around. + return; + } catch (RemoteException e) { + } + } + try { + if (DEBUG_VISIBILITY) { + RuntimeException e = new RuntimeException(); + if (!HIDE_STACK_CRAWLS) e.fillInStackTrace(); + Log.w(TAG, "Window " + this + " destroying surface " + + mSurface + ", session " + mSession, e); + } if (SHOW_TRANSACTIONS) { RuntimeException ex = new RuntimeException(); - ex.fillInStackTrace(); + if (!HIDE_STACK_CRAWLS) ex.fillInStackTrace(); Log.i(TAG, " SURFACE " + mSurface + ": DESTROY (" + mAttrs.getTitle() + ")", ex); } - mSurface.clear(); + mSurface.destroy(); } catch (RuntimeException e) { Log.w(TAG, "Exception thrown when destroying Window " + this + " surface " + mSurface + " session " + mSession + ": " + e.toString()); } + mSurface = null; - mDrawPending = false; - mCommitDrawPending = false; - mReadyToShow = false; - - int i = mChildWindows.size(); - while (i > 0) { - i--; - WindowState c = (WindowState)mChildWindows.get(i); - c.mAttachedHidden = true; - } } } @@ -6192,10 +7334,10 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } // This must be called while inside a transaction. - void commitFinishDrawingLocked(long currentTime) { + boolean commitFinishDrawingLocked(long currentTime) { //Log.i(TAG, "commitFinishDrawingLocked: " + mSurface); if (!mCommitDrawPending) { - return; + return false; } mCommitDrawPending = false; mReadyToShow = true; @@ -6204,13 +7346,14 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (atoken == null || atoken.allDrawn || starting) { performShowLocked(); } + return true; } // This must be called while inside a transaction. boolean performShowLocked() { if (DEBUG_VISIBILITY) { RuntimeException e = new RuntimeException(); - e.fillInStackTrace(); + if (!HIDE_STACK_CRAWLS) e.fillInStackTrace(); Log.v(TAG, "performShow on " + this + ": readyToShow=" + mReadyToShow + " readyForDisplay=" + isReadyForDisplay() + " starting=" + (mAttrs.type == TYPE_APPLICATION_STARTING), e); @@ -6223,7 +7366,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo + " attHidden=" + mAttachedHidden + " tok.hiddenRequested=" + (mAppToken != null ? mAppToken.hiddenRequested : false) - + " tok.idden=" + + " tok.hidden=" + (mAppToken != null ? mAppToken.hidden : false) + " animating=" + mAnimating + " tok animating=" @@ -6252,10 +7395,20 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (mAttrs.type != TYPE_APPLICATION_STARTING && mAppToken != null) { mAppToken.firstWindowDrawn = true; - if (mAnimation == null && mAppToken.startingData != null) { - if (DEBUG_STARTING_WINDOW) Log.v(TAG, "Finish starting " - + mToken + + if (mAppToken.startingData != null) { + if (DEBUG_STARTING_WINDOW || DEBUG_ANIM) Log.v(TAG, + "Finish starting " + mToken + ": first real window is shown, no animation"); + // If this initial window is animating, stop it -- we + // will do an animation to reveal it from behind the + // starting window, so there is no need for it to also + // be doing its own stuff. + if (mAnimation != null) { + mAnimation = null; + // Make sure we clean up the animation. + mAnimating = true; + } mFinishedStarting.add(mAppToken); mH.sendEmptyMessage(H.FINISHED_STARTING); } @@ -6302,7 +7455,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } mHasLocalTransformation = false; if ((!mLocalAnimating || mAnimationIsEntrance) && mAppToken != null - && mAppToken.hasTransformation) { + && mAppToken.animation != null) { // When our app token is animating, we kind-of pretend like // we are as well. Note the mLocalAnimating mAnimationIsEntrance // part of this check means that we will only do this if @@ -6344,6 +7497,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo mAnimLayer = mLayer; if (mIsImWindow) { mAnimLayer += mInputMethodAnimLayerAdjustment; + } else if (mIsWallpaper) { + mAnimLayer += mWallpaperAnimLayerAdjustment; } if (DEBUG_LAYERS) Log.v(TAG, "Stepping win " + this + " anim layer: " + mAnimLayer); @@ -6430,6 +7585,30 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo Transformation appTransformation = (mAppToken != null && mAppToken.hasTransformation) ? mAppToken.transformation : null; + + // Wallpapers are animated based on the "real" window they + // are currently targeting. + if (mAttrs.type == TYPE_WALLPAPER && mLowerWallpaperTarget == null + && mWallpaperTarget != null) { + if (mWallpaperTarget.mHasLocalTransformation && + mWallpaperTarget.mAnimation != null && + !mWallpaperTarget.mAnimation.getDetachWallpaper()) { + attachedTransformation = mWallpaperTarget.mTransformation; + if (DEBUG_WALLPAPER && attachedTransformation != null) { + Log.v(TAG, "WP target attached xform: " + attachedTransformation); + } + } + if (mWallpaperTarget.mAppToken != null && + mWallpaperTarget.mAppToken.hasTransformation && + mWallpaperTarget.mAppToken.animation != null && + !mWallpaperTarget.mAppToken.animation.getDetachWallpaper()) { + appTransformation = mWallpaperTarget.mAppToken.transformation; + if (DEBUG_WALLPAPER && appTransformation != null) { + Log.v(TAG, "WP target app xform: " + appTransformation); + } + } + } + if (selfTransformation || attachedTransformation != null || appTransformation != null) { // cache often used attributes locally @@ -6438,15 +7617,16 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo final Matrix tmpMatrix = mTmpMatrix; // Compute the desired transformation. - tmpMatrix.setTranslate(frame.left, frame.top); + tmpMatrix.setTranslate(0, 0); if (selfTransformation) { - tmpMatrix.preConcat(mTransformation.getMatrix()); + tmpMatrix.postConcat(mTransformation.getMatrix()); } + tmpMatrix.postTranslate(frame.left, frame.top); if (attachedTransformation != null) { - tmpMatrix.preConcat(attachedTransformation.getMatrix()); + tmpMatrix.postConcat(attachedTransformation.getMatrix()); } if (appTransformation != null) { - tmpMatrix.preConcat(appTransformation.getMatrix()); + tmpMatrix.postConcat(appTransformation.getMatrix()); } // "convert" it into SurfaceFlinger's format @@ -6460,8 +7640,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo mDtDx = tmpFloats[Matrix.MSKEW_X]; mDsDy = tmpFloats[Matrix.MSKEW_Y]; mDtDy = tmpFloats[Matrix.MSCALE_Y]; - int x = (int)tmpFloats[Matrix.MTRANS_X]; - int y = (int)tmpFloats[Matrix.MTRANS_Y]; + int x = (int)tmpFloats[Matrix.MTRANS_X] + mXOffset; + int y = (int)tmpFloats[Matrix.MTRANS_Y] + mYOffset; int w = frame.width(); int h = frame.height(); mShownFrame.set(x, y, x+w, y+h); @@ -6498,6 +7678,9 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } mShownFrame.set(mFrame); + if (mXOffset != 0 || mYOffset != 0) { + mShownFrame.offset(mXOffset, mYOffset); + } mShownAlpha = mAlpha; mDsDx = 1; mDtDx = 0; @@ -6518,6 +7701,21 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } /** + * Like {@link #isVisibleLw}, but also counts a window that is currently + * "hidden" behind the keyguard as visible. This allows us to apply + * things like window flags that impact the keyguard. + * XXX I am starting to think we need to have ANOTHER visibility flag + * for this "hidden behind keyguard" state rather than overloading + * mPolicyVisibility. Ungh. + */ + public boolean isVisibleOrBehindKeyguardLw() { + final AppWindowToken atoken = mAppToken; + return mSurface != null && !mAttachedHidden + && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested) + && !mExiting && !mDestroying; + } + + /** * Is this window visible, ignoring its app token? It is not visible * if there is no surface, or we are in the process of running an exit animation * that will remove the surface. @@ -6544,7 +7742,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo */ boolean isVisibleOrAdding() { final AppWindowToken atoken = mAppToken; - return (mSurface != null + return ((mSurface != null && !mReportDestroySurface) || (!mRelayoutCalled && mViewVisibility == View.VISIBLE)) && mPolicyVisibility && !mAttachedHidden && (atoken == null || !atoken.hiddenRequested) @@ -6561,10 +7759,10 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (atoken != null) { return mSurface != null && mPolicyVisibility && !mDestroying && ((!mAttachedHidden && !atoken.hiddenRequested) - || mAnimating || atoken.animating); + || mAnimation != null || atoken.animation != null); } else { return mSurface != null && mPolicyVisibility && !mDestroying - && (!mAttachedHidden || mAnimating); + && (!mAttachedHidden || mAnimation != null); } } @@ -6573,11 +7771,17 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo * of a transition that has not yet been started. */ boolean isReadyForDisplay() { + if (mRootToken.waitingToShow && + mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + return false; + } final AppWindowToken atoken = mAppToken; - final boolean animating = atoken != null ? atoken.animating : false; + final boolean animating = atoken != null + ? (atoken.animation != null) : false; return mSurface != null && mPolicyVisibility && !mDestroying - && ((!mAttachedHidden && !mRootToken.hidden) - || mAnimating || animating); + && ((!mAttachedHidden && mViewVisibility == View.VISIBLE + && !mRootToken.hidden) + || mAnimation != null || animating); } /** Is the window or its container currently animating? */ @@ -6609,6 +7813,16 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo || mAnimating); } + /** + * Returns true if the window has a surface that it has drawn a + * complete UI in to. + */ + public boolean isDrawnLw() { + final AppWindowToken atoken = mAppToken; + return mSurface != null && !mDestroying + && !mDrawPending && !mCommitDrawPending; + } + public boolean fillsScreenLw(int screenWidth, int screenHeight, boolean shownFrame, boolean onlyOpaque) { if (mSurface == null) { @@ -6635,11 +7849,15 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } /** - * Return true if the window is opaque and fully drawn. + * Return true if the window is opaque and fully drawn. This indicates + * it may obscure windows behind it. */ boolean isOpaqueDrawn() { - return mAttrs.format == PixelFormat.OPAQUE && mSurface != null - && mAnimation == null && !mDrawPending && !mCommitDrawPending; + return (mAttrs.format == PixelFormat.OPAQUE + || mAttrs.type == TYPE_WALLPAPER) + && mSurface != null && mAnimation == null + && (mAppToken == null || mAppToken.animation == null) + && !mDrawPending && !mCommitDrawPending; } boolean needsBackgroundFiller(int screenWidth, int screenHeight) { @@ -6659,7 +7877,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo boolean isFullscreen(int screenWidth, int screenHeight) { return mFrame.left <= 0 && mFrame.top <= 0 && - mFrame.right >= screenWidth && mFrame.bottom >= screenHeight; + mFrame.right >= screenWidth && mFrame.bottom >= screenHeight; } void removeLocked() { @@ -6705,38 +7923,50 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } public boolean showLw(boolean doAnimation) { - if (!mPolicyVisibility || !mPolicyVisibilityAfterAnim) { - mPolicyVisibility = true; - mPolicyVisibilityAfterAnim = true; - if (doAnimation) { - applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_ENTER, true); - } + return showLw(doAnimation, true); + } + + boolean showLw(boolean doAnimation, boolean requestAnim) { + if (mPolicyVisibility && mPolicyVisibilityAfterAnim) { + return false; + } + mPolicyVisibility = true; + mPolicyVisibilityAfterAnim = true; + if (doAnimation) { + applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_ENTER, true); + } + if (requestAnim) { requestAnimationLocked(0); - return true; } - return false; + return true; } public boolean hideLw(boolean doAnimation) { + return hideLw(doAnimation, true); + } + + boolean hideLw(boolean doAnimation, boolean requestAnim) { boolean current = doAnimation ? mPolicyVisibilityAfterAnim : mPolicyVisibility; - if (current) { - if (doAnimation) { - applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_EXIT, false); - if (mAnimation == null) { - doAnimation = false; - } - } - if (doAnimation) { - mPolicyVisibilityAfterAnim = false; - } else { - mPolicyVisibilityAfterAnim = false; - mPolicyVisibility = false; + if (!current) { + return false; + } + if (doAnimation) { + applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_EXIT, false); + if (mAnimation == null) { + doAnimation = false; } + } + if (doAnimation) { + mPolicyVisibilityAfterAnim = false; + } else { + mPolicyVisibilityAfterAnim = false; + mPolicyVisibility = false; + } + if (requestAnim) { requestAnimationLocked(0); - return true; } - return false; + return true; } void dump(PrintWriter pw, String prefix) { @@ -6749,8 +7979,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo pw.print(prefix); pw.print("mAttachedWindow="); pw.print(mAttachedWindow); pw.print(" mLayoutAttached="); pw.println(mLayoutAttached); } - if (mIsImWindow) { - pw.print(prefix); pw.print("mIsImWindow="); pw.println(mIsImWindow); + if (mIsImWindow || mIsWallpaper || mIsFloatingLayer) { + pw.print(prefix); pw.print("mIsImWindow="); pw.print(mIsImWindow); + pw.print(" mIsWallpaper="); pw.print(mIsWallpaper); + pw.print(" mIsFloatingLayer="); pw.print(mIsFloatingLayer); + pw.print(" mWallpaperVisible="); pw.println(mWallpaperVisible); } pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer); pw.print(" mSubLayer="); pw.print(mSubLayer); @@ -6773,7 +8006,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo pw.print(prefix); pw.print("mViewVisibility=0x"); pw.print(Integer.toHexString(mViewVisibility)); pw.print(" mLastHidden="); pw.print(mLastHidden); - pw.print(" mHaveFrame="); pw.println(mHaveFrame); + pw.print(" mHaveFrame="); pw.print(mHaveFrame); + pw.print(" mObscured="); pw.println(mObscured); if (!mPolicyVisibility || !mPolicyVisibilityAfterAnim || mAttachedHidden) { pw.print(prefix); pw.print("mPolicyVisibility="); pw.print(mPolicyVisibility); @@ -6782,9 +8016,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo pw.print(" mAttachedHidden="); pw.println(mAttachedHidden); } pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth); - pw.print(" h="); pw.print(mRequestedHeight); - pw.print(" x="); pw.print(mReqXPos); - pw.print(" y="); pw.println(mReqYPos); + pw.print(" h="); pw.println(mRequestedHeight); + if (mXOffset != 0 || mYOffset != 0) { + pw.print(prefix); pw.print("Offsets x="); pw.print(mXOffset); + pw.print(" y="); pw.println(mYOffset); + } pw.print(prefix); pw.print("mGivenContentInsets="); mGivenContentInsets.printShortString(pw); pw.print(" mGivenVisibleInsets="); @@ -6843,15 +8079,24 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo pw.print(" mDestroying="); pw.print(mDestroying); pw.print(" mRemoved="); pw.println(mRemoved); } - if (mOrientationChanging || mAppFreezing) { + if (mOrientationChanging || mAppFreezing || mTurnOnScreen) { pw.print(prefix); pw.print("mOrientationChanging="); pw.print(mOrientationChanging); - pw.print(" mAppFreezing="); pw.println(mAppFreezing); + pw.print(" mAppFreezing="); pw.print(mAppFreezing); + pw.print(" mTurnOnScreen="); pw.println(mTurnOnScreen); } if (mHScale != 1 || mVScale != 1) { pw.print(prefix); pw.print("mHScale="); pw.print(mHScale); pw.print(" mVScale="); pw.println(mVScale); } + if (mWallpaperX != -1 || mWallpaperY != -1) { + pw.print(prefix); pw.print("mWallpaperX="); pw.print(mWallpaperX); + pw.print(" mWallpaperY="); pw.println(mWallpaperY); + } + if (mWallpaperXStep != -1 || mWallpaperYStep != -1) { + pw.print(prefix); pw.print("mWallpaperXStep="); pw.print(mWallpaperXStep); + pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep); + } } @Override @@ -6895,6 +8140,22 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // Temporary for finding which tokens no longer have visible windows. boolean hasVisible; + // Set to true when this token is in a pending transaction where it + // will be shown. + boolean waitingToShow; + + // Set to true when this token is in a pending transaction where it + // will be hidden. + boolean waitingToHide; + + // Set to true when this token is in a pending transaction where its + // windows will be put to the bottom of the list. + boolean sendingToBottom; + + // Set to true when this token is in a pending transaction where its + // windows will be put to the top of the list. + boolean sendingToTop; + WindowToken(IBinder _token, int type, boolean _explicit) { token = _token; windowType = type; @@ -6907,6 +8168,12 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo pw.print(prefix); pw.print("windowType="); pw.print(windowType); pw.print(" hidden="); pw.print(hidden); pw.print(" hasVisible="); pw.println(hasVisible); + if (waitingToShow || waitingToHide || sendingToBottom || sendingToTop) { + pw.print(prefix); pw.print("waitingToShow="); pw.print(waitingToShow); + pw.print(" waitingToHide="); pw.print(waitingToHide); + pw.print(" sendingToBottom="); pw.print(sendingToBottom); + pw.print(" sendingToTop="); pw.println(sendingToTop); + } } @Override @@ -7036,6 +8303,9 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (w == mInputMethodTarget) { setInputMethodAnimLayerAdjustment(adj); } + if (w == mWallpaperTarget && mLowerWallpaperTarget == null) { + setWallpaperAnimLayerAdjustmentLocked(adj); + } } } @@ -7073,7 +8343,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (animation == sDummyAnimation) { // This guy is going to animate, but not yet. For now count - // it is not animating for purposes of scheduling transactions; + // it as not animating for purposes of scheduling transactions; // when it is really time to animate, this will be set to // a real animation and the next call will execute normally. return false; @@ -7161,10 +8431,10 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo continue; } if (DEBUG_VISIBILITY) { - Log.v(TAG, "Win " + win + ": isDisplayed=" - + win.isDisplayedLw() + Log.v(TAG, "Win " + win + ": isDrawn=" + + win.isDrawnLw() + ", isAnimating=" + win.isAnimating()); - if (!win.isDisplayedLw()) { + if (!win.isDrawnLw()) { Log.v(TAG, "Not displayed: s=" + win.mSurface + " pv=" + win.mPolicyVisibility + " dp=" + win.mDrawPending @@ -7177,7 +8447,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } numInteresting++; - if (win.isDisplayedLw()) { + if (win.isDrawnLw()) { if (!win.isAnimating()) { numVisible++; } @@ -7204,6 +8474,19 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } + WindowState findMainWindow() { + int j = windows.size(); + while (j > 0) { + j--; + WindowState win = windows.get(j); + if (win.mAttrs.type == WindowManager.LayoutParams.TYPE_BASE_APPLICATION + || win.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) { + return win; + } + } + return null; + } + void dump(PrintWriter pw, String prefix) { super.dump(pw, prefix); if (appToken != null) { @@ -7213,6 +8496,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo pw.print(prefix); pw.print("allAppWindows="); pw.println(allAppWindows); } pw.print(prefix); pw.print("groupId="); pw.print(groupId); + pw.print(" appFullscreen="); pw.print(appFullscreen); pw.print(" requestedOrientation="); pw.println(requestedOrientation); pw.print(prefix); pw.print("hiddenRequested="); pw.print(hiddenRequested); pw.print(" clientHidden="); pw.print(clientHidden); @@ -7269,71 +8553,6 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } - public static WindowManager.LayoutParams findAnimations( - ArrayList<AppWindowToken> order, - ArrayList<AppWindowToken> openingTokenList1, - ArrayList<AppWindowToken> closingTokenList2) { - // We need to figure out which animation to use... - - // First, check if there is a compatible window in opening/closing - // apps, and use it if exists. - WindowManager.LayoutParams animParams = null; - int animSrc = 0; - animParams = findCompatibleWindowParams(openingTokenList1); - if (animParams == null) { - animParams = findCompatibleWindowParams(closingTokenList2); - } - if (animParams != null) { - return animParams; - } - - //Log.i(TAG, "Looking for animations..."); - for (int i=order.size()-1; i>=0; i--) { - AppWindowToken wtoken = order.get(i); - //Log.i(TAG, "Token " + wtoken + " with " + wtoken.windows.size() + " windows"); - if (openingTokenList1.contains(wtoken) || closingTokenList2.contains(wtoken)) { - int j = wtoken.windows.size(); - while (j > 0) { - j--; - WindowState win = wtoken.windows.get(j); - //Log.i(TAG, "Window " + win + ": type=" + win.mAttrs.type); - if (win.mAttrs.type == WindowManager.LayoutParams.TYPE_BASE_APPLICATION - || win.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) { - //Log.i(TAG, "Found base or application window, done!"); - if (wtoken.appFullscreen) { - return win.mAttrs; - } - if (animSrc < 2) { - animParams = win.mAttrs; - animSrc = 2; - } - } else if (animSrc < 1 && win.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION) { - //Log.i(TAG, "Found normal window, we may use this..."); - animParams = win.mAttrs; - animSrc = 1; - } - } - } - } - - return animParams; - } - - private static LayoutParams findCompatibleWindowParams(ArrayList<AppWindowToken> tokenList) { - for (int appCount = tokenList.size() - 1; appCount >= 0; appCount--) { - AppWindowToken wtoken = tokenList.get(appCount); - // Just checking one window is sufficient as all windows have the compatible flag - // if the application is in compatibility mode. - if (wtoken.windows.size() > 0) { - WindowManager.LayoutParams params = wtoken.windows.get(0).mAttrs; - if ((params.flags & FLAG_COMPATIBLE_WINDOW) != 0) { - return params; - } - } - } - return null; - } - // ------------------------------------------------------------- // DummyAnimation // ------------------------------------------------------------- @@ -7650,7 +8869,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo case APP_TRANSITION_TIMEOUT: { synchronized (mWindowMap) { - if (mNextAppTransition != WindowManagerPolicy.TRANSIT_NONE) { + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { if (DEBUG_APP_TRANSITIONS) Log.v(TAG, "*** APP TRANSITION TIMEOUT"); mAppTransitionReady = true; @@ -7778,6 +8997,57 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo return win; } + final void rebuildAppWindowListLocked() { + int NW = mWindows.size(); + int i; + int lastWallpaper = -1; + int numRemoved = 0; + + // First remove all existing app windows. + i=0; + while (i < NW) { + WindowState w = (WindowState)mWindows.get(i); + if (w.mAppToken != null) { + WindowState win = (WindowState)mWindows.remove(i); + if (DEBUG_WINDOW_MOVEMENT) Log.v(TAG, + "Rebuild removing window: " + win); + NW--; + numRemoved++; + continue; + } else if (w.mAttrs.type == WindowManager.LayoutParams.TYPE_WALLPAPER + && lastWallpaper == i-1) { + lastWallpaper = i; + } + i++; + } + + // The wallpaper window(s) typically live at the bottom of the stack, + // so skip them before adding app tokens. + lastWallpaper++; + i = lastWallpaper; + + // First add all of the exiting app tokens... these are no longer + // in the main app list, but still have windows shown. We put them + // in the back because now that the animation is over we no longer + // will care about them. + int NT = mExitingAppTokens.size(); + for (int j=0; j<NT; j++) { + i = reAddAppWindowsLocked(i, mExitingAppTokens.get(j)); + } + + // And add in the still active app tokens in Z order. + NT = mAppTokens.size(); + for (int j=0; j<NT; j++) { + i = reAddAppWindowsLocked(i, mAppTokens.get(j)); + } + + i -= lastWallpaper; + if (i != numRemoved) { + Log.w(TAG, "Rebuild removed " + numRemoved + + " windows but added " + i); + } + } + private final void assignLayersLocked() { int N = mWindows.size(); int curBaseLayer = 0; @@ -7786,7 +9056,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo for (i=0; i<N; i++) { WindowState w = (WindowState)mWindows.get(i); - if (w.mBaseLayer == curBaseLayer || w.mIsImWindow) { + if (w.mBaseLayer == curBaseLayer || w.mIsImWindow + || (i > 0 && w.mIsWallpaper)) { curLayer += WINDOW_LAYER_MULTIPLIER; w.mLayer = curLayer; } else { @@ -7802,6 +9073,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } if (w.mIsImWindow) { w.mAnimLayer += mInputMethodAnimLayerAdjustment; + } else if (w.mIsWallpaper) { + w.mAnimLayer += mWallpaperAnimLayerAdjustment; } if (DEBUG_LAYERS) Log.v(TAG, "Assign layer " + w + ": " + w.mAnimLayer); @@ -7897,7 +9170,6 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo || !win.mRelayoutCalled || win.mRootToken.hidden || (atoken != null && atoken.hiddenRequested) - || !win.mPolicyVisibility || win.mAttachedHidden || win.mExiting || win.mDestroying; @@ -7935,13 +9207,34 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } - if (!mPolicy.finishLayoutLw()) { + int changes = mPolicy.finishLayoutLw(); + if ((changes&WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) { + if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { + assignLayersLocked(); + } + } + if (changes == 0) { mLayoutNeeded = false; } else if (repeats > 2) { Log.w(TAG, "Layout repeat aborted after too many iterations"); mLayoutNeeded = false; + if ((changes&WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) { + Configuration newConfig = updateOrientationFromAppTokensLocked( + null, null); + if (newConfig != null) { + mLayoutNeeded = true; + mH.sendEmptyMessage(H.COMPUTE_AND_SEND_NEW_CONFIGURATION); + } + } } else { repeats++; + if ((changes&WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) { + Configuration newConfig = updateOrientationFromAppTokensLocked( + null, null); + if (newConfig != null) { + mH.sendEmptyMessage(H.COMPUTE_AND_SEND_NEW_CONFIGURATION); + } + } } } } @@ -7952,7 +9245,6 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo final int dw = mDisplay.getWidth(); final int dh = mDisplay.getHeight(); - final int N = mWindows.size(); int i; // FIRST LOOP: Perform a layout, if needed. @@ -7984,6 +9276,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo Surface.openTransaction(); try { boolean restart; + boolean forceHiding = false; do { final int transactionSequence = ++mTransactionSequence; @@ -8008,9 +9301,14 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo restart = false; boolean tokenMayBeDrawn = false; + boolean wallpaperMayChange = false; + boolean focusMayChange = false; + boolean wallpaperForceHidingChanged = false; mPolicy.beginAnimationLw(dw, dh); + final int N = mWindows.size(); + for (i=N-1; i>=0; i--) { WindowState w = (WindowState)mWindows.get(i); @@ -8018,12 +9316,64 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (w.mSurface != null) { // Execute animation. - w.commitFinishDrawingLocked(currentTime); + if (w.commitFinishDrawingLocked(currentTime)) { + if ((w.mAttrs.flags + & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { + if (DEBUG_WALLPAPER) Log.v(TAG, + "First draw done in potential wallpaper target " + w); + wallpaperMayChange = true; + } + } + + boolean wasAnimating = w.mAnimating; if (w.stepAnimationLocked(currentTime, dw, dh)) { animating = true; //w.dump(" "); } - + if (wasAnimating && !w.mAnimating && mWallpaperTarget == w) { + wallpaperMayChange = true; + } + + if (mPolicy.doesForceHide(w, attrs)) { + if (!wasAnimating && animating) { + wallpaperForceHidingChanged = true; + focusMayChange = true; + } else if (w.isReadyForDisplay() && w.mAnimation == null) { + forceHiding = true; + } + } else if (mPolicy.canBeForceHidden(w, attrs)) { + boolean changed; + if (forceHiding) { + changed = w.hideLw(false, false); + } else { + changed = w.showLw(false, false); + if (changed && wallpaperForceHidingChanged + && w.isReadyForDisplay()) { + // Assume we will need to animate. If + // we don't (because the wallpaper will + // stay with the lock screen), then we will + // clean up later. + Animation a = mPolicy.createForceHideEnterAnimation(); + if (a != null) { + w.setAnimation(a); + } + } + } + if (changed && (attrs.flags + & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { + wallpaperMayChange = true; + } + if (changed && !forceHiding + && (mCurrentFocus == null) + && (mFocusedApp != null)) { + // It's possible that the last focus recalculation left no + // current focused window even though the app has come to the + // foreground already. In this case, we make sure to recalculate + // focus when we show a window. + focusMayChange = true; + } + } + mPolicy.animatingWindowLw(w, attrs); } @@ -8038,10 +9388,10 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo == WindowManager.LayoutParams.TYPE_BASE_APPLICATION) && !w.mExiting && !w.mDestroying) { if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) { - Log.v(TAG, "Eval win " + w + ": isDisplayed=" - + w.isDisplayedLw() + Log.v(TAG, "Eval win " + w + ": isDrawn=" + + w.isDrawnLw() + ", isAnimating=" + w.isAnimating()); - if (!w.isDisplayedLw()) { + if (!w.isDrawnLw()) { Log.v(TAG, "Not displayed: s=" + w.mSurface + " pv=" + w.mPolicyVisibility + " dp=" + w.mDrawPending @@ -8054,7 +9404,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (w != atoken.startingWindow) { if (!atoken.freezingScreen || !w.mAppFreezing) { atoken.numInterestingWindows++; - if (w.isDisplayedLw()) { + if (w.isDrawnLw()) { atoken.numDrawnWindows++; if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Log.v(TAG, "tokenMayBeDrawn: " + atoken @@ -8063,7 +9413,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo tokenMayBeDrawn = true; } } - } else if (w.isDisplayedLw()) { + } else if (w.isDrawnLw()) { atoken.startingDisplayed = true; } } @@ -8145,20 +9495,136 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (DEBUG_APP_TRANSITIONS) Log.v(TAG, "**** GOOD TO GO"); int transit = mNextAppTransition; if (mSkipAppTransitionAnimation) { - transit = WindowManagerPolicy.TRANSIT_NONE; + transit = WindowManagerPolicy.TRANSIT_UNSET; } - mNextAppTransition = WindowManagerPolicy.TRANSIT_NONE; + mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; mAppTransitionReady = false; + mAppTransitionRunning = true; mAppTransitionTimeout = false; mStartingIconInTransition = false; mSkipAppTransitionAnimation = false; mH.removeMessages(H.APP_TRANSITION_TIMEOUT); - // We need to figure out which animation to use... - WindowManager.LayoutParams lp = findAnimations(mAppTokens, - mOpeningApps, mClosingApps); - + // If there are applications waiting to come to the + // top of the stack, now is the time to move their windows. + // (Note that we don't do apps going to the bottom + // here -- we want to keep their windows in the old + // Z-order until the animation completes.) + if (mToTopApps.size() > 0) { + NN = mAppTokens.size(); + for (i=0; i<NN; i++) { + AppWindowToken wtoken = mAppTokens.get(i); + if (wtoken.sendingToTop) { + wtoken.sendingToTop = false; + moveAppWindowsLocked(wtoken, NN, false); + } + } + mToTopApps.clear(); + } + + WindowState oldWallpaper = mWallpaperTarget; + + adjustWallpaperWindowsLocked(); + wallpaperMayChange = false; + + // The top-most window will supply the layout params, + // and we will determine it below. + LayoutParams animLp = null; + AppWindowToken animToken = null; + int bestAnimLayer = -1; + + if (DEBUG_APP_TRANSITIONS) Log.v(TAG, + "New wallpaper target=" + mWallpaperTarget + + ", lower target=" + mLowerWallpaperTarget + + ", upper target=" + mUpperWallpaperTarget); + int foundWallpapers = 0; + // Do a first pass through the tokens for two + // things: + // (1) Determine if both the closing and opening + // app token sets are wallpaper targets, in which + // case special animations are needed + // (since the wallpaper needs to stay static + // behind them). + // (2) Find the layout params of the top-most + // application window in the tokens, which is + // what will control the animation theme. + final int NC = mClosingApps.size(); + NN = NC + mOpeningApps.size(); + for (i=0; i<NN; i++) { + AppWindowToken wtoken; + int mode; + if (i < NC) { + wtoken = mClosingApps.get(i); + mode = 1; + } else { + wtoken = mOpeningApps.get(i-NC); + mode = 2; + } + if (mLowerWallpaperTarget != null) { + if (mLowerWallpaperTarget.mAppToken == wtoken + || mUpperWallpaperTarget.mAppToken == wtoken) { + foundWallpapers |= mode; + } + } + if (wtoken.appFullscreen) { + WindowState ws = wtoken.findMainWindow(); + if (ws != null) { + // If this is a compatibility mode + // 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) { + animLp = ws.mAttrs; + animToken = ws.mAppToken; + bestAnimLayer = ws.mLayer; + } + } + } + } + + if (foundWallpapers == 3) { + if (DEBUG_APP_TRANSITIONS) Log.v(TAG, + "Wallpaper animation!"); + switch (transit) { + case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: + case WindowManagerPolicy.TRANSIT_TASK_OPEN: + case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: + transit = WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN; + break; + case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: + case WindowManagerPolicy.TRANSIT_TASK_CLOSE: + case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: + transit = WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE; + break; + } + if (DEBUG_APP_TRANSITIONS) Log.v(TAG, + "New transit: " + transit); + } else if (oldWallpaper != null) { + // We are transitioning from an activity with + // a wallpaper to one without. + transit = WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE; + if (DEBUG_APP_TRANSITIONS) Log.v(TAG, + "New transit away from wallpaper: " + transit); + } else if (mWallpaperTarget != null) { + // We are transitioning from an activity without + // a wallpaper to now showing the wallpaper + transit = WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN; + if (DEBUG_APP_TRANSITIONS) Log.v(TAG, + "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; + } + NN = mOpeningApps.size(); for (i=0; i<NN; i++) { AppWindowToken wtoken = mOpeningApps.get(i); @@ -8166,8 +9632,10 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo "Now opening app" + wtoken); wtoken.reportedVisible = false; wtoken.inPendingTransaction = false; - setTokenVisibilityLocked(wtoken, lp, true, transit, false); + wtoken.animation = null; + setTokenVisibilityLocked(wtoken, animLp, true, transit, false); wtoken.updateReportedVisibilityLocked(); + wtoken.waitingToShow = false; wtoken.showAllWindowsLocked(); } NN = mClosingApps.size(); @@ -8176,14 +9644,18 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (DEBUG_APP_TRANSITIONS) Log.v(TAG, "Now closing app" + wtoken); wtoken.inPendingTransaction = false; - setTokenVisibilityLocked(wtoken, lp, false, transit, false); + wtoken.animation = null; + setTokenVisibilityLocked(wtoken, animLp, false, transit, false); wtoken.updateReportedVisibilityLocked(); + wtoken.waitingToHide = false; // Force the allDrawn flag, because we want to start // this guy's animations regardless of whether it's // gotten drawn. wtoken.allDrawn = true; } + mNextAppTransitionPackage = null; + mOpeningApps.clear(); mClosingApps.clear(); @@ -8195,10 +9667,102 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } performLayoutLockedInner(); updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES); + focusMayChange = false; restart = true; } } + + if (!animating && mAppTransitionRunning) { + // We have finished the animation of an app transition. To do + // this, we have delayed a lot of operations like showing and + // hiding apps, moving apps in Z-order, etc. The app token list + // reflects the correct Z-order, but the window list may now + // be out of sync with it. So here we will just rebuild the + // entire app window list. Fun! + mAppTransitionRunning = false; + // Clear information about apps that were moving. + mToBottomApps.clear(); + + rebuildAppWindowListLocked(); + restart = true; + moveInputMethodWindowsIfNeededLocked(false); + wallpaperMayChange = true; + mLayoutNeeded = true; + // Since the window list has been rebuilt, focus might + // have to be recomputed since the actual order of windows + // might have changed again. + focusMayChange = true; + } + + int adjResult = 0; + + if (wallpaperForceHidingChanged) { + // At this point, there was a window with a wallpaper that + // was force hiding other windows behind it, but now it + // is going away. This may be simple -- just animate + // away the wallpaper and its window -- or it may be + // hard -- the wallpaper now needs to be shown behind + // something that was hidden. + WindowState oldWallpaper = mWallpaperTarget; + adjResult = adjustWallpaperWindowsLocked(); + wallpaperMayChange = false; + if (false) Log.v(TAG, "****** OLD: " + oldWallpaper + + " NEW: " + mWallpaperTarget); + if (mLowerWallpaperTarget == null) { + // Whoops, we don't need a special wallpaper animation. + // Clear them out. + forceHiding = false; + for (i=N-1; i>=0; i--) { + WindowState w = (WindowState)mWindows.get(i); + if (w.mSurface != null) { + final WindowManager.LayoutParams attrs = w.mAttrs; + if (mPolicy.doesForceHide(w, attrs) && w.isVisibleLw()) { + if (DEBUG_FOCUS) Log.i(TAG, "win=" + w + " force hides other windows"); + forceHiding = true; + } else if (mPolicy.canBeForceHidden(w, attrs)) { + if (!w.mAnimating) { + // We set the animation above so it + // is not yet running. + w.clearAnimation(); + } + } + } + } + } + } + + if (wallpaperMayChange) { + if (DEBUG_WALLPAPER) Log.v(TAG, + "Wallpaper may change! Adjusting"); + adjResult = adjustWallpaperWindowsLocked(); + } + + if ((adjResult&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { + if (DEBUG_WALLPAPER) Log.v(TAG, + "Wallpaper layer changed: assigning layers + relayout"); + restart = true; + mLayoutNeeded = true; + assignLayersLocked(); + } else if ((adjResult&ADJUST_WALLPAPER_VISIBILITY_CHANGED) != 0) { + if (DEBUG_WALLPAPER) Log.v(TAG, + "Wallpaper visibility changed: relayout"); + restart = true; + mLayoutNeeded = true; + } + + if (focusMayChange) { + if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES)) { + restart = true; + adjResult = 0; + } + } + + if (mLayoutNeeded) { + restart = true; + performLayoutLockedInner(); + } + } while (restart); // THIRD LOOP: Update the surfaces of all windows. @@ -8212,6 +9776,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo boolean syswin = false; boolean backgroundFillerShown = false; + final int N = mWindows.size(); + for (i=N-1; i>=0; i--) { WindowState w = (WindowState)mWindows.get(i); @@ -8239,6 +9805,10 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo w.mLastRequestedHeight = height; w.mLastShownFrame.set(w.mShownFrame); try { + if (SHOW_TRANSACTIONS) Log.i( + TAG, " SURFACE " + w.mSurface + + ": POS " + w.mShownFrame.left + + ", " + w.mShownFrame.top); w.mSurface.setPosition(w.mShownFrame.left, w.mShownFrame.top); } catch (RuntimeException e) { Log.w(TAG, "Error positioning surface in " + w, e); @@ -8251,14 +9821,6 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo width = w.mShownFrame.width(); height = w.mShownFrame.height(); w.mLastShownFrame.set(w.mShownFrame); - if (resize) { - if (SHOW_TRANSACTIONS) Log.i( - TAG, " SURFACE " + w.mSurface + ": (" - + w.mShownFrame.left + "," - + w.mShownFrame.top + ") (" - + w.mShownFrame.width() + "x" - + w.mShownFrame.height() + ")"); - } } if (resize) { @@ -8266,6 +9828,12 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (height < 1) height = 1; if (w.mSurface != null) { try { + if (SHOW_TRANSACTIONS) Log.i( + TAG, " SURFACE " + w.mSurface + ": POS " + + w.mShownFrame.left + "," + + w.mShownFrame.top + " SIZE " + + w.mShownFrame.width() + "x" + + w.mShownFrame.height()); w.mSurface.setSize(width, height); w.mSurface.setPosition(w.mShownFrame.left, w.mShownFrame.top); @@ -8294,6 +9862,22 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo w.mLastFrame.set(w.mFrame); w.mLastContentInsets.set(w.mContentInsets); w.mLastVisibleInsets.set(w.mVisibleInsets); + // If the screen is currently frozen, then keep + // it frozen until this window draws at its new + // orientation. + if (mDisplayFrozen) { + if (DEBUG_ORIENTATION) Log.v(TAG, + "Resizing while display frozen: " + w); + w.mOrientationChanging = true; + if (mWindowsFreezingScreen) { + mWindowsFreezingScreen = true; + // XXX should probably keep timeout from + // when we first froze the display. + mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT); + mH.sendMessageDelayed(mH.obtainMessage( + H.WINDOW_FREEZE_TIMEOUT), 2000); + } + } // If the orientation is changing, then we need to // hold off on unfreezing the display until this // window has been redrawn; to do that, we need @@ -8323,12 +9907,12 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } - if (w.mAttachedHidden) { + if (w.mAttachedHidden || !w.isReadyForDisplay()) { if (!w.mLastHidden) { //dump(); w.mLastHidden = true; if (SHOW_TRANSACTIONS) Log.i( - TAG, " SURFACE " + w.mSurface + ": HIDE (performLayout-attached)"); + TAG, " SURFACE " + w.mSurface + ": HIDE (performLayout)"); if (w.mSurface != null) { try { w.mSurface.hide(); @@ -8349,32 +9933,6 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (DEBUG_ORIENTATION) Log.v(TAG, "Orientation change skips hidden " + w); } - } else if (!w.isReadyForDisplay()) { - if (!w.mLastHidden) { - //dump(); - w.mLastHidden = true; - if (SHOW_TRANSACTIONS) Log.i( - TAG, " SURFACE " + w.mSurface + ": HIDE (performLayout-ready)"); - if (w.mSurface != null) { - try { - w.mSurface.hide(); - } catch (RuntimeException e) { - Log.w(TAG, "Exception exception hiding surface in " + w); - } - } - mKeyWaiter.releasePendingPointerLocked(w.mSession); - } - // If we are waiting for this window to handle an - // orientation change, well, it is hidden, so - // doesn't really matter. Note that this does - // introduce a potential glitch if the window - // becomes unhidden before it has drawn for the - // new orientation. - if (w.mOrientationChanging) { - w.mOrientationChanging = false; - if (DEBUG_ORIENTATION) Log.v(TAG, - "Orientation change skips hidden " + w); - } } else if (w.mLastLayer != w.mAnimLayer || w.mLastAlpha != w.mShownAlpha || w.mLastDsDx != w.mDsDx @@ -8395,7 +9953,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo w.mLastVScale = w.mVScale; if (SHOW_TRANSACTIONS) Log.i( TAG, " SURFACE " + w.mSurface + ": alpha=" - + w.mShownAlpha + " layer=" + w.mAnimLayer); + + w.mShownAlpha + " layer=" + w.mAnimLayer + + " matrix=[" + (w.mDsDx*w.mHScale) + + "," + (w.mDtDx*w.mVScale) + + "][" + (w.mDsDy*w.mHScale) + + "," + (w.mDtDy*w.mVScale) + "]"); if (w.mSurface != null) { try { w.mSurface.setAlpha(w.mShownAlpha); @@ -8464,8 +10026,10 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo focusDisplayed = true; } + final boolean obscuredChanged = w.mObscured != obscured; + // Update effect. - if (!obscured) { + if (!(w.mObscured=obscured)) { if (w.mSurface != null) { if ((attrFlags&FLAG_KEEP_SCREEN_ON) != 0) { holdScreen = w.mSession; @@ -8481,7 +10045,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } - boolean opaqueDrawn = w.isOpaqueDrawn(); + boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn(); if (opaqueDrawn && w.isFullscreen(dw, dh)) { // This window completely covers everything behind it, // so we want to leave all of them as unblurred (for @@ -8564,6 +10128,13 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } } + + if (obscuredChanged && mWallpaperTarget == w) { + // This is the wallpaper target and its obscured state + // changed... make sure the current wallaper's visibility + // has been updated accordingly. + updateWallpaperVisibilityLocked(); + } } if (backgroundFillerShown == false && mBackgroundFillerShown) { @@ -8617,6 +10188,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo i--; WindowState win = mResizingWindows.get(i); try { + if (DEBUG_ORIENTATION) Log.v(TAG, "Reporting new frame to " + + win + ": " + win.mFrame); win.mClient.resized(win.mFrame.width(), win.mFrame.height(), win.mLastContentInsets, win.mLastVisibleInsets, win.mDrawPending); @@ -8630,6 +10203,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } // Destroy the surface of any windows that are no longer visible. + boolean wallpaperDestroyed = false; i = mDestroySurface.size(); if (i > 0) { do { @@ -8639,6 +10213,9 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (mInputMethodWindow == win) { mInputMethodWindow = null; } + if (win == mWallpaperTarget) { + wallpaperDestroyed = true; + } win.destroySurfaceLocked(); } while (i > 0); mDestroySurface.clear(); @@ -8649,6 +10226,9 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo WindowToken token = mExitingTokens.get(i); if (!token.hasVisible) { mExitingTokens.remove(i); + if (token.windowType == TYPE_WALLPAPER) { + mWallpaperTokens.remove(token); + } } } @@ -8656,15 +10236,45 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo for (i=mExitingAppTokens.size()-1; i>=0; i--) { AppWindowToken token = mExitingAppTokens.get(i); if (!token.hasVisible && !mClosingApps.contains(token)) { + // Make sure there is no animation running on this token, + // so any windows associated with it will be removed as + // soon as their animations are complete + token.animation = null; + token.animating = false; mAppTokens.remove(token); mExitingAppTokens.remove(i); + if (mLastEnterAnimToken == token) { + mLastEnterAnimToken = null; + mLastEnterAnimParams = null; + } } } + boolean needRelayout = false; + + if (!animating && mAppTransitionRunning) { + // We have finished the animation of an app transition. To do + // this, we have delayed a lot of operations like showing and + // hiding apps, moving apps in Z-order, etc. The app token list + // reflects the correct Z-order, but the window list may now + // be out of sync with it. So here we will just rebuild the + // entire app window list. Fun! + mAppTransitionRunning = false; + needRelayout = true; + rebuildAppWindowListLocked(); + // Clear information about apps that were moving. + mToBottomApps.clear(); + } + if (focusDisplayed) { mH.sendEmptyMessage(H.REPORT_LOSING_FOCUS); } - if (animating) { + if (wallpaperDestroyed) { + needRelayout = adjustWallpaperWindowsLocked() != 0; + } + if (needRelayout) { + requestAnimationLocked(0); + } else if (animating) { requestAnimationLocked(currentTime+(1000/60)-SystemClock.uptimeMillis()); } mQueue.setHoldScreenLocked(holdScreen != null); @@ -8679,6 +10289,12 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, holdScreen); mH.sendMessage(m); } + + if (mTurnOnScreen) { + mPowerManager.userActivity(SystemClock.uptimeMillis(), false, + LocalPowerManager.BUTTON_EVENT, true); + mTurnOnScreen = false; + } } void requestAnimationLocked(long delay) { @@ -8700,6 +10316,10 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo try { if (win.mSurface != null) { win.mSurface.show(); + if (win.mTurnOnScreen) { + win.mTurnOnScreen = false; + mTurnOnScreen = true; + } } return true; } catch (RuntimeException e) { @@ -8738,7 +10358,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo + " token=" + win.mToken + " pid=" + ws.mSession.mPid + " uid=" + ws.mSession.mUid); - ws.mSurface.clear(); + ws.mSurface.destroy(); ws.mSurface = null; mForceRemoves.add(ws); i--; @@ -8748,7 +10368,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo Log.w(TAG, "LEAKED SURFACE (app token hidden): " + ws + " surface=" + ws.mSurface + " token=" + win.mAppToken); - ws.mSurface.clear(); + ws.mSurface.destroy(); ws.mSurface = null; leakedSurface = true; } @@ -8784,7 +10404,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // surface and ask the app to request another one. Log.w(TAG, "Looks like we have reclaimed some memory, clearing surface for retry."); if (surface != null) { - surface.clear(); + surface.destroy(); win.mSurface = null; } @@ -8934,8 +10554,9 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } mDisplayFrozen = true; - if (mNextAppTransition != WindowManagerPolicy.TRANSIT_NONE) { - mNextAppTransition = WindowManagerPolicy.TRANSIT_NONE; + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; + mNextAppTransitionPackage = null; mAppTransitionReady = true; } @@ -9043,6 +10664,16 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo w.dump(pw, " "); } } + if (mResizingWindows.size() > 0) { + pw.println(" "); + pw.println(" Windows waiting to resize:"); + for (int i=mResizingWindows.size()-1; i>=0; i--) { + WindowState w = mResizingWindows.get(i); + pw.print(" Resizing #"); pw.print(i); pw.print(' '); + pw.print(w); pw.println(":"); + w.dump(pw, " "); + } + } if (mSessions.size() > 0) { pw.println(" "); pw.println(" All active sessions:"); @@ -9071,6 +10702,16 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo pw.println(mTokenList.get(i)); } } + if (mWallpaperTokens.size() > 0) { + pw.println(" "); + pw.println(" Wallpaper tokens:"); + for (int i=mWallpaperTokens.size()-1; i>=0; i--) { + WindowToken token = mWallpaperTokens.get(i); + pw.print(" Wallpaper #"); pw.print(i); + pw.print(' '); pw.print(token); pw.println(':'); + token.dump(pw, " "); + } + } if (mAppTokens.size() > 0) { pw.println(" "); pw.println(" Application tokens in Z order:"); @@ -9115,6 +10756,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo pw.print(" mFocusedApp="); pw.println(mFocusedApp); pw.print(" mInputMethodTarget="); pw.println(mInputMethodTarget); pw.print(" mInputMethodWindow="); pw.println(mInputMethodWindow); + pw.print(" mWallpaperTarget="); pw.println(mWallpaperTarget); + if (mLowerWallpaperTarget != null && mUpperWallpaperTarget != null) { + pw.print(" mLowerWallpaperTarget="); pw.println(mLowerWallpaperTarget); + pw.print(" mUpperWallpaperTarget="); pw.println(mUpperWallpaperTarget); + } pw.print(" mInTouchMode="); pw.println(mInTouchMode); pw.print(" mSystemBooted="); pw.print(mSystemBooted); pw.print(" mDisplayEnabled="); pw.println(mDisplayEnabled); @@ -9126,7 +10772,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo pw.print( " no DimAnimator "); } pw.print(" mInputMethodAnimLayerAdjustment="); - pw.println(mInputMethodAnimLayerAdjustment); + pw.print(mInputMethodAnimLayerAdjustment); + pw.print(" mWallpaperAnimLayerAdjustment="); + pw.println(mWallpaperAnimLayerAdjustment); + pw.print(" mLastWallpaperX="); pw.print(mLastWallpaperX); + pw.print(" mLastWallpaperY="); pw.println(mLastWallpaperY); pw.print(" mDisplayFrozen="); pw.print(mDisplayFrozen); pw.print(" mWindowsFreezingScreen="); pw.print(mWindowsFreezingScreen); pw.print(" mAppsFreezingScreen="); pw.println(mAppsFreezingScreen); @@ -9139,15 +10789,34 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo pw.print(" mNextAppTransition=0x"); pw.print(Integer.toHexString(mNextAppTransition)); pw.print(", mAppTransitionReady="); pw.print(mAppTransitionReady); + pw.print(", mAppTransitionRunning="); pw.print(mAppTransitionRunning); pw.print(", mAppTransitionTimeout="); pw.println( mAppTransitionTimeout); + if (mNextAppTransitionPackage != null) { + pw.print(" mNextAppTransitionPackage="); + pw.print(mNextAppTransitionPackage); + pw.print(", mNextAppTransitionEnter=0x"); + pw.print(Integer.toHexString(mNextAppTransitionEnter)); + pw.print(", mNextAppTransitionExit=0x"); + pw.print(Integer.toHexString(mNextAppTransitionExit)); + } 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); } if (mClosingApps.size() > 0) { pw.print(" mClosingApps="); pw.println(mClosingApps); } + if (mToTopApps.size() > 0) { + pw.print(" mToTopApps="); pw.println(mToTopApps); + } + if (mToBottomApps.size() > 0) { + pw.print(" mToBottomApps="); pw.println(mToBottomApps); + } pw.print(" DisplayWidth="); pw.print(mDisplay.getWidth()); pw.print(" DisplayHeight="); pw.println(mDisplay.getHeight()); pw.println(" KeyWaiter state:"); @@ -9166,6 +10835,10 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo synchronized (mKeyWaiter) { } } + public void virtualKeyFeedback(KeyEvent event) { + mPolicy.keyFeedbackFromInput(event); + } + /** * DimAnimator class that controls the dim animation. This holds the surface and * all state used for dim animation. @@ -9215,7 +10888,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo mDimSurface.setLayer(w.mAnimLayer-1); final float target = w.mExiting ? 0 : w.mAttrs.dimAmount; - if (SHOW_TRANSACTIONS) Log.i(TAG, "layer=" + (w.mAnimLayer-1) + ", target=" + target); + if (SHOW_TRANSACTIONS) Log.i(TAG, " DIM " + mDimSurface + + ": layer=" + (w.mAnimLayer-1) + " target=" + target); if (mDimTargetAlpha != target) { // If the desired dim level has changed, then // start an animation to it. diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 5d34d00..c3aeca4 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -39,8 +39,10 @@ import android.app.IInstrumentationWatcher; import android.app.IServiceConnection; import android.app.IThumbnailReceiver; import android.app.Instrumentation; +import android.app.Notification; import android.app.PendingIntent; import android.app.ResultInfo; +import android.app.Service; import android.backup.IBackupManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; @@ -50,6 +52,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IIntentReceiver; import android.content.IIntentSender; +import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; @@ -66,6 +69,7 @@ import android.graphics.Bitmap; import android.net.Uri; import android.os.Binder; import android.os.Bundle; +import android.os.Debug; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; @@ -133,9 +137,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen static final boolean DEBUG_SERVICE = localLOGV || false; static final boolean DEBUG_VISBILITY = localLOGV || false; static final boolean DEBUG_PROCESSES = localLOGV || false; + static final boolean DEBUG_PROVIDER = localLOGV || false; static final boolean DEBUG_USER_LEAVING = localLOGV || false; static final boolean DEBUG_RESULTS = localLOGV || false; - static final boolean DEBUG_BACKUP = localLOGV || true; + static final boolean DEBUG_BACKUP = localLOGV || false; + static final boolean DEBUG_CONFIGURATION = localLOGV || false; static final boolean VALIDATE_TOKENS = false; static final boolean SHOW_ACTIVITY_START_TIME = true; @@ -334,6 +340,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Memory pages are 4K. static final int PAGE_SIZE = 4*1024; + // System property defining error report receiver for system apps + static final String SYSTEM_APPS_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.system.apps"; + + // System property defining default error report receiver + static final String DEFAULT_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.default"; + // Corresponding memory levels for above adjustments. final int EMPTY_APP_MEM; final int HIDDEN_APP_MEM; @@ -447,6 +459,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen = new ArrayList<HistoryRecord>(); /** + * Animations that for the current transition have requested not to + * be considered for the transition animation. + */ + final ArrayList<HistoryRecord> mNoAnimActivities + = new ArrayList<HistoryRecord>(); + + /** * List of intents that were used to start the most recent tasks. */ final ArrayList<TaskRecord> mRecentTasks @@ -763,6 +782,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen String mTopData; boolean mSystemReady = false; boolean mBooting = false; + boolean mWaitingUpdate = false; + boolean mDidUpdate = false; Context mContext; @@ -980,6 +1001,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen res.set(0); } } + + ensureBootCompleted(); } break; case SHOW_NOT_RESPONDING_MSG: { synchronized (ActivityManagerService.this) { @@ -1000,13 +1023,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen proc.anrDialog = d; } - ensureScreenEnabled(); + ensureBootCompleted(); } break; case SHOW_FACTORY_ERROR_MSG: { Dialog d = new FactoryErrorDialog( mContext, msg.getData().getCharSequence("msg")); d.show(); - enableScreenAfterBoot(); + ensureBootCompleted(); } break; case UPDATE_CONFIGURATION_MSG: { final ContentResolver resolver = mContext.getContentResolver(); @@ -1070,7 +1093,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // so we need to be conservative and assume it isn't. IBinder token = (IBinder)msg.obj; Log.w(TAG, "Activity idle timeout for " + token); - activityIdleInternal(token, true); + activityIdleInternal(token, true, null); } break; case DESTROY_TIMEOUT_MSG: { IBinder token = (IBinder)msg.obj; @@ -1081,7 +1104,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } break; case IDLE_NOW_MSG: { IBinder token = (IBinder)msg.obj; - activityIdle(token); + activityIdle(token, null); } break; case SERVICE_TIMEOUT_MSG: { if (mDidDexOpt) { @@ -1199,6 +1222,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ApplicationInfo info = mSelf.mContext.getPackageManager().getApplicationInfo( "android", STOCK_PM_FLAGS); + mSystemThread.installSystemApplicationInfo(info); + synchronized (mSelf) { ProcessRecord app = mSelf.newProcessRecordLocked( mSystemThread.getApplicationThread(), info, @@ -1411,6 +1436,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mSimpleProcessManagement = true; } + Log.i(TAG, "Memory class: " + ActivityManager.staticGetMemoryClass()); + MY_PID = Process.myPid(); File dataDir = Environment.getDataDirectory(); @@ -1557,6 +1584,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + long[] cpuSpeedTimes = mProcessStats.getLastCpuSpeedTimes(); final BatteryStatsImpl bstats = mBatteryStatsService.getActiveStatistics(); synchronized(bstats) { synchronized(mPidsSelfLocked) { @@ -1570,11 +1598,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (pr != null) { BatteryStatsImpl.Uid.Proc ps = pr.batteryStats; ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); + ps.addSpeedStepTimes(cpuSpeedTimes); } else { BatteryStatsImpl.Uid.Proc ps = bstats.getProcessStatsLocked(st.name, st.pid); if (ps != null) { ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); + ps.addSpeedStepTimes(cpuSpeedTimes); } } } @@ -1850,12 +1880,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } startProcessLocked(r.processName, r.info.applicationInfo, true, 0, - "activity", r.intent.getComponent()); + "activity", r.intent.getComponent(), false); } private final ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, - String hostingType, ComponentName hostingName) { + String hostingType, ComponentName hostingName, boolean allowWhileBooting) { ProcessRecord app = getProcessRecordLocked(processName, info.uid); // We don't have to do anything more if: // (1) There is an existing application record; and @@ -1908,7 +1938,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // If the system is not ready yet, then hold off on starting this // process until it is. if (!mSystemReady - && (info.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) { + && !isAllowedWhileBooting(info) + && !allowWhileBooting) { if (!mProcessesOnHold.contains(app)) { mProcessesOnHold.add(app); } @@ -1919,6 +1950,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return (app.pid != 0) ? app : null; } + boolean isAllowedWhileBooting(ApplicationInfo ai) { + return (ai.flags&ApplicationInfo.FLAG_PERSISTENT) != 0; + } + private final void startProcessLocked(ProcessRecord app, String hostingType, String hostingNameStr) { if (app.pid > 0 && app.pid != MY_PID) { @@ -2219,13 +2254,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mHandler.sendMessage(msg); } - reportResumedActivity(next); + reportResumedActivityLocked(next); next.thumbnail = null; setFocusedActivityLocked(next); next.resumeKeyDispatchingLocked(); ensureActivitiesVisibleLocked(null, 0); mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); // Mark the point when the activity is resuming // TODO: To be more accurate, the mark should be before the onCreate, @@ -2491,7 +2527,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private void reportResumedActivity(HistoryRecord r) { + private void reportResumedActivityLocked(HistoryRecord r) { //Log.i(TAG, "**** REPORT RESUME: " + r); final int identHash = System.identityHashCode(r); @@ -2542,6 +2578,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Make sure we have executed any pending transitions, since there // should be nothing left to do at this point. mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); return false; } @@ -2552,6 +2589,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Make sure we have executed any pending transitions, since there // should be nothing left to do at this point. mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); return false; } @@ -2614,17 +2652,25 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (prev.finishing) { if (DEBUG_TRANSITION) Log.v(TAG, "Prepare close transition: prev=" + prev); - mWindowManager.prepareAppTransition(prev.task == next.task - ? WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE - : WindowManagerPolicy.TRANSIT_TASK_CLOSE); + if (mNoAnimActivities.contains(prev)) { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + } else { + mWindowManager.prepareAppTransition(prev.task == next.task + ? WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE + : WindowManagerPolicy.TRANSIT_TASK_CLOSE); + } mWindowManager.setAppWillBeHidden(prev); mWindowManager.setAppVisibility(prev, false); } else { if (DEBUG_TRANSITION) Log.v(TAG, "Prepare open transition: prev=" + prev); - mWindowManager.prepareAppTransition(prev.task == next.task - ? WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN - : WindowManagerPolicy.TRANSIT_TASK_OPEN); + if (mNoAnimActivities.contains(next)) { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + } else { + mWindowManager.prepareAppTransition(prev.task == next.task + ? WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN + : WindowManagerPolicy.TRANSIT_TASK_OPEN); + } } if (false) { mWindowManager.setAppWillBeHidden(prev); @@ -2633,7 +2679,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } else if (mHistory.size() > 1) { if (DEBUG_TRANSITION) Log.v(TAG, "Prepare open transition: no previous"); - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + if (mNoAnimActivities.contains(next)) { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + } else { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + } } if (next.app != null && next.app.thread != null) { @@ -2655,13 +2705,31 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Have the window manager re-evaluate the orientation of // the screen based on the new activity order. - Configuration config = mWindowManager.updateOrientationFromAppTokens( - mConfiguration, - next.mayFreezeScreenLocked(next.app) ? next : null); - if (config != null) { - next.frozenBeforeDestroy = true; - } - if (!updateConfigurationLocked(config, next)) { + boolean updated; + synchronized (this) { + Configuration config = mWindowManager.updateOrientationFromAppTokens( + mConfiguration, + next.mayFreezeScreenLocked(next.app) ? next : null); + if (config != null) { + /* + * Explicitly restore the locale to the one from the + * old configuration, since the one that comes back from + * the window manager has the default (boot) locale. + * + * It looks like previously the locale picker only worked + * by coincidence: usually it would do its setting of + * the locale after the activity transition, so it didn't + * matter that this lost it. With the synchronized + * block now keeping them from happening at the same time, + * this one always would happen second and undo what the + * locale picker had just done. + */ + config.locale = mConfiguration.locale; + next.frozenBeforeDestroy = true; + } + updated = updateConfigurationLocked(config, next); + } + if (!updated) { // The configuration update wasn't able to keep the existing // instance of the activity, and instead started a new one. // We should be all done, but let's just make sure our activity @@ -2675,7 +2743,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Do over! mHandler.sendEmptyMessage(RESUME_TOP_ACTIVITY_MSG); } + setFocusedActivityLocked(next); + ensureActivitiesVisibleLocked(null, 0); mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); return true; } @@ -2836,9 +2907,18 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } if (DEBUG_TRANSITION) Log.v(TAG, "Prepare open transition: starting " + r); - mWindowManager.prepareAppTransition(newTask - ? WindowManagerPolicy.TRANSIT_TASK_OPEN - : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + mNoAnimActivities.add(r); + } else if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_OPEN); + mNoAnimActivities.remove(r); + } else { + mWindowManager.prepareAppTransition(newTask + ? WindowManagerPolicy.TRANSIT_TASK_OPEN + : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + mNoAnimActivities.remove(r); + } mWindowManager.addAppToken( addPos, r, r.task.taskId, r.info.screenOrientation, r.fullscreen); boolean doShow = true; @@ -2898,7 +2978,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * or null if none was found. */ private final HistoryRecord performClearTaskLocked(int taskId, - HistoryRecord newR, boolean doClear) { + HistoryRecord newR, int launchFlags, boolean doClear) { int i = mHistory.size(); // First find the requested task. @@ -2941,7 +3021,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Finally, if this is a normal launch mode (that is, not // expecting onNewIntent()), then we will finish the current // instance of the activity so a new fresh one can be started. - if (ret.launchMode == ActivityInfo.LAUNCH_MULTIPLE) { + if (ret.launchMode == ActivityInfo.LAUNCH_MULTIPLE + && (launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0) { if (!ret.finishing) { int index = indexOfTokenLocked(ret); if (index >= 0) { @@ -3312,7 +3393,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (callerAtFront) { // We really do want to push this one into the // user's face, right now. - moveTaskToFrontLocked(taskTop.task); + moveTaskToFrontLocked(taskTop.task, r); } } // If the caller has requested that the target task be @@ -3338,7 +3419,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // cases this means we are resetting the task to its // initial state. HistoryRecord top = performClearTaskLocked( - taskTop.task.taskId, r, true); + taskTop.task.taskId, r, launchFlags, true); if (top != null) { if (top.frontOfTask) { // Activity aliases may mean we use different @@ -3481,7 +3562,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // task, but the caller has asked to clear that task if the // activity is already running. HistoryRecord top = performClearTaskLocked( - sourceRecord.task.taskId, r, true); + sourceRecord.task.taskId, r, launchFlags, true); if (top != null) { logStartActivity(LOG_AM_NEW_INTENT, r, top.task); deliverNewIntentLocked(top, r.intent); @@ -3592,6 +3673,36 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + public int startActivityIntentSender(IApplicationThread caller, + IntentSender intent, Intent fillInIntent, String resolvedType, + IBinder resultTo, String resultWho, int requestCode, + int flagsMask, int flagsValues) { + // Refuse possible leaked file descriptors + if (fillInIntent != null && fillInIntent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + IIntentSender sender = intent.getTarget(); + if (!(sender instanceof PendingIntentRecord)) { + throw new IllegalArgumentException("Bad PendingIntent object"); + } + + PendingIntentRecord pir = (PendingIntentRecord)sender; + + synchronized (this) { + // If this is coming from the currently resumed activity, it is + // effectively saying that app switches are allowed at this point. + if (mResumedActivity != null + && mResumedActivity.info.applicationInfo.uid == + Binder.getCallingUid()) { + mAppSwitchesAllowedTime = 0; + } + } + + return pir.sendInner(0, fillInIntent, resolvedType, + null, resultTo, resultWho, requestCode, flagsMask, flagsValues); + } + public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent) { // Refuse possible leaked file descriptors @@ -4108,6 +4219,27 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + public void overridePendingTransition(IBinder token, String packageName, + int enterAnim, int exitAnim) { + synchronized(this) { + int index = indexOfTokenLocked(token); + if (index < 0) { + return; + } + HistoryRecord self = (HistoryRecord)mHistory.get(index); + + final long origId = Binder.clearCallingIdentity(); + + if (self.state == ActivityState.RESUMED + || self.state == ActivityState.PAUSING) { + mWindowManager.overridePendingAppTransition(packageName, + enterAnim, exitAnim); + } + + Binder.restoreCallingIdentity(origId); + } + } + /** * Perform clean-up of service connections in an activity record. */ @@ -4444,7 +4576,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen long now = SystemClock.uptimeMillis(); for (i=0; i<count; i++) { ProcessRecord rec = mLRUProcesses.get(i); - if (rec.thread != null && + if (rec != app && rec.thread != null && (rec.lastLowMemory+GC_MIN_INTERVAL) <= now) { // The low memory report is overriding any current // state for a GC request. Make sure to do @@ -4871,12 +5003,59 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } mWatchers.finishBroadcast(); + mWindowManager.closeSystemDialogs(reason); + + for (i=mHistory.size()-1; i>=0; i--) { + HistoryRecord r = (HistoryRecord)mHistory.get(i); + if ((r.info.flags&ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0) { + finishActivityLocked(r, i, + Activity.RESULT_CANCELED, null, "close-sys"); + } + } + broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, false, false, -1, uid); } Binder.restoreCallingIdentity(origId); } + public Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids) + throws RemoteException { + Debug.MemoryInfo[] infos = new Debug.MemoryInfo[pids.length]; + for (int i=pids.length-1; i>=0; i--) { + infos[i] = new Debug.MemoryInfo(); + Debug.getMemoryInfo(pids[i], infos[i]); + } + return infos; + } + + public void killApplicationProcess(String processName, int uid) { + if (processName == null) { + return; + } + + int callerUid = Binder.getCallingUid(); + // Only the system server can kill an application + if (callerUid == Process.SYSTEM_UID) { + synchronized (this) { + ProcessRecord app = getProcessRecordLocked(processName, uid); + if (app != null) { + try { + app.thread.scheduleSuicide(); + } catch (RemoteException e) { + // If the other end already died, then our work here is done. + } + } else { + Log.w(TAG, "Process/uid not found attempting kill of " + + processName + " / " + uid); + } + } + } else { + throw new SecurityException(callerUid + " cannot kill app process: " + + processName); + } + } + private void restartPackageLocked(final String packageName, int uid) { uninstallPackageLocked(packageName, uid, false); Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED, @@ -5101,8 +5280,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); - List providers = generateApplicationProvidersLocked(app); + boolean normalMode = mSystemReady || isAllowedWhileBooting(app.info); + List providers = normalMode ? generateApplicationProvidersLocked(app) : null; + if (!normalMode) { + Log.i(TAG, "Launching preboot mode app: " + app); + } + if (localLOGV) Log.v( TAG, "New app record " + app + " thread=" + thread.asBinder() + " pid=" + pid); @@ -5118,23 +5302,28 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mWaitForDebugger = mOrigWaitForDebugger; } } + // If the app is being launched for restore or full backup, set it up specially boolean isRestrictedBackupMode = false; if (mBackupTarget != null && mBackupAppName.equals(processName)) { isRestrictedBackupMode = (mBackupTarget.backupMode == BackupRecord.RESTORE) || (mBackupTarget.backupMode == BackupRecord.BACKUP_FULL); } + ensurePackageDexOpt(app.instrumentationInfo != null ? app.instrumentationInfo.packageName : app.info.packageName); if (app.instrumentationClass != null) { ensurePackageDexOpt(app.instrumentationClass.getPackageName()); } + if (DEBUG_CONFIGURATION) Log.v(TAG, "Binding proc " + + processName + " with config " + mConfiguration); thread.bindApplication(processName, app.instrumentationInfo != null ? app.instrumentationInfo : app.info, providers, app.instrumentationClass, app.instrumentationProfileFile, app.instrumentationArguments, app.instrumentationWatcher, testMode, - isRestrictedBackupMode, mConfiguration, getCommonServicesLocked()); + isRestrictedBackupMode || !normalMode, + mConfiguration, getCommonServicesLocked()); updateLRUListLocked(app, false); app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis(); } catch (Exception e) { @@ -5250,9 +5439,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - public final void activityIdle(IBinder token) { + public final void activityIdle(IBinder token, Configuration config) { final long origId = Binder.clearCallingIdentity(); - activityIdleInternal(token, false); + activityIdleInternal(token, false, config); Binder.restoreCallingIdentity(origId); } @@ -5300,10 +5489,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } void enableScreenAfterBoot() { + EventLog.writeEvent(LOG_BOOT_PROGRESS_ENABLE_SCREEN, + SystemClock.uptimeMillis()); mWindowManager.enableScreenAfterBoot(); } - final void activityIdleInternal(IBinder token, boolean fromTimeout) { + final void activityIdleInternal(IBinder token, boolean fromTimeout, + Configuration config) { if (localLOGV) Log.v(TAG, "Activity idle: " + token); ArrayList<HistoryRecord> stops = null; @@ -5326,6 +5518,15 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (index >= 0) { HistoryRecord r = (HistoryRecord)mHistory.get(index); + // This is a hack to semi-deal with a race condition + // in the client where it can be constructed with a + // newer configuration from when we asked it to launch. + // We'll update with whatever configuration it now says + // it used to launch. + if (config != null) { + r.configuration = config; + } + // No longer need to keep the device awake. if (mResumedActivity == r && mLaunchingActivity.isHeld()) { mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); @@ -5410,26 +5611,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } if (booting) { - // Ensure that any processes we had put on hold are now started - // up. - final int NP = mProcessesOnHold.size(); - if (NP > 0) { - ArrayList<ProcessRecord> procs = - new ArrayList<ProcessRecord>(mProcessesOnHold); - for (int ip=0; ip<NP; ip++) { - this.startProcessLocked(procs.get(ip), "on-hold", null); - } - } - if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { - // Tell anyone interested that we are done booting! - synchronized (this) { - broadcastIntentLocked(null, null, - new Intent(Intent.ACTION_BOOT_COMPLETED, null), - null, null, 0, null, null, - android.Manifest.permission.RECEIVE_BOOT_COMPLETED, - false, false, MY_PID, Process.SYSTEM_UID); - } - } + finishBooting(); } trimApplications(); @@ -5437,22 +5619,48 @@ public final class ActivityManagerService extends ActivityManagerNative implemen //mWindowManager.dump(); if (enableScreen) { - EventLog.writeEvent(LOG_BOOT_PROGRESS_ENABLE_SCREEN, - SystemClock.uptimeMillis()); enableScreenAfterBoot(); } } - final void ensureScreenEnabled() { + final void finishBooting() { + // Ensure that any processes we had put on hold are now started + // up. + final int NP = mProcessesOnHold.size(); + if (NP > 0) { + ArrayList<ProcessRecord> procs = + new ArrayList<ProcessRecord>(mProcessesOnHold); + for (int ip=0; ip<NP; ip++) { + this.startProcessLocked(procs.get(ip), "on-hold", null); + } + } + if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { + // Tell anyone interested that we are done booting! + synchronized (this) { + broadcastIntentLocked(null, null, + new Intent(Intent.ACTION_BOOT_COMPLETED, null), + null, null, 0, null, null, + android.Manifest.permission.RECEIVE_BOOT_COMPLETED, + false, false, MY_PID, Process.SYSTEM_UID); + } + } + } + + final void ensureBootCompleted() { + boolean booting; boolean enableScreen; synchronized (this) { + booting = mBooting; + mBooting = false; enableScreen = !mBooted; mBooted = true; } + + if (booting) { + finishBooting(); + } if (enableScreen) { - EventLog.writeEvent(LOG_BOOT_PROGRESS_ENABLE_SCREEN, - SystemClock.uptimeMillis()); enableScreenAfterBoot(); } } @@ -5552,7 +5760,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public String getCallingPackage(IBinder token) { synchronized (this) { HistoryRecord r = getCallingRecordLocked(token); - return r != null && r.app != null ? r.app.processName : null; + return r != null && r.app != null ? r.info.packageName : null; } } @@ -5604,6 +5812,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen throw new IllegalArgumentException("File descriptors passed in Intent"); } + if (type == INTENT_SENDER_BROADCAST) { + if ((intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { + throw new IllegalArgumentException( + "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); + } + } + synchronized(this) { int callingUid = Binder.getCallingUid(); try { @@ -6807,14 +7022,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen for (int i=0; i<N; i++) { TaskRecord tr = mRecentTasks.get(i); if (tr.taskId == task) { - moveTaskToFrontLocked(tr); + moveTaskToFrontLocked(tr, null); return; } } for (int i=mHistory.size()-1; i>=0; i--) { HistoryRecord hr = (HistoryRecord)mHistory.get(i); if (hr.task.taskId == task) { - moveTaskToFrontLocked(hr.task); + moveTaskToFrontLocked(hr.task, null); return; } } @@ -6824,7 +7039,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private final void moveTaskToFrontLocked(TaskRecord tr) { + private final void moveTaskToFrontLocked(TaskRecord tr, HistoryRecord reason) { if (DEBUG_SWITCH) Log.v(TAG, "moveTaskToFront: " + tr); final int task = tr.taskId; @@ -6835,10 +7050,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return; } - if (DEBUG_TRANSITION) Log.v(TAG, - "Prepare to front transition: task=" + tr); - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_FRONT); - ArrayList moved = new ArrayList(); // Applying the affinities may have removed entries from the history, @@ -6867,6 +7078,19 @@ public final class ActivityManagerService extends ActivityManagerNative implemen pos--; } + if (DEBUG_TRANSITION) Log.v(TAG, + "Prepare to front transition: task=" + tr); + if (reason != null && + (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + HistoryRecord r = topRunningActivityLocked(null); + if (r != null) { + mNoAnimActivities.add(r); + } + } else { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_FRONT); + } + mWindowManager.moveAppTokensToTop(moved); if (VALIDATE_TOKENS) { mWindowManager.validateAppTokens(mHistory); @@ -6892,7 +7116,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } final long origId = Binder.clearCallingIdentity(); - moveTaskToBackLocked(task); + moveTaskToBackLocked(task, null); Binder.restoreCallingIdentity(origId); } } @@ -6911,7 +7135,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen final long origId = Binder.clearCallingIdentity(); int taskId = getTaskForActivityLocked(token, !nonRoot); if (taskId >= 0) { - return moveTaskToBackLocked(taskId); + return moveTaskToBackLocked(taskId, null); } Binder.restoreCallingIdentity(origId); } @@ -6929,7 +7153,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * @param task The taskId to collect and move to the bottom. * @return Returns true if the move completed, false if not. */ - private final boolean moveTaskToBackLocked(int task) { + private final boolean moveTaskToBackLocked(int task, HistoryRecord reason) { Log.i(TAG, "moveTaskToBack: " + task); // If we have a watcher, preflight the move before committing to it. First check @@ -6958,7 +7182,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (DEBUG_TRANSITION) Log.v(TAG, "Prepare to back transition: task=" + task); - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_BACK); final int N = mHistory.size(); int bottom = 0; @@ -6980,6 +7203,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen pos++; } + if (reason != null && + (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + HistoryRecord r = topRunningActivityLocked(null); + if (r != null) { + mNoAnimActivities.add(r); + } + } else { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_BACK); + } mWindowManager.moveAppTokensToBottom(moved); if (VALIDATE_TOKENS) { mWindowManager.validateAppTokens(mHistory); @@ -7322,10 +7555,19 @@ public final class ActivityManagerService extends ActivityManagerNative implemen final long origId = Binder.clearCallingIdentity(); - // In this case the provider is a single instance, so we can + // In this case the provider instance already exists, so we can // return it right away. if (r != null) { - r.conProviders.add(cpr); + if (DEBUG_PROVIDER) Log.v(TAG, + "Adding provider requested by " + + r.processName + " from process " + + cpr.info.processName); + Integer cnt = r.conProviders.get(cpr); + if (cnt == null) { + r.conProviders.put(cpr, new Integer(1)); + } else { + r.conProviders.put(cpr, new Integer(cnt.intValue()+1)); + } cpr.clients.add(r); } else { cpr.externals++; @@ -7382,10 +7624,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return cpr; } - if (false) { - RuntimeException e = new RuntimeException("foo"); - //Log.w(TAG, "LAUNCHING REMOTE PROVIDER (myuid " + r.info.uid - // + " pruid " + ai.uid + "): " + cpi.className, e); + if (DEBUG_PROVIDER) { + RuntimeException e = new RuntimeException("here"); + Log.w(TAG, "LAUNCHING REMOTE PROVIDER (myuid " + r.info.uid + + " pruid " + cpr.appInfo.uid + "): " + cpr.info.name, e); } // This is single process, and our app is now connecting to it. @@ -7397,14 +7639,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (mLaunchingProviders.get(i) == cpr) { break; } - if (false) { - final ContentProviderRecord rec = - (ContentProviderRecord)mLaunchingProviders.get(i); - if (rec.info.name.equals(cpr.info.name)) { - cpr = rec; - break; - } - } } // If the provider is not already being launched, then get it @@ -7414,7 +7648,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ProcessRecord proc = startProcessLocked(cpi.processName, cpr.appInfo, false, 0, "content provider", new ComponentName(cpi.applicationInfo.packageName, - cpi.name)); + cpi.name), false); if (proc == null) { Log.w(TAG, "Unable to launch app " + cpi.applicationInfo.packageName + "/" @@ -7435,7 +7669,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mProvidersByName.put(name, cpr); if (r != null) { - r.conProviders.add(cpr); + if (DEBUG_PROVIDER) Log.v(TAG, + "Adding provider requested by " + + r.processName + " from process " + + cpr.info.processName); + Integer cnt = r.conProviders.get(cpr); + if (cnt == null) { + r.conProviders.put(cpr, new Integer(1)); + } else { + r.conProviders.put(cpr, new Integer(cnt.intValue()+1)); + } cpr.clients.add(r); } else { cpr.externals++; @@ -7489,8 +7732,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized (this) { ContentProviderRecord cpr = (ContentProviderRecord)mProvidersByName.get(name); if(cpr == null) { - //remove from mProvidersByClass - if(localLOGV) Log.v(TAG, name+" content provider not found in providers list"); + // remove from mProvidersByClass + if (DEBUG_PROVIDER) Log.v(TAG, name + + " provider not found in providers list"); return; } final ProcessRecord r = getRecordForAppLocked(caller); @@ -7500,16 +7744,24 @@ public final class ActivityManagerService extends ActivityManagerNative implemen " when removing content provider " + name); } //update content provider record entry info - ContentProviderRecord localCpr = (ContentProviderRecord) mProvidersByClass.get(cpr.info.name); - if(localLOGV) Log.v(TAG, "Removing content provider requested by "+ - r.info.processName+" from process "+localCpr.appInfo.processName); - if(localCpr.appInfo.processName == r.info.processName) { + ContentProviderRecord localCpr = (ContentProviderRecord) + mProvidersByClass.get(cpr.info.name); + if (DEBUG_PROVIDER) Log.v(TAG, "Removing provider requested by " + + r.info.processName + " from process " + + localCpr.appInfo.processName); + if (localCpr.app == r) { //should not happen. taken care of as a local provider - if(localLOGV) Log.v(TAG, "local provider doing nothing Ignoring other names"); + Log.w(TAG, "removeContentProvider called on local provider: " + + cpr.info.name + " in process " + r.processName); return; } else { - localCpr.clients.remove(r); - r.conProviders.remove(localCpr); + Integer cnt = r.conProviders.get(localCpr); + if (cnt == null || cnt.intValue() <= 1) { + localCpr.clients.remove(r); + r.conProviders.remove(localCpr); + } else { + r.conProviders.put(localCpr, new Integer(cnt.intValue()-1)); + } } updateOomAdjLocked(); } @@ -8099,7 +8351,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - systemReady(); + systemReady(null); } private void retrieveSettings() { @@ -8121,6 +8373,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // This happens before any activities are started, so we can // change mConfiguration in-place. mConfiguration.updateFrom(configuration); + if (DEBUG_CONFIGURATION) Log.v(TAG, "Initial config: " + mConfiguration); } } @@ -8129,7 +8382,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return mSystemReady; } - public void systemReady() { + public void systemReady(final Runnable goingCallback) { // In the simulator, startRunning will never have been called, which // normally sets a few crucial variables. Do it here instead. if (!Process.supportsProcesses()) { @@ -8139,19 +8392,97 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized(this) { if (mSystemReady) { + if (goingCallback != null) goingCallback.run(); return; } + + // Check to see if there are any update receivers to run. + if (!mDidUpdate) { + if (mWaitingUpdate) { + return; + } + Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); + List<ResolveInfo> ris = null; + try { + ris = ActivityThread.getPackageManager().queryIntentReceivers( + intent, null, 0); + } catch (RemoteException e) { + } + if (ris != null) { + for (int i=ris.size()-1; i>=0; i--) { + if ((ris.get(i).activityInfo.applicationInfo.flags + &ApplicationInfo.FLAG_SYSTEM) == 0) { + ris.remove(i); + } + } + intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE); + for (int i=0; i<ris.size(); i++) { + ActivityInfo ai = ris.get(i).activityInfo; + intent.setComponent(new ComponentName(ai.packageName, ai.name)); + IIntentReceiver finisher = null; + if (i == 0) { + finisher = new IIntentReceiver.Stub() { + public void performReceive(Intent intent, int resultCode, + String data, Bundle extras, boolean ordered, + boolean sticky) + throws RemoteException { + synchronized (ActivityManagerService.this) { + mDidUpdate = true; + } + systemReady(goingCallback); + } + }; + } + Log.i(TAG, "Sending system update to: " + intent.getComponent()); + broadcastIntentLocked(null, null, intent, null, finisher, + 0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID); + if (i == 0) { + mWaitingUpdate = true; + } + } + } + if (mWaitingUpdate) { + return; + } + mDidUpdate = true; + } + mSystemReady = true; if (!mStartRunning) { return; } } - if (Config.LOGD) Log.d(TAG, "Start running!"); + ArrayList<ProcessRecord> procsToKill = null; + synchronized(mPidsSelfLocked) { + for (int i=mPidsSelfLocked.size()-1; i>=0; i--) { + ProcessRecord proc = mPidsSelfLocked.valueAt(i); + if (!isAllowedWhileBooting(proc.info)){ + if (procsToKill == null) { + procsToKill = new ArrayList<ProcessRecord>(); + } + procsToKill.add(proc); + } + } + } + + if (procsToKill != null) { + synchronized(this) { + for (int i=procsToKill.size()-1; i>=0; i--) { + ProcessRecord proc = procsToKill.get(i); + Log.i(TAG, "Removing system update proc: " + proc); + removeProcessLocked(proc, true); + } + } + } + + Log.i(TAG, "System now ready"); EventLog.writeEvent(LOG_BOOT_PROGRESS_AMS_READY, SystemClock.uptimeMillis()); synchronized(this) { + // Make sure we have no pre-ready processes sitting around. + if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) { ResolveInfo ri = mContext.getPackageManager() .resolveActivity(new Intent(Intent.ACTION_FACTORY_TEST), @@ -8187,6 +8518,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen retrieveSettings(); + if (goingCallback != null) goingCallback.run(); + synchronized (this) { if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { try { @@ -8209,6 +8542,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + // Start up initial activity. + mBooting = true; + try { if (ActivityThread.getPackageManager().hasSystemUidErrors()) { Message msg = Message.obtain(); @@ -8218,8 +8554,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } catch (RemoteException e) { } - // Start up initial activity. - mBooting = true; resumeTopActivityLocked(null); } } @@ -8235,28 +8569,70 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } private ComponentName getErrorReportReceiver(ProcessRecord app) { + // check if error reporting is enabled in Gservices + int enabled = Settings.Gservices.getInt(mContext.getContentResolver(), + Settings.Gservices.SEND_ACTION_APP_ERROR, 0); + if (enabled == 0) { + return null; + } + IPackageManager pm = ActivityThread.getPackageManager(); + try { - // was an installer package name specified when this app was - // installed? - String installerPackageName = pm.getInstallerPackageName(app.info.packageName); - if (installerPackageName == null) { - return null; + // look for receiver in the installer package + String candidate = pm.getInstallerPackageName(app.info.packageName); + ComponentName result = getErrorReportReceiver(pm, app.info.packageName, candidate); + if (result != null) { + return result; } - // is there an Activity in this package that handles ACTION_APP_ERROR? - Intent intent = new Intent(Intent.ACTION_APP_ERROR); - intent.setPackage(installerPackageName); - ResolveInfo info = pm.resolveIntent(intent, null, 0); - if (info == null || info.activityInfo == null) { - return null; + // if the error app is on the system image, look for system apps + // error receiver + if ((app.info.flags&ApplicationInfo.FLAG_SYSTEM) != 0) { + candidate = SystemProperties.get(SYSTEM_APPS_ERROR_RECEIVER_PROPERTY); + result = getErrorReportReceiver(pm, app.info.packageName, candidate); + if (result != null) { + return result; + } } - return new ComponentName(installerPackageName, info.activityInfo.name); + // if there is a default receiver, try that + candidate = SystemProperties.get(DEFAULT_ERROR_RECEIVER_PROPERTY); + return getErrorReportReceiver(pm, app.info.packageName, candidate); } catch (RemoteException e) { - // will return null and no error report will be delivered + // should not happen + Log.e(TAG, "error talking to PackageManager", e); + return null; } - return null; + } + + /** + * Return activity in receiverPackage that handles ACTION_APP_ERROR. + * + * @param pm PackageManager isntance + * @param errorPackage package which caused the error + * @param receiverPackage candidate package to receive the error + * @return activity component within receiverPackage which handles + * ACTION_APP_ERROR, or null if not found + */ + private ComponentName getErrorReportReceiver(IPackageManager pm, String errorPackage, + String receiverPackage) throws RemoteException { + if (receiverPackage == null || receiverPackage.length() == 0) { + return null; + } + + // break the loop if it's the error report receiver package that crashed + if (receiverPackage.equals(errorPackage)) { + return null; + } + + Intent intent = new Intent(Intent.ACTION_APP_ERROR); + intent.setPackage(receiverPackage); + ResolveInfo info = pm.resolveIntent(intent, null, 0); + if (info == null || info.activityInfo == null) { + return null; + } + return new ComponentName(receiverPackage, info.activityInfo.name); } void makeAppNotRespondingLocked(ProcessRecord app, @@ -8578,6 +8954,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen report.crashInfo.throwFileName = trace.getFileName(); report.crashInfo.throwClassName = trace.getClassName(); report.crashInfo.throwMethodName = trace.getMethodName(); + report.crashInfo.throwLineNumber = trace.getLineNumber(); } else if (r.notResponding) { report.type = ApplicationErrorReport.TYPE_ANR; report.anrInfo = new ApplicationErrorReport.AnrInfo(); @@ -8645,6 +9022,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ActivityManager.RunningAppProcessInfo currApp = new ActivityManager.RunningAppProcessInfo(app.processName, app.pid, app.getPackageList()); + currApp.uid = app.info.uid; int adj = app.curAdj; if (adj >= CONTENT_PROVIDER_ADJ) { currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY; @@ -8661,6 +9039,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } else { currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; } + currApp.importanceReasonCode = app.adjTypeCode; + if (app.adjSource instanceof ProcessRecord) { + currApp.importanceReasonPid = ((ProcessRecord)app.adjSource).pid; + } else if (app.adjSource instanceof HistoryRecord) { + HistoryRecord r = (HistoryRecord)app.adjSource; + if (r.app != null) currApp.importanceReasonPid = r.app.pid; + } + if (app.adjTarget instanceof ComponentName) { + currApp.importanceReasonComponent = (ComponentName)app.adjTarget; + } //Log.v(TAG, "Proc " + app.processName + ": imp=" + currApp.importance // + " lru=" + currApp.lru); if (runList == null) { @@ -8830,7 +9218,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen pw.print(" lowMem="); pw.print(proc.reportLowMemory); pw.print(", last gced="); pw.print(now-proc.lastRequestedGc); - pw.print(" ms ago, last lowMwm="); + pw.print(" ms ago, last lowMem="); pw.print(now-proc.lastLowMemory); pw.println(" ms ago"); @@ -9384,7 +9772,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen sr.app = null; sr.executeNesting = 0; mStoppingServices.remove(sr); - if (sr.bindings.size() > 0) { + + boolean hasClients = sr.bindings.size() > 0; + if (hasClients) { Iterator<IntentBindRecord> bindings = sr.bindings.values().iterator(); while (bindings.hasNext()) { @@ -9405,7 +9795,20 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } else if (!allowRestart) { bringDownServiceLocked(sr, true); } else { - scheduleServiceRestartLocked(sr); + boolean canceled = scheduleServiceRestartLocked(sr, true); + + // Should the service remain running? Note that in the + // extreme case of so many attempts to deliver a command + // that it failed, that we also will stop it here. + if (sr.startRequested && (sr.stopIfKilled || canceled)) { + if (sr.pendingStarts.size() == 0) { + sr.startRequested = false; + if (!hasClients) { + // Whoops, no reason to restart! + bringDownServiceLocked(sr, true); + } + } + } } } @@ -9468,6 +9871,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mLRUProcesses.remove(index); } + mProcessesToGc.remove(app); + // Dismiss any open dialogs. if (app.crashDialog != null) { app.crashDialog.dismiss(); @@ -9545,7 +9950,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Unregister from connected content providers. if (!app.conProviders.isEmpty()) { - Iterator it = app.conProviders.iterator(); + Iterator it = app.conProviders.keySet().iterator(); while (it.hasNext()) { ContentProviderRecord cpr = (ContentProviderRecord)it.next(); cpr.clients.remove(app); @@ -9648,6 +10053,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (r.app != null) { info.pid = r.app.pid; } + info.uid = r.appInfo.uid; info.process = r.processName; info.foreground = r.isForeground; info.activeSince = r.createTime; @@ -9655,6 +10061,25 @@ public final class ActivityManagerService extends ActivityManagerNative implemen info.clientCount = r.connections.size(); info.crashCount = r.crashCount; info.lastActivityTime = r.lastActivity; + if (r.isForeground) { + info.flags |= ActivityManager.RunningServiceInfo.FLAG_FOREGROUND; + } + if (r.startRequested) { + info.flags |= ActivityManager.RunningServiceInfo.FLAG_STARTED; + } + if (r.app != null && r.app.pid == Process.myPid()) { + info.flags |= ActivityManager.RunningServiceInfo.FLAG_SYSTEM_PROCESS; + } + if (r.app != null && r.app.persistent) { + info.flags |= ActivityManager.RunningServiceInfo.FLAG_PERSISTENT_PROCESS; + } + for (ConnectionRecord conn : r.connections.values()) { + if (conn.clientLabel != 0) { + info.clientPackage = conn.binding.client.info.packageName; + info.clientLabel = conn.clientLabel; + break; + } + } return info; } @@ -9683,6 +10108,20 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + public PendingIntent getRunningServiceControlPanel(ComponentName name) { + synchronized (this) { + ServiceRecord r = mServices.get(name); + if (r != null) { + for (ConnectionRecord conn : r.connections.values()) { + if (conn.clientIntent != null) { + return conn.clientIntent; + } + } + } + } + return null; + } + private final ServiceRecord findServiceLocked(ComponentName name, IBinder token) { ServiceRecord r = mServices.get(name); @@ -9844,35 +10283,55 @@ public final class ActivityManagerService extends ActivityManagerNative implemen private final void sendServiceArgsLocked(ServiceRecord r, boolean oomAdjusted) { - final int N = r.startArgs.size(); + final int N = r.pendingStarts.size(); if (N == 0) { return; } - final int BASEID = r.lastStartId - N + 1; int i = 0; while (i < N) { try { - Intent args = r.startArgs.get(i); + ServiceRecord.StartItem si = r.pendingStarts.get(i); if (DEBUG_SERVICE) Log.v(TAG, "Sending arguments to service: " - + r.name + " " + r.intent + " args=" + args); + + r.name + " " + r.intent + " args=" + si.intent); + if (si.intent == null && N > 1) { + // If somehow we got a dummy start at the front, then + // just drop it here. + i++; + continue; + } bumpServiceExecutingLocked(r); if (!oomAdjusted) { oomAdjusted = true; updateOomAdjLocked(r.app); } - r.app.thread.scheduleServiceArgs(r, BASEID+i, args); + int flags = 0; + if (si.deliveryCount > 0) { + flags |= Service.START_FLAG_RETRY; + } + if (si.doneExecutingCount > 0) { + flags |= Service.START_FLAG_REDELIVERY; + } + r.app.thread.scheduleServiceArgs(r, si.id, flags, si.intent); + si.deliveredTime = SystemClock.uptimeMillis(); + r.deliveredStarts.add(si); + si.deliveryCount++; i++; + } catch (RemoteException e) { + // Remote process gone... we'll let the normal cleanup take + // care of this. + break; } catch (Exception e) { + Log.w(TAG, "Unexpected exception", e); break; } } if (i == N) { - r.startArgs.clear(); + r.pendingStarts.clear(); } else { while (i > 0) { - r.startArgs.remove(0); i--; + r.pendingStarts.remove(i); } } } @@ -9928,44 +10387,91 @@ public final class ActivityManagerService extends ActivityManagerNative implemen try { if (DEBUG_SERVICE) Log.v(TAG, "Scheduling start service: " + r.name + " " + r.intent); + mStringBuilder.setLength(0); + r.intent.getIntent().toShortString(mStringBuilder, false, true); EventLog.writeEvent(LOG_AM_CREATE_SERVICE, System.identityHashCode(r), r.shortName, - r.intent.getIntent().toString(), r.app.pid); + mStringBuilder.toString(), r.app.pid); synchronized (r.stats.getBatteryStats()) { r.stats.startLaunchedLocked(); } ensurePackageDexOpt(r.serviceInfo.packageName); app.thread.scheduleCreateService(r, r.serviceInfo); + r.postNotification(); created = true; } finally { if (!created) { app.services.remove(r); - scheduleServiceRestartLocked(r); + scheduleServiceRestartLocked(r, false); } } requestServiceBindingsLocked(r); + + // If the service is in the started state, and there are no + // pending arguments, then fake up one so its onStartCommand() will + // be called. + if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) { + r.lastStartId++; + if (r.lastStartId < 1) { + r.lastStartId = 1; + } + r.pendingStarts.add(new ServiceRecord.StartItem(r.lastStartId, null)); + } + sendServiceArgsLocked(r, true); } - private final void scheduleServiceRestartLocked(ServiceRecord r) { + private final boolean scheduleServiceRestartLocked(ServiceRecord r, + boolean allowCancel) { + boolean canceled = false; + final long now = SystemClock.uptimeMillis(); + long minDuration = SERVICE_RESTART_DURATION; + long resetTime = SERVICE_RESET_RUN_DURATION; + + // Any delivered but not yet finished starts should be put back + // on the pending list. + final int N = r.deliveredStarts.size(); + if (N > 0) { + for (int i=N-1; i>=0; i--) { + ServiceRecord.StartItem si = r.deliveredStarts.get(i); + if (si.intent == null) { + // We'll generate this again if needed. + } else if (!allowCancel || (si.deliveryCount < ServiceRecord.MAX_DELIVERY_COUNT + && si.doneExecutingCount < ServiceRecord.MAX_DONE_EXECUTING_COUNT)) { + r.pendingStarts.add(0, si); + long dur = SystemClock.uptimeMillis() - si.deliveredTime; + dur *= 2; + if (minDuration < dur) minDuration = dur; + if (resetTime < dur) resetTime = dur; + } else { + Log.w(TAG, "Canceling start item " + si.intent + " in service " + + r.name); + canceled = true; + } + } + r.deliveredStarts.clear(); + } r.totalRestartCount++; if (r.restartDelay == 0) { r.restartCount++; - r.restartDelay = SERVICE_RESTART_DURATION; + r.restartDelay = minDuration; } else { // If it has been a "reasonably long time" since the service // was started, then reset our restart duration back to // the beginning, so we don't infinitely increase the duration // on a service that just occasionally gets killed (which is // a normal case, due to process being killed to reclaim memory). - if (now > (r.restartTime+SERVICE_RESET_RUN_DURATION)) { + if (now > (r.restartTime+resetTime)) { r.restartCount = 1; - r.restartDelay = SERVICE_RESTART_DURATION; + r.restartDelay = minDuration; } else { r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR; + if (r.restartDelay < minDuration) { + r.restartDelay = minDuration; + } } } @@ -9994,6 +10500,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mRestartingServices.add(r); } + r.cancelNotification(); + mHandler.removeCallbacks(r.restarter); mHandler.postAtTime(r.restarter, r.nextRestartTime); r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay; @@ -10006,6 +10514,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen msg.what = SERVICE_ERROR_MSG; msg.obj = r; mHandler.sendMessage(msg); + + return canceled; } final void performServiceRestartLocked(ServiceRecord r) { @@ -10030,7 +10540,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen //Log.i(TAG, "Bring up service:"); //r.dump(" "); - if (r.app != null) { + if (r.app != null && r.app.thread != null) { sendServiceArgsLocked(r, false); return true; } @@ -10061,20 +10571,22 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // restart the application. } + // Not running -- get it started, and enqueue this service record + // to be executed when the app comes up. + if (startProcessLocked(appName, r.appInfo, true, intentFlags, + "service", r.name, false) == null) { + Log.w(TAG, "Unable to launch app " + + r.appInfo.packageName + "/" + + r.appInfo.uid + " for service " + + r.intent.getIntent() + ": process is bad"); + bringDownServiceLocked(r, true); + return false; + } + if (!mPendingServices.contains(r)) { - // Not running -- get it started, and enqueue this service record - // to be executed when the app comes up. - if (startProcessLocked(appName, r.appInfo, true, intentFlags, - "service", r.name) == null) { - Log.w(TAG, "Unable to launch app " - + r.appInfo.packageName + "/" - + r.appInfo.uid + " for service " - + r.intent.getIntent() + ": process is bad"); - bringDownServiceLocked(r, true); - return false; - } mPendingServices.add(r); } + return true; } @@ -10162,15 +10674,24 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + r.cancelNotification(); + r.isForeground = false; + r.foregroundId = 0; + r.foregroundNoti = null; + + // Clear start entries. + r.deliveredStarts.clear(); + r.pendingStarts.clear(); + if (r.app != null) { synchronized (r.stats.getBatteryStats()) { r.stats.stopLaunchedLocked(); } r.app.services.remove(r); if (r.app.thread != null) { - updateServiceForegroundLocked(r.app, false); try { - Log.i(TAG, "Stopping service: " + r.shortName); + if (DEBUG_SERVICE) Log.v(TAG, + "Stopping service: " + r.shortName); bumpServiceExecutingLocked(r); mStoppingServices.add(r); updateOomAdjLocked(r.app); @@ -10180,6 +10701,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + r.shortName, e); serviceDoneExecutingLocked(r, true); } + updateServiceForegroundLocked(r.app, false); } else { if (DEBUG_SERVICE) Log.v( TAG, "Removed service that has no process: " + r.shortName); @@ -10223,11 +10745,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + r.shortName); } r.startRequested = true; - r.startArgs.add(service); + r.callStart = false; r.lastStartId++; if (r.lastStartId < 1) { r.lastStartId = 1; } + r.pendingStarts.add(new ServiceRecord.StartItem(r.lastStartId, service)); r.lastActivity = SystemClock.uptimeMillis(); synchronized (r.stats.getBatteryStats()) { r.stats.startRunningLocked(); @@ -10295,6 +10818,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.record.stats.stopRunningLocked(); } r.record.startRequested = false; + r.record.callStart = false; final long origId = Binder.clearCallingIdentity(); bringDownServiceLocked(r.record, false); Binder.restoreCallingIdentity(origId); @@ -10343,10 +10867,35 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (DEBUG_SERVICE) Log.v(TAG, "stopServiceToken: " + className + " " + token + " startId=" + startId); ServiceRecord r = findServiceLocked(className, token); - if (r != null && (startId < 0 || r.lastStartId == startId)) { + if (r != null) { + if (startId >= 0) { + // Asked to only stop if done with all work. Note that + // to avoid leaks, we will take this as dropping all + // start items up to and including this one. + ServiceRecord.StartItem si = r.findDeliveredStart(startId, false); + if (si != null) { + while (r.deliveredStarts.size() > 0) { + if (r.deliveredStarts.remove(0) == si) { + break; + } + } + } + + if (r.lastStartId != startId) { + return false; + } + + if (r.deliveredStarts.size() > 0) { + Log.w(TAG, "stopServiceToken startId " + startId + + " is last, but have " + r.deliveredStarts.size() + + " remaining args"); + } + } + synchronized (r.stats.getBatteryStats()) { r.stats.stopRunningLocked(); r.startRequested = false; + r.callStart = false; } final long origId = Binder.clearCallingIdentity(); bringDownServiceLocked(r, false); @@ -10358,20 +10907,45 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } public void setServiceForeground(ComponentName className, IBinder token, - boolean isForeground) { + int id, Notification notification, boolean removeNotification) { + final long origId = Binder.clearCallingIdentity(); + try { synchronized(this) { ServiceRecord r = findServiceLocked(className, token); if (r != null) { - if (r.isForeground != isForeground) { - final long origId = Binder.clearCallingIdentity(); - r.isForeground = isForeground; + if (id != 0) { + if (notification == null) { + throw new IllegalArgumentException("null notification"); + } + if (r.foregroundId != id) { + r.cancelNotification(); + r.foregroundId = id; + } + notification.flags |= Notification.FLAG_FOREGROUND_SERVICE; + r.foregroundNoti = notification; + r.isForeground = true; + r.postNotification(); if (r.app != null) { updateServiceForegroundLocked(r.app, true); } - Binder.restoreCallingIdentity(origId); + } else { + if (r.isForeground) { + r.isForeground = false; + if (r.app != null) { + updateServiceForegroundLocked(r.app, true); + } + } + if (removeNotification) { + r.cancelNotification(); + r.foregroundId = 0; + r.foregroundNoti = null; + } } } } + } finally { + Binder.restoreCallingIdentity(origId); + } } public void updateServiceForegroundLocked(ProcessRecord proc, boolean oomAdj) { @@ -10420,6 +10994,29 @@ public final class ActivityManagerService extends ActivityManagerNative implemen activity = (HistoryRecord)mHistory.get(aindex); } + int clientLabel = 0; + PendingIntent clientIntent = null; + + if (callerApp.info.uid == Process.SYSTEM_UID) { + // Hacky kind of thing -- allow system stuff to tell us + // what they are, so we can report this elsewhere for + // others to know why certain services are running. + try { + clientIntent = (PendingIntent)service.getParcelableExtra( + Intent.EXTRA_CLIENT_INTENT); + } catch (RuntimeException e) { + } + if (clientIntent != null) { + clientLabel = service.getIntExtra(Intent.EXTRA_CLIENT_LABEL, 0); + if (clientLabel != 0) { + // There are no useful extras in the intent, trash them. + // System code calling with this stuff just needs to know + // this will happen. + service = service.cloneFilter(); + } + } + } + ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, Binder.getCallingPid(), Binder.getCallingUid()); @@ -10440,7 +11037,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp); ConnectionRecord c = new ConnectionRecord(b, activity, - connection, flags); + connection, flags, clientLabel, clientIntent); IBinder binder = connection.asBinder(); s.connections.put(binder, c); @@ -10665,7 +11262,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - public void serviceDoneExecuting(IBinder token) { + public void serviceDoneExecuting(IBinder token, int type, int startId, int res) { synchronized(this) { if (!(token instanceof ServiceRecord)) { throw new IllegalArgumentException("Invalid service token"); @@ -10683,6 +11280,51 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return; } + if (type == 1) { + // This is a call from a service start... take care of + // book-keeping. + r.callStart = true; + switch (res) { + case Service.START_STICKY_COMPATIBILITY: + case Service.START_STICKY: { + // We are done with the associated start arguments. + r.findDeliveredStart(startId, true); + // Don't stop if killed. + r.stopIfKilled = false; + break; + } + case Service.START_NOT_STICKY: { + // We are done with the associated start arguments. + r.findDeliveredStart(startId, true); + if (r.lastStartId == startId) { + // There is no more work, and this service + // doesn't want to hang around if killed. + r.stopIfKilled = true; + } + break; + } + case Service.START_REDELIVER_INTENT: { + // We'll keep this item until they explicitly + // call stop for it, but keep track of the fact + // that it was delivered. + ServiceRecord.StartItem si = r.findDeliveredStart(startId, false); + if (si != null) { + si.deliveryCount = 0; + si.doneExecutingCount++; + // Don't stop if killed. + r.stopIfKilled = true; + } + break; + } + default: + throw new IllegalArgumentException( + "Unknown service start result: " + res); + } + if (res == Service.START_STICKY_COMPATIBILITY) { + r.callStart = false; + } + } + final long origId = Binder.clearCallingIdentity(); serviceDoneExecutingLocked(r, inStopping); Binder.restoreCallingIdentity(origId); @@ -10761,7 +11403,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ComponentName hostingName = new ComponentName(app.packageName, app.backupAgentName); // startProcessLocked() returns existing proc's record if it's already running ProcessRecord proc = startProcessLocked(app.processName, app, - false, 0, "backup", hostingName); + false, 0, "backup", hostingName, false); if (proc == null) { Log.e(TAG, "Unable to start backup agent process " + r); return false; @@ -10781,8 +11423,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen try { proc.thread.scheduleCreateBackupAgent(app, backupMode); } catch (RemoteException e) { - // !!! TODO: notify the backup manager that we crashed, or rely on - // death notices, or...? + // Will time out on the backup manager side } } else { if (DEBUG_BACKUP) Log.v(TAG, "Agent proc not running, waiting for attach"); @@ -10969,7 +11610,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Intent intent = (Intent)allSticky.get(i); BroadcastRecord r = new BroadcastRecord(intent, null, null, -1, -1, null, receivers, null, 0, null, null, - false); + false, true); if (mParallelBroadcasts.size() == 0) { scheduleBroadcastsLocked(); } @@ -11194,7 +11835,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen BroadcastRecord r = new BroadcastRecord(intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, registeredReceivers, resultTo, resultCode, resultData, map, - ordered); + ordered, false); if (DEBUG_BROADCAST) Log.v( TAG, "Enqueueing parallel broadcast " + r + ": prev had " + mParallelBroadcasts.size()); @@ -11273,7 +11914,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen || resultTo != null) { BroadcastRecord r = new BroadcastRecord(intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, - receivers, resultTo, resultCode, resultData, map, ordered); + receivers, resultTo, resultCode, resultData, map, ordered, false); if (DEBUG_BROADCAST) Log.v( TAG, "Enqueueing ordered broadcast " + r + ": prev had " + mOrderedBroadcasts.size()); @@ -11298,10 +11939,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } synchronized(this) { + int flags = intent.getFlags(); + if (!mSystemReady) { // if the caller really truly claims to know what they're doing, go // ahead and allow the broadcast without launching any receivers - int flags = intent.getFlags(); if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) != 0) { intent = new Intent(intent); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); @@ -11312,6 +11954,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + if ((flags&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { + throw new IllegalArgumentException( + "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); + } + final ProcessRecord callerApp = getRecordForAppLocked(caller); final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); @@ -11571,15 +12218,15 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } static void performReceive(ProcessRecord app, IIntentReceiver receiver, - Intent intent, int resultCode, String data, - Bundle extras, boolean ordered) throws RemoteException { + Intent intent, int resultCode, String data, Bundle extras, + boolean ordered, boolean sticky) throws RemoteException { if (app != null && app.thread != null) { // If we have an app thread, do the call through that so it is // correctly ordered with other one-way calls. app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode, - data, extras, ordered); + data, extras, ordered, sticky); } else { - receiver.performReceive(intent, resultCode, data, extras, ordered); + receiver.performReceive(intent, resultCode, data, extras, ordered, sticky); } } @@ -11643,7 +12290,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } performReceive(filter.receiverList.app, filter.receiverList.receiver, new Intent(r.intent), r.resultCode, - r.resultData, r.resultExtras, r.ordered); + r.resultData, r.resultExtras, r.ordered, r.sticky); if (ordered) { r.state = BroadcastRecord.CALL_DONE_RECEIVE; } @@ -11698,8 +12345,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // broadcast, then do nothing at this point. Just in case, we // check that the process we're waiting for still exists. if (mPendingBroadcast != null) { - Log.i(TAG, "processNextBroadcast: waiting for " - + mPendingBroadcast.curApp); + if (DEBUG_BROADCAST_LIGHT) { + Log.v(TAG, "processNextBroadcast: waiting for " + + mPendingBroadcast.curApp); + } boolean isDead; synchronized (mPidsSelfLocked) { @@ -11774,7 +12423,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } performReceive(r.callerApp, r.resultTo, new Intent(r.intent), r.resultCode, - r.resultData, r.resultExtras, false); + r.resultData, r.resultExtras, false, false); } catch (RemoteException e) { Log.w(TAG, "Failure sending broadcast result of " + r.intent, e); } @@ -11906,12 +12555,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // restart the application. } - // Not running -- get it started, and enqueue this history record - // to be executed when the app comes up. + // Not running -- get it started, to be executed when the app comes up. if ((r.curApp=startProcessLocked(targetProcess, info.activityInfo.applicationInfo, true, r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, - "broadcast", r.curComponent)) == null) { + "broadcast", r.curComponent, + (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0)) + == null) { // Ah, this recipient is unavailable. Finish it if necessary, // and mark the broadcast record as ready for the next. Log.w(TAG, "Unable to launch app " @@ -12119,7 +12769,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Configuration newConfig = new Configuration(mConfiguration); changes = newConfig.updateFrom(values); if (changes != 0) { - if (DEBUG_SWITCH) { + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) { Log.i(TAG, "Updating configuration to: " + values); } @@ -12132,6 +12782,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } mConfiguration = newConfig; + Log.i(TAG, "Config changed: " + newConfig); Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG); msg.obj = new Configuration(mConfiguration); @@ -12142,6 +12793,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ProcessRecord app = mLRUProcesses.get(i); try { if (app.thread != null) { + if (DEBUG_CONFIGURATION) Log.v(TAG, "Sending to proc " + + app.processName + " new config " + mConfiguration); app.thread.scheduleConfigurationChanged(mConfiguration); } } catch (Exception e) { @@ -12211,6 +12864,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (andResume) { r.results = null; r.newIntents = null; + reportResumedActivityLocked(r); } return true; @@ -12225,19 +12879,21 @@ public final class ActivityManagerService extends ActivityManagerNative implemen */ private final boolean ensureActivityConfigurationLocked(HistoryRecord r, int globalChanges) { - if (DEBUG_SWITCH) Log.i(TAG, "Ensuring correct configuration: " + r); + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, + "Ensuring correct configuration: " + r); // Short circuit: if the two configurations are the exact same // object (the common case), then there is nothing to do. Configuration newConfig = mConfiguration; if (r.configuration == newConfig) { - if (DEBUG_SWITCH) Log.i(TAG, "Configuration unchanged in " + r); + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, + "Configuration unchanged in " + r); return true; } // We don't worry about activities that are finishing. if (r.finishing) { - if (DEBUG_SWITCH) Log.i(TAG, + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, "Configuration doesn't matter in finishing " + r); r.stopFreezingScreenLocked(false); return true; @@ -12251,7 +12907,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // If the activity isn't currently running, just leave the new // configuration and it will pick that up next time it starts. if (r.app == null || r.app.thread == null) { - if (DEBUG_SWITCH) Log.i(TAG, + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, "Configuration doesn't matter not running " + r); r.stopFreezingScreenLocked(false); return true; @@ -12263,22 +12919,26 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Figure out what has changed between the two configurations. int changes = oldConfig.diff(newConfig); - if (DEBUG_SWITCH) { - Log.i(TAG, "Checking to restart " + r.info.name + ": changed=0x" + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) { + Log.v(TAG, "Checking to restart " + r.info.name + ": changed=0x" + Integer.toHexString(changes) + ", handles=0x" - + Integer.toHexString(r.info.configChanges)); + + Integer.toHexString(r.info.configChanges) + + ", newConfig=" + newConfig); } if ((changes&(~r.info.configChanges)) != 0) { // Aha, the activity isn't handling the change, so DIE DIE DIE. r.configChangeFlags |= changes; r.startFreezingScreenLocked(r.app, globalChanges); if (r.app == null || r.app.thread == null) { - if (DEBUG_SWITCH) Log.i(TAG, "Switch is destroying non-running " + r); + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, + "Switch is destroying non-running " + r); destroyActivityLocked(r, true); } else if (r.state == ActivityState.PAUSING) { // A little annoying: we are waiting for this activity to // finish pausing. Let's not do anything now, but just // flag that it needs to be restarted when done pausing. + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, + "Switch is skipping already pausing " + r); r.configDestroy = true; return true; } else if (r.state == ActivityState.RESUMED) { @@ -12286,11 +12946,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // and we need to restart the top, resumed activity. // Instead of doing the normal handshaking, just say // "restart!". - if (DEBUG_SWITCH) Log.i(TAG, "Switch is restarting resumed " + r); + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, + "Switch is restarting resumed " + r); relaunchActivityLocked(r, r.configChangeFlags, true); r.configChangeFlags = 0; } else { - if (DEBUG_SWITCH) Log.i(TAG, "Switch is restarting non-resumed " + r); + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, + "Switch is restarting non-resumed " + r); relaunchActivityLocked(r, r.configChangeFlags, false); r.configChangeFlags = 0; } @@ -12308,6 +12970,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // it last got. if (r.app != null && r.app.thread != null) { try { + if (DEBUG_CONFIGURATION) Log.v(TAG, "Sending new config to " + r); r.app.thread.scheduleActivityConfigurationChanged(r); } catch (RemoteException e) { // If process died, whatever. @@ -12360,6 +13023,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return (app.curAdj=app.maxAdj); } + app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN; app.adjSource = null; app.adjTarget = null; @@ -12440,15 +13104,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } if (app.services.size() != 0 && adj > FOREGROUND_APP_ADJ) { - // If this process has active services running in it, we would - // like to avoid killing it unless it would prevent the current - // application from running. By default we put the process in - // with the rest of the background processes; as we scan through - // its services we may bump it up from there. - if (adj > hiddenAdj) { - adj = hiddenAdj; - app.adjType = "bg-services"; - } final long now = SystemClock.uptimeMillis(); // This process is more important if the top activity is // bound to the service. @@ -12493,6 +13148,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen adj = clientAdj > VISIBLE_APP_ADJ ? clientAdj : VISIBLE_APP_ADJ; app.adjType = "service"; + app.adjTypeCode = ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE; app.adjSource = cr.binding.client; app.adjTarget = s.serviceInfo.name; } @@ -12506,23 +13163,27 @@ public final class ActivityManagerService extends ActivityManagerNative implemen || a.state == ActivityState.PAUSING)) { adj = FOREGROUND_APP_ADJ; app.adjType = "service"; + app.adjTypeCode = ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE; app.adjSource = a; app.adjTarget = s.serviceInfo.name; } } } } + + // Finally, f this process has active services running in it, we + // would like to avoid killing it unless it would prevent the current + // application from running. By default we put the process in + // with the rest of the background processes; as we scan through + // its services we may bump it up from there. + if (adj > hiddenAdj) { + adj = hiddenAdj; + app.adjType = "bg-services"; + } } if (app.pubProviders.size() != 0 && adj > FOREGROUND_APP_ADJ) { - // If this process has published any content providers, then - // its adjustment makes it at least as important as any of the - // processes using those providers, and no less important than - // CONTENT_PROVIDER_ADJ, which is just shy of EMPTY. - if (adj > CONTENT_PROVIDER_ADJ) { - adj = CONTENT_PROVIDER_ADJ; - app.adjType = "pub-providers"; - } Iterator jt = app.pubProviders.values().iterator(); while (jt.hasNext() && adj > FOREGROUND_APP_ADJ) { ContentProviderRecord cpr = (ContentProviderRecord)jt.next(); @@ -12548,6 +13209,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen adj = clientAdj > FOREGROUND_APP_ADJ ? clientAdj : FOREGROUND_APP_ADJ; app.adjType = "provider"; + app.adjTypeCode = ActivityManager.RunningAppProcessInfo + .REASON_PROVIDER_IN_USE; app.adjSource = client; app.adjTarget = cpr.info.name; } @@ -12564,6 +13227,15 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } } + + // Finally, if this process has published any content providers, + // then its adjustment makes it at least as important as any of the + // processes using those providers, and no less important than + // CONTENT_PROVIDER_ADJ, which is just shy of EMPTY. + if (adj > CONTENT_PROVIDER_ADJ) { + adj = CONTENT_PROVIDER_ADJ; + app.adjType = "pub-providers"; + } } app.curRawAdj = adj; diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index c834b34..d59aead 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -16,6 +16,7 @@ package com.android.server.am; +import android.bluetooth.BluetoothHeadset; import android.content.Context; import android.os.Binder; import android.os.IBinder; @@ -23,11 +24,11 @@ import android.os.Parcel; import android.os.Process; import android.os.ServiceManager; import android.telephony.SignalStrength; -import android.telephony.TelephonyManager; import android.util.Log; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BatteryStatsImpl; +import com.android.internal.os.PowerProfile; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -49,6 +50,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub { public void publish(Context context) { mContext = context; ServiceManager.addService("batteryinfo", asBinder()); + mStats.setNumSpeedSteps(new PowerProfile(mContext).getNumSpeedSteps()); + mStats.setRadioScanningTimeout(mContext.getResources().getInteger( + com.android.internal.R.integer.config_radioScanningTimeout) + * 1000L); } public void shutdown() { @@ -193,10 +198,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub { } } - public void noteAirplaneMode(boolean airplaneMode) { + public void notePhoneState(int state) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteAirplaneModeLocked(airplaneMode); + mStats.notePhoneStateLocked(state); } } @@ -258,8 +263,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub { public void noteBluetoothOn() { enforceCallingPermission(); + BluetoothHeadset headset = new BluetoothHeadset(mContext, null); synchronized (mStats) { mStats.noteBluetoothOnLocked(); + mStats.setBtHeadset(headset); } } diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java index da55049..db0a6cb 100644 --- a/services/java/com/android/server/am/BroadcastRecord.java +++ b/services/java/com/android/server/am/BroadcastRecord.java @@ -39,7 +39,9 @@ class BroadcastRecord extends Binder { final String callerPackage; // who sent this final int callingPid; // the pid of who sent this final int callingUid; // the uid of who sent this - String requiredPermission; // a permission the caller has required + final boolean ordered; // serialize the send to receivers? + final boolean sticky; // originated from existing sticky data? + final String requiredPermission; // a permission the caller has required final List receivers; // contains BroadcastFilter and ResolveInfo final IIntentReceiver resultTo; // who receives final result if non-null long dispatchTime; // when dispatch started on this set of receivers @@ -48,7 +50,6 @@ class BroadcastRecord extends Binder { String resultData; // current result data value. Bundle resultExtras; // current result extra data values. boolean resultAbort; // current result abortBroadcast value. - boolean ordered; // serialize the send to receivers? int nextReceiver; // next receiver to be executed. IBinder receiver; // who is currently running, null if none. int state; @@ -86,7 +87,7 @@ class BroadcastRecord extends Binder { + " resultCode=" + resultCode + " resultData=" + resultData); pw.println(prefix + "resultExtras=" + resultExtras); pw.println(prefix + "resultAbort=" + resultAbort - + " ordered=" + ordered); + + " ordered=" + ordered + " sticky=" + sticky); pw.println(prefix + "nextReceiver=" + nextReceiver + " receiver=" + receiver); pw.println(prefix + "curFilter=" + curFilter); @@ -122,7 +123,8 @@ class BroadcastRecord extends Binder { BroadcastRecord(Intent _intent, ProcessRecord _callerApp, String _callerPackage, int _callingPid, int _callingUid, String _requiredPermission, List _receivers, IIntentReceiver _resultTo, int _resultCode, - String _resultData, Bundle _resultExtras, boolean _serialized) { + String _resultData, Bundle _resultExtras, boolean _serialized, + boolean _sticky) { intent = _intent; callerApp = _callerApp; callerPackage = _callerPackage; @@ -135,6 +137,7 @@ class BroadcastRecord extends Binder { resultData = _resultData; resultExtras = _resultExtras; ordered = _serialized; + sticky = _sticky; nextReceiver = 0; state = IDLE; } diff --git a/services/java/com/android/server/am/ConnectionRecord.java b/services/java/com/android/server/am/ConnectionRecord.java index b3343dd..f613b00 100644 --- a/services/java/com/android/server/am/ConnectionRecord.java +++ b/services/java/com/android/server/am/ConnectionRecord.java @@ -17,6 +17,7 @@ package com.android.server.am; import android.app.IServiceConnection; +import android.app.PendingIntent; import java.io.PrintWriter; @@ -28,6 +29,8 @@ class ConnectionRecord { final HistoryRecord activity; // If non-null, the owning activity. final IServiceConnection conn; // The client connection. final int flags; // Binding options. + final int clientLabel; // String resource labeling this client. + final PendingIntent clientIntent; // How to launch the client. String stringName; // Caching of toString. void dump(PrintWriter pw, String prefix) { @@ -40,11 +43,14 @@ class ConnectionRecord { } ConnectionRecord(AppBindRecord _binding, HistoryRecord _activity, - IServiceConnection _conn, int _flags) { + IServiceConnection _conn, int _flags, + int _clientLabel, PendingIntent _clientIntent) { binding = _binding; activity = _activity; conn = _conn; flags = _flags; + clientLabel = _clientLabel; + clientIntent = _clientIntent; } public String toString() { diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index fa2a100..b3086d5 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -22,6 +22,7 @@ import android.content.IIntentReceiver; import android.app.PendingIntent; import android.content.Intent; import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -172,6 +173,14 @@ class PendingIntentRecord extends IIntentSender.Stub { public int send(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver) { + return sendInner(code, intent, resolvedType, finishedReceiver, + null, null, 0, 0, 0); + } + + int sendInner(int code, Intent intent, String resolvedType, + IIntentReceiver finishedReceiver, + IBinder resultTo, String resultWho, int requestCode, + int flagsMask, int flagsValues) { synchronized(owner) { if (!canceled) { sent = true; @@ -189,6 +198,9 @@ class PendingIntentRecord extends IIntentSender.Stub { } else { resolvedType = key.requestResolvedType; } + flagsMask &= ~Intent.IMMUTABLE_FLAGS; + flagsValues &= flagsMask; + finalIntent.setFlags((finalIntent.getFlags()&~flagsMask) | flagsValues); final long origId = Binder.clearCallingIdentity(); @@ -198,7 +210,7 @@ class PendingIntentRecord extends IIntentSender.Stub { try { owner.startActivityInPackage(uid, finalIntent, resolvedType, - null, null, 0, false); + resultTo, resultWho, requestCode, false); } catch (RuntimeException e) { Log.w(ActivityManagerService.TAG, "Unable to send startActivity intent", e); @@ -236,7 +248,7 @@ class PendingIntentRecord extends IIntentSender.Stub { if (sendFinish) { try { finishedReceiver.performReceive(new Intent(finalIntent), 0, - null, null, false); + null, null, false, false); } catch (RemoteException e) { } } @@ -246,7 +258,7 @@ class PendingIntentRecord extends IIntentSender.Stub { return 0; } } - return -1; + return IActivityManager.START_CANCELED; } protected void finalize() throws Throwable { diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index 76fdf09..6202257 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -75,6 +75,7 @@ class ProcessRecord implements Watchdog.PssRequestor { boolean reportLowMemory; // Set to true when waiting to report low mem int lastPss; // Last pss size reported by app. String adjType; // Debugging: primary thing impacting oom_adj. + int adjTypeCode; // Debugging: adj code to report to app. Object adjSource; // Debugging: option dependent object. Object adjTarget; // Debugging: target component impacting oom_adj. @@ -93,7 +94,8 @@ class ProcessRecord implements Watchdog.PssRequestor { // class (String) -> ContentProviderRecord final HashMap pubProviders = new HashMap(); // All ContentProviderRecord process is using - final HashSet conProviders = new HashSet(); + final HashMap<ContentProviderRecord, Integer> conProviders + = new HashMap<ContentProviderRecord, Integer>(); boolean persistent; // always keep this application running? boolean crashing; // are we in the process of crashing? diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index 98df4b3..2534410 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -18,12 +18,16 @@ package com.android.server.am; import com.android.internal.os.BatteryStatsImpl; +import android.app.INotificationManager; +import android.app.Notification; +import android.app.NotificationManager; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.IBinder; +import android.os.RemoteException; import android.os.SystemClock; import java.io.PrintWriter; @@ -60,13 +64,38 @@ class ServiceRecord extends Binder { final HashMap<IBinder, ConnectionRecord> connections = new HashMap<IBinder, ConnectionRecord>(); // IBinder -> ConnectionRecord of all bound clients - final List<Intent> startArgs = new ArrayList<Intent>(); + + // Maximum number of delivery attempts before giving up. + static final int MAX_DELIVERY_COUNT = 3; + + // Maximum number of times it can fail during execution before giving up. + static final int MAX_DONE_EXECUTING_COUNT = 6; + + static class StartItem { + final int id; + final Intent intent; + long deliveredTime; + int deliveryCount; + int doneExecutingCount; + + StartItem(int _id, Intent _intent) { + id = _id; + intent = _intent; + } + } + final ArrayList<StartItem> deliveredStarts = new ArrayList<StartItem>(); + // start() arguments which been delivered. + final ArrayList<StartItem> pendingStarts = new ArrayList<StartItem>(); // start() arguments that haven't yet been delivered. - ProcessRecord app; // where this service is running or null. - boolean isForeground; // asked to run as a foreground service? + ProcessRecord app; // where this service is running or null. + boolean isForeground; // is service currently in foreground mode? + int foregroundId; // Notification ID of last foreground req. + Notification foregroundNoti; // Notification record of foreground state. long lastActivity; // last time there was some activity on the service. boolean startRequested; // someone explicitly called start? + boolean stopIfKilled; // last onStart() said to stop if service killed? + boolean callStart; // last onStart() has asked to alway be called on restart. int lastStartId; // identifier of most recent start request. int executeNesting; // number of outstanding operations keeping foreground. long executingStart; // start time of last execute request. @@ -79,6 +108,25 @@ class ServiceRecord extends Binder { String stringName; // caching of toString + void dumpStartList(PrintWriter pw, String prefix, List<StartItem> list, long now) { + final int N = list.size(); + for (int i=0; i<N; i++) { + StartItem si = list.get(i); + pw.print(prefix); pw.print("#"); pw.print(i); + pw.print(" id="); pw.print(si.id); + if (now != 0) pw.print(" dur="); pw.print(now-si.deliveredTime); + if (si.deliveryCount != 0) { + pw.print(" dc="); pw.print(si.deliveryCount); + } + if (si.doneExecutingCount != 0) { + pw.print(" dxc="); pw.print(si.doneExecutingCount); + } + pw.print(" "); + if (si.intent != null) pw.println(si.intent.toString()); + else pw.println("null"); + } + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("intent={"); pw.print(intent.getIntent().toShortString(true, false)); @@ -93,20 +141,39 @@ class ServiceRecord extends Binder { if (!resDir.equals(baseDir)) pw.print(" resDir="); pw.print(resDir); pw.print(" dataDir="); pw.println(dataDir); pw.print(prefix); pw.print("app="); pw.println(app); - pw.print(prefix); pw.print("isForeground="); pw.print(isForeground); - pw.print(" lastActivity="); pw.println(lastActivity-now); - pw.print(prefix); pw.print("startRequested="); pw.print(startRequested); - pw.print(" startId="); pw.print(lastStartId); - pw.print(" executeNesting="); pw.print(executeNesting); + if (isForeground || foregroundId != 0) { + pw.print(prefix); pw.print("isForeground="); pw.print(isForeground); + pw.print(" foregroundId="); pw.print(foregroundId); + pw.print(" foregroundNoti="); pw.println(foregroundNoti); + } + pw.print(prefix); pw.print("lastActivity="); pw.print(lastActivity-now); pw.print(" executingStart="); pw.print(executingStart-now); - pw.print(" crashCount="); pw.println(crashCount); - pw.print(prefix); pw.print("totalRestartCount="); pw.print(totalRestartCount); - pw.print(" restartCount="); pw.print(restartCount); - pw.print(" restartDelay="); pw.print(restartDelay); - pw.print(" restartTime="); pw.print(restartTime-now); - pw.print(" nextRestartTime="); pw.println(nextRestartTime-now); + pw.print(" restartTime="); pw.println(restartTime); + if (startRequested || lastStartId != 0) { + pw.print(prefix); pw.print("startRequested="); pw.print(startRequested); + pw.print(" stopIfKilled="); pw.print(stopIfKilled); + pw.print(" callStart="); pw.print(callStart); + pw.print(" lastStartId="); pw.println(lastStartId); + } + if (executeNesting != 0 || crashCount != 0 || restartCount != 0 + || restartDelay != 0 || nextRestartTime != 0) { + pw.print(prefix); pw.print("executeNesting="); pw.print(executeNesting); + pw.print(" restartCount="); pw.print(restartCount); + pw.print(" restartDelay="); pw.print(restartDelay-now); + pw.print(" nextRestartTime="); pw.print(nextRestartTime-now); + pw.print(" crashCount="); pw.println(crashCount); + } + if (deliveredStarts.size() > 0) { + pw.print(prefix); pw.println("Delivered Starts:"); + dumpStartList(pw, prefix, deliveredStarts, SystemClock.uptimeMillis()); + } + if (pendingStarts.size() > 0) { + pw.print(prefix); pw.println("Pending Starts:"); + dumpStartList(pw, prefix, pendingStarts, 0); + } if (bindings.size() > 0) { Iterator<IntentBindRecord> it = bindings.values().iterator(); + pw.print(prefix); pw.println("Bindings:"); while (it.hasNext()) { IntentBindRecord b = it.next(); pw.print(prefix); pw.print("* IntentBindRecord{"); @@ -167,6 +234,45 @@ class ServiceRecord extends Binder { restartTime = 0; } + public StartItem findDeliveredStart(int id, boolean remove) { + final int N = deliveredStarts.size(); + for (int i=0; i<N; i++) { + StartItem si = deliveredStarts.get(i); + if (si.id == id) { + if (remove) deliveredStarts.remove(i); + return si; + } + } + + return null; + } + + public void postNotification() { + if (foregroundId != 0 && foregroundNoti != null) { + INotificationManager inm = NotificationManager.getService(); + if (inm != null) { + try { + int[] outId = new int[1]; + inm.enqueueNotification(packageName, foregroundId, + foregroundNoti, outId); + } catch (RemoteException e) { + } + } + } + } + + public void cancelNotification() { + if (foregroundId != 0) { + INotificationManager inm = NotificationManager.getService(); + if (inm != null) { + try { + inm.cancelNotification(packageName, foregroundId); + } catch (RemoteException e) { + } + } + } + } + public String toString() { if (stringName != null) { return stringName; diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java index d458911..373b44e 100755..100644 --- a/services/java/com/android/server/am/UsageStatsService.java +++ b/services/java/com/android/server/am/UsageStatsService.java @@ -381,7 +381,10 @@ public final class UsageStatsService extends IUsageStats.Stub { mFileLeaf = getCurrentDateStr(FILE_PREFIX); // Copy current file to back up File backupFile = new File(mFile.getPath() + ".bak"); - mFile.renameTo(backupFile); + if (!mFile.renameTo(backupFile)) { + Log.w(TAG, "Failed to persist new stats"); + return; + } try { // Write mStats to file writeStatsFLOCK(); @@ -695,7 +698,14 @@ public final class UsageStatsService extends IUsageStats.Stub { if (NC > 0) { for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) { sb.append("A:"); - sb.append(ent.getKey()); + String activity = ent.getKey(); + if (activity.startsWith(pkgName)) { + sb.append('*'); + sb.append(activity.substring( + pkgName.length(), activity.length())); + } else { + sb.append(activity); + } TimeStats times = ent.getValue(); sb.append(','); sb.append(times.count); diff --git a/services/java/com/android/server/status/NotificationData.java b/services/java/com/android/server/status/NotificationData.java index 63a7d70..0a3411a 100644 --- a/services/java/com/android/server/status/NotificationData.java +++ b/services/java/com/android/server/status/NotificationData.java @@ -5,6 +5,7 @@ import android.widget.RemoteViews; public class NotificationData { public String pkg; + public String tag; public int id; public CharSequence tickerText; @@ -17,9 +18,6 @@ public class NotificationData { public PendingIntent deleteIntent; - public NotificationData() { - } - public String toString() { return "NotificationData(package=" + pkg + " tickerText=" + tickerText + " ongoingEvent=" + ongoingEvent + " contentIntent=" + contentIntent diff --git a/services/java/com/android/server/status/StatusBarIcon.java b/services/java/com/android/server/status/StatusBarIcon.java index 6d09919..857784b 100644 --- a/services/java/com/android/server/status/StatusBarIcon.java +++ b/services/java/com/android/server/status/StatusBarIcon.java @@ -149,6 +149,11 @@ class StatusBarIcon { r = context.getResources(); } + if (data.iconId == 0) { + Log.w(StatusBarService.TAG, "No icon ID for slot " + data.slot); + return null; + } + try { return r.getDrawable(data.iconId); } catch (RuntimeException e) { diff --git a/services/java/com/android/server/status/StatusBarPolicy.java b/services/java/com/android/server/status/StatusBarPolicy.java index a4b47b5..2b9d18f 100644 --- a/services/java/com/android/server/status/StatusBarPolicy.java +++ b/services/java/com/android/server/status/StatusBarPolicy.java @@ -18,10 +18,9 @@ package com.android.server.status; import android.app.AlertDialog; import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothError; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothIntent; +import android.bluetooth.BluetoothPbap; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -76,15 +75,8 @@ public class StatusBarPolicy { private static StatusBarPolicy sInstance; // message codes for the handler - private static final int EVENT_DATA_CONN_STATE_CHANGED = 2; - private static final int EVENT_DATA_ACTIVITY = 3; private static final int EVENT_BATTERY_CLOSE = 4; - // indices into mBatteryThresholds - private static final int BATTERY_THRESHOLD_CLOSE_WARNING = 0; - private static final int BATTERY_THRESHOLD_WARNING = 1; - private static final int BATTERY_THRESHOLD_EMPTY = 2; - private final Context mContext; private final StatusBarService mService; private final Handler mHandler = new StatusBarHandler(); @@ -99,26 +91,21 @@ public class StatusBarPolicy { private IBinder mBatteryIcon; private IconData mBatteryData; private boolean mBatteryFirst = true; - private int mBatteryPlugged; + private boolean mBatteryPlugged; private int mBatteryLevel; - private int mBatteryThreshold = 0; // index into mBatteryThresholds - private int[] mBatteryThresholds = new int[] { 20, 15, -1 }; private AlertDialog mLowBatteryDialog; private TextView mBatteryLevelTextView; private View mBatteryView; private int mBatteryViewSequence; private boolean mBatteryShowLowOnEndCall = false; - private boolean mSentLowBatteryBroadcast = false; private static final boolean SHOW_LOW_BATTERY_WARNING = true; // phone private TelephonyManager mPhone; private IBinder mPhoneIcon; - private IBinder mPhoneEvdoIcon; //***** Signal strength icons private IconData mPhoneData; - private IconData mPhoneEvdoData; //GSM/UMTS private static final int[] sSignalImages = new int[] { com.android.internal.R.drawable.stat_sys_signal_0, @@ -134,14 +121,6 @@ public class StatusBarPolicy { com.android.internal.R.drawable.stat_sys_r_signal_3, com.android.internal.R.drawable.stat_sys_r_signal_4 }; - //CDMA - private static final int[] sSignalImages_cdma = new int[] { - com.android.internal.R.drawable.stat_sys_signal_cdma_0, - com.android.internal.R.drawable.stat_sys_signal_cdma_1, - com.android.internal.R.drawable.stat_sys_signal_cdma_2, - com.android.internal.R.drawable.stat_sys_signal_cdma_3, - com.android.internal.R.drawable.stat_sys_signal_cdma_4 - }; private static final int[] sRoamingIndicatorImages_cdma = new int[] { com.android.internal.R.drawable.stat_sys_roaming_cdma_0, //Standard Roaming Indicator // 1 is Standard Roaming Indicator OFF @@ -241,14 +220,6 @@ public class StatusBarPolicy { // 128-255 Reserved }; - // EVDO - private static final int[] sSignalImages_evdo = new int[] { - com.android.internal.R.drawable.stat_sys_signal_evdo_0, - com.android.internal.R.drawable.stat_sys_signal_evdo_1, - com.android.internal.R.drawable.stat_sys_signal_evdo_2, - com.android.internal.R.drawable.stat_sys_signal_evdo_3, - com.android.internal.R.drawable.stat_sys_signal_evdo_4 - }; //***** Data connection icons private int[] mDataIconList = sDataNetType_g; @@ -271,20 +242,21 @@ public class StatusBarPolicy { com.android.internal.R.drawable.stat_sys_data_out_e, com.android.internal.R.drawable.stat_sys_data_inandout_e, }; - //CDMA - private static final int[] sDataNetType_evdo = new int[] { - com.android.internal.R.drawable.stat_sys_data_connected_evdo, - com.android.internal.R.drawable.stat_sys_data_in_evdo, - com.android.internal.R.drawable.stat_sys_data_out_evdo, - com.android.internal.R.drawable.stat_sys_data_inandout_evdo, - com.android.internal.R.drawable.stat_sys_data_dormant_evdo, + //3.5G + private static final int[] sDataNetType_h = new int[] { + com.android.internal.R.drawable.stat_sys_data_connected_h, + com.android.internal.R.drawable.stat_sys_data_in_h, + com.android.internal.R.drawable.stat_sys_data_out_h, + com.android.internal.R.drawable.stat_sys_data_inandout_h, }; - private static final int[] sDataNetType_1xrtt = new int[] { - com.android.internal.R.drawable.stat_sys_data_connected_1xrtt, - com.android.internal.R.drawable.stat_sys_data_in_1xrtt, - com.android.internal.R.drawable.stat_sys_data_out_1xrtt, - com.android.internal.R.drawable.stat_sys_data_inandout_1xrtt, - com.android.internal.R.drawable.stat_sys_data_dormant_1xrtt, + + //CDMA + // Use 3G icons for EVDO data and 1x icons for 1XRTT data + private static final int[] sDataNetType_1x = new int[] { + com.android.internal.R.drawable.stat_sys_data_connected_1x, + com.android.internal.R.drawable.stat_sys_data_in_1x, + com.android.internal.R.drawable.stat_sys_data_out_1x, + com.android.internal.R.drawable.stat_sys_data_inandout_1x, }; // Assume it's all good unless we hear otherwise. We don't always seem @@ -300,6 +272,7 @@ public class StatusBarPolicy { private IBinder mDataIcon; private IconData mDataData; private boolean mDataIconVisible; + private boolean mHspaDataDistinguishable; // ringer volume private IBinder mVolumeIcon; @@ -311,6 +284,7 @@ public class StatusBarPolicy { private IconData mBluetoothData; private int mBluetoothHeadsetState; private int mBluetoothA2dpState; + private int mBluetoothPbapState; private boolean mBluetoothEnabled; // wifi @@ -363,6 +337,9 @@ public class StatusBarPolicy { else if (action.equals(Intent.ACTION_TIME_CHANGED)) { updateClock(); } + else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { + updateBattery(intent); + } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { updateClock(); } @@ -377,12 +354,17 @@ public class StatusBarPolicy { else if (action.equals(Intent.ACTION_SYNC_STATE_CHANGED)) { updateSyncState(intent); } - else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { - updateBattery(intent); + else if (action.equals(Intent.ACTION_BATTERY_LOW)) { + onBatteryLow(intent); } - else if (action.equals(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION) || - action.equals(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION) || - action.equals(BluetoothA2dp.SINK_STATE_CHANGED_ACTION)) { + else if (action.equals(Intent.ACTION_BATTERY_OKAY) + || action.equals(Intent.ACTION_POWER_CONNECTED)) { + onBatteryOkay(intent); + } + else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED) || + action.equals(BluetoothHeadset.ACTION_STATE_CHANGED) || + action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED) || + action.equals(BluetoothPbap.PBAP_STATE_CHANGED_ACTION)) { updateBluetooth(intent); } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION) || @@ -430,12 +412,6 @@ public class StatusBarPolicy { null, com.android.internal.R.drawable.stat_sys_signal_null, 0, 0); mPhoneIcon = service.addIcon(mPhoneData, null); - // phone_evdo_signal - mPhoneEvdoData = IconData.makeIcon("phone_evdo_signal", - null, com.android.internal.R.drawable.stat_sys_signal_evdo_0, 0, 0); - mPhoneEvdoIcon = service.addIcon(mPhoneEvdoData, null); - service.setIconVisibility(mPhoneEvdoIcon, false); - // register for phone state notifications. ((TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE)) .listen(mPhoneStateListener, @@ -473,15 +449,15 @@ public class StatusBarPolicy { mBluetoothData = IconData.makeIcon("bluetooth", null, com.android.internal.R.drawable.stat_sys_data_bluetooth, 0, 0); mBluetoothIcon = service.addIcon(mBluetoothData, null); - BluetoothDevice bluetooth = - (BluetoothDevice) mContext.getSystemService(Context.BLUETOOTH_SERVICE); - if (bluetooth != null) { - mBluetoothEnabled = bluetooth.isEnabled(); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + mBluetoothEnabled = adapter.isEnabled(); } else { mBluetoothEnabled = false; } mBluetoothA2dpState = BluetoothA2dp.STATE_DISCONNECTED; mBluetoothHeadsetState = BluetoothHeadset.STATE_DISCONNECTED; + mBluetoothPbapState = BluetoothPbap.STATE_DISCONNECTED; mService.setIconVisibility(mBluetoothIcon, mBluetoothEnabled); // Gps status @@ -490,7 +466,7 @@ public class StatusBarPolicy { mGpsFixIconData = IconData.makeIcon("gps", null, com.android.internal.R.drawable.stat_sys_gps_on, 0, 0); mGpsIcon = service.addIcon(mGpsEnabledIconData, null); - service.setIconVisibility(mGpsIcon, false); + service.setIconVisibility(mGpsIcon, false); // Alarm clock mAlarmClockIconData = IconData.makeIcon( @@ -513,7 +489,7 @@ public class StatusBarPolicy { mVolumeIcon = service.addIcon(mVolumeData, null); service.setIconVisibility(mVolumeIcon, false); updateVolume(); - + IntentFilter filter = new IntentFilter(); // Register for Intent broadcasts for... @@ -521,14 +497,18 @@ public class StatusBarPolicy { filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); filter.addAction(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(Intent.ACTION_BATTERY_LOW); + filter.addAction(Intent.ACTION_BATTERY_OKAY); + filter.addAction(Intent.ACTION_POWER_CONNECTED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); filter.addAction(Intent.ACTION_ALARM_CHANGED); filter.addAction(Intent.ACTION_SYNC_STATE_CHANGED); filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); filter.addAction(AudioManager.VIBRATE_SETTING_CHANGED_ACTION); - filter.addAction(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION); - filter.addAction(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION); - filter.addAction(BluetoothA2dp.SINK_STATE_CHANGED_ACTION); + filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); + filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); + filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); + filter.addAction(BluetoothPbap.PBAP_STATE_CHANGED_ACTION); filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); filter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); @@ -538,6 +518,14 @@ public class StatusBarPolicy { filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION); mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); + + // load config to determine if to distinguish Hspa data icon + try { + mHspaDataDistinguishable = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_hspa_data_distinguishable); + } catch (Exception e) { + mHspaDataDistinguishable = false; + } } public static void installIcons(Context context, StatusBarService service) { @@ -564,38 +552,22 @@ public class StatusBarPolicy { //mService.setIconVisibility(mSyncFailingIcon, isFailing && !isActive); } - private void pickNextBatteryLevel(int level) { - final int N = mBatteryThresholds.length; - for (int i=0; i<N; i++) { - if (level >= mBatteryThresholds[i]) { - mBatteryThreshold = i; - break; - } - } - if (mBatteryThreshold >= N) { - mBatteryThreshold = N-1; - } - } - private final void updateBattery(Intent intent) { mBatteryData.iconId = intent.getIntExtra("icon-small", 0); mBatteryData.iconLevel = intent.getIntExtra("level", 0); mService.updateIcon(mBatteryIcon, mBatteryData, null); - int plugged = intent.getIntExtra("plugged", 0); + boolean plugged = intent.getIntExtra("plugged", 0) != 0; int level = intent.getIntExtra("level", -1); if (false) { Log.d(TAG, "updateBattery level=" + level + " plugged=" + plugged + " mBatteryPlugged=" + mBatteryPlugged + " mBatteryLevel=" + mBatteryLevel - + " mBatteryThreshold=" + mBatteryThreshold + " mBatteryFirst=" + mBatteryFirst); } - int oldPlugged = mBatteryPlugged; - int oldThreshold = mBatteryThreshold; - pickNextBatteryLevel(level); + boolean oldPlugged = mBatteryPlugged; mBatteryPlugged = plugged; mBatteryLevel = level; @@ -617,49 +589,35 @@ public class StatusBarPolicy { } */ if (false) { - Log.d(TAG, "plugged=" + plugged + " oldPlugged=" + oldPlugged + " level=" + level - + " mBatteryThreshold=" + mBatteryThreshold + " oldThreshold=" + oldThreshold); + Log.d(TAG, "plugged=" + plugged + " oldPlugged=" + oldPlugged + " level=" + level); } - if (plugged == 0 - && ((oldPlugged != 0 && level < mBatteryThresholds[BATTERY_THRESHOLD_WARNING]) - || (mBatteryThreshold > oldThreshold - && mBatteryThreshold > BATTERY_THRESHOLD_WARNING))) { - // Broadcast the low battery warning - mSentLowBatteryBroadcast = true; - Intent batIntent = new Intent(Intent.ACTION_BATTERY_LOW); - batIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(batIntent); - - if (SHOW_LOW_BATTERY_WARNING) { - if (false) { - Log.d(TAG, "mPhoneState=" + mPhoneState - + " mLowBatteryDialog=" + mLowBatteryDialog - + " mBatteryShowLowOnEndCall=" + mBatteryShowLowOnEndCall); - } + } - if (mPhoneState == TelephonyManager.CALL_STATE_IDLE) { - showLowBatteryWarning(); - } else { - mBatteryShowLowOnEndCall = true; - } - } - } else if (mBatteryThreshold < BATTERY_THRESHOLD_WARNING) { - if (mSentLowBatteryBroadcast == true) { - mSentLowBatteryBroadcast = false; - Intent batIntent = new Intent(Intent.ACTION_BATTERY_OKAY); - batIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(batIntent); + private void onBatteryLow(Intent intent) { + if (SHOW_LOW_BATTERY_WARNING) { + if (false) { + Log.d(TAG, "mPhoneState=" + mPhoneState + + " mLowBatteryDialog=" + mLowBatteryDialog + + " mBatteryShowLowOnEndCall=" + mBatteryShowLowOnEndCall); } - if (SHOW_LOW_BATTERY_WARNING) { - if (mLowBatteryDialog != null) { - mLowBatteryDialog.dismiss(); - mBatteryShowLowOnEndCall = false; - } + + if (mPhoneState == TelephonyManager.CALL_STATE_IDLE) { + showLowBatteryWarning(); + } else { + mBatteryShowLowOnEndCall = true; } } } - private void showBatteryView() { + private void onBatteryOkay(Intent intent) { + if (mLowBatteryDialog != null + && SHOW_LOW_BATTERY_WARNING) { + mLowBatteryDialog.dismiss(); + mBatteryShowLowOnEndCall = false; + } + } + + private void showBatteryView() { closeLastBatteryView(); if (mLowBatteryDialog != null) { mLowBatteryDialog.dismiss(); @@ -675,15 +633,20 @@ public class StatusBarPolicy { pixelFormat = bg.getOpacity(); } + int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_DIM_BEHIND; + + if (!mContext.getResources().getBoolean( + com.android.internal.R.bool.config_sf_slowBlur)) { + flags |= WindowManager.LayoutParams.FLAG_BLUR_BEHIND; + } + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_TOAST, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_BLUR_BEHIND - | WindowManager.LayoutParams.FLAG_DIM_BEHIND, - pixelFormat); + flags, pixelFormat); // Get the dim amount from the theme TypedArray a = mContext.obtainStyledAttributes( @@ -720,9 +683,9 @@ public class StatusBarPolicy { private void showLowBatteryWarning() { closeLastBatteryView(); - int level = mBatteryThresholds[mBatteryThreshold > 1 ? mBatteryThreshold - 1 : 0]; + // Show exact battery level. CharSequence levelText = mContext.getString( - com.android.internal.R.string.battery_low_percent_format, level); + com.android.internal.R.string.battery_low_percent_format, mBatteryLevel); if (mBatteryLevelTextView != null) { mBatteryLevelTextView.setText(levelText); @@ -738,7 +701,7 @@ public class StatusBarPolicy { b.setView(v); b.setIcon(android.R.drawable.ic_dialog_alert); b.setPositiveButton(android.R.string.ok, null); - + final Intent intent = new Intent(Intent.ACTION_POWER_USAGE_SUMMARY); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK @@ -773,7 +736,7 @@ public class StatusBarPolicy { } if (mPhoneState == TelephonyManager.CALL_STATE_IDLE) { if (mBatteryShowLowOnEndCall) { - if (mBatteryPlugged == 0) { + if (!mBatteryPlugged) { showLowBatteryWarning(); } mBatteryShowLowOnEndCall = false; @@ -819,19 +782,23 @@ public class StatusBarPolicy { public void onServiceStateChanged(ServiceState state) { mServiceState = state; updateSignalStrength(); - updateCdmaRoamingIcon(); + updateCdmaRoamingIcon(state); updateDataIcon(); } @Override public void onCallStateChanged(int state, String incomingNumber) { updateCallState(state); + // In cdma, if a voice call is made, RSSI should switch to 1x. + if (isCdma()) { + updateSignalStrength(); + } } @Override - public void onDataConnectionStateChanged(int state) { + public void onDataConnectionStateChanged(int state, int networkType) { mDataState = state; - updateDataNetType(); + updateDataNetType(networkType); updateDataIcon(); } @@ -854,7 +821,7 @@ public class StatusBarPolicy { final String lockedReason = intent.getStringExtra(IccCard.INTENT_KEY_LOCKED_REASON); if (IccCard.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) { mSimState = IccCard.State.PIN_REQUIRED; - } + } else if (IccCard.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) { mSimState = IccCard.State.PUK_REQUIRED; } @@ -868,7 +835,15 @@ public class StatusBarPolicy { } private boolean isCdma() { - return ((mPhone != null) && (mPhone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA)); + return (mSignalStrength != null) && !mSignalStrength.isGsm(); + } + + private boolean isEvdo() { + return ( (mServiceState != null) + && ((mServiceState.getRadioTechnology() + == ServiceState.RADIO_TECHNOLOGY_EVDO_0) + || (mServiceState.getRadioTechnology() + == ServiceState.RADIO_TECHNOLOGY_EVDO_A))); } private boolean hasService() { @@ -887,9 +862,7 @@ public class StatusBarPolicy { private final void updateSignalStrength() { int iconLevel = -1; - int evdoIconLevel = -1; int[] iconList; - int[] evdoIconList; if (!hasService()) { //Log.d(TAG, "updateSignalStrength: no service"); @@ -900,7 +873,6 @@ public class StatusBarPolicy { mPhoneData.iconId = com.android.internal.R.drawable.stat_sys_signal_null; } mService.updateIcon(mPhoneIcon, mPhoneData, null); - mService.setIconVisibility(mPhoneEvdoIcon,false); return; } @@ -917,73 +889,75 @@ public class StatusBarPolicy { else if (asu >= 4) iconLevel = 2; else iconLevel = 1; + // Though mPhone is a Manager, this call is not an IPC if (mPhone.isNetworkRoaming()) { iconList = sSignalImages_r; } else { iconList = sSignalImages; } } else { - iconList = this.sSignalImages_cdma; - - int cdmaDbm = mSignalStrength.getCdmaDbm(); - int cdmaEcio = mSignalStrength.getCdmaEcio(); - int levelDbm = 0; - int levelEcio = 0; - - if (cdmaDbm >= -75) levelDbm = 4; - else if (cdmaDbm >= -85) levelDbm = 3; - else if (cdmaDbm >= -95) levelDbm = 2; - else if (cdmaDbm >= -100) levelDbm = 1; - else levelDbm = 0; - - // Ec/Io are in dB*10 - if (cdmaEcio >= -90) levelEcio = 4; - else if (cdmaEcio >= -110) levelEcio = 3; - else if (cdmaEcio >= -130) levelEcio = 2; - else if (cdmaEcio >= -150) levelEcio = 1; - else levelEcio = 0; - - iconLevel = (levelDbm < levelEcio) ? levelDbm : levelEcio; - } + iconList = this.sSignalImages; - if ((mServiceState.getRadioTechnology() == ServiceState.RADIO_TECHNOLOGY_EVDO_0) - || (mServiceState.getRadioTechnology() == ServiceState.RADIO_TECHNOLOGY_EVDO_A)) { - // Use Evdo icon - evdoIconList = this.sSignalImages_evdo; - - int evdoEcio = mSignalStrength.getEvdoEcio(); - int evdoSnr = mSignalStrength.getEvdoSnr(); - int levelEvdoEcio = 0; - int levelEvdoSnr = 0; - - // Ec/Io are in dB*10 - if (evdoEcio >= -650) levelEvdoEcio = 4; - else if (evdoEcio >= -750) levelEvdoEcio = 3; - else if (evdoEcio >= -900) levelEvdoEcio = 2; - else if (evdoEcio >= -1050) levelEvdoEcio = 1; - else levelEvdoEcio = 0; - - if (evdoSnr > 7) levelEvdoSnr = 4; - else if (evdoSnr > 5) levelEvdoSnr = 3; - else if (evdoSnr > 3) levelEvdoSnr = 2; - else if (evdoSnr > 1) levelEvdoSnr = 1; - else levelEvdoSnr = 0; - - evdoIconLevel = (levelEvdoEcio < levelEvdoSnr) ? levelEvdoEcio : levelEvdoSnr; - - mPhoneEvdoData.iconId = evdoIconList[evdoIconLevel]; - mService.updateIcon(mPhoneEvdoIcon, mPhoneEvdoData, null); - mService.setIconVisibility(mPhoneEvdoIcon,true); - } else { - mService.setIconVisibility(mPhoneEvdoIcon,false); + // If 3G(EV) and 1x network are available than 3G should be + // displayed, displayed RSSI should be from the EV side. + // If a voice call is made then RSSI should switch to 1x. + if ((mPhoneState == TelephonyManager.CALL_STATE_IDLE) && isEvdo()){ + iconLevel = getEvdoLevel(); + if (false) { + Log.d(TAG, "use Evdo level=" + iconLevel + " to replace Cdma Level=" + getCdmaLevel()); + } + } else { + iconLevel = getCdmaLevel(); + } } - mPhoneData.iconId = iconList[iconLevel]; mService.updateIcon(mPhoneIcon, mPhoneData, null); } - private final void updateDataNetType() { - int net = mPhone.getNetworkType(); + private int getCdmaLevel() { + final int cdmaDbm = mSignalStrength.getCdmaDbm(); + final int cdmaEcio = mSignalStrength.getCdmaEcio(); + int levelDbm = 0; + int levelEcio = 0; + + if (cdmaDbm >= -75) levelDbm = 4; + else if (cdmaDbm >= -85) levelDbm = 3; + else if (cdmaDbm >= -95) levelDbm = 2; + else if (cdmaDbm >= -100) levelDbm = 1; + else levelDbm = 0; + + // Ec/Io are in dB*10 + if (cdmaEcio >= -90) levelEcio = 4; + else if (cdmaEcio >= -110) levelEcio = 3; + else if (cdmaEcio >= -130) levelEcio = 2; + else if (cdmaEcio >= -150) levelEcio = 1; + else levelEcio = 0; + + return (levelDbm < levelEcio) ? levelDbm : levelEcio; + } + + private int getEvdoLevel() { + int evdoDbm = mSignalStrength.getEvdoDbm(); + int evdoSnr = mSignalStrength.getEvdoSnr(); + int levelEvdoDbm = 0; + int levelEvdoSnr = 0; + + if (evdoDbm >= -65) levelEvdoDbm = 4; + else if (evdoDbm >= -75) levelEvdoDbm = 3; + else if (evdoDbm >= -90) levelEvdoDbm = 2; + else if (evdoDbm >= -105) levelEvdoDbm = 1; + else levelEvdoDbm = 0; + + if (evdoSnr >= 7) levelEvdoSnr = 4; + else if (evdoSnr >= 5) levelEvdoSnr = 3; + else if (evdoSnr >= 3) levelEvdoSnr = 2; + else if (evdoSnr >= 1) levelEvdoSnr = 1; + else levelEvdoSnr = 0; + + return (levelEvdoDbm < levelEvdoSnr) ? levelEvdoDbm : levelEvdoSnr; + } + + private final void updateDataNetType(int net) { switch (net) { case TelephonyManager.NETWORK_TYPE_EDGE: @@ -992,16 +966,25 @@ public class StatusBarPolicy { case TelephonyManager.NETWORK_TYPE_UMTS: mDataIconList = sDataNetType_3g; break; + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + if (mHspaDataDistinguishable) { + mDataIconList = sDataNetType_h; + } else { + mDataIconList = sDataNetType_3g; + } + break; case TelephonyManager.NETWORK_TYPE_CDMA: // display 1xRTT for IS95A/B - mDataIconList = this.sDataNetType_1xrtt; + mDataIconList = this.sDataNetType_1x; break; case TelephonyManager.NETWORK_TYPE_1xRTT: - mDataIconList = this.sDataNetType_1xrtt; + mDataIconList = this.sDataNetType_1x; break; case TelephonyManager.NETWORK_TYPE_EVDO_0: //fall through case TelephonyManager.NETWORK_TYPE_EVDO_A: - mDataIconList = sDataNetType_evdo; + mDataIconList = sDataNetType_3g; break; default: mDataIconList = sDataNetType_g; @@ -1054,8 +1037,6 @@ public class StatusBarPolicy { iconId = mDataIconList[3]; break; case TelephonyManager.DATA_ACTIVITY_DORMANT: - iconId = mDataIconList[4]; - break; default: iconId = mDataIconList[0]; break; @@ -1104,23 +1085,26 @@ public class StatusBarPolicy { int iconId = com.android.internal.R.drawable.stat_sys_data_bluetooth; String action = intent.getAction(); - if (action.equals(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION)) { - int state = intent.getIntExtra(BluetoothIntent.BLUETOOTH_STATE, - BluetoothError.ERROR); - mBluetoothEnabled = state == BluetoothDevice.BLUETOOTH_STATE_ON; - } else if (action.equals(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION)) { - mBluetoothHeadsetState = intent.getIntExtra(BluetoothIntent.HEADSET_STATE, + if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); + mBluetoothEnabled = state == BluetoothAdapter.STATE_ON; + } else if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) { + mBluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_ERROR); - } else if (action.equals(BluetoothA2dp.SINK_STATE_CHANGED_ACTION)) { - mBluetoothA2dpState = intent.getIntExtra(BluetoothA2dp.SINK_STATE, + } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { + mBluetoothA2dpState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, BluetoothA2dp.STATE_DISCONNECTED); + } else if (action.equals(BluetoothPbap.PBAP_STATE_CHANGED_ACTION)) { + mBluetoothPbapState = intent.getIntExtra(BluetoothPbap.PBAP_STATE, + BluetoothPbap.STATE_DISCONNECTED); } else { return; } if (mBluetoothHeadsetState == BluetoothHeadset.STATE_CONNECTED || mBluetoothA2dpState == BluetoothA2dp.STATE_CONNECTED || - mBluetoothA2dpState == BluetoothA2dp.STATE_PLAYING) { + mBluetoothA2dpState == BluetoothA2dp.STATE_PLAYING || + mBluetoothPbapState == BluetoothPbap.STATE_CONNECTED) { iconId = com.android.internal.R.drawable.stat_sys_data_bluetooth_connected; } @@ -1213,21 +1197,21 @@ public class StatusBarPolicy { final String action = intent.getAction(); final boolean enabled = intent.getBooleanExtra(TtyIntent.TTY_ENABLED, false); - Log.i(TAG, "updateTTY: enabled: " + enabled); + if (false) Log.v(TAG, "updateTTY: enabled: " + enabled); if (enabled) { // TTY is on - Log.i(TAG, "updateTTY: set TTY on"); + if (false) Log.v(TAG, "updateTTY: set TTY on"); mService.updateIcon(mTTYModeIcon, mTTYModeEnableIconData, null); mService.setIconVisibility(mTTYModeIcon, true); } else { // TTY is off - Log.i(TAG, "updateTTY: set TTY off"); + if (false) Log.v(TAG, "updateTTY: set TTY off"); mService.setIconVisibility(mTTYModeIcon, false); } } - private final void updateCdmaRoamingIcon() { + private final void updateCdmaRoamingIcon(ServiceState state) { if (!hasService()) { mService.setIconVisibility(mCdmaRoamingIndicatorIcon, false); return; @@ -1239,8 +1223,8 @@ public class StatusBarPolicy { } int[] iconList = sRoamingIndicatorImages_cdma; - int iconIndex = mPhone.getCdmaEriIconIndex(); - int iconMode = mPhone.getCdmaEriIconMode(); + int iconIndex = state.getCdmaEriIconIndex(); + int iconMode = state.getCdmaEriIconMode(); if (iconIndex == -1) { Log.e(TAG, "getCdmaEriIconIndex returned null, skipping ERI icon update"); @@ -1253,7 +1237,7 @@ public class StatusBarPolicy { } if (iconIndex == EriInfo.ROAMING_INDICATOR_OFF) { - Log.d(TAG, "Cdma ROAMING_INDICATOR_OFF, removing ERI icon"); + if (false) Log.v(TAG, "Cdma ROAMING_INDICATOR_OFF, removing ERI icon"); mService.setIconVisibility(mCdmaRoamingIndicatorIcon, false); return; } diff --git a/services/java/com/android/server/status/StatusBarService.java b/services/java/com/android/server/status/StatusBarService.java index b44168a..8d73904 100644 --- a/services/java/com/android/server/status/StatusBarService.java +++ b/services/java/com/android/server/status/StatusBarService.java @@ -126,7 +126,7 @@ public class StatusBarService extends IStatusBar.Stub public interface NotificationCallbacks { void onSetDisabled(int status); void onClearAll(); - void onNotificationClick(String pkg, int id); + void onNotificationClick(String pkg, String tag, int id); void onPanelRevealed(); } @@ -140,7 +140,7 @@ public class StatusBarService extends IStatusBar.Stub boolean down = event.getAction() == KeyEvent.ACTION_DOWN; switch (event.getKeyCode()) { case KeyEvent.KEYCODE_BACK: - if (down) { + if (!down) { StatusBarService.this.deactivate(); } return true; @@ -322,6 +322,7 @@ public class StatusBarService extends IStatusBar.Stub IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Telephony.Intents.SPN_STRINGS_UPDATED_ACTION); context.registerReceiver(mBroadcastReceiver, filter); } @@ -691,6 +692,7 @@ public class StatusBarService extends IStatusBar.Stub mTicker.addEntry(n, StatusBarIcon.getIcon(mContext, data), n.tickerText); } } + updateExpandedViewPos(EXPANDED_LEAVE_ALONE); } // icon @@ -832,7 +834,7 @@ public class StatusBarService extends IStatusBar.Stub content.setOnFocusChangeListener(mFocusChangeListener); PendingIntent contentIntent = n.contentIntent; if (contentIntent != null) { - content.setOnClickListener(new Launcher(contentIntent, n.pkg, n.id)); + content.setOnClickListener(new Launcher(contentIntent, n.pkg, n.tag, n.id)); } View child = null; @@ -895,7 +897,7 @@ public class StatusBarService extends IStatusBar.Stub com.android.internal.R.id.content); PendingIntent contentIntent = n.contentIntent; if (contentIntent != null) { - content.setOnClickListener(new Launcher(contentIntent, n.pkg, n.id)); + content.setOnClickListener(new Launcher(contentIntent, n.pkg, n.tag, n.id)); } } catch (RuntimeException e) { @@ -949,7 +951,9 @@ public class StatusBarService extends IStatusBar.Stub panelSlightlyVisible(true); updateExpandedViewPos(EXPANDED_LEAVE_ALONE); - mExpandedDialog.show(); + mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + mExpandedDialog.getWindow().setAttributes(mExpandedParams); mExpandedView.requestFocus(View.FOCUS_FORWARD); mTrackingView.setVisibility(View.VISIBLE); @@ -972,15 +976,24 @@ public class StatusBarService extends IStatusBar.Stub } void animateCollapse() { - if (SPEW) Log.d(TAG, "Animate collapse: expanded=" + mExpanded - + " expanded visible=" + mExpandedVisible); + if (SPEW) { + Log.d(TAG, "animateCollapse(): mExpanded=" + mExpanded + + " mExpandedVisible=" + mExpandedVisible + + " mAnimating=" + mAnimating + + " mAnimVel=" + mAnimVel); + } if (!mExpandedVisible) { return; } - prepareTracking(mDisplay.getHeight()-1); - performFling(mDisplay.getHeight()-1, -2000.0f, true); + if (mAnimating) { + return; + } + + int y = mDisplay.getHeight()-1; + prepareTracking(y); + performFling(y, -2000.0f, true); } void performExpand() { @@ -1017,7 +1030,9 @@ public class StatusBarService extends IStatusBar.Stub } mExpandedVisible = false; panelSlightlyVisible(false); - mExpandedDialog.hide(); + mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + mExpandedDialog.getWindow().setAttributes(mExpandedParams); mTrackingView.setVisibility(View.GONE); if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) { @@ -1046,6 +1061,7 @@ public class StatusBarService extends IStatusBar.Stub else if (mAnimY < mStatusBarView.getHeight()) { if (SPEW) Log.d(TAG, "Animation completed to collapsed state."); mAnimating = false; + updateExpandedViewPos(0); performCollapse(); } else { @@ -1095,7 +1111,7 @@ public class StatusBarService extends IStatusBar.Stub mTracking = true; mVelocityTracker = VelocityTracker.obtain(); boolean opening = !mExpanded; - if (!mExpanded) { + if (opening) { mAnimAccel = 2000.0f; mAnimVel = 200; mAnimY = mStatusBarView.getHeight(); @@ -1110,16 +1126,13 @@ public class StatusBarService extends IStatusBar.Stub mAnimating = true; mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL), mCurAnimationTime); + makeExpandedVisible(); } else { // it's open, close it? if (mAnimating) { mAnimating = false; mHandler.removeMessages(MSG_ANIMATE); } - } - if (opening) { - makeExpandedVisible(); - } else { updateExpandedViewPos(y + mViewDelta); } } @@ -1247,11 +1260,13 @@ public class StatusBarService extends IStatusBar.Stub private class Launcher implements View.OnClickListener { private PendingIntent mIntent; private String mPkg; + private String mTag; private int mId; - Launcher(PendingIntent intent, String pkg, int id) { + Launcher(PendingIntent intent, String pkg, String tag, int id) { mIntent = intent; mPkg = pkg; + mTag = tag; mId = id; } @@ -1266,7 +1281,7 @@ public class StatusBarService extends IStatusBar.Stub } try { mIntent.send(); - mNotificationCallbacks.onNotificationClick(mPkg, mId); + mNotificationCallbacks.onNotificationClick(mPkg, mTag, mId); } catch (PendingIntent.CanceledException e) { // the stack trace isn't very helpful here. Just log the exception message. Log.w(TAG, "Sending contentIntent failed: " + e); @@ -1489,24 +1504,28 @@ public class StatusBarService extends IStatusBar.Stub /// ---------- Expanded View -------------- pixelFormat = PixelFormat.TRANSLUCENT; - if (false) { - bg = mExpandedView.getBackground(); - if (bg != null) { - pixelFormat = bg.getOpacity(); + bg = mExpandedView.getBackground(); + if (bg != null) { + pixelFormat = bg.getOpacity(); + if (pixelFormat != PixelFormat.TRANSLUCENT) { + // we want good-looking gradients, so we force a 8-bits per + // pixel format. + pixelFormat = PixelFormat.RGBX_8888; } } + final int disph = mDisplay.getHeight(); lp = mExpandedDialog.getWindow().getAttributes(); lp.width = ViewGroup.LayoutParams.FILL_PARENT; lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; lp.x = 0; - lp.y = 0; + mTrackingPosition = lp.y = -disph; // sufficiently large negative lp.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; lp.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM - | WindowManager.LayoutParams.FLAG_DITHER; + | WindowManager.LayoutParams.FLAG_DITHER + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; lp.format = pixelFormat; lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; lp.setTitle("StatusBarExpanded"); @@ -1519,7 +1538,6 @@ public class StatusBarService extends IStatusBar.Stub new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); mExpandedDialog.show(); - mExpandedDialog.hide(); FrameLayout hack = (FrameLayout)mExpandedView.getParent(); hack.setForeground(null); } @@ -1541,20 +1559,29 @@ public class StatusBarService extends IStatusBar.Stub void updateExpandedViewPos(int expandedPosition) { if (SPEW) { - Log.d(TAG, "updateExpandedViewPos before pos=" + expandedPosition + Log.d(TAG, "updateExpandedViewPos before expandedPosition=" + expandedPosition + " mTrackingParams.y=" + mTrackingParams.y + " mTrackingPosition=" + mTrackingPosition); } - // If the expanded view is not visible, there is no reason to do - // any work. + int h = mStatusBarView.getHeight(); + int disph = mDisplay.getHeight(); + + // If the expanded view is not visible, make sure they're still off screen. + // Maybe the view was resized. if (!mExpandedVisible) { + if (mTrackingView != null) { + mTrackingPosition = mTrackingParams.y = -disph; + WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams); + } + if (mExpandedParams != null) { + mExpandedParams.y = -disph; + mExpandedDialog.getWindow().setAttributes(mExpandedParams); + } return; } - + // tracking view... - int h = mStatusBarView.getHeight(); - int disph = mDisplay.getHeight(); int pos; if (expandedPosition == EXPANDED_FULL_OPEN) { pos = h; @@ -1665,14 +1692,15 @@ public class StatusBarService extends IStatusBar.Stub private View.OnClickListener mClearButtonListener = new View.OnClickListener() { public void onClick(View v) { mNotificationCallbacks.onClearAll(); - performCollapse(); + addPendingOp(OP_EXPAND, null, false); } }; private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) + || Intent.ACTION_SCREEN_OFF.equals(action)) { deactivate(); } else if (Telephony.Intents.SPN_STRINGS_UPDATED_ACTION.equals(action)) { @@ -1726,7 +1754,7 @@ public class StatusBarService extends IStatusBar.Stub mOngoingTitle.setText(mContext.getText(R.string.status_bar_ongoing_events_title)); mLatestTitle.setText(mContext.getText(R.string.status_bar_latest_events_title)); mNoNotificationsTitle.setText(mContext.getText(R.string.status_bar_no_notifications_title)); - Log.d(TAG, "updateResources"); + if (false) Log.v(TAG, "updateResources"); } // diff --git a/services/java/com/android/server/status/TrackingPatternView.java b/services/java/com/android/server/status/TrackingPatternView.java index 0ae9984..4cb8eff 100644 --- a/services/java/com/android/server/status/TrackingPatternView.java +++ b/services/java/com/android/server/status/TrackingPatternView.java @@ -55,8 +55,6 @@ public class TrackingPatternView extends View { final int textureWidth = mTextureWidth; final int textureHeight = mTextureHeight; - Log.d("TrackingPatternView", "width=" + width + " textureWidth=" + textureWidth); - int x = 0; int y; |