diff options
Diffstat (limited to 'services/java/com/android/server')
62 files changed, 11277 insertions, 5189 deletions
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index 65f8b34..fd502d8 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -542,7 +542,9 @@ class AppWidgetService extends IAppWidgetService.Stub IRemoteViewsFactory.Stub.asInterface(service); try { cb.onDestroy(intent); - } catch (Exception e) { + } catch (RemoteException e) { + e.printStackTrace(); + } catch (RuntimeException e) { e.printStackTrace(); } mContext.unbindService(this); diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index ea38fbb..cd58b9b 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -23,10 +23,14 @@ import android.app.IActivityManager; import android.app.IApplicationThread; import android.app.IBackupAgent; import android.app.PendingIntent; +import android.app.backup.BackupDataOutput; +import android.app.backup.FullBackup; import android.app.backup.RestoreSet; import android.app.backup.IBackupManager; +import android.app.backup.IFullBackupRestoreObserver; import android.app.backup.IRestoreObserver; import android.app.backup.IRestoreSession; +import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -35,6 +39,7 @@ import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageDataObserver; +import android.content.pm.IPackageInstallObserver; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -42,6 +47,7 @@ import android.content.pm.Signature; import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; @@ -60,6 +66,7 @@ import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; +import android.util.StringBuilderPrinter; import com.android.internal.backup.BackupConstants; import com.android.internal.backup.IBackupTransport; @@ -69,22 +76,31 @@ import com.android.server.PackageManagerBackupAgent.Metadata; import java.io.EOFException; import java.io.File; import java.io.FileDescriptor; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; class BackupManagerService extends IBackupManager.Stub { private static final String TAG = "BackupManagerService"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = true; + + // Name and current contents version of the full-backup manifest file + static final String BACKUP_MANIFEST_FILENAME = "_manifest"; + static final int BACKUP_MANIFEST_VERSION = 1; // How often we perform a backup pass. Privileged external callers can // trigger an immediate pass. @@ -108,14 +124,20 @@ class BackupManagerService extends IBackupManager.Stub { private static final int MSG_RUN_GET_RESTORE_SETS = 6; private static final int MSG_TIMEOUT = 7; private static final int MSG_RESTORE_TIMEOUT = 8; + private static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9; + private static final int MSG_RUN_FULL_RESTORE = 10; // Timeout interval for deciding that a bind or clear-data has taken too long static final long TIMEOUT_INTERVAL = 10 * 1000; // Timeout intervals for agent backup & restore operations static final long TIMEOUT_BACKUP_INTERVAL = 30 * 1000; + static final long TIMEOUT_FULL_BACKUP_INTERVAL = 5 * 60 * 1000; static final long TIMEOUT_RESTORE_INTERVAL = 60 * 1000; + // User confirmation timeout for a full backup/restore operation + static final long TIMEOUT_FULL_CONFIRMATION = 30 * 1000; + private Context mContext; private PackageManager mPackageManager; IPackageManager mPackageManagerBinder; @@ -138,15 +160,13 @@ class BackupManagerService extends IBackupManager.Stub { // set of backup services that have pending changes class BackupRequest { public ApplicationInfo appInfo; - public boolean fullBackup; - BackupRequest(ApplicationInfo app, boolean isFull) { + BackupRequest(ApplicationInfo app) { appInfo = app; - fullBackup = isFull; } public String toString() { - return "BackupRequest{app=" + appInfo + " full=" + fullBackup + "}"; + return "BackupRequest{app=" + appInfo + "}"; } } // Backups that we haven't started yet. Keys are package names. @@ -232,6 +252,38 @@ class BackupManagerService extends IBackupManager.Stub { } } + class FullParams { + public ParcelFileDescriptor fd; + public final AtomicBoolean latch; + public IFullBackupRestoreObserver observer; + + FullParams() { + latch = new AtomicBoolean(false); + } + } + + class FullBackupParams extends FullParams { + public boolean includeApks; + public boolean includeShared; + public boolean allApps; + public String[] packages; + + FullBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveShared, + boolean doAllApps, String[] pkgList) { + fd = output; + includeApks = saveApks; + includeShared = saveShared; + allApps = doAllApps; + packages = pkgList; + } + } + + class FullRestoreParams extends FullParams { + FullRestoreParams(ParcelFileDescriptor input) { + fd = input; + } + } + // Bookkeeping of in-flight operations for timeout etc. purposes. The operation // token is the index of the entry in the pending-operations list. static final int OP_PENDING = 0; @@ -242,6 +294,8 @@ class BackupManagerService extends IBackupManager.Stub { final Object mCurrentOpLock = new Object(); final Random mTokenGenerator = new Random(); + final SparseArray<FullParams> mFullConfirmations = new SparseArray<FullParams>(); + // Where we keep our journal files and other bookkeeping File mBaseStateDir; File mDataDir; @@ -264,6 +318,17 @@ class BackupManagerService extends IBackupManager.Stub { static final String INIT_SENTINEL_FILE_NAME = "_need_init_"; HashSet<String> mPendingInits = new HashSet<String>(); // transport names + // Utility: build a new random integer token + int generateToken() { + int token; + do { + synchronized (mTokenGenerator) { + token = mTokenGenerator.nextInt(); + } + } while (token < 0); + return token; + } + // ----- Asynchronous backup/restore handler thread ----- private class BackupHandler extends Handler { @@ -321,7 +386,13 @@ class BackupManagerService extends IBackupManager.Stub { } case MSG_RUN_FULL_BACKUP: + { + FullBackupParams params = (FullBackupParams)msg.obj; + (new PerformFullBackupTask(params.fd, params.observer, params.includeApks, + params.includeShared, params.allApps, params.packages, + params.latch)).run(); break; + } case MSG_RUN_RESTORE: { @@ -333,6 +404,13 @@ class BackupManagerService extends IBackupManager.Stub { break; } + case MSG_RUN_FULL_RESTORE: + { + FullRestoreParams params = (FullRestoreParams)msg.obj; + (new PerformFullRestoreTask(params.fd, params.observer, params.latch)).run(); + break; + } + case MSG_RUN_CLEAR: { ClearParams params = (ClearParams)msg.obj; @@ -416,6 +494,34 @@ class BackupManagerService extends IBackupManager.Stub { } } } + + case MSG_FULL_CONFIRMATION_TIMEOUT: + { + synchronized (mFullConfirmations) { + FullParams params = mFullConfirmations.get(msg.arg1); + if (params != null) { + Slog.i(TAG, "Full backup/restore timed out waiting for user confirmation"); + + // Release the waiter; timeout == completion + signalFullBackupRestoreCompletion(params); + + // Remove the token from the set + mFullConfirmations.delete(msg.arg1); + + // Report a timeout to the observer, if any + if (params.observer != null) { + try { + params.observer.onTimeout(); + } catch (RemoteException e) { + /* don't care if the app has gone away */ + } + } + } else { + Slog.d(TAG, "couldn't find params for token " + msg.arg1); + } + } + break; + } } } } @@ -760,15 +866,15 @@ class BackupManagerService extends IBackupManager.Stub { 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) { - dataChangedImpl(app.packageName); - } + // 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) { + dataChangedImpl(app.packageName); } } } @@ -1143,7 +1249,7 @@ class BackupManagerService extends IBackupManager.Stub { Slog.d(TAG, "awaiting agent for " + app); // success; wait for the agent to arrive - // only wait 10 seconds for the clear data to happen + // only wait 10 seconds for the bind to happen long timeoutMark = System.currentTimeMillis() + TIMEOUT_INTERVAL; while (mConnecting && mConnectedAgent == null && (System.currentTimeMillis() < timeoutMark)) { @@ -1253,9 +1359,11 @@ class BackupManagerService extends IBackupManager.Stub { void prepareOperationTimeout(int token, long interval) { if (DEBUG) Slog.v(TAG, "starting timeout: token=" + Integer.toHexString(token) + " interval=" + interval); - mCurrentOperations.put(token, OP_PENDING); - Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0); - mBackupHandler.sendMessageDelayed(msg, interval); + synchronized (mCurrentOpLock) { + mCurrentOperations.put(token, OP_PENDING); + Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0); + mBackupHandler.sendMessageDelayed(msg, interval); + } } // ----- Back up a set of applications via a worker thread ----- @@ -1313,7 +1421,7 @@ class BackupManagerService extends IBackupManager.Stub { if (status == BackupConstants.TRANSPORT_OK) { PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent( mPackageManager, allAgentPackages()); - BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false); + BackupRequest pmRequest = new BackupRequest(new ApplicationInfo()); pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL; status = processOneBackup(pmRequest, IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport); @@ -1407,12 +1515,10 @@ class BackupManagerService extends IBackupManager.Stub { } IBackupAgent agent = null; - int mode = (request.fullBackup) - ? IApplicationThread.BACKUP_MODE_FULL - : IApplicationThread.BACKUP_MODE_INCREMENTAL; try { mWakelock.setWorkSource(new WorkSource(request.appInfo.uid)); - agent = bindToAgentSynchronous(request.appInfo, mode); + agent = bindToAgentSynchronous(request.appInfo, + IApplicationThread.BACKUP_MODE_INCREMENTAL); if (agent != null) { int result = processOneBackup(request, agent, transport); if (result != BackupConstants.TRANSPORT_OK) return result; @@ -1446,7 +1552,7 @@ class BackupManagerService extends IBackupManager.Stub { ParcelFileDescriptor newState = null; PackageInfo packInfo; - int token = mTokenGenerator.nextInt(); + final int token = generateToken(); 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 @@ -1461,12 +1567,11 @@ class BackupManagerService extends IBackupManager.Stub { } // In a full backup, we pass a null ParcelFileDescriptor as - // the saved-state "file" - if (!request.fullBackup) { - savedState = ParcelFileDescriptor.open(savedStateName, - ParcelFileDescriptor.MODE_READ_ONLY | - ParcelFileDescriptor.MODE_CREATE); // Make an empty file if necessary - } + // the saved-state "file". This is by definition an incremental, + // so we build a saved state file to pass. + savedState = ParcelFileDescriptor.open(savedStateName, + ParcelFileDescriptor.MODE_READ_ONLY | + ParcelFileDescriptor.MODE_CREATE); // Make an empty file if necessary backupData = ParcelFileDescriptor.open(backupDataName, ParcelFileDescriptor.MODE_READ_WRITE | @@ -1480,7 +1585,8 @@ class BackupManagerService extends IBackupManager.Stub { // Initiate the target's backup pass prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL); - agent.doBackup(savedState, backupData, newState, token, mBackupManagerBinder); + agent.doBackup(savedState, backupData, newState, false, + token, mBackupManagerBinder); boolean success = waitUntilOperationComplete(token); if (!success) { @@ -1552,6 +1658,1120 @@ class BackupManagerService extends IBackupManager.Stub { } + // ----- Full backup to a file/socket ----- + + class PerformFullBackupTask implements Runnable { + ParcelFileDescriptor mOutputFile; + IFullBackupRestoreObserver mObserver; + boolean mIncludeApks; + boolean mIncludeShared; + boolean mAllApps; + String[] mPackages; + AtomicBoolean mLatchObject; + File mFilesDir; + File mManifestFile; + + PerformFullBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer, + boolean includeApks, boolean includeShared, + boolean doAllApps, String[] packages, AtomicBoolean latch) { + mOutputFile = fd; + mObserver = observer; + mIncludeApks = includeApks; + mIncludeShared = includeShared; + mAllApps = doAllApps; + mPackages = packages; + mLatchObject = latch; + + mFilesDir = new File("/data/system"); + mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME); + } + + @Override + public void run() { + final List<PackageInfo> packagesToBackup; + + Slog.i(TAG, "--- Performing full-dataset restore ---"); + sendStartBackup(); + + // doAllApps supersedes the package set if any + if (mAllApps) { + packagesToBackup = mPackageManager.getInstalledPackages( + PackageManager.GET_SIGNATURES); + } else { + packagesToBackup = new ArrayList<PackageInfo>(); + for (String pkgName : mPackages) { + try { + packagesToBackup.add(mPackageManager.getPackageInfo(pkgName, + PackageManager.GET_SIGNATURES)); + } catch (NameNotFoundException e) { + Slog.w(TAG, "Unknown package " + pkgName + ", skipping"); + } + } + } + + // Now back up the app data via the agent mechanism + PackageInfo pkg = null; + try { + int N = packagesToBackup.size(); + for (int i = 0; i < N; i++) { + pkg = packagesToBackup.get(i); + + Slog.d(TAG, "Binding to full backup agent : " + pkg.packageName); + + IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo, + IApplicationThread.BACKUP_MODE_FULL); + if (agent != null) { + try { + ApplicationInfo app = pkg.applicationInfo; + boolean sendApk = mIncludeApks + && ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0) + && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 || + (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); + + sendOnBackupPackage(pkg.packageName); + + { + BackupDataOutput output = new BackupDataOutput( + mOutputFile.getFileDescriptor()); + + if (DEBUG) Slog.d(TAG, "Writing manifest for " + pkg.packageName); + writeAppManifest(pkg, mManifestFile, sendApk); + FullBackup.backupToTar(pkg.packageName, null, null, + mFilesDir.getAbsolutePath(), + mManifestFile.getAbsolutePath(), + output); + } + + if (DEBUG) Slog.d(TAG, "Calling doBackup()"); + final int token = generateToken(); + prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL); + agent.doBackup(null, mOutputFile, null, sendApk, + token, mBackupManagerBinder); + boolean success = waitUntilOperationComplete(token); + if (!success) { + Slog.d(TAG, "Full backup failed on package " + pkg.packageName); + } else { + if (DEBUG) Slog.d(TAG, "Full backup success: " + pkg.packageName); + } + } catch (IOException e) { + Slog.e(TAG, "Error backing up " + pkg.packageName, e); + } + } else { + Slog.w(TAG, "Unable to bind to full agent for " + pkg.packageName); + } + tearDown(pkg); + } + } catch (RemoteException e) { + Slog.e(TAG, "App died during full backup"); + } finally { + if (pkg != null) { + tearDown(pkg); + } + try { + mOutputFile.close(); + } catch (IOException e) { + /* nothing we can do about this */ + } + synchronized (mCurrentOpLock) { + mCurrentOperations.clear(); + } + synchronized (mLatchObject) { + mLatchObject.set(true); + mLatchObject.notifyAll(); + } + sendEndBackup(); + mWakelock.release(); + if (DEBUG) Slog.d(TAG, "Full backup pass complete."); + } + } + + private void writeAppManifest(PackageInfo pkg, File manifestFile, boolean withApk) + throws IOException { + // Manifest format. All data are strings ending in LF: + // BACKUP_MANIFEST_VERSION, currently 1 + // + // Version 1: + // package name + // package's versionCode + // platform versionCode + // getInstallerPackageName() for this package (maybe empty) + // boolean: "1" if archive includes .apk; any other string means not + // number of signatures == N + // N*: signature byte array in ascii format per Signature.toCharsString() + StringBuilder builder = new StringBuilder(4096); + StringBuilderPrinter printer = new StringBuilderPrinter(builder); + + printer.println(Integer.toString(BACKUP_MANIFEST_VERSION)); + printer.println(pkg.packageName); + printer.println(Integer.toString(pkg.versionCode)); + printer.println(Integer.toString(Build.VERSION.SDK_INT)); + + String installerName = mPackageManager.getInstallerPackageName(pkg.packageName); + printer.println((installerName != null) ? installerName : ""); + + printer.println(withApk ? "1" : "0"); + if (pkg.signatures == null) { + printer.println("0"); + } else { + printer.println(Integer.toString(pkg.signatures.length)); + for (Signature sig : pkg.signatures) { + printer.println(sig.toCharsString()); + } + } + + FileOutputStream outstream = new FileOutputStream(manifestFile); + outstream.write(builder.toString().getBytes()); + outstream.close(); + } + + private void tearDown(PackageInfo pkg) { + final ApplicationInfo app = pkg.applicationInfo; + try { + // unbind and tidy up even on timeout or failure, just in case + mActivityManager.unbindBackupAgent(app); + + // The agent was running with a stub Application object, so shut it down. + // !!! We hardcode the confirmation UI's package name here rather than use a + // manifest flag! TODO something less direct. + if (app.uid != Process.SYSTEM_UID + && !pkg.packageName.equals("com.android.backupconfirm")) { + if (DEBUG) Slog.d(TAG, "Backup complete, killing host process"); + mActivityManager.killApplicationProcess(app.processName, app.uid); + } else { + if (DEBUG) Slog.d(TAG, "Not killing after restore: " + app.processName); + } + } catch (RemoteException e) { + Slog.d(TAG, "Lost app trying to shut down"); + } + } + + // wrappers for observer use + void sendStartBackup() { + if (mObserver != null) { + try { + mObserver.onStartBackup(); + } catch (RemoteException e) { + Slog.w(TAG, "full backup observer went away: startBackup"); + mObserver = null; + } + } + } + + void sendOnBackupPackage(String name) { + if (mObserver != null) { + try { + // TODO: use a more user-friendly name string + mObserver.onBackupPackage(name); + } catch (RemoteException e) { + Slog.w(TAG, "full backup observer went away: backupPackage"); + mObserver = null; + } + } + } + + void sendEndBackup() { + if (mObserver != null) { + try { + mObserver.onEndBackup(); + } catch (RemoteException e) { + Slog.w(TAG, "full backup observer went away: endBackup"); + mObserver = null; + } + } + } + } + + + // ----- Full restore from a file/socket ----- + + // Description of a file in the restore datastream + static class FileMetadata { + String packageName; // name of the owning app + String installerPackageName; // name of the market-type app that installed the owner + int type; // e.g. FullBackup.TYPE_DIRECTORY + String domain; // e.g. FullBackup.DATABASE_TREE_TOKEN + String path; // subpath within the semantic domain + long mode; // e.g. 0666 (actually int) + long mtime; // last mod time, UTC time_t (actually int) + long size; // bytes of content + } + + enum RestorePolicy { + IGNORE, + ACCEPT, + ACCEPT_IF_APK + } + + class PerformFullRestoreTask implements Runnable { + ParcelFileDescriptor mInputFile; + IFullBackupRestoreObserver mObserver; + AtomicBoolean mLatchObject; + IBackupAgent mAgent; + String mAgentPackage; + ApplicationInfo mTargetApp; + ParcelFileDescriptor[] mPipes = null; + + // possible handling states for a given package in the restore dataset + final HashMap<String, RestorePolicy> mPackagePolicies + = new HashMap<String, RestorePolicy>(); + + // installer package names for each encountered app, derived from the manifests + final HashMap<String, String> mPackageInstallers = new HashMap<String, String>(); + + // Signatures for a given package found in its manifest file + final HashMap<String, Signature[]> mManifestSignatures + = new HashMap<String, Signature[]>(); + + // Packages we've already wiped data on when restoring their first file + final HashSet<String> mClearedPackages = new HashSet<String>(); + + PerformFullRestoreTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer, + AtomicBoolean latch) { + mInputFile = fd; + mObserver = observer; + mLatchObject = latch; + mAgent = null; + mAgentPackage = null; + mTargetApp = null; + + // Which packages we've already wiped data on. We prepopulate this + // with a whitelist of packages known to be unclearable. + mClearedPackages.add("android"); + mClearedPackages.add("com.android.backupconfirm"); + mClearedPackages.add("com.android.providers.settings"); + } + + class RestoreFileRunnable implements Runnable { + IBackupAgent mAgent; + FileMetadata mInfo; + ParcelFileDescriptor mSocket; + int mToken; + + RestoreFileRunnable(IBackupAgent agent, FileMetadata info, + ParcelFileDescriptor socket, int token) throws IOException { + mAgent = agent; + mInfo = info; + mToken = token; + + // This class is used strictly for process-local binder invocations. The + // semantics of ParcelFileDescriptor differ in this case; in particular, we + // do not automatically get a 'dup'ed descriptor that we can can continue + // to use asynchronously from the caller. So, we make sure to dup it ourselves + // before proceeding to do the restore. + mSocket = ParcelFileDescriptor.dup(socket.getFileDescriptor()); + } + + @Override + public void run() { + try { + mAgent.doRestoreFile(mSocket, mInfo.size, mInfo.type, + mInfo.domain, mInfo.path, mInfo.mode, mInfo.mtime, + mToken, mBackupManagerBinder); + } catch (RemoteException e) { + // never happens; this is used strictly for local binder calls + } + } + } + + @Override + public void run() { + Slog.i(TAG, "--- Performing full-dataset restore ---"); + sendStartRestore(); + + try { + byte[] buffer = new byte[32 * 1024]; + FileInputStream instream = new FileInputStream(mInputFile.getFileDescriptor()); + + boolean didRestore; + do { + didRestore = restoreOneFile(instream, buffer); + } while (didRestore); + + if (DEBUG) Slog.v(TAG, "Done consuming input tarfile"); + } finally { + tearDownPipes(); + tearDownAgent(mTargetApp); + + try { + mInputFile.close(); + } catch (IOException e) { + /* nothing we can do about this */ + } + synchronized (mCurrentOpLock) { + mCurrentOperations.clear(); + } + synchronized (mLatchObject) { + mLatchObject.set(true); + mLatchObject.notifyAll(); + } + sendEndRestore(); + mWakelock.release(); + if (DEBUG) Slog.d(TAG, "Full restore pass complete."); + } + } + + boolean restoreOneFile(InputStream instream, byte[] buffer) { + FileMetadata info; + try { + info = readTarHeaders(instream); + if (info != null) { + if (DEBUG) { + dumpFileMetadata(info); + } + + final String pkg = info.packageName; + if (!pkg.equals(mAgentPackage)) { + // okay, change in package; set up our various + // bookkeeping if we haven't seen it yet + if (!mPackagePolicies.containsKey(pkg)) { + mPackagePolicies.put(pkg, RestorePolicy.IGNORE); + } + + // Clean up the previous agent relationship if necessary, + // and let the observer know we're considering a new app. + if (mAgent != null) { + if (DEBUG) Slog.d(TAG, "Saw new package; tearing down old one"); + tearDownPipes(); + tearDownAgent(mTargetApp); + mTargetApp = null; + mAgentPackage = null; + } + } + + if (info.path.equals(BACKUP_MANIFEST_FILENAME)) { + mPackagePolicies.put(pkg, readAppManifest(info, instream)); + mPackageInstallers.put(pkg, info.installerPackageName); + // We've read only the manifest content itself at this point, + // so consume the footer before looping around to the next + // input file + skipTarPadding(info.size, instream); + sendOnRestorePackage(pkg); + } else { + // Non-manifest, so it's actual file data. Is this a package + // we're ignoring? + boolean okay = true; + RestorePolicy policy = mPackagePolicies.get(pkg); + switch (policy) { + case IGNORE: + okay = false; + break; + + case ACCEPT_IF_APK: + // If we're in accept-if-apk state, then the first file we + // see MUST be the apk. + if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) { + if (DEBUG) Slog.d(TAG, "APK file; installing"); + // Try to install the app. + String installerName = mPackageInstallers.get(pkg); + okay = installApk(info, installerName, instream); + // good to go; promote to ACCEPT + mPackagePolicies.put(pkg, (okay) + ? RestorePolicy.ACCEPT + : RestorePolicy.IGNORE); + // At this point we've consumed this file entry + // ourselves, so just strip the tar footer and + // go on to the next file in the input stream + skipTarPadding(info.size, instream); + return true; + } else { + // File data before (or without) the apk. We can't + // handle it coherently in this case so ignore it. + mPackagePolicies.put(pkg, RestorePolicy.IGNORE); + okay = false; + } + break; + + case ACCEPT: + if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) { + if (DEBUG) Slog.d(TAG, "apk present but ACCEPT"); + // we can take the data without the apk, so we + // *want* to do so. skip the apk by declaring this + // one file not-okay without changing the restore + // policy for the package. + okay = false; + } + break; + + default: + // Something has gone dreadfully wrong when determining + // the restore policy from the manifest. Ignore the + // rest of this package's data. + Slog.e(TAG, "Invalid policy from manifest"); + okay = false; + mPackagePolicies.put(pkg, RestorePolicy.IGNORE); + break; + } + + // If the policy is satisfied, go ahead and set up to pipe the + // data to the agent. + if (DEBUG && okay && mAgent != null) { + Slog.i(TAG, "Reusing existing agent instance"); + } + if (okay && mAgent == null) { + if (DEBUG) Slog.d(TAG, "Need to launch agent for " + pkg); + + try { + mTargetApp = mPackageManager.getApplicationInfo(pkg, 0); + + // If we haven't sent any data to this app yet, we probably + // need to clear it first. Check that. + if (!mClearedPackages.contains(pkg)) { + // apps with their own full backup agents are + // responsible for coherently managing a full + // restore. + if (mTargetApp.fullBackupAgentName == null) { + if (DEBUG) Slog.d(TAG, "Clearing app data preparatory to full restore"); + clearApplicationDataSynchronous(pkg); + } else { + if (DEBUG) Slog.d(TAG, "full backup agent (" + + mTargetApp.fullBackupAgentName + ") => no clear"); + } + mClearedPackages.add(pkg); + } else { + if (DEBUG) Slog.d(TAG, "We've initialized this app already; no clear required"); + } + + // All set; now set up the IPC and launch the agent + setUpPipes(); + mAgent = bindToAgentSynchronous(mTargetApp, + IApplicationThread.BACKUP_MODE_RESTORE_FULL); + mAgentPackage = pkg; + } catch (IOException e) { + // fall through to error handling + } catch (NameNotFoundException e) { + // fall through to error handling + } + + if (mAgent == null) { + if (DEBUG) Slog.d(TAG, "Unable to create agent for " + pkg); + okay = false; + tearDownPipes(); + mPackagePolicies.put(pkg, RestorePolicy.IGNORE); + } + } + + // Sanity check: make sure we never give data to the wrong app. This + // should never happen but a little paranoia here won't go amiss. + if (okay && !pkg.equals(mAgentPackage)) { + Slog.e(TAG, "Restoring data for " + pkg + + " but agent is for " + mAgentPackage); + okay = false; + } + + // At this point we have an agent ready to handle the full + // restore data as well as a pipe for sending data to + // that agent. Tell the agent to start reading from the + // pipe. + if (okay) { + boolean agentSuccess = true; + long toCopy = info.size; + final int token = generateToken(); + try { + if (DEBUG) Slog.d(TAG, "Invoking agent to restore file " + + info.path); + prepareOperationTimeout(token, + TIMEOUT_FULL_BACKUP_INTERVAL); + // fire up the app's agent listening on the socket. If + // the agent is running in the system process we can't + // just invoke it asynchronously, so we provide a thread + // for it here. + if (mTargetApp.processName.equals("system")) { + Slog.d(TAG, "system process agent - spinning a thread"); + RestoreFileRunnable runner = new RestoreFileRunnable( + mAgent, info, mPipes[0], token); + new Thread(runner).start(); + } else { + mAgent.doRestoreFile(mPipes[0], info.size, info.type, + info.domain, info.path, info.mode, info.mtime, + token, mBackupManagerBinder); + } + } catch (IOException e) { + // couldn't dup the socket for a process-local restore + Slog.d(TAG, "Couldn't establish restore"); + agentSuccess = false; + okay = false; + } catch (RemoteException e) { + // whoops, remote agent went away. We'll eat the content + // ourselves, then, and not copy it over. + Slog.e(TAG, "Agent crashed during full restore"); + agentSuccess = false; + okay = false; + } + + // Copy over the data if the agent is still good + if (okay) { + boolean pipeOkay = true; + FileOutputStream pipe = new FileOutputStream( + mPipes[1].getFileDescriptor()); + if (DEBUG) Slog.d(TAG, "Piping data to agent"); + while (toCopy > 0) { + int toRead = (toCopy > buffer.length) + ? buffer.length : (int)toCopy; + int nRead = instream.read(buffer, 0, toRead); + if (nRead <= 0) break; + toCopy -= nRead; + + // send it to the output pipe as long as things + // are still good + if (pipeOkay) { + try { + pipe.write(buffer, 0, nRead); + } catch (IOException e) { + Slog.e(TAG, + "Failed to write to restore pipe", e); + pipeOkay = false; + } + } + } + + // done sending that file! Now we just need to consume + // the delta from info.size to the end of block. + skipTarPadding(info.size, instream); + + // and now that we've sent it all, wait for the remote + // side to acknowledge receipt + agentSuccess = waitUntilOperationComplete(token); + } + + // okay, if the remote end failed at any point, deal with + // it by ignoring the rest of the restore on it + if (!agentSuccess) { + mBackupHandler.removeMessages(MSG_TIMEOUT); + tearDownPipes(); + tearDownAgent(mTargetApp); + mAgent = null; + mPackagePolicies.put(pkg, RestorePolicy.IGNORE); + } + } + + // Problems setting up the agent communication, or an already- + // ignored package: skip to the next tar stream entry by + // reading and discarding this file. + if (!okay) { + if (DEBUG) Slog.d(TAG, "[discarding file content]"); + long bytesToConsume = (info.size + 511) & ~511; + while (bytesToConsume > 0) { + int toRead = (bytesToConsume > buffer.length) + ? buffer.length : (int)bytesToConsume; + long nRead = instream.read(buffer, 0, toRead); + if (nRead <= 0) break; + bytesToConsume -= nRead; + } + } + } + } + } catch (IOException e) { + Slog.w(TAG, "io exception on restore socket read", e); + // treat as EOF + info = null; + } + + return (info != null); + } + + void setUpPipes() throws IOException { + mPipes = ParcelFileDescriptor.createPipe(); + } + + void tearDownPipes() { + if (mPipes != null) { + if (mPipes[0] != null) { + try { + mPipes[0].close(); + mPipes[0] = null; + mPipes[1].close(); + mPipes[1] = null; + } catch (IOException e) { + Slog.w(TAG, "Couldn't close agent pipes", e); + } + } + mPipes = null; + } + } + + void tearDownAgent(ApplicationInfo app) { + if (mAgent != null) { + try { + // unbind and tidy up even on timeout or failure, just in case + mActivityManager.unbindBackupAgent(app); + + // The agent was running with a stub Application object, so shut it down. + // !!! We hardcode the confirmation UI's package name here rather than use a + // manifest flag! TODO something less direct. + if (app.uid != Process.SYSTEM_UID + && !app.packageName.equals("com.android.backupconfirm")) { + if (DEBUG) Slog.d(TAG, "Killing host process"); + mActivityManager.killApplicationProcess(app.processName, app.uid); + } else { + if (DEBUG) Slog.d(TAG, "Not killing after full restore"); + } + } catch (RemoteException e) { + Slog.d(TAG, "Lost app trying to shut down"); + } + mAgent = null; + } + } + + class RestoreInstallObserver extends IPackageInstallObserver.Stub { + final AtomicBoolean mDone = new AtomicBoolean(); + int mResult; + + public void reset() { + synchronized (mDone) { + mDone.set(false); + } + } + + public void waitForCompletion() { + synchronized (mDone) { + while (mDone.get() == false) { + try { + mDone.wait(); + } catch (InterruptedException e) { } + } + } + } + + int getResult() { + return mResult; + } + + @Override + public void packageInstalled(String packageName, int returnCode) + throws RemoteException { + synchronized (mDone) { + mResult = returnCode; + mDone.set(true); + mDone.notifyAll(); + } + } + } + final RestoreInstallObserver mInstallObserver = new RestoreInstallObserver(); + + boolean installApk(FileMetadata info, String installerPackage, InputStream instream) { + boolean okay = true; + + if (DEBUG) Slog.d(TAG, "Installing from backup: " + info.packageName); + + // The file content is an .apk file. Copy it out to a staging location and + // attempt to install it. + File apkFile = new File(mDataDir, info.packageName); + try { + FileOutputStream apkStream = new FileOutputStream(apkFile); + byte[] buffer = new byte[32 * 1024]; + long size = info.size; + while (size > 0) { + long toRead = (buffer.length < size) ? buffer.length : size; + int didRead = instream.read(buffer, 0, (int)toRead); + apkStream.write(buffer, 0, didRead); + size -= didRead; + } + apkStream.close(); + + // make sure the installer can read it + apkFile.setReadable(true, false); + + // Now install it + Uri packageUri = Uri.fromFile(apkFile); + mInstallObserver.reset(); + mPackageManager.installPackage(packageUri, mInstallObserver, + PackageManager.INSTALL_REPLACE_EXISTING, installerPackage); + mInstallObserver.waitForCompletion(); + + if (mInstallObserver.getResult() != PackageManager.INSTALL_SUCCEEDED) { + // The only time we continue to accept install of data even if the + // apk install failed is if we had already determined that we could + // accept the data regardless. + if (mPackagePolicies.get(info.packageName) != RestorePolicy.ACCEPT) { + okay = false; + } + } + } catch (IOException e) { + Slog.e(TAG, "Unable to transcribe restored apk for install"); + okay = false; + } finally { + apkFile.delete(); + } + + return okay; + } + + // Given an actual file content size, consume the post-content padding mandated + // by the tar format. + void skipTarPadding(long size, InputStream instream) throws IOException { + long partial = (size + 512) % 512; + if (partial > 0) { + byte[] buffer = new byte[512]; + instream.read(buffer, 0, 512 - (int)partial); + } + } + + // Returns a policy constant; takes a buffer arg to reduce memory churn + RestorePolicy readAppManifest(FileMetadata info, InputStream instream) + throws IOException { + // Fail on suspiciously large manifest files + if (info.size > 64 * 1024) { + throw new IOException("Restore manifest too big; corrupt? size=" + info.size); + } + byte[] buffer = new byte[(int) info.size]; + int nRead = 0; + while (nRead < info.size) { + nRead += instream.read(buffer, nRead, (int)info.size - nRead); + } + + RestorePolicy policy = RestorePolicy.IGNORE; + String[] str = new String[1]; + int offset = 0; + + try { + offset = extractLine(buffer, offset, str); + int version = Integer.parseInt(str[0]); + if (version == BACKUP_MANIFEST_VERSION) { + offset = extractLine(buffer, offset, str); + String manifestPackage = str[0]; + // TODO: handle <original-package> + if (manifestPackage.equals(info.packageName)) { + offset = extractLine(buffer, offset, str); + version = Integer.parseInt(str[0]); // app version + offset = extractLine(buffer, offset, str); + int platformVersion = Integer.parseInt(str[0]); + offset = extractLine(buffer, offset, str); + info.installerPackageName = (str[0].length() > 0) ? str[0] : null; + offset = extractLine(buffer, offset, str); + boolean hasApk = str[0].equals("1"); + offset = extractLine(buffer, offset, str); + int numSigs = Integer.parseInt(str[0]); + Signature[] sigs = null; + if (numSigs > 0) { + sigs = new Signature[numSigs]; + for (int i = 0; i < numSigs; i++) { + offset = extractLine(buffer, offset, str); + sigs[i] = new Signature(str[0]); + } + + // Okay, got the manifest info we need... + try { + // Verify signatures against any installed version; if they + // don't match, then we fall though and ignore the data. The + // signatureMatch() method explicitly ignores the signature + // check for packages installed on the system partition, because + // such packages are signed with the platform cert instead of + // the app developer's cert, so they're different on every + // device. + PackageInfo pkgInfo = mPackageManager.getPackageInfo( + info.packageName, PackageManager.GET_SIGNATURES); + if (signaturesMatch(sigs, pkgInfo)) { + if (pkgInfo.versionCode >= version) { + Slog.i(TAG, "Sig + version match; taking data"); + policy = RestorePolicy.ACCEPT; + } else { + // The data is from a newer version of the app than + // is presently installed. That means we can only + // use it if the matching apk is also supplied. + Slog.d(TAG, "Data version " + version + + " is newer than installed version " + + pkgInfo.versionCode + " - requiring apk"); + policy = RestorePolicy.ACCEPT_IF_APK; + } + } + } catch (NameNotFoundException e) { + // Okay, the target app isn't installed. We can process + // the restore properly only if the dataset provides the + // apk file and we can successfully install it. + if (DEBUG) Slog.i(TAG, "Package " + info.packageName + + " not installed; requiring apk in dataset"); + policy = RestorePolicy.ACCEPT_IF_APK; + } + + if (policy == RestorePolicy.ACCEPT_IF_APK && !hasApk) { + Slog.i(TAG, "Cannot restore package " + info.packageName + + " without the matching .apk"); + } + } else { + Slog.i(TAG, "Missing signature on backed-up package " + + info.packageName); + } + } else { + Slog.i(TAG, "Expected package " + info.packageName + + " but restore manifest claims " + manifestPackage); + } + } else { + Slog.i(TAG, "Unknown restore manifest version " + version + + " for package " + info.packageName); + } + } catch (NumberFormatException e) { + Slog.w(TAG, "Corrupt restore manifest for package " + info.packageName); + } + + return policy; + } + + // Builds a line from a byte buffer starting at 'offset', and returns + // the index of the next unconsumed data in the buffer. + int extractLine(byte[] buffer, int offset, String[] outStr) throws IOException { + final int end = buffer.length; + if (offset >= end) throw new IOException("Incomplete data"); + + int pos; + for (pos = offset; pos < end; pos++) { + byte c = buffer[pos]; + // at LF we declare end of line, and return the next char as the + // starting point for the next time through + if (c == '\n') { + break; + } + } + outStr[0] = new String(buffer, offset, pos - offset); + pos++; // may be pointing an extra byte past the end but that's okay + return pos; + } + + void dumpFileMetadata(FileMetadata info) { + if (DEBUG) { + StringBuilder b = new StringBuilder(128); + + // mode string + b.append((info.type == FullBackup.TYPE_DIRECTORY) ? 'd' : '-'); + b.append(((info.mode & 0400) != 0) ? 'r' : '-'); + b.append(((info.mode & 0200) != 0) ? 'w' : '-'); + b.append(((info.mode & 0100) != 0) ? 'x' : '-'); + b.append(((info.mode & 0040) != 0) ? 'r' : '-'); + b.append(((info.mode & 0020) != 0) ? 'w' : '-'); + b.append(((info.mode & 0010) != 0) ? 'x' : '-'); + b.append(((info.mode & 0004) != 0) ? 'r' : '-'); + b.append(((info.mode & 0002) != 0) ? 'w' : '-'); + b.append(((info.mode & 0001) != 0) ? 'x' : '-'); + b.append(String.format(" %9d ", info.size)); + + Date stamp = new Date(info.mtime); + b.append(new SimpleDateFormat("MMM dd kk:mm:ss ").format(stamp)); + + b.append(info.packageName); + b.append(" :: "); + b.append(info.domain); + b.append(" :: "); + b.append(info.path); + + Slog.i(TAG, b.toString()); + } + } + // Consume a tar file header block [sequence] and accumulate the relevant metadata + FileMetadata readTarHeaders(InputStream instream) throws IOException { + byte[] block = new byte[512]; + FileMetadata info = null; + + boolean gotHeader = readTarHeader(instream, block); + if (gotHeader) { + // okay, presume we're okay, and extract the various metadata + info = new FileMetadata(); + info.size = extractRadix(block, 124, 12, 8); + info.mtime = extractRadix(block, 136, 12, 8); + info.mode = extractRadix(block, 100, 8, 8); + + info.path = extractString(block, 345, 155); // prefix + String path = extractString(block, 0, 100); + if (path.length() > 0) { + if (info.path.length() > 0) info.path += '/'; + info.path += path; + } + + // tar link indicator field: 1 byte at offset 156 in the header. + int typeChar = block[156]; + if (typeChar == 'x') { + // pax extended header, so we need to read that + gotHeader = readPaxExtendedHeader(instream, info); + if (gotHeader) { + // and after a pax extended header comes another real header -- read + // that to find the real file type + gotHeader = readTarHeader(instream, block); + } + if (!gotHeader) throw new IOException("Bad or missing pax header"); + + typeChar = block[156]; + } + + switch (typeChar) { + case '0': info.type = FullBackup.TYPE_FILE; break; + case '5': info.type = FullBackup.TYPE_DIRECTORY; break; + case 0: { + // presume EOF + return null; + } + default: { + Slog.e(TAG, "Unknown tar entity type: " + typeChar); + throw new IOException("Unknown entity type " + typeChar); + } + } + + // Parse out the path + // + // first: apps/shared/unrecognized + if (FullBackup.SHARED_PREFIX.regionMatches(0, + info.path, 0, FullBackup.SHARED_PREFIX.length())) { + // File in shared storage. !!! TODO: implement this. + info.path = info.path.substring(FullBackup.SHARED_PREFIX.length()); + info.domain = FullBackup.SHARED_STORAGE_TOKEN; + } else if (FullBackup.APPS_PREFIX.regionMatches(0, + info.path, 0, FullBackup.APPS_PREFIX.length())) { + // App content! Parse out the package name and domain + + // strip the apps/ prefix + info.path = info.path.substring(FullBackup.APPS_PREFIX.length()); + + // extract the package name + int slash = info.path.indexOf('/'); + if (slash < 0) throw new IOException("Illegal semantic path in " + info.path); + info.packageName = info.path.substring(0, slash); + info.path = info.path.substring(slash+1); + + // if it's a manifest we're done, otherwise parse out the domains + if (!info.path.equals(BACKUP_MANIFEST_FILENAME)) { + slash = info.path.indexOf('/'); + if (slash < 0) throw new IOException("Illegal semantic path in non-manifest " + info.path); + info.domain = info.path.substring(0, slash); + // validate that it's one of the domains we understand + if (!info.domain.equals(FullBackup.APK_TREE_TOKEN) + && !info.domain.equals(FullBackup.DATA_TREE_TOKEN) + && !info.domain.equals(FullBackup.DATABASE_TREE_TOKEN) + && !info.domain.equals(FullBackup.ROOT_TREE_TOKEN) + && !info.domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN) + && !info.domain.equals(FullBackup.OBB_TREE_TOKEN) + && !info.domain.equals(FullBackup.CACHE_TREE_TOKEN)) { + throw new IOException("Unrecognized domain " + info.domain); + } + + info.path = info.path.substring(slash + 1); + } + } + } + return info; + } + + boolean readTarHeader(InputStream instream, byte[] block) throws IOException { + int nRead = instream.read(block, 0, 512); + if (nRead > 0 && nRead != 512) { + // if we read only a partial block, then things are + // clearly screwed up. terminate the restore. + throw new IOException("Partial header block: " + nRead); + } + return (nRead > 0); + } + + // overwrites 'info' fields based on the pax extended header + boolean readPaxExtendedHeader(InputStream instream, FileMetadata info) + throws IOException { + // We should never see a pax extended header larger than this + if (info.size > 32*1024) { + Slog.w(TAG, "Suspiciously large pax header size " + info.size + + " - aborting"); + throw new IOException("Sanity failure: pax header size " + info.size); + } + + // read whole blocks, not just the content size + int numBlocks = (int)((info.size + 511) >> 9); + byte[] data = new byte[numBlocks * 512]; + int nRead = instream.read(data); + if (nRead != data.length) { + return false; + } + + final int contentSize = (int) info.size; + int offset = 0; + do { + // extract the line at 'offset' + int eol = offset+1; + while (eol < contentSize && data[eol] != ' ') eol++; + if (eol >= contentSize) { + // error: we just hit EOD looking for the end of the size field + throw new IOException("Invalid pax data"); + } + // eol points to the space between the count and the key + int linelen = (int) extractRadix(data, offset, eol - offset, 10); + int key = eol + 1; // start of key=value + eol = offset + linelen - 1; // trailing LF + int value; + for (value = key+1; data[value] != '=' && value <= eol; value++); + if (value > eol) { + throw new IOException("Invalid pax declaration"); + } + + // pax requires that key/value strings be in UTF-8 + String keyStr = new String(data, key, value-key, "UTF-8"); + // -1 to strip the trailing LF + String valStr = new String(data, value+1, eol-value-1, "UTF-8"); + + if ("path".equals(keyStr)) { + info.path = valStr; + } else if ("size".equals(keyStr)) { + info.size = Long.parseLong(valStr); + } else { + if (DEBUG) Slog.i(TAG, "Unhandled pax key: " + key); + } + + offset += linelen; + } while (offset < contentSize); + + return true; + } + + long extractRadix(byte[] data, int offset, int maxChars, int radix) + throws IOException { + long value = 0; + final int end = offset + maxChars; + for (int i = offset; i < end; i++) { + final byte b = data[i]; + if (b == 0 || b == ' ') break; + if (b < '0' || b > ('0' + radix - 1)) { + throw new IOException("Invalid number in header"); + } + value = radix * value + (b - '0'); + } + return value; + } + + String extractString(byte[] data, int offset, int maxChars) throws IOException { + final int end = offset + maxChars; + int eos = offset; + // tar string fields can end with either NUL or SPC + while (eos < end && data[eos] != 0 && data[eos] != ' ') eos++; + return new String(data, offset, eos-offset, "US-ASCII"); + } + + void sendStartRestore() { + if (mObserver != null) { + try { + mObserver.onStartRestore(); + } catch (RemoteException e) { + Slog.w(TAG, "full restore observer went away: startRestore"); + mObserver = null; + } + } + } + + void sendOnRestorePackage(String name) { + if (mObserver != null) { + try { + // TODO: use a more user-friendly name string + mObserver.onRestorePackage(name); + } catch (RemoteException e) { + Slog.w(TAG, "full restore observer went away: restorePackage"); + mObserver = null; + } + } + } + + void sendEndRestore() { + if (mObserver != null) { + try { + mObserver.onEndRestore(); + } catch (RemoteException e) { + Slog.w(TAG, "full restore observer went away: endRestore"); + mObserver = null; + } + } + } + } + // ----- Restore handling ----- private boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) { @@ -1893,7 +3113,7 @@ class BackupManagerService extends IBackupManager.Stub { ParcelFileDescriptor backupData = null; ParcelFileDescriptor newState = null; - int token = mTokenGenerator.nextInt(); + final int token = generateToken(); try { // Run the transport's restore pass backupData = ParcelFileDescriptor.open(backupDataName, @@ -1957,7 +3177,9 @@ class BackupManagerService extends IBackupManager.Stub { try { if (backupData != null) backupData.close(); } catch (IOException e) {} try { if (newState != null) newState.close(); } catch (IOException e) {} backupData = newState = null; - mCurrentOperations.delete(token); + synchronized (mCurrentOperations) { + mCurrentOperations.delete(token); + } // If we know a priori that we'll need to perform a full post-restore backup // pass, clear the new state file data. This means we're discarding work that @@ -2092,7 +3314,7 @@ class BackupManagerService extends IBackupManager.Stub { if (app.packageName.equals(packageName)) { // Add the caller to the set of pending backups. If there is // one already there, then overwrite it, but no harm done. - BackupRequest req = new BackupRequest(app, false); + BackupRequest req = new BackupRequest(app); if (mPendingBackups.put(app.packageName, req) == null) { // Journal this request in case of crash. The put() // operation returned null when this package was not already @@ -2239,10 +3461,196 @@ class BackupManagerService extends IBackupManager.Stub { } } + // Run a *full* backup pass for the given package, writing the resulting data stream + // to the supplied file descriptor. This method is synchronous and does not return + // to the caller until the backup has been completed. + public void fullBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeShared, + boolean doAllApps, String[] pkgList) { + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup"); + + // Validate + if (!doAllApps) { + if (!includeShared) { + // If we're backing up shared data (sdcard or equivalent), then we can run + // without any supplied app names. Otherwise, we'd be doing no work, so + // report the error. + if (pkgList == null || pkgList.length == 0) { + throw new IllegalArgumentException( + "Backup requested but neither shared nor any apps named"); + } + } + } + + if (DEBUG) Slog.v(TAG, "Requesting full backup: apks=" + includeApks + + " shared=" + includeShared + " all=" + doAllApps + + " pkgs=" + pkgList); + + long oldId = Binder.clearCallingIdentity(); + try { + FullBackupParams params = new FullBackupParams(fd, includeApks, includeShared, + doAllApps, pkgList); + final int token = generateToken(); + synchronized (mFullConfirmations) { + mFullConfirmations.put(token, params); + } + + // start up the confirmation UI + if (DEBUG) Slog.d(TAG, "Starting backup confirmation UI, token=" + token); + if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) { + Slog.e(TAG, "Unable to launch full backup confirmation"); + mFullConfirmations.delete(token); + return; + } + + // make sure the screen is lit for the user interaction + mPowerManager.userActivity(SystemClock.uptimeMillis(), false); + + // start the confirmation countdown + startConfirmationTimeout(token, params); + + // wait for the backup to be performed + if (DEBUG) Slog.d(TAG, "Waiting for full backup completion..."); + waitForCompletion(params); + } finally { + try { + fd.close(); + } catch (IOException e) { + // just eat it + } + Binder.restoreCallingIdentity(oldId); + } + if (DEBUG) Slog.d(TAG, "Full backup done; returning to caller"); + } + + public void fullRestore(ParcelFileDescriptor fd) { + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup"); + Slog.i(TAG, "Beginning full restore..."); + + long oldId = Binder.clearCallingIdentity(); + + try { + FullRestoreParams params = new FullRestoreParams(fd); + final int token = generateToken(); + synchronized (mFullConfirmations) { + mFullConfirmations.put(token, params); + } + + // start up the confirmation UI + if (DEBUG) Slog.d(TAG, "Starting restore confirmation UI, token=" + token); + if (!startConfirmationUi(token, FullBackup.FULL_RESTORE_INTENT_ACTION)) { + Slog.e(TAG, "Unable to launch full restore confirmation"); + mFullConfirmations.delete(token); + return; + } + + // make sure the screen is lit for the user interaction + mPowerManager.userActivity(SystemClock.uptimeMillis(), false); + + // start the confirmation countdown + startConfirmationTimeout(token, params); + + // wait for the restore to be performed + if (DEBUG) Slog.d(TAG, "Waiting for full restore completion..."); + waitForCompletion(params); + } finally { + try { + fd.close(); + } catch (IOException e) { + Slog.w(TAG, "Error trying to close fd after full restore: " + e); + } + Binder.restoreCallingIdentity(oldId); + Slog.i(TAG, "Full restore completed"); + } + } + + boolean startConfirmationUi(int token, String action) { + try { + Intent confIntent = new Intent(action); + confIntent.setClassName("com.android.backupconfirm", + "com.android.backupconfirm.BackupRestoreConfirmation"); + confIntent.putExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, token); + confIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(confIntent); + } catch (ActivityNotFoundException e) { + return false; + } + return true; + } + + void startConfirmationTimeout(int token, FullParams params) { + if (DEBUG) Slog.d(TAG, "Posting conf timeout msg after " + + TIMEOUT_FULL_CONFIRMATION + " millis"); + Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT, + token, 0, params); + mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION); + } + + void waitForCompletion(FullParams params) { + synchronized (params.latch) { + while (params.latch.get() == false) { + try { + params.latch.wait(); + } catch (InterruptedException e) { /* never interrupted */ } + } + } + } + + void signalFullBackupRestoreCompletion(FullParams params) { + synchronized (params.latch) { + params.latch.set(true); + params.latch.notifyAll(); + } + } + + // Confirm that the previously-requested full backup/restore operation can proceed. This + // is used to require a user-facing disclosure about the operation. + public void acknowledgeFullBackupOrRestore(int token, boolean allow, + IFullBackupRestoreObserver observer) { + if (DEBUG) Slog.d(TAG, "acknowledgeFullBackupOrRestore : token=" + token + + " allow=" + allow); + + // TODO: possibly require not just this signature-only permission, but even + // require that the specific designated confirmation-UI app uid is the caller? + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup"); + + long oldId = Binder.clearCallingIdentity(); + try { + + FullParams params; + synchronized (mFullConfirmations) { + params = mFullConfirmations.get(token); + if (params != null) { + mBackupHandler.removeMessages(MSG_FULL_CONFIRMATION_TIMEOUT, params); + mFullConfirmations.delete(token); + + if (allow) { + params.observer = observer; + final int verb = params instanceof FullBackupParams + ? MSG_RUN_FULL_BACKUP + : MSG_RUN_FULL_RESTORE; + + if (DEBUG) Slog.d(TAG, "Sending conf message with verb " + verb); + mWakelock.acquire(); + Message msg = mBackupHandler.obtainMessage(verb, params); + mBackupHandler.sendMessage(msg); + } else { + Slog.w(TAG, "User rejected full backup/restore operation"); + // indicate completion without having actually transferred any data + signalFullBackupRestoreCompletion(params); + } + } else { + Slog.w(TAG, "Attempted to ack full backup/restore with invalid token"); + } + } + } finally { + Binder.restoreCallingIdentity(oldId); + } + } + // Enable/disable the backup service public void setBackupEnabled(boolean enable) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "setBackupEnabled"); + "setBackupEnabled"); Slog.i(TAG, "Backup enabled => " + enable); diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java index 47599c8..1aff9a2 100644 --- a/services/java/com/android/server/BatteryService.java +++ b/services/java/com/android/server/BatteryService.java @@ -390,7 +390,7 @@ class BatteryService extends Binder { intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryTechnology); intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger); - if (true) { + if (false) { Slog.d(TAG, "level:" + mBatteryLevel + " scale:" + BATTERY_SCALE + " status:" + mBatteryStatus + " health:" + mBatteryHealth + " present:" + mBatteryPresent + diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 55ce80b..dd76eb8 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -16,6 +16,11 @@ package com.android.server; +import static android.Manifest.permission.UPDATE_DEVICE_STATS; +import static android.net.ConnectivityManager.isNetworkTypeValid; +import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; +import static android.net.NetworkPolicyManager.RULE_REJECT_PAID; + import android.bluetooth.BluetoothTetheringDataTracker; import android.content.ContentResolver; import android.content.Context; @@ -26,11 +31,13 @@ import android.net.ConnectivityManager; import android.net.DummyDataStateTracker; import android.net.EthernetDataTracker; import android.net.IConnectivityManager; -import android.net.LinkAddress; +import android.net.INetworkPolicyListener; +import android.net.INetworkPolicyManager; import android.net.LinkProperties; import android.net.MobileDataStateTracker; import android.net.NetworkConfig; import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; import android.net.NetworkStateTracker; import android.net.NetworkUtils; import android.net.Proxy; @@ -39,6 +46,7 @@ import android.net.RouteInfo; import android.net.vpn.VpnManager; import android.net.wifi.WifiStateTracker; import android.os.Binder; +import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -53,22 +61,21 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.EventLog; import android.util.Slog; +import android.util.SparseIntArray; import com.android.internal.telephony.Phone; import com.android.server.connectivity.Tethering; import java.io.FileDescriptor; -import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.net.InetAddress; -import java.net.Inet4Address; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.GregorianCalendar; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; /** * @hide @@ -78,6 +85,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final boolean DBG = true; private static final String TAG = "ConnectivityService"; + private static final boolean LOGD_RULES = false; + // 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 @@ -91,6 +100,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { private Tethering mTethering; private boolean mTetheringConfigValid = false; + /** Currently active network rules by UID. */ + private SparseIntArray mUidRules = new SparseIntArray(); + /** * Sometimes we want to refer to the individual network state * trackers separately, and sometimes we just want to treat them @@ -128,6 +140,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private AtomicBoolean mBackgroundDataEnabled = new AtomicBoolean(true); private INetworkManagementService mNetd; + private INetworkPolicyManager mPolicyManager; private static final int ENABLED = 1; private static final int DISABLED = 0; @@ -250,14 +263,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } RadioAttributes[] mRadioAttributes; - public static synchronized ConnectivityService getInstance(Context context) { - if (sServiceInstance == null) { - sServiceInstance = new ConnectivityService(context); - } - return sServiceInstance; - } - - private ConnectivityService(Context context) { + public ConnectivityService( + Context context, INetworkManagementService netd, INetworkPolicyManager policyManager) { if (DBG) log("ConnectivityService starting up"); HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread"); @@ -290,9 +297,19 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("Error setting defaultDns using " + dns); } - mContext = context; + mContext = checkNotNull(context, "missing Context"); + mNetd = checkNotNull(netd, "missing INetworkManagementService"); + mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager"); - PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + try { + mPolicyManager.registerListener(mPolicyListener); + } catch (RemoteException e) { + // ouch, no rules updates means some processes may never get network + Slog.e(TAG, "unable to register INetworkPolicyListener", e); + } + + final PowerManager powerManager = (PowerManager) context.getSystemService( + Context.POWER_SERVICE); mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mNetTransitionWakeLockTimeout = mContext.getResources().getInteger( com.android.internal.R.integer.config_networkTransitionTimeout); @@ -430,7 +447,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - mTethering = new Tethering(mContext, mHandler.getLooper()); + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService nmService = INetworkManagementService.Stub.asInterface(b); + + mTethering = new Tethering(mContext, nmService, mHandler.getLooper()); mTetheringConfigValid = (((mNetTrackers[ConnectivityManager.TYPE_MOBILE_DUN] != null) || !mTethering.isDunRequired()) && (mTethering.getTetherableUsbRegexs().length != 0 || @@ -533,32 +553,92 @@ public class ConnectivityService extends IConnectivityManager.Stub { } /** + * Check if UID is blocked from using the given {@link NetworkInfo}. + */ + private boolean isNetworkBlocked(NetworkInfo info, int uid) { + synchronized (mUidRules) { + return isNetworkBlockedLocked(info, uid); + } + } + + /** + * Check if UID is blocked from using the given {@link NetworkInfo}. + */ + private boolean isNetworkBlockedLocked(NetworkInfo info, int uid) { + // TODO: expand definition of "paid" network to cover tethered or paid + // hotspot use cases. + final boolean networkIsPaid = info.getType() != ConnectivityManager.TYPE_WIFI; + final int uidRules = mUidRules.get(uid, RULE_ALLOW_ALL); + + if (networkIsPaid && (uidRules & RULE_REJECT_PAID) != 0) { + return true; + } + + // no restrictive rules; network is visible + return false; + } + + /** * 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 */ + @Override public NetworkInfo getActiveNetworkInfo() { - return getNetworkInfo(mActiveDefaultNetwork); + enforceAccessPermission(); + final int uid = Binder.getCallingUid(); + return getNetworkInfo(mActiveDefaultNetwork, uid); + } + + @Override + public NetworkInfo getActiveNetworkInfoForUid(int uid) { + enforceConnectivityInternalPermission(); + return getNetworkInfo(mActiveDefaultNetwork, uid); } + @Override public NetworkInfo getNetworkInfo(int networkType) { enforceAccessPermission(); - if (ConnectivityManager.isNetworkTypeValid(networkType)) { - NetworkStateTracker t = mNetTrackers[networkType]; - if (t != null) - return t.getNetworkInfo(); + final int uid = Binder.getCallingUid(); + return getNetworkInfo(networkType, uid); + } + + private NetworkInfo getNetworkInfo(int networkType, int uid) { + NetworkInfo info = null; + if (isNetworkTypeValid(networkType)) { + final NetworkStateTracker tracker = mNetTrackers[networkType]; + if (tracker != null) { + info = tracker.getNetworkInfo(); + if (isNetworkBlocked(info, uid)) { + // network is blocked; clone and override state + info = new NetworkInfo(info); + info.setDetailedState(DetailedState.BLOCKED, null, null); + } + } } - return null; + return info; } + @Override public NetworkInfo[] getAllNetworkInfo() { enforceAccessPermission(); - NetworkInfo[] result = new NetworkInfo[mNetworksDefined]; + final int uid = Binder.getCallingUid(); + final NetworkInfo[] result = new NetworkInfo[mNetworksDefined]; int i = 0; - for (NetworkStateTracker t : mNetTrackers) { - if(t != null) result[i++] = t.getNetworkInfo(); + synchronized (mUidRules) { + for (NetworkStateTracker tracker : mNetTrackers) { + if (tracker != null) { + NetworkInfo info = tracker.getNetworkInfo(); + if (isNetworkBlockedLocked(info, uid)) { + // network is blocked; clone and override state + info = new NetworkInfo(info); + info.setDetailedState(DetailedState.BLOCKED, null, null); + } + result[i++] = info; + } + } } return result; } @@ -571,15 +651,19 @@ public class ConnectivityService extends IConnectivityManager.Stub { * @return the ip properties for the active network, or {@code null} if * none is active */ + @Override public LinkProperties getActiveLinkProperties() { return getLinkProperties(mActiveDefaultNetwork); } + @Override public LinkProperties getLinkProperties(int networkType) { enforceAccessPermission(); - if (ConnectivityManager.isNetworkTypeValid(networkType)) { - NetworkStateTracker t = mNetTrackers[networkType]; - if (t != null) return t.getLinkProperties(); + if (isNetworkTypeValid(networkType)) { + final NetworkStateTracker tracker = mNetTrackers[networkType]; + if (tracker != null) { + return tracker.getLinkProperties(); + } } return null; } @@ -1024,6 +1108,30 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() { + @Override + public void onRulesChanged(int uid, int uidRules) { + // only someone like NPMS should only be calling us + // TODO: create permission for modifying data policy + mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG); + + if (LOGD_RULES) { + Slog.d(TAG, "onRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")"); + } + + synchronized (mUidRules) { + // skip update when we've already applied rules + final int oldRules = mUidRules.get(uid, RULE_ALLOW_ALL); + if (oldRules == uidRules) return; + + mUidRules.put(uid, uidRules); + } + + // TODO: dispatch into NMS to push rules towards kernel module + // TODO: notify UID when it has requested targeted updates + } + }; + /** * @see ConnectivityManager#setMobileDataEnabled(boolean) */ @@ -1281,9 +1389,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } void systemReady() { - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - mNetd = INetworkManagementService.Stub.asInterface(b); - synchronized(this) { mSystemReady = true; if (mInitialBroadcast != null) { @@ -1378,13 +1483,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { addDefaultRoute(mNetTrackers[netType]); } else { // many radios add a default route even when we don't want one. - // remove the default interface unless we need it for our active network + // remove the default route unless we need it for our active network if (mActiveDefaultNetwork != -1) { - LinkProperties linkProperties = + LinkProperties defaultLinkProperties = mNetTrackers[mActiveDefaultNetwork].getLinkProperties(); LinkProperties newLinkProperties = mNetTrackers[netType].getLinkProperties(); - String defaultIface = linkProperties.getInterfaceName(); + String defaultIface = defaultLinkProperties.getInterfaceName(); if (defaultIface != null && !defaultIface.equals(newLinkProperties.getInterfaceName())) { removeDefaultRoute(mNetTrackers[netType]); @@ -1549,12 +1654,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (values.length == 6) { final String prefix = "/sys/kernel/ipv4/tcp_"; - stringToFile(prefix + "rmem_min", values[0]); - stringToFile(prefix + "rmem_def", values[1]); - stringToFile(prefix + "rmem_max", values[2]); - stringToFile(prefix + "wmem_min", values[3]); - stringToFile(prefix + "wmem_def", values[4]); - stringToFile(prefix + "wmem_max", values[5]); + FileUtils.stringToFile(prefix + "rmem_min", values[0]); + FileUtils.stringToFile(prefix + "rmem_def", values[1]); + FileUtils.stringToFile(prefix + "rmem_max", values[2]); + FileUtils.stringToFile(prefix + "wmem_min", values[3]); + FileUtils.stringToFile(prefix + "wmem_def", values[4]); + FileUtils.stringToFile(prefix + "wmem_max", values[5]); } else { loge("Invalid buffersize string: " + bufferSizes); } @@ -1563,23 +1668,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - /** - * Writes string to file. Basically same as "echo -n $string > $filename" - * - * @param filename - * @param string - * @throws IOException - */ - private void stringToFile(String filename, String string) throws IOException { - FileWriter out = new FileWriter(filename); - try { - out.write(string); - } finally { - out.close(); - } - } - - /** * Adjust the per-process dns entries (net.dns<x>.<pid>) based * on the highest priority active net which this process requested. @@ -2269,4 +2357,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { } return networkType; } + + private static <T> T checkNotNull(T value, String message) { + if (value == null) { + throw new NullPointerException(message); + } + return value; + } } diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index 3bd8215..3181a9d 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -1925,9 +1925,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Check for permissions if a particular caller is specified if (who != null) { // When checking for a single caller, status is based on caller's request - ActiveAdmin ap = getActiveAdminForCallerLocked(who, - DeviceAdminInfo.USES_ENCRYPTED_STORAGE); - return ap.encryptionRequested; + ActiveAdmin ap = getActiveAdminUncheckedLocked(who); + return ap != null ? ap.encryptionRequested : false; } // If no particular caller is specified, return the aggregate set of requests. diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java index 0fba7c3..d34087f 100644 --- a/services/java/com/android/server/DeviceStorageMonitorService.java +++ b/services/java/com/android/server/DeviceStorageMonitorService.java @@ -34,7 +34,6 @@ import android.os.StatFs; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; -import android.util.Config; import android.util.EventLog; import android.util.Slog; @@ -56,10 +55,10 @@ import android.util.Slog; * settings parameter with a default value of 2MB), the free memory is * logged to the event log. */ -class DeviceStorageMonitorService extends Binder { +public class DeviceStorageMonitorService extends Binder { private static final String TAG = "DeviceStorageMonitorService"; private static final boolean DEBUG = false; - private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; + private static final boolean localLOGV = false; private static final int DEVICE_MEMORY_WHAT = 1; private static final int MONITOR_INTERVAL = 1; //in minutes private static final int LOW_MEMORY_NOTIFICATION_ID = 1; @@ -99,7 +98,7 @@ class DeviceStorageMonitorService extends Binder { /** * This string is used for ServiceManager access to this class. */ - static final String SERVICE = "devicestoragemonitor"; + public static final String SERVICE = "devicestoragemonitor"; /** * Handler that checks the amount of disk space on the device and sends a @@ -398,4 +397,24 @@ class DeviceStorageMonitorService extends Binder { // force an early check postCheckMemoryMsg(true, 0); } + + /** + * Callable from other things in the system service to obtain the low memory + * threshold. + * + * @return low memory threshold in bytes + */ + public long getMemoryLowThreshold() { + return mMemLowThreshold; + } + + /** + * Callable from other things in the system process to check whether memory + * is low. + * + * @return true is memory is low + */ + public boolean isMemoryLow() { + return mLowMemFlag; + } } diff --git a/services/java/com/android/server/EntropyService.java b/services/java/com/android/server/EntropyService.java index 0f1fc78..788a2f5 100644 --- a/services/java/com/android/server/EntropyService.java +++ b/services/java/com/android/server/EntropyService.java @@ -96,7 +96,7 @@ public class EntropyService extends Binder { private void loadInitialEntropy() { try { - RandomBlock.fromFile(entropyFile).toFile(randomDevice); + RandomBlock.fromFile(entropyFile).toFile(randomDevice, false); } catch (IOException e) { Slog.w(TAG, "unable to load initial entropy (first boot?)", e); } @@ -104,7 +104,7 @@ public class EntropyService extends Binder { private void writeEntropy() { try { - RandomBlock.fromFile(randomDevice).toFile(entropyFile); + RandomBlock.fromFile(randomDevice).toFile(entropyFile, true); } catch (IOException e) { Slog.w(TAG, "unable to write entropy", e); } diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 7a0dc3e..f4308cd 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -64,7 +64,9 @@ import android.provider.Settings; import android.provider.Settings.Secure; import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; +import android.text.style.SuggestionSpan; import android.util.EventLog; +import android.util.LruCache; import android.util.Pair; import android.util.Slog; import android.util.PrintWriterPrinter; @@ -117,15 +119,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final long TIME_TO_RECONNECT = 10*1000; + static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20; + private static final int NOT_A_SUBTYPE_ID = -1; private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID); private static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; private static final String SUBTYPE_MODE_VOICE = "voice"; - // TODO: Will formalize this value as API - private static final String SUBTYPE_EXTRAVALUE_EXCLUDE_FROM_LAST_IME = - "excludeFromLastInputMethod"; - final Context mContext; final Resources mRes; final Handler mHandler; @@ -141,6 +141,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // lock for this class. final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>(); final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>(); + private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans = + new LruCache<SuggestionSpan, InputMethodInfo>(SECURE_SUGGESTION_SPANS_MAX_SIZE); class SessionState { final ClientState client; @@ -965,6 +967,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @Override public void updateStatusIcon(IBinder token, String packageName, int iconId) { int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); @@ -989,6 +992,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @Override public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); @@ -1008,6 +1012,42 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) { + synchronized (mMethodMap) { + final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId); + for (int i = 0; i < spans.length; ++i) { + SuggestionSpan ss = spans[i]; + if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) { + mSecureSuggestionSpans.put(ss, currentImi); + final InputMethodInfo targetImi = mSecureSuggestionSpans.get(ss); + } + } + } + } + + public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) { + synchronized (mMethodMap) { + final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span); + // TODO: Do not send the intent if the process of the targetImi is already dead. + if (targetImi != null) { + final String[] suggestions = span.getSuggestions(); + if (index < 0 || index >= suggestions.length) return false; + final String className = span.getNotificationTargetClassName(); + final Intent intent = new Intent(); + // Ensures that only a class in the original IME package will receive the + // notification. + intent.setClassName(targetImi.getPackageName(), className); + intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED); + intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString); + intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]); + intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode()); + mContext.sendBroadcast(intent); + return true; + } + } + return false; + } + void updateFromSettingsLocked() { // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and // ENABLED_INPUT_METHODS is taking care of keeping them correctly in @@ -1102,6 +1142,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @Override public boolean showSoftInput(IInputMethodClient client, int flags, ResultReceiver resultReceiver) { int uid = Binder.getCallingUid(); @@ -1167,6 +1208,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return res; } + @Override public boolean hideSoftInput(IInputMethodClient client, int flags, ResultReceiver resultReceiver) { int uid = Binder.getCallingUid(); @@ -1229,6 +1271,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return res; } + @Override public void windowGainedFocus(IInputMethodClient client, IBinder windowToken, boolean viewHasFocus, boolean isTextEditor, int softInputMode, boolean first, int windowFlags) { @@ -1330,6 +1373,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @Override public void showInputMethodPickerFromClient(IInputMethodClient client) { synchronized (mMethodMap) { if (mCurClient == null || client == null @@ -1344,10 +1388,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @Override public void setInputMethod(IBinder token, String id) { setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID); } + @Override public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) { synchronized (mMethodMap) { if (subtype != null) { @@ -1359,6 +1405,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @Override public void showInputMethodAndSubtypeEnablerFromClient( IInputMethodClient client, String inputMethodId) { synchronized (mMethodMap) { @@ -1438,6 +1485,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + public InputMethodSubtype getLastInputMethodSubtype() { + synchronized (mMethodMap) { + final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); + // TODO: Handle the case of the last IME with no subtypes + if (lastIme == null || TextUtils.isEmpty(lastIme.first) + || TextUtils.isEmpty(lastIme.second)) return null; + final InputMethodInfo lastImi = mMethodMap.get(lastIme.first); + if (lastImi == null) return null; + try { + final int lastSubtypeHash = Integer.valueOf(lastIme.second); + return lastImi.getSubtypeAt(getSubtypeIdFromHashCode( + lastImi, lastSubtypeHash)); + } catch (NumberFormatException e) { + return null; + } + } + } + private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) { synchronized (mMethodMap) { if (token == null) { @@ -1463,6 +1528,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @Override public void hideMySoftInput(IBinder token, int flags) { synchronized (mMethodMap) { if (token == null || mCurToken != token) { @@ -1479,6 +1545,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @Override public void showMySoftInput(IBinder token, int flags) { synchronized (mMethodMap) { if (token == null || mCurToken != token) { @@ -1515,6 +1582,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @Override public boolean handleMessage(Message msg) { HandlerCaller.SomeArgs args; switch (msg.what) { @@ -1811,13 +1879,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final int subtypeCount = imi.getSubtypeCount(); for (int j = 0; j < subtypeCount; ++j) { InputMethodSubtype subtype = imi.getSubtypeAt(j); - if (enabledSubtypeSet.contains(String.valueOf(subtype.hashCode()))) { + if (enabledSubtypeSet.contains(String.valueOf(subtype.hashCode())) + && !subtype.isAuxiliary()) { final CharSequence title; int nameResId = subtype.getNameResId(); String mode = subtype.getMode(); if (nameResId != 0) { - title = TextUtils.concat(pm.getText(imi.getPackageName(), - nameResId, imi.getServiceInfo().applicationInfo), + title = TextUtils.concat(subtype.getDisplayName(context, + imi.getPackageName(), imi.getServiceInfo().applicationInfo), (TextUtils.isEmpty(label) ? "" : " (" + label + ")")); } else { CharSequence language = subtype.getLocale(); @@ -1858,6 +1927,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { hideInputMethodMenu(); } @@ -1869,6 +1939,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mDialogBuilder = new AlertDialog.Builder(context) .setTitle(com.android.internal.R.string.select_input_method) .setOnCancelListener(new OnCancelListener() { + @Override public void onCancel(DialogInterface dialog) { hideInputMethodMenu(); } @@ -1879,6 +1950,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mDialogBuilder.setSingleChoiceItems(mItems, checkedItem, new AlertDialog.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { synchronized (mMethodMap) { if (mIms == null || mIms.length <= which @@ -1903,6 +1975,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mDialogBuilder.setPositiveButton( com.android.internal.R.string.configure_input_methods, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { showConfigureInputMethods(); } @@ -1938,6 +2011,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // ---------------------------------------------------------------------- + @Override public boolean setInputMethodEnabled(String id, boolean enabled) { synchronized (mMethodMap) { if (mContext.checkCallingOrSelfPermission( @@ -2001,7 +2075,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private boolean canAddToLastInputMethod(InputMethodSubtype subtype) { if (subtype == null) return true; - return !subtype.containsExtraValueKey(SUBTYPE_EXTRAVALUE_EXCLUDE_FROM_LAST_IME); + return !subtype.isAuxiliary(); } private void saveCurrentInputMethodAndSubtypeToHistory() { @@ -2268,6 +2342,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub /** * @return Return the current subtype of this input method. */ + @Override public InputMethodSubtype getCurrentInputMethodSubtype() { boolean subtypeIsSelected = false; try { @@ -2350,6 +2425,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @Override public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { synchronized (mMethodMap) { if (subtype != null && mCurMethodId != null) { diff --git a/services/java/com/android/server/IntentResolver.java b/services/java/com/android/server/IntentResolver.java index b78389b..1d3e3ac 100644 --- a/services/java/com/android/server/IntentResolver.java +++ b/services/java/com/android/server/IntentResolver.java @@ -35,7 +35,6 @@ import android.util.Slog; import android.util.LogPrinter; import android.util.Printer; -import android.util.Config; import android.content.Intent; import android.content.IntentFilter; @@ -45,7 +44,7 @@ import android.content.IntentFilter; public class IntentResolver<F extends IntentFilter, R extends Object> { final private static String TAG = "IntentResolver"; final private static boolean DEBUG = false; - final private static boolean localLOGV = DEBUG || Config.LOGV; + final private static boolean localLOGV = DEBUG || false; public void addFilter(F f) { if (localLOGV) { diff --git a/services/java/com/android/server/LightsService.java b/services/java/com/android/server/LightsService.java index 21f2bcf..1e95f3e 100644 --- a/services/java/com/android/server/LightsService.java +++ b/services/java/com/android/server/LightsService.java @@ -148,7 +148,6 @@ public class LightsService { fis.close(); return (result != '0'); } catch (Exception e) { - Slog.e(TAG, "getFlashlightEnabled failed", e); return false; } } @@ -168,7 +167,7 @@ public class LightsService { fos.write(bytes); fos.close(); } catch (Exception e) { - Slog.e(TAG, "setFlashlightEnabled failed", e); + // fail silently } } }; diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index 8570e6e..f78dca9 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -19,6 +19,7 @@ package com.android.server; import com.android.internal.app.IMediaContainerService; import com.android.internal.util.XmlUtils; import com.android.server.am.ActivityManagerService; +import com.android.server.pm.PackageManagerService; import android.Manifest; import android.content.BroadcastReceiver; @@ -98,6 +99,9 @@ class MountService extends IMountService.Stub implements INativeDaemonConnectorC private static final String VOLD_TAG = "VoldConnector"; + /** Maximum number of ASEC containers allowed to be mounted. */ + private static final int MAX_CONTAINERS = 250; + /* * Internal vold volume state constants */ @@ -524,7 +528,6 @@ class MountService extends IMountService.Stub implements INativeDaemonConnectorC } } }; - private final class MountServiceBinderListener implements IBinder.DeathRecipient { final IMountServiceListener mListener; @@ -1207,8 +1210,7 @@ class MountService extends IMountService.Stub implements INativeDaemonConnectorC * amount of containers we'd ever expect to have. This keeps an * "asec list" from blocking a thread repeatedly. */ - mConnector = new NativeDaemonConnector(this, "vold", - PackageManagerService.MAX_CONTAINERS * 2, VOLD_TAG); + mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG); mReady = false; Thread thread = new Thread(mConnector, VOLD_TAG); thread.start(); diff --git a/services/java/com/android/server/NetStatService.java b/services/java/com/android/server/NetStatService.java deleted file mode 100644 index 7fe6743..0000000 --- a/services/java/com/android/server/NetStatService.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import android.content.Context; -import android.net.TrafficStats; -import android.os.INetStatService; -import android.os.SystemClock; - -import java.io.FileDescriptor; -import java.io.PrintWriter; - -public class NetStatService extends INetStatService.Stub { - private final Context mContext; - - public NetStatService(Context context) { - mContext = context; - } - - public long getMobileTxPackets() { - return TrafficStats.getMobileTxPackets(); - } - - public long getMobileRxPackets() { - return TrafficStats.getMobileRxPackets(); - } - - public long getMobileTxBytes() { - return TrafficStats.getMobileTxBytes(); - } - - public long getMobileRxBytes() { - return TrafficStats.getMobileRxBytes(); - } - - public long getTotalTxPackets() { - return TrafficStats.getTotalTxPackets(); - } - - public long getTotalRxPackets() { - return TrafficStats.getTotalRxPackets(); - } - - public long getTotalTxBytes() { - return TrafficStats.getTotalTxBytes(); - } - - public long getTotalRxBytes() { - return TrafficStats.getTotalRxBytes(); - } - - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - // This data is accessible to any app -- no permission check needed. - - pw.print("Elapsed: total="); - pw.print(SystemClock.elapsedRealtime()); - pw.print("ms awake="); - pw.print(SystemClock.uptimeMillis()); - pw.println("ms"); - - pw.print("Mobile: Tx="); - pw.print(getMobileTxBytes()); - pw.print("B/"); - pw.print(getMobileTxPackets()); - pw.print("Pkts Rx="); - pw.print(getMobileRxBytes()); - pw.print("B/"); - pw.print(getMobileRxPackets()); - pw.println("Pkts"); - - pw.print("Total: Tx="); - pw.print(getTotalTxBytes()); - pw.print("B/"); - pw.print(getTotalTxPackets()); - pw.print("Pkts Rx="); - pw.print(getTotalRxBytes()); - pw.print("B/"); - pw.print(getTotalRxPackets()); - pw.println("Pkts"); - } -} diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index 0b4b958..8f179f5 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -16,59 +16,52 @@ package com.android.server; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Resources; import android.content.pm.PackageManager; -import android.net.Uri; -import android.net.InterfaceConfiguration; import android.net.INetworkManagementEventObserver; +import android.net.InterfaceConfiguration; import android.net.LinkAddress; +import android.net.NetworkStats; import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; +import android.os.Binder; import android.os.INetworkManagementService; -import android.os.Handler; +import android.os.SystemClock; import android.os.SystemProperties; -import android.text.TextUtils; import android.util.Log; import android.util.Slog; -import java.util.ArrayList; -import java.util.NoSuchElementException; -import java.util.StringTokenizer; -import android.provider.Settings; -import android.content.ContentResolver; -import android.database.ContentObserver; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileReader; -import java.io.InputStreamReader; import java.io.IOException; -import java.lang.IllegalStateException; -import java.net.InetAddress; +import java.io.InputStreamReader; import java.net.Inet4Address; -import java.net.UnknownHostException; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; import java.util.concurrent.CountDownLatch; +import libcore.io.IoUtils; + /** * @hide */ class NetworkManagementService extends INetworkManagementService.Stub { - - private static final String TAG = "NetworkManagmentService"; + private static final String TAG = "NetworkManagementService"; private static final boolean DBG = false; private static final String NETD_TAG = "NetdConnector"; private static final int ADD = 1; private static final int REMOVE = 2; + /** Base path to UID-granularity network statistics. */ + private static final File PATH_PROC_UID_STAT = new File("/proc/uid_stat"); + class NetdResponseCode { public static final int InterfaceListResult = 110; public static final int TetherInterfaceListResult = 111; @@ -883,12 +876,62 @@ class NetworkManagementService extends INetworkManagementService.Stub { return -1; } - public long getInterfaceRxCounter(String iface) { - return getInterfaceCounter(iface, true); + @Override + public NetworkStats getNetworkStatsSummary() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + + final String[] ifaces = listInterfaces(); + final NetworkStats.Builder stats = new NetworkStats.Builder( + SystemClock.elapsedRealtime(), ifaces.length); + + for (String iface : ifaces) { + final long rx = getInterfaceCounter(iface, true); + final long tx = getInterfaceCounter(iface, false); + stats.addEntry(iface, NetworkStats.UID_ALL, rx, tx); + } + + return stats.build(); + } + + @Override + public NetworkStats getNetworkStatsDetail() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + + final String[] knownUids = PATH_PROC_UID_STAT.list(); + final NetworkStats.Builder stats = new NetworkStats.Builder( + SystemClock.elapsedRealtime(), knownUids.length); + + for (String uid : knownUids) { + final int uidInt = Integer.parseInt(uid); + collectNetworkStatsDetail(stats, uidInt); + } + + return stats.build(); + } + + @Override + public NetworkStats getNetworkStatsUidDetail(int uid) { + if (Binder.getCallingUid() != uid) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + } + + final NetworkStats.Builder stats = new NetworkStats.Builder( + SystemClock.elapsedRealtime(), 1); + collectNetworkStatsDetail(stats, uid); + return stats.build(); } - public long getInterfaceTxCounter(String iface) { - return getInterfaceCounter(iface, false); + private void collectNetworkStatsDetail(NetworkStats.Builder stats, int uid) { + // TODO: kernel module will provide interface-level stats in future + // TODO: migrate these stats to come across netd in bulk, instead of all + // these individual file reads. + final File uidPath = new File(PATH_PROC_UID_STAT, Integer.toString(uid)); + final long rx = readSingleLongFromFile(new File(uidPath, "tcp_rcv")); + final long tx = readSingleLongFromFile(new File(uidPath, "tcp_snd")); + stats.addEntry(NetworkStats.IFACE_ALL, uid, rx, tx); } public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) { @@ -949,4 +992,19 @@ class NetworkManagementService extends INetworkManagementService.Stub { public int getInterfaceTxThrottle(String iface) { return getInterfaceThrottle(iface, false); } + + /** + * Utility method to read a single plain-text {@link Long} from the given + * {@link File}, usually from a {@code /proc/} filesystem. + */ + private static long readSingleLongFromFile(File file) { + try { + final byte[] buffer = IoUtils.readFileAsByteArray(file.toString()); + return Long.parseLong(new String(buffer).trim()); + } catch (NumberFormatException e) { + return -1; + } catch (IOException e) { + return -1; + } + } } diff --git a/services/java/com/android/server/ProcessStats.java b/services/java/com/android/server/ProcessStats.java index 1a12a84..f693ed1 100644 --- a/services/java/com/android/server/ProcessStats.java +++ b/services/java/com/android/server/ProcessStats.java @@ -20,7 +20,6 @@ import static android.os.Process.*; import android.os.Process; import android.os.SystemClock; -import android.util.Config; import android.util.Slog; import java.io.File; @@ -35,7 +34,7 @@ import java.util.StringTokenizer; public class ProcessStats { private static final String TAG = "ProcessStats"; private static final boolean DEBUG = false; - private static final boolean localLOGV = DEBUG || Config.LOGV; + private static final boolean localLOGV = DEBUG || false; private static final int[] PROCESS_STATS_FORMAT = new int[] { PROC_SPACE_TERM, diff --git a/services/java/com/android/server/RandomBlock.java b/services/java/com/android/server/RandomBlock.java index cc22bd9..e5d7301 100644 --- a/services/java/com/android/server/RandomBlock.java +++ b/services/java/com/android/server/RandomBlock.java @@ -62,11 +62,11 @@ class RandomBlock { return retval; } - void toFile(String filename) throws IOException { + void toFile(String filename, boolean sync) throws IOException { if (DEBUG) Slog.v(TAG, "writing to file " + filename); RandomAccessFile out = null; try { - out = new RandomAccessFile(filename, "rws"); + out = new RandomAccessFile(filename, sync ? "rws" : "rw"); toDataOut(out); truncateIfPossible(out); } finally { diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java index 8df8177..1d2072c 100644 --- a/services/java/com/android/server/StatusBarManagerService.java +++ b/services/java/com/android/server/StatusBarManagerService.java @@ -122,6 +122,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub // ================================================================================ // From IStatusBarService // ================================================================================ + public void userActivity() { + if (mBar != null) try { + mBar.userActivity(); + } catch (RemoteException ex) {} + } public void expand() { enforceExpandStatusBar(); diff --git a/services/java/com/android/server/SystemBackupAgent.java b/services/java/com/android/server/SystemBackupAgent.java index 80b0174..99c8af6 100644 --- a/services/java/com/android/server/SystemBackupAgent.java +++ b/services/java/com/android/server/SystemBackupAgent.java @@ -20,6 +20,7 @@ package com.android.server; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.app.backup.BackupAgentHelper; +import android.app.backup.FullBackup; import android.app.backup.WallpaperBackupHelper; import android.content.Context; import android.os.ParcelFileDescriptor; @@ -36,13 +37,29 @@ import java.io.IOException; public class SystemBackupAgent extends BackupAgentHelper { 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"; + // These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME + // are also used in the full-backup file format, so must not change unless steps are + // taken to support the legacy backed-up datasets. + private static final String WALLPAPER_IMAGE_FILENAME = "wallpaper"; + private static final String WALLPAPER_INFO_FILENAME = "wallpaper_info.xml"; + + private static final String WALLPAPER_IMAGE_DIR = "/data/data/com.android.settings/files"; + private static final String WALLPAPER_IMAGE = WALLPAPER_IMAGE_DIR + "/" + WALLPAPER_IMAGE_FILENAME; + + private static final String WALLPAPER_INFO_DIR = "/data/system"; + private static final String WALLPAPER_INFO = WALLPAPER_INFO_DIR + "/" + WALLPAPER_INFO_FILENAME; + @Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException { + if (oldState == null) { + // Ah, it's a full backup dataset, being restored piecemeal. Just + // pop over to the full restore handling and we're done. + runFullBackup(data); + return; + } + // We only back up the data under the current "wallpaper" schema with metadata WallpaperManagerService wallpaper = (WallpaperManagerService)ServiceManager.getService( Context.WALLPAPER_SERVICE); @@ -57,6 +74,21 @@ public class SystemBackupAgent extends BackupAgentHelper { super.onBackup(oldState, data, newState); } + private void runFullBackup(BackupDataOutput output) { + fullWallpaperBackup(output); + } + + private void fullWallpaperBackup(BackupDataOutput output) { + // Back up the data files directly. We do them in this specific order -- + // info file followed by image -- because then we need take no special + // steps during restore; the restore will happen properly when the individual + // files are restored piecemeal. + FullBackup.backupToTar(getPackageName(), FullBackup.ROOT_TREE_TOKEN, null, + WALLPAPER_INFO_DIR, WALLPAPER_INFO, output); + FullBackup.backupToTar(getPackageName(), FullBackup.ROOT_TREE_TOKEN, null, + WALLPAPER_IMAGE_DIR, WALLPAPER_IMAGE, output); + } + @Override public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { @@ -80,4 +112,46 @@ public class SystemBackupAgent extends BackupAgentHelper { (new File(WALLPAPER_INFO)).delete(); } } + + @Override + public void onRestoreFile(ParcelFileDescriptor data, long size, + int type, String domain, String path, long mode, long mtime) + throws IOException { + Slog.i(TAG, "Restoring file domain=" + domain + " path=" + path); + + // Bits to indicate postprocessing we may need to perform + boolean restoredWallpaper = false; + + File outFile = null; + // Various domain+files we understand a priori + if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) { + if (path.equals(WALLPAPER_INFO_FILENAME)) { + outFile = new File(WALLPAPER_INFO); + restoredWallpaper = true; + } else if (path.equals(WALLPAPER_IMAGE_FILENAME)) { + outFile = new File(WALLPAPER_IMAGE); + restoredWallpaper = true; + } + } + + try { + if (outFile == null) { + Slog.w(TAG, "Skipping unrecognized system file: [ " + domain + " : " + path + " ]"); + } + FullBackup.restoreToFile(data, size, type, mode, mtime, outFile); + + if (restoredWallpaper) { + WallpaperManagerService wallpaper = + (WallpaperManagerService)ServiceManager.getService( + Context.WALLPAPER_SERVICE); + wallpaper.settingsRestored(); + } + } catch (IOException e) { + if (restoredWallpaper) { + // Make sure we wind up in a good state + (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 d160963..596cbac 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -16,16 +16,6 @@ package com.android.server; -import com.android.server.am.ActivityManagerService; -import com.android.server.usb.UsbService; -import com.android.server.wm.WindowManagerService; -import com.android.internal.app.ShutdownThread; -import com.android.internal.os.BinderInternal; -import com.android.internal.os.SamplingProfilerIntegration; - -import dalvik.system.VMRuntime; -import dalvik.system.Zygote; - import android.accounts.AccountManagerService; import android.app.ActivityManagerNative; import android.bluetooth.BluetoothAdapter; @@ -38,25 +28,35 @@ import android.content.pm.IPackageManager; import android.content.res.Configuration; import android.database.ContentObserver; import android.media.AudioService; -import android.os.Build; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; -import android.provider.Contacts.People; import android.provider.Settings; import android.server.BluetoothA2dpService; import android.server.BluetoothService; import android.server.search.SearchManagerService; import android.util.DisplayMetrics; import android.util.EventLog; -import android.util.Log; import android.util.Slog; -import android.view.Display; import android.view.WindowManager; +import com.android.internal.app.ShutdownThread; +import com.android.internal.os.BinderInternal; +import com.android.internal.os.SamplingProfilerIntegration; +import com.android.server.accessibility.AccessibilityManagerService; +import com.android.server.am.ActivityManagerService; +import com.android.server.net.NetworkPolicyManagerService; +import com.android.server.net.NetworkStatsService; +import com.android.server.pm.PackageManagerService; +import com.android.server.usb.UsbService; +import com.android.server.wm.WindowManagerService; + +import dalvik.system.VMRuntime; +import dalvik.system.Zygote; + import java.io.File; import java.util.Timer; import java.util.TimerTask; @@ -117,6 +117,10 @@ class ServerThread extends Thread { LightsService lights = null; PowerManagerService power = null; BatteryService battery = null; + AlarmManagerService alarm = null; + NetworkManagementService networkManagement = null; + NetworkStatsService networkStats = null; + NetworkPolicyManagerService networkPolicy = null; ConnectivityService connectivity = null; IPackageManager pm = null; Context context = null; @@ -187,7 +191,7 @@ class ServerThread extends Thread { power.init(context, lights, ActivityManagerService.getDefault(), battery); Slog.i(TAG, "Alarm Manager"); - AlarmManagerService alarm = new AlarmManagerService(context); + alarm = new AlarmManagerService(context); ServiceManager.addService(Context.ALARM_SERVICE, alarm); Slog.i(TAG, "Init Watchdog"); @@ -273,24 +277,33 @@ class ServerThread extends Thread { } try { - Slog.i(TAG, "NetStat Service"); - ServiceManager.addService("netstat", new NetStatService(context)); + Slog.i(TAG, "NetworkManagement Service"); + networkManagement = NetworkManagementService.create(context); + ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting NetworkManagement Service", e); + } + + try { + Slog.i(TAG, "NetworkStats Service"); + networkStats = new NetworkStatsService(context, networkManagement, alarm); + ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats); } catch (Throwable e) { - Slog.e(TAG, "Failure starting NetStat Service", e); + Slog.e(TAG, "Failure starting NetworkStats Service", e); } try { - Slog.i(TAG, "NetworkManagement Service"); - ServiceManager.addService( - Context.NETWORKMANAGEMENT_SERVICE, - NetworkManagementService.create(context)); + Slog.i(TAG, "NetworkPolicy Service"); + networkPolicy = new NetworkPolicyManagerService( + context, ActivityManagerService.self(), power, networkStats); + ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy); } catch (Throwable e) { - Slog.e(TAG, "Failure starting NetworkManagement Service", e); + Slog.e(TAG, "Failure starting NetworkPolicy Service", e); } try { Slog.i(TAG, "Connectivity Service"); - connectivity = ConnectivityService.getInstance(context); + connectivity = new ConnectivityService(context, networkManagement, networkPolicy); ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity); } catch (Throwable e) { Slog.e(TAG, "Failure starting Connectivity Service", e); @@ -526,6 +539,8 @@ class ServerThread extends Thread { // These are needed to propagate to the runnable below. final Context contextF = context; final BatteryService batteryF = battery; + final NetworkStatsService networkStatsF = networkStats; + final NetworkPolicyManagerService networkPolicyF = networkPolicy; final ConnectivityService connectivityF = connectivity; final DockObserver dockF = dock; final UsbService usbF = usb; @@ -551,6 +566,8 @@ class ServerThread extends Thread { startSystemUi(contextF); if (batteryF != null) batteryF.systemReady(); + if (networkStatsF != null) networkStatsF.systemReady(); + if (networkPolicyF != null) networkPolicyF.systemReady(); if (connectivityF != null) connectivityF.systemReady(); if (dockF != null) dockF.systemReady(); if (usbF != null) usbF.systemReady(); diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java index a8d40b7..dd54c16 100644 --- a/services/java/com/android/server/TelephonyRegistry.java +++ b/services/java/com/android/server/TelephonyRegistry.java @@ -279,7 +279,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (!checkNotifyPermission("notifyServiceState()")){ return; } - Slog.i(TAG, "notifyServiceState: " + state); synchronized (mRecords) { mServiceState = state; for (Record r : mRecords) { diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java index d841cb3..510ff62 100644 --- a/services/java/com/android/server/ThrottleService.java +++ b/services/java/com/android/server/ThrottleService.java @@ -16,6 +16,9 @@ package com.android.server; +import com.android.internal.R; +import com.android.internal.telephony.TelephonyProperties; + import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; @@ -30,7 +33,7 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.net.INetworkManagementEventObserver; import android.net.IThrottleManager; -import android.net.SntpClient; +import android.net.NetworkStats; import android.net.ThrottleManager; import android.os.Binder; import android.os.Environment; @@ -47,10 +50,9 @@ import android.os.SystemProperties; import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.NtpTrustedTime; import android.util.Slog; - -import com.android.internal.R; -import com.android.internal.telephony.TelephonyProperties; +import android.util.TrustedTime; import java.io.BufferedWriter; import java.io.File; @@ -60,11 +62,11 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.Calendar; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; import java.util.GregorianCalendar; import java.util.Properties; import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; // TODO - add comments - reference the ThrottleManager for public API public class ThrottleService extends IThrottleManager.Stub { @@ -84,6 +86,11 @@ public class ThrottleService extends IThrottleManager.Stub { private static final int TESTING_RESET_PERIOD_SEC = 60 * 10; private static final long TESTING_THRESHOLD = 1 * 1024 * 1024; + private static final long MAX_NTP_CACHE_AGE = 24 * 60 * 60 * 1000; + private static final long MAX_NTP_FETCH_WAIT = 20 * 1000; + + private long mMaxNtpCacheAge = MAX_NTP_CACHE_AGE; + private int mPolicyPollPeriodSec; private AtomicLong mPolicyThreshold; private AtomicInteger mPolicyThrottleValue; @@ -121,10 +128,24 @@ public class ThrottleService extends IThrottleManager.Stub { private static final int THROTTLE_INDEX_UNTHROTTLED = 0; private static final String PROPERTIES_FILE = "/etc/gps.conf"; - private String mNtpServer; - private boolean mNtpActive; + + private Intent mPollStickyBroadcast; + + private TrustedTime mTime; + + private static INetworkManagementService getNetworkManagementService() { + final IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + return INetworkManagementService.Stub.asInterface(b); + } public ThrottleService(Context context) { + // TODO: move to using cached NtpTrustedTime + this(context, getNetworkManagementService(), new NtpTrustedTime(), + context.getResources().getString(R.string.config_datause_iface)); + } + + public ThrottleService(Context context, INetworkManagementService nmService, TrustedTime time, + String iface) { if (VDBG) Slog.v(TAG, "Starting ThrottleService"); mContext = context; @@ -132,17 +153,15 @@ public class ThrottleService extends IThrottleManager.Stub { mPolicyThrottleValue = new AtomicInteger(); mThrottleIndex = new AtomicInteger(); - mNtpActive = false; - - mIface = mContext.getResources().getString(R.string.config_datause_iface); + mIface = iface; mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); Intent pollIntent = new Intent(ACTION_POLL, null); mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0); Intent resetIntent = new Intent(ACTION_RESET, null); mPendingResetIntent = PendingIntent.getBroadcast(mContext, RESET_REQUEST, resetIntent, 0); - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - mNMService = INetworkManagementService.Stub.asInterface(b); + mNMService = nmService; + mTime = time; mNotificationManager = (NotificationManager)mContext.getSystemService( Context.NOTIFICATION_SERVICE); @@ -189,7 +208,7 @@ public class ThrottleService extends IThrottleManager.Stub { mMsg = msg; } - void observe(Context context) { + void register(Context context) { ContentResolver resolver = context.getContentResolver(); resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.THROTTLE_POLLING_SEC), false, this); @@ -207,6 +226,11 @@ public class ThrottleService extends IThrottleManager.Stub { Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC), false, this); } + void unregister(Context context) { + final ContentResolver resolver = context.getContentResolver(); + resolver.unregisterContentObserver(this); + } + @Override public void onChange(boolean selfChange) { mHandler.obtainMessage(mMsg).sendToTarget(); @@ -220,7 +244,9 @@ public class ThrottleService extends IThrottleManager.Stub { } private long ntpToWallTime(long ntpTime) { - long bestNow = getBestTime(true); // do it quickly + // get time quickly without worrying about trusted state + long bestNow = mTime.hasCache() ? mTime.currentTimeMillis() + : System.currentTimeMillis(); long localNow = System.currentTimeMillis(); return localNow + (ntpTime - bestNow); } @@ -300,7 +326,7 @@ public class ThrottleService extends IThrottleManager.Stub { new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - mHandler.obtainMessage(EVENT_POLL_ALARM).sendToTarget(); + dispatchPoll(); } }, new IntentFilter(ACTION_POLL)); @@ -308,7 +334,7 @@ public class ThrottleService extends IThrottleManager.Stub { new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget(); + dispatchReset(); } }, new IntentFilter(ACTION_RESET)); @@ -318,7 +344,10 @@ public class ThrottleService extends IThrottleManager.Stub { File file = new File(PROPERTIES_FILE); stream = new FileInputStream(file); properties.load(stream); - mNtpServer = properties.getProperty("NTP_SERVER", null); + final String ntpServer = properties.getProperty("NTP_SERVER", null); + if (mTime instanceof NtpTrustedTime) { + ((NtpTrustedTime) mTime).setNtpServer(ntpServer, MAX_NTP_FETCH_WAIT); + } } catch (IOException e) { Slog.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE); } finally { @@ -343,9 +372,33 @@ public class ThrottleService extends IThrottleManager.Stub { } mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED); - mSettingsObserver.observe(mContext); + mSettingsObserver.register(mContext); + } + + void shutdown() { + // TODO: eventually connect with ShutdownThread to persist stats during + // graceful shutdown. + + if (mThread != null) { + mThread.quit(); + } + + if (mSettingsObserver != null) { + mSettingsObserver.unregister(mContext); + } + + if (mPollStickyBroadcast != null) { + mContext.removeStickyBroadcast(mPollStickyBroadcast); + } } + void dispatchPoll() { + mHandler.obtainMessage(EVENT_POLL_ALARM).sendToTarget(); + } + + void dispatchReset() { + mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget(); + } private static final int EVENT_REBOOT_RECOVERY = 0; private static final int EVENT_POLICY_CHANGED = 1; @@ -440,15 +493,17 @@ public class ThrottleService extends IThrottleManager.Stub { mPolicyNotificationsAllowedMask = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.THROTTLE_NOTIFICATION_TYPE, defaultNotificationType); - mMaxNtpCacheAgeSec = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC, MAX_NTP_CACHE_AGE_SEC); + final int maxNtpCacheAgeSec = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC, + (int) (MAX_NTP_CACHE_AGE / 1000)); + mMaxNtpCacheAge = maxNtpCacheAgeSec * 1000; if (VDBG || (mPolicyThreshold.get() != 0)) { Slog.d(TAG, "onPolicyChanged testing=" + testing +", period=" + mPolicyPollPeriodSec + ", threshold=" + mPolicyThreshold.get() + ", value=" + mPolicyThrottleValue.get() + ", resetDay=" + mPolicyResetDay + - ", noteType=" + mPolicyNotificationsAllowedMask + ", maxNtpCacheAge=" + - mMaxNtpCacheAgeSec); + ", noteType=" + mPolicyNotificationsAllowedMask + ", mMaxNtpCacheAge=" + + mMaxNtpCacheAge); } // force updates @@ -464,15 +519,30 @@ public class ThrottleService extends IThrottleManager.Stub { private void onPollAlarm() { long now = SystemClock.elapsedRealtime(); - long next = now + mPolicyPollPeriodSec*1000; + long next = now + mPolicyPollPeriodSec * 1000; - checkForAuthoritativeTime(); + // when trusted cache outdated, try refreshing + if (mTime.getCacheAge() > mMaxNtpCacheAge) { + if (mTime.forceRefresh()) { + if (VDBG) Slog.d(TAG, "updated trusted time, reseting alarm"); + dispatchReset(); + } + } long incRead = 0; long incWrite = 0; try { - incRead = mNMService.getInterfaceRxCounter(mIface) - mLastRead; - incWrite = mNMService.getInterfaceTxCounter(mIface) - mLastWrite; + final NetworkStats stats = mNMService.getNetworkStatsSummary(); + final int index = stats.findIndex(mIface, NetworkStats.UID_ALL); + + if (index != -1) { + incRead = stats.rx[index] - mLastRead; + incWrite = stats.tx[index] - mLastWrite; + } else { + // missing iface, assume stats are 0 + Slog.w(TAG, "unable to find stats for iface " + mIface); + } + // handle iface resets - on some device the 3g iface comes and goes and gets // totals reset to 0. Deal with it if ((incRead < 0) || (incWrite < 0)) { @@ -509,6 +579,7 @@ public class ThrottleService extends IThrottleManager.Stub { broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_START, getPeriodStartTime(mIface)); broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_END, getResetTime(mIface)); mContext.sendStickyBroadcast(broadcast); + mPollStickyBroadcast = broadcast; mAlarmManager.cancel(mPendingPollIntent); mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent); @@ -538,7 +609,8 @@ public class ThrottleService extends IThrottleManager.Stub { // have we spoken with an ntp server yet? // this is controversial, but we'd rather err towards not throttling - if ((mNtpServer != null) && !mNtpActive) { + if (!mTime.hasCache()) { + Slog.w(TAG, "missing trusted time, skipping throttle check"); return; } @@ -697,9 +769,15 @@ public class ThrottleService extends IThrottleManager.Stub { " bytes read and " + mRecorder.getPeriodTx(0) + " written"); } - long now = getBestTime(false); + // when trusted cache outdated, try refreshing + if (mTime.getCacheAge() > mMaxNtpCacheAge) { + mTime.forceRefresh(); + } - if (mNtpActive || (mNtpServer == null)) { + // as long as we have a trusted time cache, we always reset alarms, + // even if the refresh above failed. + if (mTime.hasCache()) { + final long now = mTime.currentTimeMillis(); Calendar end = calculatePeriodEnd(now); Calendar start = calculatePeriodStart(end); @@ -714,56 +792,11 @@ public class ThrottleService extends IThrottleManager.Stub { SystemClock.elapsedRealtime() + offset, mPendingResetIntent); } else { - if (VDBG) Slog.d(TAG, "no authoritative time - not resetting period"); + if (VDBG) Slog.d(TAG, "no trusted time, not resetting period"); } } } - private void checkForAuthoritativeTime() { - if (mNtpActive || (mNtpServer == null)) return; - - // will try to get the ntp time and switch to it if found. - // will also cache the time so we don't fetch it repeatedly. - getBestTime(false); - } - - private static final int MAX_NTP_CACHE_AGE_SEC = 60 * 60 * 24; // 1 day - private static final int MAX_NTP_FETCH_WAIT = 20 * 1000; - private int mMaxNtpCacheAgeSec = MAX_NTP_CACHE_AGE_SEC; - private long cachedNtp; - private long cachedNtpTimestamp; - - // if the request is tied to UI and ANR's are a danger, request a fast result - // the regular polling should have updated the cached time recently using the - // slower method (!fast) - private long getBestTime(boolean fast) { - if (mNtpServer != null) { - if (mNtpActive) { - long ntpAge = SystemClock.elapsedRealtime() - cachedNtpTimestamp; - if (ntpAge < mMaxNtpCacheAgeSec * 1000 || fast) { - if (VDBG) Slog.v(TAG, "using cached time"); - return cachedNtp + ntpAge; - } - } - SntpClient client = new SntpClient(); - if (client.requestTime(mNtpServer, MAX_NTP_FETCH_WAIT)) { - cachedNtp = client.getNtpTime(); - cachedNtpTimestamp = SystemClock.elapsedRealtime(); - if (!mNtpActive) { - mNtpActive = true; - if (VDBG) Slog.d(TAG, "found Authoritative time - reseting alarm"); - mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget(); - } - if (VDBG) Slog.v(TAG, "using Authoritative time: " + cachedNtp); - return cachedNtp; - } - } - long time = System.currentTimeMillis(); - if (VDBG) Slog.v(TAG, "using User time: " + time); - mNtpActive = false; - return time; - } - // records bytecount data for a given time and accumulates it into larger time windows // for logging and other purposes // @@ -929,7 +962,7 @@ public class ThrottleService extends IThrottleManager.Stub { private void checkAndDeleteLRUDataFile(File dir) { File[] files = dir.listFiles(); - if (files.length <= MAX_SIMS_SUPPORTED) return; + if (files == null || files.length <= MAX_SIMS_SUPPORTED) return; if (DBG) Slog.d(TAG, "Too many data files"); do { File oldest = null; @@ -949,9 +982,11 @@ public class ThrottleService extends IThrottleManager.Stub { File newest = null; File[] files = dir.listFiles(); - for (File f : files) { - if ((newest == null) || (newest.lastModified() < f.lastModified())) { - newest = f; + if (files != null) { + for (File f : files) { + if ((newest == null) || (newest.lastModified() < f.lastModified())) { + newest = f; + } } } if (newest == null) { @@ -1126,7 +1161,7 @@ public class ThrottleService extends IThrottleManager.Stub { " seconds."); pw.println("Polling every " + mPolicyPollPeriodSec + " seconds"); pw.println("Current Throttle Index is " + mThrottleIndex.get()); - pw.println("Max NTP Cache Age is " + mMaxNtpCacheAgeSec); + pw.println("mMaxNtpCacheAge=" + mMaxNtpCacheAge); for (int i = 0; i < mRecorder.getPeriodCount(); i++) { pw.println(" Period[" + i + "] - read:" + mRecorder.getPeriodRx(i) + ", written:" + diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java index 2a25c2a..2d3ac00 100644 --- a/services/java/com/android/server/Watchdog.java +++ b/services/java/com/android/server/Watchdog.java @@ -33,7 +33,6 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; -import android.util.Config; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -45,7 +44,7 @@ import java.util.Calendar; /** This class calls its monitor every minute. Killing this process if they don't return **/ public class Watchdog extends Thread { static final String TAG = "Watchdog"; - static final boolean localLOGV = false || Config.LOGV; + static final boolean localLOGV = false || false; // Set this to true to use debug default values. static final boolean DB = false; diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 6acc32f..41ec63a 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -235,6 +235,15 @@ public class WifiService extends IWifiManager.Stub { } break; } + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { + if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) { + Slog.d(TAG, "Send failed, client connection lost"); + } else { + Slog.d(TAG, "Client connection lost with reason: " + msg.arg1); + } + mClients.remove((AsyncChannel) msg.obj); + break; + } case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { AsyncChannel ac = new AsyncChannel(); ac.connect(mContext, this, msg.replyTo); @@ -889,7 +898,7 @@ public class WifiService extends IWifiManager.Stub { evaluateTrafficStatsPolling(); mWifiStateMachine.enableRssiPolling(true); if (mBackgroundScanSupported) { - mWifiStateMachine.enableBackgroundScan(false); + mWifiStateMachine.enableBackgroundScanCommand(false); } mWifiStateMachine.enableAllNetworks(); updateWifiState(); @@ -901,7 +910,7 @@ public class WifiService extends IWifiManager.Stub { evaluateTrafficStatsPolling(); mWifiStateMachine.enableRssiPolling(false); if (mBackgroundScanSupported) { - mWifiStateMachine.enableBackgroundScan(true); + mWifiStateMachine.enableBackgroundScanCommand(true); } /* * Set a timer to put Wi-Fi to sleep, but only if the screen is off diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java index 94531bb..6ef6963 100644 --- a/services/java/com/android/server/WifiWatchdogService.java +++ b/services/java/com/android/server/WifiWatchdogService.java @@ -28,12 +28,12 @@ import android.net.NetworkInfo; import android.net.wifi.ScanResult; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; +import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.provider.Settings; import android.text.TextUtils; -import android.util.Config; import android.util.Slog; import java.io.IOException; @@ -73,8 +73,8 @@ import java.util.Random; */ public class WifiWatchdogService { private static final String TAG = "WifiWatchdogService"; - private static final boolean V = false || Config.LOGV; - private static final boolean D = true || Config.LOGD; + private static final boolean V = false || false; + private static final boolean D = true || false; private Context mContext; private ContentResolver mContentResolver; @@ -509,6 +509,7 @@ public class WifiWatchdogService { // This access point does not require a watchdog, so queue idle on the main thread mHandler.idle(); } + mHandler.checkWalledGarden(ssid); } /** @@ -1003,6 +1004,8 @@ public class WifiWatchdogService { * The object will be an {@link AccessPoint}. */ static final int ACTION_BACKGROUND_CHECK_AP = 3; + /** Check whether the connection is a walled garden */ + static final int ACTION_CHECK_WALLED_GARDEN = 4; /** * Go to sleep for the current network. We are conservative with making @@ -1023,7 +1026,16 @@ public class WifiWatchdogService { static final int MESSAGE_DISCONNECTED = 104; /** Performs a hard-reset on the watchdog state. */ static final int MESSAGE_RESET = 105; - + + /* Walled garden detection */ + private String mLastSsid; + private long mLastTime; + private final long MIN_WALLED_GARDEN_TEST_INTERVAL = 15 * 60 * 1000; //15 minutes + + void checkWalledGarden(String ssid) { + sendMessage(obtainMessage(ACTION_CHECK_WALLED_GARDEN, ssid)); + } + void checkAp(AccessPoint ap) { removeAllActions(); sendMessage(obtainMessage(ACTION_CHECK_AP, ap)); @@ -1085,6 +1097,9 @@ public class WifiWatchdogService { case ACTION_BACKGROUND_CHECK_AP: handleBackgroundCheckAp((AccessPoint) msg.obj); break; + case ACTION_CHECK_WALLED_GARDEN: + handleWalledGardenCheck((String) msg.obj); + break; case MESSAGE_SLEEP: handleSleep((String) msg.obj); break; @@ -1102,6 +1117,43 @@ public class WifiWatchdogService { break; } } + + private boolean isWalledGardenConnection() { + //One way to detect a walled garden is to see if multiple DNS queries + //resolve to the same IP address + try { + String host1 = "www.google.com"; + String host2 = "www.android.com"; + String address1 = InetAddress.getByName(host1).getHostAddress(); + String address2 = InetAddress.getByName(host2).getHostAddress(); + if (address1.equals(address2)) return true; + } catch (UnknownHostException e) { + return false; + } + return false; + } + + private void handleWalledGardenCheck(String ssid) { + long currentTime = System.currentTimeMillis(); + //Avoid a walled garden test on the same network if one was already done + //within MIN_WALLED_GARDEN_TEST_INTERVAL. This will handle scenarios where + //there are frequent network disconnections + if (ssid.equals(mLastSsid) && + (currentTime - mLastTime) < MIN_WALLED_GARDEN_TEST_INTERVAL) { + return; + } + + mLastTime = currentTime; + mLastSsid = ssid; + + if (isWalledGardenConnection()) { + Uri uri = Uri.parse("http://www.google.com"); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | + Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } + } } /** @@ -1264,7 +1316,7 @@ public class WifiWatchdogService { return false; } catch (Exception e) { - if (V || Config.LOGD) { + if (V || false) { Slog.d(TAG, "DnsPinger.isReachable got an unknown exception", e); } return false; diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java new file mode 100644 index 0000000..8ba0a0b --- /dev/null +++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility; + +import com.android.server.wm.InputFilter; + +import android.content.Context; +import android.util.Slog; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.MotionEvent; +import android.view.WindowManagerPolicy; + +/** + * Input filter for accessibility. + * + * Currently just a stub but will eventually implement touch exploration, etc. + */ +public class AccessibilityInputFilter extends InputFilter { + private static final String TAG = "AccessibilityInputFilter"; + private static final boolean DEBUG = false; + + private final Context mContext; + + /** + * This is an interface for explorers that take a {@link MotionEvent} + * stream and perform touch exploration of the screen content. + */ + public interface Explorer { + /** + * Handles a {@link MotionEvent}. + * + * @param event The event to handle. + * @param policyFlags The policy flags associated with the event. + */ + public void onMotionEvent(MotionEvent event, int policyFlags); + + /** + * Requests that the explorer clears its internal state. + * + * @param event The last received event. + * @param policyFlags The policy flags associated with the event. + */ + public void clear(MotionEvent event, int policyFlags); + } + + private TouchExplorer mTouchExplorer; + private int mTouchscreenSourceDeviceId; + + public AccessibilityInputFilter(Context context) { + super(context.getMainLooper()); + mContext = context; + } + + @Override + public void onInstalled() { + if (DEBUG) { + Slog.d(TAG, "Accessibility input filter installed."); + } + super.onInstalled(); + } + + @Override + public void onUninstalled() { + if (DEBUG) { + Slog.d(TAG, "Accessibility input filter uninstalled."); + } + super.onUninstalled(); + } + + @Override + public void onInputEvent(InputEvent event, int policyFlags) { + if (DEBUG) { + Slog.d(TAG, "Received event: " + event + ", policyFlags=0x" + + Integer.toHexString(policyFlags)); + } + if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) { + MotionEvent motionEvent = (MotionEvent) event; + int deviceId = event.getDeviceId(); + if (mTouchscreenSourceDeviceId != deviceId) { + mTouchscreenSourceDeviceId = deviceId; + if (mTouchExplorer != null) { + mTouchExplorer.clear(motionEvent, policyFlags); + } else { + mTouchExplorer = new TouchExplorer(this, mContext); + } + } + if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) != 0) { + mTouchExplorer.onMotionEvent(motionEvent, policyFlags); + } else { + mTouchExplorer.clear(motionEvent, policyFlags); + } + } else { + super.onInputEvent(event, policyFlags); + } + } +} diff --git a/services/java/com/android/server/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 04ae490..7801aec 100644 --- a/services/java/com/android/server/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -14,11 +14,14 @@ ** limitations under the License. */ -package com.android.server; +package com.android.server.accessibility; import com.android.internal.content.PackageMonitor; import com.android.internal.os.HandlerCaller; import com.android.internal.os.HandlerCaller.SomeArgs; +import com.android.server.wm.WindowManagerService; + +import org.xmlpull.v1.XmlPullParserException; import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; @@ -38,21 +41,21 @@ import android.content.pm.ServiceInfo; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; -import android.os.DeadObjectException; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; +import android.os.ServiceManager; import android.provider.Settings; import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; -import android.util.Config; import android.util.Slog; import android.util.SparseArray; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.IAccessibilityManager; import android.view.accessibility.IAccessibilityManagerClient; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -73,6 +76,8 @@ import java.util.Set; public class AccessibilityManagerService extends IAccessibilityManager.Stub implements HandlerCaller.Callback { + private static final boolean DEBUG = false; + private static final String LOG_TAG = "AccessibilityManagerService"; private static int sIdCounter = 0; @@ -92,10 +97,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final List<IAccessibilityManagerClient> mClients = new ArrayList<IAccessibilityManagerClient>(); - final Map<ComponentName, Service> mComponentNameToServiceMap = - new HashMap<ComponentName, Service>(); + final Map<ComponentName, Service> mComponentNameToServiceMap = new HashMap<ComponentName, Service>(); - private final List<ServiceInfo> mInstalledServices = new ArrayList<ServiceInfo>(); + private final List<AccessibilityServiceInfo> mInstalledServices = new ArrayList<AccessibilityServiceInfo>(); private final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>(); @@ -107,6 +111,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private boolean mIsEnabled; + private AccessibilityInputFilter mInputFilter; + + private final List<AccessibilityServiceInfo> mEnabledServicesForFeedbackTempList = new ArrayList<AccessibilityServiceInfo>(); + + private boolean mHasInputFilter; + /** * Handler for delayed event dispatch. */ @@ -131,7 +141,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * * @param context A {@link Context} instance. */ - AccessibilityManagerService(Context context) { + public AccessibilityManagerService(Context context) { mContext = context; mPackageManager = mContext.getPackageManager(); mCaller = new HandlerCaller(context, this); @@ -249,6 +259,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub unbindAllServicesLocked(); } updateClientsLocked(); + updateInputFilterLocked(); } } }); @@ -268,9 +279,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub }); } - public boolean addClient(IAccessibilityManagerClient client) { + public boolean addClient(IAccessibilityManagerClient client) throws RemoteException { synchronized (mLock) { - mClients.add(client); + final IAccessibilityManagerClient addedClient = client; + mClients.add(addedClient); + // Clients are registered all the time until their process is + // killed, therefore we do not have a corresponding unlinkToDeath. + client.asBinder().linkToDeath(new DeathRecipient() { + public void binderDied() { + synchronized (mLock) { + mClients.remove(addedClient); + } + } + }, 0); return mIsEnabled; } } @@ -290,12 +311,31 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return (OWN_PROCESS_ID != Binder.getCallingPid()); } - public List<ServiceInfo> getAccessibilityServiceList() { + public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { synchronized (mLock) { return mInstalledServices; } } + public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) { + List<AccessibilityServiceInfo> result = mEnabledServicesForFeedbackTempList; + List<Service> services = mServices; + synchronized (mLock) { + while (feedbackType != 0) { + final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackType)); + feedbackType &= ~feedbackTypeBit; + final int serviceCount = services.size(); + for (int i = 0; i < serviceCount; i++) { + Service service = services.get(i); + if (service.mFeedbackType == feedbackType) { + result.add(service.mAccessibilityServiceInfo); + } + } + } + } + return result; + } + public void interrupt() { synchronized (mLock) { for (int i = 0, count = mServices.size(); i < count; i++) { @@ -303,16 +343,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub try { service.mServiceInterface.onInterrupt(); } catch (RemoteException re) { - if (re instanceof DeadObjectException) { - Slog.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up."); - if (removeDeadServiceLocked(service)) { - count--; - i--; - } - } else { - Slog.e(LOG_TAG, "Error during sending interrupt request to " - + service.mService, re); - } + Slog.e(LOG_TAG, "Error during sending interrupt request to " + + service.mService, re); } } } @@ -327,14 +359,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub Service service = (Service) arguments.arg2; synchronized (mLock) { - service.mEventTypes = info.eventTypes; - service.mFeedbackType = info.feedbackType; - String[] packageNames = info.packageNames; - if (packageNames != null) { - service.mPackageNames.addAll(Arrays.asList(packageNames)); + // If the XML manifest had data to configure the service its info + // should be already set. In such a case update only the dynamically + // configurable properties. + AccessibilityServiceInfo oldInfo = service.mAccessibilityServiceInfo; + if (oldInfo != null) { + oldInfo.updateDynamicallyConfigurableProperties(info); + service.setAccessibilityServiceInfo(oldInfo); + } else { + service.setAccessibilityServiceInfo(info); + tryAddServiceLocked(service); } - service.mNotificationTimeout = info.notificationTimeout; - service.mIsDefault = (info.flags & AccessibilityServiceInfo.DEFAULT) != 0; + + updateInputFilterLocked(); } return; default: @@ -349,10 +386,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mInstalledServices.clear(); List<ResolveInfo> installedServices = mPackageManager.queryIntentServices( - new Intent(AccessibilityService.SERVICE_INTERFACE), PackageManager.GET_SERVICES); + new Intent(AccessibilityService.SERVICE_INTERFACE), + PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); for (int i = 0, count = installedServices.size(); i < count; i++) { - mInstalledServices.add(installedServices.get(i).serviceInfo); + ResolveInfo resolveInfo = installedServices.get(i); + AccessibilityServiceInfo accessibilityServiceInfo; + try { + accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); + mInstalledServices.add(accessibilityServiceInfo); + } catch (XmlPullParserException xppe) { + Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe); + } catch (IOException ioe) { + Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", ioe); + } } } @@ -445,31 +492,49 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub try { listener.onAccessibilityEvent(event); - if (Config.DEBUG) { + if (DEBUG) { Slog.i(LOG_TAG, "Event " + event + " sent to " + listener); } } catch (RemoteException re) { - if (re instanceof DeadObjectException) { - Slog.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up."); - removeDeadServiceLocked(service); - } else { - Slog.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re); + Slog.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re); + } + } + + /** + * Adds a service. + * + * @param service The service to add. + */ + private void tryAddServiceLocked(Service service) { + try { + if (mServices.contains(service) || !service.isConfigured()) { + return; } + service.linkToOwnDeath(); + mServices.add(service); + mComponentNameToServiceMap.put(service.mComponentName, service); + updateInputFilterLocked(); + } catch (RemoteException e) { + /* do nothing */ } } /** - * Removes a dead service. + * Removes a service. * * @param service The service. * @return True if the service was removed, false otherwise. */ - private boolean removeDeadServiceLocked(Service service) { - if (Config.DEBUG) { - Slog.i(LOG_TAG, "Dead service " + service.mService + " removed"); + private boolean tryRemoveServiceLocked(Service service) { + final boolean removed = mServices.remove(service); + if (!removed) { + return false; } + mComponentNameToServiceMap.remove(service.mComponentName); mHandler.removeMessages(service.mId); - return mServices.remove(service); + service.unlinkToOwnDeath(); + updateInputFilterLocked(); + return removed; } /** @@ -492,11 +557,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return false; } - if (!service.mService.isBinderAlive()) { - removeDeadServiceLocked(service); - return false; - } - int eventType = event.getEventType(); if ((service.mEventTypes & eventType) != eventType) { return false; @@ -574,22 +634,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * @param installedServices All installed {@link AccessibilityService}s. * @param enabledServices The {@link ComponentName}s of the enabled services. */ - private void updateServicesStateLocked(List<ServiceInfo> installedServices, + private void updateServicesStateLocked(List<AccessibilityServiceInfo> installedServices, Set<ComponentName> enabledServices) { Map<ComponentName, Service> componentNameToServiceMap = mComponentNameToServiceMap; boolean isEnabled = mIsEnabled; for (int i = 0, count = installedServices.size(); i < count; i++) { - ServiceInfo intalledService = installedServices.get(i); - ComponentName componentName = new ComponentName(intalledService.packageName, - intalledService.name); + AccessibilityServiceInfo installedService = installedServices.get(i); + ComponentName componentName = ComponentName.unflattenFromString( + installedService.getId()); Service service = componentNameToServiceMap.get(componentName); if (isEnabled) { if (enabledServices.contains(componentName)) { if (service == null) { - service = new Service(componentName); + service = new Service(installedService); } service.bind(); } else if (!enabledServices.contains(componentName)) { @@ -621,6 +681,40 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } /** + * Updates the input filter state. The filter is enabled if accessibility + * is enabled and there is at least one accessibility service providing + * spoken feedback. + */ + private void updateInputFilterLocked() { + WindowManagerService wm = (WindowManagerService)ServiceManager.getService( + Context.WINDOW_SERVICE); + if (mIsEnabled) { + final boolean hasSpokenFeedbackServices = !getEnabledAccessibilityServiceList( + AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty(); + if (hasSpokenFeedbackServices) { + if (mHasInputFilter) { + return; + } + if (mInputFilter == null) { + mInputFilter = new AccessibilityInputFilter(mContext); + } + wm.setInputFilter(mInputFilter); + mHasInputFilter = true; + } else { + if (mHasInputFilter) { + wm.setInputFilter(null); + mHasInputFilter = false; + } + } + } else { + if (mHasInputFilter) { + wm.setInputFilter(null); + mHasInputFilter = false; + } + } + } + + /** * This class represents an accessibility service. It stores all per service * data required for the service management, provides API for starting/stopping the * service and is responsible for adding/removing the service in the data structures @@ -628,9 +722,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * passed to the service it represents as soon it is bound. It also serves as the * connection for the service. */ - class Service extends IAccessibilityServiceConnection.Stub implements ServiceConnection { + class Service extends IAccessibilityServiceConnection.Stub + implements ServiceConnection, DeathRecipient { int mId = 0; + AccessibilityServiceInfo mAccessibilityServiceInfo; + IBinder mService; IEventListener mServiceInterface; @@ -645,8 +742,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub long mNotificationTimeout; - boolean mIsActive; - ComponentName mComponentName; Intent mIntent; @@ -655,9 +750,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<AccessibilityEvent>(); - Service(ComponentName componentName) { + Service(AccessibilityServiceInfo accessibilityServiceInfo) { mId = sIdCounter++; - mComponentName = componentName; + setAccessibilityServiceInfo(accessibilityServiceInfo); mIntent = new Intent().setComponent(mComponentName); mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, com.android.internal.R.string.accessibility_binding_label); @@ -665,6 +760,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0)); } + public void setAccessibilityServiceInfo(AccessibilityServiceInfo info) { + mAccessibilityServiceInfo = info; + ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo; + mComponentName = new ComponentName(serviceInfo.packageName, serviceInfo.name); + mEventTypes = info.eventTypes; + mFeedbackType = info.feedbackType; + String[] packageNames = info.packageNames; + if (packageNames != null) { + mPackageNames.addAll(Arrays.asList(packageNames)); + } + mNotificationTimeout = info.notificationTimeout; + mIsDefault = (info.flags & AccessibilityServiceInfo.DEFAULT) != 0; + } + /** * Binds to the accessibility service. * @@ -685,10 +794,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ public boolean unbind() { if (mService != null) { - mService = null; + tryRemoveServiceLocked(this); mContext.unbindService(this); - mComponentNameToServiceMap.remove(mComponentName); - mServices.remove(this); + mService = null; return true; } return false; @@ -711,14 +819,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public void onServiceConnected(ComponentName componentName, IBinder service) { mService = service; mServiceInterface = IEventListener.Stub.asInterface(service); - try { mServiceInterface.setConnection(this); synchronized (mLock) { - if (!mServices.contains(this)) { - mServices.add(this); - mComponentNameToServiceMap.put(componentName, this); - } + tryAddServiceLocked(this); } } catch (RemoteException re) { Slog.w(LOG_TAG, "Error while setting Controller for service: " + service, re); @@ -726,9 +830,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } public void onServiceDisconnected(ComponentName componentName) { + /* do nothing - #binderDied takes care */ + } + + public void linkToOwnDeath() throws RemoteException { + mService.linkToDeath(this, 0); + } + + public void unlinkToOwnDeath() { + mService.unlinkToDeath(this, 0); + } + + public void binderDied() { synchronized (mLock) { - Service service = mComponentNameToServiceMap.remove(componentName); - mServices.remove(service); + tryRemoveServiceLocked(this); } } } diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java new file mode 100644 index 0000000..4c7f595 --- /dev/null +++ b/services/java/com/android/server/accessibility/TouchExplorer.java @@ -0,0 +1,1458 @@ +/* + ** Copyright 2011, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +package com.android.server.accessibility; + +import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END; +import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START; + +import com.android.server.accessibility.AccessibilityInputFilter.Explorer; +import com.android.server.wm.InputFilter; + +import android.content.Context; +import android.os.Handler; +import android.os.SystemClock; +import android.util.Slog; +import android.util.SparseArray; +import android.view.MotionEvent; +import android.view.ViewConfiguration; +import android.view.WindowManagerPolicy; +import android.view.MotionEvent.PointerCoords; +import android.view.MotionEvent.PointerProperties; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; + +import java.util.Arrays; + +/** + * This class is a strategy for performing touch exploration. It + * transforms the motion event stream by modifying, adding, replacing, + * and consuming certain events. The interaction model is: + * + * <ol> + * <li>1. One finger moving around performs touch exploration.</li> + * <li>2. Two close fingers moving in the same direction perform a drag.</li> + * <li>3. Multi-finger gestures are delivered to view hierarchy.</li> + * <li>4. Pointers that have not moved more than a specified distance after they + * went down are considered inactive.</li> + * <li>5. Two fingers moving too far from each other or in different directions + * are considered a multi-finger gesture.</li> + * <li>6. Tapping on the last touch explored location within given time and + * distance slop performs a click.</li> + * <li>7. Tapping and holding for a while on the last touch explored location within + * given time and distance slop performs a long press.</li> + * <ol> + * + * @hide + */ +public class TouchExplorer implements Explorer { + private static final boolean DEBUG = false; + + // Tag for logging received events. + private static final String LOG_TAG_RECEIVED = "TouchExplorer-RECEIVED"; + // Tag for logging injected events. + private static final String LOG_TAG_INJECTED = "TouchExplorer-INJECTED"; + // Tag for logging the current state. + private static final String LOG_TAG_STATE = "TouchExplorer-STATE"; + + // States this explorer can be in. + private static final int STATE_TOUCH_EXPLORING = 0x00000001; + private static final int STATE_DRAGGING = 0x00000002; + private static final int STATE_DELEGATING = 0x00000004; + + // Human readable symbolic names for the states of the explorer. + private static final SparseArray<String> sStateSymbolicNames = new SparseArray<String>(); + static { + SparseArray<String> symbolicNames = sStateSymbolicNames; + symbolicNames.append(STATE_TOUCH_EXPLORING, "STATE_TOUCH_EXPLORING"); + symbolicNames.append(STATE_DRAGGING, "STATE_DRAGING"); + symbolicNames.append(STATE_DELEGATING, "STATE_DELEGATING"); + } + + // Invalid pointer ID. + private static final int INVALID_POINTER_ID = -1; + + // The coefficient by which to multiply + // ViewConfiguration.#getScaledTouchExplorationTapSlop() + // to compute #mDraggingDistance. + private static final int COEFFICIENT_DRAGGING_DISTANCE = 2; + + // The time slop in milliseconds for activating an item after it has + // been touch explored. Tapping on an item within this slop will perform + // a click and tapping and holding down a long press. + private static final long ACTIVATION_TIME_SLOP = 2000; + + // This constant captures the current implementation detail that + // pointer IDs are between 0 and 31 inclusive (subject to change). + // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) + private static final int MAX_POINTER_COUNT = 32; + + // The minimum of the cosine between the vectors of two moving + // pointers so they can be considered moving in the same direction. + private static final float MIN_ANGLE_COS = 0.866025404f; // cos(pi/6) + + // The delay for sending a hover enter event. + private static final long DELAY_SEND_HOVER_MOVE = 200; + + // Temporary array for storing pointer IDs. + private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT]; + + // Temporary array for storing PointerProperties + private final PointerProperties[] mTempPointerProperties = + PointerProperties.createArray(MAX_POINTER_COUNT); + + // Temporary array for storing PointerCoords + private final PointerCoords[] mTempPointerCoords = + PointerCoords.createArray(MAX_POINTER_COUNT); + + // The maximal distance between two pointers so they are + // considered to be performing a drag operation. + private final float mDraggingDistance; + + // The distance from the last touch explored location tapping within + // which would perform a click and tapping and holding a long press. + private final int mTouchExplorationTapSlop; + + // Context handle for accessing resources. + private final Context mContext; + + // The InputFilter this tracker is associated with i.e. the filter + // which delegates event processing to this touch explorer. + private final InputFilter mInputFilter; + + // Helper class for tracking pointers on the screen, for example which + // pointers are down, which are active, etc. + private final PointerTracker mPointerTracker; + + // Handle to the accessibility manager for firing accessibility events + // announcing touch exploration gesture start and end. + private final AccessibilityManager mAccessibilityManager; + + // The last event that was received while performing touch exploration. + private MotionEvent mLastTouchExploreEvent; + + // The current state of the touch explorer. + private int mCurrentState = STATE_TOUCH_EXPLORING; + + // Flag whether a touch exploration gesture is in progress. + private boolean mTouchExploreGestureInProgress; + + // The ID of the pointer used for dragging. + private int mDraggingPointerId; + + // Handler for performing asynchronous operations. + private final Handler mHandler; + + // Command for delayed sending of a hover event. + private final SendHoverDelayed mSendHoverDelayed; + + /** + * Creates a new instance. + * + * @param inputFilter The input filter associated with this explorer. + * @param context A context handle for accessing resources. + */ + public TouchExplorer(InputFilter inputFilter, Context context) { + mInputFilter = inputFilter; + mTouchExplorationTapSlop = + ViewConfiguration.get(context).getScaledTouchExplorationTapSlop(); + mDraggingDistance = mTouchExplorationTapSlop * COEFFICIENT_DRAGGING_DISTANCE; + mPointerTracker = new PointerTracker(context); + mContext = context; + mHandler = new Handler(context.getMainLooper()); + mSendHoverDelayed = new SendHoverDelayed(); + mAccessibilityManager = AccessibilityManager.getInstance(context); + } + + public void clear(MotionEvent event, int policyFlags) { + sendUpForInjectedDownPointers(event, policyFlags); + clear(); + } + + /** + * {@inheritDoc} + */ + public void onMotionEvent(MotionEvent event, int policyFlags) { + if (DEBUG) { + Slog.d(LOG_TAG_RECEIVED, "Received event: " + event + ", policyFlags=0x" + + Integer.toHexString(policyFlags)); + Slog.d(LOG_TAG_STATE, sStateSymbolicNames.get(mCurrentState)); + } + + // Keep track of the pointers's state. + mPointerTracker.onReceivedMotionEvent(event); + + switch(mCurrentState) { + case STATE_TOUCH_EXPLORING: { + handleMotionEventStateTouchExploring(event, policyFlags); + } break; + case STATE_DRAGGING: { + handleMotionEventStateDragging(event, policyFlags); + } break; + case STATE_DELEGATING: { + handleMotionEventStateDelegating(event, policyFlags); + } break; + default: { + throw new IllegalStateException("Illegal state: " + mCurrentState); + } + } + } + + /** + * Handles a motion event in touch exploring state. + * + * @param event The event to be handled. + * @param policyFlags The policy flags associated with the event. + */ + private void handleMotionEventStateTouchExploring(MotionEvent event, int policyFlags) { + PointerTracker pointerTracker = mPointerTracker; + final int activePointerCount = pointerTracker.getActivePointerCount(); + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + // Send a hover for every finger down so the user gets feedback + // where she is currently touching. + mSendHoverDelayed.forceSendAndRemove(); + mSendHoverDelayed.post(event, MotionEvent.ACTION_HOVER_ENTER, 1, policyFlags, + DELAY_SEND_HOVER_MOVE); + } break; + case MotionEvent.ACTION_POINTER_DOWN: { + switch (activePointerCount) { + case 0: { + throw new IllegalStateException("The must always be one active pointer in" + + "touch exploring state!"); + } + case 1: { + // Schedule a hover event which will lead to firing an + // accessibility event from the hovered view. + mSendHoverDelayed.remove(); + final int pointerId = pointerTracker.getPrimaryActivePointerId(); + final int pointerIdBits = (1 << pointerId); + final int lastAction = pointerTracker.getLastInjectedHoverAction(); + // If a schedules hover enter for another pointer is delivered we send move. + final int action = (lastAction == MotionEvent.ACTION_HOVER_ENTER) + ? MotionEvent.ACTION_HOVER_MOVE + : MotionEvent.ACTION_HOVER_ENTER; + mSendHoverDelayed.post(event, action, pointerIdBits, policyFlags, + DELAY_SEND_HOVER_MOVE); + + if (mLastTouchExploreEvent == null) { + break; + } + + // If more pointers down on the screen since the last touch + // exploration we discard the last cached touch explore event. + if (event.getPointerCount() != mLastTouchExploreEvent.getPointerCount()) { + mLastTouchExploreEvent = null; + } + } break; + default: { + /* do nothing - let the code for ACTION_MOVE decide what to do */ + } break; + } + } break; + case MotionEvent.ACTION_MOVE: { + final int pointerId = pointerTracker.getPrimaryActivePointerId(); + final int pointerIndex = event.findPointerIndex(pointerId); + final int pointerIdBits = (1 << pointerId); + switch (activePointerCount) { + case 0: { + /* do nothing - no active pointers so we swallow the event */ + } break; + case 1: { + // Detect touch exploration gesture start by having one active pointer + // that moved more than a given distance. + if (!mTouchExploreGestureInProgress) { + final float deltaX = pointerTracker.getReceivedPointerDownX(pointerId) + - event.getX(pointerIndex); + final float deltaY = pointerTracker.getReceivedPointerDownY(pointerId) + - event.getY(pointerIndex); + final double moveDelta = Math.hypot(deltaX, deltaY); + + if (moveDelta > mTouchExplorationTapSlop) { + mTouchExploreGestureInProgress = true; + sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START); + // Make sure the scheduled down/move event is sent. + mSendHoverDelayed.forceSendAndRemove(); + // If we have transitioned to exploring state from another one + // we need to send a hover enter event here. + final int lastAction = mPointerTracker.getLastInjectedHoverAction(); + if (lastAction == MotionEvent.ACTION_HOVER_EXIT) { + sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, + pointerIdBits, policyFlags); + } + sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, + policyFlags); + } + } else { + // Touch exploration gesture in progress so send a hover event. + sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, + policyFlags); + } + + // Detect long press on the last touch explored position. + if (!mTouchExploreGestureInProgress && mLastTouchExploreEvent != null) { + + // If the down was not in the time slop => nothing else to do. + final long pointerDownTime = + pointerTracker.getReceivedPointerDownTime(pointerId); + final long lastExploreTime = mLastTouchExploreEvent.getEventTime(); + final long deltaTimeExplore = pointerDownTime - lastExploreTime; + if (deltaTimeExplore > ACTIVATION_TIME_SLOP) { + mLastTouchExploreEvent = null; + break; + } + + // If the pointer moved more than the tap slop => nothing else to do. + final float deltaX = mLastTouchExploreEvent.getX(pointerIndex) + - event.getX(pointerIndex); + final float deltaY = mLastTouchExploreEvent.getY(pointerIndex) + - event.getY(pointerIndex); + final float moveDelta = (float) Math.hypot(deltaX, deltaY); + if (moveDelta > mTouchExplorationTapSlop) { + mLastTouchExploreEvent = null; + break; + } + + // If down for long enough we get a long press. + final long deltaTimeMove = event.getEventTime() - pointerDownTime; + if (deltaTimeMove > ViewConfiguration.getLongPressTimeout()) { + mCurrentState = STATE_DELEGATING; + // Make sure the scheduled hover exit is delivered. + mSendHoverDelayed.forceSendAndRemove(); + sendDownForAllActiveNotInjectedPointers(event, policyFlags); + sendMotionEvent(event, policyFlags); + mTouchExploreGestureInProgress = false; + mLastTouchExploreEvent = null; + } + } + } break; + case 2: { + mSendHoverDelayed.forceSendAndRemove(); + // We want to no longer hover over the location so subsequent + // touch at the same spot will generate a hover enter. + sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, + policyFlags); + + if (isDraggingGesture(event)) { + // Two pointers moving in the same direction within + // a given distance perform a drag. + mCurrentState = STATE_DRAGGING; + if (mTouchExploreGestureInProgress) { + sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); + mTouchExploreGestureInProgress = false; + } + mDraggingPointerId = pointerId; + sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits, + policyFlags); + } else { + // Two pointers moving arbitrary are delegated to the view hierarchy. + mCurrentState = STATE_DELEGATING; + if (mTouchExploreGestureInProgress) { + sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); + mTouchExploreGestureInProgress = false; + } + sendDownForAllActiveNotInjectedPointers(event, policyFlags); + } + } break; + default: { + mSendHoverDelayed.forceSendAndRemove(); + // We want to no longer hover over the location so subsequent + // touch at the same spot will generate a hover enter. + sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, + policyFlags); + + // More than two pointers are delegated to the view hierarchy. + mCurrentState = STATE_DELEGATING; + mSendHoverDelayed.remove(); + if (mTouchExploreGestureInProgress) { + sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); + mTouchExploreGestureInProgress = false; + } + sendDownForAllActiveNotInjectedPointers(event, policyFlags); + } + } + } break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: { + final int pointerId = pointerTracker.getLastReceivedUpPointerId(); + final int pointerIdBits = (1 << pointerId); + switch (activePointerCount) { + case 0: { + // If the pointer that went up was not active we have nothing to do. + if (!pointerTracker.wasLastReceivedUpPointerActive()) { + break; + } + + // If touch exploring announce the end of the gesture. + if (mTouchExploreGestureInProgress) { + sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); + mTouchExploreGestureInProgress = false; + } + + // Detect whether to activate i.e. click on the last explored location. + if (mLastTouchExploreEvent != null) { + + // If the down was not in the time slop => nothing else to do. + final long eventTime = + pointerTracker.getLastReceivedUpPointerDownTime(); + final long exploreTime = mLastTouchExploreEvent.getEventTime(); + final long deltaTime = eventTime - exploreTime; + if (deltaTime > ACTIVATION_TIME_SLOP) { + mSendHoverDelayed.forceSendAndRemove(); + final int lastAction = mPointerTracker.getLastInjectedHoverAction(); + if (lastAction != MotionEvent.ACTION_HOVER_EXIT) { + sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, + pointerIdBits, policyFlags); + } + mLastTouchExploreEvent = MotionEvent.obtain(event); + break; + } + + // If the pointer moved more than the tap slop => nothing else to do. + final int pointerIndex = event.findPointerIndex(pointerId); + final float deltaX = pointerTracker.getLastReceivedUpPointerDownX() + - event.getX(pointerIndex); + final float deltaY = pointerTracker.getLastReceivedUpPointerDownY() + - event.getY(pointerIndex); + final float deltaMove = (float) Math.hypot(deltaX, deltaY); + if (deltaMove > mTouchExplorationTapSlop) { + mSendHoverDelayed.forceSendAndRemove(); + final int lastAction = mPointerTracker.getLastInjectedHoverAction(); + if (lastAction != MotionEvent.ACTION_HOVER_EXIT) { + sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, + pointerIdBits, policyFlags); + } + mLastTouchExploreEvent = MotionEvent.obtain(event); + break; + } + + // All preconditions are met, so click the last explored location. + mSendHoverDelayed.forceSendAndRemove(); + sendActionDownAndUp(mLastTouchExploreEvent, policyFlags); + mLastTouchExploreEvent = null; + } else { + mSendHoverDelayed.forceSendAndRemove(); + final int lastAction = mPointerTracker.getLastInjectedHoverAction(); + if (lastAction != MotionEvent.ACTION_HOVER_EXIT) { + sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, + pointerIdBits, policyFlags); + } + mLastTouchExploreEvent = MotionEvent.obtain(event); + } + } break; + } + } break; + case MotionEvent.ACTION_CANCEL: { + final int lastAction = pointerTracker.getLastInjectedHoverAction(); + if (lastAction != MotionEvent.ACTION_HOVER_EXIT) { + final int pointerId = pointerTracker.getPrimaryActivePointerId(); + final int pointerIdBits = (1 << pointerId); + sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, + policyFlags); + } + clear(); + } break; + } + } + + /** + * Handles a motion event in dragging state. + * + * @param event The event to be handled. + * @param policyFlags The policy flags associated with the event. + */ + private void handleMotionEventStateDragging(MotionEvent event, int policyFlags) { + final int pointerIdBits = (1 << mDraggingPointerId); + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + throw new IllegalStateException("Dragging state can be reached only if two " + + "pointers are already down"); + } + case MotionEvent.ACTION_POINTER_DOWN: { + // We are in dragging state so we have two pointers and another one + // goes down => delegate the three pointers to the view hierarchy + mCurrentState = STATE_DELEGATING; + sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); + sendDownForAllActiveNotInjectedPointers(event, policyFlags); + } break; + case MotionEvent.ACTION_MOVE: { + final int activePointerCount = mPointerTracker.getActivePointerCount(); + switch (activePointerCount) { + case 2: { + if (isDraggingGesture(event)) { + // If still dragging send a drag event. + sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits, + policyFlags); + } else { + // The two pointers are moving either in different directions or + // no close enough => delegate the gesture to the view hierarchy. + mCurrentState = STATE_DELEGATING; + // Send an event to the end of the drag gesture. + sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, + policyFlags); + // Deliver all active pointers to the view hierarchy. + sendDownForAllActiveNotInjectedPointers(event, policyFlags); + } + } break; + default: { + mCurrentState = STATE_DELEGATING; + // Send an event to the end of the drag gesture. + sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, + policyFlags); + // Deliver all active pointers to the view hierarchy. + sendDownForAllActiveNotInjectedPointers(event, policyFlags); + } + } + } break; + case MotionEvent.ACTION_POINTER_UP: { + mCurrentState = STATE_TOUCH_EXPLORING; + // Send an event to the end of the drag gesture. + sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); + } break; + case MotionEvent.ACTION_CANCEL: { + clear(); + } break; + } + } + + /** + * Handles a motion event in delegating state. + * + * @param event The event to be handled. + * @param policyFlags The policy flags associated with the event. + */ + public void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + throw new IllegalStateException("Delegating state can only be reached if " + + "there is at least one pointer down!"); + } + case MotionEvent.ACTION_UP: { + mCurrentState = STATE_TOUCH_EXPLORING; + } break; + case MotionEvent.ACTION_MOVE: { + // Check whether some other pointer became active because they have moved + // a given distance and if such exist send them to the view hierarchy + final int notInjectedCount = mPointerTracker.getNotInjectedActivePointerCount(); + if (notInjectedCount > 0) { + sendDownForAllActiveNotInjectedPointers(event, policyFlags); + } + } break; + case MotionEvent.ACTION_POINTER_UP: { + // No active pointers => go to initial state. + if (mPointerTracker.getActivePointerCount() == 0) { + mCurrentState = STATE_TOUCH_EXPLORING; + } + } break; + case MotionEvent.ACTION_CANCEL: { + clear(); + } break; + } + // Deliver the event striping out inactive pointers. + sendMotionEventStripInactivePointers(event, policyFlags); + } + + /** + * Sends down events to the view hierarchy for all active pointers which are + * not already being delivered i.e. pointers that are not yet injected. + * + * @param prototype The prototype from which to create the injected events. + * @param policyFlags The policy flags associated with the event. + */ + private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int policyFlags) { + final PointerProperties[] pointerProperties = mTempPointerProperties; + final PointerCoords[] pointerCoords = mTempPointerCoords; + final PointerTracker pointerTracker = mPointerTracker; + int pointerDataIndex = 0; + + final int pinterCount = prototype.getPointerCount(); + for (int i = 0; i < pinterCount; i++) { + final int pointerId = prototype.getPointerId(i); + + // Skip inactive pointers. + if (!pointerTracker.isActivePointer(pointerId)) { + continue; + } + // Skip already delivered pointers. + if (pointerTracker.isInjectedPointerDown(pointerId)) { + continue; + } + + // Populate and inject an event for the current pointer. + prototype.getPointerProperties(i, pointerProperties[pointerDataIndex]); + prototype.getPointerCoords(i, pointerCoords[pointerDataIndex]); + + final long downTime = pointerTracker.getLastInjectedDownEventTime(); + final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, pointerDataIndex); + final int pointerCount = pointerDataIndex + 1; + final long eventTime = SystemClock.uptimeMillis(); + + MotionEvent event = MotionEvent.obtain(downTime, eventTime, + action, pointerCount, pointerProperties, pointerCoords, + prototype.getMetaState(), prototype.getButtonState(), + prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(), + prototype.getEdgeFlags(), prototype.getSource(), prototype.getFlags()); + sendMotionEvent(event, policyFlags); + event.recycle(); + + pointerDataIndex++; + } + } + + /** + * Sends up events to the view hierarchy for all active pointers which are + * already being delivered i.e. pointers that are injected. + * + * @param prototype The prototype from which to create the injected events. + * @param policyFlags The policy flags associated with the event. + */ + private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) { + final PointerTracker pointerTracker = mPointerTracker; + final PointerProperties[] pointerProperties = mTempPointerProperties; + final PointerCoords[] pointerCoords = mTempPointerCoords; + int pointerDataIndex = 0; + + final int pointerCount = prototype.getPointerCount(); + for (int i = 0; i < pointerCount; i++) { + final int pointerId = prototype.getPointerId(i); + + // Skip non injected down pointers. + if (!pointerTracker.isInjectedPointerDown(pointerId)) { + continue; + } + + // Populate and inject event. + prototype.getPointerProperties(i, pointerProperties[pointerDataIndex]); + prototype.getPointerCoords(i, pointerCoords[pointerDataIndex]); + + final long downTime = pointerTracker.getLastInjectedDownEventTime(); + final int action = computeInjectionAction(MotionEvent.ACTION_UP, pointerDataIndex); + final int newPointerCount = pointerDataIndex + 1; + final long eventTime = SystemClock.uptimeMillis(); + + MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, + newPointerCount, pointerProperties, pointerCoords, + prototype.getMetaState(), prototype.getButtonState(), + prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(), + prototype.getEdgeFlags(), prototype.getSource(), prototype.getFlags()); + + sendMotionEvent(event, policyFlags); + event.recycle(); + + pointerDataIndex++; + } + } + + /** + * Sends a motion event by first stripping the inactive pointers. + * + * @param prototype The prototype from which to create the injected event. + * @param policyFlags The policy flags associated with the event. + */ + private void sendMotionEventStripInactivePointers(MotionEvent prototype, int policyFlags) { + PointerTracker pointerTracker = mPointerTracker; + + // All pointers active therefore we just inject the event as is. + if (prototype.getPointerCount() == pointerTracker.getActivePointerCount()) { + sendMotionEvent(prototype, policyFlags); + return; + } + + // No active pointers and the one that just went up was not + // active, therefore we have nothing to do. + if (pointerTracker.getActivePointerCount() == 0 + && !pointerTracker.wasLastReceivedUpPointerActive()) { + return; + } + + int pointerIdBits = 0; + final int pointerCount = prototype.getPointerCount(); + for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) { + final int pointerId = prototype.getPointerId(pointerIndex); + // If the pointer is inactive or the pointer that just went up + // was inactive we strip the pointer data from the event. + if (pointerTracker.isActiveOrWasLastActiveUpPointer(pointerId)) { + pointerIdBits |= (1 << pointerId); + } + } + + MotionEvent event = prototype.split(pointerIdBits); + sendMotionEvent(event, policyFlags); + event.recycle(); + } + + /** + * Sends an up and down events. + * + * @param prototype The prototype from which to create the injected events. + * @param policyFlags The policy flags associated with the event. + */ + private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) { + final PointerProperties[] pointerProperties = mTempPointerProperties; + final PointerCoords[] pointerCoords = mTempPointerCoords; + final int pointerId = mPointerTracker.getLastReceivedUpPointerId(); + final int pointerIndex = prototype.findPointerIndex(pointerId); + + // Send down. + prototype.getPointerProperties(pointerIndex, pointerProperties[0]); + prototype.getPointerCoords(pointerIndex, pointerCoords[0]); + + final long downTime = SystemClock.uptimeMillis(); + MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, + 1, pointerProperties, pointerCoords, + prototype.getMetaState(), prototype.getButtonState(), + prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(), + prototype.getEdgeFlags(), prototype.getSource(), prototype.getFlags()); + sendMotionEvent(event, policyFlags); + + // Send up. + event.setAction(MotionEvent.ACTION_UP); + sendMotionEvent(event, policyFlags); + event.recycle(); + } + + /** + * Sends an event. + * + * @param event The event to send. + * @param action The action of the event. + * @param pointerIdBits The bits of the pointers to send. + * @param policyFlags The policy flags associated with the event. + */ + private void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits, + int policyFlags) { + MotionEvent event = prototype.split(pointerIdBits); + event.setDownTime(mPointerTracker.getLastInjectedDownEventTime()); + event.setAction(action); + sendMotionEvent(event, policyFlags); + event.recycle(); + } + + /** + * Computes the action for an injected event based on a masked action + * and a pointer index. + * + * @param actionMasked The masked action. + * @param pointerIndex The index of the pointer which has changed. + * @return The action to be used for injection. + */ + private int computeInjectionAction(int actionMasked, int pointerIndex) { + switch (actionMasked) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: { + PointerTracker pointerTracker = mPointerTracker; + // Compute the action based on how many down pointers are injected. + if (pointerTracker.getInjectedPointerDownCount() == 0) { + return MotionEvent.ACTION_DOWN; + } else { + return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + | MotionEvent.ACTION_POINTER_DOWN; + } + } + case MotionEvent.ACTION_POINTER_UP: { + PointerTracker pointerTracker = mPointerTracker; + // Compute the action based on how many down pointers are injected. + if (pointerTracker.getInjectedPointerDownCount() == 1) { + return MotionEvent.ACTION_UP; + } else { + return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + | MotionEvent.ACTION_POINTER_UP; + } + } + default: + return actionMasked; + } + } + + /** + * Determines whether a two pointer gesture is a dragging one. + * + * @param event The event with the pointer data. + * @return True if the gesture is a dragging one. + */ + private boolean isDraggingGesture(MotionEvent event) { + PointerTracker pointerTracker = mPointerTracker; + int[] pointerIds = mTempPointerIds; + pointerTracker.populateActivePointerIds(pointerIds); + + final int firstPtrIndex = event.findPointerIndex(pointerIds[0]); + final int secondPtrIndex = event.findPointerIndex(pointerIds[1]); + + final float firstPtrX = event.getX(firstPtrIndex); + final float firstPtrY = event.getY(firstPtrIndex); + final float secondPtrX = event.getX(secondPtrIndex); + final float secondPtrY = event.getY(secondPtrIndex); + + // Check if the pointers are close enough. + final float deltaX = firstPtrX - secondPtrX; + final float deltaY = firstPtrY - secondPtrY; + final float deltaMove = (float) Math.hypot(deltaX, deltaY); + if (deltaMove > mDraggingDistance) { + return false; + } + + // Check if the pointers are moving in the same direction. + final float firstDeltaX = + firstPtrX - pointerTracker.getReceivedPointerDownX(firstPtrIndex); + final float firstDeltaY = + firstPtrY - pointerTracker.getReceivedPointerDownY(firstPtrIndex); + final float firstMagnitude = + (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY); + final float firstXNormalized = + (firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX; + final float firstYNormalized = + (firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY; + + final float secondDeltaX = + secondPtrX - pointerTracker.getReceivedPointerDownX(secondPtrIndex); + final float secondDeltaY = + secondPtrY - pointerTracker.getReceivedPointerDownY(secondPtrIndex); + final float secondMagnitude = + (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY); + final float secondXNormalized = + (secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX; + final float secondYNormalized = + (secondMagnitude > 0) ? secondDeltaY / secondMagnitude : secondDeltaY; + + final float angleCos = + firstXNormalized * secondXNormalized + firstYNormalized * secondYNormalized; + + if (angleCos < MIN_ANGLE_COS) { + return false; + } + + return true; + } + + /** + * Sends an event announcing the start/end of a touch exploration gesture. + * + * @param eventType The type of the event to send. + */ + private void sendAccessibilityEvent(int eventType) { + AccessibilityEvent event = AccessibilityEvent.obtain(eventType); + event.setPackageName(mContext.getPackageName()); + event.setClassName(getClass().getName()); + mAccessibilityManager.sendAccessibilityEvent(event); + } + + /** + * Sends a motion event to the input filter for injection. + * + * @param event The event to send. + * @param policyFlags The policy flags associated with the event. + */ + private void sendMotionEvent(MotionEvent event, int policyFlags) { + if (DEBUG) { + Slog.d(LOG_TAG_INJECTED, "Injecting event: " + event + ", policyFlags=0x" + + Integer.toHexString(policyFlags)); + } + // Make sure that the user will see the event. + policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER; + mPointerTracker.onInjectedMotionEvent(event); + mInputFilter.sendInputEvent(event, policyFlags); + } + + /** + * Clears the internal state of this explorer. + */ + private void clear() { + mSendHoverDelayed.remove(); + mPointerTracker.clear(); + mLastTouchExploreEvent = null; + mCurrentState = STATE_TOUCH_EXPLORING; + mTouchExploreGestureInProgress = false; + mDraggingPointerId = INVALID_POINTER_ID; + } + + /** + * Helper class for tracking pointers and more specifically which of + * them are currently down, which are active, and which are delivered + * to the view hierarchy. The enclosing {@link TouchExplorer} uses the + * pointer state reported by this class to perform touch exploration. + * <p> + * The main purpose of this class is to allow the touch explorer to + * disregard pointers put down by accident by the user and not being + * involved in the interaction. For example, a blind user grabs the + * device with her left hand such that she touches the screen and she + * uses her right hand's index finger to explore the screen content. + * In this scenario the touches generated by the left hand are to be + * ignored. + */ + class PointerTracker { + private static final String LOG_TAG = "PointerTracker"; + + // The coefficient by which to multiply + // ViewConfiguration.#getScaledTouchSlop() + // to compute #mThresholdActivePointer. + private static final int COEFFICIENT_ACTIVE_POINTER = 2; + + // Pointers that moved less than mThresholdActivePointer + // are considered active i.e. are ignored. + private final double mThresholdActivePointer; + + // Keep track of where and when a pointer went down. + private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT]; + private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT]; + private final long[] mReceivedPointerDownTime = new long[MAX_POINTER_COUNT]; + + // Which pointers are down. + private int mReceivedPointersDown; + + // Which down pointers are active. + private int mActivePointers; + + // Primary active pointer which is either the first that went down + // or if it goes up the next active that most recently went down. + private int mPrimaryActivePointerId; + + // Flag indicating that there is at least one active pointer moving. + private boolean mHasMovingActivePointer; + + // Keep track of which pointers sent to the system are down. + private int mInjectedPointersDown; + + // Keep track of the last up pointer data. + private float mLastReceivedUpPointerDownX; + private float mLastReveivedUpPointerDownY; + private long mLastReceivedUpPointerDownTime; + private int mLastReceivedUpPointerId; + private boolean mLastReceivedUpPointerActive; + + // The time of the last injected down. + private long mLastInjectedDownEventTime; + + // The action of the last injected hover event. + private int mLastInjectedHoverEventAction = MotionEvent.ACTION_HOVER_EXIT; + + /** + * Creates a new instance. + * + * @param context Context for looking up resources. + */ + public PointerTracker(Context context) { + mThresholdActivePointer = + ViewConfiguration.get(context).getScaledTouchSlop() * COEFFICIENT_ACTIVE_POINTER; + } + + /** + * Clears the internals state. + */ + public void clear() { + Arrays.fill(mReceivedPointerDownX, 0); + Arrays.fill(mReceivedPointerDownY, 0); + Arrays.fill(mReceivedPointerDownTime, 0); + mReceivedPointersDown = 0; + mActivePointers = 0; + mPrimaryActivePointerId = 0; + mHasMovingActivePointer = false; + mInjectedPointersDown = 0; + mLastReceivedUpPointerDownX = 0; + mLastReveivedUpPointerDownY = 0; + mLastReceivedUpPointerDownTime = 0; + mLastReceivedUpPointerId = 0; + mLastReceivedUpPointerActive = false; + } + + /** + * Processes a received {@link MotionEvent} event. + * + * @param event The event to process. + */ + public void onReceivedMotionEvent(MotionEvent event) { + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: { + // New gesture so restart tracking injected down pointers. + mInjectedPointersDown = 0; + handleReceivedPointerDown(0, event); + } break; + case MotionEvent.ACTION_POINTER_DOWN: { + handleReceivedPointerDown(event.getActionIndex(), event); + } break; + case MotionEvent.ACTION_MOVE: { + handleReceivedPointerMove(event); + } break; + case MotionEvent.ACTION_UP: { + handleReceivedPointerUp(0, event); + } break; + case MotionEvent.ACTION_POINTER_UP: { + handleReceivedPointerUp(event.getActionIndex(), event); + } break; + } + if (DEBUG) { + Slog.i(LOG_TAG, "Received pointer: " + toString()); + } + } + + /** + * Processes an injected {@link MotionEvent} event. + * + * @param event The event to process. + */ + public void onInjectedMotionEvent(MotionEvent event) { + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: { + handleInjectedPointerDown(0, event); + } break; + case MotionEvent.ACTION_POINTER_DOWN: { + handleInjectedPointerDown(event.getActionIndex(), event); + } break; + case MotionEvent.ACTION_UP: { + handleInjectedPointerUp(0, event); + } break; + case MotionEvent.ACTION_POINTER_UP: { + handleInjectedPointerUp(event.getActionIndex(), event); + } break; + case MotionEvent.ACTION_HOVER_ENTER: + case MotionEvent.ACTION_HOVER_MOVE: + case MotionEvent.ACTION_HOVER_EXIT: { + mLastInjectedHoverEventAction = event.getActionMasked(); + } break; + } + if (DEBUG) { + Slog.i(LOG_TAG, "Injected pointer: " + toString()); + } + } + + /** + * @return The number of received pointers that are down. + */ + public int getReceivedPointerDownCount() { + return Integer.bitCount(mReceivedPointersDown); + } + + /** + * @return The number of down input pointers that are active. + */ + public int getActivePointerCount() { + return Integer.bitCount(mActivePointers); + } + + /** + * Whether an received pointer is down. + * + * @param pointerId The unique pointer id. + * @return True if the pointer is down. + */ + public boolean isReceivedPointerDown(int pointerId) { + final int pointerFlag = (1 << pointerId); + return (mReceivedPointersDown & pointerFlag) != 0; + } + + /** + * Whether an injected pointer is down. + * + * @param pointerId The unique pointer id. + * @return True if the pointer is down. + */ + public boolean isInjectedPointerDown(int pointerId) { + final int pointerFlag = (1 << pointerId); + return (mInjectedPointersDown & pointerFlag) != 0; + } + + /** + * @return The number of down pointers injected to the view hierarchy. + */ + public int getInjectedPointerDownCount() { + return Integer.bitCount(mInjectedPointersDown); + } + + /** + * Whether an input pointer is active. + * + * @param pointerId The unique pointer id. + * @return True if the pointer is active. + */ + public boolean isActivePointer(int pointerId) { + final int pointerFlag = (1 << pointerId); + return (mActivePointers & pointerFlag) != 0; + } + + /** + * @param pointerId The unique pointer id. + * @return The X coordinate where the pointer went down. + */ + public float getReceivedPointerDownX(int pointerId) { + return mReceivedPointerDownX[pointerId]; + } + + /** + * @param pointerId The unique pointer id. + * @return The Y coordinate where the pointer went down. + */ + public float getReceivedPointerDownY(int pointerId) { + return mReceivedPointerDownY[pointerId]; + } + + /** + * @param pointerId The unique pointer id. + * @return The time when the pointer went down. + */ + public long getReceivedPointerDownTime(int pointerId) { + return mReceivedPointerDownTime[pointerId]; + } + + /** + * @return The id of the primary pointer. + */ + public int getPrimaryActivePointerId() { + if (mPrimaryActivePointerId == INVALID_POINTER_ID) { + mPrimaryActivePointerId = findPrimaryActivePointer(); + } + return mPrimaryActivePointerId; + } + + /** + * @return The X coordinate where the last up received pointer went down. + */ + public float getLastReceivedUpPointerDownX() { + return mLastReceivedUpPointerDownX; + } + + /** + * @return The Y coordinate where the last up received pointer went down. + */ + public float getLastReceivedUpPointerDownY() { + return mLastReveivedUpPointerDownY; + } + + /** + * @return The time when the last up received pointer went down. + */ + public long getLastReceivedUpPointerDownTime() { + return mLastReceivedUpPointerDownTime; + } + + /** + * @return The id of the last received pointer that went up. + */ + public int getLastReceivedUpPointerId() { + return mLastReceivedUpPointerId; + } + + /** + * @return Whether the last received pointer that went up was active. + */ + public boolean wasLastReceivedUpPointerActive() { + return mLastReceivedUpPointerActive; + } + + /** + * @return The time of the last injected down event. + */ + public long getLastInjectedDownEventTime() { + return mLastInjectedDownEventTime; + } + + /** + * @return The action of the last injected hover event. + */ + public int getLastInjectedHoverAction() { + return mLastInjectedHoverEventAction; + } + + /** + * Populates the active pointer IDs to the given array. + * <p> + * Note: The client is responsible for providing large enough array. + * + * @param outPointerIds The array to which to write the active pointers. + */ + public void populateActivePointerIds(int[] outPointerIds) { + int index = 0; + for (int idBits = mActivePointers; idBits != 0; ) { + final int id = Integer.numberOfTrailingZeros(idBits); + idBits &= ~(1 << id); + outPointerIds[index] = id; + index++; + } + } + + /** + * @return The number of non injected active pointers. + */ + public int getNotInjectedActivePointerCount() { + final int pointerState = mActivePointers & ~mInjectedPointersDown; + return Integer.bitCount(pointerState); + } + + /** + * @param pointerId The unique pointer id. + * @return Whether the pointer is active or was the last active than went up. + */ + private boolean isActiveOrWasLastActiveUpPointer(int pointerId) { + return (isActivePointer(pointerId) + || (mLastReceivedUpPointerId == pointerId + && mLastReceivedUpPointerActive)); + } + + /** + * Handles a received pointer down event. + * + * @param pointerIndex The index of the pointer that has changed. + * @param event The event to be handled. + */ + private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) { + final int pointerId = event.getPointerId(pointerIndex); + final int pointerFlag = (1 << pointerId); + + mLastReceivedUpPointerId = 0; + mLastReceivedUpPointerDownX = 0; + mLastReveivedUpPointerDownY = 0; + mLastReceivedUpPointerDownTime = 0; + mLastReceivedUpPointerActive = false; + + mReceivedPointersDown |= pointerFlag; + mReceivedPointerDownX[pointerId] = event.getX(pointerIndex); + mReceivedPointerDownY[pointerId] = event.getY(pointerIndex); + mReceivedPointerDownTime[pointerId] = event.getEventTime(); + + if (!mHasMovingActivePointer) { + // If still no moving active pointers every + // down pointer is the only active one. + mActivePointers = pointerFlag; + mPrimaryActivePointerId = pointerId; + } else { + // If at least one moving active pointer every + // subsequent down pointer is active. + mActivePointers |= pointerFlag; + } + } + + /** + * Handles a received pointer move event. + * + * @param event The event to be handled. + */ + private void handleReceivedPointerMove(MotionEvent event) { + detectActivePointers(event); + } + + /** + * Handles a received pointer up event. + * + * @param pointerIndex The index of the pointer that has changed. + * @param event The event to be handled. + */ + private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) { + final int pointerId = event.getPointerId(pointerIndex); + final int pointerFlag = (1 << pointerId); + + mLastReceivedUpPointerId = pointerId; + mLastReceivedUpPointerDownX = getReceivedPointerDownX(pointerId); + mLastReveivedUpPointerDownY = getReceivedPointerDownY(pointerId); + mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId); + mLastReceivedUpPointerActive = isActivePointer(pointerId); + + mReceivedPointersDown &= ~pointerFlag; + mActivePointers &= ~pointerFlag; + mReceivedPointerDownX[pointerId] = 0; + mReceivedPointerDownY[pointerId] = 0; + mReceivedPointerDownTime[pointerId] = 0; + + if (mActivePointers == 0) { + mHasMovingActivePointer = false; + } + if (mPrimaryActivePointerId == pointerId) { + mPrimaryActivePointerId = INVALID_POINTER_ID; + } + } + + /** + * Handles a injected pointer down event. + * + * @param pointerIndex The index of the pointer that has changed. + * @param event The event to be handled. + */ + private void handleInjectedPointerDown(int pointerIndex, MotionEvent event) { + final int pointerId = event.getPointerId(pointerIndex); + final int pointerFlag = (1 << pointerId); + mInjectedPointersDown |= pointerFlag; + mLastInjectedDownEventTime = event.getEventTime(); + } + + /** + * Handles a injected pointer up event. + * + * @param pointerIndex The index of the pointer that has changed. + * @param event The event to be handled. + */ + private void handleInjectedPointerUp(int pointerIndex, MotionEvent event) { + final int pointerId = event.getPointerId(pointerIndex); + final int pointerFlag = (1 << pointerId); + mInjectedPointersDown &= ~pointerFlag; + if (mInjectedPointersDown == 0) { + mLastInjectedDownEventTime = 0; + } + } + + /** + * Detects the active pointers in an event. + * + * @param event The event to examine. + */ + private void detectActivePointers(MotionEvent event) { + for (int i = 0, count = event.getPointerCount(); i < count; i++) { + final int pointerId = event.getPointerId(i); + if (mHasMovingActivePointer) { + // If already active => nothing to do. + if (isActivePointer(pointerId)) { + continue; + } + } + // Active pointers are ones that moved more than a given threshold. + final float pointerDeltaMove = computePointerDeltaMove(i, event); + if (pointerDeltaMove > mThresholdActivePointer) { + final int pointerFlag = (1 << pointerId); + mActivePointers |= pointerFlag; + mHasMovingActivePointer = true; + } + } + } + + /** + * @return The primary active pointer. + */ + private int findPrimaryActivePointer() { + int primaryActivePointerId = INVALID_POINTER_ID; + long minDownTime = Long.MAX_VALUE; + // Find the active pointer that went down first. + for (int i = 0, count = mReceivedPointerDownTime.length; i < count; i++) { + if (isActivePointer(i)) { + final long downPointerTime = mReceivedPointerDownTime[i]; + if (downPointerTime < minDownTime) { + minDownTime = downPointerTime; + primaryActivePointerId = i; + } + } + } + return primaryActivePointerId; + } + + /** + * Computes the move for a given action pointer index since the + * corresponding pointer went down. + * + * @param pointerIndex The action pointer index. + * @param event The event to examine. + * @return The distance the pointer has moved. + */ + private float computePointerDeltaMove(int pointerIndex, MotionEvent event) { + final int pointerId = event.getPointerId(pointerIndex); + final float deltaX = event.getX(pointerIndex) - mReceivedPointerDownX[pointerId]; + final float deltaY = event.getY(pointerIndex) - mReceivedPointerDownY[pointerId]; + return (float) Math.hypot(deltaX, deltaY); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("========================="); + builder.append("\nDown pointers #"); + builder.append(getReceivedPointerDownCount()); + builder.append(" [ "); + for (int i = 0; i < MAX_POINTER_COUNT; i++) { + if (isReceivedPointerDown(i)) { + builder.append(i); + builder.append(" "); + } + } + builder.append("]"); + builder.append("\nActive pointers #"); + builder.append(getActivePointerCount()); + builder.append(" [ "); + for (int i = 0; i < MAX_POINTER_COUNT; i++) { + if (isActivePointer(i)) { + builder.append(i); + builder.append(" "); + } + } + builder.append("]"); + builder.append("\nPrimary active pointer id [ "); + builder.append(getPrimaryActivePointerId()); + builder.append(" ]"); + builder.append("\n========================="); + return builder.toString(); + } + } + + /** + * Class for delayed sending of hover events. + */ + private final class SendHoverDelayed implements Runnable { + private static final String LOG_TAG = "SendHoverEnterOrExitDelayed"; + + private MotionEvent mEvent; + private int mAction; + private int mPointerIdBits; + private int mPolicyFlags; + + public void post(MotionEvent prototype, int action, int pointerIdBits, int policyFlags, + long delay) { + remove(); + mEvent = MotionEvent.obtain(prototype); + mAction = action; + mPointerIdBits = pointerIdBits; + mPolicyFlags = policyFlags; + mHandler.postDelayed(this, delay); + } + + public void remove() { + mHandler.removeCallbacks(this); + clear(); + } + + private boolean isPenidng() { + return (mEvent != null); + } + + private void clear() { + if (!isPenidng()) { + return; + } + mEvent.recycle(); + mEvent = null; + mAction = 0; + mPointerIdBits = -1; + mPolicyFlags = 0; + } + + public void forceSendAndRemove() { + if (isPenidng()) { + run(); + remove(); + } + } + + public void run() { + if (DEBUG) { + if (mAction == MotionEvent.ACTION_HOVER_ENTER) { + Slog.d(LOG_TAG, "Injecting: " + MotionEvent.ACTION_HOVER_ENTER); + } else if (mAction == MotionEvent.ACTION_HOVER_MOVE) { + Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_MOVE"); + } else if (mAction == MotionEvent.ACTION_HOVER_EXIT) { + Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_EXIT"); + } + } + + sendMotionEvent(mEvent, mAction, mPointerIdBits, mPolicyFlags); + clear(); + } + } +} diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 9989d3b..c2a8a1d 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -42,8 +42,10 @@ import android.app.IActivityWatcher; import android.app.IApplicationThread; import android.app.IInstrumentationWatcher; import android.app.INotificationManager; +import android.app.IProcessObserver; import android.app.IServiceConnection; import android.app.IThumbnailReceiver; +import android.app.IThumbnailRetriever; import android.app.Instrumentation; import android.app.Notification; import android.app.NotificationManager; @@ -104,7 +106,6 @@ import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; -import android.util.Config; import android.util.EventLog; import android.util.Slog; import android.util.Log; @@ -164,7 +165,7 @@ public final class ActivityManagerService extends ActivityManagerNative static final boolean DEBUG_URI_PERMISSION = localLOGV || false; static final boolean DEBUG_USER_LEAVING = localLOGV || false; static final boolean DEBUG_RESULTS = localLOGV || false; - static final boolean DEBUG_BACKUP = localLOGV || false; + static final boolean DEBUG_BACKUP = localLOGV || true; static final boolean DEBUG_CONFIGURATION = localLOGV || false; static final boolean DEBUG_POWER = localLOGV || false; static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false; @@ -442,8 +443,7 @@ public final class ActivityManagerService extends ActivityManagerNative /** * List of intents that were used to start the most recent tasks. */ - final ArrayList<TaskRecord> mRecentTasks - = new ArrayList<TaskRecord>(); + final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>(); /** * All of the applications we currently have running organized by name. @@ -451,8 +451,7 @@ public final class ActivityManagerService extends ActivityManagerNative * returned by the package manager), and the keys are ApplicationRecord * objects. */ - final ProcessMap<ProcessRecord> mProcessNames - = new ProcessMap<ProcessRecord>(); + final ProcessMap<ProcessRecord> mProcessNames = new ProcessMap<ProcessRecord>(); /** * The currently running heavy-weight process, if any. @@ -481,8 +480,7 @@ public final class ActivityManagerService extends ActivityManagerNative * <p>NOTE: This object is protected by its own lock, NOT the global * activity manager lock! */ - final SparseArray<ProcessRecord> mPidsSelfLocked - = new SparseArray<ProcessRecord>(); + final SparseArray<ProcessRecord> mPidsSelfLocked = new SparseArray<ProcessRecord>(); /** * All of the processes that have been forced to be foreground. The key @@ -854,14 +852,6 @@ public final class ActivityManagerService extends ActivityManagerNative * Current sequence id for process LRU updating. */ int mLruSeq = 0; - - /** - * Set to true if the ANDROID_SIMPLE_PROCESS_MANAGEMENT envvar - * is set, indicating the user wants processes started in such a way - * that they can use ANDROID_PROCESS_WRAPPER and know what will be - * running in each process (thus no pre-initialized process, etc). - */ - boolean mSimpleProcessManagement = false; /** * System monitoring: number of processes that died since the last @@ -885,7 +875,10 @@ public final class ActivityManagerService extends ActivityManagerNative final RemoteCallbackList<IActivityWatcher> mWatchers = new RemoteCallbackList<IActivityWatcher>(); - + + final RemoteCallbackList<IProcessObserver> mProcessObservers + = new RemoteCallbackList<IProcessObserver>(); + /** * Callback of last caller to {@link #requestPss}. */ @@ -975,6 +968,8 @@ public final class ActivityManagerService extends ActivityManagerNative static final int CLEAR_DNS_CACHE = 28; static final int UPDATE_HTTP_PROXY = 29; static final int SHOW_COMPAT_MODE_DIALOG_MSG = 30; + static final int DISPATCH_FOREGROUND_ACTIVITIES_CHANGED = 31; + static final int DISPATCH_PROCESS_DIED = 32; AlertDialog mUidAlert; CompatModeDialog mCompatModeDialog; @@ -1302,6 +1297,20 @@ public final class ActivityManagerService extends ActivityManagerNative } } } + break; + } + case DISPATCH_FOREGROUND_ACTIVITIES_CHANGED: { + final int pid = msg.arg1; + final int uid = msg.arg2; + final boolean foregroundActivities = (Boolean) msg.obj; + dispatchForegroundActivitiesChanged(pid, uid, foregroundActivities); + break; + } + case DISPATCH_PROCESS_DIED: { + final int pid = msg.arg1; + final int uid = msg.arg2; + dispatchProcessDied(pid, uid); + break; } } } @@ -1313,6 +1322,7 @@ public final class ActivityManagerService extends ActivityManagerNative ServiceManager.addService("activity", m); ServiceManager.addService("meminfo", new MemBinder(m)); + ServiceManager.addService("gfxinfo", new GraphicsBinder(m)); if (MONITOR_CPU_USAGE) { ServiceManager.addService("cpuinfo", new CpuBinder(m)); } @@ -1438,6 +1448,18 @@ public final class ActivityManagerService extends ActivityManagerNative } } + static class GraphicsBinder extends Binder { + ActivityManagerService mActivityManagerService; + GraphicsBinder(ActivityManagerService activityManagerService) { + mActivityManagerService = activityManagerService; + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mActivityManagerService.dumpGraphicsHardwareUsage(fd, pw, args); + } + } + static class CpuBinder extends Binder { ActivityManagerService mActivityManagerService; CpuBinder(ActivityManagerService activityManagerService) { @@ -1455,15 +1477,6 @@ public final class ActivityManagerService extends ActivityManagerNative } private ActivityManagerService() { - String v = System.getenv("ANDROID_SIMPLE_PROCESS_MANAGEMENT"); - if (v != null && Integer.getInteger(v) != 0) { - mSimpleProcessManagement = true; - } - v = System.getenv("ANDROID_DEBUG_APP"); - if (v != null) { - mSimpleProcessManagement = true; - } - Slog.i(TAG, "Memory class: " + ActivityManager.staticGetMemoryClass()); File dataDir = Environment.getDataDirectory(); @@ -1762,7 +1775,7 @@ public final class ActivityManagerService extends ActivityManagerNative mLruSeq++; updateLruProcessInternalLocked(app, oomAdj, updateActivityTime, 0); } - + final ProcessRecord getProcessRecordLocked( String processName, int uid) { if (uid == Process.SYSTEM_UID) { @@ -1935,8 +1948,7 @@ public final class ActivityManagerService extends ActivityManagerNative debugFlags |= Zygote.DEBUG_ENABLE_ASSERT; } int pid = Process.start("android.app.ActivityThread", - mSimpleProcessManagement ? app.processName : null, uid, uid, - gids, debugFlags, null); + app.processName, uid, uid, gids, debugFlags, null); BatteryStatsImpl bs = app.batteryStats.getBatteryStats(); synchronized (bs) { if (bs.isOnBattery()) { @@ -2162,6 +2174,36 @@ public final class ActivityManagerService extends ActivityManagerNative mWatchers.finishBroadcast(); } + private void dispatchForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) { + int i = mProcessObservers.beginBroadcast(); + while (i > 0) { + i--; + final IProcessObserver observer = mProcessObservers.getBroadcastItem(i); + if (observer != null) { + try { + observer.onForegroundActivitiesChanged(pid, uid, foregroundActivities); + } catch (RemoteException e) { + } + } + } + mProcessObservers.finishBroadcast(); + } + + private void dispatchProcessDied(int pid, int uid) { + int i = mProcessObservers.beginBroadcast(); + while (i > 0) { + i--; + final IProcessObserver observer = mProcessObservers.getBroadcastItem(i); + if (observer != null) { + try { + observer.onProcessDied(pid, uid); + } catch (RemoteException e) { + } + } + } + mProcessObservers.finishBroadcast(); + } + final void doPendingActivityLaunchesLocked(boolean doResume) { final int N = mPendingActivityLaunches.size(); if (N <= 0) { @@ -2668,8 +2710,7 @@ public final class ActivityManagerService extends ActivityManagerNative + " finishing=" + r.finishing); r.makeFinishing(); mMainStack.mHistory.remove(i); - - r.inHistory = false; + r.takeFromHistory(); mWindowManager.removeAppToken(r); if (VALIDATE_TOKENS) { mWindowManager.validateAppTokens(mMainStack.mHistory); @@ -3047,7 +3088,10 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) { - Process.killProcess(app.pid); + Slog.w(TAG, "Killing " + app + ": background ANR"); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, "background ANR"); + Process.killProcessQuiet(app.pid); return; } @@ -3299,7 +3343,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (callerUid == Process.SYSTEM_UID) { synchronized (this) { ProcessRecord app = getProcessRecordLocked(processName, uid); - if (app != null) { + if (app != null && app.thread != null) { try { app.thread.scheduleSuicide(); } catch (RemoteException e) { @@ -3368,7 +3412,8 @@ public final class ActivityManagerService extends ActivityManagerNative private final boolean forceStopPackageLocked(String name, int uid, boolean callerWillRestart, boolean purgeCache, boolean doit) { - int i, N; + int i; + int N; if (uid < 0) { try { @@ -3512,7 +3557,9 @@ public final class ActivityManagerService extends ActivityManagerNative bringDownServiceLocked(sr, true); } } - Process.killProcess(pid); + EventLog.writeEvent(EventLogTags.AM_KILL, pid, + app.processName, app.setAdj, "start timeout"); + Process.killProcessQuiet(pid); if (mBackupTarget != null && mBackupTarget.app.pid == pid) { Slog.w(TAG, "Unattached app died before backup, skipping"); try { @@ -3558,7 +3605,7 @@ public final class ActivityManagerService extends ActivityManagerNative + " (IApplicationThread " + thread + "); dropping process"); EventLog.writeEvent(EventLogTags.AM_DROP_PROCESS, pid); if (pid > 0 && pid != MY_PID) { - Process.killProcess(pid); + Process.killProcessQuiet(pid); } else { try { thread.scheduleExit(); @@ -3629,6 +3676,7 @@ public final class ActivityManagerService extends ActivityManagerNative boolean isRestrictedBackupMode = false; if (mBackupTarget != null && mBackupAppName.equals(processName)) { isRestrictedBackupMode = (mBackupTarget.backupMode == BackupRecord.RESTORE) + || (mBackupTarget.backupMode == BackupRecord.RESTORE_FULL) || (mBackupTarget.backupMode == BackupRecord.BACKUP_FULL); } @@ -3877,16 +3925,7 @@ public final class ActivityManagerService extends ActivityManagerNative r = (ActivityRecord)mMainStack.mHistory.get(index); r.icicle = icicle; r.haveState = true; - if (thumbnail != null) { - r.thumbnail = thumbnail; - if (r.task != null) { - r.task.lastThumbnail = r.thumbnail; - } - } - r.description = description; - if (r.task != null) { - r.task.lastDescription = r.description; - } + r.updateThumbnail(thumbnail, description); r.stopped = true; r.state = ActivityState.STOPPED; if (!r.finishing) { @@ -4910,16 +4949,10 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } - final boolean canReadFb = (flags&ActivityManager.TASKS_GET_THUMBNAILS) != 0 - && checkCallingPermission( - android.Manifest.permission.READ_FRAME_BUFFER) - == PackageManager.PERMISSION_GRANTED; - int pos = mMainStack.mHistory.size()-1; ActivityRecord next = pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null; ActivityRecord top = null; - CharSequence topDescription = null; TaskRecord curTask = null; int numActivities = 0; int numRunning = 0; @@ -4933,7 +4966,6 @@ public final class ActivityManagerService extends ActivityManagerNative (top.state == ActivityState.INITIALIZING && top.task == r.task)) { top = r; - topDescription = r.description; curTask = r.task; numActivities = numRunning = 0; } @@ -4943,9 +4975,6 @@ public final class ActivityManagerService extends ActivityManagerNative if (r.app != null && r.app.thread != null) { numRunning++; } - if (topDescription == null) { - topDescription = r.description; - } if (localLOGV) Slog.v( TAG, r.intent.getComponent().flattenToShortString() @@ -4959,14 +4988,9 @@ public final class ActivityManagerService extends ActivityManagerNative ci.id = curTask.taskId; ci.baseActivity = r.intent.getComponent(); ci.topActivity = top.intent.getComponent(); - if (canReadFb) { - if (top.thumbnail != null) { - ci.thumbnail = top.thumbnail; - } else if (top.state == ActivityState.RESUMED) { - ci.thumbnail = top.stack.screenshotActivities(top); - } + if (top.thumbHolder != null) { + ci.description = top.thumbHolder.lastDescription; } - ci.description = topDescription; ci.numActivities = numActivities; ci.numRunning = numRunning; //System.out.println( @@ -5036,8 +5060,6 @@ public final class ActivityManagerService extends ActivityManagerNative IPackageManager pm = AppGlobals.getPackageManager(); - ActivityRecord resumed = mMainStack.mResumedActivity; - final int N = mRecentTasks.size(); ArrayList<ActivityManager.RecentTaskInfo> res = new ArrayList<ActivityManager.RecentTaskInfo>( @@ -5083,24 +5105,121 @@ public final class ActivityManagerService extends ActivityManagerNative } } - public Bitmap getTaskThumbnail(int id) { + private TaskRecord taskForIdLocked(int id) { + final int N = mRecentTasks.size(); + for (int i=0; i<N; i++) { + TaskRecord tr = mRecentTasks.get(i); + if (tr.taskId == id) { + return tr; + } + } + return null; + } + + public ActivityManager.TaskThumbnails getTaskThumbnails(int id) { synchronized (this) { enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER, - "getTaskThumbnail()"); - ActivityRecord resumed = mMainStack.mResumedActivity; - final int N = mRecentTasks.size(); - for (int i=0; i<N; i++) { - TaskRecord tr = mRecentTasks.get(i); - if (tr.taskId == id) { - if (resumed != null && resumed.task == tr) { - return resumed.stack.screenshotActivities(resumed); - } else { - return tr.lastThumbnail; + "getTaskThumbnails()"); + TaskRecord tr = taskForIdLocked(id); + if (tr != null) { + return mMainStack.getTaskThumbnailsLocked(tr); + } + } + return null; + } + + public boolean removeSubTask(int taskId, int subTaskIndex) { + synchronized (this) { + enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, + "removeSubTask()"); + long ident = Binder.clearCallingIdentity(); + try { + return mMainStack.removeTaskActivitiesLocked(taskId, subTaskIndex) != null; + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + private void removeTaskProcessesLocked(ActivityRecord root) { + TaskRecord tr = root.task; + Intent baseIntent = new Intent( + tr.intent != null ? tr.intent : tr.affinityIntent); + ComponentName component = baseIntent.getComponent(); + if (component == null) { + Slog.w(TAG, "Now component for base intent of task: " + tr); + return; + } + + // Find any running services associated with this app. + ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); + for (ServiceRecord sr : mServices.values()) { + if (sr.packageName.equals(component.getPackageName())) { + services.add(sr); + } + } + + // Take care of any running services associated with the app. + for (int i=0; i<services.size(); i++) { + ServiceRecord sr = services.get(i); + if (sr.startRequested) { + if ((sr.serviceInfo.flags&ServiceInfo.FLAG_STOP_WITH_TASK) != 0) { + stopServiceLocked(sr); + } else { + sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true, + sr.makeNextStartId(), baseIntent, -1)); + if (sr.app != null && sr.app.thread != null) { + sendServiceArgsLocked(sr, false); } } } } - return null; + + // Find any running processes associated with this app. + ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>(); + SparseArray<ProcessRecord> appProcs + = mProcessNames.getMap().get(component.getPackageName()); + if (appProcs != null) { + for (int i=0; i<appProcs.size(); i++) { + procs.add(appProcs.valueAt(i)); + } + } + + // Kill the running processes. + for (int i=0; i<procs.size(); i++) { + ProcessRecord pr = procs.get(i); + if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) { + Slog.i(TAG, "Killing " + pr + ": remove task"); + EventLog.writeEvent(EventLogTags.AM_KILL, pr.pid, + pr.processName, pr.setAdj, "remove task"); + Process.killProcessQuiet(pr.pid); + } else { + pr.waitingToKill = "remove task"; + } + } + } + + public boolean removeTask(int taskId, int flags) { + synchronized (this) { + enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, + "removeTask()"); + long ident = Binder.clearCallingIdentity(); + try { + ActivityRecord r = mMainStack.removeTaskActivitiesLocked(taskId, -1); + if (r != null) { + mRecentTasks.remove(r.task); + + if ((flags&ActivityManager.REMOVE_TASK_KILL_PROCESS) != 0) { + removeTaskProcessesLocked(r); + } + + return true; + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + return false; } private final int findAffinityTaskTopLocked(int startIndex, String affinity) { @@ -5154,21 +5273,18 @@ public final class ActivityManagerService extends ActivityManagerNative } final long origId = Binder.clearCallingIdentity(); try { - int N = mRecentTasks.size(); - for (int i=0; i<N; i++) { - TaskRecord tr = mRecentTasks.get(i); - if (tr.taskId == task) { - if ((flags&ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { - mMainStack.mUserLeaving = true; - } - if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) { - // Caller wants the home activity moved with it. To accomplish this, - // we'll just move the home task to the top first. - mMainStack.moveHomeToFrontLocked(); - } - mMainStack.moveTaskToFrontLocked(tr, null); - return; + TaskRecord tr = taskForIdLocked(task); + if (tr != null) { + if ((flags&ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { + mMainStack.mUserLeaving = true; } + if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) { + // Caller wants the home activity moved with it. To accomplish this, + // we'll just move the home task to the top first. + mMainStack.moveHomeToFrontLocked(); + } + mMainStack.moveTaskToFrontLocked(tr, null); + return; } for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { ActivityRecord hr = (ActivityRecord)mMainStack.mHistory.get(i); @@ -5322,9 +5438,9 @@ public final class ActivityManagerService extends ActivityManagerNative } r = (ActivityRecord)mMainStack.mHistory.get(index); } - if (thumbnail == null) { - thumbnail = r.thumbnail; - description = r.description; + if (thumbnail == null && r.thumbHolder != null) { + thumbnail = r.thumbHolder.lastThumbnail; + description = r.thumbHolder.lastDescription; } if (thumbnail == null && !always) { // If there is no thumbnail, and this entry is not actually @@ -6177,6 +6293,14 @@ public final class ActivityManagerService extends ActivityManagerNative } } + public void registerProcessObserver(IProcessObserver observer) { + mProcessObservers.register(observer); + } + + public void unregisterProcessObserver(IProcessObserver observer) { + mProcessObservers.unregister(observer); + } + public void setImmersive(IBinder token, boolean immersive) { synchronized(this) { int index = (token != null) ? mMainStack.indexOfTokenLocked(token) : -1; @@ -6692,11 +6816,10 @@ public final class ActivityManagerService extends ActivityManagerNative } if (app.pid > 0 && app.pid != MY_PID) { handleAppCrashLocked(app); - Slog.i(ActivityManagerService.TAG, "Killing " - + app.processName + " (pid=" + app.pid + "): user's request"); + Slog.i(ActivityManagerService.TAG, "Killing " + app + ": user's request"); EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, app.processName, app.setAdj, "user's request after error"); - Process.killProcess(app.pid); + Process.killProcessQuiet(app.pid); } } } @@ -8525,7 +8648,7 @@ public final class ActivityManagerService extends ActivityManagerNative final String[] args = new String[0]; for (int i=list.size()-1; i>=0; i--) { final ActivityRecord r = (ActivityRecord)list.get(i); - final boolean full = !brief && (complete || !r.inHistory); + final boolean full = !brief && (complete || !r.isInHistory()); if (needNL) { pw.println(" "); needNL = false; @@ -8657,9 +8780,17 @@ public final class ActivityManagerService extends ActivityManagerNative schedGroup = Integer.toString(r.setSchedGroup); break; } - pw.println(String.format("%s%s #%2d: adj=%s/%s %s (%s)", + String foreground; + if (r.foregroundActivities) { + foreground = "A"; + } else if (r.foregroundServices) { + foreground = "S"; + } else { + foreground = " "; + } + pw.println(String.format("%s%s #%2d: adj=%s/%s%s %s (%s)", prefix, (r.persistent ? persistentLabel : normalLabel), - N-i, oomAdj, schedGroup, r.toShortString(), r.adjType)); + N-i, oomAdj, schedGroup, foreground, r.toShortString(), r.adjType)); if (r.adjSource != null || r.adjTarget != null) { pw.print(prefix); pw.print(" "); @@ -8763,6 +8894,37 @@ public final class ActivityManagerService extends ActivityManagerNative return procs; } + final void dumpGraphicsHardwareUsage(FileDescriptor fd, + PrintWriter pw, String[] args) { + ArrayList<ProcessRecord> procs = collectProcesses(pw, args); + if (procs == null) { + return; + } + + long uptime = SystemClock.uptimeMillis(); + long realtime = SystemClock.elapsedRealtime(); + pw.println("Applications Graphics Acceleration Info:"); + pw.println("Uptime: " + uptime + " Realtime: " + realtime); + + String callArgs[] = {"graphics"}; + for (int i = procs.size() - 1 ; i >= 0 ; i--) { + ProcessRecord r = procs.get(i); + if (r.thread != null) { + pw.println("\n** Graphics info for pid " + r.pid + " [" + r.processName + "] **"); + pw.flush(); + try { + TransferPipe.goDump(r.thread.asBinder(), fd, callArgs); + } catch (IOException e) { + pw.println("Failure: " + e); + pw.flush(); + } catch (RemoteException e) { + pw.println("Got RemoteException!"); + pw.flush(); + } + } + } + } + final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw, String prefix, String[] args) { ArrayList<ProcessRecord> procs = collectProcesses(pw, args); @@ -8963,7 +9125,7 @@ public final class ActivityManagerService extends ActivityManagerNative + " in dying process " + proc.processName); EventLog.writeEvent(EventLogTags.AM_KILL, capp.pid, capp.processName, capp.setAdj, "dying provider " + proc.processName); - Process.killProcess(capp.pid); + Process.killProcessQuiet(capp.pid); } } @@ -9004,6 +9166,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.thread = null; app.forcingToForeground = null; app.foregroundServices = false; + app.foregroundActivities = false; killServicesLocked(app, true); @@ -9097,6 +9260,8 @@ public final class ActivityManagerService extends ActivityManagerNative } } + mHandler.obtainMessage(DISPATCH_PROCESS_DIED, app.pid, app.info.uid, null).sendToTarget(); + // If the caller is restarting this app, then leave it in its // current lists and let the caller take care of it. if (restarting) { @@ -9470,7 +9635,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (si.doneExecutingCount > 0) { flags |= Service.START_FLAG_REDELIVERY; } - r.app.thread.scheduleServiceArgs(r, si.id, flags, si.intent); + r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent); } catch (RemoteException e) { // Remote process gone... we'll let the normal cleanup take // care of this. @@ -9557,11 +9722,8 @@ public final class ActivityManagerService extends ActivityManagerNative // 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, r.lastStartId, null, -1)); + r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), + null, -1)); } sendServiceArgsLocked(r, true); @@ -9915,11 +10077,7 @@ public final class ActivityManagerService extends ActivityManagerNative } r.startRequested = true; r.callStart = false; - r.lastStartId++; - if (r.lastStartId < 1) { - r.lastStartId = 1; - } - r.pendingStarts.add(new ServiceRecord.StartItem(r, r.lastStartId, + r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), service, targetPermissionUid)); r.lastActivity = SystemClock.uptimeMillis(); synchronized (r.stats.getBatteryStats()) { @@ -9961,6 +10119,15 @@ public final class ActivityManagerService extends ActivityManagerNative } } + private void stopServiceLocked(ServiceRecord service) { + synchronized (service.stats.getBatteryStats()) { + service.stats.stopRunningLocked(); + } + service.startRequested = false; + service.callStart = false; + bringDownServiceLocked(service, false); + } + public int stopService(IApplicationThread caller, Intent service, String resolvedType) { // Refuse possible leaked file descriptors @@ -9984,14 +10151,12 @@ public final class ActivityManagerService extends ActivityManagerNative ServiceLookupResult r = findServiceLocked(service, resolvedType); if (r != null) { if (r.record != null) { - synchronized (r.record.stats.getBatteryStats()) { - r.record.stats.stopRunningLocked(); - } - r.record.startRequested = false; - r.record.callStart = false; final long origId = Binder.clearCallingIdentity(); - bringDownServiceLocked(r.record, false); - Binder.restoreCallingIdentity(origId); + try { + stopServiceLocked(r.record); + } finally { + Binder.restoreCallingIdentity(origId); + } return 1; } return -1; @@ -10053,7 +10218,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - if (r.lastStartId != startId) { + if (r.getLastStartId() != startId) { return false; } @@ -10494,7 +10659,7 @@ public final class ActivityManagerService extends ActivityManagerNative case Service.START_NOT_STICKY: { // We are done with the associated start arguments. r.findDeliveredStart(startId, true); - if (r.lastStartId == startId) { + if (r.getLastStartId() == startId) { // There is no more work, and this service // doesn't want to hang around if killed. r.stopIfKilled = true; @@ -10514,6 +10679,12 @@ public final class ActivityManagerService extends ActivityManagerNative } break; } + case Service.START_TASK_REMOVED_COMPLETE: { + // Special processing for onTaskRemoved(). Don't + // impact normal onStartCommand() processing. + r.findDeliveredStart(startId, true); + break; + } default: throw new IllegalArgumentException( "Unknown service start result: " + res); @@ -10624,7 +10795,9 @@ public final class ActivityManagerService extends ActivityManagerNative } BackupRecord r = new BackupRecord(ss, app, backupMode); - ComponentName hostingName = new ComponentName(app.packageName, app.backupAgentName); + ComponentName hostingName = (backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL) + ? new ComponentName(app.packageName, app.backupAgentName) + : new ComponentName("android", "FullBackupAgent"); // startProcessLocked() returns existing proc's record if it's already running ProcessRecord proc = startProcessLocked(app.processName, app, false, 0, "backup", hostingName, false); @@ -12334,25 +12507,30 @@ public final class ActivityManagerService extends ActivityManagerNative app.keeping = true; app.curSchedGroup = Process.THREAD_GROUP_DEFAULT; return (app.curAdj=app.maxAdj); - } - + } + + final boolean hadForegroundActivities = app.foregroundActivities; + app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN; app.adjSource = null; app.adjTarget = null; app.keeping = false; app.empty = false; app.hidden = false; + app.foregroundActivities = false; + + final int activitiesSize = app.activities.size(); // Determine the importance of the process, starting with most // important to least, and assign an appropriate OOM adjustment. int adj; int schedGroup; - int N; if (app == TOP_APP) { // The last app on the list is the foreground app. adj = FOREGROUND_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "top-activity"; + app.foregroundActivities = true; } else if (app.instrumentationClass != null) { // Don't want to kill running instrumentation. adj = FOREGROUND_APP_ADJ; @@ -12371,54 +12549,64 @@ public final class ActivityManagerService extends ActivityManagerNative adj = FOREGROUND_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "exec-service"; - } else if ((N=app.activities.size()) != 0) { + } else if (activitiesSize > 0) { // This app is in the background with paused activities. - app.hidden = true; + // We inspect activities to potentially upgrade adjustment further below. adj = hiddenAdj; schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; + app.hidden = true; app.adjType = "bg-activities"; - N = app.activities.size(); - for (int j=0; j<N; j++) { - ActivityRecord r = app.activities.get(j); - if (r.visible) { - // This app has a visible activity! - app.hidden = false; - adj = VISIBLE_APP_ADJ; - schedGroup = Process.THREAD_GROUP_DEFAULT; - app.adjType = "visible"; - break; - } else if (r.state == ActivityState.PAUSING - || r.state == ActivityState.PAUSED - || r.state == ActivityState.STOPPING) { - adj = PERCEPTIBLE_APP_ADJ; - app.adjType = "stopping"; - } - } } else { // A very not-needed process. If this is lower in the lru list, // we will push it in to the empty bucket. + adj = hiddenAdj; + schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; app.hidden = true; app.empty = true; - schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; - adj = hiddenAdj; app.adjType = "bg-empty"; } - + + // Examine all activities if not already foreground. + if (!app.foregroundActivities && activitiesSize > 0) { + for (int j = 0; j < activitiesSize; j++) { + final ActivityRecord r = app.activities.get(j); + if (r.visible) { + // App has a visible activity; only upgrade adjustment. + if (adj > VISIBLE_APP_ADJ) { + adj = VISIBLE_APP_ADJ; + app.adjType = "visible"; + } + schedGroup = Process.THREAD_GROUP_DEFAULT; + app.hidden = false; + app.foregroundActivities = true; + break; + } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED + || r.state == ActivityState.STOPPING) { + // Only upgrade adjustment. + if (adj > PERCEPTIBLE_APP_ADJ) { + adj = PERCEPTIBLE_APP_ADJ; + app.adjType = "stopping"; + } + app.foregroundActivities = true; + } + } + } + if (adj > PERCEPTIBLE_APP_ADJ) { if (app.foregroundServices) { // The user is aware of this app, so make it visible. adj = PERCEPTIBLE_APP_ADJ; - schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "foreground-service"; + schedGroup = Process.THREAD_GROUP_DEFAULT; } else if (app.forcingToForeground != null) { // The user is aware of this app, so make it visible. adj = PERCEPTIBLE_APP_ADJ; - schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "force-foreground"; app.adjSource = app.forcingToForeground; + schedGroup = Process.THREAD_GROUP_DEFAULT; } } - + if (adj > HEAVY_WEIGHT_APP_ADJ && app == mHeavyWeightProcess) { // We don't want to kill the current heavy-weight process. adj = HEAVY_WEIGHT_APP_ADJ; @@ -12638,7 +12826,12 @@ public final class ActivityManagerService extends ActivityManagerNative app.curAdj = adj; app.curSchedGroup = schedGroup; - + + if (hadForegroundActivities != app.foregroundActivities) { + mHandler.obtainMessage(DISPATCH_FOREGROUND_ACTIVITIES_CHANGED, app.pid, app.info.uid, + app.foregroundActivities).sendToTarget(); + } + return adj; } @@ -12871,13 +13064,15 @@ public final class ActivityManagerService extends ActivityManagerNative } private final boolean updateOomAdjLocked( - ProcessRecord app, int hiddenAdj, ProcessRecord TOP_APP) { + ProcessRecord app, int hiddenAdj, ProcessRecord TOP_APP) { app.hiddenAdj = hiddenAdj; if (app.thread == null) { return true; } + boolean success = true; + final boolean wasKeeping = app.keeping; int adj = computeOomAdjLocked(app, hiddenAdj, TOP_APP, false); @@ -12917,7 +13112,7 @@ public final class ActivityManagerService extends ActivityManagerNative " oom adj to " + adj); app.setAdj = adj; } else { - return false; + success = false; } } if (app.setSchedGroup != app.curSchedGroup) { @@ -12925,30 +13120,38 @@ public final class ActivityManagerService extends ActivityManagerNative if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG, "Setting process group of " + app.processName + " to " + app.curSchedGroup); - if (true) { - long oldId = Binder.clearCallingIdentity(); - try { - Process.setProcessGroup(app.pid, app.curSchedGroup); - } catch (Exception e) { - Slog.w(TAG, "Failed setting process group of " + app.pid - + " to " + app.curSchedGroup); - e.printStackTrace(); - } finally { - Binder.restoreCallingIdentity(oldId); - } - } - if (false) { - if (app.thread != null) { + if (app.waitingToKill != null && + app.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) { + Slog.i(TAG, "Killing " + app + ": " + app.waitingToKill); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, app.waitingToKill); + Process.killProcessQuiet(app.pid); + } else { + if (true) { + long oldId = Binder.clearCallingIdentity(); try { - app.thread.setSchedulingGroup(app.curSchedGroup); - } catch (RemoteException e) { + Process.setProcessGroup(app.pid, app.curSchedGroup); + } catch (Exception e) { + Slog.w(TAG, "Failed setting process group of " + app.pid + + " to " + app.curSchedGroup); + e.printStackTrace(); + } finally { + Binder.restoreCallingIdentity(oldId); + } + } + if (false) { + if (app.thread != null) { + try { + app.thread.setSchedulingGroup(app.curSchedGroup); + } catch (RemoteException e) { + } } } } } } - return true; + return success; } private final ActivityRecord resumedAppLocked() { @@ -13064,7 +13267,9 @@ public final class ActivityManagerService extends ActivityManagerNative + (app.thread != null ? app.thread.asBinder() : null) + ")\n"); if (app.pid > 0 && app.pid != MY_PID) { - Process.killProcess(app.pid); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, "empty"); + Process.killProcessQuiet(app.pid); } else { try { app.thread.scheduleExit(); @@ -13130,7 +13335,9 @@ public final class ActivityManagerService extends ActivityManagerNative + (app.thread != null ? app.thread.asBinder() : null) + ")\n"); if (app.pid > 0 && app.pid != MY_PID) { - Process.killProcess(app.pid); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, "empty"); + Process.killProcessQuiet(app.pid); } else { try { app.thread.scheduleExit(); @@ -13149,7 +13356,7 @@ public final class ActivityManagerService extends ActivityManagerNative // If we still have too many processes, now from the least // recently used process we start finishing activities. - if (Config.LOGV) Slog.v( + if (false) Slog.v( TAG, "*** NOW HAVE " + mLruProcesses.size() + " of " + curMaxProcs + " processes"); for ( i=0; @@ -13163,11 +13370,11 @@ public final class ActivityManagerService extends ActivityManagerNative && app.services.size() == 0; int NUMA = app.activities.size(); int j; - if (Config.LOGV) Slog.v( + if (false) Slog.v( TAG, "Looking to quit " + app.processName); for (j=0; j<NUMA && canQuit; j++) { ActivityRecord r = app.activities.get(j); - if (Config.LOGV) Slog.v( + if (false) Slog.v( TAG, " " + r.intent.getComponent().flattenToShortString() + ": frozen=" + r.haveState + ", visible=" + r.visible); canQuit = (r.haveState || !r.stateNotNeeded) @@ -13187,7 +13394,9 @@ public final class ActivityManagerService extends ActivityManagerNative + (app.thread != null ? app.thread.asBinder() : null) + ")\n"); if (app.pid > 0 && app.pid != MY_PID) { - Process.killProcess(app.pid); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, "old background"); + Process.killProcessQuiet(app.pid); } else { try { app.thread.scheduleExit(); @@ -13404,4 +13613,11 @@ public final class ActivityManagerService extends ActivityManagerNative } } } + + // Multi-user methods + + public boolean switchUser(int userid) { + // TODO + return true; + } } diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index 33c12f4..090e26b 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -48,7 +48,7 @@ import java.util.HashSet; /** * An entry in the history stack, representing an activity. */ -class ActivityRecord extends IApplicationToken.Stub { +final class ActivityRecord extends IApplicationToken.Stub { final ActivityManagerService service; // owner final ActivityStack stack; // owner final ActivityInfo info; // all about me @@ -75,6 +75,7 @@ class ActivityRecord extends IApplicationToken.Stub { int realTheme; // actual theme resource we will use, never 0. int windowFlags; // custom window flags for preview window. TaskRecord task; // the task this is in. + ThumbnailHolder thumbHolder; // where our thumbnails should go. long launchTime; // when we starting launching this activity long startTime; // last time this activity was started long cpuTimeAtResume; // the cpu time of host process at the time of resuming activity @@ -88,9 +89,7 @@ class ActivityRecord extends IApplicationToken.Stub { ArrayList newIntents; // any pending new intents for single-top mode HashSet<ConnectionRecord> connections; // All ConnectionRecord we hold UriPermissionOwner uriPermissions; // current special URI access perms. - ProcessRecord app; // if non-null, hosting application - Bitmap thumbnail; // icon representation of paused screen - CharSequence description; // textual description of paused screen + ProcessRecord app; // if non-null, hosting application ActivityState state; // current state we are in Bundle icicle; // last saved activity state boolean frontOfTask; // is this the root activity of its task? @@ -102,7 +101,6 @@ class ActivityRecord extends IApplicationToken.Stub { boolean configDestroy; // need to destroy due to config change? int configChangeFlags; // which config values have changed boolean keysPaused; // has key dispatching been paused for it? - boolean inHistory; // are we in the history stack? int launchMode; // the launch mode activity attribute. boolean visible; // does this activity's window need to be shown? boolean sleeping; // have we told the activity to sleep? @@ -117,6 +115,8 @@ class ActivityRecord extends IApplicationToken.Stub { String stringName; // for caching of toString(). + private boolean inHistory; // are we in the history stack? + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("packageName="); pw.print(packageName); pw.print(" processName="); pw.println(processName); @@ -180,6 +180,7 @@ class ActivityRecord extends IApplicationToken.Stub { pw.print(prefix); pw.print("frozenBeforeDestroy="); pw.print(frozenBeforeDestroy); pw.print(" thumbnailNeeded="); pw.print(thumbnailNeeded); pw.print(" forceNewConfig="); pw.println(forceNewConfig); + pw.print(prefix); pw.print("thumbHolder="); pw.println(thumbHolder); if (launchTime != 0 || startTime != 0) { pw.print(prefix); pw.print("launchTime="); TimeUtils.formatDuration(launchTime, pw); pw.print(" startTime="); @@ -333,10 +334,55 @@ class ActivityRecord extends IApplicationToken.Stub { } } + void setTask(TaskRecord newTask, ThumbnailHolder newThumbHolder, boolean isRoot) { + if (inHistory && !finishing) { + if (task != null) { + task.numActivities--; + } + if (newTask != null) { + newTask.numActivities++; + } + } + if (newThumbHolder == null) { + newThumbHolder = newTask; + } + task = newTask; + if (!isRoot && (intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { + // This is the start of a new sub-task. + if (thumbHolder == null) { + thumbHolder = new ThumbnailHolder(); + } + } else { + thumbHolder = newThumbHolder; + } + } + + void putInHistory() { + if (!inHistory) { + inHistory = true; + if (task != null && !finishing) { + task.numActivities++; + } + } + } + + void takeFromHistory() { + if (inHistory) { + inHistory = false; + if (task != null && !finishing) { + task.numActivities--; + } + } + } + + boolean isInHistory() { + return inHistory; + } + void makeFinishing() { if (!finishing) { finishing = true; - if (task != null) { + if (task != null && inHistory) { task.numActivities--; } } @@ -435,6 +481,25 @@ class ActivityRecord extends IApplicationToken.Stub { } } + void updateThumbnail(Bitmap newThumbnail, CharSequence description) { + if ((intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { + // This is a logical break in the task; it repre + } + if (thumbHolder != null) { + if (newThumbnail != null) { + thumbHolder.lastThumbnail = newThumbnail; + } + thumbHolder.lastDescription = description; + } + } + + void clearThumbnail() { + if (thumbHolder != null) { + thumbHolder.lastThumbnail = null; + thumbHolder.lastDescription = null; + } + } + // IApplicationToken public boolean mayFreezeScreenLocked(ProcessRecord app) { diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 5cbb9e1..d8772b8 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -21,8 +21,10 @@ import com.android.internal.os.BatteryStatsImpl; import com.android.server.am.ActivityManagerService.PendingActivityLaunch; import android.app.Activity; +import android.app.ActivityManager; import android.app.AppGlobals; import android.app.IActivityManager; +import android.app.IThumbnailRetriever; import static android.app.IActivityManager.START_CLASS_NOT_FOUND; import static android.app.IActivityManager.START_DELIVERED_TO_TOP; import static android.app.IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; @@ -69,9 +71,9 @@ import java.util.List; /** * State and management of a single stack of activities. */ -public class ActivityStack { +final class ActivityStack { static final String TAG = ActivityManagerService.TAG; - static final boolean localLOGV = ActivityManagerService.localLOGV; + static final boolean localLOGV = ActivityManagerService.localLOGV || true; static final boolean DEBUG_SWITCH = ActivityManagerService.DEBUG_SWITCH; static final boolean DEBUG_PAUSE = ActivityManagerService.DEBUG_PAUSE; static final boolean DEBUG_VISBILITY = ActivityManagerService.DEBUG_VISBILITY; @@ -135,14 +137,14 @@ public class ActivityStack { * The back history of all previous (and possibly still * running) activities. It contains HistoryRecord objects. */ - final ArrayList mHistory = new ArrayList(); + final ArrayList<ActivityRecord> mHistory = new ArrayList<ActivityRecord>(); /** * List of running activities, sorted by recent usage. * The first entry in the list is the least recently used. * It contains HistoryRecord objects. */ - final ArrayList mLRUActivities = new ArrayList(); + final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<ActivityRecord>(); /** * List of activities that are waiting for a new activity @@ -346,7 +348,7 @@ public class ActivityStack { final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) { int i = mHistory.size()-1; while (i >= 0) { - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (!r.finishing && r != notTop) { return r; } @@ -358,7 +360,7 @@ public class ActivityStack { final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) { int i = mHistory.size()-1; while (i >= 0) { - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (!r.finishing && !r.delayedResume && r != notTop) { return r; } @@ -379,7 +381,7 @@ public class ActivityStack { final ActivityRecord topRunningActivityLocked(IBinder token, int taskId) { int i = mHistory.size()-1; while (i >= 0) { - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); // Note: the taskId check depends on real taskId fields being non-zero if (!r.finishing && (token != r) && (taskId != r.task.taskId)) { return r; @@ -425,7 +427,7 @@ public class ActivityStack { final int N = mHistory.size(); for (int i=(N-1); i>=0; i--) { - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (!r.finishing && r.task != cp && r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE) { cp = r.task; @@ -469,7 +471,7 @@ public class ActivityStack { final int N = mHistory.size(); for (int i=(N-1); i>=0; i--) { - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (!r.finishing) { if (r.intent.getComponent().equals(cls)) { //Slog.i(TAG, "Found matching class!"); @@ -511,6 +513,7 @@ public class ActivityStack { } r.app = app; + app.waitingToKill = null; if (localLOGV) Slog.v(TAG, "Launching: " + r); @@ -688,7 +691,7 @@ public class ActivityStack { } // Ensure activities are no longer sleeping. for (int i=mHistory.size()-1; i>=0; i--) { - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); r.setSleeping(false); } mGoingToSleepActivities.clear(); @@ -734,7 +737,7 @@ public class ActivityStack { // Make sure any stopped but visible activities are now sleeping. // This ensures that the activity's onStop() is called. for (int i=mHistory.size()-1; i>=0; i--) { - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (r.state == ActivityState.STOPPING || r.state == ActivityState.STOPPED) { r.setSleeping(true); } @@ -799,10 +802,7 @@ public class ActivityStack { mLastPausedActivity = prev; prev.state = ActivityState.PAUSING; prev.task.touchActiveTime(); - prev.thumbnail = screenshotActivities(prev); - if (prev.task != null) { - prev.task.lastThumbnail = prev.thumbnail; - } + prev.updateThumbnail(screenshotActivities(prev), null); mService.updateCpuStats(); @@ -875,7 +875,7 @@ public class ActivityStack { synchronized (mService) { int index = indexOfTokenLocked(token); if (index >= 0) { - r = (ActivityRecord)mHistory.get(index); + r = mHistory.get(index); mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); if (mPausingActivity == r) { r.state = ActivityState.PAUSED; @@ -996,7 +996,7 @@ public class ActivityStack { mService.reportResumedActivityLocked(next); } - next.thumbnail = null; + next.clearThumbnail(); if (mMainStack) { mService.setFocusedActivityLocked(next); } @@ -1037,7 +1037,7 @@ public class ActivityStack { ActivityRecord r; boolean behindFullscreen = false; for (; i>=0; i--) { - r = (ActivityRecord)mHistory.get(i); + r = mHistory.get(i); if (DEBUG_VISBILITY) Slog.v( TAG, "Make visible? " + r + " finishing=" + r.finishing + " state=" + r.state); @@ -1121,7 +1121,7 @@ public class ActivityStack { // Now for any activities that aren't visible to the user, make // sure they no longer are keeping the screen frozen. while (i >= 0) { - r = (ActivityRecord)mHistory.get(i); + r = mHistory.get(i); if (DEBUG_VISBILITY) Slog.v( TAG, "Make invisible? " + r + " finishing=" + r.finishing + " state=" + r.state @@ -1517,7 +1517,7 @@ public class ActivityStack { // If starting in an existing task, find where that is... boolean startIt = true; for (int i = NH-1; i >= 0; i--) { - ActivityRecord p = (ActivityRecord)mHistory.get(i); + ActivityRecord p = mHistory.get(i); if (p.finishing) { continue; } @@ -1528,8 +1528,7 @@ public class ActivityStack { addPos = i+1; if (!startIt) { mHistory.add(addPos, r); - r.inHistory = true; - r.task.numActivities++; + r.putInHistory(); mService.mWindowManager.addAppToken(addPos, r, r.task.taskId, r.info.screenOrientation, r.fullscreen); if (VALIDATE_TOKENS) { @@ -1561,9 +1560,8 @@ public class ActivityStack { // Slot the activity into the history stack and proceed mHistory.add(addPos, r); - r.inHistory = true; + r.putInHistory(); r.frontOfTask = newTask; - r.task.numActivities++; if (NH > 0) { // We want to show the starting preview window if we are // switching to a new task, or the next activity's process is @@ -1668,7 +1666,7 @@ public class ActivityStack { int replyChainEnd = -1; int lastReparentPos = -1; for (int i=mHistory.size()-1; i>=-1; i--) { - ActivityRecord below = i >= 0 ? (ActivityRecord)mHistory.get(i) : null; + ActivityRecord below = i >= 0 ? mHistory.get(i) : null; if (below != null && below.finishing) { continue; @@ -1722,13 +1720,13 @@ public class ActivityStack { // bottom of the activity stack. This also keeps it // correctly ordered with any activities we previously // moved. - ActivityRecord p = (ActivityRecord)mHistory.get(0); + ActivityRecord p = mHistory.get(0); if (target.taskAffinity != null && target.taskAffinity.equals(p.task.affinity)) { // If the activity currently at the bottom has the // same task affinity as the one we are moving, // then merge it into the same task. - target.task = p.task; + target.setTask(p.task, p.thumbHolder, false); if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + " out to bottom task " + p.task); } else { @@ -1736,7 +1734,8 @@ public class ActivityStack { if (mService.mCurTask <= 0) { mService.mCurTask = 1; } - target.task = new TaskRecord(mService.mCurTask, target.info, null); + target.setTask(new TaskRecord(mService.mCurTask, target.info, null), + null, false); target.task.affinityIntent = target.intent; if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + " out to new task " + target.task); @@ -1746,16 +1745,16 @@ public class ActivityStack { replyChainEnd = targetI; } int dstPos = 0; + ThumbnailHolder curThumbHolder = target.thumbHolder; for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { - p = (ActivityRecord)mHistory.get(srcPos); + p = mHistory.get(srcPos); if (p.finishing) { continue; } if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p + " out to target's task " + target.task); - task.numActivities--; - p.task = target.task; - target.task.numActivities++; + p.setTask(target.task, curThumbHolder, false); + curThumbHolder = p.thumbHolder; mHistory.remove(srcPos); mHistory.add(dstPos, p); mService.mWindowManager.moveAppToken(dstPos, p); @@ -1785,7 +1784,7 @@ public class ActivityStack { // like these are all in the reply chain. replyChainEnd = targetI+1; while (replyChainEnd < mHistory.size() && - ((ActivityRecord)mHistory.get( + (mHistory.get( replyChainEnd)).task == task) { replyChainEnd++; } @@ -1795,7 +1794,7 @@ public class ActivityStack { } ActivityRecord p = null; for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { - p = (ActivityRecord)mHistory.get(srcPos); + p = mHistory.get(srcPos); if (p.finishing) { continue; } @@ -1855,7 +1854,7 @@ public class ActivityStack { } ActivityRecord p = null; for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { - p = (ActivityRecord)mHistory.get(srcPos); + p = mHistory.get(srcPos); if (p.finishing) { continue; } @@ -1873,7 +1872,7 @@ public class ActivityStack { replyChainEnd = targetI; } for (int srcPos=replyChainEnd; srcPos>=targetI; srcPos--) { - ActivityRecord p = (ActivityRecord)mHistory.get(srcPos); + ActivityRecord p = mHistory.get(srcPos); if (p.finishing) { continue; } @@ -1884,12 +1883,10 @@ public class ActivityStack { lastReparentPos--; } mHistory.remove(srcPos); - p.task.numActivities--; - p.task = task; + p.setTask(task, null, false); mHistory.add(lastReparentPos, p); if (DEBUG_TASKS) Slog.v(TAG, "Pulling activity " + p + " in to resetting task " + task); - task.numActivities++; mService.mWindowManager.moveAppToken(lastReparentPos, p); mService.mWindowManager.setAppGroupId(p, p.task.taskId); if (VALIDATE_TOKENS) { @@ -1904,7 +1901,7 @@ public class ActivityStack { // below so it remains singleTop. if (target.info.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) { for (int j=lastReparentPos-1; j>=0; j--) { - ActivityRecord p = (ActivityRecord)mHistory.get(j); + ActivityRecord p = mHistory.get(j); if (p.finishing) { continue; } @@ -1945,7 +1942,7 @@ public class ActivityStack { // First find the requested task. while (i > 0) { i--; - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (r.task.taskId == taskId) { i++; break; @@ -1955,7 +1952,7 @@ public class ActivityStack { // Now clear it. while (i > 0) { i--; - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (r.finishing) { continue; } @@ -1967,7 +1964,7 @@ public class ActivityStack { ActivityRecord ret = r; while (i < (mHistory.size()-1)) { i++; - r = (ActivityRecord)mHistory.get(i); + r = mHistory.get(i); if (r.task.taskId != taskId) { break; } @@ -2003,6 +2000,28 @@ public class ActivityStack { } /** + * Completely remove all activities associated with an existing + * task starting at a specified index. + */ + private final void performClearTaskAtIndexLocked(int taskId, int i) { + while (i < (mHistory.size()-1)) { + ActivityRecord r = mHistory.get(i); + if (r.task.taskId != taskId) { + // Whoops hit the end. + return; + } + if (r.finishing) { + i++; + continue; + } + if (!finishActivityLocked(r, i, Activity.RESULT_CANCELED, + null, "clear")) { + i++; + } + } + } + + /** * Completely remove all activities associated with an existing task. */ private final void performClearTaskLocked(int taskId) { @@ -2011,37 +2030,23 @@ public class ActivityStack { // First find the requested task. while (i > 0) { i--; - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (r.task.taskId == taskId) { i++; break; } } - // Now clear it. + // Now find the start and clear it. while (i > 0) { i--; - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (r.finishing) { continue; } if (r.task.taskId != taskId) { // We hit the bottom. Now finish it all... - while (i < (mHistory.size()-1)) { - i++; - r = (ActivityRecord)mHistory.get(i); - if (r.task.taskId != taskId) { - // Whoops hit the end. - return; - } - if (r.finishing) { - continue; - } - if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "clear")) { - i--; - } - } + performClearTaskAtIndexLocked(taskId, i+1); return; } } @@ -2055,7 +2060,7 @@ public class ActivityStack { int i = mHistory.size(); while (i > 0) { i--; - ActivityRecord candidate = (ActivityRecord)mHistory.get(i); + ActivityRecord candidate = mHistory.get(i); if (candidate.task.taskId != task) { break; } @@ -2072,9 +2077,9 @@ public class ActivityStack { * brought to the front. */ private final ActivityRecord moveActivityToFrontLocked(int where) { - ActivityRecord newTop = (ActivityRecord)mHistory.remove(where); + ActivityRecord newTop = mHistory.remove(where); int top = mHistory.size(); - ActivityRecord oldTop = (ActivityRecord)mHistory.get(top-1); + ActivityRecord oldTop = mHistory.get(top-1); mHistory.add(top, newTop); oldTop.frontOfTask = false; newTop.frontOfTask = true; @@ -2117,7 +2122,7 @@ public class ActivityStack { if (DEBUG_RESULTS) Slog.v( TAG, "Sending result to " + resultTo + " (index " + index + ")"); if (index >= 0) { - sourceRecord = (ActivityRecord)mHistory.get(index); + sourceRecord = mHistory.get(index); if (requestCode >= 0 && !sourceRecord.finishing) { resultRecord = sourceRecord; } @@ -2542,11 +2547,11 @@ public class ActivityStack { if (mService.mCurTask <= 0) { mService.mCurTask = 1; } - r.task = new TaskRecord(mService.mCurTask, r.info, intent); + r.setTask(new TaskRecord(mService.mCurTask, r.info, intent), null, true); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " + r.task); } else { - r.task = reuseTask; + r.setTask(reuseTask, reuseTask, true); } newTask = true; moveHomeToFrontFromLaunchLocked(launchFlags); @@ -2589,7 +2594,7 @@ public class ActivityStack { // An existing activity is starting this new activity, so we want // to keep the new one in the same task as the one that is starting // it. - r.task = sourceRecord.task; + r.setTask(sourceRecord.task, sourceRecord.thumbHolder, false); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in existing task " + r.task); @@ -2599,10 +2604,10 @@ public class ActivityStack { // this case should never happen. final int N = mHistory.size(); ActivityRecord prev = - N > 0 ? (ActivityRecord)mHistory.get(N-1) : null; - r.task = prev != null + N > 0 ? mHistory.get(N-1) : null; + r.setTask(prev != null ? prev.task - : new TaskRecord(mService.mCurTask, r.info, intent); + : new TaskRecord(mService.mCurTask, r.info, intent), null, true); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new guessed " + r.task); } @@ -3044,7 +3049,7 @@ public class ActivityStack { // Get the activity record. int index = indexOfTokenLocked(token); if (index >= 0) { - ActivityRecord r = (ActivityRecord)mHistory.get(index); + ActivityRecord r = mHistory.get(index); if (fromTimeout) { reportActivityLaunchedLocked(fromTimeout, r, -1, -1); @@ -3176,12 +3181,12 @@ public class ActivityStack { if (index < 0) { return false; } - ActivityRecord r = (ActivityRecord)mHistory.get(index); + ActivityRecord r = mHistory.get(index); // Is this the last activity left? boolean lastActivity = true; for (int i=mHistory.size()-1; i>=0; i--) { - ActivityRecord p = (ActivityRecord)mHistory.get(i); + ActivityRecord p = mHistory.get(i); if (!p.finishing && p != r) { lastActivity = false; break; @@ -3216,7 +3221,7 @@ public class ActivityStack { System.identityHashCode(r), r.task.taskId, r.shortComponentName, reason); if (index < (mHistory.size()-1)) { - ActivityRecord next = (ActivityRecord)mHistory.get(index+1); + ActivityRecord next = mHistory.get(index+1); if (next.task == r.task) { if (r.frontOfTask) { // The next activity is now the front of the task. @@ -3272,7 +3277,7 @@ public class ActivityStack { if (mResumedActivity == r) { boolean endTask = index <= 0 - || ((ActivityRecord)mHistory.get(index-1)).task != r.task; + || (mHistory.get(index-1)).task != r.task; if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare close transition: finishing " + r); mService.mWindowManager.prepareAppTransition(endTask @@ -3414,13 +3419,14 @@ public class ActivityStack { // Get rid of any pending idle timeouts. mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); mHandler.removeMessages(IDLE_TIMEOUT_MSG, r); + mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r); } private final void removeActivityFromHistoryLocked(ActivityRecord r) { if (r.state != ActivityState.DESTROYED) { r.makeFinishing(); mHistory.remove(r); - r.inHistory = false; + r.takeFromHistory(); r.state = ActivityState.DESTROYED; mService.mWindowManager.removeAppToken(r); if (VALIDATE_TOKENS) { @@ -3539,7 +3545,7 @@ public class ActivityStack { int index = indexOfTokenLocked(token); if (index >= 0) { - ActivityRecord r = (ActivityRecord)mHistory.get(index); + ActivityRecord r = mHistory.get(index); if (r.state == ActivityState.DESTROYING) { final long origId = Binder.clearCallingIdentity(); removeActivityFromHistoryLocked(r); @@ -3581,7 +3587,7 @@ public class ActivityStack { final void moveHomeToFrontLocked() { TaskRecord homeTask = null; for (int i=mHistory.size()-1; i>=0; i--) { - ActivityRecord hr = (ActivityRecord)mHistory.get(i); + ActivityRecord hr = mHistory.get(i); if (hr.isHomeActivity) { homeTask = hr.task; break; @@ -3599,7 +3605,7 @@ public class ActivityStack { final int task = tr.taskId; int top = mHistory.size()-1; - if (top < 0 || ((ActivityRecord)mHistory.get(top)).task.taskId == task) { + if (top < 0 || (mHistory.get(top)).task.taskId == task) { // nothing to do! return; } @@ -3614,7 +3620,7 @@ public class ActivityStack { // Shift all activities with this task up to the top // of the stack, keeping them in the same internal order. while (pos >= 0) { - ActivityRecord r = (ActivityRecord)mHistory.get(pos); + ActivityRecord r = mHistory.get(pos); if (localLOGV) Slog.v( TAG, "At " + pos + " ckp " + r.task + ": " + r); if (r.task.taskId == task) { @@ -3703,7 +3709,7 @@ public class ActivityStack { // Shift all activities with this task down to the bottom // of the stack, keeping them in the same internal order. while (pos < N) { - ActivityRecord r = (ActivityRecord)mHistory.get(pos); + ActivityRecord r = mHistory.get(pos); if (localLOGV) Slog.v( TAG, "At " + pos + " ckp " + r.task + ": " + r); if (r.task.taskId == task) { @@ -3737,6 +3743,106 @@ public class ActivityStack { return true; } + public ActivityManager.TaskThumbnails getTaskThumbnailsLocked(TaskRecord tr) { + TaskAccessInfo info = getTaskAccessInfoLocked(tr.taskId, true); + ActivityRecord resumed = mResumedActivity; + if (resumed != null && resumed.thumbHolder == tr) { + info.mainThumbnail = resumed.stack.screenshotActivities(resumed); + } else { + info.mainThumbnail = tr.lastThumbnail; + } + return info; + } + + public ActivityRecord removeTaskActivitiesLocked(int taskId, int subTaskIndex) { + TaskAccessInfo info = getTaskAccessInfoLocked(taskId, false); + if (info.root == null) { + Slog.w(TAG, "removeTaskLocked: unknown taskId " + taskId); + return null; + } + + if (subTaskIndex < 0) { + // Just remove the entire task. + performClearTaskAtIndexLocked(taskId, info.rootIndex); + return info.root; + } + + if (subTaskIndex >= info.subtasks.size()) { + Slog.w(TAG, "removeTaskLocked: unknown subTaskIndex " + subTaskIndex); + return null; + } + + // Remove all of this task's activies starting at the sub task. + TaskAccessInfo.SubTask subtask = info.subtasks.get(subTaskIndex); + performClearTaskAtIndexLocked(taskId, subtask.index); + return subtask.activity; + } + + public TaskAccessInfo getTaskAccessInfoLocked(int taskId, boolean inclThumbs) { + ActivityRecord resumed = mResumedActivity; + final TaskAccessInfo thumbs = new TaskAccessInfo(); + // How many different sub-thumbnails? + final int NA = mHistory.size(); + int j = 0; + ThumbnailHolder holder = null; + while (j < NA) { + ActivityRecord ar = mHistory.get(j); + if (!ar.finishing && ar.task.taskId == taskId) { + holder = ar.thumbHolder; + break; + } + j++; + } + + if (j >= NA) { + return thumbs; + } + + thumbs.root = mHistory.get(j); + thumbs.rootIndex = j; + + ArrayList<TaskAccessInfo.SubTask> subtasks = new ArrayList<TaskAccessInfo.SubTask>(); + thumbs.subtasks = subtasks; + ActivityRecord lastActivity = null; + while (j < NA) { + ActivityRecord ar = mHistory.get(j); + j++; + if (ar.finishing) { + continue; + } + if (ar.task.taskId != taskId) { + break; + } + lastActivity = ar; + if (ar.thumbHolder != holder && holder != null) { + thumbs.numSubThumbbails++; + holder = ar.thumbHolder; + TaskAccessInfo.SubTask sub = new TaskAccessInfo.SubTask(); + sub.thumbnail = holder.lastThumbnail; + sub.activity = ar; + sub.index = j-1; + subtasks.add(sub); + } + } + if (lastActivity != null && subtasks.size() > 0) { + if (resumed == lastActivity) { + TaskAccessInfo.SubTask sub = subtasks.get(subtasks.size()-1); + sub.thumbnail = lastActivity.stack.screenshotActivities(lastActivity); + } + } + if (thumbs.numSubThumbbails > 0) { + thumbs.retriever = new IThumbnailRetriever.Stub() { + public Bitmap getThumbnail(int index) { + if (index < 0 || index >= thumbs.subtasks.size()) { + return null; + } + return thumbs.subtasks.get(index).thumbnail; + } + }; + } + return thumbs; + } + private final void logStartActivity(int tag, ActivityRecord r, TaskRecord task) { EventLog.writeEvent(tag, diff --git a/services/java/com/android/server/am/BackupRecord.java b/services/java/com/android/server/am/BackupRecord.java index 6590b91..7e73106 100644 --- a/services/java/com/android/server/am/BackupRecord.java +++ b/services/java/com/android/server/am/BackupRecord.java @@ -26,6 +26,7 @@ class BackupRecord { public static final int BACKUP_NORMAL = 0; public static final int BACKUP_FULL = 1; public static final int RESTORE = 2; + public static final int RESTORE_FULL = 3; final BatteryStatsImpl.Uid.Pkg.Serv stats; String stringName; // cached toString() output diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index 963a691..b4fdc9f 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -449,6 +449,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { boolean isCheckin = false; + boolean noOutput = false; if (args != null) { for (String arg : args) { if ("--checkin".equals(arg)) { @@ -457,10 +458,22 @@ public final class BatteryStatsService extends IBatteryStats.Stub { synchronized (mStats) { mStats.resetAllStatsLocked(); pw.println("Battery stats reset."); + noOutput = true; } + } else if ("--write".equals(arg)) { + synchronized (mStats) { + mStats.writeSyncLocked(); + pw.println("Battery stats written."); + noOutput = true; + } + } else { + pw.println("Unknown option: " + arg); } } } + if (noOutput) { + return; + } if (isCheckin) { List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0); synchronized (mStats) { diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index 5465e37..3968f66 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -64,8 +64,10 @@ class ProcessRecord { boolean keeping; // Actively running code so don't kill due to that? boolean setIsForeground; // Running foreground UI when last set? boolean foregroundServices; // Running any services that are foreground? + boolean foregroundActivities; // Running any activities that are foreground? boolean bad; // True if disabled in the bad process list boolean killedBackground; // True when proc has been killed due to too many bg + String waitingToKill; // Process is waiting to be killed when in the bg; reason IBinder forcingToForeground;// Token that is forcing this process to be foreground int adjSeq; // Sequence id for identifying oom_adj assignment cycles int lruSeq; // Sequence id for identifying LRU update cycles @@ -205,8 +207,9 @@ class ProcessRecord { pw.print(" lastLowMemory="); TimeUtils.formatDuration(lastLowMemory, now, pw); pw.print(" reportLowMemory="); pw.println(reportLowMemory); - if (killedBackground) { - pw.print(prefix); pw.print("killedBackground="); pw.println(killedBackground); + if (killedBackground || waitingToKill != null) { + pw.print(prefix); pw.print("killedBackground="); pw.print(killedBackground); + pw.print(" waitingToKill="); pw.println(waitingToKill); } if (debugging || crashing || crashDialog != null || notResponding || anrDialog != null || bad) { diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index 1a617dd..004e963 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -84,7 +84,6 @@ class ServiceRecord extends Binder { 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. int crashCount; // number of times proc has crashed with service running @@ -96,8 +95,11 @@ class ServiceRecord extends Binder { String stringName; // caching of toString + private int lastStartId; // identifier of most recent start request. + static class StartItem { final ServiceRecord sr; + final boolean taskRemoved; final int id; final Intent intent; final int targetPermissionUid; @@ -108,8 +110,10 @@ class ServiceRecord extends Binder { String stringName; // caching of toString - StartItem(ServiceRecord _sr, int _id, Intent _intent, int _targetPermissionUid) { + StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id, Intent _intent, + int _targetPermissionUid) { sr = _sr; + taskRemoved = _taskRemoved; id = _id; intent = _intent; targetPermissionUid = _targetPermissionUid; @@ -198,7 +202,9 @@ class ServiceRecord extends Binder { long now = SystemClock.uptimeMillis(); long nowReal = SystemClock.elapsedRealtime(); pw.print(prefix); pw.print("baseDir="); pw.println(baseDir); - if (!resDir.equals(baseDir)) pw.print(prefix); pw.print("resDir="); pw.println(resDir); + if (!resDir.equals(baseDir)) { + pw.print(prefix); pw.print("resDir="); pw.println(resDir); + } pw.print(prefix); pw.print("dataDir="); pw.println(dataDir); pw.print(prefix); pw.print("app="); pw.println(app); if (isForeground || foregroundId != 0) { @@ -321,6 +327,18 @@ class ServiceRecord extends Binder { return null; } + public int getLastStartId() { + return lastStartId; + } + + public int makeNextStartId() { + lastStartId++; + if (lastStartId < 1) { + lastStartId = 1; + } + return lastStartId; + } + public void postNotification() { final int appUid = appInfo.uid; final int appPid = app.pid; diff --git a/services/java/com/android/server/am/TaskAccessInfo.java b/services/java/com/android/server/am/TaskAccessInfo.java new file mode 100644 index 0000000..5618c1a --- /dev/null +++ b/services/java/com/android/server/am/TaskAccessInfo.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import java.util.ArrayList; + +import android.app.ActivityManager.TaskThumbnails; +import android.graphics.Bitmap; + +final class TaskAccessInfo extends TaskThumbnails { + final static class SubTask { + Bitmap thumbnail; + ActivityRecord activity; + int index; + } + + public ActivityRecord root; + public int rootIndex; + + public ArrayList<SubTask> subtasks; +} diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java index 76b4914..e61a7f4 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -23,7 +23,7 @@ import android.graphics.Bitmap; import java.io.PrintWriter; -class TaskRecord { +class TaskRecord extends ThumbnailHolder { final int taskId; // Unique identifier for this task. final String affinity; // The affinity name for this task, or null. Intent intent; // The original intent that started the task. @@ -35,8 +35,6 @@ class TaskRecord { boolean rootWasReset; // True if the intent at the root of the task had // the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag. boolean askedCompatMode;// Have asked the user about compat mode for this task. - Bitmap lastThumbnail; // Last thumbnail captured for this task. - CharSequence lastDescription; // Last description captured for this task. String stringName; // caching of toString() result. diff --git a/services/java/com/android/server/am/ThumbnailHolder.java b/services/java/com/android/server/am/ThumbnailHolder.java new file mode 100644 index 0000000..02f4fcb --- /dev/null +++ b/services/java/com/android/server/am/ThumbnailHolder.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import android.graphics.Bitmap; + +public class ThumbnailHolder { + Bitmap lastThumbnail; // Last thumbnail captured for this item. + CharSequence lastDescription; // Last description captured for this item. +} diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index ffadc65..e8155b4 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -84,6 +84,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private String[] mTetherableBluetoothRegexs; private String[] mUpstreamIfaceRegexs; + private INetworkManagementService mNMService; private Looper mLooper; private HandlerThread mThread; @@ -100,21 +101,12 @@ public class Tethering extends INetworkManagementEventObserver.Stub { // with 255.255.255.0 private String[] mDhcpRange; - private static final String DHCP_DEFAULT_RANGE1_START = "192.168.42.2"; - private static final String DHCP_DEFAULT_RANGE1_STOP = "192.168.42.254"; - private static final String DHCP_DEFAULT_RANGE2_START = "192.168.43.2"; - private static final String DHCP_DEFAULT_RANGE2_STOP = "192.168.43.254"; - private static final String DHCP_DEFAULT_RANGE3_START = "192.168.44.2"; - private static final String DHCP_DEFAULT_RANGE3_STOP = "192.168.44.254"; - private static final String DHCP_DEFAULT_RANGE4_START = "192.168.45.2"; - private static final String DHCP_DEFAULT_RANGE4_STOP = "192.168.45.254"; - private static final String DHCP_DEFAULT_RANGE5_START = "192.168.46.2"; - private static final String DHCP_DEFAULT_RANGE5_STOP = "192.168.46.254"; - private static final String DHCP_DEFAULT_RANGE6_START = "192.168.47.2"; - private static final String DHCP_DEFAULT_RANGE6_STOP = "192.168.47.254"; - private static final String DHCP_DEFAULT_RANGE7_START = "192.168.48.2"; - private static final String DHCP_DEFAULT_RANGE7_STOP = "192.168.48.254"; - + private static final String[] DHCP_DEFAULT_RANGE = { + "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254", + "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254", + "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254", + "192.168.48.2", "192.168.48.254", + }; private String[] mDnsServers; private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8"; @@ -132,15 +124,14 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private boolean mUsbMassStorageOff; // track the status of USB Mass Storage private boolean mUsbConnected; // track the status of USB connection - public Tethering(Context context, Looper looper) { + public Tethering(Context context, INetworkManagementService nmService, Looper looper) { mContext = context; + mNMService = nmService; mLooper = looper; // register for notifications from NetworkManagement Service - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); try { - service.registerObserver(this); + mNMService.registerObserver(this); } catch (RemoteException e) { Log.e(TAG, "Error registering observer :" + e); } @@ -173,21 +164,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mDhcpRange = context.getResources().getStringArray( com.android.internal.R.array.config_tether_dhcp_range); if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) { - mDhcpRange = new String[14]; - mDhcpRange[0] = DHCP_DEFAULT_RANGE1_START; - mDhcpRange[1] = DHCP_DEFAULT_RANGE1_STOP; - mDhcpRange[2] = DHCP_DEFAULT_RANGE2_START; - mDhcpRange[3] = DHCP_DEFAULT_RANGE2_STOP; - mDhcpRange[4] = DHCP_DEFAULT_RANGE3_START; - mDhcpRange[5] = DHCP_DEFAULT_RANGE3_STOP; - mDhcpRange[6] = DHCP_DEFAULT_RANGE4_START; - mDhcpRange[7] = DHCP_DEFAULT_RANGE4_STOP; - mDhcpRange[8] = DHCP_DEFAULT_RANGE5_START; - mDhcpRange[9] = DHCP_DEFAULT_RANGE5_STOP; - mDhcpRange[10] = DHCP_DEFAULT_RANGE6_START; - mDhcpRange[11] = DHCP_DEFAULT_RANGE6_STOP; - mDhcpRange[12] = DHCP_DEFAULT_RANGE7_START; - mDhcpRange[13] = DHCP_DEFAULT_RANGE7_STOP; + mDhcpRange = DHCP_DEFAULT_RANGE; } mDunRequired = false; // resample when we turn on @@ -258,8 +235,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return false; } public void interfaceAdded(String iface) { - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); boolean found = false; boolean usb = false; if (isWifi(iface)) { @@ -354,9 +329,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private void sendTetherStateChangedBroadcast() { IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); - IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); + IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b); try { - if (!service.isTetheringSupported()) return; + if (!cm.isTetheringSupported()) return; } catch (RemoteException e) { return; } @@ -503,11 +478,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub { // used on cable insert/remove private void enableUsbIfaces(boolean enable) { - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); String[] ifaces = new String[0]; try { - ifaces = service.listInterfaces(); + ifaces = mNMService.listInterfaces(); } catch (Exception e) { Log.e(TAG, "Error listing Interfaces :" + e); return; @@ -526,19 +499,17 @@ public class Tethering extends INetworkManagementEventObserver.Stub { // toggled when we enter/leave the fully tethered state private boolean enableUsbRndis(boolean enabled) { if (DEBUG) Log.d(TAG, "enableUsbRndis(" + enabled + ")"); - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); try { if (enabled) { synchronized (this) { - if (!service.isUsbRNDISStarted()) { - service.startUsbRNDIS(); + if (!mNMService.isUsbRNDISStarted()) { + mNMService.startUsbRNDIS(); } } } else { - if (service.isUsbRNDISStarted()) { - service.stopUsbRNDIS(); + if (mNMService.isUsbRNDISStarted()) { + mNMService.stopUsbRNDIS(); } } } catch (Exception e) { @@ -552,13 +523,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private boolean configureUsbIface(boolean enabled) { if (DEBUG) Log.d(TAG, "configureUsbIface(" + enabled + ")"); - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); - // bring toggle the interfaces String[] ifaces = new String[0]; try { - ifaces = service.listInterfaces(); + ifaces = mNMService.listInterfaces(); } catch (Exception e) { Log.e(TAG, "Error listing Interfaces :" + e); return false; @@ -567,7 +535,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { if (isUsb(iface)) { InterfaceConfiguration ifcg = null; try { - ifcg = service.getInterfaceConfig(iface); + ifcg = mNMService.getInterfaceConfig(iface); if (ifcg != null) { InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR); ifcg.addr = new LinkAddress(addr, USB_PREFIX_LENGTH); @@ -578,7 +546,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", ""); ifcg.interfaceFlags = ifcg.interfaceFlags.replace(" "," "); - service.setInterfaceConfig(iface, ifcg); + mNMService.setInterfaceConfig(iface, ifcg); } } catch (Exception e) { Log.e(TAG, "Error configuring interface " + iface + ", :" + e); @@ -884,11 +852,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { class TetheredState extends State { @Override public void enter() { - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = - INetworkManagementService.Stub.asInterface(b); try { - service.tetherInterface(mIfaceName); + mNMService.tetherInterface(mIfaceName); } catch (Exception e) { setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR); @@ -913,16 +878,13 @@ public class Tethering extends INetworkManagementEventObserver.Stub { switch (message.what) { case CMD_TETHER_UNREQUESTED: case CMD_INTERFACE_DOWN: - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = - INetworkManagementService.Stub.asInterface(b); if (mMyUpstreamIfaceName != null) { try { - service.disableNat(mIfaceName, mMyUpstreamIfaceName); + mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName); mMyUpstreamIfaceName = null; } catch (Exception e) { try { - service.untetherInterface(mIfaceName); + mNMService.untetherInterface(mIfaceName); } catch (Exception ee) {} setLastErrorAndTransitionToInitialState( @@ -931,7 +893,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } try { - service.untetherInterface(mIfaceName); + mNMService.untetherInterface(mIfaceName); } catch (Exception e) { setLastErrorAndTransitionToInitialState( ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); @@ -954,8 +916,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { break; case CMD_TETHER_CONNECTION_CHANGED: String newUpstreamIfaceName = (String)(message.obj); - b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - service = INetworkManagementService.Stub.asInterface(b); if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) || (mMyUpstreamIfaceName != null && mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) { @@ -964,11 +924,11 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } if (mMyUpstreamIfaceName != null) { try { - service.disableNat(mIfaceName, mMyUpstreamIfaceName); + mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName); mMyUpstreamIfaceName = null; } catch (Exception e) { try { - service.untetherInterface(mIfaceName); + mNMService.untetherInterface(mIfaceName); } catch (Exception ee) {} setLastErrorAndTransitionToInitialState( @@ -978,10 +938,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } if (newUpstreamIfaceName != null) { try { - service.enableNat(mIfaceName, newUpstreamIfaceName); + mNMService.enableNat(mIfaceName, newUpstreamIfaceName); } catch (Exception e) { try { - service.untetherInterface(mIfaceName); + mNMService.untetherInterface(mIfaceName); } catch (Exception ee) {} setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR); @@ -1000,15 +960,13 @@ public class Tethering extends INetworkManagementEventObserver.Stub { error = true; // fall through case CMD_TETHER_MODE_DEAD: - b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - service = INetworkManagementService.Stub.asInterface(b); if (mMyUpstreamIfaceName != null) { try { - service.disableNat(mIfaceName, mMyUpstreamIfaceName); + mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName); mMyUpstreamIfaceName = null; } catch (Exception e) { try { - service.untetherInterface(mIfaceName); + mNMService.untetherInterface(mIfaceName); } catch (Exception ee) {} setLastErrorAndTransitionToInitialState( @@ -1017,7 +975,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } try { - service.untetherInterface(mIfaceName); + mNMService.untetherInterface(mIfaceName); } catch (Exception e) { setLastErrorAndTransitionToInitialState( ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); @@ -1150,10 +1108,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub { boolean retValue = true; if (mMobileReserved) return retValue; IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); - IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); + IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b); int result = Phone.APN_REQUEST_FAILED; try { - result = service.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, + result = cm.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, (mDunRequired ? Phone.FEATURE_ENABLE_DUN_ALWAYS : Phone.FEATURE_ENABLE_HIPRI), new Binder()); } catch (Exception e) { @@ -1177,10 +1135,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub { protected boolean turnOffMobileConnection() { if (mMobileReserved) { IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); - IConnectivityManager service = - IConnectivityManager.Stub.asInterface(b); + IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b); try { - service.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, + cm.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, (mDunRequired? Phone.FEATURE_ENABLE_DUN_ALWAYS : Phone.FEATURE_ENABLE_HIPRI)); } catch (Exception e) { @@ -1191,28 +1148,25 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return true; } protected boolean turnOnMasterTetherSettings() { - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = - INetworkManagementService.Stub.asInterface(b); try { - service.setIpForwardingEnabled(true); + mNMService.setIpForwardingEnabled(true); } catch (Exception e) { transitionTo(mSetIpForwardingEnabledErrorState); return false; } try { - service.startTethering(mDhcpRange); + mNMService.startTethering(mDhcpRange); } catch (Exception e) { try { - service.stopTethering(); - service.startTethering(mDhcpRange); + mNMService.stopTethering(); + mNMService.startTethering(mDhcpRange); } catch (Exception ee) { transitionTo(mStartTetheringErrorState); return false; } } try { - service.setDnsForwarders(mDnsServers); + mNMService.setDnsForwarders(mDnsServers); } catch (Exception e) { transitionTo(mSetDnsForwardersErrorState); return false; @@ -1220,17 +1174,14 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return true; } protected boolean turnOffMasterTetherSettings() { - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = - INetworkManagementService.Stub.asInterface(b); try { - service.stopTethering(); + mNMService.stopTethering(); } catch (Exception e) { transitionTo(mStopTetheringErrorState); return false; } try { - service.setIpForwardingEnabled(false); + mNMService.setIpForwardingEnabled(false); } catch (Exception e) { transitionTo(mSetIpForwardingDisabledErrorState); return false; @@ -1253,12 +1204,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } catch (RemoteException e) { } - b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); - String[] ifaces = new String[0]; try { - ifaces = service.listInterfaces(); + ifaces = mNMService.listInterfaces(); } catch (Exception e) { Log.e(TAG, "Error listing Interfaces :" + e); return null; @@ -1270,7 +1218,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { // verify it is active InterfaceConfiguration ifcg = null; try { - ifcg = service.getInterfaceConfig(iface); + ifcg = mNMService.getInterfaceConfig(iface); if (ifcg.isActive()) { return iface; } @@ -1503,11 +1451,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { public void enter() { Log.e(TAG, "Error in startTethering"); notify(TetherInterfaceSM.CMD_START_TETHERING_ERROR); - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = - INetworkManagementService.Stub.asInterface(b); try { - service.setIpForwardingEnabled(false); + mNMService.setIpForwardingEnabled(false); } catch (Exception e) {} } } @@ -1517,11 +1462,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { public void enter() { Log.e(TAG, "Error in stopTethering"); notify(TetherInterfaceSM.CMD_STOP_TETHERING_ERROR); - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = - INetworkManagementService.Stub.asInterface(b); try { - service.setIpForwardingEnabled(false); + mNMService.setIpForwardingEnabled(false); } catch (Exception e) {} } } @@ -1531,14 +1473,11 @@ public class Tethering extends INetworkManagementEventObserver.Stub { public void enter() { Log.e(TAG, "Error in setDnsForwarders"); notify(TetherInterfaceSM.CMD_SET_DNS_FORWARDERS_ERROR); - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = - INetworkManagementService.Stub.asInterface(b); try { - service.stopTethering(); + mNMService.stopTethering(); } catch (Exception e) {} try { - service.setIpForwardingEnabled(false); + mNMService.setIpForwardingEnabled(false); } catch (Exception e) {} } } diff --git a/services/java/com/android/server/location/ComprehensiveCountryDetector.java b/services/java/com/android/server/location/ComprehensiveCountryDetector.java index e9ce3ce..bb9e60f 100755 --- a/services/java/com/android/server/location/ComprehensiveCountryDetector.java +++ b/services/java/com/android/server/location/ComprehensiveCountryDetector.java @@ -56,6 +56,7 @@ import java.util.TimerTask; public class ComprehensiveCountryDetector extends CountryDetectorBase { private final static String TAG = "ComprehensiveCountryDetector"; + /* package */ static final boolean DEBUG = false; /** * The refresh interval when the location based country was used @@ -90,7 +91,9 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase { * The listener for receiving the notification from LocationBasedCountryDetector. */ private CountryListener mLocationBasedCountryDetectionListener = new CountryListener() { + @Override public void onCountryDetected(Country country) { + if (DEBUG) Slog.d(TAG, "Country detected via LocationBasedCountryDetector"); mCountryFromLocation = country; // Don't start the LocationBasedCountryDetector. detectCountry(true, false); @@ -206,6 +209,7 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase { protected void runAfterDetectionAsync(final Country country, final Country detectedCountry, final boolean notifyChange, final boolean startLocationBasedDetection) { mHandler.post(new Runnable() { + @Override public void run() { runAfterDetection( country, detectedCountry, notifyChange, startLocationBasedDetection); @@ -233,9 +237,20 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase { if (notifyChange) { notifyIfCountryChanged(country, detectedCountry); } + if (DEBUG) { + Slog.d(TAG, "startLocationBasedDetection=" + startLocationBasedDetection + + " detectCountry=" + (detectedCountry == null ? null : + "(source: " + detectedCountry.getSource() + + ", countryISO: " + detectedCountry.getCountryIso() + ")") + + " isAirplaneModeOff()=" + isAirplaneModeOff() + + " mListener=" + mListener + + " isGeoCoderImplemnted()=" + isGeoCoderImplemented()); + } + if (startLocationBasedDetection && (detectedCountry == null || detectedCountry.getSource() > Country.COUNTRY_SOURCE_LOCATION) && isAirplaneModeOff() && mListener != null && isGeoCoderImplemented()) { + if (DEBUG) Slog.d(TAG, "run startLocationBasedDetector()"); // Start finding location when the source is less reliable than the // location and the airplane mode is off (as geocoder will not // work). @@ -266,12 +281,20 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase { if (mLocationBasedCountryDetector != null) { return; } + if (DEBUG) { + Slog.d(TAG, "starts LocationBasedDetector to detect Country code via Location info " + + "(e.g. GPS)"); + } mLocationBasedCountryDetector = createLocationBasedCountryDetector(); mLocationBasedCountryDetector.setCountryListener(listener); mLocationBasedCountryDetector.detectCountry(); } private synchronized void stopLocationBasedDetector() { + if (DEBUG) { + Slog.d(TAG, "tries to stop LocationBasedDetector " + + "(current detector: " + mLocationBasedCountryDetector + ")"); + } if (mLocationBasedCountryDetector != null) { mLocationBasedCountryDetector.stop(); mLocationBasedCountryDetector = null; @@ -305,10 +328,17 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase { */ private synchronized void scheduleLocationRefresh() { if (mLocationRefreshTimer != null) return; + if (DEBUG) { + Slog.d(TAG, "start periodic location refresh timer. Interval: " + + LOCATION_REFRESH_INTERVAL); + } mLocationRefreshTimer = new Timer(); mLocationRefreshTimer.schedule(new TimerTask() { @Override public void run() { + if (DEBUG) { + Slog.d(TAG, "periodic location refresh event. Starts detecting Country code"); + } mLocationRefreshTimer = null; detectCountry(false, true); } diff --git a/services/java/com/android/server/location/GpsXtraDownloader.java b/services/java/com/android/server/location/GpsXtraDownloader.java index 813d255..e420073 100644 --- a/services/java/com/android/server/location/GpsXtraDownloader.java +++ b/services/java/com/android/server/location/GpsXtraDownloader.java @@ -19,7 +19,6 @@ package com.android.server.location; import android.content.Context; import android.net.Proxy; import android.net.http.AndroidHttpClient; -import android.util.Config; import android.util.Log; import org.apache.http.HttpEntity; diff --git a/services/java/com/android/server/location/LocationBasedCountryDetector.java b/services/java/com/android/server/location/LocationBasedCountryDetector.java index 139f05d..d4fb8ee 100755 --- a/services/java/com/android/server/location/LocationBasedCountryDetector.java +++ b/services/java/com/android/server/location/LocationBasedCountryDetector.java @@ -16,12 +16,6 @@ package com.android.server.location; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; - import android.content.Context; import android.location.Address; import android.location.Country; @@ -32,6 +26,12 @@ import android.location.LocationManager; import android.os.Bundle; import android.util.Slog; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + /** * This class detects which country the user currently is in through the enabled * location providers and the GeoCoder @@ -86,24 +86,23 @@ public class LocationBasedCountryDetector extends CountryDetectorBase { return country; } + protected boolean isAcceptableProvider(String provider) { + // We don't want to actively initiate a location fix here (with gps or network providers). + return LocationManager.PASSIVE_PROVIDER.equals(provider); + } + /** - * Register the listeners with the location providers + * Register a listener with a provider name */ - protected void registerEnabledProviders(List<LocationListener> listeners) { - int total = listeners.size(); - for (int i = 0; i< total; i++) { - mLocationManager.requestLocationUpdates( - mEnabledProviders.get(i), 0, 0, listeners.get(i)); - } + protected void registerListener(String provider, LocationListener listener) { + mLocationManager.requestLocationUpdates(provider, 0, 0, listener); } /** - * Unregister the listeners with the location providers + * Unregister an already registered listener */ - protected void unregisterProviders(List<LocationListener> listeners) { - for (LocationListener listener : listeners) { - mLocationManager.removeUpdates(listener); - } + protected void unregisterListener(LocationListener listener) { + mLocationManager.removeUpdates(listener); } /** @@ -130,14 +129,11 @@ public class LocationBasedCountryDetector extends CountryDetectorBase { return QUERY_LOCATION_TIMEOUT; } - /** - * @return the total number of enabled location providers - */ - protected int getTotalEnabledProviders() { + protected List<String> getEnabledProviders() { if (mEnabledProviders == null) { mEnabledProviders = mLocationManager.getProviders(true); } - return mEnabledProviders.size(); + return mEnabledProviders; } /** @@ -152,27 +148,36 @@ public class LocationBasedCountryDetector extends CountryDetectorBase { throw new IllegalStateException(); } // Request the location from all enabled providers. - int totalProviders = getTotalEnabledProviders(); + List<String> enabledProviders = getEnabledProviders(); + int totalProviders = enabledProviders.size(); if (totalProviders > 0) { mLocationListeners = new ArrayList<LocationListener>(totalProviders); for (int i = 0; i < totalProviders; i++) { - LocationListener listener = new LocationListener () { - public void onLocationChanged(Location location) { - if (location != null) { - LocationBasedCountryDetector.this.stop(); - queryCountryCode(location); + String provider = enabledProviders.get(i); + if (isAcceptableProvider(provider)) { + LocationListener listener = new LocationListener () { + @Override + public void onLocationChanged(Location location) { + if (location != null) { + LocationBasedCountryDetector.this.stop(); + queryCountryCode(location); + } } - } - public void onProviderDisabled(String provider) { - } - public void onProviderEnabled(String provider) { - } - public void onStatusChanged(String provider, int status, Bundle extras) { - } - }; - mLocationListeners.add(listener); + @Override + public void onProviderDisabled(String provider) { + } + @Override + public void onProviderEnabled(String provider) { + } + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; + mLocationListeners.add(listener); + registerListener(provider, listener); + } } - registerEnabledProviders(mLocationListeners); + mTimer = new Timer(); mTimer.schedule(new TimerTask() { @Override @@ -197,7 +202,9 @@ public class LocationBasedCountryDetector extends CountryDetectorBase { @Override public synchronized void stop() { if (mLocationListeners != null) { - unregisterProviders(mLocationListeners); + for (LocationListener listener : mLocationListeners) { + unregisterListener(listener); + } mLocationListeners = null; } if (mTimer != null) { @@ -216,6 +223,7 @@ public class LocationBasedCountryDetector extends CountryDetectorBase { } if (mQueryThread != null) return; mQueryThread = new Thread(new Runnable() { + @Override public void run() { String countryIso = null; if (location != null) { diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java new file mode 100644 index 0000000..17c7161 --- /dev/null +++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import static android.Manifest.permission.DUMP; +import static android.Manifest.permission.MANAGE_APP_TOKENS; +import static android.Manifest.permission.UPDATE_DEVICE_STATS; +import static android.net.NetworkPolicyManager.POLICY_NONE; +import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND; +import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; +import static android.net.NetworkPolicyManager.RULE_REJECT_PAID; +import static android.net.NetworkPolicyManager.dumpPolicy; +import static android.net.NetworkPolicyManager.dumpRules; + +import android.app.IActivityManager; +import android.app.IProcessObserver; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.INetworkPolicyListener; +import android.net.INetworkPolicyManager; +import android.net.INetworkStatsService; +import android.os.IPowerManager; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * Service that maintains low-level network policy rules and collects usage + * statistics to drive those rules. + * <p> + * Derives active rules by combining a given policy with other system status, + * and delivers to listeners, such as {@link ConnectivityManager}, for + * enforcement. + */ +public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { + private static final String TAG = "NetworkPolicy"; + private static final boolean LOGD = true; + + private final Context mContext; + private final IActivityManager mActivityManager; + private final IPowerManager mPowerManager; + private final INetworkStatsService mNetworkStats; + + private final Object mRulesLock = new Object(); + + private boolean mScreenOn; + + /** Current network policy for each UID. */ + private SparseIntArray mUidPolicy = new SparseIntArray(); + /** Current derived network rules for each UID. */ + private SparseIntArray mUidRules = new SparseIntArray(); + + /** Foreground at both UID and PID granularity. */ + private SparseBooleanArray mUidForeground = new SparseBooleanArray(); + private SparseArray<SparseBooleanArray> mUidPidForeground = new SparseArray< + SparseBooleanArray>(); + + private final RemoteCallbackList<INetworkPolicyListener> mListeners = new RemoteCallbackList< + INetworkPolicyListener>(); + + // TODO: save/restore policy information from disk + + // TODO: keep whitelist of system-critical services that should never have + // rules enforced, such as system, phone, and radio UIDs. + + // TODO: keep record of billing cycle details, and limit rules + // TODO: keep map of interfaces-to-billing-relationship + + public NetworkPolicyManagerService(Context context, IActivityManager activityManager, + IPowerManager powerManager, INetworkStatsService networkStats) { + mContext = checkNotNull(context, "missing context"); + mActivityManager = checkNotNull(activityManager, "missing activityManager"); + mPowerManager = checkNotNull(powerManager, "missing powerManager"); + mNetworkStats = checkNotNull(networkStats, "missing networkStats"); + } + + public void systemReady() { + // TODO: read current policy from disk + + updateScreenOn(); + + try { + mActivityManager.registerProcessObserver(mProcessObserver); + } catch (RemoteException e) { + // ouch, no foregroundActivities updates means some processes may + // never get network access. + Slog.e(TAG, "unable to register IProcessObserver", e); + } + + // TODO: traverse existing processes to know foreground state, or have + // activitymanager dispatch current state when new observer attached. + + final IntentFilter screenFilter = new IntentFilter(); + screenFilter.addAction(Intent.ACTION_SCREEN_ON); + screenFilter.addAction(Intent.ACTION_SCREEN_OFF); + mContext.registerReceiver(mScreenReceiver, screenFilter); + + } + + private IProcessObserver mProcessObserver = new IProcessObserver.Stub() { + @Override + public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) { + // only someone like AMS should only be calling us + mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG); + + synchronized (mRulesLock) { + // because a uid can have multiple pids running inside, we need to + // remember all pid states and summarize foreground at uid level. + + // record foreground for this specific pid + SparseBooleanArray pidForeground = mUidPidForeground.get(uid); + if (pidForeground == null) { + pidForeground = new SparseBooleanArray(2); + mUidPidForeground.put(uid, pidForeground); + } + pidForeground.put(pid, foregroundActivities); + computeUidForegroundL(uid); + } + } + + @Override + public void onProcessDied(int pid, int uid) { + // only someone like AMS should only be calling us + mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG); + + synchronized (mRulesLock) { + // clear records and recompute, when they exist + final SparseBooleanArray pidForeground = mUidPidForeground.get(uid); + if (pidForeground != null) { + pidForeground.delete(pid); + computeUidForegroundL(uid); + } + } + } + }; + + private BroadcastReceiver mScreenReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mRulesLock) { + // screen-related broadcasts are protected by system, no need + // for permissions check. + updateScreenOn(); + } + } + }; + + @Override + public void setUidPolicy(int uid, int policy) { + // TODO: create permission for modifying data policy + mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG); + + final int oldPolicy; + synchronized (mRulesLock) { + oldPolicy = getUidPolicy(uid); + mUidPolicy.put(uid, policy); + updateRulesForUidL(uid); + } + + // TODO: consider dispatching BACKGROUND_DATA_SETTING broadcast + } + + @Override + public int getUidPolicy(int uid) { + synchronized (mRulesLock) { + return mUidPolicy.get(uid, POLICY_NONE); + } + } + + @Override + public void registerListener(INetworkPolicyListener listener) { + mListeners.register(listener); + + synchronized (mRulesLock) { + // dispatch any existing rules to new listeners + final int size = mUidRules.size(); + for (int i = 0; i < size; i++) { + final int uid = mUidRules.keyAt(i); + final int uidRules = mUidRules.valueAt(i); + if (uidRules != RULE_ALLOW_ALL) { + try { + listener.onRulesChanged(uid, uidRules); + } catch (RemoteException e) { + } + } + } + } + } + + @Override + public void unregisterListener(INetworkPolicyListener listener) { + mListeners.unregister(listener); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + mContext.enforceCallingOrSelfPermission(DUMP, TAG); + + synchronized (mRulesLock) { + fout.println("Policy status for known UIDs:"); + + final SparseBooleanArray knownUids = new SparseBooleanArray(); + collectKeys(mUidPolicy, knownUids); + collectKeys(mUidForeground, knownUids); + collectKeys(mUidRules, knownUids); + + final int size = knownUids.size(); + for (int i = 0; i < size; i++) { + final int uid = knownUids.keyAt(i); + fout.print(" UID="); + fout.print(uid); + + fout.print(" policy="); + final int policyIndex = mUidPolicy.indexOfKey(uid); + if (policyIndex < 0) { + fout.print("UNKNOWN"); + } else { + dumpPolicy(fout, mUidPolicy.valueAt(policyIndex)); + } + + fout.print(" foreground="); + final int foregroundIndex = mUidPidForeground.indexOfKey(uid); + if (foregroundIndex < 0) { + fout.print("UNKNOWN"); + } else { + dumpSparseBooleanArray(fout, mUidPidForeground.valueAt(foregroundIndex)); + } + + fout.print(" rules="); + final int rulesIndex = mUidRules.indexOfKey(uid); + if (rulesIndex < 0) { + fout.print("UNKNOWN"); + } else { + dumpRules(fout, mUidRules.valueAt(rulesIndex)); + } + + fout.println(); + } + } + } + + @Override + public boolean isUidForeground(int uid) { + synchronized (mRulesLock) { + // only really in foreground when screen is also on + return mUidForeground.get(uid, false) && mScreenOn; + } + } + + /** + * Foreground for PID changed; recompute foreground at UID level. If + * changed, will trigger {@link #updateRulesForUidL(int)}. + */ + private void computeUidForegroundL(int uid) { + final SparseBooleanArray pidForeground = mUidPidForeground.get(uid); + + // current pid is dropping foreground; examine other pids + boolean uidForeground = false; + final int size = pidForeground.size(); + for (int i = 0; i < size; i++) { + if (pidForeground.valueAt(i)) { + uidForeground = true; + break; + } + } + + final boolean oldUidForeground = mUidForeground.get(uid, false); + if (oldUidForeground != uidForeground) { + // foreground changed, push updated rules + mUidForeground.put(uid, uidForeground); + updateRulesForUidL(uid); + } + } + + private void updateScreenOn() { + synchronized (mRulesLock) { + try { + mScreenOn = mPowerManager.isScreenOn(); + } catch (RemoteException e) { + } + updateRulesForScreenL(); + } + } + + /** + * Update rules that might be changed by {@link #mScreenOn} value. + */ + private void updateRulesForScreenL() { + // only update rules for anyone with foreground activities + final int size = mUidForeground.size(); + for (int i = 0; i < size; i++) { + if (mUidForeground.valueAt(i)) { + final int uid = mUidForeground.keyAt(i); + updateRulesForUidL(uid); + } + } + } + + private void updateRulesForUidL(int uid) { + final int uidPolicy = getUidPolicy(uid); + final boolean uidForeground = isUidForeground(uid); + + // derive active rules based on policy and active state + int uidRules = RULE_ALLOW_ALL; + if (!uidForeground && (uidPolicy & POLICY_REJECT_PAID_BACKGROUND) != 0) { + // uid in background, and policy says to block paid data + uidRules = RULE_REJECT_PAID; + } + + // TODO: only dispatch when rules actually change + + // record rule locally to dispatch to new listeners + mUidRules.put(uid, uidRules); + + // dispatch changed rule to existing listeners + final int length = mListeners.beginBroadcast(); + for (int i = 0; i < length; i++) { + final INetworkPolicyListener listener = mListeners.getBroadcastItem(i); + if (listener != null) { + try { + listener.onRulesChanged(uid, uidRules); + } catch (RemoteException e) { + } + } + } + mListeners.finishBroadcast(); + } + + private static <T> T checkNotNull(T value, String message) { + if (value == null) { + throw new NullPointerException(message); + } + return value; + } + + private static void collectKeys(SparseIntArray source, SparseBooleanArray target) { + final int size = source.size(); + for (int i = 0; i < size; i++) { + target.put(source.keyAt(i), true); + } + } + + private static void collectKeys(SparseBooleanArray source, SparseBooleanArray target) { + final int size = source.size(); + for (int i = 0; i < size; i++) { + target.put(source.keyAt(i), true); + } + } + + private static void dumpSparseBooleanArray(PrintWriter fout, SparseBooleanArray value) { + fout.print("["); + final int size = value.size(); + for (int i = 0; i < size; i++) { + fout.print(value.keyAt(i) + "=" + value.valueAt(i)); + if (i < size - 1) fout.print(","); + } + fout.print("]"); + } +} diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java new file mode 100644 index 0000000..d9c1f25 --- /dev/null +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -0,0 +1,403 @@ +/* + * 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.net; + +import static android.Manifest.permission.DUMP; +import static android.Manifest.permission.SHUTDOWN; +import static android.Manifest.permission.UPDATE_DEVICE_STATS; +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.NetworkStats.UID_ALL; + +import android.app.AlarmManager; +import android.app.IAlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.INetworkStatsService; +import android.net.LinkProperties; +import android.net.NetworkStats; +import android.net.NetworkStatsHistory; +import android.net.wifi.WifiManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.SystemClock; +import android.telephony.TelephonyManager; +import android.text.format.DateUtils; +import android.util.NtpTrustedTime; +import android.util.Slog; +import android.util.TrustedTime; + +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.TelephonyIntents; +import com.google.android.collect.Maps; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashMap; + +/** + * Collect and persist detailed network statistics, and provide this data to + * other system services. + */ +public class NetworkStatsService extends INetworkStatsService.Stub { + private static final String TAG = "NetworkStatsService"; + private static final boolean LOGD = true; + + private final Context mContext; + private final INetworkManagementService mNetworkManager; + private final IAlarmManager mAlarmManager; + private final TrustedTime mTime; + + private static final String ACTION_NETWORK_STATS_POLL = + "com.android.server.action.NETWORK_STATS_POLL"; + + private PendingIntent mPollIntent; + + // TODO: move tweakable params to Settings.Secure + // TODO: listen for kernel push events through netd instead of polling + + private static final long KB_IN_BYTES = 1024; + + private static final long POLL_INTERVAL = AlarmManager.INTERVAL_FIFTEEN_MINUTES; + private static final long SUMMARY_BUCKET_DURATION = 6 * DateUtils.HOUR_IN_MILLIS; + private static final long SUMMARY_MAX_HISTORY = 90 * DateUtils.DAY_IN_MILLIS; + + // TODO: remove these high-frequency testing values +// private static final long POLL_INTERVAL = 5 * DateUtils.SECOND_IN_MILLIS; +// private static final long SUMMARY_BUCKET_DURATION = 10 * DateUtils.SECOND_IN_MILLIS; +// private static final long SUMMARY_MAX_HISTORY = 2 * DateUtils.MINUTE_IN_MILLIS; + + /** Minimum delta required to persist to disk. */ + private static final long SUMMARY_PERSIST_THRESHOLD = 64 * KB_IN_BYTES; + + private static final long TIME_CACHE_MAX_AGE = DateUtils.DAY_IN_MILLIS; + + private final Object mStatsLock = new Object(); + + /** Set of active ifaces during this boot. */ + private HashMap<String, InterfaceInfo> mActiveIface = Maps.newHashMap(); + /** Set of historical stats for known ifaces. */ + private HashMap<InterfaceInfo, NetworkStatsHistory> mIfaceStats = Maps.newHashMap(); + + private NetworkStats mLastPollStats; + private NetworkStats mLastPersistStats; + + private final HandlerThread mHandlerThread; + private final Handler mHandler; + + // TODO: collect detailed uid stats, storing tag-granularity data until next + // dropbox, and uid summary for a specific bucket count. + + // TODO: periodically compile statistics and send to dropbox. + + public NetworkStatsService( + Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) { + // TODO: move to using cached NtpTrustedTime + this(context, networkManager, alarmManager, new NtpTrustedTime()); + } + + public NetworkStatsService(Context context, INetworkManagementService networkManager, + IAlarmManager alarmManager, TrustedTime time) { + mContext = checkNotNull(context, "missing Context"); + mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService"); + mAlarmManager = checkNotNull(alarmManager, "missing IAlarmManager"); + mTime = checkNotNull(time, "missing TrustedTime"); + + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + } + + public void systemReady() { + // read historical stats from disk + readStatsLocked(); + + // watch other system services that claim interfaces + // TODO: protect incoming broadcast with permissions check. + // TODO: consider migrating this to ConnectivityService, but it might + // cause a circular dependency. + final IntentFilter interfaceFilter = new IntentFilter(); + interfaceFilter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); + interfaceFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + mContext.registerReceiver(mInterfaceReceiver, interfaceFilter); + + // listen for periodic polling events + final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL); + mContext.registerReceiver(mPollReceiver, pollFilter, UPDATE_DEVICE_STATS, mHandler); + + // persist stats during clean shutdown + final IntentFilter shutdownFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); + mContext.registerReceiver(mShutdownReceiver, shutdownFilter, SHUTDOWN, null); + + try { + registerPollAlarmLocked(); + } catch (RemoteException e) { + Slog.w(TAG, "unable to register poll alarm"); + } + } + + /** + * Clear any existing {@link #ACTION_NETWORK_STATS_POLL} alarms, and + * reschedule based on current {@link #POLL_INTERVAL} value. + */ + private void registerPollAlarmLocked() throws RemoteException { + if (mPollIntent != null) { + mAlarmManager.remove(mPollIntent); + } + + mPollIntent = PendingIntent.getBroadcast( + mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0); + + final long currentRealtime = SystemClock.elapsedRealtime(); + mAlarmManager.setInexactRepeating( + AlarmManager.ELAPSED_REALTIME, currentRealtime, POLL_INTERVAL, mPollIntent); + } + + @Override + public NetworkStatsHistory[] getNetworkStatsSummary(int networkType) { + // TODO: return history for requested types + return null; + } + + @Override + public NetworkStatsHistory getNetworkStatsUid(int uid) { + // TODO: return history for requested uid + return null; + } + + /** + * Receiver that watches for other system components that claim network + * interfaces. Used to associate {@link TelephonyManager#getSubscriberId()} + * with mobile interfaces. + */ + private BroadcastReceiver mInterfaceReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED.equals(action)) { + final LinkProperties prop = intent.getParcelableExtra( + Phone.DATA_LINK_PROPERTIES_KEY); + final String iface = prop != null ? prop.getInterfaceName() : null; + if (iface != null) { + final TelephonyManager teleManager = (TelephonyManager) context + .getSystemService(Context.TELEPHONY_SERVICE); + final InterfaceInfo info = new InterfaceInfo( + iface, TYPE_MOBILE, teleManager.getSubscriberId()); + reportActiveInterface(info); + } + } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { + final LinkProperties prop = intent.getParcelableExtra( + WifiManager.EXTRA_LINK_PROPERTIES); + final String iface = prop != null ? prop.getInterfaceName() : null; + if (iface != null) { + final InterfaceInfo info = new InterfaceInfo(iface, TYPE_WIFI, null); + reportActiveInterface(info); + } + } + } + }; + + private BroadcastReceiver mPollReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // already running on background handler, network/io is safe, and + // caller verified to have UPDATE_DEVICE_STATS permission above. + synchronized (mStatsLock) { + // TODO: acquire wakelock while performing poll + performPollLocked(); + } + } + }; + + private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // persist stats during clean shutdown + synchronized (mStatsLock) { + writeStatsLocked(); + } + } + }; + + private void performPollLocked() { + if (LOGD) Slog.v(TAG, "performPollLocked()"); + + // try refreshing time source when stale + if (mTime.getCacheAge() > TIME_CACHE_MAX_AGE) { + mTime.forceRefresh(); + } + + // TODO: consider marking "untrusted" times in historical stats + final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis() + : System.currentTimeMillis(); + + final NetworkStats current; + try { + current = mNetworkManager.getNetworkStatsSummary(); + } catch (RemoteException e) { + Slog.w(TAG, "problem reading network stats"); + return; + } + + // update historical usage with delta since last poll + final NetworkStats pollDelta = computeStatsDelta(mLastPollStats, current); + final long timeStart = currentTime - pollDelta.elapsedRealtime; + for (String iface : pollDelta.getKnownIfaces()) { + final InterfaceInfo info = mActiveIface.get(iface); + if (info == null) { + if (LOGD) Slog.w(TAG, "unknown interface " + iface + ", ignoring stats"); + continue; + } + + final int index = pollDelta.findIndex(iface, UID_ALL); + final long rx = pollDelta.rx[index]; + final long tx = pollDelta.tx[index]; + + final NetworkStatsHistory history = findOrCreateHistoryLocked(info); + history.recordData(timeStart, currentTime, rx, tx); + history.removeBucketsBefore(currentTime - SUMMARY_MAX_HISTORY); + } + + mLastPollStats = current; + + // decide if enough has changed to trigger persist + final NetworkStats persistDelta = computeStatsDelta(mLastPersistStats, current); + for (String iface : persistDelta.getKnownIfaces()) { + final int index = persistDelta.findIndex(iface, UID_ALL); + if (persistDelta.rx[index] > SUMMARY_PERSIST_THRESHOLD + || persistDelta.tx[index] > SUMMARY_PERSIST_THRESHOLD) { + writeStatsLocked(); + mLastPersistStats = current; + break; + } + } + } + + private NetworkStatsHistory findOrCreateHistoryLocked(InterfaceInfo info) { + NetworkStatsHistory stats = mIfaceStats.get(info); + if (stats == null) { + stats = new NetworkStatsHistory( + info.networkType, info.identity, UID_ALL, SUMMARY_BUCKET_DURATION); + mIfaceStats.put(info, stats); + } + return stats; + } + + private void readStatsLocked() { + if (LOGD) Slog.v(TAG, "readStatsLocked()"); + // TODO: read historical stats from disk using AtomicFile + } + + private void writeStatsLocked() { + if (LOGD) Slog.v(TAG, "writeStatsLocked()"); + // TODO: persist historical stats to disk using AtomicFile + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mContext.enforceCallingOrSelfPermission(DUMP, TAG); + + pw.println("Active interfaces:"); + for (InterfaceInfo info : mActiveIface.values()) { + info.dump(" ", pw); + } + + pw.println("Known historical stats:"); + for (NetworkStatsHistory stats : mIfaceStats.values()) { + stats.dump(" ", pw); + } + } + + /** + * Details for a well-known network interface, including its name, network + * type, and billing relationship identity (such as IMSI). + */ + private static class InterfaceInfo { + public final String iface; + public final int networkType; + public final String identity; + + public InterfaceInfo(String iface, int networkType, String identity) { + this.iface = iface; + this.networkType = networkType; + this.identity = identity; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((identity == null) ? 0 : identity.hashCode()); + result = prime * result + ((iface == null) ? 0 : iface.hashCode()); + result = prime * result + networkType; + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof InterfaceInfo) { + final InterfaceInfo info = (InterfaceInfo) obj; + return equal(iface, info.iface) && networkType == info.networkType + && equal(identity, info.identity); + } + return false; + } + + public void dump(String prefix, PrintWriter pw) { + pw.print(prefix); + pw.print("InterfaceInfo: iface="); pw.print(iface); + pw.print(" networkType="); pw.print(networkType); + pw.print(" identity="); pw.println(identity); + } + } + + private void reportActiveInterface(InterfaceInfo info) { + synchronized (mStatsLock) { + // TODO: when interface redefined, port over historical stats + mActiveIface.put(info.iface, info); + } + } + + /** + * Return the delta between two {@link NetworkStats} snapshots, where {@code + * before} can be {@code null}. + */ + private static NetworkStats computeStatsDelta(NetworkStats before, NetworkStats current) { + if (before != null) { + return current.subtract(before, false); + } else { + return current; + } + } + + private static boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + private static <T> T checkNotNull(T value, String message) { + if (value == null) { + throw new NullPointerException(message); + } + return value; + } + +} diff --git a/services/java/com/android/server/pm/BasePermission.java b/services/java/com/android/server/pm/BasePermission.java new file mode 100644 index 0000000..4f27408 --- /dev/null +++ b/services/java/com/android/server/pm/BasePermission.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.content.pm.PackageParser; +import android.content.pm.PermissionInfo; + +final class BasePermission { + final static int TYPE_NORMAL = 0; + + final static int TYPE_BUILTIN = 1; + + final static int TYPE_DYNAMIC = 2; + + final String name; + + String sourcePackage; + + PackageSettingBase packageSetting; + + final int type; + + int protectionLevel; + + PackageParser.Permission perm; + + PermissionInfo pendingInfo; + + int uid; + + int[] gids; + + BasePermission(String _name, String _sourcePackage, int _type) { + name = _name; + sourcePackage = _sourcePackage; + type = _type; + // Default to most conservative protection level. + protectionLevel = PermissionInfo.PROTECTION_SIGNATURE; + } + + public String toString() { + return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name + + "}"; + } +} diff --git a/services/java/com/android/server/pm/GrantedPermissions.java b/services/java/com/android/server/pm/GrantedPermissions.java new file mode 100644 index 0000000..c7629b9 --- /dev/null +++ b/services/java/com/android/server/pm/GrantedPermissions.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.content.pm.ApplicationInfo; + +import java.util.HashSet; + +class GrantedPermissions { + int pkgFlags; + + HashSet<String> grantedPermissions = new HashSet<String>(); + + int[] gids; + + GrantedPermissions(int pkgFlags) { + setFlags(pkgFlags); + } + + @SuppressWarnings("unchecked") + GrantedPermissions(GrantedPermissions base) { + pkgFlags = base.pkgFlags; + grantedPermissions = (HashSet<String>) base.grantedPermissions.clone(); + + if (base.gids != null) { + gids = base.gids.clone(); + } + } + + void setFlags(int pkgFlags) { + this.pkgFlags = pkgFlags + & (ApplicationInfo.FLAG_SYSTEM + | ApplicationInfo.FLAG_FORWARD_LOCK + | ApplicationInfo.FLAG_EXTERNAL_STORAGE); + } +} diff --git a/services/java/com/android/server/Installer.java b/services/java/com/android/server/pm/Installer.java index 08d1b82..d10aa97 100644 --- a/services/java/com/android/server/Installer.java +++ b/services/java/com/android/server/pm/Installer.java @@ -14,28 +14,31 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.pm; import android.content.pm.PackageStats; -import android.net.LocalSocketAddress; import android.net.LocalSocket; -import android.util.Config; +import android.net.LocalSocketAddress; import android.util.Slog; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.Socket; - class Installer { private static final String TAG = "Installer"; - InputStream mIn; - OutputStream mOut; - LocalSocket mSocket; - byte buf[] = new byte[1024]; - int buflen = 0; + private static final boolean LOCAL_DEBUG = false; + + InputStream mIn; + + OutputStream mOut; + + LocalSocket mSocket; + + byte buf[] = new byte[1024]; + + int buflen = 0; private boolean connect() { if (mSocket != null) { @@ -45,8 +48,8 @@ class Installer { try { mSocket = new LocalSocket(); - LocalSocketAddress address = new LocalSocketAddress( - "installd", LocalSocketAddress.Namespace.RESERVED); + LocalSocketAddress address = new LocalSocketAddress("installd", + LocalSocketAddress.Namespace.RESERVED); mSocket.connect(address); @@ -59,112 +62,131 @@ class Installer { return true; } - private void disconnect() { - Slog.i(TAG,"disconnecting..."); - try { - if (mSocket != null) mSocket.close(); - } catch (IOException ex) { } - try { - if (mIn != null) mIn.close(); - } catch (IOException ex) { } - try { - if (mOut != null) mOut.close(); - } catch (IOException ex) { } - mSocket = null; - mIn = null; - mOut = null; - } - - private boolean readBytes(byte buffer[], int len) { - int off = 0, count; - if (len < 0) return false; - while (off != len) { - try { - count = mIn.read(buffer, off, len - off); - if (count <= 0) { + private void disconnect() { + Slog.i(TAG, "disconnecting..."); + try { + if (mSocket != null) + mSocket.close(); + } catch (IOException ex) { + } + try { + if (mIn != null) + mIn.close(); + } catch (IOException ex) { + } + try { + if (mOut != null) + mOut.close(); + } catch (IOException ex) { + } + mSocket = null; + mIn = null; + mOut = null; + } + + private boolean readBytes(byte buffer[], int len) { + int off = 0, count; + if (len < 0) + return false; + while (off != len) { + try { + count = mIn.read(buffer, off, len - off); + if (count <= 0) { Slog.e(TAG, "read error " + count); break; } - off += count; - } catch (IOException ex) { - Slog.e(TAG,"read exception"); - break; - } - } -// Slog.i(TAG, "read "+len+" bytes"); - if (off == len) return true; - disconnect(); - return false; - } - - private boolean readReply() { - int len; - buflen = 0; - if (!readBytes(buf, 2)) return false; - len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8); - if ((len < 1) || (len > 1024)) { - Slog.e(TAG,"invalid reply length ("+len+")"); - disconnect(); - return false; - } - if (!readBytes(buf, len)) return false; - buflen = len; - return true; - } - - private boolean writeCommand(String _cmd) { - byte[] cmd = _cmd.getBytes(); - int len = cmd.length; - if ((len < 1) || (len > 1024)) return false; - buf[0] = (byte) (len & 0xff); - buf[1] = (byte) ((len >> 8) & 0xff); - try { - mOut.write(buf, 0, 2); - mOut.write(cmd, 0, len); - } catch (IOException ex) { - Slog.e(TAG,"write error"); - disconnect(); - return false; - } - return true; - } - - private synchronized String transaction(String cmd) { - if (!connect()) { + off += count; + } catch (IOException ex) { + Slog.e(TAG, "read exception"); + break; + } + } + if (LOCAL_DEBUG) { + Slog.i(TAG, "read " + len + " bytes"); + } + if (off == len) + return true; + disconnect(); + return false; + } + + private boolean readReply() { + int len; + buflen = 0; + if (!readBytes(buf, 2)) + return false; + len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8); + if ((len < 1) || (len > 1024)) { + Slog.e(TAG, "invalid reply length (" + len + ")"); + disconnect(); + return false; + } + if (!readBytes(buf, len)) + return false; + buflen = len; + return true; + } + + private boolean writeCommand(String _cmd) { + byte[] cmd = _cmd.getBytes(); + int len = cmd.length; + if ((len < 1) || (len > 1024)) + return false; + buf[0] = (byte) (len & 0xff); + buf[1] = (byte) ((len >> 8) & 0xff); + try { + mOut.write(buf, 0, 2); + mOut.write(cmd, 0, len); + } catch (IOException ex) { + Slog.e(TAG, "write error"); + disconnect(); + return false; + } + return true; + } + + private synchronized String transaction(String cmd) { + if (!connect()) { Slog.e(TAG, "connection failed"); return "-1"; } if (!writeCommand(cmd)) { - /* If installd died and restarted in the background - * (unlikely but possible) we'll fail on the next - * write (this one). Try to reconnect and write - * the command one more time before giving up. - */ + /* + * If installd died and restarted in the background (unlikely but + * possible) we'll fail on the next write (this one). Try to + * reconnect and write the command one more time before giving up. + */ Slog.e(TAG, "write command failed? reconnect!"); if (!connect() || !writeCommand(cmd)) { return "-1"; } } -// Slog.i(TAG,"send: '"+cmd+"'"); - if (readReply()) { + if (LOCAL_DEBUG) { + Slog.i(TAG, "send: '" + cmd + "'"); + } + if (readReply()) { String s = new String(buf, 0, buflen); -// Slog.i(TAG,"recv: '"+s+"'"); - return s; - } else { -// Slog.i(TAG,"fail"); - return "-1"; - } - } - - private int execute(String cmd) { - String res = transaction(cmd); - try { - return Integer.parseInt(res); - } catch (NumberFormatException ex) { - return -1; - } - } + if (LOCAL_DEBUG) { + Slog.i(TAG, "recv: '" + s + "'"); + } + return s; + } else { + if (LOCAL_DEBUG) { + Slog.i(TAG, "fail"); + } + return "-1"; + } + } + + private int execute(String cmd) { + String res = transaction(cmd); + try { + return Integer.parseInt(res); + } catch (NumberFormatException ex) { + return -1; + } + } public int install(String name, int uid, int gid) { StringBuilder builder = new StringBuilder("install"); @@ -203,10 +225,12 @@ class Installer { return execute(builder.toString()); } - public int remove(String name) { + public int remove(String name, int userId) { StringBuilder builder = new StringBuilder("remove"); builder.append(' '); builder.append(name); + builder.append(' '); + builder.append(userId); return execute(builder.toString()); } @@ -225,14 +249,34 @@ class Installer { builder.append(name); return execute(builder.toString()); } - - public int clearUserData(String name) { + + public int createUserData(String name, int uid, int userId) { + StringBuilder builder = new StringBuilder("mkuserdata"); + builder.append(' '); + builder.append(name); + builder.append(' '); + builder.append(uid); + builder.append(' '); + builder.append(userId); + return execute(builder.toString()); + } + + public int removeUserDataDirs(int userId) { + StringBuilder builder = new StringBuilder("rmuser"); + builder.append(' '); + builder.append(userId); + return execute(builder.toString()); + } + + public int clearUserData(String name, int userId) { StringBuilder builder = new StringBuilder("rmuserdata"); builder.append(' '); builder.append(name); + builder.append(' '); + builder.append(userId); return execute(builder.toString()); } - + public boolean ping() { if (execute("ping") < 0) { return false; @@ -240,7 +284,7 @@ class Installer { return true; } } - + public int freeCache(long freeStorageSize) { StringBuilder builder = new StringBuilder("freecache"); builder.append(' '); @@ -250,8 +294,8 @@ class Installer { /* * @param packagePathSuffix The name of the path relative to install - * directory. Say if the path name is /data/app/com.test-1.apk, - * the package suffix path will be com.test-1 + * directory. Say if the path name is /data/app/com.test-1.apk, the package + * suffix path will be com.test-1 */ public int setForwardLockPerm(String packagePathSuffix, int gid) { StringBuilder builder = new StringBuilder("protect"); @@ -261,7 +305,7 @@ class Installer { builder.append(gid); return execute(builder.toString()); } - + public int getSizeInfo(String pkgName, String apkPath, String fwdLockApkPath, PackageStats pStats) { StringBuilder builder = new StringBuilder("getsize"); @@ -275,7 +319,7 @@ class Installer { String s = transaction(builder.toString()); String res[] = s.split(" "); - if((res == null) || (res.length != 4)) { + if ((res == null) || (res.length != 4)) { return -1; } try { @@ -286,7 +330,7 @@ class Installer { } catch (NumberFormatException e) { return -1; } - } + } public int moveFiles() { return execute("movefiles"); diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index dd5c2a9..e1fbe1c 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -14,26 +14,30 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.pm; + +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import com.android.internal.app.IMediaContainerService; import com.android.internal.app.ResolverActivity; import com.android.internal.content.NativeLibraryHelper; import com.android.internal.content.PackageHelper; -import com.android.internal.util.FastXmlSerializer; -import com.android.internal.util.JournaledFile; import com.android.internal.util.XmlUtils; +import com.android.server.DeviceStorageMonitorService; +import com.android.server.EventLogTags; +import com.android.server.IntentResolver; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; import android.app.ActivityManagerNative; import android.app.IActivityManager; import android.app.admin.IDevicePolicyManager; import android.app.backup.IBackupManager; -import android.content.Context; import android.content.ComponentName; +import android.content.Context; import android.content.IIntentReceiver; import android.content.Intent; import android.content.IntentFilter; @@ -42,7 +46,6 @@ import android.content.ServiceConnection; 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; @@ -54,46 +57,46 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageManager; +import android.content.pm.PackageParser; import android.content.pm.PackageStats; import android.content.pm.ParceledListSlice; - -import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; -import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; -import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; -import android.content.pm.PackageParser; -import android.content.pm.PermissionInfo; import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.Signature; +import android.content.pm.UserInfo; import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; -import android.os.Debug; +import android.os.Environment; +import android.os.FileObserver; +import android.os.FileUtils; +import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Parcel; -import android.os.Parcelable; -import android.os.RemoteException; -import android.os.Environment; -import android.os.FileObserver; -import android.os.FileUtils; -import android.os.Handler; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.security.SystemKeyStore; -import android.util.*; +import android.util.DisplayMetrics; +import android.util.EventLog; +import android.util.Log; +import android.util.LogPrinter; +import android.util.Slog; +import android.util.SparseArray; +import android.util.Xml; import android.view.Display; import android.view.WindowManager; -import java.io.BufferedOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -132,25 +135,30 @@ import java.util.zip.ZipOutputStream; mmm frameworks/base/tests/AndroidTests adb install -r -f out/target/product/passion/data/app/AndroidTests.apk adb shell am instrument -w -e class com.android.unit_tests.PackageManagerTests com.android.unit_tests/android.test.InstrumentationTestRunner - * + * + * {@hide} */ -class PackageManagerService extends IPackageManager.Stub { - private static final String TAG = "PackageManager"; - private static final boolean DEBUG_SETTINGS = false; +public class PackageManagerService extends IPackageManager.Stub { + static final String TAG = "PackageManager"; + static final boolean DEBUG_SETTINGS = false; private static final boolean DEBUG_PREFERRED = false; - private static final boolean DEBUG_UPGRADE = false; + static final boolean DEBUG_UPGRADE = false; private static final boolean DEBUG_INSTALL = false; - private static final boolean DEBUG_STOPPED = false; - - private static final boolean MULTIPLE_APPLICATION_UIDS = true; + private static final boolean DEBUG_REMOVE = false; + private static final boolean DEBUG_SHOW_INFO = false; + private static final boolean DEBUG_PACKAGE_INFO = false; + private static final boolean DEBUG_INTENT_MATCHING = false; + private static final boolean DEBUG_PACKAGE_SCANNING = false; + private static final boolean DEBUG_APP_DIR_OBSERVER = false; + + 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 NFC_UID = Process.NFC_UID; - private static final int FIRST_APPLICATION_UID = + private static final int KEYCHAIN_UID = Process.KEYCHAIN_UID; + static final int FIRST_APPLICATION_UID = Process.FIRST_APPLICATION_UID; - private static final int MAX_APPLICATION_UIDS = 1000; - - private static final boolean SHOW_INFO = false; + static final int MAX_APPLICATION_UIDS = 1000; private static final boolean GET_CERTIFICATES = true; @@ -164,19 +172,6 @@ class PackageManagerService extends IPackageManager.Stub { // package apks to install directory. private static final String INSTALL_PACKAGE_SUFFIX = "-"; - /** - * Indicates the state of installation. Used by PackageManager to - * figure out incomplete installations. Say a package is being installed - * (the state is set to PKG_INSTALL_INCOMPLETE) and remains so till - * the package installation is successful or unsuccesful lin which case - * the PackageManager will no longer maintain state information associated - * with the package. If some exception(like device freeze or battery being - * pulled out) occurs during installation of a package, the PackageManager - * needs this information to clean up the previously failed installation. - */ - private static final int PKG_INSTALL_INCOMPLETE = 0; - private static final int PKG_INSTALL_COMPLETE = 1; - static final int SCAN_MONITOR = 1<<0; static final int SCAN_NO_DEX = 1<<1; static final int SCAN_FORCE_DEX = 1<<2; @@ -215,6 +210,9 @@ class PackageManagerService extends IPackageManager.Stub { // This is where all application persistent data goes. final File mAppDataDir; + // This is where all application persistent data goes for secondary users. + final File mUserAppDataDir; + // This is the object monitoring the framework dir. final FileObserver mFrameworkInstallObserver; @@ -365,6 +363,9 @@ class PackageManagerService extends IPackageManager.Stub { // Delay time in millisecs static final int BROADCAST_DELAY = 10 * 1000; + + final UserManager mUserManager; + final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); class DefaultContainerConnection implements ServiceConnection { @@ -529,12 +530,12 @@ class PackageManagerService extends IPackageManager.Stub { } case MCS_GIVE_UP: { if (DEBUG_SD_INSTALL) Log.i(TAG, "mcs_giveup too many retries"); - HandlerParams params = mPendingInstalls.remove(0); + mPendingInstalls.remove(0); break; } case SEND_PENDING_BROADCAST : { String packages[]; - ArrayList components[]; + ArrayList<String> components[]; int size = 0; int uids[]; Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); @@ -566,8 +567,7 @@ class PackageManagerService extends IPackageManager.Stub { } // Send broadcasts for (int i = 0; i < size; i++) { - sendPackageChangedBroadcast(packages[i], true, - (ArrayList<String>)components[i], uids[i]); + sendPackageChangedBroadcast(packages[i], true, components[i], uids[i]); } Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); break; @@ -665,7 +665,7 @@ class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { removeMessages(WRITE_SETTINGS); removeMessages(WRITE_STOPPED_PACKAGES); - mSettings.writeLP(); + mSettings.writeLPr(); } Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } break; @@ -673,7 +673,7 @@ class PackageManagerService extends IPackageManager.Stub { Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); synchronized (mPackages) { removeMessages(WRITE_STOPPED_PACKAGES); - mSettings.writeStoppedLP(); + mSettings.writeStoppedLPr(); } Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } break; @@ -745,20 +745,24 @@ class PackageManagerService extends IPackageManager.Stub { mNoDexOpt = "eng".equals(SystemProperties.get("ro.build.type")); mMetrics = new DisplayMetrics(); mSettings = new Settings(); - mSettings.addSharedUserLP("android.uid.system", + mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLP("android.uid.phone", + mSettings.addSharedUserLPw("android.uid.phone", MULTIPLE_APPLICATION_UIDS ? RADIO_UID : FIRST_APPLICATION_UID, ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLP("android.uid.log", + mSettings.addSharedUserLPw("android.uid.log", MULTIPLE_APPLICATION_UIDS ? LOG_UID : FIRST_APPLICATION_UID, ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLP("android.uid.nfc", + mSettings.addSharedUserLPw("android.uid.nfc", MULTIPLE_APPLICATION_UIDS ? NFC_UID : FIRST_APPLICATION_UID, ApplicationInfo.FLAG_SYSTEM); + mSettings.addSharedUserLPw("android.uid.keychain", + MULTIPLE_APPLICATION_UIDS + ? KEYCHAIN_UID : FIRST_APPLICATION_UID, + ApplicationInfo.FLAG_SYSTEM); String separateProcesses = SystemProperties.get("debug.separate_processes"); if (separateProcesses != null && separateProcesses.length() > 0) { @@ -793,14 +797,18 @@ class PackageManagerService extends IPackageManager.Stub { d.getMetrics(mMetrics); synchronized (mInstallLock) { + // writer synchronized (mPackages) { mHandlerThread.start(); mHandler = new PackageHandler(mHandlerThread.getLooper()); File dataDir = Environment.getDataDirectory(); mAppDataDir = new File(dataDir, "data"); + mUserAppDataDir = new File(dataDir, "user"); mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); + mUserManager = new UserManager(mInstaller, mUserAppDataDir); + if (mInstaller == null) { // Make sure these dirs exist, when we are running in // the simulator. @@ -808,12 +816,13 @@ class PackageManagerService extends IPackageManager.Stub { File miscDir = new File(dataDir, "misc"); miscDir.mkdirs(); mAppDataDir.mkdirs(); + mUserAppDataDir.mkdirs(); mDrmAppPrivateInstallDir.mkdirs(); } readPermissions(); - mRestoredSettings = mSettings.readLP(); + mRestoredSettings = mSettings.readLPw(); long startTime = SystemClock.uptimeMillis(); EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START, @@ -976,7 +985,8 @@ class PackageManagerService extends IPackageManager.Stub { + " no longer exists; wiping its data"; reportSettingsProblem(Log.WARN, msg); if (mInstaller != null) { - mInstaller.remove(ps.name); + mInstaller.remove(ps.name, 0); + mUserManager.removePackageForAllUsers(ps.name); } } } @@ -988,7 +998,7 @@ class PackageManagerService extends IPackageManager.Stub { mAppInstallDir.mkdirs(); // scanDirLI() assumes this dir exists } //look for any incomplete package installations - ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackages(); + ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackagesLPr(); //clean up list for(int i = 0; i < deletePkgsList.size(); i++) { //clean up here @@ -1029,9 +1039,10 @@ class PackageManagerService extends IPackageManager.Stub { + "; regranting permissions for internal storage"); mSettings.mInternalSdkPlatform = mSdkVersion; - updatePermissionsLP(null, null, true, regrantPermissions, regrantPermissions); + updatePermissionsLPw(null, null, true, regrantPermissions, regrantPermissions); - mSettings.writeLP(); + // can downgrade to reader + mSettings.writeLPr(); EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY, SystemClock.uptimeMillis()); @@ -1060,10 +1071,12 @@ class PackageManagerService extends IPackageManager.Stub { void cleanupInstallFailedPackage(PackageSetting ps) { Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name); if (mInstaller != null) { - int retCode = mInstaller.remove(ps.name); + int retCode = mInstaller.remove(ps.name, 0); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data directory for package: " + ps.name + ", retcode=" + retCode); + } else { + mUserManager.removePackageForAllUsers(ps.name); } } else { //for emulator @@ -1081,7 +1094,7 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Unable to remove old code file: " + ps.resourcePath); } } - mSettings.removePackageLP(ps.name); + mSettings.removePackageLPw(ps.name); } void readPermissions() { @@ -1348,16 +1361,16 @@ class PackageManagerService extends IPackageManager.Stub { } public PackageInfo getPackageInfo(String packageName, int flags) { + // reader synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); - if (Config.LOGV) Log.v( - TAG, "getPackageInfo " + packageName - + ": " + p); + if (DEBUG_PACKAGE_INFO) + Log.v(TAG, "getPackageInfo " + packageName + ": " + p); if (p != null) { return generatePackageInfo(p, flags); } if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { - return generatePackageInfoFromSettingsLP(packageName, flags); + return generatePackageInfoFromSettingsLPw(packageName, flags); } } return null; @@ -1365,6 +1378,7 @@ class PackageManagerService extends IPackageManager.Stub { public String[] currentToCanonicalPackageNames(String[] names) { String[] out = new String[names.length]; + // reader synchronized (mPackages) { for (int i=names.length-1; i>=0; i--) { PackageSetting ps = mSettings.mPackages.get(names[i]); @@ -1376,6 +1390,7 @@ class PackageManagerService extends IPackageManager.Stub { public String[] canonicalToCurrentPackageNames(String[] names) { String[] out = new String[names.length]; + // reader synchronized (mPackages) { for (int i=names.length-1; i>=0; i--) { String cur = mSettings.mRenamedPackages.get(names[i]); @@ -1386,6 +1401,7 @@ class PackageManagerService extends IPackageManager.Stub { } public int getPackageUid(String packageName) { + // reader synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); if(p != null) { @@ -1401,11 +1417,11 @@ class PackageManagerService extends IPackageManager.Stub { } public int[] getPackageGids(String packageName) { + // reader synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); - if (Config.LOGV) Log.v( - TAG, "getPackageGids" + packageName - + ": " + p); + if (DEBUG_PACKAGE_INFO) + Log.v(TAG, "getPackageGids" + packageName + ": " + p); if (p != null) { final PackageSetting ps = (PackageSetting)p.mExtras; final SharedUserSetting suid = ps.sharedUser; @@ -1430,6 +1446,7 @@ class PackageManagerService extends IPackageManager.Stub { } public PermissionInfo getPermissionInfo(String name, int flags) { + // reader synchronized (mPackages) { final BasePermission p = mSettings.mPermissions.get(name); if (p != null) { @@ -1440,6 +1457,7 @@ class PackageManagerService extends IPackageManager.Stub { } public List<PermissionInfo> queryPermissionsByGroup(String group, int flags) { + // reader synchronized (mPackages) { ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10); for (BasePermission p : mSettings.mPermissions.values()) { @@ -1462,6 +1480,7 @@ class PackageManagerService extends IPackageManager.Stub { } public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) { + // reader synchronized (mPackages) { return PackageParser.generatePermissionGroupInfo( mPermissionGroups.get(name), flags); @@ -1469,6 +1488,7 @@ class PackageManagerService extends IPackageManager.Stub { } public List<PermissionGroupInfo> getAllPermissionGroups(int flags) { + // reader synchronized (mPackages) { final int N = mPermissionGroups.size(); ArrayList<PermissionGroupInfo> out @@ -1480,12 +1500,12 @@ class PackageManagerService extends IPackageManager.Stub { } } - private ApplicationInfo generateApplicationInfoFromSettingsLP(String packageName, int flags) { + private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags) { PackageSetting ps = mSettings.mPackages.get(packageName); - if(ps != null) { - if(ps.pkg == null) { - PackageInfo pInfo = generatePackageInfoFromSettingsLP(packageName, flags); - if(pInfo != null) { + if (ps != null) { + if (ps.pkg == null) { + PackageInfo pInfo = generatePackageInfoFromSettingsLPw(packageName, flags); + if (pInfo != null) { return pInfo.applicationInfo; } return null; @@ -1495,16 +1515,17 @@ class PackageManagerService extends IPackageManager.Stub { return null; } - private PackageInfo generatePackageInfoFromSettingsLP(String packageName, int flags) { + private PackageInfo generatePackageInfoFromSettingsLPw(String packageName, int flags) { PackageSetting ps = mSettings.mPackages.get(packageName); - if(ps != null) { - if(ps.pkg == null) { + if (ps != null) { + if (ps.pkg == null) { ps.pkg = new PackageParser.Package(packageName); ps.pkg.applicationInfo.packageName = packageName; ps.pkg.applicationInfo.flags = ps.pkgFlags; ps.pkg.applicationInfo.publicSourceDir = ps.resourcePathString; ps.pkg.applicationInfo.sourceDir = ps.codePathString; - ps.pkg.applicationInfo.dataDir = getDataPathForPackage(ps.pkg).getPath(); + ps.pkg.applicationInfo.dataDir = + getDataPathForPackage(ps.pkg.packageName, 0).getPath(); ps.pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString; ps.pkg.mSetEnabled = ps.enabled; ps.pkg.mSetStopped = ps.stopped; @@ -1515,9 +1536,10 @@ class PackageManagerService extends IPackageManager.Stub { } public ApplicationInfo getApplicationInfo(String packageName, int flags) { + // writer synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); - if (Config.LOGV) Log.v( + if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getApplicationInfo " + packageName + ": " + p); if (p != null) { @@ -1528,7 +1550,7 @@ class PackageManagerService extends IPackageManager.Stub { return mAndroidApplication; } if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { - return generateApplicationInfoFromSettingsLP(packageName, flags); + return generateApplicationInfoFromSettingsLPw(packageName, flags); } } return null; @@ -1592,8 +1614,8 @@ class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { PackageParser.Activity a = mActivities.mActivities.get(component); - if (Config.LOGV) Log.v(TAG, "getActivityInfo " + component + ": " + a); - if (a != null && mSettings.isEnabledLP(a.info, flags)) { + if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a); + if (a != null && mSettings.isEnabledLPr(a.info, flags)) { return PackageParser.generateActivityInfo(a, flags); } if (mResolveComponentName.equals(component)) { @@ -1606,9 +1628,9 @@ class PackageManagerService extends IPackageManager.Stub { public ActivityInfo getReceiverInfo(ComponentName component, int flags) { synchronized (mPackages) { PackageParser.Activity a = mReceivers.mActivities.get(component); - if (Config.LOGV) Log.v( + if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getReceiverInfo " + component + ": " + a); - if (a != null && mSettings.isEnabledLP(a.info, flags)) { + if (a != null && mSettings.isEnabledLPr(a.info, flags)) { return PackageParser.generateActivityInfo(a, flags); } } @@ -1618,9 +1640,9 @@ class PackageManagerService extends IPackageManager.Stub { public ServiceInfo getServiceInfo(ComponentName component, int flags) { synchronized (mPackages) { PackageParser.Service s = mServices.mServices.get(component); - if (Config.LOGV) Log.v( + if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getServiceInfo " + component + ": " + s); - if (s != null && mSettings.isEnabledLP(s.info, flags)) { + if (s != null && mSettings.isEnabledLPr(s.info, flags)) { return PackageParser.generateServiceInfo(s, flags); } } @@ -1630,9 +1652,9 @@ class PackageManagerService extends IPackageManager.Stub { public ProviderInfo getProviderInfo(ComponentName component, int flags) { synchronized (mPackages) { PackageParser.Provider p = mProvidersByComponent.get(component); - if (Config.LOGV) Log.v( + if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getProviderInfo " + component + ": " + p); - if (p != null && mSettings.isEnabledLP(p.info, flags)) { + if (p != null && mSettings.isEnabledLPr(p.info, flags)) { return PackageParser.generateProviderInfo(p, flags); } } @@ -1696,7 +1718,7 @@ class PackageManagerService extends IPackageManager.Stub { public int checkUidPermission(String permName, int uid) { synchronized (mPackages) { - Object obj = mSettings.getUserIdLP(uid); + Object obj = mSettings.getUserIdLPr(uid); if (obj != null) { GrantedPermissions gp = (GrantedPermissions)obj; if (gp.grantedPermissions.contains(permName)) { @@ -1801,7 +1823,7 @@ class PackageManagerService extends IPackageManager.Stub { } if (changed) { if (!async) { - mSettings.writeLP(); + mSettings.writeLPr(); } else { scheduleWriteSettingsLocked(); } @@ -1832,7 +1854,7 @@ class PackageManagerService extends IPackageManager.Stub { + name); } mSettings.mPermissions.remove(name); - mSettings.writeLP(); + mSettings.writeLPr(); } } } @@ -1845,21 +1867,22 @@ class PackageManagerService extends IPackageManager.Stub { public int checkSignatures(String pkg1, String pkg2) { synchronized (mPackages) { - PackageParser.Package p1 = mPackages.get(pkg1); - PackageParser.Package p2 = mPackages.get(pkg2); + final PackageParser.Package p1 = mPackages.get(pkg1); + final PackageParser.Package p2 = mPackages.get(pkg2); if (p1 == null || p1.mExtras == null || p2 == null || p2.mExtras == null) { return PackageManager.SIGNATURE_UNKNOWN_PACKAGE; } - return checkSignaturesLP(p1.mSignatures, p2.mSignatures); + return compareSignatures(p1.mSignatures, p2.mSignatures); } } public int checkUidSignatures(int uid1, int uid2) { + // reader synchronized (mPackages) { Signature[] s1; Signature[] s2; - Object obj = mSettings.getUserIdLP(uid1); + Object obj = mSettings.getUserIdLPr(uid1); if (obj != null) { if (obj instanceof SharedUserSetting) { s1 = ((SharedUserSetting)obj).signatures.mSignatures; @@ -1871,7 +1894,7 @@ class PackageManagerService extends IPackageManager.Stub { } else { return PackageManager.SIGNATURE_UNKNOWN_PACKAGE; } - obj = mSettings.getUserIdLP(uid2); + obj = mSettings.getUserIdLPr(uid2); if (obj != null) { if (obj instanceof SharedUserSetting) { s2 = ((SharedUserSetting)obj).signatures.mSignatures; @@ -1883,11 +1906,11 @@ class PackageManagerService extends IPackageManager.Stub { } else { return PackageManager.SIGNATURE_UNKNOWN_PACKAGE; } - return checkSignaturesLP(s1, s2); + return compareSignatures(s1, s2); } } - int checkSignaturesLP(Signature[] s1, Signature[] s2) { + static int compareSignatures(Signature[] s1, Signature[] s2) { if (s1 == null) { return s2 == null ? PackageManager.SIGNATURE_NEITHER_SIGNED @@ -1912,20 +1935,21 @@ class PackageManagerService extends IPackageManager.Stub { } public String[] getPackagesForUid(int uid) { + // reader synchronized (mPackages) { - Object obj = mSettings.getUserIdLP(uid); + Object obj = mSettings.getUserIdLPr(uid); if (obj instanceof SharedUserSetting) { - SharedUserSetting sus = (SharedUserSetting)obj; + final SharedUserSetting sus = (SharedUserSetting) obj; final int N = sus.packages.size(); - String[] res = new String[N]; - Iterator<PackageSetting> it = sus.packages.iterator(); - int i=0; + final String[] res = new String[N]; + final Iterator<PackageSetting> it = sus.packages.iterator(); + int i = 0; while (it.hasNext()) { res[i++] = it.next().name; } return res; } else if (obj instanceof PackageSetting) { - PackageSetting ps = (PackageSetting)obj; + final PackageSetting ps = (PackageSetting) obj; return new String[] { ps.name }; } } @@ -1933,13 +1957,14 @@ class PackageManagerService extends IPackageManager.Stub { } public String getNameForUid(int uid) { + // reader synchronized (mPackages) { - Object obj = mSettings.getUserIdLP(uid); + Object obj = mSettings.getUserIdLPr(uid); if (obj instanceof SharedUserSetting) { - SharedUserSetting sus = (SharedUserSetting)obj; + final SharedUserSetting sus = (SharedUserSetting) obj; return sus.name + ":" + sus.userId; } else if (obj instanceof PackageSetting) { - PackageSetting ps = (PackageSetting)obj; + final PackageSetting ps = (PackageSetting) obj; return ps.name; } } @@ -1950,8 +1975,9 @@ class PackageManagerService extends IPackageManager.Stub { if(sharedUserName == null) { return -1; } + // reader synchronized (mPackages) { - SharedUserSetting suid = mSettings.getSharedUserLP(sharedUserName, 0, false); + final SharedUserSetting suid = mSettings.getSharedUserLPw(sharedUserName, 0, false); if(suid == null) { return -1; } @@ -1976,11 +2002,9 @@ class PackageManagerService extends IPackageManager.Stub { // then let the user decide between them. ResolveInfo r0 = query.get(0); ResolveInfo r1 = query.get(1); - if (false) { - System.out.println(r0.activityInfo.name + - "=" + r0.priority + " vs " + - r1.activityInfo.name + - "=" + r1.priority); + if (DEBUG_INTENT_MATCHING) { + Log.d(TAG, r0.activityInfo.name + "=" + r0.priority + " vs " + + r1.activityInfo.name + "=" + r1.priority); } // If the first activity has a higher priority, or a different // default, then it is always desireable to pick it. @@ -2004,6 +2028,7 @@ class PackageManagerService extends IPackageManager.Stub { ResolveInfo findPreferredActivity(Intent intent, String resolvedType, int flags, List<ResolveInfo> query, int priority) { + // writer synchronized (mPackages) { if (DEBUG_PREFERRED) intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION); List<PreferredActivity> prefs = @@ -2014,24 +2039,35 @@ class PackageManagerService extends IPackageManager.Stub { // We will only allow preferred activities that came // from the same match quality. int match = 0; + + if (DEBUG_PREFERRED) { + Log.v(TAG, "Figuring out best match..."); + } + final int N = query.size(); - if (DEBUG_PREFERRED) Log.v(TAG, "Figuring out best match..."); for (int j=0; j<N; j++) { - ResolveInfo ri = query.get(j); - if (DEBUG_PREFERRED) Log.v(TAG, "Match for " + ri.activityInfo - + ": 0x" + Integer.toHexString(match)); - if (ri.match > match) match = ri.match; + final ResolveInfo ri = query.get(j); + if (DEBUG_PREFERRED) { + Log.v(TAG, "Match for " + ri.activityInfo + ": 0x" + + Integer.toHexString(match)); + } + if (ri.match > match) { + match = ri.match; + } + } + + if (DEBUG_PREFERRED) { + Log.v(TAG, "Best match: 0x" + Integer.toHexString(match)); } - if (DEBUG_PREFERRED) Log.v(TAG, "Best match: 0x" - + Integer.toHexString(match)); + match &= IntentFilter.MATCH_CATEGORY_MASK; final int M = prefs.size(); for (int i=0; i<M; i++) { - PreferredActivity pa = prefs.get(i); + final PreferredActivity pa = prefs.get(i); if (pa.mPref.mMatch != match) { continue; } - ActivityInfo ai = getActivityInfo(pa.mPref.mComponent, flags); + final ActivityInfo ai = getActivityInfo(pa.mPref.mComponent, flags); if (DEBUG_PREFERRED) { Log.v(TAG, "Got preferred activity:"); if (ai != null) { @@ -2042,7 +2078,7 @@ class PackageManagerService extends IPackageManager.Stub { } if (ai != null) { for (int j=0; j<N; j++) { - ResolveInfo ri = query.get(j); + final ResolveInfo ri = query.get(j); if (!ri.activityInfo.applicationInfo.packageName .equals(ai.applicationInfo.packageName)) { continue; @@ -2074,28 +2110,28 @@ class PackageManagerService extends IPackageManager.Stub { public List<ResolveInfo> queryIntentActivities(Intent intent, String resolvedType, int flags) { - ComponentName comp = intent.getComponent(); + final ComponentName comp = intent.getComponent(); if (comp != null) { - List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); - ActivityInfo ai = getActivityInfo(comp, flags); + final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); + final ActivityInfo ai = getActivityInfo(comp, flags); if (ai != null) { - ResolveInfo ri = new ResolveInfo(); + final ResolveInfo ri = new ResolveInfo(); ri.activityInfo = ai; list.add(ri); } return list; } + // reader synchronized (mPackages) { - String pkgName = intent.getPackage(); + final String pkgName = intent.getPackage(); if (pkgName == null) { - return (List<ResolveInfo>)mActivities.queryIntent(intent, - resolvedType, flags); + return mActivities.queryIntent(intent, resolvedType, flags); } - PackageParser.Package pkg = mPackages.get(pkgName); + final PackageParser.Package pkg = mPackages.get(pkgName); if (pkg != null) { - return (List<ResolveInfo>) mActivities.queryIntentForPackage(intent, - resolvedType, flags, pkg.activities); + return mActivities.queryIntentForPackage(intent, resolvedType, flags, + pkg.activities); } return new ArrayList<ResolveInfo>(); } @@ -2106,9 +2142,12 @@ class PackageManagerService extends IPackageManager.Stub { String resolvedType, int flags) { final String resultsAction = intent.getAction(); - List<ResolveInfo> results = queryIntentActivities( - intent, resolvedType, flags|PackageManager.GET_RESOLVED_FILTER); - if (Config.LOGV) Log.v(TAG, "Query " + intent + ": " + results); + List<ResolveInfo> results = queryIntentActivities(intent, resolvedType, flags + | PackageManager.GET_RESOLVED_FILTER); + + if (DEBUG_INTENT_MATCHING) { + Log.v(TAG, "Query " + intent + ": " + results); + } int specificsPos = 0; int N; @@ -2128,16 +2167,21 @@ class PackageManagerService extends IPackageManager.Stub { continue; } - if (Config.LOGV) Log.v(TAG, "Specific #" + i + ": " + sintent); + if (DEBUG_INTENT_MATCHING) { + Log.v(TAG, "Specific #" + i + ": " + sintent); + } + String action = sintent.getAction(); if (resultsAction != null && resultsAction.equals(action)) { // If this action was explicitly requested, then don't // remove things that have it. action = null; } - ComponentName comp = sintent.getComponent(); + ResolveInfo ri = null; ActivityInfo ai = null; + + ComponentName comp = sintent.getComponent(); if (comp == null) { ri = resolveIntent( sintent, @@ -2161,7 +2205,7 @@ class PackageManagerService extends IPackageManager.Stub { // Look for any generic query activities that are duplicates // of this specific one, and remove them from the results. - if (Config.LOGV) Log.v(TAG, "Specific #" + i + ": " + ai); + if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Specific #" + i + ": " + ai); N = results.size(); int j; for (j=specificsPos; j<N; j++) { @@ -2171,7 +2215,7 @@ class PackageManagerService extends IPackageManager.Stub { comp.getPackageName())) || (action != null && sri.filter.matchAction(action))) { results.remove(j); - if (Config.LOGV) Log.v( + if (DEBUG_INTENT_MATCHING) Log.v( TAG, "Removing duplicate item from " + j + " due to specific " + specificsPos); if (ri == null) { @@ -2219,7 +2263,7 @@ class PackageManagerService extends IPackageManager.Stub { final ResolveInfo rij = results.get(j); if (rij.filter != null && rij.filter.hasAction(action)) { results.remove(j); - if (Config.LOGV) Log.v( + if (DEBUG_INTENT_MATCHING) Log.v( TAG, "Removing duplicate item from " + j + " due to action " + action + " at " + i); j--; @@ -2258,12 +2302,11 @@ class PackageManagerService extends IPackageManager.Stub { } } - if (Config.LOGV) Log.v(TAG, "Result: " + results); + if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Result: " + results); return results; } - public List<ResolveInfo> queryIntentReceivers(Intent intent, - String resolvedType, int flags) { + public List<ResolveInfo> queryIntentReceivers(Intent intent, String resolvedType, int flags) { ComponentName comp = intent.getComponent(); if (comp != null) { List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); @@ -2276,25 +2319,22 @@ class PackageManagerService extends IPackageManager.Stub { return list; } + // reader synchronized (mPackages) { String pkgName = intent.getPackage(); if (pkgName == null) { - return (List<ResolveInfo>)mReceivers.queryIntent(intent, - resolvedType, flags); + return mReceivers.queryIntent(intent, resolvedType, flags); } - PackageParser.Package pkg = mPackages.get(pkgName); + final PackageParser.Package pkg = mPackages.get(pkgName); if (pkg != null) { - return (List<ResolveInfo>) mReceivers.queryIntentForPackage(intent, - resolvedType, flags, pkg.receivers); + return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers); } return null; } } - public ResolveInfo resolveService(Intent intent, String resolvedType, - int flags) { - List<ResolveInfo> query = queryIntentServices(intent, resolvedType, - flags); + public ResolveInfo resolveService(Intent intent, String resolvedType, int flags) { + List<ResolveInfo> query = queryIntentServices(intent, resolvedType, flags); if (query != null) { if (query.size() >= 1) { // If there is more than one service with the same priority, @@ -2305,30 +2345,28 @@ class PackageManagerService extends IPackageManager.Stub { return null; } - public List<ResolveInfo> queryIntentServices(Intent intent, - String resolvedType, int flags) { - ComponentName comp = intent.getComponent(); + public List<ResolveInfo> queryIntentServices(Intent intent, String resolvedType, int flags) { + final ComponentName comp = intent.getComponent(); if (comp != null) { - List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); - ServiceInfo si = getServiceInfo(comp, flags); + final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); + final ServiceInfo si = getServiceInfo(comp, flags); if (si != null) { - ResolveInfo ri = new ResolveInfo(); + final ResolveInfo ri = new ResolveInfo(); ri.serviceInfo = si; list.add(ri); } return list; } + // reader synchronized (mPackages) { String pkgName = intent.getPackage(); if (pkgName == null) { - return (List<ResolveInfo>)mServices.queryIntent(intent, - resolvedType, flags); + return mServices.queryIntent(intent, resolvedType, flags); } - PackageParser.Package pkg = mPackages.get(pkgName); + final PackageParser.Package pkg = mPackages.get(pkgName); if (pkg != null) { - return (List<ResolveInfo>)mServices.queryIntentForPackage(intent, - resolvedType, flags, pkg.services); + return mServices.queryIntentForPackage(intent, resolvedType, flags, pkg.services); } return null; } @@ -2354,6 +2392,7 @@ class PackageManagerService extends IPackageManager.Stub { final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; final String[] keys; + // writer synchronized (mPackages) { if (listUninstalled) { keys = mSettings.mPackages.keySet().toArray(new String[mSettings.mPackages.size()]); @@ -2372,7 +2411,7 @@ class PackageManagerService extends IPackageManager.Stub { if (listUninstalled) { final PackageSetting ps = mSettings.mPackages.get(packageName); if (ps != null) { - pi = generatePackageInfoFromSettingsLP(ps.name, flags); + pi = generatePackageInfoFromSettingsLPw(ps.name, flags); } } else { final PackageParser.Package p = mPackages.get(packageName); @@ -2400,6 +2439,7 @@ class PackageManagerService extends IPackageManager.Stub { final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; final String[] keys; + // writer synchronized (mPackages) { if (listUninstalled) { keys = mSettings.mPackages.keySet().toArray(new String[mSettings.mPackages.size()]); @@ -2418,7 +2458,7 @@ class PackageManagerService extends IPackageManager.Stub { if (listUninstalled) { final PackageSetting ps = mSettings.mPackages.get(packageName); if (ps != null) { - ai = generateApplicationInfoFromSettingsLP(ps.name, flags); + ai = generateApplicationInfoFromSettingsLPw(ps.name, flags); } } else { final PackageParser.Package p = mPackages.get(packageName); @@ -2441,12 +2481,13 @@ class PackageManagerService extends IPackageManager.Stub { } public List<ApplicationInfo> getPersistentApplications(int flags) { - ArrayList<ApplicationInfo> finalList = new ArrayList<ApplicationInfo>(); + final ArrayList<ApplicationInfo> finalList = new ArrayList<ApplicationInfo>(); + // reader synchronized (mPackages) { - Iterator<PackageParser.Package> i = mPackages.values().iterator(); + final Iterator<PackageParser.Package> i = mPackages.values().iterator(); while (i.hasNext()) { - PackageParser.Package p = i.next(); + final PackageParser.Package p = i.next(); if (p.applicationInfo != null && (p.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) != 0 && (!mSafeMode || isSystemApp(p))) { @@ -2459,10 +2500,11 @@ class PackageManagerService extends IPackageManager.Stub { } public ProviderInfo resolveContentProvider(String name, int flags) { + // reader synchronized (mPackages) { final PackageParser.Provider provider = mProviders.get(name); return provider != null - && mSettings.isEnabledLP(provider.info, flags) + && mSettings.isEnabledLPr(provider.info, flags) && (!mSafeMode || (provider.info.applicationInfo.flags &ApplicationInfo.FLAG_SYSTEM) != 0) ? PackageParser.generateProviderInfo(provider, flags) @@ -2473,10 +2515,12 @@ class PackageManagerService extends IPackageManager.Stub { /** * @deprecated */ - public void querySyncProviders(List outNames, List outInfo) { + @Deprecated + public void querySyncProviders(List<String> outNames, List<ProviderInfo> outInfo) { + // reader synchronized (mPackages) { - Iterator<Map.Entry<String, PackageParser.Provider>> i - = mProviders.entrySet().iterator(); + final Iterator<Map.Entry<String, PackageParser.Provider>> i = mProviders.entrySet() + .iterator(); while (i.hasNext()) { Map.Entry<String, PackageParser.Provider> entry = i.next(); @@ -2496,22 +2540,22 @@ class PackageManagerService extends IPackageManager.Stub { int uid, int flags) { ArrayList<ProviderInfo> finalList = null; + // reader synchronized (mPackages) { - Iterator<PackageParser.Provider> i = mProvidersByComponent.values().iterator(); + final Iterator<PackageParser.Provider> i = mProvidersByComponent.values().iterator(); while (i.hasNext()) { - PackageParser.Provider p = i.next(); + final PackageParser.Provider p = i.next(); if (p.info.authority != null - && (processName == null || - (p.info.processName.equals(processName) - && p.info.applicationInfo.uid == uid)) - && mSettings.isEnabledLP(p.info, flags) - && (!mSafeMode || (p.info.applicationInfo.flags - &ApplicationInfo.FLAG_SYSTEM) != 0)) { + && (processName == null + || (p.info.processName.equals(processName) + && p.info.applicationInfo.uid == uid)) + && mSettings.isEnabledLPr(p.info, flags) + && (!mSafeMode + || (p.info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)) { if (finalList == null) { finalList = new ArrayList<ProviderInfo>(3); } - finalList.add(PackageParser.generateProviderInfo(p, - flags)); + finalList.add(PackageParser.generateProviderInfo(p, flags)); } } } @@ -2525,6 +2569,7 @@ class PackageManagerService extends IPackageManager.Stub { public InstrumentationInfo getInstrumentationInfo(ComponentName name, int flags) { + // reader synchronized (mPackages) { final PackageParser.Instrumentation i = mInstrumentation.get(name); return PackageParser.generateInstrumentationInfo(i, flags); @@ -2536,10 +2581,11 @@ class PackageManagerService extends IPackageManager.Stub { ArrayList<InstrumentationInfo> finalList = new ArrayList<InstrumentationInfo>(); + // reader synchronized (mPackages) { - Iterator<PackageParser.Instrumentation> i = mInstrumentation.values().iterator(); + final Iterator<PackageParser.Instrumentation> i = mInstrumentation.values().iterator(); while (i.hasNext()) { - PackageParser.Instrumentation p = i.next(); + final PackageParser.Instrumentation p = i.next(); if (targetPackage == null || targetPackage.equals(p.info.targetPackage)) { finalList.add(PackageParser.generateInstrumentationInfo(p, @@ -2558,7 +2604,7 @@ class PackageManagerService extends IPackageManager.Stub { return; } - if (false) { + if (DEBUG_PACKAGE_SCANNING) { Log.d(TAG, "Scanning app dir " + dir); } @@ -2588,7 +2634,7 @@ class PackageManagerService extends IPackageManager.Stub { return fname; } - private static void reportSettingsProblem(int priority, String msg) { + static void reportSettingsProblem(int priority, String msg) { try { File fname = getSettingsProblemFile(); FileOutputStream out = new FileOutputStream(fname, true); @@ -2652,17 +2698,18 @@ class PackageManagerService extends IPackageManager.Stub { } PackageSetting ps = null; PackageSetting updatedPkg; + // reader synchronized (mPackages) { // Look to see if we already know about this package. String oldName = mSettings.mRenamedPackages.get(pkg.packageName); if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) { // This package has been renamed to its original name. Let's // use that. - ps = mSettings.peekPackageLP(oldName); + ps = mSettings.peekPackageLPr(oldName); } // If there was no original package, see one for the real package name. if (ps == null) { - ps = mSettings.peekPackageLP(pkg.packageName); + ps = mSettings.peekPackageLPr(pkg.packageName); } // Check to see if this package could be hiding/updating a system // package. Must look for it either under the original or real @@ -2691,6 +2738,7 @@ class PackageManagerService extends IPackageManager.Stub { // 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 + // writer synchronized (mPackages) { // Just remove the loaded entries from package lists. mPackages.remove(ps.name); @@ -2702,7 +2750,7 @@ class PackageManagerService extends IPackageManager.Stub { InstallArgs args = new FileInstallArgs(ps.codePathString, ps.resourcePathString, ps.nativeLibraryPathString); args.cleanUpResourcesLI(); - mSettings.enableSystemPackageLP(ps.name); + mSettings.enableSystemPackageLPw(ps.name); } } } @@ -2760,7 +2808,7 @@ class PackageManagerService extends IPackageManager.Stub { PackageParser.Package pkg) { if (pkgSetting.signatures.mSignatures != null) { // Already existing package. Make sure signatures match - if (checkSignaturesLP(pkgSetting.signatures.mSignatures, pkg.mSignatures) != + if (compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) { Slog.e(TAG, "Package " + pkg.packageName + " signatures do not match the previously installed version; ignoring!"); @@ -2770,7 +2818,7 @@ class PackageManagerService extends IPackageManager.Stub { } // Check for shared user signatures if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) { - if (checkSignaturesLP(pkgSetting.sharedUser.signatures.mSignatures, + if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures, pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) { Slog.e(TAG, "Package " + pkg.packageName + " has no signatures that match those in shared user " @@ -2837,7 +2885,7 @@ class PackageManagerService extends IPackageManager.Stub { return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED; } - private boolean verifyPackageUpdate(PackageSetting oldPkg, PackageParser.Package newPkg) { + private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, PackageParser.Package newPkg) { if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { Slog.w(TAG, "Unable to update from " + oldPkg.name + " to " + newPkg.packageName @@ -2852,11 +2900,15 @@ class PackageManagerService extends IPackageManager.Stub { return true; } - private File getDataPathForPackage(PackageParser.Package pkg) { - final File dataPath = new File(mAppDataDir, pkg.packageName); - return dataPath; + File getDataPathForUser(int userId) { + return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId); } - + + private File getDataPathForPackage(String packageName, int userId) { + return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId + + File.separator + packageName); + } + private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags, int scanMode, long currentTime) { File scanFile = new File(pkg.mScanPath); @@ -2911,8 +2963,11 @@ class PackageManagerService extends IPackageManager.Stub { } } - if ((parseFlags&PackageParser.PARSE_CHATTY) != 0 && Config.LOGD) Log.d( - TAG, "Scanning package " + pkg.packageName); + if (DEBUG_PACKAGE_SCANNING) { + if ((parseFlags & PackageParser.PARSE_CHATTY) != 0) + Log.d(TAG, "Scanning package " + pkg.packageName); + } + if (mPackages.containsKey(pkg.packageName) || mSharedLibraries.containsKey(pkg.packageName)) { Slog.w(TAG, "Application package " + pkg.packageName @@ -2935,6 +2990,7 @@ class PackageManagerService extends IPackageManager.Stub { pkg.mAdoptPermissions = null; } + // writer synchronized (mPackages) { // Check all shared libraries and map to their actual file path. if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) { @@ -2945,7 +3001,7 @@ class PackageManagerService extends IPackageManager.Stub { 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)); + final String file = mSharedLibraries.get(pkg.usesLibraries.get(i)); if (file == null) { Slog.e(TAG, "Package " + pkg.packageName + " requires unavailable shared library " @@ -2958,7 +3014,7 @@ class PackageManagerService extends IPackageManager.Stub { } N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0; for (int i=0; i<N; i++) { - String file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i)); + final String file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i)); if (file == null) { Slog.w(TAG, "Package " + pkg.packageName + " desires unavailable shared library " @@ -2976,7 +3032,7 @@ class PackageManagerService extends IPackageManager.Stub { } if (pkg.mSharedUserId != null) { - suid = mSettings.getSharedUserLP(pkg.mSharedUserId, + suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, pkg.applicationInfo.flags, true); if (suid == null) { Slog.w(TAG, "Creating application package " + pkg.packageName @@ -2984,18 +3040,10 @@ class PackageManagerService extends IPackageManager.Stub { mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; return null; } - if ((parseFlags&PackageParser.PARSE_CHATTY) != 0 && Config.LOGD) { - Log.d(TAG, "Shared UserID " + pkg.mSharedUserId + " (uid=" - + suid.userId + "): packages=" + suid.packages); - } - } - - if (false) { - if (pkg.mOriginalPackages != null) { - Log.w(TAG, "WAITING FOR DEBUGGER"); - Debug.waitForDebugger(); - Log.i(TAG, "Package " + pkg.packageName + " from original packages" - + pkg.mOriginalPackages); + if (DEBUG_PACKAGE_SCANNING) { + if ((parseFlags & PackageParser.PARSE_CHATTY) != 0) + Log.d(TAG, "Shared UserID " + pkg.mSharedUserId + " (uid=" + suid.userId + + "): packages=" + suid.packages); } } @@ -3005,7 +3053,7 @@ class PackageManagerService extends IPackageManager.Stub { if (pkg.mOriginalPackages != null) { // This package may need to be renamed to a previously // installed name. Let's check on that... - String renamed = mSettings.mRenamedPackages.get(pkg.mRealPackage); + final String renamed = mSettings.mRenamedPackages.get(pkg.mRealPackage); if (pkg.mOriginalPackages.contains(renamed)) { // This package had originally been installed as the // original name, and we have already taken care of @@ -3021,11 +3069,11 @@ class PackageManagerService extends IPackageManager.Stub { } else { for (int i=pkg.mOriginalPackages.size()-1; i>=0; i--) { - if ((origPackage=mSettings.peekPackageLP( + if ((origPackage = mSettings.peekPackageLPr( pkg.mOriginalPackages.get(i))) != null) { // We do have the package already installed under its // original name... should we use it? - if (!verifyPackageUpdate(origPackage, pkg)) { + if (!verifyPackageUpdateLPr(origPackage, pkg)) { // New package is not compatible with original. origPackage = null; continue; @@ -3056,7 +3104,7 @@ class PackageManagerService extends IPackageManager.Stub { // Just create the setting, don't add it yet. For already existing packages // the PkgSetting exists already and doesn't have to be created. - pkgSetting = mSettings.getPackageLP(pkg, origPackage, realName, suid, destCodeFile, + pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile, destResourceFile, pkg.applicationInfo.nativeLibraryDir, pkg.applicationInfo.flags, true, false); if (pkgSetting == null) { @@ -3109,7 +3157,7 @@ class PackageManagerService extends IPackageManager.Stub { // associated with an overall shared user, which doesn't seem all // that unreasonable. if (pkgSetting.sharedUser != null) { - if (checkSignaturesLP(pkgSetting.sharedUser.signatures.mSignatures, + if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures, pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) { Log.w(TAG, "Signature mismatch for shared user : " + pkgSetting.sharedUser); mLastScanError = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; @@ -3127,7 +3175,7 @@ class PackageManagerService extends IPackageManager.Stub { // package isn't already installed, since we don't want to break // things that are installed. if ((scanMode&SCAN_NEW_INSTALL) != 0) { - int N = pkg.providers.size(); + final int N = pkg.providers.size(); int i; for (i=0; i<N; i++) { PackageParser.Provider p = pkg.providers.get(i); @@ -3148,29 +3196,28 @@ class PackageManagerService extends IPackageManager.Stub { } } } - } - final String pkgName = pkg.packageName; - - if (pkg.mAdoptPermissions != null) { - // This package wants to adopt ownership of permissions from - // another package. - for (int i=pkg.mAdoptPermissions.size()-1; i>=0; i--) { - String origName = pkg.mAdoptPermissions.get(i); - PackageSetting orig = mSettings.peekPackageLP(origName); - if (orig != null) { - if (verifyPackageUpdate(orig, pkg)) { - Slog.i(TAG, "Adopting permissions from " - + origName + " to " + pkg.packageName); - mSettings.transferPermissions(origName, pkg.packageName); + if (pkg.mAdoptPermissions != null) { + // This package wants to adopt ownership of permissions from + // another package. + for (int i = pkg.mAdoptPermissions.size() - 1; i >= 0; i--) { + final String origName = pkg.mAdoptPermissions.get(i); + final PackageSetting orig = mSettings.peekPackageLPr(origName); + if (orig != null) { + if (verifyPackageUpdateLPr(orig, pkg)) { + Slog.i(TAG, "Adopting permissions from " + origName + " to " + + pkg.packageName); + mSettings.transferPermissionsLPw(origName, pkg.packageName); + } } } } } + + final String pkgName = pkg.packageName; final long scanFileTime = scanFile.lastModified(); final boolean forceDex = (scanMode&SCAN_FORCE_DEX) != 0; - final boolean scanFileNewer = forceDex || scanFileTime != pkgSetting.timeStamp; pkg.applicationInfo.processName = fixProcessName( pkg.applicationInfo.packageName, pkg.applicationInfo.processName, @@ -3183,7 +3230,7 @@ class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.dataDir = dataPath.getPath(); } else { // This is a normal package, need to make its data directory. - dataPath = getDataPathForPackage(pkg); + dataPath = getDataPathForPackage(pkg.packageName, 0); boolean uidError = false; @@ -3199,8 +3246,11 @@ class PackageManagerService extends IPackageManager.Stub { // If this is a system app, we can at least delete its // current data so the application will still work. if (mInstaller != null) { - int ret = mInstaller.remove(pkgName); + int ret = mInstaller.remove(pkgName, 0); if (ret >= 0) { + // TODO: Kill the processes first + // Remove the data directories for all users + mUserManager.removePackageForAllUsers(pkgName); // Old data gone! String msg = "System package " + pkg.packageName + " has changed from uid: " @@ -3220,6 +3270,9 @@ class PackageManagerService extends IPackageManager.Stub { mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; return null; } + // Create data directories for all users + mUserManager.installPackageForAllUsers(pkgName, + pkg.applicationInfo.uid); } } if (!recovered) { @@ -3235,6 +3288,7 @@ class PackageManagerService extends IPackageManager.Stub { + " has mismatched uid: " + mOutPermissions[1] + " on disk, " + pkg.applicationInfo.uid + " in settings"; + // writer synchronized (mPackages) { mSettings.mReadMessages.append(msg); mSettings.mReadMessages.append('\n'); @@ -3247,17 +3301,21 @@ class PackageManagerService extends IPackageManager.Stub { } pkg.applicationInfo.dataDir = dataPath.getPath(); } else { - if ((parseFlags&PackageParser.PARSE_CHATTY) != 0 && Config.LOGV) - Log.v(TAG, "Want this data dir: " + dataPath); + if (DEBUG_PACKAGE_SCANNING) { + if ((parseFlags & PackageParser.PARSE_CHATTY) != 0) + Log.v(TAG, "Want this data dir: " + dataPath); + } //invoke installer to do the actual installation if (mInstaller != null) { int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, pkg.applicationInfo.uid); - if(ret < 0) { + if (ret < 0) { // Error from installer mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; return null; } + // Create data directories for all users + mUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid); } else { dataPath.mkdirs(); if (dataPath.exists()) { @@ -3311,36 +3369,42 @@ class PackageManagerService extends IPackageManager.Stub { * only for non-system apps and system app upgrades. */ if (pkg.applicationInfo.nativeLibraryDir != null) { - final File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir); - final String dataPathString = dataPath.getPath(); + try { + final File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir); + final String dataPathString = dataPath.getCanonicalFile().getPath(); - if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) { - /* - * Upgrading from a previous version of the OS sometimes - * leaves native libraries in the /data/data/<app>/lib - * directory for system apps even when they shouldn't be. - * Recent changes in the JNI library search path - * necessitates we remove those to match previous behavior. - */ - if (NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryDir)) { - Log.i(TAG, "removed obsolete native libraries for system package " + path); + if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) { + /* + * Upgrading from a previous version of the OS sometimes + * leaves native libraries in the /data/data/<app>/lib + * directory for system apps even when they shouldn't be. + * Recent changes in the JNI library search path + * necessitates we remove those to match previous behavior. + */ + if (NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryDir)) { + Log.i(TAG, "removed obsolete native libraries for system package " + + path); + } + } else if (nativeLibraryDir.getCanonicalFile().getParent() + .equals(dataPathString)) { + /* + * If this is an internal application or our + * nativeLibraryPath points to our data directory, unpack + * the libraries. The native library path pointing to the + * data directory for an application in an ASEC container + * can happen for older apps that existed before an OTA to + * Gingerbread. + */ + Slog.i(TAG, "Unpacking native libraries for " + path); + mInstaller.unlinkNativeLibraryDirectory(dataPathString); + NativeLibraryHelper.copyNativeBinariesLI(scanFile, nativeLibraryDir); + } else { + Slog.i(TAG, "Linking native library dir for " + path); + mInstaller.linkNativeLibraryDirectory(dataPathString, + pkg.applicationInfo.nativeLibraryDir); } - } else if (nativeLibraryDir.getParent().equals(dataPathString)) { - /* - * If this is an internal application or our - * nativeLibraryPath points to our data directory, unpack - * the libraries. The native library path pointing to the - * data directory for an application in an ASEC container - * can happen for older apps that existed before an OTA to - * Gingerbread. - */ - Slog.i(TAG, "Unpacking native libraries for " + path); - mInstaller.unlinkNativeLibraryDirectory(dataPathString); - NativeLibraryHelper.copyNativeBinariesLI(scanFile, nativeLibraryDir); - } else { - Slog.i(TAG, "Linking native library dir for " + path); - mInstaller.linkNativeLibraryDirectory(dataPathString, - pkg.applicationInfo.nativeLibraryDir); + } catch (IOException ioe) { + Log.e(TAG, "Unable to get canonical file " + ioe.toString()); } } pkg.mScanPath = path; @@ -3366,13 +3430,14 @@ class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.uid); } + // writer synchronized (mPackages) { // We don't expect installation to fail beyond this point, if ((scanMode&SCAN_MONITOR) != 0) { mAppDirs.put(pkg.mPath, pkg); } // Add the new setting to mSettings - mSettings.insertPackageSettingLP(pkgSetting, pkg); + mSettings.insertPackageSettingLPw(pkgSetting, pkg); // Add the new setting to mPackages mPackages.put(pkg.applicationInfo.packageName, pkg); // Make sure we don't accidentally delete its data. @@ -3428,10 +3493,12 @@ class PackageManagerService extends IPackageManager.Stub { } else { p.info.authority = p.info.authority + ";" + names[j]; } - if ((parseFlags&PackageParser.PARSE_CHATTY) != 0 && Config.LOGD) - Log.d(TAG, "Registered content provider: " + names[j] + - ", className = " + p.info.name + - ", isSyncable = " + p.info.isSyncable); + if (DEBUG_PACKAGE_SCANNING) { + if ((parseFlags & PackageParser.PARSE_CHATTY) != 0) + Log.d(TAG, "Registered content provider: " + names[j] + + ", className = " + p.info.name + ", isSyncable = " + + p.info.isSyncable); + } } else { PackageParser.Provider other = mProviders.get(names[j]); Slog.w(TAG, "Skipping provider name " + names[j] + @@ -3452,7 +3519,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Providers: " + r); + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Providers: " + r); } N = pkg.services.size(); @@ -3472,7 +3539,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Services: " + r); + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Services: " + r); } N = pkg.receivers.size(); @@ -3492,7 +3559,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Receivers: " + r); + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Receivers: " + r); } N = pkg.activities.size(); @@ -3512,7 +3579,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Activities: " + r); + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Activities: " + r); } N = pkg.permissionGroups.size(); @@ -3546,7 +3613,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Permission Groups: " + r); + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Permission Groups: " + r); } N = pkg.permissions.size(); @@ -3611,7 +3678,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Permissions: " + r); + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Permissions: " + r); } N = pkg.instrumentation.size(); @@ -3634,7 +3701,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Instrumentation: " + r); + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Instrumentation: " + r); } if (pkg.protectedBroadcasts != null) { @@ -3663,25 +3730,15 @@ class PackageManagerService extends IPackageManager.Stub { } } - // Return the path of the directory that will contain the native binaries - // of a given installed package. This is relative to the data path. - // - private File getNativeBinaryDirForPackage(PackageParser.Package pkg) { - final String nativeLibraryDir = pkg.applicationInfo.nativeLibraryDir; - if (nativeLibraryDir != null) { - return new File(nativeLibraryDir); - } else { - // Fall back for old packages - return new File(pkg.applicationInfo.dataDir, LIB_DIR_NAME); - } - } - void removePackageLI(PackageParser.Package pkg, boolean chatty) { - if (chatty && Config.LOGD) Log.d( - TAG, "Removing package " + pkg.applicationInfo.packageName ); + if (DEBUG_INSTALL) { + if (chatty) + Log.d(TAG, "Removing package " + pkg.applicationInfo.packageName); + } + // writer synchronized (mPackages) { - clearPackagePreferredActivitiesLP(pkg.packageName); + clearPackagePreferredActivitiesLPw(pkg.packageName); mPackages.remove(pkg.applicationInfo.packageName); if (pkg.mPath != null) { @@ -3707,10 +3764,12 @@ class PackageManagerService extends IPackageManager.Stub { for (int j = 0; j < names.length; j++) { if (mProviders.get(names[j]) == p) { mProviders.remove(names[j]); - if (chatty && Config.LOGD) Log.d( - TAG, "Unregistered content provider: " + names[j] + - ", className = " + p.info.name + - ", isSyncable = " + p.info.isSyncable); + if (DEBUG_REMOVE) { + if (chatty) + Log.d(TAG, "Unregistered content provider: " + names[j] + + ", className = " + p.info.name + ", isSyncable = " + + p.info.isSyncable); + } } } if (chatty) { @@ -3723,7 +3782,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Providers: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Providers: " + r); } N = pkg.services.size(); @@ -3741,7 +3800,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Services: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Services: " + r); } N = pkg.receivers.size(); @@ -3759,7 +3818,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Receivers: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Receivers: " + r); } N = pkg.activities.size(); @@ -3777,17 +3836,15 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Activities: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Activities: " + r); } N = pkg.permissions.size(); r = null; for (i=0; i<N; i++) { PackageParser.Permission p = pkg.permissions.get(i); - boolean tree = false; BasePermission bp = mSettings.mPermissions.get(p.info.name); if (bp == null) { - tree = true; bp = mSettings.mPermissionTrees.get(p.info.name); } if (bp != null && bp.perm == p) { @@ -3803,7 +3860,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Permissions: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); } N = pkg.instrumentation.size(); @@ -3821,7 +3878,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Instrumentation: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Instrumentation: " + r); } } } @@ -3839,14 +3896,13 @@ class PackageManagerService extends IPackageManager.Stub { return false; } - private void updatePermissionsLP(String changingPkg, + private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo, boolean grantPermissions, boolean replace, boolean replaceAll) { // Make sure there are no dangling permission trees. - Iterator<BasePermission> it = mSettings.mPermissionTrees - .values().iterator(); + Iterator<BasePermission> it = mSettings.mPermissionTrees.values().iterator(); while (it.hasNext()) { - BasePermission bp = it.next(); + final BasePermission bp = it.next(); if (bp.packageSetting == null) { // We may not yet have parsed the package, so just see if // we still know about its settings. @@ -3870,13 +3926,13 @@ class PackageManagerService extends IPackageManager.Stub { // and make sure there are no dangling permissions. it = mSettings.mPermissions.values().iterator(); while (it.hasNext()) { - BasePermission bp = it.next(); + final BasePermission bp = it.next(); if (bp.type == BasePermission.TYPE_DYNAMIC) { if (DEBUG_SETTINGS) Log.v(TAG, "Dynamic permission: name=" + bp.name + " pkg=" + bp.sourcePackage + " info=" + bp.pendingInfo); if (bp.packageSetting == null && bp.pendingInfo != null) { - BasePermission tree = findPermissionTreeLP(bp.name); + final BasePermission tree = findPermissionTreeLP(bp.name); if (tree != null) { bp.packageSetting = tree.packageSetting; bp.perm = new PackageParser.Permission(tree.perm.owner, @@ -3911,18 +3967,18 @@ class PackageManagerService extends IPackageManager.Stub { if (grantPermissions) { for (PackageParser.Package pkg : mPackages.values()) { if (pkg != pkgInfo) { - grantPermissionsLP(pkg, replaceAll); + grantPermissionsLPw(pkg, replaceAll); } } } if (pkgInfo != null) { - grantPermissionsLP(pkgInfo, replace); + grantPermissionsLPw(pkgInfo, replace); } } - private void grantPermissionsLP(PackageParser.Package pkg, boolean replace) { - final PackageSetting ps = (PackageSetting)pkg.mExtras; + private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace) { + final PackageSetting ps = (PackageSetting) pkg.mExtras; if (ps == null) { return; } @@ -3943,12 +3999,11 @@ class PackageManagerService extends IPackageManager.Stub { final int N = pkg.requestedPermissions.size(); for (int i=0; i<N; i++) { - String name = pkg.requestedPermissions.get(i); - BasePermission bp = mSettings.mPermissions.get(name); - if (false) { + final String name = pkg.requestedPermissions.get(i); + final BasePermission bp = mSettings.mPermissions.get(name); + if (DEBUG_INSTALL) { if (gp != ps) { - Log.i(TAG, "Package " + pkg.packageName + " checking " + name - + ": " + bp); + Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp); } } if (bp != null && bp.packageSetting != null) { @@ -3963,10 +4018,10 @@ class PackageManagerService extends IPackageManager.Stub { allowed = false; } else if (bp.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE || bp.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) { - allowed = (checkSignaturesLP( + allowed = (compareSignatures( bp.packageSetting.signatures.mSignatures, pkg.mSignatures) == PackageManager.SIGNATURE_MATCH) - || (checkSignaturesLP(mPlatformPackage.mSignatures, pkg.mSignatures) + || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures) == PackageManager.SIGNATURE_MATCH); if (!allowed && bp.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) { @@ -3974,8 +4029,8 @@ class PackageManagerService extends IPackageManager.Stub { // For updated system applications, the signatureOrSystem permission // is granted only if it had been defined by the original application. if (isUpdatedSystemApp(pkg)) { - PackageSetting sysPs = mSettings.getDisabledSystemPkg( - pkg.packageName); + final PackageSetting sysPs = mSettings + .getDisabledSystemPkgLPr(pkg.packageName); final GrantedPermissions origGp = sysPs.sharedUser != null ? sysPs.sharedUser : sysPs; if (origGp.grantedPermissions.contains(perm)) { @@ -3994,7 +4049,7 @@ class PackageManagerService extends IPackageManager.Stub { } else { allowed = false; } - if (false) { + if (DEBUG_INSTALL) { if (gp != ps) { Log.i(TAG, "Package " + pkg.packageName + " granting " + perm); } @@ -4072,25 +4127,26 @@ class PackageManagerService extends IPackageManager.Stub { private final class ActivityIntentResolver extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> { - public List queryIntent(Intent intent, String resolvedType, boolean defaultOnly) { + public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, + boolean defaultOnly) { mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0; return super.queryIntent(intent, resolvedType, defaultOnly); } - public List queryIntent(Intent intent, String resolvedType, int flags) { + public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags) { mFlags = flags; return super.queryIntent(intent, resolvedType, (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0); } - public List queryIntentForPackage(Intent intent, String resolvedType, int flags, - ArrayList<PackageParser.Activity> packageActivities) { + public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType, + int flags, ArrayList<PackageParser.Activity> packageActivities) { if (packageActivities == null) { return null; } mFlags = flags; final boolean defaultOnly = (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0; - int N = packageActivities.size(); + final int N = packageActivities.size(); ArrayList<ArrayList<PackageParser.ActivityIntentInfo>> listCut = new ArrayList<ArrayList<PackageParser.ActivityIntentInfo>>(N); @@ -4107,11 +4163,13 @@ class PackageManagerService extends IPackageManager.Stub { public final void addActivity(PackageParser.Activity a, String type) { final boolean systemApp = isSystemApp(a.info.applicationInfo); mActivities.put(a.getComponentName(), a); - if (SHOW_INFO || Config.LOGV) Log.v( + if (DEBUG_SHOW_INFO) + Log.v( TAG, " " + type + " " + (a.info.nonLocalizedLabel != null ? a.info.nonLocalizedLabel : a.info.name) + ":"); - if (SHOW_INFO || Config.LOGV) Log.v(TAG, " Class=" + a.info.name); - int NI = a.intents.size(); + if (DEBUG_SHOW_INFO) + Log.v(TAG, " Class=" + a.info.name); + final int NI = a.intents.size(); for (int j=0; j<NI; j++) { PackageParser.ActivityIntentInfo intent = a.intents.get(j); if (!systemApp && intent.getPriority() > 0 && "activity".equals(type)) { @@ -4119,7 +4177,7 @@ class PackageManagerService extends IPackageManager.Stub { Log.w(TAG, "Package " + a.info.applicationInfo.packageName + " has activity " + a.className + " with priority > 0, forcing to 0"); } - if (SHOW_INFO || Config.LOGV) { + if (DEBUG_SHOW_INFO) { Log.v(TAG, " IntentFilter:"); intent.dump(new LogPrinter(Log.VERBOSE, TAG), " "); } @@ -4132,14 +4190,16 @@ class PackageManagerService extends IPackageManager.Stub { public final void removeActivity(PackageParser.Activity a, String type) { mActivities.remove(a.getComponentName()); - if (SHOW_INFO || Config.LOGV) Log.v( - TAG, " " + type + " " + - (a.info.nonLocalizedLabel != null ? a.info.nonLocalizedLabel : a.info.name) + ":"); - if (SHOW_INFO || Config.LOGV) Log.v(TAG, " Class=" + a.info.name); - int NI = a.intents.size(); + if (DEBUG_SHOW_INFO) { + Log.v(TAG, " " + type + " " + + (a.info.nonLocalizedLabel != null ? a.info.nonLocalizedLabel + : a.info.name) + ":"); + Log.v(TAG, " Class=" + a.info.name); + } + final int NI = a.intents.size(); for (int j=0; j<NI; j++) { PackageParser.ActivityIntentInfo intent = a.intents.get(j); - if (SHOW_INFO || Config.LOGV) { + if (DEBUG_SHOW_INFO) { Log.v(TAG, " IntentFilter:"); intent.dump(new LogPrinter(Log.VERBOSE, TAG), " "); } @@ -4181,7 +4241,7 @@ class PackageManagerService extends IPackageManager.Stub { @Override protected ResolveInfo newResult(PackageParser.ActivityIntentInfo info, int match) { - if (!mSettings.isEnabledLP(info.activity.info, mFlags)) { + if (!mSettings.isEnabledLPr(info.activity.info, mFlags)) { return null; } final PackageParser.Activity activity = info.activity; @@ -4243,25 +4303,26 @@ class PackageManagerService extends IPackageManager.Stub { private final class ServiceIntentResolver extends IntentResolver<PackageParser.ServiceIntentInfo, ResolveInfo> { - public List queryIntent(Intent intent, String resolvedType, boolean defaultOnly) { + public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, + boolean defaultOnly) { mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0; return super.queryIntent(intent, resolvedType, defaultOnly); } - public List queryIntent(Intent intent, String resolvedType, int flags) { + public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags) { mFlags = flags; return super.queryIntent(intent, resolvedType, (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0); } - public List queryIntentForPackage(Intent intent, String resolvedType, int flags, - ArrayList<PackageParser.Service> packageServices) { + public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType, + int flags, ArrayList<PackageParser.Service> packageServices) { if (packageServices == null) { return null; } mFlags = flags; final boolean defaultOnly = (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0; - int N = packageServices.size(); + final int N = packageServices.size(); ArrayList<ArrayList<PackageParser.ServiceIntentInfo>> listCut = new ArrayList<ArrayList<PackageParser.ServiceIntentInfo>>(N); @@ -4277,16 +4338,17 @@ class PackageManagerService extends IPackageManager.Stub { public final void addService(PackageParser.Service s) { mServices.put(s.getComponentName(), s); - if (SHOW_INFO || Config.LOGV) Log.v( - TAG, " " + (s.info.nonLocalizedLabel != null + if (DEBUG_SHOW_INFO) { + Log.v(TAG, " " + + (s.info.nonLocalizedLabel != null ? s.info.nonLocalizedLabel : s.info.name) + ":"); - if (SHOW_INFO || Config.LOGV) Log.v( - TAG, " Class=" + s.info.name); - int NI = s.intents.size(); + Log.v(TAG, " Class=" + s.info.name); + } + final int NI = s.intents.size(); int j; for (j=0; j<NI; j++) { PackageParser.ServiceIntentInfo intent = s.intents.get(j); - if (SHOW_INFO || Config.LOGV) { + if (DEBUG_SHOW_INFO) { Log.v(TAG, " IntentFilter:"); intent.dump(new LogPrinter(Log.VERBOSE, TAG), " "); } @@ -4299,16 +4361,16 @@ class PackageManagerService extends IPackageManager.Stub { public final void removeService(PackageParser.Service s) { mServices.remove(s.getComponentName()); - if (SHOW_INFO || Config.LOGV) Log.v( - TAG, " " + (s.info.nonLocalizedLabel != null + if (DEBUG_SHOW_INFO) { + Log.v(TAG, " " + (s.info.nonLocalizedLabel != null ? s.info.nonLocalizedLabel : s.info.name) + ":"); - if (SHOW_INFO || Config.LOGV) Log.v( - TAG, " Class=" + s.info.name); - int NI = s.intents.size(); + Log.v(TAG, " Class=" + s.info.name); + } + final int NI = s.intents.size(); int j; for (j=0; j<NI; j++) { PackageParser.ServiceIntentInfo intent = s.intents.get(j); - if (SHOW_INFO || Config.LOGV) { + if (DEBUG_SHOW_INFO) { Log.v(TAG, " IntentFilter:"); intent.dump(new LogPrinter(Log.VERBOSE, TAG), " "); } @@ -4351,7 +4413,7 @@ class PackageManagerService extends IPackageManager.Stub { protected ResolveInfo newResult(PackageParser.ServiceIntentInfo filter, int match) { final PackageParser.ServiceIntentInfo info = (PackageParser.ServiceIntentInfo)filter; - if (!mSettings.isEnabledLP(info.service.info, mFlags)) { + if (!mSettings.isEnabledLPr(info.service.info, mFlags)) { return null; } final PackageParser.Service service = info.service; @@ -4444,7 +4506,7 @@ class PackageManagerService extends IPackageManager.Stub { } }; - private static final void sendPackageBroadcast(String action, String pkg, + static final void sendPackageBroadcast(String action, String pkg, Bundle extras, String targetPkg, IIntentReceiver finishedReceiver) { IActivityManager am = ActivityManagerNative.getDefault(); if (am != null) { @@ -4475,6 +4537,7 @@ class PackageManagerService extends IPackageManager.Stub { } public String nextPackageToClean(String lastPackage) { + // writer synchronized (mPackages) { if (!isExternalMediaAvailable()) { // If the external storage is no longer mounted at this point, @@ -4495,6 +4558,7 @@ class PackageManagerService extends IPackageManager.Stub { } void startCleaningPackages() { + // reader synchronized (mPackages) { if (!isExternalMediaAvailable()) { return; @@ -4527,6 +4591,7 @@ class PackageManagerService extends IPackageManager.Stub { String addedPackage = null; int addedUid = -1; + // TODO post a message to the handler to obtain serial ordering synchronized (mInstallLock) { String fullPathStr = null; File fullPath = null; @@ -4535,13 +4600,12 @@ class PackageManagerService extends IPackageManager.Stub { fullPathStr = fullPath.getPath(); } - if (Config.LOGV) Log.v( - TAG, "File " + fullPathStr + " changed: " - + Integer.toHexString(event)); + if (DEBUG_APP_DIR_OBSERVER) + Log.v(TAG, "File " + fullPathStr + " changed: " + Integer.toHexString(event)); if (!isPackageFilename(path)) { - if (Config.LOGV) Log.v( - TAG, "Ignoring change of non-package file: " + fullPathStr); + if (DEBUG_APP_DIR_OBSERVER) + Log.v(TAG, "Ignoring change of non-package file: " + fullPathStr); return; } @@ -4551,6 +4615,7 @@ class PackageManagerService extends IPackageManager.Stub { return; } PackageParser.Package p = null; + // reader synchronized (mPackages) { p = mAppDirs.get(fullPathStr); } @@ -4572,8 +4637,14 @@ class PackageManagerService extends IPackageManager.Stub { SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME, System.currentTimeMillis()); if (p != null) { + /* + * TODO this seems dangerous as the package may have + * changed since we last acquired the mPackages + * lock. + */ + // writer synchronized (mPackages) { - updatePermissionsLP(p.packageName, p, + updatePermissionsLPw(p.packageName, p, p.permissions.size() > 0, false, false); } addedPackage = p.applicationInfo.packageName; @@ -4582,8 +4653,9 @@ class PackageManagerService extends IPackageManager.Stub { } } + // reader synchronized (mPackages) { - mSettings.writeLP(); + mSettings.writeLPr(); } } @@ -4631,13 +4703,9 @@ class PackageManagerService extends IPackageManager.Stub { mHandler.sendMessage(msg); } - public void setInstallerPackageName(String targetPackage, - String installerPackageName) { - PackageSetting pkgSetting; + public void setInstallerPackageName(String targetPackage, String installerPackageName) { final int uid = Binder.getCallingUid(); - final int permission = mContext.checkCallingPermission( - android.Manifest.permission.INSTALL_PACKAGES); - final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); + // writer synchronized (mPackages) { PackageSetting targetPackageSetting = mSettings.mPackages.get(targetPackage); if (targetPackageSetting == null) { @@ -4656,7 +4724,7 @@ class PackageManagerService extends IPackageManager.Stub { } Signature[] callerSignature; - Object obj = mSettings.getUserIdLP(uid); + Object obj = mSettings.getUserIdLPr(uid); if (obj != null) { if (obj instanceof SharedUserSetting) { callerSignature = ((SharedUserSetting)obj).signatures.mSignatures; @@ -4672,7 +4740,7 @@ class PackageManagerService extends IPackageManager.Stub { // Verify: can't set installerPackageName to a package that is // not signed with the same cert as the caller. if (installerPackageSetting != null) { - if (checkSignaturesLP(callerSignature, + if (compareSignatures(callerSignature, installerPackageSetting.signatures.mSignatures) != PackageManager.SIGNATURE_MATCH) { throw new SecurityException( @@ -4689,7 +4757,7 @@ class PackageManagerService extends IPackageManager.Stub { // If the currently set package isn't valid, then it's always // okay to change it. if (setting != null) { - if (checkSignaturesLP(callerSignature, + if (compareSignatures(callerSignature, setting.signatures.mSignatures) != PackageManager.SIGNATURE_MATCH) { throw new SecurityException( @@ -4905,6 +4973,7 @@ class PackageManagerService extends IPackageManager.Stub { String packageName = pkgLite.packageName; int installLocation = pkgLite.installLocation; boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0; + // reader synchronized (mPackages) { PackageParser.Package pkg = mPackages.get(packageName); if (pkg != null) { @@ -4969,12 +5038,24 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Cannot install fwd locked apps on sdcard"); ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } else { + final long lowThreshold; + + final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager + .getService(DeviceStorageMonitorService.SERVICE); + if (dsm == null) { + Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed"); + lowThreshold = 0L; + } else { + lowThreshold = dsm.getMemoryLowThreshold(); + } + // Remote call to find out default install location final PackageInfoLite pkgLite; try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); - pkgLite = mContainerService.getMinimalPackageInfo(packageURI, flags); + pkgLite = mContainerService.getMinimalPackageInfo(packageURI, flags, + lowThreshold); } finally { mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } @@ -5194,10 +5275,26 @@ class PackageManagerService extends IPackageManager.Stub { } boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { + final long lowThreshold; + + final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager + .getService(DeviceStorageMonitorService.SERVICE); + if (dsm == null) { + Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed"); + lowThreshold = 0L; + } else { + if (dsm.isMemoryLow()) { + Log.w(TAG, "Memory is reported as being too low; aborting package install"); + return false; + } + + lowThreshold = dsm.getMemoryLowThreshold(); + } + try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); - return imcs.checkFreeStorage(false, packageURI); + return imcs.checkInternalFreeStorage(packageURI, lowThreshold); } finally { mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } @@ -5423,7 +5520,7 @@ class PackageManagerService extends IPackageManager.Stub { try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); - return imcs.checkFreeStorage(true, packageURI); + return imcs.checkExternalFreeStorage(packageURI); } finally { mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } @@ -5688,7 +5785,7 @@ class PackageManagerService extends IPackageManager.Stub { // Remember this for later, in case we need to rollback this install String pkgName = pkg.packageName; - boolean dataDirExists = getDataPathForPackage(pkg).exists(); + boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists(); res.name = pkgName; synchronized(mPackages) { if (mSettings.mRenamedPackages.containsKey(pkgName)) { @@ -5747,7 +5844,7 @@ class PackageManagerService extends IPackageManager.Stub { // First find the old package info and check signatures synchronized(mPackages) { oldPackage = mPackages.get(pkgName); - if (checkSignaturesLP(oldPackage.mSignatures, pkg.mSignatures) + if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) { res.returnCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; return; @@ -5770,11 +5867,6 @@ class PackageManagerService extends IPackageManager.Stub { boolean deletedPkg = true; boolean updatedSettings = false; - String oldInstallerPackageName = null; - synchronized (mPackages) { - oldInstallerPackageName = mSettings.getInstallerPackageName(pkgName); - } - long origUpdateTime; if (pkg.mExtras != null) { origUpdateTime = ((PackageSetting)pkg.mExtras).lastUpdateTime; @@ -5838,10 +5930,12 @@ class PackageManagerService extends IPackageManager.Stub { return; } // Restore of old package succeeded. Update permissions. + // writer synchronized (mPackages) { - updatePermissionsLP(deletedPackage.packageName, deletedPackage, + updatePermissionsLPw(deletedPackage.packageName, deletedPackage, true, false, false); - mSettings.writeLP(); + // can downgrade to reader + mSettings.writeLPr(); } Slog.i(TAG, "Successfully restored package : " + pkgName + " after failed upgrade"); } @@ -5864,6 +5958,7 @@ class PackageManagerService extends IPackageManager.Stub { } PackageParser.Package oldPkg; PackageSetting oldPkgSetting; + // reader synchronized (mPackages) { oldPkg = mPackages.get(packageName); oldPkgSetting = mSettings.mPackages.get(packageName); @@ -5880,8 +5975,9 @@ class PackageManagerService extends IPackageManager.Stub { res.removedInfo.removedPackage = packageName; // Remove existing system package removePackageLI(oldPkg, true); + // writer synchronized (mPackages) { - if (!mSettings.disableSystemPackageLP(packageName) && deletedPackage != null) { + if (!mSettings.disableSystemPackageLPw(packageName) && deletedPackage != null) { // We didn't need to disable the .apk as a current system package, // which means we are replacing another update that is already // installed. We need to make sure to delete the older one's .apk. @@ -5925,11 +6021,11 @@ class PackageManagerService extends IPackageManager.Stub { // Restore the old system information in Settings synchronized(mPackages) { if (updatedSettings) { - mSettings.enableSystemPackageLP(packageName); + mSettings.enableSystemPackageLPw(packageName); mSettings.setInstallerPackageName(packageName, oldPkgSetting.installerPackageName); } - mSettings.writeLP(); + mSettings.writeLPr(); } } } @@ -5963,8 +6059,8 @@ class PackageManagerService extends IPackageManager.Stub { //write settings. the installStatus will be incomplete at this stage. //note that the new package setting would have already been //added to mPackages. It hasn't been persisted yet. - mSettings.setInstallStatus(pkgName, PKG_INSTALL_INCOMPLETE); - mSettings.writeLP(); + mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_INCOMPLETE); + mSettings.writeLPr(); } if ((res.returnCode = moveDexFilesLI(newPackage)) @@ -5982,16 +6078,16 @@ class PackageManagerService extends IPackageManager.Stub { Log.d(TAG, "New package installed in " + newPackage.mPath); } synchronized (mPackages) { - updatePermissionsLP(newPackage.packageName, newPackage, + updatePermissionsLPw(newPackage.packageName, newPackage, newPackage.permissions.size() > 0, true, false); res.name = pkgName; res.uid = newPackage.applicationInfo.uid; res.pkg = newPackage; - mSettings.setInstallStatus(pkgName, PKG_INSTALL_COMPLETE); + mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_COMPLETE); mSettings.setInstallerPackageName(pkgName, installerPackageName); res.returnCode = PackageManager.INSTALL_SUCCEEDED; //to update install status - mSettings.writeLP(); + mSettings.writeLPr(); } } @@ -6090,7 +6186,6 @@ class PackageManagerService extends IPackageManager.Stub { } private int setPermissionsLI(PackageParser.Package newPackage) { - String pkgName = newPackage.packageName; int retCode = 0; // TODO Gross hack but fix later. Ideally move this to be a post installation // check after alloting uid. @@ -6120,9 +6215,8 @@ class PackageManagerService extends IPackageManager.Stub { } if (retCode != 0) { - Slog.e(TAG, "Couldn't set new package file permissions for " + - newPackage.mPath - + ". The return code was: " + retCode); + Slog.e(TAG, "Couldn't set new package file permissions for " + newPackage.mPath + + ". The return code was: " + retCode); // TODO Define new internal error return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; } @@ -6371,37 +6465,46 @@ class PackageManagerService extends IPackageManager.Stub { } removePackageLI(p, (flags&REMOVE_CHATTY) != 0); // Retrieve object to delete permissions for shared user later on - PackageSetting deletedPs; + final PackageSetting deletedPs; + // reader synchronized (mPackages) { deletedPs = mSettings.mPackages.get(packageName); } if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { if (mInstaller != null) { - int retCode = mInstaller.remove(packageName); + int retCode = mInstaller.remove(packageName, 0); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data or cache directory for package: " + packageName + ", retcode=" + retCode); // we don't consider this to be a failure of the core package deletion + } else { + // TODO: Kill the processes first + mUserManager.removePackageForAllUsers(packageName); } } else { // for simulator - PackageParser.Package pkg = mPackages.get(packageName); - File dataDir = new File(pkg.applicationInfo.dataDir); + File dataDir; + // reader + synchronized (mPackages) { + PackageParser.Package pkg = mPackages.get(packageName); + dataDir = new File(pkg.applicationInfo.dataDir); + } dataDir.delete(); } schedulePackageCleaning(packageName); } + // writer synchronized (mPackages) { if (deletedPs != null) { if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { if (outInfo != null) { - outInfo.removedUid = mSettings.removePackageLP(packageName); + outInfo.removedUid = mSettings.removePackageLPw(packageName); } if (deletedPs != null) { - updatePermissionsLP(deletedPs.name, null, false, false, false); + updatePermissionsLPw(deletedPs.name, null, false, false, false); if (deletedPs.sharedUser != null) { // remove permissions associated with package - mSettings.updateSharedUserPermsLP(deletedPs, mGlobalGids); + mSettings.updateSharedUserPermsLPw(deletedPs, mGlobalGids); } } } @@ -6416,9 +6519,10 @@ class PackageManagerService extends IPackageManager.Stub { mSettings.mPreferredActivities.removeFilter(pa); } } + // can downgrade to reader if (writeSettings) { // Save settings now - mSettings.writeLP(); + mSettings.writeLPr(); } } } @@ -6438,8 +6542,9 @@ class PackageManagerService extends IPackageManager.Stub { // Confirm if the system package has been updated // An updated system app can be deleted. This will also have to restore // the system pkg from system partition + // reader synchronized (mPackages) { - ps = mSettings.getDisabledSystemPkg(p.packageName); + ps = mSettings.getDisabledSystemPkgLPr(p.packageName); } if (ps == null) { Slog.w(TAG, "Attempt to delete unknown system package "+ p.packageName); @@ -6461,9 +6566,10 @@ class PackageManagerService extends IPackageManager.Stub { if (!ret) { return false; } + // writer synchronized (mPackages) { // Reinstate the old system package - mSettings.enableSystemPackageLP(p.packageName); + mSettings.enableSystemPackageLPw(p.packageName); // Remove any native libraries from the upgraded package. NativeLibraryHelper.removeNativeBinariesLI(p.applicationInfo.nativeLibraryDir); } @@ -6476,10 +6582,12 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Failed to restore system package:"+p.packageName+" with error:" + mLastScanError); return false; } + // writer synchronized (mPackages) { - updatePermissionsLP(newPkg.packageName, newPkg, true, true, false); + updatePermissionsLPw(newPkg.packageName, newPkg, true, true, false); + // can downgrade to reader here if (writeSettings) { - mSettings.writeLP(); + mSettings.writeLPr(); } } return true; @@ -6631,7 +6739,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (mInstaller != null) { - int retCode = mInstaller.clearUserData(packageName); + int retCode = mInstaller.clearUserData(packageName, 0); // TODO - correct userId if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " + packageName); @@ -6767,16 +6875,14 @@ class PackageManagerService extends IPackageManager.Stub { return new ArrayList<PackageInfo>(); } - int getUidTargetSdkVersionLockedLP(int uid) { - Object obj = mSettings.getUserIdLP(uid); + private int getUidTargetSdkVersionLockedLPr(int uid) { + Object obj = mSettings.getUserIdLPr(uid); if (obj instanceof SharedUserSetting) { - SharedUserSetting sus = (SharedUserSetting)obj; - final int N = sus.packages.size(); + final SharedUserSetting sus = (SharedUserSetting) obj; int vers = Build.VERSION_CODES.CUR_DEVELOPMENT; - Iterator<PackageSetting> it = sus.packages.iterator(); - int i=0; + final Iterator<PackageSetting> it = sus.packages.iterator(); while (it.hasNext()) { - PackageSetting ps = it.next(); + final PackageSetting ps = it.next(); if (ps.pkg != null) { int v = ps.pkg.applicationInfo.targetSdkVersion; if (v < vers) vers = v; @@ -6784,7 +6890,7 @@ class PackageManagerService extends IPackageManager.Stub { } return vers; } else if (obj instanceof PackageSetting) { - PackageSetting ps = (PackageSetting)obj; + final PackageSetting ps = (PackageSetting) obj; if (ps.pkg != null) { return ps.pkg.applicationInfo.targetSdkVersion; } @@ -6794,11 +6900,12 @@ class PackageManagerService extends IPackageManager.Stub { public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) { + // writer synchronized (mPackages) { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.SET_PREFERRED_APPLICATIONS) != PackageManager.PERMISSION_GRANTED) { - if (getUidTargetSdkVersionLockedLP(Binder.getCallingUid()) + if (getUidTargetSdkVersionLockedLPr(Binder.getCallingUid()) < Build.VERSION_CODES.FROYO) { Slog.w(TAG, "Ignoring addPreferredActivity() from uid " + Binder.getCallingUid()); @@ -6838,7 +6945,7 @@ class PackageManagerService extends IPackageManager.Stub { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.SET_PREFERRED_APPLICATIONS) != PackageManager.PERMISSION_GRANTED) { - if (getUidTargetSdkVersionLockedLP(Binder.getCallingUid()) + if (getUidTargetSdkVersionLockedLPr(Binder.getCallingUid()) < Build.VERSION_CODES.FROYO) { Slog.w(TAG, "Ignoring replacePreferredActivity() from uid " + Binder.getCallingUid()); @@ -6864,14 +6971,15 @@ class PackageManagerService extends IPackageManager.Stub { } public void clearPackagePreferredActivities(String packageName) { + final int uid = Binder.getCallingUid(); + // writer synchronized (mPackages) { - int uid = Binder.getCallingUid(); PackageParser.Package pkg = mPackages.get(packageName); if (pkg == null || pkg.applicationInfo.uid != uid) { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.SET_PREFERRED_APPLICATIONS) != PackageManager.PERMISSION_GRANTED) { - if (getUidTargetSdkVersionLockedLP(Binder.getCallingUid()) + if (getUidTargetSdkVersionLockedLPr(Binder.getCallingUid()) < Build.VERSION_CODES.FROYO) { Slog.w(TAG, "Ignoring clearPackagePreferredActivities() from uid " + Binder.getCallingUid()); @@ -6882,13 +6990,13 @@ class PackageManagerService extends IPackageManager.Stub { } } - if (clearPackagePreferredActivitiesLP(packageName)) { + if (clearPackagePreferredActivitiesLPw(packageName)) { scheduleWriteSettingsLocked(); } } } - boolean clearPackagePreferredActivitiesLP(String packageName) { + boolean clearPackagePreferredActivitiesLPw(String packageName) { boolean changed = false; Iterator<PreferredActivity> it = mSettings.mPreferredActivities.filterIterator(); while (it.hasNext()) { @@ -6905,10 +7013,11 @@ class PackageManagerService extends IPackageManager.Stub { List<ComponentName> outActivities, String packageName) { int num = 0; + // reader synchronized (mPackages) { - Iterator<PreferredActivity> it = mSettings.mPreferredActivities.filterIterator(); + final Iterator<PreferredActivity> it = mSettings.mPreferredActivities.filterIterator(); while (it.hasNext()) { - PreferredActivity pa = it.next(); + final PreferredActivity pa = it.next(); if (packageName == null || pa.mPref.mComponent.getPackageName().equals(packageName)) { if (outFilters != null) { @@ -6953,6 +7062,8 @@ class PackageManagerService extends IPackageManager.Stub { String componentName = isApp ? packageName : className; int packageUid = -1; ArrayList<String> components; + + // writer synchronized (mPackages) { pkgSetting = mSettings.mPackages.get(packageName); if (pkgSetting == null) { @@ -6982,17 +7093,17 @@ class PackageManagerService extends IPackageManager.Stub { // We're dealing with a component level state change switch (newState) { case COMPONENT_ENABLED_STATE_ENABLED: - if (!pkgSetting.enableComponentLP(className)) { + if (!pkgSetting.enableComponentLPw(className)) { return; } break; case COMPONENT_ENABLED_STATE_DISABLED: - if (!pkgSetting.disableComponentLP(className)) { + if (!pkgSetting.disableComponentLPw(className)) { return; } break; case COMPONENT_ENABLED_STATE_DEFAULT: - if (!pkgSetting.restoreComponentLP(className)) { + if (!pkgSetting.restoreComponentLPw(className)) { return; } break; @@ -7001,10 +7112,10 @@ class PackageManagerService extends IPackageManager.Stub { return; } } - mSettings.writeLP(); + mSettings.writeLPr(); packageUid = pkgSetting.userId; components = mPendingBroadcasts.get(packageName); - boolean newPackage = components == null; + final boolean newPackage = components == null; if (newPackage) { components = new ArrayList<String>(); } @@ -7040,8 +7151,9 @@ class PackageManagerService extends IPackageManager.Stub { private void sendPackageChangedBroadcast(String packageName, boolean killFlag, ArrayList<String> componentNames, int packageUid) { - if (false) Log.v(TAG, "Sending package changed: package=" + packageName - + " components=" + componentNames); + if (DEBUG_INSTALL) + Log.v(TAG, "Sending package changed: package=" + packageName + " components=" + + componentNames); Bundle extras = new Bundle(4); extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentNames.get(0)); String nameList[] = new String[componentNames.size()]; @@ -7053,72 +7165,37 @@ class PackageManagerService extends IPackageManager.Stub { } public void setPackageStoppedState(String packageName, boolean stopped) { - PackageSetting pkgSetting; final int uid = Binder.getCallingUid(); final int permission = mContext.checkCallingOrSelfPermission( android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE); final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); + // writer synchronized (mPackages) { - pkgSetting = mSettings.mPackages.get(packageName); - if (pkgSetting == null) { - throw new IllegalArgumentException("Unknown package: " + packageName); - } - if (!allowedByPermission && (uid != pkgSetting.userId)) { - throw new SecurityException( - "Permission Denial: attempt to change stopped state from pid=" - + Binder.getCallingPid() - + ", uid=" + uid + ", package uid=" + pkgSetting.userId); - } - if (DEBUG_STOPPED && stopped) { - RuntimeException e = new RuntimeException("here"); - e.fillInStackTrace(); - Slog.i(TAG, "Stopping package " + packageName, e); - } - if (pkgSetting.stopped != stopped) { - pkgSetting.stopped = stopped; - pkgSetting.pkg.mSetStopped = stopped; - if (pkgSetting.notLaunched) { - if (pkgSetting.installerPackageName != null) { - sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, - pkgSetting.name, null, - pkgSetting.installerPackageName, null); - } - pkgSetting.notLaunched = false; - } + if (mSettings.setPackageStoppedStateLPw(packageName, stopped, allowedByPermission, + uid)) { scheduleWriteStoppedPackagesLocked(); } } } public String getInstallerPackageName(String packageName) { + // reader synchronized (mPackages) { - PackageSetting pkg = mSettings.mPackages.get(packageName); - if (pkg == null) { - throw new IllegalArgumentException("Unknown package: " + packageName); - } - return pkg.installerPackageName; + return mSettings.getInstallerPackageNameLPr(packageName); } } - public int getApplicationEnabledSetting(String appPackageName) { + public int getApplicationEnabledSetting(String packageName) { + // reader synchronized (mPackages) { - PackageSetting pkg = mSettings.mPackages.get(appPackageName); - if (pkg == null) { - throw new IllegalArgumentException("Unknown package: " + appPackageName); - } - return pkg.enabled; + return mSettings.getApplicationEnabledSettingLPr(packageName); } } public int getComponentEnabledSetting(ComponentName componentName) { + // reader synchronized (mPackages) { - final String packageNameStr = componentName.getPackageName(); - PackageSetting pkg = mSettings.mPackages.get(packageNameStr); - if (pkg == null) { - throw new IllegalArgumentException("Unknown component: " + componentName); - } - final String classNameStr = componentName.getClassName(); - return pkg.currentEnabledStateLP(classNameStr); + return mSettings.getComponentEnabledSettingLPr(componentName); } } @@ -7162,6 +7239,76 @@ class PackageManagerService extends IPackageManager.Stub { return buf.toString(); } + static class DumpState { + public static final int DUMP_LIBS = 1 << 0; + + public static final int DUMP_FEATURES = 1 << 1; + + public static final int DUMP_RESOLVERS = 1 << 2; + + public static final int DUMP_PERMISSIONS = 1 << 3; + + public static final int DUMP_PACKAGES = 1 << 4; + + public static final int DUMP_SHARED_USERS = 1 << 5; + + public static final int DUMP_MESSAGES = 1 << 6; + + public static final int DUMP_PROVIDERS = 1 << 7; + + public static final int OPTION_SHOW_FILTERS = 1 << 0; + + private int mTypes; + + private int mOptions; + + private boolean mTitlePrinted; + + private SharedUserSetting mSharedUser; + + public boolean isDumping(int type) { + if (mTypes == 0) { + return true; + } + + return (mTypes & type) != 0; + } + + public void setDump(int type) { + mTypes |= type; + } + + public boolean isOptionEnabled(int option) { + return (mOptions & option) != 0; + } + + public void setOptionEnabled(int option) { + mOptions |= option; + } + + public boolean onTitlePrinted() { + final boolean printed = mTitlePrinted; + mTitlePrinted = true; + return printed; + } + + public boolean getTitlePrinted() { + return mTitlePrinted; + } + + public void setTitlePrinted(boolean enabled) { + mTitlePrinted = enabled; + } + + public SharedUserSetting getSharedUser() { + return mSharedUser; + } + + public void setSharedUser(SharedUserSetting user) { + mSharedUser = user; + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) @@ -7174,18 +7321,9 @@ class PackageManagerService extends IPackageManager.Stub { return; } - boolean dumpStar = true; - boolean dumpLibs = false; - boolean dumpFeatures = false; - boolean dumpResolvers = false; - boolean dumpPermissions = false; - boolean dumpPackages = false; - boolean dumpSharedUsers = false; - boolean dumpMessages = false; - boolean dumpProviders = false; + DumpState dumpState = new DumpState(); String packageName = null; - boolean showFilters = false; int opti = 0; while (opti < args.length) { @@ -7213,7 +7351,7 @@ class PackageManagerService extends IPackageManager.Stub { pw.println(" <package.name>: info about given package"); return; } else if ("-f".equals(opt)) { - showFilters = true; + dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS); } else { pw.println("Unknown argument: " + opt + "; use -h for help"); } @@ -7227,40 +7365,31 @@ class PackageManagerService extends IPackageManager.Stub { if ("android".equals(cmd) || cmd.contains(".")) { packageName = cmd; } else if ("l".equals(cmd) || "libraries".equals(cmd)) { - dumpStar = false; - dumpLibs = true; + dumpState.setDump(DumpState.DUMP_LIBS); } else if ("f".equals(cmd) || "features".equals(cmd)) { - dumpStar = false; - dumpFeatures = true; + dumpState.setDump(DumpState.DUMP_FEATURES); } else if ("r".equals(cmd) || "resolvers".equals(cmd)) { - dumpStar = false; - dumpResolvers = true; + dumpState.setDump(DumpState.DUMP_RESOLVERS); } else if ("perm".equals(cmd) || "permissions".equals(cmd)) { - dumpStar = false; - dumpPermissions = true; + dumpState.setDump(DumpState.DUMP_PERMISSIONS); } else if ("p".equals(cmd) || "packages".equals(cmd)) { - dumpStar = false; - dumpPackages = true; + dumpState.setDump(DumpState.DUMP_PACKAGES); } else if ("s".equals(cmd) || "shared-users".equals(cmd)) { - dumpStar = false; - dumpSharedUsers = true; + dumpState.setDump(DumpState.DUMP_SHARED_USERS); } else if ("prov".equals(cmd) || "providers".equals(cmd)) { - dumpStar = false; - dumpProviders = true; + dumpState.setDump(DumpState.DUMP_PROVIDERS); } else if ("m".equals(cmd) || "messages".equals(cmd)) { - dumpStar = false; - dumpMessages = true; + dumpState.setDump(DumpState.DUMP_MESSAGES); } } - - boolean printedTitle = false; - + + // reader synchronized (mPackages) { - if ((dumpStar || dumpLibs) && packageName == null) { - if (printedTitle) pw.println(" "); - printedTitle = true; + if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) { + if (dumpState.onTitlePrinted()) + pw.println(" "); pw.println("Libraries:"); - Iterator<String> it = mSharedLibraries.keySet().iterator(); + final Iterator<String> it = mSharedLibraries.keySet().iterator(); while (it.hasNext()) { String name = it.next(); pw.print(" "); @@ -7270,9 +7399,9 @@ class PackageManagerService extends IPackageManager.Stub { } } - if ((dumpStar || dumpFeatures) && packageName == null) { - if (printedTitle) pw.println(" "); - printedTitle = true; + if (dumpState.isDumping(DumpState.DUMP_FEATURES) && packageName == null) { + if (dumpState.onTitlePrinted()) + pw.println(" "); pw.println("Features:"); Iterator<String> it = mAvailableFeatures.keySet().iterator(); while (it.hasNext()) { @@ -7282,276 +7411,72 @@ class PackageManagerService extends IPackageManager.Stub { } } - if (dumpStar || dumpResolvers) { - if (mActivities.dump(pw, printedTitle - ? "\nActivity Resolver Table:" : "Activity Resolver Table:", - " ", packageName, showFilters)) { - printedTitle = true; + if (dumpState.isDumping(DumpState.DUMP_RESOLVERS)) { + if (mActivities.dump(pw, dumpState.getTitlePrinted() ? "\nActivity Resolver Table:" + : "Activity Resolver Table:", " ", packageName, + dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { + dumpState.setTitlePrinted(true); } - if (mReceivers.dump(pw, printedTitle - ? "\nReceiver Resolver Table:" : "Receiver Resolver Table:", - " ", packageName, showFilters)) { - printedTitle = true; + if (mReceivers.dump(pw, dumpState.getTitlePrinted() ? "\nReceiver Resolver Table:" + : "Receiver Resolver Table:", " ", packageName, + dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { + dumpState.setTitlePrinted(true); } - if (mServices.dump(pw, printedTitle - ? "\nService Resolver Table:" : "Service Resolver Table:", - " ", packageName, showFilters)) { - printedTitle = true; + if (mServices.dump(pw, dumpState.getTitlePrinted() ? "\nService Resolver Table:" + : "Service Resolver Table:", " ", packageName, + dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { + dumpState.setTitlePrinted(true); } - if (mSettings.mPreferredActivities.dump(pw, printedTitle - ? "\nPreferred Activities:" : "Preferred Activities:", - " ", packageName, showFilters)) { - printedTitle = true; + if (mSettings.mPreferredActivities.dump(pw, + dumpState.getTitlePrinted() ? "\nPreferred Activities:" + : "Preferred Activities:", " ", + packageName, dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { + dumpState.setTitlePrinted(true); } } - boolean printedSomething = false; - if (dumpStar || dumpPermissions) { - for (BasePermission p : mSettings.mPermissions.values()) { - if (packageName != null && !packageName.equals(p.sourcePackage)) { - continue; - } - if (!printedSomething) { - if (printedTitle) pw.println(" "); - pw.println("Permissions:"); - printedSomething = true; - printedTitle = true; - } - pw.print(" Permission ["); pw.print(p.name); pw.print("] ("); - pw.print(Integer.toHexString(System.identityHashCode(p))); - pw.println("):"); - pw.print(" sourcePackage="); pw.println(p.sourcePackage); - pw.print(" uid="); pw.print(p.uid); - pw.print(" gids="); pw.print(arrayToString(p.gids)); - pw.print(" type="); pw.print(p.type); - pw.print(" prot="); pw.println(p.protectionLevel); - if (p.packageSetting != null) { - pw.print(" packageSetting="); pw.println(p.packageSetting); - } - if (p.perm != null) { - pw.print(" perm="); pw.println(p.perm); - } - } + if (dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) { + mSettings.dumpPermissionsLPr(pw, packageName, dumpState); } - if (dumpStar || dumpProviders) { - printedSomething = false; + if (dumpState.isDumping(DumpState.DUMP_PROVIDERS)) { + boolean printedSomething = false; for (PackageParser.Provider p : mProviders.values()) { if (packageName != null && !packageName.equals(p.info.packageName)) { continue; } if (!printedSomething) { - if (printedTitle) pw.println(" "); + if (dumpState.onTitlePrinted()) + pw.println(" "); pw.println("Registered ContentProviders:"); printedSomething = true; - printedTitle = true; } pw.print(" ["); pw.print(p.info.authority); pw.print("]: "); pw.println(p.toString()); } } - printedSomething = false; - SharedUserSetting packageSharedUser = null; - if (dumpStar || dumpPackages) { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - Date date = new Date(); - for (PackageSetting ps : mSettings.mPackages.values()) { - if (packageName != null && !packageName.equals(ps.realName) - && !packageName.equals(ps.name)) { - continue; - } - if (!printedSomething) { - if (printedTitle) pw.println(" "); - pw.println("Packages:"); - printedSomething = true; - printedTitle = true; - } - packageSharedUser = ps.sharedUser; - pw.print(" Package ["); - pw.print(ps.realName != null ? ps.realName : ps.name); - pw.print("] ("); - pw.print(Integer.toHexString(System.identityHashCode(ps))); - pw.println("):"); - if (ps.realName != null) { - pw.print(" compat name="); pw.println(ps.name); - } - pw.print(" userId="); pw.print(ps.userId); - pw.print(" gids="); pw.println(arrayToString(ps.gids)); - pw.print(" sharedUser="); pw.println(ps.sharedUser); - pw.print(" pkg="); pw.println(ps.pkg); - pw.print(" codePath="); pw.println(ps.codePathString); - pw.print(" resourcePath="); pw.println(ps.resourcePathString); - pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString); - pw.print(" versionCode="); pw.println(ps.versionCode); - if (ps.pkg != null) { - pw.print(" versionName="); pw.println(ps.pkg.mVersionName); - pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir); - pw.print(" targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion); - if (ps.pkg.mOperationPending) { - pw.println(" mOperationPending=true"); - } - pw.print(" supportsScreens=["); - boolean first = true; - if ((ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) { - if (!first) pw.print(", "); - first = false; - pw.print("small"); - } - if ((ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) { - if (!first) pw.print(", "); - first = false; - pw.print("medium"); - } - if ((ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { - if (!first) pw.print(", "); - first = false; - pw.print("large"); - } - if ((ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { - if (!first) pw.print(", "); - first = false; - pw.print("xlarge"); - } - if ((ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { - if (!first) pw.print(", "); - first = false; - pw.print("resizeable"); - } - if ((ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) { - if (!first) pw.print(", "); - first = false; - pw.print("anyDensity"); - } - } - pw.println("]"); - pw.print(" timeStamp="); - date.setTime(ps.timeStamp); pw.println(sdf.format(date)); - pw.print(" firstInstallTime="); - date.setTime(ps.firstInstallTime); pw.println(sdf.format(date)); - pw.print(" lastUpdateTime="); - date.setTime(ps.lastUpdateTime); pw.println(sdf.format(date)); - if (ps.installerPackageName != null) { - pw.print(" installerPackageName="); pw.println(ps.installerPackageName); - } - pw.print(" signatures="); pw.println(ps.signatures); - pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed); - pw.print(" haveGids="); pw.println(ps.haveGids); - pw.print(" pkgFlags=0x"); pw.print(Integer.toHexString(ps.pkgFlags)); - pw.print(" installStatus="); pw.print(ps.installStatus); - pw.print(" stopped="); pw.print(ps.stopped); - pw.print(" enabled="); pw.println(ps.enabled); - if (ps.disabledComponents.size() > 0) { - pw.println(" disabledComponents:"); - for (String s : ps.disabledComponents) { - pw.print(" "); pw.println(s); - } - } - if (ps.enabledComponents.size() > 0) { - pw.println(" enabledComponents:"); - for (String s : ps.enabledComponents) { - pw.print(" "); pw.println(s); - } - } - if (ps.grantedPermissions.size() > 0) { - pw.println(" grantedPermissions:"); - for (String s : ps.grantedPermissions) { - pw.print(" "); pw.println(s); - } - } - } - } - printedSomething = false; - if (dumpStar || dumpPackages) { - if (mSettings.mRenamedPackages.size() > 0) { - for (HashMap.Entry<String, String> e - : mSettings.mRenamedPackages.entrySet()) { - if (packageName != null && !packageName.equals(e.getKey()) - && !packageName.equals(e.getValue())) { - continue; - } - if (!printedSomething) { - if (printedTitle) pw.println(" "); - pw.println("Renamed packages:"); - printedSomething = true; - printedTitle = true; - } - pw.print(" "); pw.print(e.getKey()); pw.print(" -> "); - pw.println(e.getValue()); - } - } - printedSomething = false; - if (mSettings.mDisabledSysPackages.size() > 0) { - for (PackageSetting ps : mSettings.mDisabledSysPackages.values()) { - if (packageName != null && !packageName.equals(ps.realName) - && !packageName.equals(ps.name)) { - continue; - } - if (!printedSomething) { - if (printedTitle) pw.println(" "); - pw.println("Hidden system packages:"); - printedSomething = true; - printedTitle = true; - } - pw.print(" Package ["); - pw.print(ps.realName != null ? ps.realName : ps.name); - pw.print("] ("); - pw.print(Integer.toHexString(System.identityHashCode(ps))); - pw.println("):"); - if (ps.realName != null) { - pw.print(" compat name="); pw.println(ps.name); - } - pw.print(" userId="); pw.println(ps.userId); - pw.print(" sharedUser="); pw.println(ps.sharedUser); - pw.print(" codePath="); pw.println(ps.codePathString); - pw.print(" resourcePath="); pw.println(ps.resourcePathString); - } - } + if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) { + mSettings.dumpPackagesLPr(pw, packageName, dumpState); } - printedSomething = false; - if (dumpStar || dumpSharedUsers) { - for (SharedUserSetting su : mSettings.mSharedUsers.values()) { - if (packageName != null && su != packageSharedUser) { - continue; - } - if (!printedSomething) { - if (printedTitle) pw.println(" "); - pw.println("Shared users:"); - printedSomething = true; - printedTitle = true; - } - pw.print(" SharedUser ["); pw.print(su.name); pw.print("] ("); - pw.print(Integer.toHexString(System.identityHashCode(su))); - pw.println("):"); - pw.print(" userId="); pw.print(su.userId); - pw.print(" gids="); pw.println(arrayToString(su.gids)); - pw.println(" grantedPermissions:"); - for (String s : su.grantedPermissions) { - pw.print(" "); pw.println(s); - } - } + + if (dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) { + mSettings.dumpSharedUsersLPr(pw, packageName, dumpState); } - - if ((dumpStar || dumpMessages) && packageName == null) { - if (printedTitle) pw.println(" "); - printedTitle = true; - pw.println("Settings parse messages:"); - pw.print(mSettings.mReadMessages.toString()); - + + if (dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) { + if (dumpState.onTitlePrinted()) + pw.println(" "); + mSettings.dumpReadMessagesLPr(pw, dumpState); + pw.println(" "); pw.println("Package warning messages:"); - File fname = getSettingsProblemFile(); + final File fname = getSettingsProblemFile(); FileInputStream in = null; try { in = new FileInputStream(fname); - int avail = in.available(); - byte[] data = new byte[avail]; + final int avail = in.available(); + final byte[] data = new byte[avail]; in.read(data); pw.print(new String(data)); } catch (FileNotFoundException e) { @@ -7560,2918 +7485,513 @@ class PackageManagerService extends IPackageManager.Stub { if (in != null) { try { in.close(); - } catch (IOException e) { - } - } - } - } - } - } - - static final class BasePermission { - final static int TYPE_NORMAL = 0; - final static int TYPE_BUILTIN = 1; - final static int TYPE_DYNAMIC = 2; - - final String name; - String sourcePackage; - PackageSettingBase packageSetting; - final int type; - int protectionLevel; - PackageParser.Permission perm; - PermissionInfo pendingInfo; - int uid; - int[] gids; - - BasePermission(String _name, String _sourcePackage, int _type) { - name = _name; - sourcePackage = _sourcePackage; - type = _type; - // Default to most conservative protection level. - protectionLevel = PermissionInfo.PROTECTION_SIGNATURE; - } - - public String toString() { - return "BasePermission{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + name + "}"; - } - } - - static class PackageSignatures { - private Signature[] mSignatures; - - PackageSignatures(PackageSignatures orig) { - if (orig != null && orig.mSignatures != null) { - mSignatures = orig.mSignatures.clone(); - } - } - - PackageSignatures(Signature[] sigs) { - assignSignatures(sigs); - } - - PackageSignatures() { - } - - void writeXml(XmlSerializer serializer, String tagName, - ArrayList<Signature> pastSignatures) throws IOException { - if (mSignatures == null) { - return; - } - serializer.startTag(null, tagName); - serializer.attribute(null, "count", - Integer.toString(mSignatures.length)); - for (int i=0; i<mSignatures.length; i++) { - serializer.startTag(null, "cert"); - final Signature sig = mSignatures[i]; - final int sigHash = sig.hashCode(); - final int numPast = pastSignatures.size(); - int j; - for (j=0; j<numPast; j++) { - Signature pastSig = pastSignatures.get(j); - if (pastSig.hashCode() == sigHash && pastSig.equals(sig)) { - serializer.attribute(null, "index", Integer.toString(j)); - break; - } - } - if (j >= numPast) { - pastSignatures.add(sig); - serializer.attribute(null, "index", Integer.toString(numPast)); - serializer.attribute(null, "key", sig.toCharsString()); - } - serializer.endTag(null, "cert"); - } - serializer.endTag(null, tagName); - } - - void readXml(XmlPullParser parser, ArrayList<Signature> pastSignatures) - throws IOException, XmlPullParserException { - String countStr = parser.getAttributeValue(null, "count"); - if (countStr == null) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <signatures> has" - + " no count at " + parser.getPositionDescription()); - XmlUtils.skipCurrentTag(parser); - } - final int count = Integer.parseInt(countStr); - mSignatures = new Signature[count]; - int pos = 0; - - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("cert")) { - if (pos < count) { - String index = parser.getAttributeValue(null, "index"); - if (index != null) { - try { - int idx = Integer.parseInt(index); - String key = parser.getAttributeValue(null, "key"); - if (key == null) { - if (idx >= 0 && idx < pastSignatures.size()) { - Signature sig = pastSignatures.get(idx); - if (sig != null) { - mSignatures[pos] = pastSignatures.get(idx); - pos++; - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <cert> " - + "index " + index + " is not defined at " - + parser.getPositionDescription()); - } - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <cert> " - + "index " + index + " is out of bounds at " - + parser.getPositionDescription()); - } - } else { - while (pastSignatures.size() <= idx) { - pastSignatures.add(null); - } - Signature sig = new Signature(key); - pastSignatures.set(idx, sig); - mSignatures[pos] = sig; - pos++; - } - } catch (NumberFormatException e) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <cert> " - + "index " + index + " is not a number at " - + parser.getPositionDescription()); - } - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <cert> has" - + " no index at " + parser.getPositionDescription()); + } catch (IOException e) { } - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: too " - + "many <cert> tags, expected " + count - + " at " + parser.getPositionDescription()); } - } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <cert>: " - + parser.getName()); - } - XmlUtils.skipCurrentTag(parser); - } - - if (pos < count) { - // Should never happen -- there is an error in the written - // settings -- but if it does we don't want to generate - // a bad array. - Signature[] newSigs = new Signature[pos]; - System.arraycopy(mSignatures, 0, newSigs, 0, pos); - mSignatures = newSigs; - } - } - - private void assignSignatures(Signature[] sigs) { - if (sigs == null) { - mSignatures = null; - return; - } - mSignatures = new Signature[sigs.length]; - for (int i=0; i<sigs.length; i++) { - mSignatures[i] = sigs[i]; - } - } - - @Override - public String toString() { - StringBuffer buf = new StringBuffer(128); - buf.append("PackageSignatures{"); - buf.append(Integer.toHexString(System.identityHashCode(this))); - buf.append(" ["); - if (mSignatures != null) { - for (int i=0; i<mSignatures.length; i++) { - if (i > 0) buf.append(", "); - buf.append(Integer.toHexString( - System.identityHashCode(mSignatures[i]))); } } - buf.append("]}"); - return buf.toString(); - } - } - - static class PreferredActivity extends IntentFilter implements PreferredComponent.Callbacks { - final PreferredComponent mPref; - - PreferredActivity(IntentFilter filter, int match, ComponentName[] set, - ComponentName activity) { - super(filter); - mPref = new PreferredComponent(this, match, set, activity); - } - - PreferredActivity(XmlPullParser parser) throws XmlPullParserException, - IOException { - mPref = new PreferredComponent(this, parser); - } - - public void writeToXml(XmlSerializer serializer) throws IOException { - mPref.writeToXml(serializer); - serializer.startTag(null, "filter"); - super.writeToXml(serializer); - serializer.endTag(null, "filter"); - } - - public boolean onReadTag(String tagName, XmlPullParser parser) - throws XmlPullParserException, IOException { - if (tagName.equals("filter")) { - //Log.i(TAG, "Starting to parse filter..."); - readFromXml(parser); - //Log.i(TAG, "Finished filter: outerDepth=" + outerDepth + " depth=" - // + parser.getDepth() + " tag=" + parser.getName()); - } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <preferred-activities>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - return true; } } - static class GrantedPermissions { - int pkgFlags; - - HashSet<String> grantedPermissions = new HashSet<String>(); - int[] gids; - - GrantedPermissions(int pkgFlags) { - setFlags(pkgFlags); - } - - GrantedPermissions(GrantedPermissions base) { - pkgFlags = base.pkgFlags; - grantedPermissions = (HashSet<String>) base.grantedPermissions.clone(); - - if (base.gids != null) { - gids = base.gids.clone(); - } - } - - void setFlags(int pkgFlags) { - this.pkgFlags = pkgFlags & ( - ApplicationInfo.FLAG_SYSTEM | - ApplicationInfo.FLAG_FORWARD_LOCK | - ApplicationInfo.FLAG_EXTERNAL_STORAGE); - } - } - - /** - * Settings base class for pending and resolved classes. - */ - static class PackageSettingBase extends GrantedPermissions { - final String name; - final String realName; - File codePath; - String codePathString; - File resourcePath; - String resourcePathString; - String nativeLibraryPathString; - long timeStamp; - long firstInstallTime; - long lastUpdateTime; - int versionCode; - - boolean uidError; - - PackageSignatures signatures = new PackageSignatures(); - - boolean permissionsFixed; - boolean haveGids; - - // Whether this package is currently stopped, thus can not be - // started until explicitly launched by the user. - public boolean stopped; - - // Set to true if we have never launched this app. - public boolean notLaunched; - - /* Explicitly disabled components */ - HashSet<String> disabledComponents = new HashSet<String>(0); - /* Explicitly enabled components */ - HashSet<String> enabledComponents = new HashSet<String>(0); - int enabled = COMPONENT_ENABLED_STATE_DEFAULT; - int installStatus = PKG_INSTALL_COMPLETE; - - PackageSettingBase origPackage; - - /* package name of the app that installed this package */ - String installerPackageName; - - PackageSettingBase(String name, String realName, File codePath, File resourcePath, - String nativeLibraryPathString, int pVersionCode, int pkgFlags) { - super(pkgFlags); - this.name = name; - this.realName = realName; - init(codePath, resourcePath, nativeLibraryPathString, pVersionCode); - } - - /** - * New instance of PackageSetting with one-level-deep cloning. - */ - PackageSettingBase(PackageSettingBase base) { - super(base); - - name = base.name; - realName = base.realName; - codePath = base.codePath; - codePathString = base.codePathString; - resourcePath = base.resourcePath; - resourcePathString = base.resourcePathString; - nativeLibraryPathString = base.nativeLibraryPathString; - timeStamp = base.timeStamp; - firstInstallTime = base.firstInstallTime; - lastUpdateTime = base.lastUpdateTime; - versionCode = base.versionCode; - - uidError = base.uidError; - - signatures = new PackageSignatures(base.signatures); - - permissionsFixed = base.permissionsFixed; - haveGids = base.haveGids; - stopped = base.stopped; - notLaunched = base.notLaunched; - - disabledComponents = (HashSet<String>) base.disabledComponents.clone(); - - enabledComponents = (HashSet<String>) base.enabledComponents.clone(); - - enabled = base.enabled; - installStatus = base.installStatus; - - origPackage = base.origPackage; - - installerPackageName = base.installerPackageName; - } - - void init(File codePath, File resourcePath, String nativeLibraryPathString, - int pVersionCode) { - this.codePath = codePath; - this.codePathString = codePath.toString(); - this.resourcePath = resourcePath; - this.resourcePathString = resourcePath.toString(); - this.nativeLibraryPathString = nativeLibraryPathString; - this.versionCode = pVersionCode; - } - - public void setInstallerPackageName(String packageName) { - installerPackageName = packageName; - } - - String getInstallerPackageName() { - return installerPackageName; - } - - public void setInstallStatus(int newStatus) { - installStatus = newStatus; - } - - public int getInstallStatus() { - return installStatus; - } - - public void setTimeStamp(long newStamp) { - timeStamp = newStamp; - } - - /** - * Make a shallow copy of this package settings. - */ - public void copyFrom(PackageSettingBase base) { - grantedPermissions = base.grantedPermissions; - gids = base.gids; - - timeStamp = base.timeStamp; - firstInstallTime = base.firstInstallTime; - lastUpdateTime = base.lastUpdateTime; - signatures = base.signatures; - permissionsFixed = base.permissionsFixed; - haveGids = base.haveGids; - stopped = base.stopped; - notLaunched = base.notLaunched; - disabledComponents = base.disabledComponents; - enabledComponents = base.enabledComponents; - enabled = base.enabled; - installStatus = base.installStatus; - } - - boolean enableComponentLP(String componentClassName) { - boolean changed = disabledComponents.remove(componentClassName); - changed |= enabledComponents.add(componentClassName); - return changed; - } - - boolean disableComponentLP(String componentClassName) { - boolean changed = enabledComponents.remove(componentClassName); - changed |= disabledComponents.add(componentClassName); - return changed; - } - - boolean restoreComponentLP(String componentClassName) { - boolean changed = enabledComponents.remove(componentClassName); - changed |= disabledComponents.remove(componentClassName); - return changed; - } - - int currentEnabledStateLP(String componentName) { - if (enabledComponents.contains(componentName)) { - return COMPONENT_ENABLED_STATE_ENABLED; - } else if (disabledComponents.contains(componentName)) { - return COMPONENT_ENABLED_STATE_DISABLED; - } else { - return COMPONENT_ENABLED_STATE_DEFAULT; - } - } - } - - /** - * Settings data for a particular package we know about. - */ - static final class PackageSetting extends PackageSettingBase { - int userId; - PackageParser.Package pkg; - SharedUserSetting sharedUser; - - PackageSetting(String name, String realName, File codePath, File resourcePath, - String nativeLibraryPathString, int pVersionCode, int pkgFlags) { - super(name, realName, codePath, resourcePath, nativeLibraryPathString, pVersionCode, - pkgFlags); - } - - /** - * New instance of PackageSetting replicating the original settings. - * Note that it keeps the same PackageParser.Package instance. - */ - PackageSetting(PackageSetting orig) { - super(orig); - - userId = orig.userId; - pkg = orig.pkg; - sharedUser = orig.sharedUser; - } - - @Override - public String toString() { - return "PackageSetting{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + name + "/" + userId + "}"; - } - } - - /** - * Settings data for a particular shared user ID we know about. - */ - static final class SharedUserSetting extends GrantedPermissions { - final String name; - int userId; - final HashSet<PackageSetting> packages = new HashSet<PackageSetting>(); - final PackageSignatures signatures = new PackageSignatures(); - - SharedUserSetting(String _name, int _pkgFlags) { - super(_pkgFlags); - name = _name; - } - - @Override - public String toString() { - return "SharedUserSetting{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + name + "/" + userId + "}"; - } - } - - /** - * Holds information about dynamic settings. - */ - private static final class Settings { - private final File mSettingsFilename; - private final File mBackupSettingsFilename; - private final File mPackageListFilename; - private final File mStoppedPackagesFilename; - private final File mBackupStoppedPackagesFilename; - private final HashMap<String, PackageSetting> mPackages = - new HashMap<String, PackageSetting>(); - // List of replaced system applications - final HashMap<String, PackageSetting> mDisabledSysPackages = - new HashMap<String, PackageSetting>(); - - // These are the last platform API version we were using for - // the apps installed on internal and external storage. It is - // used to grant newer permissions one time during a system upgrade. - int mInternalSdkPlatform; - int mExternalSdkPlatform; - - // The user's preferred activities associated with particular intent - // filters. - private final IntentResolver<PreferredActivity, PreferredActivity> mPreferredActivities = - new IntentResolver<PreferredActivity, PreferredActivity>() { - @Override - protected String packageForFilter(PreferredActivity filter) { - return filter.mPref.mComponent.getPackageName(); - } - @Override - protected void dumpFilter(PrintWriter out, String prefix, - PreferredActivity filter) { - filter.mPref.dump(out, prefix, filter); - } - }; - private final HashMap<String, SharedUserSetting> mSharedUsers = - new HashMap<String, SharedUserSetting>(); - private final ArrayList<Object> mUserIds = new ArrayList<Object>(); - private final SparseArray<Object> mOtherUserIds = - new SparseArray<Object>(); - - // For reading/writing settings file. - private final ArrayList<Signature> mPastSignatures = - new ArrayList<Signature>(); - - // Mapping from permission names to info about them. - final HashMap<String, BasePermission> mPermissions = - new HashMap<String, BasePermission>(); - - // Mapping from permission tree names to info about them. - final HashMap<String, BasePermission> mPermissionTrees = - new HashMap<String, BasePermission>(); - - // Packages that have been uninstalled and still need their external - // storage data deleted. - final ArrayList<String> mPackagesToBeCleaned = new ArrayList<String>(); - - // Packages that have been renamed since they were first installed. - // Keys are the new names of the packages, values are the original - // names. The packages appear everwhere else under their original - // names. - final HashMap<String, String> mRenamedPackages = new HashMap<String, String>(); - - private final StringBuilder mReadMessages = new StringBuilder(); - - private static final class PendingPackage extends PackageSettingBase { - final int sharedId; - - PendingPackage(String name, String realName, File codePath, File resourcePath, - String nativeLibraryPathString, int sharedId, int pVersionCode, int pkgFlags) { - super(name, realName, codePath, resourcePath, nativeLibraryPathString, - pVersionCode, pkgFlags); - this.sharedId = sharedId; - } - } - private final ArrayList<PendingPackage> mPendingPackages - = new ArrayList<PendingPackage>(); - - Settings() { - File dataDir = Environment.getDataDirectory(); - File systemDir = new File(dataDir, "system"); - systemDir.mkdirs(); - FileUtils.setPermissions(systemDir.toString(), - FileUtils.S_IRWXU|FileUtils.S_IRWXG - |FileUtils.S_IROTH|FileUtils.S_IXOTH, - -1, -1); - mSettingsFilename = new File(systemDir, "packages.xml"); - mBackupSettingsFilename = new File(systemDir, "packages-backup.xml"); - mPackageListFilename = new File(systemDir, "packages.list"); - mStoppedPackagesFilename = new File(systemDir, "packages-stopped.xml"); - mBackupStoppedPackagesFilename = new File(systemDir, "packages-stopped-backup.xml"); - } - - PackageSetting getPackageLP(PackageParser.Package pkg, PackageSetting origPackage, - String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, - String nativeLibraryPathString, int pkgFlags, boolean create, boolean add) { - final String name = pkg.packageName; - PackageSetting p = getPackageLP(name, origPackage, realName, sharedUser, codePath, - resourcePath, nativeLibraryPathString, pkg.mVersionCode, pkgFlags, create, add); - return p; - } - - PackageSetting peekPackageLP(String name) { - return mPackages.get(name); - /* - PackageSetting p = mPackages.get(name); - if (p != null && p.codePath.getPath().equals(codePath)) { - return p; - } - return null; - */ - } - - void setInstallStatus(String pkgName, int status) { - PackageSetting p = mPackages.get(pkgName); - if(p != null) { - if(p.getInstallStatus() != status) { - p.setInstallStatus(status); - } - } - } + // ------- apps on sdcard specific code ------- + static final boolean DEBUG_SD_INSTALL = false; - void setInstallerPackageName(String pkgName, - String installerPkgName) { - PackageSetting p = mPackages.get(pkgName); - if(p != null) { - p.setInstallerPackageName(installerPkgName); - } - } + private static final String SD_ENCRYPTION_KEYSTORE_NAME = "AppsOnSD"; - String getInstallerPackageName(String pkgName) { - PackageSetting p = mPackages.get(pkgName); - return (p == null) ? null : p.getInstallerPackageName(); - } + private static final String SD_ENCRYPTION_ALGORITHM = "AES"; - int getInstallStatus(String pkgName) { - PackageSetting p = mPackages.get(pkgName); - if(p != null) { - return p.getInstallStatus(); - } - return -1; - } + private boolean mMediaMounted = false; - SharedUserSetting getSharedUserLP(String name, - int pkgFlags, boolean create) { - SharedUserSetting s = mSharedUsers.get(name); - if (s == null) { - if (!create) { + private String getEncryptKey() { + try { + String sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString( + SD_ENCRYPTION_KEYSTORE_NAME); + if (sdEncKey == null) { + sdEncKey = SystemKeyStore.getInstance().generateNewKeyHexString(128, + SD_ENCRYPTION_ALGORITHM, SD_ENCRYPTION_KEYSTORE_NAME); + if (sdEncKey == null) { + Slog.e(TAG, "Failed to create encryption keys"); return null; } - s = new SharedUserSetting(name, pkgFlags); - if (MULTIPLE_APPLICATION_UIDS) { - s.userId = newUserIdLP(s); - } else { - s.userId = FIRST_APPLICATION_UID; - } - Log.i(TAG, "New shared user " + name + ": id=" + s.userId); - // < 0 means we couldn't assign a userid; fall out and return - // s, which is currently null - if (s.userId >= 0) { - mSharedUsers.put(name, s); - } - } - - return s; - } - - boolean disableSystemPackageLP(String name) { - PackageSetting p = mPackages.get(name); - if(p == null) { - Log.w(TAG, "Package:"+name+" is not an installed package"); - return false; - } - PackageSetting dp = mDisabledSysPackages.get(name); - // always make sure the system package code and resource paths dont change - if (dp == null) { - if((p.pkg != null) && (p.pkg.applicationInfo != null)) { - p.pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; - } - mDisabledSysPackages.put(name, p); - - // a little trick... when we install the new package, we don't - // want to modify the existing PackageSetting for the built-in - // version. so at this point we need a new PackageSetting that - // is okay to muck with. - PackageSetting newp = new PackageSetting(p); - replacePackageLP(name, newp); - return true; - } - return false; - } - - PackageSetting enableSystemPackageLP(String name) { - PackageSetting p = mDisabledSysPackages.get(name); - if(p == null) { - Log.w(TAG, "Package:"+name+" is not disabled"); - return null; - } - // Reset flag in ApplicationInfo object - if((p.pkg != null) && (p.pkg.applicationInfo != null)) { - p.pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; - } - PackageSetting ret = addPackageLP(name, p.realName, p.codePath, p.resourcePath, - p.nativeLibraryPathString, p.userId, p.versionCode, p.pkgFlags); - mDisabledSysPackages.remove(name); - return ret; - } - - PackageSetting addPackageLP(String name, String realName, File codePath, File resourcePath, - String nativeLibraryPathString, int uid, int vc, int pkgFlags) { - PackageSetting p = mPackages.get(name); - if (p != null) { - if (p.userId == uid) { - return p; - } - reportSettingsProblem(Log.ERROR, - "Adding duplicate package, keeping first: " + name); - return null; - } - p = new PackageSetting(name, realName, codePath, resourcePath, nativeLibraryPathString, - vc, pkgFlags); - p.userId = uid; - if (addUserIdLP(uid, p, name)) { - mPackages.put(name, p); - return p; } + return sdEncKey; + } catch (NoSuchAlgorithmException nsae) { + Slog.e(TAG, "Failed to create encryption keys with exception: " + nsae); return null; - } - - SharedUserSetting addSharedUserLP(String name, int uid, int pkgFlags) { - SharedUserSetting s = mSharedUsers.get(name); - if (s != null) { - if (s.userId == uid) { - return s; - } - reportSettingsProblem(Log.ERROR, - "Adding duplicate shared user, keeping first: " + name); - return null; - } - s = new SharedUserSetting(name, pkgFlags); - s.userId = uid; - if (addUserIdLP(uid, s, name)) { - mSharedUsers.put(name, s); - return s; - } + } catch (IOException ioe) { + Slog.e(TAG, "Failed to retrieve encryption keys with exception: " + ioe); return null; } - // Transfer ownership of permissions from one package to another. - private void transferPermissions(String origPkg, String newPkg) { - // Transfer ownership of permissions to the new package. - for (int i=0; i<2; i++) { - HashMap<String, BasePermission> permissions = - i == 0 ? mPermissionTrees : mPermissions; - for (BasePermission bp : permissions.values()) { - if (origPkg.equals(bp.sourcePackage)) { - if (DEBUG_UPGRADE) Log.v(TAG, - "Moving permission " + bp.name - + " from pkg " + bp.sourcePackage - + " to " + newPkg); - bp.sourcePackage = newPkg; - bp.packageSetting = null; - bp.perm = null; - if (bp.pendingInfo != null) { - bp.pendingInfo.packageName = newPkg; - } - bp.uid = 0; - bp.gids = null; - } - } - } - } - - private PackageSetting getPackageLP(String name, PackageSetting origPackage, - String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, - String nativeLibraryPathString, int vc, int pkgFlags, boolean create, boolean add) { - PackageSetting p = mPackages.get(name); - if (p != null) { - if (!p.codePath.equals(codePath)) { - // Check to see if its a disabled system app - if ((p.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) { - // This is an updated system app with versions in both system - // and data partition. Just let the most recent version - // take precedence. - Slog.w(TAG, "Trying to update system app code path from " + - p.codePathString + " to " + codePath.toString()); - } else { - // Just a change in the code path is not an issue, but - // let's log a message about it. - Slog.i(TAG, "Package " + name + " codePath changed from " + p.codePath - + " to " + codePath + "; Retaining data and using new"); - /* - * Since we've changed paths, we need to prefer the new - * native library path over the one stored in the - * package settings since we might have moved from - * internal to external storage or vice versa. - */ - p.nativeLibraryPathString = nativeLibraryPathString; - } - } - if (p.sharedUser != sharedUser) { - reportSettingsProblem(Log.WARN, - "Package " + name + " shared user changed from " - + (p.sharedUser != null ? p.sharedUser.name : "<nothing>") - + " to " - + (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) { - // Create a new PackageSettings entry. this can end up here because - // of code path mismatch or user id mismatch of an updated system partition - if (!create) { - return null; - } - if (origPackage != null) { - // We are consuming the data from an existing package. - p = new PackageSetting(origPackage.name, name, codePath, resourcePath, - nativeLibraryPathString, vc, pkgFlags); - if (DEBUG_UPGRADE) Log.v(TAG, "Package " + name - + " is adopting original package " + origPackage.name); - // Note that we will retain the new package's signature so - // that we can keep its data. - PackageSignatures s = p.signatures; - p.copyFrom(origPackage); - p.signatures = s; - p.sharedUser = origPackage.sharedUser; - p.userId = origPackage.userId; - p.origPackage = origPackage; - mRenamedPackages.put(name, origPackage.name); - name = origPackage.name; - // Update new package state. - p.setTimeStamp(codePath.lastModified()); - } else { - p = new PackageSetting(name, realName, codePath, resourcePath, - nativeLibraryPathString, vc, pkgFlags); - p.setTimeStamp(codePath.lastModified()); - p.sharedUser = sharedUser; - // If this is not a system app, it starts out stopped. - if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { - if (DEBUG_STOPPED) { - RuntimeException e = new RuntimeException("here"); - e.fillInStackTrace(); - Slog.i(TAG, "Stopping package " + name, e); - } - p.stopped = true; - p.notLaunched = true; - } - if (sharedUser != null) { - p.userId = sharedUser.userId; - } else if (MULTIPLE_APPLICATION_UIDS) { - // Clone the setting here for disabled system packages - PackageSetting dis = mDisabledSysPackages.get(name); - if (dis != null) { - // For disabled packages a new setting is created - // from the existing user id. This still has to be - // added to list of user id's - // Copy signatures from previous setting - if (dis.signatures.mSignatures != null) { - p.signatures.mSignatures = dis.signatures.mSignatures.clone(); - } - p.userId = dis.userId; - // Clone permissions - p.grantedPermissions = new HashSet<String>(dis.grantedPermissions); - // Clone component info - p.disabledComponents = new HashSet<String>(dis.disabledComponents); - p.enabledComponents = new HashSet<String>(dis.enabledComponents); - // Add new setting to list of user ids - addUserIdLP(p.userId, p, name); - } else { - // Assign new user id - p.userId = newUserIdLP(p); - } - } else { - p.userId = FIRST_APPLICATION_UID; - } - } - if (p.userId < 0) { - reportSettingsProblem(Log.WARN, - "Package " + name + " could not be assigned a valid uid"); - return null; - } - if (add) { - // Finish adding new package by adding it and updating shared - // user preferences - addPackageSettingLP(p, name, sharedUser); - } - } - return p; - } - - private void insertPackageSettingLP(PackageSetting p, PackageParser.Package pkg) { - p.pkg = pkg; - pkg.mSetEnabled = p.enabled; - pkg.mSetStopped = p.stopped; - final String codePath = pkg.applicationInfo.sourceDir; - final String resourcePath = pkg.applicationInfo.publicSourceDir; - // Update code path if needed - if (!codePath.equalsIgnoreCase(p.codePathString)) { - Slog.w(TAG, "Code path for pkg : " + p.pkg.packageName + - " changing from " + p.codePathString + " to " + codePath); - p.codePath = new File(codePath); - p.codePathString = codePath; - } - //Update resource path if needed - if (!resourcePath.equalsIgnoreCase(p.resourcePathString)) { - Slog.w(TAG, "Resource path for pkg : " + p.pkg.packageName + - " changing from " + p.resourcePathString + " to " + resourcePath); - p.resourcePath = new File(resourcePath); - p.resourcePathString = resourcePath; - } - // Update the native library path if needed - final String nativeLibraryPath = pkg.applicationInfo.nativeLibraryDir; - if (nativeLibraryPath != null - && !nativeLibraryPath.equalsIgnoreCase(p.nativeLibraryPathString)) { - p.nativeLibraryPathString = nativeLibraryPath; - } - // Update version code if needed - if (pkg.mVersionCode != p.versionCode) { - p.versionCode = pkg.mVersionCode; - } - // Update signatures if needed. - if (p.signatures.mSignatures == null) { - p.signatures.assignSignatures(pkg.mSignatures); - } - // If this app defines a shared user id initialize - // the shared user signatures as well. - if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) { - p.sharedUser.signatures.assignSignatures(pkg.mSignatures); - } - addPackageSettingLP(p, pkg.packageName, p.sharedUser); - } - - // Utility method that adds a PackageSetting to mPackages and - // completes updating the shared user attributes - private void addPackageSettingLP(PackageSetting p, String name, - SharedUserSetting sharedUser) { - mPackages.put(name, p); - if (sharedUser != null) { - if (p.sharedUser != null && p.sharedUser != sharedUser) { - reportSettingsProblem(Log.ERROR, - "Package " + p.name + " was user " - + p.sharedUser + " but is now " + sharedUser - + "; I am not changing its files so it will probably fail!"); - p.sharedUser.packages.remove(p); - } else if (p.userId != sharedUser.userId) { - reportSettingsProblem(Log.ERROR, - "Package " + p.name + " was user id " + p.userId - + " but is now user " + sharedUser - + " with id " + sharedUser.userId - + "; I am not changing its files so it will probably fail!"); - } - - sharedUser.packages.add(p); - p.sharedUser = sharedUser; - p.userId = sharedUser.userId; - } - } + } - /* - * Update the shared user setting when a package using - * specifying the shared user id is removed. The gids - * associated with each permission of the deleted package - * are removed from the shared user's gid list only if its - * not in use by other permissions of packages in the - * shared user setting. - */ - private void updateSharedUserPermsLP(PackageSetting deletedPs, int[] globalGids) { - if ( (deletedPs == null) || (deletedPs.pkg == null)) { - Slog.i(TAG, "Trying to update info for null package. Just ignoring"); - return; - } - // No sharedUserId - if (deletedPs.sharedUser == null) { - return; - } - SharedUserSetting sus = deletedPs.sharedUser; - // Update permissions - for (String eachPerm: deletedPs.pkg.requestedPermissions) { - boolean used = false; - if (!sus.grantedPermissions.contains (eachPerm)) { + /* package */static String getTempContainerId() { + int tmpIdx = 1; + String list[] = PackageHelper.getSecureContainerList(); + if (list != null) { + for (final String name : list) { + // Ignore null and non-temporary container entries + if (name == null || !name.startsWith(mTempContainerPrefix)) { continue; } - for (PackageSetting pkg:sus.packages) { - if (pkg.pkg != null && - !pkg.pkg.packageName.equals(deletedPs.pkg.packageName) && - pkg.pkg.requestedPermissions.contains(eachPerm)) { - used = true; - break; - } - } - if (!used) { - // can safely delete this permission from list - sus.grantedPermissions.remove(eachPerm); - } - } - // Update gids - int newGids[] = globalGids; - for (String eachPerm : sus.grantedPermissions) { - BasePermission bp = mPermissions.get(eachPerm); - if (bp != null) { - newGids = appendInts(newGids, bp.gids); - } - } - sus.gids = newGids; - } - private int removePackageLP(String name) { - PackageSetting p = mPackages.get(name); - if (p != null) { - mPackages.remove(name); - if (p.sharedUser != null) { - p.sharedUser.packages.remove(p); - if (p.sharedUser.packages.size() == 0) { - mSharedUsers.remove(p.sharedUser.name); - removeUserIdLP(p.sharedUser.userId); - return p.sharedUser.userId; + String subStr = name.substring(mTempContainerPrefix.length()); + try { + int cid = Integer.parseInt(subStr); + if (cid >= tmpIdx) { + tmpIdx = cid + 1; } - } else { - removeUserIdLP(p.userId); - return p.userId; - } - } - return -1; - } - - private void replacePackageLP(String name, PackageSetting newp) { - PackageSetting p = mPackages.get(name); - if (p != null) { - if (p.sharedUser != null) { - p.sharedUser.packages.remove(p); - p.sharedUser.packages.add(newp); - } else { - replaceUserIdLP(p.userId, newp); - } - } - mPackages.put(name, newp); - } - - private boolean addUserIdLP(int uid, Object obj, Object name) { - if (uid >= FIRST_APPLICATION_UID + MAX_APPLICATION_UIDS) { - return false; - } - - if (uid >= FIRST_APPLICATION_UID) { - int N = mUserIds.size(); - final int index = uid - FIRST_APPLICATION_UID; - while (index >= N) { - mUserIds.add(null); - N++; - } - if (mUserIds.get(index) != null) { - reportSettingsProblem(Log.ERROR, - "Adding duplicate user id: " + uid - + " name=" + name); - return false; - } - mUserIds.set(index, obj); - } else { - if (mOtherUserIds.get(uid) != null) { - reportSettingsProblem(Log.ERROR, - "Adding duplicate shared id: " + uid - + " name=" + name); - return false; - } - mOtherUserIds.put(uid, obj); - } - return true; - } - - public Object getUserIdLP(int uid) { - if (uid >= FIRST_APPLICATION_UID) { - int N = mUserIds.size(); - final int index = uid - FIRST_APPLICATION_UID; - return index < N ? mUserIds.get(index) : null; - } else { - return mOtherUserIds.get(uid); - } - } - - private Set<String> findPackagesWithFlag(int flag) { - Set<String> ret = new HashSet<String>(); - for (PackageSetting ps : mPackages.values()) { - // Has to match atleast all the flag bits set on flag - if ((ps.pkgFlags & flag) == flag) { - ret.add(ps.name); + } catch (NumberFormatException e) { } } - return ret; } + return mTempContainerPrefix + tmpIdx; + } - private void removeUserIdLP(int uid) { - if (uid >= FIRST_APPLICATION_UID) { - int N = mUserIds.size(); - final int index = uid - FIRST_APPLICATION_UID; - if (index < N) mUserIds.set(index, null); - } else { - mOtherUserIds.remove(uid); - } + /* + * Update media status on PackageManager. + */ + public void updateExternalMediaStatus(final boolean mediaStatus, final boolean reportStatus) { + int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { + throw new SecurityException("Media status can only be updated by the system"); } - - private void replaceUserIdLP(int uid, Object obj) { - if (uid >= FIRST_APPLICATION_UID) { - int N = mUserIds.size(); - final int index = uid - FIRST_APPLICATION_UID; - if (index < N) mUserIds.set(index, obj); - } else { - mOtherUserIds.put(uid, obj); - } - } - - void writeStoppedLP() { - // Keep the old stopped packages around until we know the new ones have - // been successfully written. - if (mStoppedPackagesFilename.exists()) { - // Presence of backup settings file indicates that we failed - // to persist packages earlier. So preserve the older - // backup for future reference since the current packages - // might have been corrupted. - if (!mBackupStoppedPackagesFilename.exists()) { - if (!mStoppedPackagesFilename.renameTo(mBackupStoppedPackagesFilename)) { - Log.wtf(TAG, "Unable to backup package manager stopped packages, " - + "current changes will be lost at reboot"); - return; - } - } else { - mStoppedPackagesFilename.delete(); - Slog.w(TAG, "Preserving older stopped packages backup"); - } - } - - try { - FileOutputStream fstr = new FileOutputStream(mStoppedPackagesFilename); - BufferedOutputStream str = new BufferedOutputStream(fstr); - - //XmlSerializer serializer = XmlUtils.serializerInstance(); - XmlSerializer serializer = new FastXmlSerializer(); - serializer.setOutput(str, "utf-8"); - serializer.startDocument(null, true); - serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - - serializer.startTag(null, "stopped-packages"); - - for (PackageSetting pkg : mPackages.values()) { - if (pkg.stopped) { - serializer.startTag(null, "pkg"); - serializer.attribute(null, "name", pkg.name); - if (pkg.notLaunched) { - serializer.attribute(null, "nl", "1"); - } - serializer.endTag(null, "pkg"); - } - } - - serializer.endTag(null, "stopped-packages"); - - serializer.endDocument(); - - str.flush(); - FileUtils.sync(fstr); - str.close(); - - // New settings successfully written, old ones are no longer - // needed. - mBackupStoppedPackagesFilename.delete(); - FileUtils.setPermissions(mStoppedPackagesFilename.toString(), - FileUtils.S_IRUSR|FileUtils.S_IWUSR - |FileUtils.S_IRGRP|FileUtils.S_IWGRP - |FileUtils.S_IROTH, - -1, -1); - - // Done, all is good! + // reader; this apparently protects mMediaMounted, but should probably + // be a different lock in that case. + synchronized (mPackages) { + Log.i(TAG, "Updating external media status from " + + (mMediaMounted ? "mounted" : "unmounted") + " to " + + (mediaStatus ? "mounted" : "unmounted")); + if (DEBUG_SD_INSTALL) + Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" + mediaStatus + + ", mMediaMounted=" + mMediaMounted); + if (mediaStatus == mMediaMounted) { + final Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, reportStatus ? 1 + : 0, -1); + mHandler.sendMessage(msg); return; - - } catch(java.io.IOException e) { - Log.wtf(TAG, "Unable to write package manager stopped packages, " - + " current changes will be lost at reboot", e); - } - - // Clean up partially written files - if (mStoppedPackagesFilename.exists()) { - if (!mStoppedPackagesFilename.delete()) { - Log.i(TAG, "Failed to clean up mangled file: " + mStoppedPackagesFilename); - } } + mMediaMounted = mediaStatus; } - - // Note: assumed "stopped" field is already cleared in all packages. - void readStoppedLP() { - FileInputStream str = null; - if (mBackupStoppedPackagesFilename.exists()) { - try { - str = new FileInputStream(mBackupStoppedPackagesFilename); - mReadMessages.append("Reading from backup stopped packages file\n"); - reportSettingsProblem(Log.INFO, "Need to read from backup stopped packages file"); - if (mSettingsFilename.exists()) { - // If both the backup and normal file exist, we - // ignore the normal one since it might have been - // corrupted. - Slog.w(TAG, "Cleaning up stopped packages file " - + mStoppedPackagesFilename); - mStoppedPackagesFilename.delete(); - } - } catch (java.io.IOException e) { - // We'll try for the normal settings file. - } + // Queue up an async operation since the package installation may take a + // little while. + mHandler.post(new Runnable() { + public void run() { + // TODO fix this; this does nothing. + mHandler.removeCallbacks(this); + updateExternalMediaStatusInner(mediaStatus, reportStatus); } + }); + } - try { - if (str == null) { - if (!mStoppedPackagesFilename.exists()) { - mReadMessages.append("No stopped packages file found\n"); - reportSettingsProblem(Log.INFO, "No stopped packages file file; " - + "assuming all started"); - // At first boot, make sure no packages are stopped. - // We usually want to have third party apps initialize - // in the stopped state, but not at first boot. - for (PackageSetting pkg : mPackages.values()) { - pkg.stopped = false; - pkg.notLaunched = false; - } - return; - } - str = new FileInputStream(mStoppedPackagesFilename); - } - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(str, null); - - int type; - while ((type=parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - ; - } - - if (type != XmlPullParser.START_TAG) { - mReadMessages.append("No start tag found in stopped packages file\n"); - reportSettingsProblem(Log.WARN, - "No start tag found in package manager stopped packages"); - return; - } - - int outerDepth = parser.getDepth(); - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { + /* + * Collect information of applications on external media, map them against + * existing containers and update information based on current mount status. + * Please note that we always have to report status if reportStatus has been + * set to true especially when unloading packages. + */ + private void updateExternalMediaStatusInner(boolean mediaStatus, boolean reportStatus) { + // Collection of uids + int uidArr[] = null; + // Collection of stale containers + HashSet<String> removeCids = new HashSet<String>(); + // Collection of packages on external media with valid containers. + HashMap<SdInstallArgs, String> processCids = new HashMap<SdInstallArgs, String>(); + // Get list of secure containers. + final String list[] = PackageHelper.getSecureContainerList(); + if (list == null || list.length == 0) { + Log.i(TAG, "No secure containers on sdcard"); + } else { + // Process list of secure containers and categorize them + // as active or stale based on their package internal state. + int uidList[] = new int[list.length]; + int num = 0; + // reader + synchronized (mPackages) { + for (String cid : list) { + SdInstallArgs args = new SdInstallArgs(cid); + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Processing container " + cid); + String pkgName = args.getPackageName(); + if (pkgName == null) { + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Container : " + cid + " stale"); + removeCids.add(cid); continue; } - - String tagName = parser.getName(); - if (tagName.equals("pkg")) { - String name = parser.getAttributeValue(null, "name"); - PackageSetting ps = mPackages.get(name); - if (ps != null) { - ps.stopped = true; - if ("1".equals(parser.getAttributeValue(null, "nl"))) { - ps.notLaunched = true; - } - } else { - Slog.w(TAG, "No package known for stopped package: " + name); + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Looking for pkg : " + pkgName); + PackageSetting ps = mSettings.mPackages.get(pkgName); + // The package status is changed only if the code path + // matches between settings and the container id. + if (ps != null && ps.codePathString != null + && ps.codePathString.equals(args.getCodePath())) { + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Container : " + cid + " corresponds to pkg : " + pkgName + + " at code path: " + ps.codePathString); + // We do have a valid package installed on sdcard + processCids.put(args, ps.codePathString); + int uid = ps.userId; + if (uid != -1) { + uidList[num++] = uid; } - XmlUtils.skipCurrentTag(parser); } else { - Slog.w(TAG, "Unknown element under <stopped-packages>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); + // Stale container on sdcard. Just delete + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Container : " + cid + " stale"); + removeCids.add(cid); } } - - str.close(); - - } catch(XmlPullParserException e) { - mReadMessages.append("Error reading: " + e.toString()); - reportSettingsProblem(Log.ERROR, "Error reading stopped packages: " + e); - Log.wtf(TAG, "Error reading package manager stopped packages", e); - - } catch(java.io.IOException e) { - mReadMessages.append("Error reading: " + e.toString()); - reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); - Log.wtf(TAG, "Error reading package manager stopped packages", e); - } - } - - void writeLP() { - //Debug.startMethodTracing("/data/system/packageprof", 8 * 1024 * 1024); - // Keep the old settings around until we know the new ones have - // been successfully written. - if (mSettingsFilename.exists()) { - // 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.wtf(TAG, "Unable to backup package manager settings, " - + " current changes will be lost at reboot"); - return; + if (num > 0) { + // Sort uid list + Arrays.sort(uidList, 0, num); + // Throw away duplicates + uidArr = new int[num]; + uidArr[0] = uidList[0]; + int di = 0; + for (int i = 1; i < num; i++) { + if (uidList[i - 1] != uidList[i]) { + uidArr[di++] = uidList[i]; } - } else { - mSettingsFilename.delete(); - Slog.w(TAG, "Preserving older settings backup"); } } - - mPastSignatures.clear(); - - try { - FileOutputStream fstr = new FileOutputStream(mSettingsFilename); - BufferedOutputStream str = new BufferedOutputStream(fstr); - - //XmlSerializer serializer = XmlUtils.serializerInstance(); - XmlSerializer serializer = new FastXmlSerializer(); - serializer.setOutput(str, "utf-8"); - serializer.startDocument(null, true); - serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - - serializer.startTag(null, "packages"); - - serializer.startTag(null, "last-platform-version"); - serializer.attribute(null, "internal", Integer.toString(mInternalSdkPlatform)); - serializer.attribute(null, "external", Integer.toString(mExternalSdkPlatform)); - serializer.endTag(null, "last-platform-version"); - - serializer.startTag(null, "permission-trees"); - for (BasePermission bp : mPermissionTrees.values()) { - writePermission(serializer, bp); - } - serializer.endTag(null, "permission-trees"); - - serializer.startTag(null, "permissions"); - for (BasePermission bp : mPermissions.values()) { - writePermission(serializer, bp); - } - serializer.endTag(null, "permissions"); - - for (PackageSetting pkg : mPackages.values()) { - writePackage(serializer, pkg); - } - - for (PackageSetting pkg : mDisabledSysPackages.values()) { - writeDisabledSysPackage(serializer, pkg); - } - - serializer.startTag(null, "preferred-activities"); - for (PreferredActivity pa : mPreferredActivities.filterSet()) { - serializer.startTag(null, "item"); - pa.writeToXml(serializer); - serializer.endTag(null, "item"); - } - serializer.endTag(null, "preferred-activities"); - - for (SharedUserSetting usr : mSharedUsers.values()) { - serializer.startTag(null, "shared-user"); - serializer.attribute(null, "name", usr.name); - serializer.attribute(null, "userId", - Integer.toString(usr.userId)); - usr.signatures.writeXml(serializer, "sigs", mPastSignatures); - serializer.startTag(null, "perms"); - for (String name : usr.grantedPermissions) { - serializer.startTag(null, "item"); - serializer.attribute(null, "name", name); - serializer.endTag(null, "item"); - } - serializer.endTag(null, "perms"); - serializer.endTag(null, "shared-user"); - } - - if (mPackagesToBeCleaned.size() > 0) { - for (int i=0; i<mPackagesToBeCleaned.size(); i++) { - serializer.startTag(null, "cleaning-package"); - serializer.attribute(null, "name", mPackagesToBeCleaned.get(i)); - serializer.endTag(null, "cleaning-package"); - } - } - - if (mRenamedPackages.size() > 0) { - for (HashMap.Entry<String, String> e : mRenamedPackages.entrySet()) { - serializer.startTag(null, "renamed-package"); - serializer.attribute(null, "new", e.getKey()); - serializer.attribute(null, "old", e.getValue()); - serializer.endTag(null, "renamed-package"); - } - } - - serializer.endTag(null, "packages"); - - serializer.endDocument(); - - str.flush(); - FileUtils.sync(fstr); - str.close(); - - // New settings successfully written, old ones are no longer - // needed. - mBackupSettingsFilename.delete(); - FileUtils.setPermissions(mSettingsFilename.toString(), - FileUtils.S_IRUSR|FileUtils.S_IWUSR - |FileUtils.S_IRGRP|FileUtils.S_IWGRP - |FileUtils.S_IROTH, - -1, -1); - - // Write package list file now, use a JournaledFile. - // - File tempFile = new File(mPackageListFilename.toString() + ".tmp"); - JournaledFile journal = new JournaledFile(mPackageListFilename, tempFile); - - fstr = new FileOutputStream(journal.chooseForWrite()); - str = new BufferedOutputStream(fstr); - try { - StringBuilder sb = new StringBuilder(); - for (PackageSetting pkg : mPackages.values()) { - ApplicationInfo ai = pkg.pkg.applicationInfo; - String dataPath = ai.dataDir; - boolean isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; - - // Avoid any application that has a space in its path - // or that is handled by the system. - if (dataPath.indexOf(" ") >= 0 || ai.uid <= Process.FIRST_APPLICATION_UID) - continue; - - // we store on each line the following information for now: - // - // pkgName - package name - // userId - application-specific user id - // debugFlag - 0 or 1 if the package is debuggable. - // dataPath - path to package's data path - // - // NOTE: We prefer not to expose all ApplicationInfo flags for now. - // - // DO NOT MODIFY THIS FORMAT UNLESS YOU CAN ALSO MODIFY ITS USERS - // FROM NATIVE CODE. AT THE MOMENT, LOOK AT THE FOLLOWING SOURCES: - // system/core/run-as/run-as.c - // - sb.setLength(0); - sb.append(ai.packageName); - sb.append(" "); - sb.append((int)ai.uid); - sb.append(isDebug ? " 1 " : " 0 "); - sb.append(dataPath); - sb.append("\n"); - str.write(sb.toString().getBytes()); - } - str.flush(); - FileUtils.sync(fstr); - str.close(); - journal.commit(); - } - catch (Exception e) { - journal.rollback(); - } - - FileUtils.setPermissions(mPackageListFilename.toString(), - FileUtils.S_IRUSR|FileUtils.S_IWUSR - |FileUtils.S_IRGRP|FileUtils.S_IWGRP - |FileUtils.S_IROTH, - -1, -1); - - writeStoppedLP(); - - return; - - } catch(XmlPullParserException e) { - Log.wtf(TAG, "Unable to write package manager settings, " - + "current changes will be lost at reboot", e); - } catch(java.io.IOException e) { - Log.wtf(TAG, "Unable to write package manager settings, " - + "current changes will be lost at reboot", e); - } - // Clean up partially written files - if (mSettingsFilename.exists()) { - if (!mSettingsFilename.delete()) { - Log.wtf(TAG, "Failed to clean up mangled file: " + mSettingsFilename); - } - } - //Debug.stopMethodTracing(); - } - - void writeDisabledSysPackage(XmlSerializer serializer, final PackageSetting pkg) - throws java.io.IOException { - serializer.startTag(null, "updated-package"); - serializer.attribute(null, "name", pkg.name); - if (pkg.realName != null) { - serializer.attribute(null, "realName", pkg.realName); - } - serializer.attribute(null, "codePath", pkg.codePathString); - serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp)); - serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime)); - serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime)); - serializer.attribute(null, "version", String.valueOf(pkg.versionCode)); - if (!pkg.resourcePathString.equals(pkg.codePathString)) { - serializer.attribute(null, "resourcePath", pkg.resourcePathString); - } - if (pkg.nativeLibraryPathString != null) { - serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString); - } - if (pkg.sharedUser == null) { - serializer.attribute(null, "userId", - Integer.toString(pkg.userId)); - } else { - serializer.attribute(null, "sharedUserId", - Integer.toString(pkg.userId)); - } - serializer.startTag(null, "perms"); - if (pkg.sharedUser == null) { - // If this is a shared user, the permissions will - // be written there. We still need to write an - // empty permissions list so permissionsFixed will - // be set. - for (final String name : pkg.grantedPermissions) { - BasePermission bp = mPermissions.get(name); - if (bp != null) { - // We only need to write signature or system permissions but this wont - // match the semantics of grantedPermissions. So write all permissions. - serializer.startTag(null, "item"); - serializer.attribute(null, "name", name); - serializer.endTag(null, "item"); - } - } - } - serializer.endTag(null, "perms"); - serializer.endTag(null, "updated-package"); } - - void writePackage(XmlSerializer serializer, final PackageSetting pkg) - throws java.io.IOException { - serializer.startTag(null, "package"); - serializer.attribute(null, "name", pkg.name); - if (pkg.realName != null) { - serializer.attribute(null, "realName", pkg.realName); - } - serializer.attribute(null, "codePath", pkg.codePathString); - if (!pkg.resourcePathString.equals(pkg.codePathString)) { - serializer.attribute(null, "resourcePath", pkg.resourcePathString); - } - if (pkg.nativeLibraryPathString != null) { - serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString); - } - serializer.attribute(null, "flags", - Integer.toString(pkg.pkgFlags)); - serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp)); - serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime)); - serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime)); - serializer.attribute(null, "version", String.valueOf(pkg.versionCode)); - if (pkg.sharedUser == null) { - serializer.attribute(null, "userId", - Integer.toString(pkg.userId)); - } else { - serializer.attribute(null, "sharedUserId", - Integer.toString(pkg.userId)); - } - if (pkg.uidError) { - serializer.attribute(null, "uidError", "true"); - } - if (pkg.enabled != COMPONENT_ENABLED_STATE_DEFAULT) { - serializer.attribute(null, "enabled", - pkg.enabled == COMPONENT_ENABLED_STATE_ENABLED - ? "true" : "false"); - } - if(pkg.installStatus == PKG_INSTALL_INCOMPLETE) { - serializer.attribute(null, "installStatus", "false"); - } - if (pkg.installerPackageName != null) { - serializer.attribute(null, "installer", pkg.installerPackageName); - } - pkg.signatures.writeXml(serializer, "sigs", mPastSignatures); - if ((pkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { - serializer.startTag(null, "perms"); - if (pkg.sharedUser == null) { - // If this is a shared user, the permissions will - // be written there. We still need to write an - // empty permissions list so permissionsFixed will - // be set. - for (final String name : pkg.grantedPermissions) { - serializer.startTag(null, "item"); - serializer.attribute(null, "name", name); - serializer.endTag(null, "item"); - } - } - serializer.endTag(null, "perms"); - } - if (pkg.disabledComponents.size() > 0) { - serializer.startTag(null, "disabled-components"); - for (final String name : pkg.disabledComponents) { - serializer.startTag(null, "item"); - serializer.attribute(null, "name", name); - serializer.endTag(null, "item"); - } - serializer.endTag(null, "disabled-components"); - } - if (pkg.enabledComponents.size() > 0) { - serializer.startTag(null, "enabled-components"); - for (final String name : pkg.enabledComponents) { - serializer.startTag(null, "item"); - serializer.attribute(null, "name", name); - serializer.endTag(null, "item"); - } - serializer.endTag(null, "enabled-components"); - } - - serializer.endTag(null, "package"); - } - - void writePermission(XmlSerializer serializer, BasePermission bp) - throws XmlPullParserException, java.io.IOException { - if (bp.type != BasePermission.TYPE_BUILTIN - && bp.sourcePackage != null) { - serializer.startTag(null, "item"); - serializer.attribute(null, "name", bp.name); - serializer.attribute(null, "package", bp.sourcePackage); - if (bp.protectionLevel != - PermissionInfo.PROTECTION_NORMAL) { - serializer.attribute(null, "protection", - Integer.toString(bp.protectionLevel)); - } - if (DEBUG_SETTINGS) Log.v(TAG, - "Writing perm: name=" + bp.name + " type=" + bp.type); - if (bp.type == BasePermission.TYPE_DYNAMIC) { - PermissionInfo pi = bp.perm != null ? bp.perm.info - : bp.pendingInfo; - if (pi != null) { - serializer.attribute(null, "type", "dynamic"); - if (pi.icon != 0) { - serializer.attribute(null, "icon", - Integer.toString(pi.icon)); - } - if (pi.nonLocalizedLabel != null) { - serializer.attribute(null, "label", - pi.nonLocalizedLabel.toString()); - } - } - } - serializer.endTag(null, "item"); - } - } - - String getReadMessagesLP() { - return mReadMessages.toString(); - } - - ArrayList<PackageSetting> getListOfIncompleteInstallPackages() { - HashSet<String> kList = new HashSet<String>(mPackages.keySet()); - Iterator<String> its = kList.iterator(); - ArrayList<PackageSetting> ret = new ArrayList<PackageSetting>(); - while(its.hasNext()) { - String key = its.next(); - PackageSetting ps = mPackages.get(key); - if(ps.getInstallStatus() == PKG_INSTALL_INCOMPLETE) { - ret.add(ps); - } - } - return ret; + // Process packages with valid entries. + if (mediaStatus) { + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Loading packages"); + loadMediaPackages(processCids, uidArr, removeCids); + startCleaningPackages(); + } else { + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Unloading packages"); + unloadMediaPackages(processCids, uidArr, reportStatus); } + } - boolean readLP() { - FileInputStream str = null; - if (mBackupSettingsFilename.exists()) { - try { - str = new FileInputStream(mBackupSettingsFilename); - mReadMessages.append("Reading from backup settings file\n"); - reportSettingsProblem(Log.INFO, "Need to read 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. - Slog.w(TAG, "Cleaning up settings file " + mSettingsFilename); - mSettingsFilename.delete(); - } - } catch (java.io.IOException e) { - // We'll try for the normal settings file. - } - } - - mPastSignatures.clear(); - - try { - if (str == null) { - if (!mSettingsFilename.exists()) { - mReadMessages.append("No settings file found\n"); - reportSettingsProblem(Log.INFO, "No settings file; creating initial state"); - return false; - } - str = new FileInputStream(mSettingsFilename); - } - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(str, null); - - int type; - while ((type=parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - ; - } - - if (type != XmlPullParser.START_TAG) { - mReadMessages.append("No start tag found in settings file\n"); - reportSettingsProblem(Log.WARN, "No start tag found in package manager settings"); - Log.wtf(TAG, "No start tag found in package manager settings"); - return false; - } - - int outerDepth = parser.getDepth(); - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("package")) { - readPackageLP(parser); - } else if (tagName.equals("permissions")) { - readPermissionsLP(mPermissions, parser); - } else if (tagName.equals("permission-trees")) { - readPermissionsLP(mPermissionTrees, parser); - } else if (tagName.equals("shared-user")) { - readSharedUserLP(parser); - } else if (tagName.equals("preferred-packages")) { - // no longer used. - } else if (tagName.equals("preferred-activities")) { - readPreferredActivitiesLP(parser); - } else if(tagName.equals("updated-package")) { - readDisabledSysPackageLP(parser); - } else if (tagName.equals("cleaning-package")) { - String name = parser.getAttributeValue(null, "name"); - if (name != null) { - mPackagesToBeCleaned.add(name); - } - } else if (tagName.equals("renamed-package")) { - String nname = parser.getAttributeValue(null, "new"); - String oname = parser.getAttributeValue(null, "old"); - if (nname != null && oname != null) { - mRenamedPackages.put(nname, oname); - } - } else if (tagName.equals("last-platform-version")) { - mInternalSdkPlatform = mExternalSdkPlatform = 0; - try { - String internal = parser.getAttributeValue(null, "internal"); - if (internal != null) { - mInternalSdkPlatform = Integer.parseInt(internal); - } - String external = parser.getAttributeValue(null, "external"); - if (external != null) { - mExternalSdkPlatform = Integer.parseInt(external); - } - } catch (NumberFormatException e) { - } - } else { - Slog.w(TAG, "Unknown element under <packages>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - - str.close(); - - } catch(XmlPullParserException e) { - mReadMessages.append("Error reading: " + e.toString()); - reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); - Log.wtf(TAG, "Error reading package manager settings", e); - - } catch(java.io.IOException e) { - mReadMessages.append("Error reading: " + e.toString()); - reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); - Log.wtf(TAG, "Error reading package manager settings", e); - - } - - int N = mPendingPackages.size(); - for (int i=0; i<N; i++) { - final PendingPackage pp = mPendingPackages.get(i); - Object idObj = getUserIdLP(pp.sharedId); - if (idObj != null && idObj instanceof SharedUserSetting) { - PackageSetting p = getPackageLP(pp.name, null, pp.realName, - (SharedUserSetting) idObj, pp.codePath, pp.resourcePath, - pp.nativeLibraryPathString, pp.versionCode, pp.pkgFlags, true, true); - if (p == null) { - reportSettingsProblem(Log.WARN, "Unable to create application package for " - + pp.name); - continue; - } - p.copyFrom(pp); - } else if (idObj != null) { - String msg = "Bad package setting: package " + pp.name - + " has shared uid " + pp.sharedId - + " that is not a shared uid\n"; - mReadMessages.append(msg); - reportSettingsProblem(Log.ERROR, msg); - } else { - String msg = "Bad package setting: package " + pp.name - + " has shared uid " + pp.sharedId - + " that is not defined\n"; - mReadMessages.append(msg); - reportSettingsProblem(Log.ERROR, msg); - } + private void sendResourcesChangedBroadcast(boolean mediaStatus, ArrayList<String> pkgList, + int uidArr[], IIntentReceiver finishedReceiver) { + int size = pkgList.size(); + if (size > 0) { + // Send broadcasts here + Bundle extras = new Bundle(); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList + .toArray(new String[size])); + if (uidArr != null) { + extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr); } - mPendingPackages.clear(); - - /* - * Make sure all the updated system packages have their shared users - * associated with them. - */ - final Iterator<PackageSetting> disabledIt = mDisabledSysPackages.values().iterator(); - while (disabledIt.hasNext()) { - final PackageSetting disabledPs = disabledIt.next(); - final Object id = getUserIdLP(disabledPs.userId); - if (id != null && id instanceof SharedUserSetting) { - disabledPs.sharedUser = (SharedUserSetting) id; - } - } - - readStoppedLP(); - - mReadMessages.append("Read completed successfully: " - + mPackages.size() + " packages, " - + mSharedUsers.size() + " shared uids\n"); - - return true; + String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE + : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE; + sendPackageBroadcast(action, null, extras, null, finishedReceiver); } + } - private int readInt(XmlPullParser parser, String ns, String name, - int defValue) { - String v = parser.getAttributeValue(ns, name); + /* + * Look at potentially valid container ids from processCids If package + * information doesn't match the one on record or package scanning fails, + * the cid is added to list of removeCids. We currently don't delete stale + * containers. + */ + private void loadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[], + HashSet<String> removeCids) { + ArrayList<String> pkgList = new ArrayList<String>(); + Set<SdInstallArgs> keys = processCids.keySet(); + boolean doGc = false; + for (SdInstallArgs args : keys) { + String codePath = processCids.get(args); + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Loading container : " + args.cid); + int retCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR; try { - if (v == null) { - return defValue; - } - return Integer.parseInt(v); - } catch (NumberFormatException e) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: attribute " + - name + " has bad integer value " + v + " at " - + parser.getPositionDescription()); - } - return defValue; - } - - private void readPermissionsLP(HashMap<String, BasePermission> out, - XmlPullParser parser) - throws IOException, XmlPullParserException { - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { + // Make sure there are no container errors first. + if (args.doPreInstall(PackageManager.INSTALL_SUCCEEDED) != PackageManager.INSTALL_SUCCEEDED) { + Slog.e(TAG, "Failed to mount cid : " + args.cid + + " when installing from sdcard"); continue; } - - String tagName = parser.getName(); - if (tagName.equals("item")) { - String name = parser.getAttributeValue(null, "name"); - String sourcePackage = parser.getAttributeValue(null, "package"); - String ptype = parser.getAttributeValue(null, "type"); - if (name != null && sourcePackage != null) { - boolean dynamic = "dynamic".equals(ptype); - BasePermission bp = new BasePermission(name, sourcePackage, - dynamic - ? BasePermission.TYPE_DYNAMIC - : BasePermission.TYPE_NORMAL); - bp.protectionLevel = readInt(parser, null, "protection", - PermissionInfo.PROTECTION_NORMAL); - if (dynamic) { - PermissionInfo pi = new PermissionInfo(); - pi.packageName = sourcePackage.intern(); - pi.name = name.intern(); - pi.icon = readInt(parser, null, "icon", 0); - pi.nonLocalizedLabel = parser.getAttributeValue( - null, "label"); - pi.protectionLevel = bp.protectionLevel; - bp.pendingInfo = pi; - } - out.put(bp.name, bp); - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: permissions has" - + " no name at " + parser.getPositionDescription()); - } - } else { - reportSettingsProblem(Log.WARN, - "Unknown element reading permissions: " - + parser.getName() + " at " - + parser.getPositionDescription()); - } - XmlUtils.skipCurrentTag(parser); - } - } - - private void readDisabledSysPackageLP(XmlPullParser parser) - throws XmlPullParserException, IOException { - String name = parser.getAttributeValue(null, "name"); - String realName = parser.getAttributeValue(null, "realName"); - String codePathStr = parser.getAttributeValue(null, "codePath"); - String resourcePathStr = parser.getAttributeValue(null, "resourcePath"); - String nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath"); - if (resourcePathStr == null) { - resourcePathStr = codePathStr; - } - String version = parser.getAttributeValue(null, "version"); - int versionCode = 0; - if (version != null) { - try { - versionCode = Integer.parseInt(version); - } catch (NumberFormatException e) { - } - } - - int pkgFlags = 0; - pkgFlags |= ApplicationInfo.FLAG_SYSTEM; - PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr), - new File(resourcePathStr), nativeLibraryPathStr, versionCode, pkgFlags); - String timeStampStr = parser.getAttributeValue(null, "ft"); - if (timeStampStr != null) { - try { - long timeStamp = Long.parseLong(timeStampStr, 16); - ps.setTimeStamp(timeStamp); - } catch (NumberFormatException e) { - } - } else { - timeStampStr = parser.getAttributeValue(null, "ts"); - if (timeStampStr != null) { - try { - long timeStamp = Long.parseLong(timeStampStr); - ps.setTimeStamp(timeStamp); - } catch (NumberFormatException e) { - } - } - } - timeStampStr = parser.getAttributeValue(null, "it"); - if (timeStampStr != null) { - try { - ps.firstInstallTime = Long.parseLong(timeStampStr, 16); - } catch (NumberFormatException e) { - } - } - timeStampStr = parser.getAttributeValue(null, "ut"); - if (timeStampStr != null) { - try { - ps.lastUpdateTime = Long.parseLong(timeStampStr, 16); - } catch (NumberFormatException e) { - } - } - String idStr = parser.getAttributeValue(null, "userId"); - ps.userId = idStr != null ? Integer.parseInt(idStr) : 0; - if(ps.userId <= 0) { - String sharedIdStr = parser.getAttributeValue(null, "sharedUserId"); - ps.userId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0; - } - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { + // Check code path here. + if (codePath == null || !codePath.equals(args.getCodePath())) { + Slog.e(TAG, "Container " + args.cid + " cachepath " + args.getCodePath() + + " does not match one in settings " + codePath); continue; } - - String tagName = parser.getName(); - if (tagName.equals("perms")) { - readGrantedPermissionsLP(parser, - ps.grantedPermissions); - } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <updated-package>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - mDisabledSysPackages.put(name, ps); - } - - private void readPackageLP(XmlPullParser parser) - throws XmlPullParserException, IOException { - String name = null; - String realName = null; - String idStr = null; - String sharedIdStr = null; - String codePathStr = null; - String resourcePathStr = null; - String nativeLibraryPathStr = null; - String systemStr = null; - String installerPackageName = null; - String uidError = null; - int pkgFlags = 0; - long timeStamp = 0; - long firstInstallTime = 0; - long lastUpdateTime = 0; - PackageSettingBase packageSetting = null; - String version = null; - int versionCode = 0; - try { - name = parser.getAttributeValue(null, "name"); - realName = parser.getAttributeValue(null, "realName"); - idStr = parser.getAttributeValue(null, "userId"); - uidError = parser.getAttributeValue(null, "uidError"); - sharedIdStr = parser.getAttributeValue(null, "sharedUserId"); - codePathStr = parser.getAttributeValue(null, "codePath"); - resourcePathStr = parser.getAttributeValue(null, "resourcePath"); - nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath"); - version = parser.getAttributeValue(null, "version"); - if (version != null) { - try { - versionCode = Integer.parseInt(version); - } catch (NumberFormatException e) { - } - } - installerPackageName = parser.getAttributeValue(null, "installer"); - - systemStr = parser.getAttributeValue(null, "flags"); - if (systemStr != null) { - try { - pkgFlags = Integer.parseInt(systemStr); - } catch (NumberFormatException e) { - } - } else { - // For backward compatibility - systemStr = parser.getAttributeValue(null, "system"); - if (systemStr != null) { - pkgFlags |= ("true".equalsIgnoreCase(systemStr)) ? ApplicationInfo.FLAG_SYSTEM : 0; - } else { - // Old settings that don't specify system... just treat - // them as system, good enough. - pkgFlags |= ApplicationInfo.FLAG_SYSTEM; - } - } - String timeStampStr = parser.getAttributeValue(null, "ft"); - if (timeStampStr != null) { - try { - timeStamp = Long.parseLong(timeStampStr, 16); - } catch (NumberFormatException e) { - } - } else { - timeStampStr = parser.getAttributeValue(null, "ts"); - if (timeStampStr != null) { - try { - timeStamp = Long.parseLong(timeStampStr); - } catch (NumberFormatException e) { + // Parse package + int parseFlags = PackageParser.PARSE_ON_SDCARD | mDefParseFlags; + doGc = true; + synchronized (mInstallLock) { + final PackageParser.Package pkg = scanPackageLI(new File(codePath), parseFlags, + 0, 0); + // Scan the package + if (pkg != null) { + /* + * TODO why is the lock being held? doPostInstall is + * called in other places without the lock. This needs + * to be straightened out. + */ + // writer + synchronized (mPackages) { + retCode = PackageManager.INSTALL_SUCCEEDED; + pkgList.add(pkg.packageName); + // Post process args + args.doPostInstall(PackageManager.INSTALL_SUCCEEDED); } - } - } - timeStampStr = parser.getAttributeValue(null, "it"); - if (timeStampStr != null) { - try { - firstInstallTime = Long.parseLong(timeStampStr, 16); - } catch (NumberFormatException e) { - } - } - timeStampStr = parser.getAttributeValue(null, "ut"); - if (timeStampStr != null) { - try { - lastUpdateTime = Long.parseLong(timeStampStr, 16); - } catch (NumberFormatException e) { - } - } - if (DEBUG_SETTINGS) Log.v(TAG, "Reading package: " + name - + " userId=" + idStr + " sharedUserId=" + sharedIdStr); - int userId = idStr != null ? Integer.parseInt(idStr) : 0; - if (resourcePathStr == null) { - resourcePathStr = codePathStr; - } - if (realName != null) { - realName = realName.intern(); - } - if (name == null) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <package> has no name at " - + parser.getPositionDescription()); - } else if (codePathStr == null) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <package> has no codePath at " - + parser.getPositionDescription()); - } else if (userId > 0) { - packageSetting = addPackageLP(name.intern(), realName, new File(codePathStr), - new File(resourcePathStr), nativeLibraryPathStr, userId, versionCode, - pkgFlags); - if (DEBUG_SETTINGS) Log.i(TAG, "Reading package " + name - + ": userId=" + userId + " pkg=" + packageSetting); - if (packageSetting == null) { - reportSettingsProblem(Log.ERROR, - "Failure adding uid " + userId - + " while parsing settings at " - + parser.getPositionDescription()); } else { - packageSetting.setTimeStamp(timeStamp); - packageSetting.firstInstallTime = firstInstallTime; - packageSetting.lastUpdateTime = lastUpdateTime; + Slog.i(TAG, "Failed to install pkg from " + codePath + " from sdcard"); } - } else if (sharedIdStr != null) { - userId = sharedIdStr != null - ? Integer.parseInt(sharedIdStr) : 0; - if (userId > 0) { - packageSetting = new PendingPackage(name.intern(), realName, - new File(codePathStr), new File(resourcePathStr), - nativeLibraryPathStr, userId, versionCode, pkgFlags); - packageSetting.setTimeStamp(timeStamp); - packageSetting.firstInstallTime = firstInstallTime; - packageSetting.lastUpdateTime = lastUpdateTime; - mPendingPackages.add((PendingPackage) packageSetting); - if (DEBUG_SETTINGS) Log.i(TAG, "Reading package " + name - + ": sharedUserId=" + userId + " pkg=" - + packageSetting); - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: package " - + name + " has bad sharedId " + sharedIdStr - + " at " + parser.getPositionDescription()); - } - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: package " - + name + " has bad userId " + idStr + " at " - + parser.getPositionDescription()); } - } catch (NumberFormatException e) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: package " - + name + " has bad userId " + idStr + " at " - + parser.getPositionDescription()); - } - if (packageSetting != null) { - packageSetting.uidError = "true".equals(uidError); - packageSetting.installerPackageName = installerPackageName; - packageSetting.nativeLibraryPathString = nativeLibraryPathStr; - final String enabledStr = parser.getAttributeValue(null, "enabled"); - if (enabledStr != null) { - if (enabledStr.equalsIgnoreCase("true")) { - packageSetting.enabled = COMPONENT_ENABLED_STATE_ENABLED; - } else if (enabledStr.equalsIgnoreCase("false")) { - packageSetting.enabled = COMPONENT_ENABLED_STATE_DISABLED; - } else if (enabledStr.equalsIgnoreCase("default")) { - packageSetting.enabled = COMPONENT_ENABLED_STATE_DEFAULT; - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: package " - + name + " has bad enabled value: " + idStr - + " at " + parser.getPositionDescription()); - } - } else { - packageSetting.enabled = COMPONENT_ENABLED_STATE_DEFAULT; - } - final String installStatusStr = parser.getAttributeValue(null, "installStatus"); - if (installStatusStr != null) { - if (installStatusStr.equalsIgnoreCase("false")) { - packageSetting.installStatus = PKG_INSTALL_INCOMPLETE; - } else { - packageSetting.installStatus = PKG_INSTALL_COMPLETE; - } - } - - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } - String tagName = parser.getName(); - if (tagName.equals("disabled-components")) { - readDisabledComponentsLP(packageSetting, parser); - } else if (tagName.equals("enabled-components")) { - readEnabledComponentsLP(packageSetting, parser); - } else if (tagName.equals("sigs")) { - packageSetting.signatures.readXml(parser, mPastSignatures); - } else if (tagName.equals("perms")) { - readGrantedPermissionsLP(parser, - packageSetting.grantedPermissions); - packageSetting.permissionsFixed = true; - } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <package>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } + } finally { + if (retCode != PackageManager.INSTALL_SUCCEEDED) { + // Don't destroy container here. Wait till gc clears things + // up. + removeCids.add(args.cid); } - } else { - XmlUtils.skipCurrentTag(parser); } } - - private void readDisabledComponentsLP(PackageSettingBase packageSetting, - XmlPullParser parser) - throws IOException, XmlPullParserException { - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("item")) { - String name = parser.getAttributeValue(null, "name"); - if (name != null) { - packageSetting.disabledComponents.add(name.intern()); - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <disabled-components> has" - + " no name at " + parser.getPositionDescription()); - } + // writer + synchronized (mPackages) { + // If the platform SDK has changed since the last time we booted, + // we need to re-grant app permission to catch any new ones that + // appear. This is really a hack, and means that apps can in some + // cases get permissions that the user didn't initially explicitly + // allow... it would be nice to have some better way to handle + // this situation. + final boolean regrantPermissions = mSettings.mExternalSdkPlatform != mSdkVersion; + if (regrantPermissions) + Slog.i(TAG, "Platform changed from " + mSettings.mExternalSdkPlatform + " to " + + mSdkVersion + "; regranting permissions for external storage"); + mSettings.mExternalSdkPlatform = mSdkVersion; + + // Make sure group IDs have been assigned, and any permission + // changes in other apps are accounted for + updatePermissionsLPw(null, null, true, regrantPermissions, regrantPermissions); + // can downgrade to reader + // Persist settings + mSettings.writeLPr(); + } + // Send a broadcast to let everyone know we are done processing + if (pkgList.size() > 0) { + sendResourcesChangedBroadcast(true, pkgList, uidArr, null); + } + // Force gc to avoid any stale parser references that we might have. + if (doGc) { + Runtime.getRuntime().gc(); + } + // List stale containers and destroy stale temporary containers. + if (removeCids != null) { + for (String cid : removeCids) { + if (cid.startsWith(mTempContainerPrefix)) { + Log.i(TAG, "Destroying stale temporary container " + cid); + PackageHelper.destroySdDir(cid); } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <disabled-components>: " - + parser.getName()); - } - XmlUtils.skipCurrentTag(parser); - } + Log.w(TAG, "Container " + cid + " is stale"); + } + } } + } - private void readEnabledComponentsLP(PackageSettingBase packageSetting, - XmlPullParser parser) - throws IOException, XmlPullParserException { - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } + /* + * Utility method to unload a list of specified containers + */ + private void unloadAllContainers(Set<SdInstallArgs> cidArgs) { + // Just unmount all valid containers. + for (SdInstallArgs arg : cidArgs) { + synchronized (mInstallLock) { + arg.doPostDeleteLI(false); + } + } + } - String tagName = parser.getName(); - if (tagName.equals("item")) { - String name = parser.getAttributeValue(null, "name"); - if (name != null) { - packageSetting.enabledComponents.add(name.intern()); - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <enabled-components> has" - + " no name at " + parser.getPositionDescription()); - } + /* + * Unload packages mounted on external media. This involves deleting package + * data from internal structures, sending broadcasts about diabled packages, + * gc'ing to free up references, unmounting all secure containers + * corresponding to packages on external media, and posting a + * UPDATED_MEDIA_STATUS message if status has been requested. Please note + * that we always have to post this message if status has been requested no + * matter what. + */ + private void unloadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[], + final boolean reportStatus) { + if (DEBUG_SD_INSTALL) + Log.i(TAG, "unloading media packages"); + ArrayList<String> pkgList = new ArrayList<String>(); + ArrayList<SdInstallArgs> failedList = new ArrayList<SdInstallArgs>(); + final Set<SdInstallArgs> keys = processCids.keySet(); + for (SdInstallArgs args : keys) { + String pkgName = args.getPackageName(); + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Trying to unload pkg : " + pkgName); + // Delete package internally + PackageRemovedInfo outInfo = new PackageRemovedInfo(); + synchronized (mInstallLock) { + boolean res = deletePackageLI(pkgName, false, PackageManager.DONT_DELETE_DATA, + outInfo, false); + if (res) { + pkgList.add(pkgName); } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <enabled-components>: " - + parser.getName()); + Slog.e(TAG, "Failed to delete pkg from sdcard : " + pkgName); + failedList.add(args); } - XmlUtils.skipCurrentTag(parser); } } - private void readSharedUserLP(XmlPullParser parser) - throws XmlPullParserException, IOException { - String name = null; - String idStr = null; - int pkgFlags = 0; - SharedUserSetting su = null; - try { - name = parser.getAttributeValue(null, "name"); - idStr = parser.getAttributeValue(null, "userId"); - int userId = idStr != null ? Integer.parseInt(idStr) : 0; - if ("true".equals(parser.getAttributeValue(null, "system"))) { - pkgFlags |= ApplicationInfo.FLAG_SYSTEM; - } - if (name == null) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <shared-user> has no name at " - + parser.getPositionDescription()); - } else if (userId == 0) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: shared-user " - + name + " has bad userId " + idStr + " at " - + parser.getPositionDescription()); - } else { - if ((su=addSharedUserLP(name.intern(), userId, pkgFlags)) == null) { - reportSettingsProblem(Log.ERROR, - "Occurred while parsing settings at " - + parser.getPositionDescription()); - } - } - } catch (NumberFormatException e) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: package " - + name + " has bad userId " + idStr + " at " - + parser.getPositionDescription()); - }; - - if (su != null) { - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("sigs")) { - su.signatures.readXml(parser, mPastSignatures); - } else if (tagName.equals("perms")) { - readGrantedPermissionsLP(parser, su.grantedPermissions); - } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <shared-user>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } + // reader + synchronized (mPackages) { + // We didn't update the settings after removing each package; + // write them now for all packages. + mSettings.writeLPr(); + } + + // We have to absolutely send UPDATED_MEDIA_STATUS only + // after confirming that all the receivers processed the ordered + // broadcast when packages get disabled, force a gc to clean things up. + // and unload all the containers. + if (pkgList.size() > 0) { + sendResourcesChangedBroadcast(false, pkgList, uidArr, new IIntentReceiver.Stub() { + public void performReceive(Intent intent, int resultCode, String data, + Bundle extras, boolean ordered, boolean sticky) throws RemoteException { + Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, + reportStatus ? 1 : 0, 1, keys); + mHandler.sendMessage(msg); } - - } else { - XmlUtils.skipCurrentTag(parser); - } + }); + } else { + Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, reportStatus ? 1 : 0, -1, + keys); + mHandler.sendMessage(msg); } + } - private void readGrantedPermissionsLP(XmlPullParser parser, - HashSet<String> outPerms) throws IOException, XmlPullParserException { - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("item")) { - String name = parser.getAttributeValue(null, "name"); - if (name != null) { - outPerms.add(name.intern()); - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <perms> has" - + " no name at " + parser.getPositionDescription()); - } + public void movePackage(final String packageName, final IPackageMoveObserver observer, + final int flags) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null); + int returnCode = PackageManager.MOVE_SUCCEEDED; + int currFlags = 0; + int newFlags = 0; + // reader + synchronized (mPackages) { + PackageParser.Package pkg = mPackages.get(packageName); + if (pkg == null) { + returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST; + } else { + // Disable moving fwd locked apps and system packages + if (pkg.applicationInfo != null && isSystemApp(pkg)) { + Slog.w(TAG, "Cannot move system application"); + returnCode = PackageManager.MOVE_FAILED_SYSTEM_PACKAGE; + } else if (pkg.applicationInfo != null && isForwardLocked(pkg)) { + Slog.w(TAG, "Cannot move forward locked app."); + returnCode = PackageManager.MOVE_FAILED_FORWARD_LOCKED; + } else if (pkg.mOperationPending) { + Slog.w(TAG, "Attempt to move package which has pending operations"); + returnCode = PackageManager.MOVE_FAILED_OPERATION_PENDING; } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <perms>: " - + parser.getName()); - } - XmlUtils.skipCurrentTag(parser); - } - } - - private void readPreferredActivitiesLP(XmlPullParser parser) - throws XmlPullParserException, IOException { - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("item")) { - PreferredActivity pa = new PreferredActivity(parser); - if (pa.mPref.getParseError() == null) { - mPreferredActivities.addFilter(pa); + // Find install location first + if ((flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 + && (flags & PackageManager.MOVE_INTERNAL) != 0) { + Slog.w(TAG, "Ambigous flags specified for move location."); + returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION; } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <preferred-activity> " - + pa.mPref.getParseError() + " at " - + parser.getPositionDescription()); + newFlags = (flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 ? PackageManager.INSTALL_EXTERNAL + : PackageManager.INSTALL_INTERNAL; + currFlags = isExternal(pkg) ? PackageManager.INSTALL_EXTERNAL + : PackageManager.INSTALL_INTERNAL; + if (newFlags == currFlags) { + Slog.w(TAG, "No move required. Trying to move to same location"); + returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION; + } } - } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <preferred-activities>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - } - - // Returns -1 if we could not find an available UserId to assign - private int newUserIdLP(Object obj) { - // Let's be stupidly inefficient for now... - final int N = mUserIds.size(); - for (int i=0; i<N; i++) { - if (mUserIds.get(i) == null) { - mUserIds.set(i, obj); - return FIRST_APPLICATION_UID + i; - } - } - - // None left? - if (N >= MAX_APPLICATION_UIDS) { - return -1; - } - - mUserIds.add(obj); - return FIRST_APPLICATION_UID + N; - } - - public PackageSetting getDisabledSystemPkg(String name) { - synchronized(mPackages) { - PackageSetting ps = mDisabledSysPackages.get(name); - return ps; - } - } - - boolean isEnabledLP(ComponentInfo componentInfo, int flags) { - if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { - return true; - } - final PackageSetting packageSettings = mPackages.get(componentInfo.packageName); - if (Config.LOGV) { - Log.v(TAG, "isEnabledLock - packageName = " + componentInfo.packageName - + " componentName = " + componentInfo.name); - Log.v(TAG, "enabledComponents: " - + Arrays.toString(packageSettings.enabledComponents.toArray())); - Log.v(TAG, "disabledComponents: " - + Arrays.toString(packageSettings.disabledComponents.toArray())); - } - if (packageSettings == null) { - if (false) { - Log.w(TAG, "WAITING FOR DEBUGGER"); - Debug.waitForDebugger(); - Log.i(TAG, "We will crash!"); - } - return false; - } - if (packageSettings.enabled == COMPONENT_ENABLED_STATE_DISABLED - || (packageSettings.pkg != null && !packageSettings.pkg.applicationInfo.enabled - && packageSettings.enabled == COMPONENT_ENABLED_STATE_DEFAULT)) { - return false; - } - if (packageSettings.enabledComponents.contains(componentInfo.name)) { - return true; - } - if (packageSettings.disabledComponents.contains(componentInfo.name)) { - return false; - } - return componentInfo.enabled; - } - } - - // ------- apps on sdcard specific code ------- - static final boolean DEBUG_SD_INSTALL = false; - private static final String SD_ENCRYPTION_KEYSTORE_NAME = "AppsOnSD"; - private static final String SD_ENCRYPTION_ALGORITHM = "AES"; - static final int MAX_CONTAINERS = 250; - private boolean mMediaMounted = false; - - private String getEncryptKey() { - try { - String sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString( - SD_ENCRYPTION_KEYSTORE_NAME); - if (sdEncKey == null) { - sdEncKey = SystemKeyStore.getInstance().generateNewKeyHexString(128, - SD_ENCRYPTION_ALGORITHM, SD_ENCRYPTION_KEYSTORE_NAME); - if (sdEncKey == null) { - Slog.e(TAG, "Failed to create encryption keys"); - return null; - } - } - return sdEncKey; - } catch (NoSuchAlgorithmException nsae) { - Slog.e(TAG, "Failed to create encryption keys with exception: " + nsae); - return null; - } catch (IOException ioe) { - Slog.e(TAG, "Failed to retrieve encryption keys with exception: " - + ioe); - return null; - } - - } - - /* package */ static String getTempContainerId() { - int tmpIdx = 1; - String list[] = PackageHelper.getSecureContainerList(); - if (list != null) { - for (final String name : list) { - // Ignore null and non-temporary container entries - if (name == null || !name.startsWith(mTempContainerPrefix)) { - continue; - } - - String subStr = name.substring(mTempContainerPrefix.length()); - try { - int cid = Integer.parseInt(subStr); - if (cid >= tmpIdx) { - tmpIdx = cid + 1; + if (returnCode == PackageManager.MOVE_SUCCEEDED) { + pkg.mOperationPending = true; } - } catch (NumberFormatException e) { } } - } - return mTempContainerPrefix + tmpIdx; - } - - /* - * Update media status on PackageManager. - */ - public void updateExternalMediaStatus(final boolean mediaStatus, final boolean reportStatus) { - int callingUid = Binder.getCallingUid(); - if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { - throw new SecurityException("Media status can only be updated by the system"); - } - synchronized (mPackages) { - Log.i(TAG, "Updating external media status from " + - (mMediaMounted ? "mounted" : "unmounted") + " to " + - (mediaStatus ? "mounted" : "unmounted")); - if (DEBUG_SD_INSTALL) Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" + - mediaStatus+", mMediaMounted=" + mMediaMounted); - if (mediaStatus == mMediaMounted) { - Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, - reportStatus ? 1 : 0, -1); - mHandler.sendMessage(msg); - return; - } - mMediaMounted = mediaStatus; - } - // Queue up an async operation since the package installation may take a little while. - mHandler.post(new Runnable() { - public void run() { - mHandler.removeCallbacks(this); - updateExternalMediaStatusInner(mediaStatus, reportStatus); - } - }); - } - - /* - * Collect information of applications on external media, map them - * against existing containers and update information based on current - * mount status. Please note that we always have to report status - * if reportStatus has been set to true especially when unloading packages. - */ - private void updateExternalMediaStatusInner(boolean mediaStatus, - boolean reportStatus) { - // Collection of uids - int uidArr[] = null; - // Collection of stale containers - HashSet<String> removeCids = new HashSet<String>(); - // Collection of packages on external media with valid containers. - HashMap<SdInstallArgs, String> processCids = new HashMap<SdInstallArgs, String>(); - // Get list of secure containers. - final String list[] = PackageHelper.getSecureContainerList(); - if (list == null || list.length == 0) { - Log.i(TAG, "No secure containers on sdcard"); - } else { - // Process list of secure containers and categorize them - // as active or stale based on their package internal state. - int uidList[] = new int[list.length]; - int num = 0; - synchronized (mPackages) { - for (String cid : list) { - SdInstallArgs args = new SdInstallArgs(cid); - if (DEBUG_SD_INSTALL) Log.i(TAG, "Processing container " + cid); - String pkgName = args.getPackageName(); - if (pkgName == null) { - if (DEBUG_SD_INSTALL) Log.i(TAG, "Container : " + cid + " stale"); - removeCids.add(cid); - continue; - } - if (DEBUG_SD_INSTALL) Log.i(TAG, "Looking for pkg : " + pkgName); - PackageSetting ps = mSettings.mPackages.get(pkgName); - // The package status is changed only if the code path - // matches between settings and the container id. - if (ps != null && ps.codePathString != null && - ps.codePathString.equals(args.getCodePath())) { - if (DEBUG_SD_INSTALL) Log.i(TAG, "Container : " + cid + - " corresponds to pkg : " + pkgName + - " at code path: " + ps.codePathString); - // We do have a valid package installed on sdcard - processCids.put(args, ps.codePathString); - int uid = ps.userId; - if (uid != -1) { - uidList[num++] = uid; - } - } else { - // Stale container on sdcard. Just delete - if (DEBUG_SD_INSTALL) Log.i(TAG, "Container : " + cid + " stale"); - removeCids.add(cid); - } - } - } - - if (num > 0) { - // Sort uid list - Arrays.sort(uidList, 0, num); - // Throw away duplicates - uidArr = new int[num]; - uidArr[0] = uidList[0]; - int di = 0; - for (int i = 1; i < num; i++) { - if (uidList[i-1] != uidList[i]) { - uidArr[di++] = uidList[i]; - } - } - } - } - // Process packages with valid entries. - if (mediaStatus) { - if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading packages"); - loadMediaPackages(processCids, uidArr, removeCids); - startCleaningPackages(); - } else { - if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading packages"); - unloadMediaPackages(processCids, uidArr, reportStatus); - } - } - - private void sendResourcesChangedBroadcast(boolean mediaStatus, - ArrayList<String> pkgList, int uidArr[], IIntentReceiver finishedReceiver) { - int size = pkgList.size(); - if (size > 0) { - // Send broadcasts here - Bundle extras = new Bundle(); - extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, - pkgList.toArray(new String[size])); - if (uidArr != null) { - extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr); - } - String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE - : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE; - sendPackageBroadcast(action, null, extras, null, finishedReceiver); - } - } - - /* - * Look at potentially valid container ids from processCids - * If package information doesn't match the one on record - * or package scanning fails, the cid is added to list of - * removeCids. We currently don't delete stale containers. - */ - private void loadMediaPackages(HashMap<SdInstallArgs, String> processCids, - int uidArr[], HashSet<String> removeCids) { - ArrayList<String> pkgList = new ArrayList<String>(); - Set<SdInstallArgs> keys = processCids.keySet(); - boolean doGc = false; - for (SdInstallArgs args : keys) { - String codePath = processCids.get(args); - if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading container : " - + args.cid); - int retCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR; - try { - // Make sure there are no container errors first. - if (args.doPreInstall(PackageManager.INSTALL_SUCCEEDED) - != PackageManager.INSTALL_SUCCEEDED) { - Slog.e(TAG, "Failed to mount cid : " + args.cid + - " when installing from sdcard"); - continue; - } - // Check code path here. - if (codePath == null || !codePath.equals(args.getCodePath())) { - Slog.e(TAG, "Container " + args.cid + " cachepath " + args.getCodePath()+ - " does not match one in settings " + codePath); - continue; - } - // Parse package - int parseFlags = PackageParser.PARSE_ON_SDCARD | mDefParseFlags; - doGc = true; - synchronized (mInstallLock) { - final PackageParser.Package pkg = scanPackageLI(new File(codePath), - parseFlags, 0, 0); - // Scan the package - if (pkg != null) { - synchronized (mPackages) { - retCode = PackageManager.INSTALL_SUCCEEDED; - pkgList.add(pkg.packageName); - // Post process args - args.doPostInstall(PackageManager.INSTALL_SUCCEEDED); - } - } else { - Slog.i(TAG, "Failed to install pkg from " + - codePath + " from sdcard"); - } - } - } finally { - if (retCode != PackageManager.INSTALL_SUCCEEDED) { - // Don't destroy container here. Wait till gc clears things up. - removeCids.add(args.cid); - } - } - } - synchronized (mPackages) { - // If the platform SDK has changed since the last time we booted, - // we need to re-grant app permission to catch any new ones that - // appear. This is really a hack, and means that apps can in some - // cases get permissions that the user didn't initially explicitly - // allow... it would be nice to have some better way to handle - // this situation. - final boolean regrantPermissions = mSettings.mExternalSdkPlatform - != mSdkVersion; - if (regrantPermissions) Slog.i(TAG, "Platform changed from " - + mSettings.mExternalSdkPlatform + " to " + mSdkVersion - + "; regranting permissions for external storage"); - mSettings.mExternalSdkPlatform = mSdkVersion; - - // Make sure group IDs have been assigned, and any permission - // changes in other apps are accounted for - updatePermissionsLP(null, null, true, regrantPermissions, regrantPermissions); - // Persist settings - mSettings.writeLP(); - } - // Send a broadcast to let everyone know we are done processing - if (pkgList.size() > 0) { - sendResourcesChangedBroadcast(true, pkgList, uidArr, null); - } - // Force gc to avoid any stale parser references that we might have. - if (doGc) { - Runtime.getRuntime().gc(); - } - // List stale containers and destroy stale temporary containers. - if (removeCids != null) { - for (String cid : removeCids) { - if (cid.startsWith(mTempContainerPrefix)) { - Log.i(TAG, "Destroying stale temporary container " + cid); - PackageHelper.destroySdDir(cid); - } else { - Log.w(TAG, "Container " + cid + " is stale"); - } - } - } - } - - /* - * Utility method to unload a list of specified containers - */ - private void unloadAllContainers(Set<SdInstallArgs> cidArgs) { - // Just unmount all valid containers. - for (SdInstallArgs arg : cidArgs) { - synchronized (mInstallLock) { - arg.doPostDeleteLI(false); - } - } - } - - /* - * Unload packages mounted on external media. This involves deleting - * package data from internal structures, sending broadcasts about - * diabled packages, gc'ing to free up references, unmounting all - * secure containers corresponding to packages on external media, and - * posting a UPDATED_MEDIA_STATUS message if status has been requested. - * Please note that we always have to post this message if status has - * been requested no matter what. - */ - private void unloadMediaPackages(HashMap<SdInstallArgs, String> processCids, - int uidArr[], final boolean reportStatus) { - if (DEBUG_SD_INSTALL) Log.i(TAG, "unloading media packages"); - ArrayList<String> pkgList = new ArrayList<String>(); - ArrayList<SdInstallArgs> failedList = new ArrayList<SdInstallArgs>(); - final Set<SdInstallArgs> keys = processCids.keySet(); - for (SdInstallArgs args : keys) { - String cid = args.cid; - String pkgName = args.getPackageName(); - if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to unload pkg : " + pkgName); - // Delete package internally - PackageRemovedInfo outInfo = new PackageRemovedInfo(); - synchronized (mInstallLock) { - boolean res = deletePackageLI(pkgName, false, - PackageManager.DONT_DELETE_DATA, outInfo, false); - if (res) { - pkgList.add(pkgName); - } else { - Slog.e(TAG, "Failed to delete pkg from sdcard : " + pkgName); - failedList.add(args); - } - } - } - - synchronized (mPackages) { - // We didn't update the settings after removing each package; - // write them now for all packages. - mSettings.writeLP(); - } - - // We have to absolutely send UPDATED_MEDIA_STATUS only - // after confirming that all the receivers processed the ordered - // broadcast when packages get disabled, force a gc to clean things up. - // and unload all the containers. - if (pkgList.size() > 0) { - sendResourcesChangedBroadcast(false, pkgList, uidArr, new IIntentReceiver.Stub() { - public void performReceive(Intent intent, int resultCode, String data, Bundle extras, - boolean ordered, boolean sticky) throws RemoteException { - Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, - reportStatus ? 1 : 0, 1, keys); - mHandler.sendMessage(msg); - } - }); - } else { - Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, - reportStatus ? 1 : 0, -1, keys); - mHandler.sendMessage(msg); - } - } - - public void movePackage(final String packageName, - final IPackageMoveObserver observer, final int flags) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.MOVE_PACKAGE, null); - int returnCode = PackageManager.MOVE_SUCCEEDED; - int currFlags = 0; - int newFlags = 0; - synchronized (mPackages) { - PackageParser.Package pkg = mPackages.get(packageName); - if (pkg == null) { - returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST; - } else { - // Disable moving fwd locked apps and system packages - if (pkg.applicationInfo != null && isSystemApp(pkg)) { - Slog.w(TAG, "Cannot move system application"); - returnCode = PackageManager.MOVE_FAILED_SYSTEM_PACKAGE; - } else if (pkg.applicationInfo != null && isForwardLocked(pkg)) { - Slog.w(TAG, "Cannot move forward locked app."); - returnCode = PackageManager.MOVE_FAILED_FORWARD_LOCKED; - } else if (pkg.mOperationPending) { - Slog.w(TAG, "Attempt to move package which has pending operations"); - returnCode = PackageManager.MOVE_FAILED_OPERATION_PENDING; - } else { - // Find install location first - if ((flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 && - (flags & PackageManager.MOVE_INTERNAL) != 0) { - Slog.w(TAG, "Ambigous flags specified for move location."); - returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION; - } else { - newFlags = (flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 ? - PackageManager.INSTALL_EXTERNAL : PackageManager.INSTALL_INTERNAL; - currFlags = isExternal(pkg) ? PackageManager.INSTALL_EXTERNAL - : PackageManager.INSTALL_INTERNAL; - if (newFlags == currFlags) { - Slog.w(TAG, "No move required. Trying to move to same location"); - returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION; - } - } - if (returnCode == PackageManager.MOVE_SUCCEEDED) { - pkg.mOperationPending = true; - } - } - } - if (returnCode != PackageManager.MOVE_SUCCEEDED) { + /* + * TODO this next block probably shouldn't be inside the lock. We + * can't guarantee these won't change after this is fired off + * anyway. + */ + if (returnCode != PackageManager.MOVE_SUCCEEDED) { processPendingMove(new MoveParams(null, observer, 0, packageName, null), returnCode); - } else { - Message msg = mHandler.obtainMessage(INIT_COPY); - InstallArgs srcArgs = createInstallArgs(currFlags, pkg.applicationInfo.sourceDir, + } else { + Message msg = mHandler.obtainMessage(INIT_COPY); + InstallArgs srcArgs = createInstallArgs(currFlags, pkg.applicationInfo.sourceDir, pkg.applicationInfo.publicSourceDir, pkg.applicationInfo.nativeLibraryDir); - MoveParams mp = new MoveParams(srcArgs, observer, newFlags, packageName, + MoveParams mp = new MoveParams(srcArgs, observer, newFlags, packageName, pkg.applicationInfo.dataDir); - msg.obj = mp; - mHandler.sendMessage(msg); - } - } - } + msg.obj = mp; + mHandler.sendMessage(msg); + } + } + } - private void processPendingMove(final MoveParams mp, final int currentStatus) { - // Queue up an async operation since the package deletion may take a little while. - mHandler.post(new Runnable() { - public void run() { - mHandler.removeCallbacks(this); - int returnCode = currentStatus; - if (currentStatus == PackageManager.MOVE_SUCCEEDED) { - int uidArr[] = null; - ArrayList<String> pkgList = null; - synchronized (mPackages) { - PackageParser.Package pkg = mPackages.get(mp.packageName); - if (pkg == null) { - Slog.w(TAG, " Package " + mp.packageName + - " doesn't exist. Aborting move"); - returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST; - } else if (!mp.srcArgs.getCodePath().equals(pkg.applicationInfo.sourceDir)) { - Slog.w(TAG, "Package " + mp.packageName + " code path changed from " + - mp.srcArgs.getCodePath() + " to " + pkg.applicationInfo.sourceDir + - " Aborting move and returning error"); - returnCode = PackageManager.MOVE_FAILED_INTERNAL_ERROR; - } else { - uidArr = new int[] { pkg.applicationInfo.uid }; - pkgList = new ArrayList<String>(); - pkgList.add(mp.packageName); - } - } - if (returnCode == PackageManager.MOVE_SUCCEEDED) { - // Send resources unavailable broadcast - sendResourcesChangedBroadcast(false, pkgList, uidArr, null); - // Update package code and resource paths - synchronized (mInstallLock) { - synchronized (mPackages) { - PackageParser.Package pkg = mPackages.get(mp.packageName); - // Recheck for package again. + private void processPendingMove(final MoveParams mp, final int currentStatus) { + // Queue up an async operation since the package deletion may take a + // little while. + mHandler.post(new Runnable() { + public void run() { + // TODO fix this; this does nothing. + mHandler.removeCallbacks(this); + int returnCode = currentStatus; + if (currentStatus == PackageManager.MOVE_SUCCEEDED) { + int uidArr[] = null; + ArrayList<String> pkgList = null; + synchronized (mPackages) { + PackageParser.Package pkg = mPackages.get(mp.packageName); + if (pkg == null) { + Slog.w(TAG, " Package " + mp.packageName + + " doesn't exist. Aborting move"); + returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST; + } else if (!mp.srcArgs.getCodePath().equals(pkg.applicationInfo.sourceDir)) { + Slog.w(TAG, "Package " + mp.packageName + " code path changed from " + + mp.srcArgs.getCodePath() + " to " + + pkg.applicationInfo.sourceDir + + " Aborting move and returning error"); + returnCode = PackageManager.MOVE_FAILED_INTERNAL_ERROR; + } else { + uidArr = new int[] { + pkg.applicationInfo.uid + }; + pkgList = new ArrayList<String>(); + pkgList.add(mp.packageName); + } + } + if (returnCode == PackageManager.MOVE_SUCCEEDED) { + // Send resources unavailable broadcast + sendResourcesChangedBroadcast(false, pkgList, uidArr, null); + // Update package code and resource paths + synchronized (mInstallLock) { + synchronized (mPackages) { + PackageParser.Package pkg = mPackages.get(mp.packageName); + // Recheck for package again. if (pkg == null) { Slog.w(TAG, " Package " + mp.packageName + " doesn't exist. Aborting move"); returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST; - } else if (!mp.srcArgs.getCodePath().equals(pkg.applicationInfo.sourceDir)) { - Slog.w(TAG, "Package " + mp.packageName + " code path changed from " + - mp.srcArgs.getCodePath() + " to " + pkg.applicationInfo.sourceDir + - " Aborting move and returning error"); - returnCode = PackageManager.MOVE_FAILED_INTERNAL_ERROR; - } else { - final String oldCodePath = pkg.mPath; - final String newCodePath = mp.targetArgs.getCodePath(); - final String newResPath = mp.targetArgs.getResourcePath(); - final String newNativePath = mp.targetArgs.getNativeLibraryPath(); + } else if (!mp.srcArgs.getCodePath().equals( + pkg.applicationInfo.sourceDir)) { + Slog.w(TAG, "Package " + mp.packageName + + " code path changed from " + mp.srcArgs.getCodePath() + + " to " + pkg.applicationInfo.sourceDir + + " Aborting move and returning error"); + returnCode = PackageManager.MOVE_FAILED_INTERNAL_ERROR; + } else { + final String oldCodePath = pkg.mPath; + final String newCodePath = mp.targetArgs.getCodePath(); + final String newResPath = mp.targetArgs.getResourcePath(); + final String newNativePath = mp.targetArgs + .getNativeLibraryPath(); if ((mp.flags & PackageManager.INSTALL_EXTERNAL) == 0) { if (mInstaller .unlinkNativeLibraryDirectory(pkg.applicationInfo.dataDir) < 0) { returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE; } else { - NativeLibraryHelper.copyNativeBinariesLI( - new File(newCodePath), new File(newNativePath)); + NativeLibraryHelper.copyNativeBinariesLI(new File( + newCodePath), new File(newNativePath)); } } else { if (mInstaller.linkNativeLibraryDirectory( @@ -10492,90 +8012,106 @@ class PackageManagerService extends IPackageManager.Stub { } if (returnCode == PackageManager.MOVE_SUCCEEDED) { - pkg.mScanPath = newCodePath; - pkg.applicationInfo.sourceDir = newCodePath; - pkg.applicationInfo.publicSourceDir = newResPath; - pkg.applicationInfo.nativeLibraryDir = newNativePath; - PackageSetting ps = (PackageSetting) pkg.mExtras; - ps.codePath = new File(pkg.applicationInfo.sourceDir); - ps.codePathString = ps.codePath.getPath(); - ps.resourcePath = new File(pkg.applicationInfo.publicSourceDir); - ps.resourcePathString = ps.resourcePath.getPath(); - ps.nativeLibraryPathString = newNativePath; - // Set the application info flag correctly. - if ((mp.flags & PackageManager.INSTALL_EXTERNAL) != 0) { - pkg.applicationInfo.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE; - } else { - pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_EXTERNAL_STORAGE; - } - ps.setFlags(pkg.applicationInfo.flags); - mAppDirs.remove(oldCodePath); - mAppDirs.put(newCodePath, pkg); - // Persist settings - mSettings.writeLP(); - } - } - } - } - // Send resources available broadcast - sendResourcesChangedBroadcast(true, pkgList, uidArr, null); - } - } - if (returnCode != PackageManager.MOVE_SUCCEEDED){ - // Clean up failed installation - if (mp.targetArgs != null) { - mp.targetArgs.doPostInstall(PackageManager.INSTALL_FAILED_INTERNAL_ERROR); - } - } else { - // Force a gc to clear things up. - Runtime.getRuntime().gc(); - // Delete older code - synchronized (mInstallLock) { - mp.srcArgs.doPostDeleteLI(true); - } - } + pkg.mScanPath = newCodePath; + pkg.applicationInfo.sourceDir = newCodePath; + pkg.applicationInfo.publicSourceDir = newResPath; + pkg.applicationInfo.nativeLibraryDir = newNativePath; + PackageSetting ps = (PackageSetting) pkg.mExtras; + ps.codePath = new File(pkg.applicationInfo.sourceDir); + ps.codePathString = ps.codePath.getPath(); + ps.resourcePath = new File( + pkg.applicationInfo.publicSourceDir); + ps.resourcePathString = ps.resourcePath.getPath(); + ps.nativeLibraryPathString = newNativePath; + // Set the application info flag + // correctly. + if ((mp.flags & PackageManager.INSTALL_EXTERNAL) != 0) { + pkg.applicationInfo.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE; + } else { + pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_EXTERNAL_STORAGE; + } + ps.setFlags(pkg.applicationInfo.flags); + mAppDirs.remove(oldCodePath); + mAppDirs.put(newCodePath, pkg); + // Persist settings + mSettings.writeLPr(); + } + } + } + } + // Send resources available broadcast + sendResourcesChangedBroadcast(true, pkgList, uidArr, null); + } + } + if (returnCode != PackageManager.MOVE_SUCCEEDED) { + // Clean up failed installation + if (mp.targetArgs != null) { + mp.targetArgs.doPostInstall(PackageManager.INSTALL_FAILED_INTERNAL_ERROR); + } + } else { + // Force a gc to clear things up. + Runtime.getRuntime().gc(); + // Delete older code + synchronized (mInstallLock) { + mp.srcArgs.doPostDeleteLI(true); + } + } - // Allow more operations on this file if we didn't fail because - // an operation was already pending for this package. - if (returnCode != PackageManager.MOVE_FAILED_OPERATION_PENDING) { - synchronized (mPackages) { - PackageParser.Package pkg = mPackages.get(mp.packageName); - if (pkg != null) { - pkg.mOperationPending = false; + // Allow more operations on this file if we didn't fail because + // an operation was already pending for this package. + if (returnCode != PackageManager.MOVE_FAILED_OPERATION_PENDING) { + synchronized (mPackages) { + PackageParser.Package pkg = mPackages.get(mp.packageName); + if (pkg != null) { + pkg.mOperationPending = false; } } - } + } - IPackageMoveObserver observer = mp.observer; - if (observer != null) { - try { - observer.packageMoved(mp.packageName, returnCode); - } catch (RemoteException e) { - Log.i(TAG, "Observer no longer exists."); - } - } - } - }); - } + IPackageMoveObserver observer = mp.observer; + if (observer != null) { + try { + observer.packageMoved(mp.packageName, returnCode); + } catch (RemoteException e) { + Log.i(TAG, "Observer no longer exists."); + } + } + } + }); + } - public boolean setInstallLocation(int loc) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.WRITE_SECURE_SETTINGS, null); - if (getInstallLocation() == loc) { - return true; - } - if (loc == PackageHelper.APP_INSTALL_AUTO || - loc == PackageHelper.APP_INSTALL_INTERNAL || - loc == PackageHelper.APP_INSTALL_EXTERNAL) { - android.provider.Settings.System.putInt(mContext.getContentResolver(), - android.provider.Settings.Secure.DEFAULT_INSTALL_LOCATION, loc); - return true; - } - return false; + public boolean setInstallLocation(int loc) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, + null); + if (getInstallLocation() == loc) { + return true; + } + if (loc == PackageHelper.APP_INSTALL_AUTO || loc == PackageHelper.APP_INSTALL_INTERNAL + || loc == PackageHelper.APP_INSTALL_EXTERNAL) { + android.provider.Settings.System.putInt(mContext.getContentResolver(), + android.provider.Settings.Secure.DEFAULT_INSTALL_LOCATION, loc); + return true; + } + return false; } - public int getInstallLocation() { - return android.provider.Settings.System.getInt(mContext.getContentResolver(), - android.provider.Settings.Secure.DEFAULT_INSTALL_LOCATION, PackageHelper.APP_INSTALL_AUTO); - } + public int getInstallLocation() { + return android.provider.Settings.System.getInt(mContext.getContentResolver(), + android.provider.Settings.Secure.DEFAULT_INSTALL_LOCATION, + PackageHelper.APP_INSTALL_AUTO); + } + + public UserInfo createUser(String name, int flags) { + // TODO(kroot): fix this API + UserInfo userInfo = mUserManager.createUser(name, flags, new ArrayList<ApplicationInfo>()); + return userInfo; + } + + public boolean removeUser(int userId) { + if (userId == 0) { + return false; + } + mUserManager.removeUser(userId); + return true; + } } diff --git a/services/java/com/android/server/pm/PackageSetting.java b/services/java/com/android/server/pm/PackageSetting.java new file mode 100644 index 0000000..efdc2b3 --- /dev/null +++ b/services/java/com/android/server/pm/PackageSetting.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.content.pm.PackageParser; + +import java.io.File; + +/** + * Settings data for a particular package we know about. + */ +final class PackageSetting extends PackageSettingBase { + int userId; + PackageParser.Package pkg; + SharedUserSetting sharedUser; + + PackageSetting(String name, String realName, File codePath, File resourcePath, + String nativeLibraryPathString, int pVersionCode, int pkgFlags) { + super(name, realName, codePath, resourcePath, nativeLibraryPathString, pVersionCode, + pkgFlags); + } + + /** + * New instance of PackageSetting replicating the original settings. + * Note that it keeps the same PackageParser.Package instance. + */ + PackageSetting(PackageSetting orig) { + super(orig); + + userId = orig.userId; + pkg = orig.pkg; + sharedUser = orig.sharedUser; + } + + @Override + public String toString() { + return "PackageSetting{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + name + "/" + userId + "}"; + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/pm/PackageSettingBase.java b/services/java/com/android/server/pm/PackageSettingBase.java new file mode 100644 index 0000000..e2f83ad --- /dev/null +++ b/services/java/com/android/server/pm/PackageSettingBase.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + + +import java.io.File; +import java.util.HashSet; + +/** + * Settings base class for pending and resolved classes. + */ +class PackageSettingBase extends GrantedPermissions { + /** + * Indicates the state of installation. Used by PackageManager to figure out + * incomplete installations. Say a package is being installed (the state is + * set to PKG_INSTALL_INCOMPLETE) and remains so till the package + * installation is successful or unsuccessful in which case the + * PackageManager will no longer maintain state information associated with + * the package. If some exception(like device freeze or battery being pulled + * out) occurs during installation of a package, the PackageManager needs + * this information to clean up the previously failed installation. + */ + static final int PKG_INSTALL_COMPLETE = 1; + static final int PKG_INSTALL_INCOMPLETE = 0; + + final String name; + final String realName; + File codePath; + String codePathString; + File resourcePath; + String resourcePathString; + String nativeLibraryPathString; + long timeStamp; + long firstInstallTime; + long lastUpdateTime; + int versionCode; + + boolean uidError; + + PackageSignatures signatures = new PackageSignatures(); + + boolean permissionsFixed; + boolean haveGids; + + // Whether this package is currently stopped, thus can not be + // started until explicitly launched by the user. + public boolean stopped; + + // Set to true if we have never launched this app. + public boolean notLaunched; + + /* Explicitly disabled components */ + HashSet<String> disabledComponents = new HashSet<String>(0); + /* Explicitly enabled components */ + HashSet<String> enabledComponents = new HashSet<String>(0); + int enabled = COMPONENT_ENABLED_STATE_DEFAULT; + int installStatus = PKG_INSTALL_COMPLETE; + + PackageSettingBase origPackage; + + /* package name of the app that installed this package */ + String installerPackageName; + PackageSettingBase(String name, String realName, File codePath, File resourcePath, + String nativeLibraryPathString, int pVersionCode, int pkgFlags) { + super(pkgFlags); + this.name = name; + this.realName = realName; + init(codePath, resourcePath, nativeLibraryPathString, pVersionCode); + } + + /** + * New instance of PackageSetting with one-level-deep cloning. + */ + @SuppressWarnings("unchecked") + PackageSettingBase(PackageSettingBase base) { + super(base); + + name = base.name; + realName = base.realName; + codePath = base.codePath; + codePathString = base.codePathString; + resourcePath = base.resourcePath; + resourcePathString = base.resourcePathString; + nativeLibraryPathString = base.nativeLibraryPathString; + timeStamp = base.timeStamp; + firstInstallTime = base.firstInstallTime; + lastUpdateTime = base.lastUpdateTime; + versionCode = base.versionCode; + + uidError = base.uidError; + + signatures = new PackageSignatures(base.signatures); + + permissionsFixed = base.permissionsFixed; + haveGids = base.haveGids; + stopped = base.stopped; + notLaunched = base.notLaunched; + + disabledComponents = (HashSet<String>) base.disabledComponents.clone(); + + enabledComponents = (HashSet<String>) base.enabledComponents.clone(); + + enabled = base.enabled; + installStatus = base.installStatus; + + origPackage = base.origPackage; + + installerPackageName = base.installerPackageName; + } + + void init(File codePath, File resourcePath, String nativeLibraryPathString, + int pVersionCode) { + this.codePath = codePath; + this.codePathString = codePath.toString(); + this.resourcePath = resourcePath; + this.resourcePathString = resourcePath.toString(); + this.nativeLibraryPathString = nativeLibraryPathString; + this.versionCode = pVersionCode; + } + + public void setInstallerPackageName(String packageName) { + installerPackageName = packageName; + } + + String getInstallerPackageName() { + return installerPackageName; + } + + public void setInstallStatus(int newStatus) { + installStatus = newStatus; + } + + public int getInstallStatus() { + return installStatus; + } + + public void setTimeStamp(long newStamp) { + timeStamp = newStamp; + } + + /** + * Make a shallow copy of this package settings. + */ + public void copyFrom(PackageSettingBase base) { + grantedPermissions = base.grantedPermissions; + gids = base.gids; + + timeStamp = base.timeStamp; + firstInstallTime = base.firstInstallTime; + lastUpdateTime = base.lastUpdateTime; + signatures = base.signatures; + permissionsFixed = base.permissionsFixed; + haveGids = base.haveGids; + stopped = base.stopped; + notLaunched = base.notLaunched; + disabledComponents = base.disabledComponents; + enabledComponents = base.enabledComponents; + enabled = base.enabled; + installStatus = base.installStatus; + } + + boolean enableComponentLPw(String componentClassName) { + boolean changed = disabledComponents.remove(componentClassName); + changed |= enabledComponents.add(componentClassName); + return changed; + } + + boolean disableComponentLPw(String componentClassName) { + boolean changed = enabledComponents.remove(componentClassName); + changed |= disabledComponents.add(componentClassName); + return changed; + } + + boolean restoreComponentLPw(String componentClassName) { + boolean changed = enabledComponents.remove(componentClassName); + changed |= disabledComponents.remove(componentClassName); + return changed; + } + + int getCurrentEnabledStateLPr(String componentName) { + if (enabledComponents.contains(componentName)) { + return COMPONENT_ENABLED_STATE_ENABLED; + } else if (disabledComponents.contains(componentName)) { + return COMPONENT_ENABLED_STATE_DISABLED; + } else { + return COMPONENT_ENABLED_STATE_DEFAULT; + } + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/pm/PackageSignatures.java b/services/java/com/android/server/pm/PackageSignatures.java new file mode 100644 index 0000000..a25ec6c --- /dev/null +++ b/services/java/com/android/server/pm/PackageSignatures.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import android.content.pm.Signature; +import android.util.Log; + +import java.io.IOException; +import java.util.ArrayList; + +class PackageSignatures { + Signature[] mSignatures; + + PackageSignatures(PackageSignatures orig) { + if (orig != null && orig.mSignatures != null) { + mSignatures = orig.mSignatures.clone(); + } + } + + PackageSignatures(Signature[] sigs) { + assignSignatures(sigs); + } + + PackageSignatures() { + } + + void writeXml(XmlSerializer serializer, String tagName, + ArrayList<Signature> pastSignatures) throws IOException { + if (mSignatures == null) { + return; + } + serializer.startTag(null, tagName); + serializer.attribute(null, "count", + Integer.toString(mSignatures.length)); + for (int i=0; i<mSignatures.length; i++) { + serializer.startTag(null, "cert"); + final Signature sig = mSignatures[i]; + final int sigHash = sig.hashCode(); + final int numPast = pastSignatures.size(); + int j; + for (j=0; j<numPast; j++) { + Signature pastSig = pastSignatures.get(j); + if (pastSig.hashCode() == sigHash && pastSig.equals(sig)) { + serializer.attribute(null, "index", Integer.toString(j)); + break; + } + } + if (j >= numPast) { + pastSignatures.add(sig); + serializer.attribute(null, "index", Integer.toString(numPast)); + serializer.attribute(null, "key", sig.toCharsString()); + } + serializer.endTag(null, "cert"); + } + serializer.endTag(null, tagName); + } + + void readXml(XmlPullParser parser, ArrayList<Signature> pastSignatures) + throws IOException, XmlPullParserException { + String countStr = parser.getAttributeValue(null, "count"); + if (countStr == null) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <signatures> has" + + " no count at " + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + } + final int count = Integer.parseInt(countStr); + mSignatures = new Signature[count]; + int pos = 0; + + int outerDepth = parser.getDepth(); + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("cert")) { + if (pos < count) { + String index = parser.getAttributeValue(null, "index"); + if (index != null) { + try { + int idx = Integer.parseInt(index); + String key = parser.getAttributeValue(null, "key"); + if (key == null) { + if (idx >= 0 && idx < pastSignatures.size()) { + Signature sig = pastSignatures.get(idx); + if (sig != null) { + mSignatures[pos] = pastSignatures.get(idx); + pos++; + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <cert> " + + "index " + index + " is not defined at " + + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <cert> " + + "index " + index + " is out of bounds at " + + parser.getPositionDescription()); + } + } else { + while (pastSignatures.size() <= idx) { + pastSignatures.add(null); + } + Signature sig = new Signature(key); + pastSignatures.set(idx, sig); + mSignatures[pos] = sig; + pos++; + } + } catch (NumberFormatException e) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <cert> " + + "index " + index + " is not a number at " + + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <cert> has" + + " no index at " + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: too " + + "many <cert> tags, expected " + count + + " at " + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <cert>: " + + parser.getName()); + } + XmlUtils.skipCurrentTag(parser); + } + + if (pos < count) { + // Should never happen -- there is an error in the written + // settings -- but if it does we don't want to generate + // a bad array. + Signature[] newSigs = new Signature[pos]; + System.arraycopy(mSignatures, 0, newSigs, 0, pos); + mSignatures = newSigs; + } + } + + void assignSignatures(Signature[] sigs) { + if (sigs == null) { + mSignatures = null; + return; + } + mSignatures = new Signature[sigs.length]; + for (int i=0; i<sigs.length; i++) { + mSignatures[i] = sigs[i]; + } + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(128); + buf.append("PackageSignatures{"); + buf.append(Integer.toHexString(System.identityHashCode(this))); + buf.append(" ["); + if (mSignatures != null) { + for (int i=0; i<mSignatures.length; i++) { + if (i > 0) buf.append(", "); + buf.append(Integer.toHexString( + System.identityHashCode(mSignatures[i]))); + } + } + buf.append("]}"); + return buf.toString(); + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/pm/PendingPackage.java b/services/java/com/android/server/pm/PendingPackage.java new file mode 100644 index 0000000..c17cc46 --- /dev/null +++ b/services/java/com/android/server/pm/PendingPackage.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import java.io.File; + +final class PendingPackage extends PackageSettingBase { + final int sharedId; + + PendingPackage(String name, String realName, File codePath, File resourcePath, + String nativeLibraryPathString, int sharedId, int pVersionCode, int pkgFlags) { + super(name, realName, codePath, resourcePath, nativeLibraryPathString, pVersionCode, + pkgFlags); + this.sharedId = sharedId; + } +} diff --git a/services/java/com/android/server/pm/PreferredActivity.java b/services/java/com/android/server/pm/PreferredActivity.java new file mode 100644 index 0000000..b100eb1 --- /dev/null +++ b/services/java/com/android/server/pm/PreferredActivity.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import com.android.internal.util.XmlUtils; +import com.android.server.PreferredComponent; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import android.content.ComponentName; +import android.content.IntentFilter; +import android.util.Log; + +import java.io.IOException; + +class PreferredActivity extends IntentFilter implements PreferredComponent.Callbacks { + private static final String TAG = "PreferredActivity"; + + private static final boolean DEBUG_FILTERS = false; + + final PreferredComponent mPref; + + PreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) { + super(filter); + mPref = new PreferredComponent(this, match, set, activity); + } + + PreferredActivity(XmlPullParser parser) throws XmlPullParserException, IOException { + mPref = new PreferredComponent(this, parser); + } + + public void writeToXml(XmlSerializer serializer) throws IOException { + mPref.writeToXml(serializer); + serializer.startTag(null, "filter"); + super.writeToXml(serializer); + serializer.endTag(null, "filter"); + } + + public boolean onReadTag(String tagName, XmlPullParser parser) throws XmlPullParserException, + IOException { + if (tagName.equals("filter")) { + if (DEBUG_FILTERS) { + Log.i(TAG, "Starting to parse filter..."); + } + readFromXml(parser); + if (DEBUG_FILTERS) { + Log.i(TAG, "Finished filter: depth=" + parser.getDepth() + " tag=" + + parser.getName()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <preferred-activities>: " + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + return true; + } +} diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java new file mode 100644 index 0000000..2720bf8 --- /dev/null +++ b/services/java/com/android/server/pm/Settings.java @@ -0,0 +1,2230 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.JournaledFile; +import com.android.internal.util.XmlUtils; +import com.android.server.IntentResolver; +import com.android.server.pm.PackageManagerService.DumpState; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.ComponentInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageParser; +import android.content.pm.PermissionInfo; +import android.content.pm.Signature; +import android.os.Binder; +import android.os.Environment; +import android.os.FileUtils; +import android.os.Process; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.util.Xml; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; + +/** + * Holds information about dynamic settings. + */ +final class Settings { + private static final String TAG = "PackageSettings"; + + private static final boolean DEBUG_STOPPED = false; + + private final File mSettingsFilename; + private final File mBackupSettingsFilename; + private final File mPackageListFilename; + private final File mStoppedPackagesFilename; + private final File mBackupStoppedPackagesFilename; + final HashMap<String, PackageSetting> mPackages = + new HashMap<String, PackageSetting>(); + // List of replaced system applications + final HashMap<String, PackageSetting> mDisabledSysPackages = + new HashMap<String, PackageSetting>(); + + // These are the last platform API version we were using for + // the apps installed on internal and external storage. It is + // used to grant newer permissions one time during a system upgrade. + int mInternalSdkPlatform; + int mExternalSdkPlatform; + + // The user's preferred activities associated with particular intent + // filters. + final IntentResolver<PreferredActivity, PreferredActivity> mPreferredActivities = + new IntentResolver<PreferredActivity, PreferredActivity>() { + @Override + protected String packageForFilter(PreferredActivity filter) { + return filter.mPref.mComponent.getPackageName(); + } + @Override + protected void dumpFilter(PrintWriter out, String prefix, + PreferredActivity filter) { + filter.mPref.dump(out, prefix, filter); + } + }; + final HashMap<String, SharedUserSetting> mSharedUsers = + new HashMap<String, SharedUserSetting>(); + private final ArrayList<Object> mUserIds = new ArrayList<Object>(); + private final SparseArray<Object> mOtherUserIds = + new SparseArray<Object>(); + + // For reading/writing settings file. + private final ArrayList<Signature> mPastSignatures = + new ArrayList<Signature>(); + + // Mapping from permission names to info about them. + final HashMap<String, BasePermission> mPermissions = + new HashMap<String, BasePermission>(); + + // Mapping from permission tree names to info about them. + final HashMap<String, BasePermission> mPermissionTrees = + new HashMap<String, BasePermission>(); + + // Packages that have been uninstalled and still need their external + // storage data deleted. + final ArrayList<String> mPackagesToBeCleaned = new ArrayList<String>(); + + // Packages that have been renamed since they were first installed. + // Keys are the new names of the packages, values are the original + // names. The packages appear everwhere else under their original + // names. + final HashMap<String, String> mRenamedPackages = new HashMap<String, String>(); + + final StringBuilder mReadMessages = new StringBuilder(); + + /** + * Used to track packages that have a shared user ID that hasn't been read + * in yet. + * <p> + * TODO: make this just a local variable that is passed in during package + * scanning to make it less confusing. + */ + private final ArrayList<PendingPackage> mPendingPackages = new ArrayList<PendingPackage>(); + + Settings() { + File dataDir = Environment.getDataDirectory(); + File systemDir = new File(dataDir, "system"); + systemDir.mkdirs(); + FileUtils.setPermissions(systemDir.toString(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG + |FileUtils.S_IROTH|FileUtils.S_IXOTH, + -1, -1); + mSettingsFilename = new File(systemDir, "packages.xml"); + mBackupSettingsFilename = new File(systemDir, "packages-backup.xml"); + mPackageListFilename = new File(systemDir, "packages.list"); + mStoppedPackagesFilename = new File(systemDir, "packages-stopped.xml"); + mBackupStoppedPackagesFilename = new File(systemDir, "packages-stopped-backup.xml"); + } + + PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage, + String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, + String nativeLibraryPathString, int pkgFlags, boolean create, boolean add) { + final String name = pkg.packageName; + PackageSetting p = getPackageLPw(name, origPackage, realName, sharedUser, codePath, + resourcePath, nativeLibraryPathString, pkg.mVersionCode, pkgFlags, create, add); + return p; + } + + PackageSetting peekPackageLPr(String name) { + return mPackages.get(name); + } + + void setInstallStatus(String pkgName, int status) { + PackageSetting p = mPackages.get(pkgName); + if(p != null) { + if(p.getInstallStatus() != status) { + p.setInstallStatus(status); + } + } + } + + void setInstallerPackageName(String pkgName, + String installerPkgName) { + PackageSetting p = mPackages.get(pkgName); + if(p != null) { + p.setInstallerPackageName(installerPkgName); + } + } + + SharedUserSetting getSharedUserLPw(String name, + int pkgFlags, boolean create) { + SharedUserSetting s = mSharedUsers.get(name); + if (s == null) { + if (!create) { + return null; + } + s = new SharedUserSetting(name, pkgFlags); + if (PackageManagerService.MULTIPLE_APPLICATION_UIDS) { + s.userId = newUserIdLPw(s); + } else { + s.userId = PackageManagerService.FIRST_APPLICATION_UID; + } + Log.i(PackageManagerService.TAG, "New shared user " + name + ": id=" + s.userId); + // < 0 means we couldn't assign a userid; fall out and return + // s, which is currently null + if (s.userId >= 0) { + mSharedUsers.put(name, s); + } + } + + return s; + } + + boolean disableSystemPackageLPw(String name) { + final PackageSetting p = mPackages.get(name); + if(p == null) { + Log.w(PackageManagerService.TAG, "Package:"+name+" is not an installed package"); + return false; + } + final PackageSetting dp = mDisabledSysPackages.get(name); + // always make sure the system package code and resource paths dont change + if (dp == null) { + if((p.pkg != null) && (p.pkg.applicationInfo != null)) { + p.pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; + } + mDisabledSysPackages.put(name, p); + + // a little trick... when we install the new package, we don't + // want to modify the existing PackageSetting for the built-in + // version. so at this point we need a new PackageSetting that + // is okay to muck with. + PackageSetting newp = new PackageSetting(p); + replacePackageLPw(name, newp); + return true; + } + return false; + } + + PackageSetting enableSystemPackageLPw(String name) { + PackageSetting p = mDisabledSysPackages.get(name); + if(p == null) { + Log.w(PackageManagerService.TAG, "Package:"+name+" is not disabled"); + return null; + } + // Reset flag in ApplicationInfo object + if((p.pkg != null) && (p.pkg.applicationInfo != null)) { + p.pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; + } + PackageSetting ret = addPackageLPw(name, p.realName, p.codePath, p.resourcePath, + p.nativeLibraryPathString, p.userId, p.versionCode, p.pkgFlags); + mDisabledSysPackages.remove(name); + return ret; + } + + PackageSetting addPackageLPw(String name, String realName, File codePath, File resourcePath, + String nativeLibraryPathString, int uid, int vc, int pkgFlags) { + PackageSetting p = mPackages.get(name); + if (p != null) { + if (p.userId == uid) { + return p; + } + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Adding duplicate package, keeping first: " + name); + return null; + } + p = new PackageSetting(name, realName, codePath, resourcePath, nativeLibraryPathString, + vc, pkgFlags); + p.userId = uid; + if (addUserIdLPw(uid, p, name)) { + mPackages.put(name, p); + return p; + } + return null; + } + + SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags) { + SharedUserSetting s = mSharedUsers.get(name); + if (s != null) { + if (s.userId == uid) { + return s; + } + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Adding duplicate shared user, keeping first: " + name); + return null; + } + s = new SharedUserSetting(name, pkgFlags); + s.userId = uid; + if (addUserIdLPw(uid, s, name)) { + mSharedUsers.put(name, s); + return s; + } + return null; + } + + // Transfer ownership of permissions from one package to another. + void transferPermissionsLPw(String origPkg, String newPkg) { + // Transfer ownership of permissions to the new package. + for (int i=0; i<2; i++) { + HashMap<String, BasePermission> permissions = + i == 0 ? mPermissionTrees : mPermissions; + for (BasePermission bp : permissions.values()) { + if (origPkg.equals(bp.sourcePackage)) { + if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, + "Moving permission " + bp.name + + " from pkg " + bp.sourcePackage + + " to " + newPkg); + bp.sourcePackage = newPkg; + bp.packageSetting = null; + bp.perm = null; + if (bp.pendingInfo != null) { + bp.pendingInfo.packageName = newPkg; + } + bp.uid = 0; + bp.gids = null; + } + } + } + } + + private PackageSetting getPackageLPw(String name, PackageSetting origPackage, + String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, + String nativeLibraryPathString, int vc, int pkgFlags, boolean create, boolean add) { + PackageSetting p = mPackages.get(name); + if (p != null) { + if (!p.codePath.equals(codePath)) { + // Check to see if its a disabled system app + if ((p.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) { + // This is an updated system app with versions in both system + // and data partition. Just let the most recent version + // take precedence. + Slog.w(PackageManagerService.TAG, "Trying to update system app code path from " + + p.codePathString + " to " + codePath.toString()); + } else { + // Just a change in the code path is not an issue, but + // let's log a message about it. + Slog.i(PackageManagerService.TAG, "Package " + name + " codePath changed from " + p.codePath + + " to " + codePath + "; Retaining data and using new"); + /* + * Since we've changed paths, we need to prefer the new + * native library path over the one stored in the + * package settings since we might have moved from + * internal to external storage or vice versa. + */ + p.nativeLibraryPathString = nativeLibraryPathString; + } + } + if (p.sharedUser != sharedUser) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Package " + name + " shared user changed from " + + (p.sharedUser != null ? p.sharedUser.name : "<nothing>") + + " to " + + (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) { + // Create a new PackageSettings entry. this can end up here because + // of code path mismatch or user id mismatch of an updated system partition + if (!create) { + return null; + } + if (origPackage != null) { + // We are consuming the data from an existing package. + p = new PackageSetting(origPackage.name, name, codePath, resourcePath, + nativeLibraryPathString, vc, pkgFlags); + if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package " + name + + " is adopting original package " + origPackage.name); + // Note that we will retain the new package's signature so + // that we can keep its data. + PackageSignatures s = p.signatures; + p.copyFrom(origPackage); + p.signatures = s; + p.sharedUser = origPackage.sharedUser; + p.userId = origPackage.userId; + p.origPackage = origPackage; + mRenamedPackages.put(name, origPackage.name); + name = origPackage.name; + // Update new package state. + p.setTimeStamp(codePath.lastModified()); + } else { + p = new PackageSetting(name, realName, codePath, resourcePath, + nativeLibraryPathString, vc, pkgFlags); + p.setTimeStamp(codePath.lastModified()); + p.sharedUser = sharedUser; + // If this is not a system app, it starts out stopped. + if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { + if (DEBUG_STOPPED) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Slog.i(PackageManagerService.TAG, "Stopping package " + name, e); + } + p.stopped = true; + p.notLaunched = true; + } + if (sharedUser != null) { + p.userId = sharedUser.userId; + } else if (PackageManagerService.MULTIPLE_APPLICATION_UIDS) { + // Clone the setting here for disabled system packages + PackageSetting dis = mDisabledSysPackages.get(name); + if (dis != null) { + // For disabled packages a new setting is created + // from the existing user id. This still has to be + // added to list of user id's + // Copy signatures from previous setting + if (dis.signatures.mSignatures != null) { + p.signatures.mSignatures = dis.signatures.mSignatures.clone(); + } + p.userId = dis.userId; + // Clone permissions + p.grantedPermissions = new HashSet<String>(dis.grantedPermissions); + // Clone component info + p.disabledComponents = new HashSet<String>(dis.disabledComponents); + p.enabledComponents = new HashSet<String>(dis.enabledComponents); + // Add new setting to list of user ids + addUserIdLPw(p.userId, p, name); + } else { + // Assign new user id + p.userId = newUserIdLPw(p); + } + } else { + p.userId = PackageManagerService.FIRST_APPLICATION_UID; + } + } + if (p.userId < 0) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Package " + name + " could not be assigned a valid uid"); + return null; + } + if (add) { + // Finish adding new package by adding it and updating shared + // user preferences + addPackageSettingLPw(p, name, sharedUser); + } + } + return p; + } + + void insertPackageSettingLPw(PackageSetting p, PackageParser.Package pkg) { + p.pkg = pkg; + pkg.mSetEnabled = p.enabled; + pkg.mSetStopped = p.stopped; + final String codePath = pkg.applicationInfo.sourceDir; + final String resourcePath = pkg.applicationInfo.publicSourceDir; + // Update code path if needed + if (!codePath.equalsIgnoreCase(p.codePathString)) { + Slog.w(PackageManagerService.TAG, "Code path for pkg : " + p.pkg.packageName + + " changing from " + p.codePathString + " to " + codePath); + p.codePath = new File(codePath); + p.codePathString = codePath; + } + //Update resource path if needed + if (!resourcePath.equalsIgnoreCase(p.resourcePathString)) { + Slog.w(PackageManagerService.TAG, "Resource path for pkg : " + p.pkg.packageName + + " changing from " + p.resourcePathString + " to " + resourcePath); + p.resourcePath = new File(resourcePath); + p.resourcePathString = resourcePath; + } + // Update the native library path if needed + final String nativeLibraryPath = pkg.applicationInfo.nativeLibraryDir; + if (nativeLibraryPath != null + && !nativeLibraryPath.equalsIgnoreCase(p.nativeLibraryPathString)) { + p.nativeLibraryPathString = nativeLibraryPath; + } + // Update version code if needed + if (pkg.mVersionCode != p.versionCode) { + p.versionCode = pkg.mVersionCode; + } + // Update signatures if needed. + if (p.signatures.mSignatures == null) { + p.signatures.assignSignatures(pkg.mSignatures); + } + // If this app defines a shared user id initialize + // the shared user signatures as well. + if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) { + p.sharedUser.signatures.assignSignatures(pkg.mSignatures); + } + addPackageSettingLPw(p, pkg.packageName, p.sharedUser); + } + + // Utility method that adds a PackageSetting to mPackages and + // completes updating the shared user attributes + private void addPackageSettingLPw(PackageSetting p, String name, + SharedUserSetting sharedUser) { + mPackages.put(name, p); + if (sharedUser != null) { + if (p.sharedUser != null && p.sharedUser != sharedUser) { + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Package " + p.name + " was user " + + p.sharedUser + " but is now " + sharedUser + + "; I am not changing its files so it will probably fail!"); + p.sharedUser.packages.remove(p); + } else if (p.userId != sharedUser.userId) { + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Package " + p.name + " was user id " + p.userId + + " but is now user " + sharedUser + + " with id " + sharedUser.userId + + "; I am not changing its files so it will probably fail!"); + } + + sharedUser.packages.add(p); + p.sharedUser = sharedUser; + p.userId = sharedUser.userId; + } + } + + /* + * Update the shared user setting when a package using + * specifying the shared user id is removed. The gids + * associated with each permission of the deleted package + * are removed from the shared user's gid list only if its + * not in use by other permissions of packages in the + * shared user setting. + */ + void updateSharedUserPermsLPw(PackageSetting deletedPs, int[] globalGids) { + if ((deletedPs == null) || (deletedPs.pkg == null)) { + Slog.i(PackageManagerService.TAG, + "Trying to update info for null package. Just ignoring"); + return; + } + // No sharedUserId + if (deletedPs.sharedUser == null) { + return; + } + SharedUserSetting sus = deletedPs.sharedUser; + // Update permissions + for (String eachPerm : deletedPs.pkg.requestedPermissions) { + boolean used = false; + if (!sus.grantedPermissions.contains(eachPerm)) { + continue; + } + for (PackageSetting pkg:sus.packages) { + if (pkg.pkg != null && + !pkg.pkg.packageName.equals(deletedPs.pkg.packageName) && + pkg.pkg.requestedPermissions.contains(eachPerm)) { + used = true; + break; + } + } + if (!used) { + // can safely delete this permission from list + sus.grantedPermissions.remove(eachPerm); + } + } + // Update gids + int newGids[] = globalGids; + for (String eachPerm : sus.grantedPermissions) { + BasePermission bp = mPermissions.get(eachPerm); + if (bp != null) { + newGids = PackageManagerService.appendInts(newGids, bp.gids); + } + } + sus.gids = newGids; + } + + int removePackageLPw(String name) { + final PackageSetting p = mPackages.get(name); + if (p != null) { + mPackages.remove(name); + if (p.sharedUser != null) { + p.sharedUser.packages.remove(p); + if (p.sharedUser.packages.size() == 0) { + mSharedUsers.remove(p.sharedUser.name); + removeUserIdLPw(p.sharedUser.userId); + return p.sharedUser.userId; + } + } else { + removeUserIdLPw(p.userId); + return p.userId; + } + } + return -1; + } + + private void replacePackageLPw(String name, PackageSetting newp) { + final PackageSetting p = mPackages.get(name); + if (p != null) { + if (p.sharedUser != null) { + p.sharedUser.packages.remove(p); + p.sharedUser.packages.add(newp); + } else { + replaceUserIdLPw(p.userId, newp); + } + } + mPackages.put(name, newp); + } + + private boolean addUserIdLPw(int uid, Object obj, Object name) { + if (uid >= PackageManagerService.FIRST_APPLICATION_UID + PackageManagerService.MAX_APPLICATION_UIDS) { + return false; + } + + if (uid >= PackageManagerService.FIRST_APPLICATION_UID) { + int N = mUserIds.size(); + final int index = uid - PackageManagerService.FIRST_APPLICATION_UID; + while (index >= N) { + mUserIds.add(null); + N++; + } + if (mUserIds.get(index) != null) { + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Adding duplicate user id: " + uid + + " name=" + name); + return false; + } + mUserIds.set(index, obj); + } else { + if (mOtherUserIds.get(uid) != null) { + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Adding duplicate shared id: " + uid + + " name=" + name); + return false; + } + mOtherUserIds.put(uid, obj); + } + return true; + } + + public Object getUserIdLPr(int uid) { + if (uid >= PackageManagerService.FIRST_APPLICATION_UID) { + final int N = mUserIds.size(); + final int index = uid - PackageManagerService.FIRST_APPLICATION_UID; + return index < N ? mUserIds.get(index) : null; + } else { + return mOtherUserIds.get(uid); + } + } + + private void removeUserIdLPw(int uid) { + if (uid >= PackageManagerService.FIRST_APPLICATION_UID) { + final int N = mUserIds.size(); + final int index = uid - PackageManagerService.FIRST_APPLICATION_UID; + if (index < N) mUserIds.set(index, null); + } else { + mOtherUserIds.remove(uid); + } + } + + private void replaceUserIdLPw(int uid, Object obj) { + if (uid >= PackageManagerService.FIRST_APPLICATION_UID) { + final int N = mUserIds.size(); + final int index = uid - PackageManagerService.FIRST_APPLICATION_UID; + if (index < N) mUserIds.set(index, obj); + } else { + mOtherUserIds.put(uid, obj); + } + } + + void writeStoppedLPr() { + // Keep the old stopped packages around until we know the new ones have + // been successfully written. + if (mStoppedPackagesFilename.exists()) { + // Presence of backup settings file indicates that we failed + // to persist packages earlier. So preserve the older + // backup for future reference since the current packages + // might have been corrupted. + if (!mBackupStoppedPackagesFilename.exists()) { + if (!mStoppedPackagesFilename.renameTo(mBackupStoppedPackagesFilename)) { + Log.wtf(PackageManagerService.TAG, "Unable to backup package manager stopped packages, " + + "current changes will be lost at reboot"); + return; + } + } else { + mStoppedPackagesFilename.delete(); + Slog.w(PackageManagerService.TAG, "Preserving older stopped packages backup"); + } + } + + try { + final FileOutputStream fstr = new FileOutputStream(mStoppedPackagesFilename); + final BufferedOutputStream str = new BufferedOutputStream(fstr); + + //XmlSerializer serializer = XmlUtils.serializerInstance(); + final XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(str, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startTag(null, "stopped-packages"); + + for (final PackageSetting pkg : mPackages.values()) { + if (pkg.stopped) { + serializer.startTag(null, "pkg"); + serializer.attribute(null, "name", pkg.name); + if (pkg.notLaunched) { + serializer.attribute(null, "nl", "1"); + } + serializer.endTag(null, "pkg"); + } + } + + serializer.endTag(null, "stopped-packages"); + + serializer.endDocument(); + + str.flush(); + FileUtils.sync(fstr); + str.close(); + + // New settings successfully written, old ones are no longer + // needed. + mBackupStoppedPackagesFilename.delete(); + FileUtils.setPermissions(mStoppedPackagesFilename.toString(), + FileUtils.S_IRUSR|FileUtils.S_IWUSR + |FileUtils.S_IRGRP|FileUtils.S_IWGRP + |FileUtils.S_IROTH, + -1, -1); + + // Done, all is good! + return; + } catch(java.io.IOException e) { + Log.wtf(PackageManagerService.TAG, "Unable to write package manager stopped packages, " + + " current changes will be lost at reboot", e); + } + + // Clean up partially written files + if (mStoppedPackagesFilename.exists()) { + if (!mStoppedPackagesFilename.delete()) { + Log.i(PackageManagerService.TAG, "Failed to clean up mangled file: " + mStoppedPackagesFilename); + } + } + } + + // Note: assumed "stopped" field is already cleared in all packages. + void readStoppedLPw() { + FileInputStream str = null; + if (mBackupStoppedPackagesFilename.exists()) { + try { + str = new FileInputStream(mBackupStoppedPackagesFilename); + mReadMessages.append("Reading from backup stopped packages file\n"); + PackageManagerService.reportSettingsProblem(Log.INFO, "Need to read from backup stopped packages file"); + if (mSettingsFilename.exists()) { + // If both the backup and normal file exist, we + // ignore the normal one since it might have been + // corrupted. + Slog.w(PackageManagerService.TAG, "Cleaning up stopped packages file " + + mStoppedPackagesFilename); + mStoppedPackagesFilename.delete(); + } + } catch (java.io.IOException e) { + // We'll try for the normal settings file. + } + } + + try { + if (str == null) { + if (!mStoppedPackagesFilename.exists()) { + mReadMessages.append("No stopped packages file found\n"); + PackageManagerService.reportSettingsProblem(Log.INFO, "No stopped packages file file; " + + "assuming all started"); + // At first boot, make sure no packages are stopped. + // We usually want to have third party apps initialize + // in the stopped state, but not at first boot. + for (PackageSetting pkg : mPackages.values()) { + pkg.stopped = false; + pkg.notLaunched = false; + } + return; + } + str = new FileInputStream(mStoppedPackagesFilename); + } + final XmlPullParser parser = Xml.newPullParser(); + parser.setInput(str, null); + + int type; + while ((type=parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + mReadMessages.append("No start tag found in stopped packages file\n"); + PackageManagerService.reportSettingsProblem(Log.WARN, + "No start tag found in package manager stopped packages"); + return; + } + + int outerDepth = parser.getDepth(); + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("pkg")) { + String name = parser.getAttributeValue(null, "name"); + PackageSetting ps = mPackages.get(name); + if (ps != null) { + ps.stopped = true; + if ("1".equals(parser.getAttributeValue(null, "nl"))) { + ps.notLaunched = true; + } + } else { + Slog.w(PackageManagerService.TAG, "No package known for stopped package: " + name); + } + XmlUtils.skipCurrentTag(parser); + } else { + Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + + str.close(); + + } catch(XmlPullParserException e) { + mReadMessages.append("Error reading: " + e.toString()); + PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading stopped packages: " + e); + Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e); + + } catch(java.io.IOException e) { + mReadMessages.append("Error reading: " + e.toString()); + PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); + Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e); + + } + } + + void writeLPr() { + //Debug.startMethodTracing("/data/system/packageprof", 8 * 1024 * 1024); + + // Keep the old settings around until we know the new ones have + // been successfully written. + if (mSettingsFilename.exists()) { + // 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.wtf(PackageManagerService.TAG, "Unable to backup package manager settings, " + + " current changes will be lost at reboot"); + return; + } + } else { + mSettingsFilename.delete(); + Slog.w(PackageManagerService.TAG, "Preserving older settings backup"); + } + } + + mPastSignatures.clear(); + + try { + FileOutputStream fstr = new FileOutputStream(mSettingsFilename); + BufferedOutputStream str = new BufferedOutputStream(fstr); + + //XmlSerializer serializer = XmlUtils.serializerInstance(); + XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(str, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startTag(null, "packages"); + + serializer.startTag(null, "last-platform-version"); + serializer.attribute(null, "internal", Integer.toString(mInternalSdkPlatform)); + serializer.attribute(null, "external", Integer.toString(mExternalSdkPlatform)); + serializer.endTag(null, "last-platform-version"); + + serializer.startTag(null, "permission-trees"); + for (BasePermission bp : mPermissionTrees.values()) { + writePermissionLPr(serializer, bp); + } + serializer.endTag(null, "permission-trees"); + + serializer.startTag(null, "permissions"); + for (BasePermission bp : mPermissions.values()) { + writePermissionLPr(serializer, bp); + } + serializer.endTag(null, "permissions"); + + for (final PackageSetting pkg : mPackages.values()) { + writePackageLPr(serializer, pkg); + } + + for (final PackageSetting pkg : mDisabledSysPackages.values()) { + writeDisabledSysPackageLPr(serializer, pkg); + } + + serializer.startTag(null, "preferred-activities"); + for (final PreferredActivity pa : mPreferredActivities.filterSet()) { + serializer.startTag(null, "item"); + pa.writeToXml(serializer); + serializer.endTag(null, "item"); + } + serializer.endTag(null, "preferred-activities"); + + for (final SharedUserSetting usr : mSharedUsers.values()) { + serializer.startTag(null, "shared-user"); + serializer.attribute(null, "name", usr.name); + serializer.attribute(null, "userId", + Integer.toString(usr.userId)); + usr.signatures.writeXml(serializer, "sigs", mPastSignatures); + serializer.startTag(null, "perms"); + for (String name : usr.grantedPermissions) { + serializer.startTag(null, "item"); + serializer.attribute(null, "name", name); + serializer.endTag(null, "item"); + } + serializer.endTag(null, "perms"); + serializer.endTag(null, "shared-user"); + } + + if (mPackagesToBeCleaned.size() > 0) { + for (int i=0; i<mPackagesToBeCleaned.size(); i++) { + serializer.startTag(null, "cleaning-package"); + serializer.attribute(null, "name", mPackagesToBeCleaned.get(i)); + serializer.endTag(null, "cleaning-package"); + } + } + + if (mRenamedPackages.size() > 0) { + for (HashMap.Entry<String, String> e : mRenamedPackages.entrySet()) { + serializer.startTag(null, "renamed-package"); + serializer.attribute(null, "new", e.getKey()); + serializer.attribute(null, "old", e.getValue()); + serializer.endTag(null, "renamed-package"); + } + } + + serializer.endTag(null, "packages"); + + serializer.endDocument(); + + str.flush(); + FileUtils.sync(fstr); + str.close(); + + // New settings successfully written, old ones are no longer + // needed. + mBackupSettingsFilename.delete(); + FileUtils.setPermissions(mSettingsFilename.toString(), + FileUtils.S_IRUSR|FileUtils.S_IWUSR + |FileUtils.S_IRGRP|FileUtils.S_IWGRP + |FileUtils.S_IROTH, + -1, -1); + + // Write package list file now, use a JournaledFile. + // + File tempFile = new File(mPackageListFilename.toString() + ".tmp"); + JournaledFile journal = new JournaledFile(mPackageListFilename, tempFile); + + fstr = new FileOutputStream(journal.chooseForWrite()); + str = new BufferedOutputStream(fstr); + try { + StringBuilder sb = new StringBuilder(); + for (final PackageSetting pkg : mPackages.values()) { + ApplicationInfo ai = pkg.pkg.applicationInfo; + String dataPath = ai.dataDir; + boolean isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + + // Avoid any application that has a space in its path + // or that is handled by the system. + if (dataPath.indexOf(" ") >= 0 || ai.uid <= Process.FIRST_APPLICATION_UID) + continue; + + // we store on each line the following information for now: + // + // pkgName - package name + // userId - application-specific user id + // debugFlag - 0 or 1 if the package is debuggable. + // dataPath - path to package's data path + // + // NOTE: We prefer not to expose all ApplicationInfo flags for now. + // + // DO NOT MODIFY THIS FORMAT UNLESS YOU CAN ALSO MODIFY ITS USERS + // FROM NATIVE CODE. AT THE MOMENT, LOOK AT THE FOLLOWING SOURCES: + // system/core/run-as/run-as.c + // + sb.setLength(0); + sb.append(ai.packageName); + sb.append(" "); + sb.append((int)ai.uid); + sb.append(isDebug ? " 1 " : " 0 "); + sb.append(dataPath); + sb.append("\n"); + str.write(sb.toString().getBytes()); + } + str.flush(); + FileUtils.sync(fstr); + str.close(); + journal.commit(); + } + catch (Exception e) { + journal.rollback(); + } + + FileUtils.setPermissions(mPackageListFilename.toString(), + FileUtils.S_IRUSR|FileUtils.S_IWUSR + |FileUtils.S_IRGRP|FileUtils.S_IWGRP + |FileUtils.S_IROTH, + -1, -1); + + writeStoppedLPr(); + + return; + + } catch(XmlPullParserException e) { + Log.wtf(PackageManagerService.TAG, "Unable to write package manager settings, " + + "current changes will be lost at reboot", e); + } catch(java.io.IOException e) { + Log.wtf(PackageManagerService.TAG, "Unable to write package manager settings, " + + "current changes will be lost at reboot", e); + } + // Clean up partially written files + if (mSettingsFilename.exists()) { + if (!mSettingsFilename.delete()) { + Log.wtf(PackageManagerService.TAG, "Failed to clean up mangled file: " + mSettingsFilename); + } + } + //Debug.stopMethodTracing(); + } + + void writeDisabledSysPackageLPr(XmlSerializer serializer, final PackageSetting pkg) + throws java.io.IOException { + serializer.startTag(null, "updated-package"); + serializer.attribute(null, "name", pkg.name); + if (pkg.realName != null) { + serializer.attribute(null, "realName", pkg.realName); + } + serializer.attribute(null, "codePath", pkg.codePathString); + serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp)); + serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime)); + serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime)); + serializer.attribute(null, "version", String.valueOf(pkg.versionCode)); + if (!pkg.resourcePathString.equals(pkg.codePathString)) { + serializer.attribute(null, "resourcePath", pkg.resourcePathString); + } + if (pkg.nativeLibraryPathString != null) { + serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString); + } + if (pkg.sharedUser == null) { + serializer.attribute(null, "userId", Integer.toString(pkg.userId)); + } else { + serializer.attribute(null, "sharedUserId", Integer.toString(pkg.userId)); + } + serializer.startTag(null, "perms"); + if (pkg.sharedUser == null) { + // If this is a shared user, the permissions will + // be written there. We still need to write an + // empty permissions list so permissionsFixed will + // be set. + for (final String name : pkg.grantedPermissions) { + BasePermission bp = mPermissions.get(name); + if (bp != null) { + // We only need to write signature or system permissions but + // this wont + // match the semantics of grantedPermissions. So write all + // permissions. + serializer.startTag(null, "item"); + serializer.attribute(null, "name", name); + serializer.endTag(null, "item"); + } + } + } + serializer.endTag(null, "perms"); + serializer.endTag(null, "updated-package"); + } + + void writePackageLPr(XmlSerializer serializer, final PackageSetting pkg) + throws java.io.IOException { + serializer.startTag(null, "package"); + serializer.attribute(null, "name", pkg.name); + if (pkg.realName != null) { + serializer.attribute(null, "realName", pkg.realName); + } + serializer.attribute(null, "codePath", pkg.codePathString); + if (!pkg.resourcePathString.equals(pkg.codePathString)) { + serializer.attribute(null, "resourcePath", pkg.resourcePathString); + } + if (pkg.nativeLibraryPathString != null) { + serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString); + } + serializer.attribute(null, "flags", Integer.toString(pkg.pkgFlags)); + serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp)); + serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime)); + serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime)); + serializer.attribute(null, "version", String.valueOf(pkg.versionCode)); + if (pkg.sharedUser == null) { + serializer.attribute(null, "userId", Integer.toString(pkg.userId)); + } else { + serializer.attribute(null, "sharedUserId", Integer.toString(pkg.userId)); + } + if (pkg.uidError) { + serializer.attribute(null, "uidError", "true"); + } + if (pkg.enabled != COMPONENT_ENABLED_STATE_DEFAULT) { + serializer.attribute(null, "enabled", + pkg.enabled == COMPONENT_ENABLED_STATE_ENABLED ? "true" : "false"); + } + if (pkg.installStatus == PackageSettingBase.PKG_INSTALL_INCOMPLETE) { + serializer.attribute(null, "installStatus", "false"); + } + if (pkg.installerPackageName != null) { + serializer.attribute(null, "installer", pkg.installerPackageName); + } + pkg.signatures.writeXml(serializer, "sigs", mPastSignatures); + if ((pkg.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) { + serializer.startTag(null, "perms"); + if (pkg.sharedUser == null) { + // If this is a shared user, the permissions will + // be written there. We still need to write an + // empty permissions list so permissionsFixed will + // be set. + for (final String name : pkg.grantedPermissions) { + serializer.startTag(null, "item"); + serializer.attribute(null, "name", name); + serializer.endTag(null, "item"); + } + } + serializer.endTag(null, "perms"); + } + if (pkg.disabledComponents.size() > 0) { + serializer.startTag(null, "disabled-components"); + for (final String name : pkg.disabledComponents) { + serializer.startTag(null, "item"); + serializer.attribute(null, "name", name); + serializer.endTag(null, "item"); + } + serializer.endTag(null, "disabled-components"); + } + if (pkg.enabledComponents.size() > 0) { + serializer.startTag(null, "enabled-components"); + for (final String name : pkg.enabledComponents) { + serializer.startTag(null, "item"); + serializer.attribute(null, "name", name); + serializer.endTag(null, "item"); + } + serializer.endTag(null, "enabled-components"); + } + + serializer.endTag(null, "package"); + } + + void writePermissionLPr(XmlSerializer serializer, BasePermission bp) + throws XmlPullParserException, java.io.IOException { + if (bp.type != BasePermission.TYPE_BUILTIN && bp.sourcePackage != null) { + serializer.startTag(null, "item"); + serializer.attribute(null, "name", bp.name); + serializer.attribute(null, "package", bp.sourcePackage); + if (bp.protectionLevel != PermissionInfo.PROTECTION_NORMAL) { + serializer.attribute(null, "protection", Integer.toString(bp.protectionLevel)); + } + if (PackageManagerService.DEBUG_SETTINGS) + Log.v(PackageManagerService.TAG, "Writing perm: name=" + bp.name + " type=" + + bp.type); + if (bp.type == BasePermission.TYPE_DYNAMIC) { + final PermissionInfo pi = bp.perm != null ? bp.perm.info : bp.pendingInfo; + if (pi != null) { + serializer.attribute(null, "type", "dynamic"); + if (pi.icon != 0) { + serializer.attribute(null, "icon", Integer.toString(pi.icon)); + } + if (pi.nonLocalizedLabel != null) { + serializer.attribute(null, "label", pi.nonLocalizedLabel.toString()); + } + } + } + serializer.endTag(null, "item"); + } + } + + ArrayList<PackageSetting> getListOfIncompleteInstallPackagesLPr() { + final HashSet<String> kList = new HashSet<String>(mPackages.keySet()); + final Iterator<String> its = kList.iterator(); + final ArrayList<PackageSetting> ret = new ArrayList<PackageSetting>(); + while (its.hasNext()) { + final String key = its.next(); + final PackageSetting ps = mPackages.get(key); + if (ps.getInstallStatus() == PackageSettingBase.PKG_INSTALL_INCOMPLETE) { + ret.add(ps); + } + } + return ret; + } + + boolean readLPw() { + FileInputStream str = null; + if (mBackupSettingsFilename.exists()) { + try { + str = new FileInputStream(mBackupSettingsFilename); + mReadMessages.append("Reading from backup settings file\n"); + PackageManagerService.reportSettingsProblem(Log.INFO, + "Need to read 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. + Slog.w(PackageManagerService.TAG, "Cleaning up settings file " + + mSettingsFilename); + mSettingsFilename.delete(); + } + } catch (java.io.IOException e) { + // We'll try for the normal settings file. + } + } + + mPendingPackages.clear(); + mPastSignatures.clear(); + + try { + if (str == null) { + if (!mSettingsFilename.exists()) { + mReadMessages.append("No settings file found\n"); + PackageManagerService.reportSettingsProblem(Log.INFO, + "No settings file; creating initial state"); + return false; + } + str = new FileInputStream(mSettingsFilename); + } + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(str, null); + + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + mReadMessages.append("No start tag found in settings file\n"); + PackageManagerService.reportSettingsProblem(Log.WARN, + "No start tag found in package manager settings"); + Log + .wtf(PackageManagerService.TAG, + "No start tag found in package manager settings"); + return false; + } + + int outerDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("package")) { + readPackageLPw(parser); + } else if (tagName.equals("permissions")) { + readPermissionsLPw(mPermissions, parser); + } else if (tagName.equals("permission-trees")) { + readPermissionsLPw(mPermissionTrees, parser); + } else if (tagName.equals("shared-user")) { + readSharedUserLPw(parser); + } else if (tagName.equals("preferred-packages")) { + // no longer used. + } else if (tagName.equals("preferred-activities")) { + readPreferredActivitiesLPw(parser); + } else if (tagName.equals("updated-package")) { + readDisabledSysPackageLPw(parser); + } else if (tagName.equals("cleaning-package")) { + String name = parser.getAttributeValue(null, "name"); + if (name != null) { + mPackagesToBeCleaned.add(name); + } + } else if (tagName.equals("renamed-package")) { + String nname = parser.getAttributeValue(null, "new"); + String oname = parser.getAttributeValue(null, "old"); + if (nname != null && oname != null) { + mRenamedPackages.put(nname, oname); + } + } else if (tagName.equals("last-platform-version")) { + mInternalSdkPlatform = mExternalSdkPlatform = 0; + try { + String internal = parser.getAttributeValue(null, "internal"); + if (internal != null) { + mInternalSdkPlatform = Integer.parseInt(internal); + } + String external = parser.getAttributeValue(null, "external"); + if (external != null) { + mExternalSdkPlatform = Integer.parseInt(external); + } + } catch (NumberFormatException e) { + } + } else { + Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + + str.close(); + + } catch (XmlPullParserException e) { + mReadMessages.append("Error reading: " + e.toString()); + PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); + Log.wtf(PackageManagerService.TAG, "Error reading package manager settings", e); + + } catch (java.io.IOException e) { + mReadMessages.append("Error reading: " + e.toString()); + PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); + Log.wtf(PackageManagerService.TAG, "Error reading package manager settings", e); + + } + + final int N = mPendingPackages.size(); + for (int i = 0; i < N; i++) { + final PendingPackage pp = mPendingPackages.get(i); + Object idObj = getUserIdLPr(pp.sharedId); + if (idObj != null && idObj instanceof SharedUserSetting) { + PackageSetting p = getPackageLPw(pp.name, null, pp.realName, + (SharedUserSetting) idObj, pp.codePath, pp.resourcePath, + pp.nativeLibraryPathString, pp.versionCode, pp.pkgFlags, true, true); + if (p == null) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unable to create application package for " + pp.name); + continue; + } + p.copyFrom(pp); + } else if (idObj != null) { + String msg = "Bad package setting: package " + pp.name + " has shared uid " + + pp.sharedId + " that is not a shared uid\n"; + mReadMessages.append(msg); + PackageManagerService.reportSettingsProblem(Log.ERROR, msg); + } else { + String msg = "Bad package setting: package " + pp.name + " has shared uid " + + pp.sharedId + " that is not defined\n"; + mReadMessages.append(msg); + PackageManagerService.reportSettingsProblem(Log.ERROR, msg); + } + } + mPendingPackages.clear(); + + /* + * Make sure all the updated system packages have their shared users + * associated with them. + */ + final Iterator<PackageSetting> disabledIt = mDisabledSysPackages.values().iterator(); + while (disabledIt.hasNext()) { + final PackageSetting disabledPs = disabledIt.next(); + final Object id = getUserIdLPr(disabledPs.userId); + if (id != null && id instanceof SharedUserSetting) { + disabledPs.sharedUser = (SharedUserSetting) id; + } + } + + readStoppedLPw(); + + mReadMessages.append("Read completed successfully: " + mPackages.size() + " packages, " + + mSharedUsers.size() + " shared uids\n"); + + return true; + } + + private int readInt(XmlPullParser parser, String ns, String name, int defValue) { + String v = parser.getAttributeValue(ns, name); + try { + if (v == null) { + return defValue; + } + return Integer.parseInt(v); + } catch (NumberFormatException e) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: attribute " + name + + " has bad integer value " + v + " at " + + parser.getPositionDescription()); + } + return defValue; + } + + private void readPermissionsLPw(HashMap<String, BasePermission> out, XmlPullParser parser) + throws IOException, XmlPullParserException { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + final String tagName = parser.getName(); + if (tagName.equals("item")) { + final String name = parser.getAttributeValue(null, "name"); + final String sourcePackage = parser.getAttributeValue(null, "package"); + final String ptype = parser.getAttributeValue(null, "type"); + if (name != null && sourcePackage != null) { + final boolean dynamic = "dynamic".equals(ptype); + final BasePermission bp = new BasePermission(name, sourcePackage, + dynamic ? BasePermission.TYPE_DYNAMIC : BasePermission.TYPE_NORMAL); + bp.protectionLevel = readInt(parser, null, "protection", + PermissionInfo.PROTECTION_NORMAL); + if (dynamic) { + PermissionInfo pi = new PermissionInfo(); + pi.packageName = sourcePackage.intern(); + pi.name = name.intern(); + pi.icon = readInt(parser, null, "icon", 0); + pi.nonLocalizedLabel = parser.getAttributeValue(null, "label"); + pi.protectionLevel = bp.protectionLevel; + bp.pendingInfo = pi; + } + out.put(bp.name, bp); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: permissions has" + " no name at " + + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element reading permissions: " + parser.getName() + " at " + + parser.getPositionDescription()); + } + XmlUtils.skipCurrentTag(parser); + } + } + + private void readDisabledSysPackageLPw(XmlPullParser parser) throws XmlPullParserException, + IOException { + String name = parser.getAttributeValue(null, "name"); + String realName = parser.getAttributeValue(null, "realName"); + String codePathStr = parser.getAttributeValue(null, "codePath"); + String resourcePathStr = parser.getAttributeValue(null, "resourcePath"); + String nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath"); + if (resourcePathStr == null) { + resourcePathStr = codePathStr; + } + String version = parser.getAttributeValue(null, "version"); + int versionCode = 0; + if (version != null) { + try { + versionCode = Integer.parseInt(version); + } catch (NumberFormatException e) { + } + } + + int pkgFlags = 0; + pkgFlags |= ApplicationInfo.FLAG_SYSTEM; + PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr), + new File(resourcePathStr), nativeLibraryPathStr, versionCode, pkgFlags); + String timeStampStr = parser.getAttributeValue(null, "ft"); + if (timeStampStr != null) { + try { + long timeStamp = Long.parseLong(timeStampStr, 16); + ps.setTimeStamp(timeStamp); + } catch (NumberFormatException e) { + } + } else { + timeStampStr = parser.getAttributeValue(null, "ts"); + if (timeStampStr != null) { + try { + long timeStamp = Long.parseLong(timeStampStr); + ps.setTimeStamp(timeStamp); + } catch (NumberFormatException e) { + } + } + } + timeStampStr = parser.getAttributeValue(null, "it"); + if (timeStampStr != null) { + try { + ps.firstInstallTime = Long.parseLong(timeStampStr, 16); + } catch (NumberFormatException e) { + } + } + timeStampStr = parser.getAttributeValue(null, "ut"); + if (timeStampStr != null) { + try { + ps.lastUpdateTime = Long.parseLong(timeStampStr, 16); + } catch (NumberFormatException e) { + } + } + String idStr = parser.getAttributeValue(null, "userId"); + ps.userId = idStr != null ? Integer.parseInt(idStr) : 0; + if (ps.userId <= 0) { + String sharedIdStr = parser.getAttributeValue(null, "sharedUserId"); + ps.userId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0; + } + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("perms")) { + readGrantedPermissionsLPw(parser, ps.grantedPermissions); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <updated-package>: " + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + mDisabledSysPackages.put(name, ps); + } + + private void readPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException { + String name = null; + String realName = null; + String idStr = null; + String sharedIdStr = null; + String codePathStr = null; + String resourcePathStr = null; + String nativeLibraryPathStr = null; + String systemStr = null; + String installerPackageName = null; + String uidError = null; + int pkgFlags = 0; + long timeStamp = 0; + long firstInstallTime = 0; + long lastUpdateTime = 0; + PackageSettingBase packageSetting = null; + String version = null; + int versionCode = 0; + try { + name = parser.getAttributeValue(null, "name"); + realName = parser.getAttributeValue(null, "realName"); + idStr = parser.getAttributeValue(null, "userId"); + uidError = parser.getAttributeValue(null, "uidError"); + sharedIdStr = parser.getAttributeValue(null, "sharedUserId"); + codePathStr = parser.getAttributeValue(null, "codePath"); + resourcePathStr = parser.getAttributeValue(null, "resourcePath"); + nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath"); + version = parser.getAttributeValue(null, "version"); + if (version != null) { + try { + versionCode = Integer.parseInt(version); + } catch (NumberFormatException e) { + } + } + installerPackageName = parser.getAttributeValue(null, "installer"); + + systemStr = parser.getAttributeValue(null, "flags"); + if (systemStr != null) { + try { + pkgFlags = Integer.parseInt(systemStr); + } catch (NumberFormatException e) { + } + } else { + // For backward compatibility + systemStr = parser.getAttributeValue(null, "system"); + if (systemStr != null) { + pkgFlags |= ("true".equalsIgnoreCase(systemStr)) ? ApplicationInfo.FLAG_SYSTEM + : 0; + } else { + // Old settings that don't specify system... just treat + // them as system, good enough. + pkgFlags |= ApplicationInfo.FLAG_SYSTEM; + } + } + String timeStampStr = parser.getAttributeValue(null, "ft"); + if (timeStampStr != null) { + try { + timeStamp = Long.parseLong(timeStampStr, 16); + } catch (NumberFormatException e) { + } + } else { + timeStampStr = parser.getAttributeValue(null, "ts"); + if (timeStampStr != null) { + try { + timeStamp = Long.parseLong(timeStampStr); + } catch (NumberFormatException e) { + } + } + } + timeStampStr = parser.getAttributeValue(null, "it"); + if (timeStampStr != null) { + try { + firstInstallTime = Long.parseLong(timeStampStr, 16); + } catch (NumberFormatException e) { + } + } + timeStampStr = parser.getAttributeValue(null, "ut"); + if (timeStampStr != null) { + try { + lastUpdateTime = Long.parseLong(timeStampStr, 16); + } catch (NumberFormatException e) { + } + } + if (PackageManagerService.DEBUG_SETTINGS) + Log.v(PackageManagerService.TAG, "Reading package: " + name + " userId=" + idStr + + " sharedUserId=" + sharedIdStr); + int userId = idStr != null ? Integer.parseInt(idStr) : 0; + if (resourcePathStr == null) { + resourcePathStr = codePathStr; + } + if (realName != null) { + realName = realName.intern(); + } + if (name == null) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <package> has no name at " + + parser.getPositionDescription()); + } else if (codePathStr == null) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <package> has no codePath at " + + parser.getPositionDescription()); + } else if (userId > 0) { + packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr), + new File(resourcePathStr), nativeLibraryPathStr, userId, versionCode, + pkgFlags); + if (PackageManagerService.DEBUG_SETTINGS) + Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId=" + + userId + " pkg=" + packageSetting); + if (packageSetting == null) { + PackageManagerService.reportSettingsProblem(Log.ERROR, "Failure adding uid " + + userId + " while parsing settings at " + + parser.getPositionDescription()); + } else { + packageSetting.setTimeStamp(timeStamp); + packageSetting.firstInstallTime = firstInstallTime; + packageSetting.lastUpdateTime = lastUpdateTime; + } + } else if (sharedIdStr != null) { + userId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0; + if (userId > 0) { + packageSetting = new PendingPackage(name.intern(), realName, new File( + codePathStr), new File(resourcePathStr), nativeLibraryPathStr, userId, + versionCode, pkgFlags); + packageSetting.setTimeStamp(timeStamp); + packageSetting.firstInstallTime = firstInstallTime; + packageSetting.lastUpdateTime = lastUpdateTime; + mPendingPackages.add((PendingPackage) packageSetting); + if (PackageManagerService.DEBUG_SETTINGS) + Log.i(PackageManagerService.TAG, "Reading package " + name + + ": sharedUserId=" + userId + " pkg=" + packageSetting); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: package " + name + + " has bad sharedId " + sharedIdStr + " at " + + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: package " + name + " has bad userId " + + idStr + " at " + parser.getPositionDescription()); + } + } catch (NumberFormatException e) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: package " + name + " has bad userId " + + idStr + " at " + parser.getPositionDescription()); + } + if (packageSetting != null) { + packageSetting.uidError = "true".equals(uidError); + packageSetting.installerPackageName = installerPackageName; + packageSetting.nativeLibraryPathString = nativeLibraryPathStr; + final String enabledStr = parser.getAttributeValue(null, "enabled"); + if (enabledStr != null) { + if (enabledStr.equalsIgnoreCase("true")) { + packageSetting.enabled = COMPONENT_ENABLED_STATE_ENABLED; + } else if (enabledStr.equalsIgnoreCase("false")) { + packageSetting.enabled = COMPONENT_ENABLED_STATE_DISABLED; + } else if (enabledStr.equalsIgnoreCase("default")) { + packageSetting.enabled = COMPONENT_ENABLED_STATE_DEFAULT; + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: package " + name + + " has bad enabled value: " + idStr + " at " + + parser.getPositionDescription()); + } + } else { + packageSetting.enabled = COMPONENT_ENABLED_STATE_DEFAULT; + } + final String installStatusStr = parser.getAttributeValue(null, "installStatus"); + if (installStatusStr != null) { + if (installStatusStr.equalsIgnoreCase("false")) { + packageSetting.installStatus = PackageSettingBase.PKG_INSTALL_INCOMPLETE; + } else { + packageSetting.installStatus = PackageSettingBase.PKG_INSTALL_COMPLETE; + } + } + + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("disabled-components")) { + readDisabledComponentsLPw(packageSetting, parser); + } else if (tagName.equals("enabled-components")) { + readEnabledComponentsLPw(packageSetting, parser); + } else if (tagName.equals("sigs")) { + packageSetting.signatures.readXml(parser, mPastSignatures); + } else if (tagName.equals("perms")) { + readGrantedPermissionsLPw(parser, packageSetting.grantedPermissions); + packageSetting.permissionsFixed = true; + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <package>: " + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + } else { + XmlUtils.skipCurrentTag(parser); + } + } + + private void readDisabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser) + throws IOException, XmlPullParserException { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("item")) { + String name = parser.getAttributeValue(null, "name"); + if (name != null) { + packageSetting.disabledComponents.add(name.intern()); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <disabled-components> has" + + " no name at " + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <disabled-components>: " + parser.getName()); + } + XmlUtils.skipCurrentTag(parser); + } + } + + private void readEnabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser) + throws IOException, XmlPullParserException { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("item")) { + String name = parser.getAttributeValue(null, "name"); + if (name != null) { + packageSetting.enabledComponents.add(name.intern()); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <enabled-components> has" + + " no name at " + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <enabled-components>: " + parser.getName()); + } + XmlUtils.skipCurrentTag(parser); + } + } + + private void readSharedUserLPw(XmlPullParser parser) throws XmlPullParserException, IOException { + String name = null; + String idStr = null; + int pkgFlags = 0; + SharedUserSetting su = null; + try { + name = parser.getAttributeValue(null, "name"); + idStr = parser.getAttributeValue(null, "userId"); + int userId = idStr != null ? Integer.parseInt(idStr) : 0; + if ("true".equals(parser.getAttributeValue(null, "system"))) { + pkgFlags |= ApplicationInfo.FLAG_SYSTEM; + } + if (name == null) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <shared-user> has no name at " + + parser.getPositionDescription()); + } else if (userId == 0) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: shared-user " + name + + " has bad userId " + idStr + " at " + + parser.getPositionDescription()); + } else { + if ((su = addSharedUserLPw(name.intern(), userId, pkgFlags)) == null) { + PackageManagerService + .reportSettingsProblem(Log.ERROR, "Occurred while parsing settings at " + + parser.getPositionDescription()); + } + } + } catch (NumberFormatException e) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: package " + name + " has bad userId " + + idStr + " at " + parser.getPositionDescription()); + } + ; + + if (su != null) { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("sigs")) { + su.signatures.readXml(parser, mPastSignatures); + } else if (tagName.equals("perms")) { + readGrantedPermissionsLPw(parser, su.grantedPermissions); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <shared-user>: " + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + + } else { + XmlUtils.skipCurrentTag(parser); + } + } + + private void readGrantedPermissionsLPw(XmlPullParser parser, HashSet<String> outPerms) + throws IOException, XmlPullParserException { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("item")) { + String name = parser.getAttributeValue(null, "name"); + if (name != null) { + outPerms.add(name.intern()); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <perms> has" + " no name at " + + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <perms>: " + parser.getName()); + } + XmlUtils.skipCurrentTag(parser); + } + } + + private void readPreferredActivitiesLPw(XmlPullParser parser) throws XmlPullParserException, + IOException { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("item")) { + PreferredActivity pa = new PreferredActivity(parser); + if (pa.mPref.getParseError() == null) { + mPreferredActivities.addFilter(pa); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <preferred-activity> " + + pa.mPref.getParseError() + " at " + + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <preferred-activities>: " + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + } + + // Returns -1 if we could not find an available UserId to assign + private int newUserIdLPw(Object obj) { + // Let's be stupidly inefficient for now... + final int N = mUserIds.size(); + for (int i = 0; i < N; i++) { + if (mUserIds.get(i) == null) { + mUserIds.set(i, obj); + return PackageManagerService.FIRST_APPLICATION_UID + i; + } + } + + // None left? + if (N >= PackageManagerService.MAX_APPLICATION_UIDS) { + return -1; + } + + mUserIds.add(obj); + return PackageManagerService.FIRST_APPLICATION_UID + N; + } + + public PackageSetting getDisabledSystemPkgLPr(String name) { + PackageSetting ps = mDisabledSysPackages.get(name); + return ps; + } + + boolean isEnabledLPr(ComponentInfo componentInfo, int flags) { + if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { + return true; + } + final PackageSetting packageSettings = mPackages.get(componentInfo.packageName); + if (PackageManagerService.DEBUG_SETTINGS) { + Log.v(PackageManagerService.TAG, "isEnabledLock - packageName = " + componentInfo.packageName + + " componentName = " + componentInfo.name); + Log.v(PackageManagerService.TAG, "enabledComponents: " + + Arrays.toString(packageSettings.enabledComponents.toArray())); + Log.v(PackageManagerService.TAG, "disabledComponents: " + + Arrays.toString(packageSettings.disabledComponents.toArray())); + } + if (packageSettings == null) { + return false; + } + if (packageSettings.enabled == COMPONENT_ENABLED_STATE_DISABLED + || (packageSettings.pkg != null && !packageSettings.pkg.applicationInfo.enabled + && packageSettings.enabled == COMPONENT_ENABLED_STATE_DEFAULT)) { + return false; + } + if (packageSettings.enabledComponents.contains(componentInfo.name)) { + return true; + } + if (packageSettings.disabledComponents.contains(componentInfo.name)) { + return false; + } + return componentInfo.enabled; + } + + String getInstallerPackageNameLPr(String packageName) { + final PackageSetting pkg = mPackages.get(packageName); + if (pkg == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + return pkg.installerPackageName; + } + + int getApplicationEnabledSettingLPr(String packageName) { + final PackageSetting pkg = mPackages.get(packageName); + if (pkg == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + return pkg.enabled; + } + + int getComponentEnabledSettingLPr(ComponentName componentName) { + final String packageName = componentName.getPackageName(); + final PackageSetting pkg = mPackages.get(packageName); + if (pkg == null) { + throw new IllegalArgumentException("Unknown component: " + componentName); + } + final String classNameStr = componentName.getClassName(); + return pkg.getCurrentEnabledStateLPr(classNameStr); + } + + boolean setPackageStoppedStateLPw(String packageName, boolean stopped, + boolean allowedByPermission, int uid) { + final PackageSetting pkgSetting = mPackages.get(packageName); + if (pkgSetting == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + if (!allowedByPermission && (uid != pkgSetting.userId)) { + throw new SecurityException( + "Permission Denial: attempt to change stopped state from pid=" + + Binder.getCallingPid() + + ", uid=" + uid + ", package uid=" + pkgSetting.userId); + } + if (DEBUG_STOPPED) { + if (stopped) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Slog.i(TAG, "Stopping package " + packageName, e); + } + } + if (pkgSetting.stopped != stopped) { + pkgSetting.stopped = stopped; + pkgSetting.pkg.mSetStopped = stopped; + if (pkgSetting.notLaunched) { + if (pkgSetting.installerPackageName != null) { + PackageManagerService.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, + pkgSetting.name, null, + pkgSetting.installerPackageName, null); + } + pkgSetting.notLaunched = false; + } + return true; + } + return false; + } + + void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState) { + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + final Date date = new Date(); + boolean printedSomething = false; + for (final PackageSetting ps : mPackages.values()) { + if (packageName != null && !packageName.equals(ps.realName) + && !packageName.equals(ps.name)) { + continue; + } + + if (packageName != null) { + dumpState.setSharedUser(ps.sharedUser); + } + + if (!printedSomething) { + if (dumpState.onTitlePrinted()) + pw.println(" "); + pw.println("Packages:"); + printedSomething = true; + } + pw.print(" Package ["); + pw.print(ps.realName != null ? ps.realName : ps.name); + pw.print("] ("); + pw.print(Integer.toHexString(System.identityHashCode(ps))); + pw.println("):"); + + if (ps.realName != null) { + pw.print(" compat name="); + pw.println(ps.name); + } + + pw.print(" userId="); pw.print(ps.userId); + pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids)); + pw.print(" sharedUser="); pw.println(ps.sharedUser); + pw.print(" pkg="); pw.println(ps.pkg); + pw.print(" codePath="); pw.println(ps.codePathString); + pw.print(" resourcePath="); pw.println(ps.resourcePathString); + pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString); + pw.print(" versionCode="); pw.println(ps.versionCode); + if (ps.pkg != null) { + pw.print(" versionName="); pw.println(ps.pkg.mVersionName); + pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir); + pw.print(" targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion); + if (ps.pkg.mOperationPending) { + pw.println(" mOperationPending=true"); + } + pw.print(" supportsScreens=["); + boolean first = true; + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("small"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("medium"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("large"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("xlarge"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("resizeable"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("anyDensity"); + } + } + pw.println("]"); + pw.print(" timeStamp="); + date.setTime(ps.timeStamp); + pw.println(sdf.format(date)); + pw.print(" firstInstallTime="); + date.setTime(ps.firstInstallTime); + pw.println(sdf.format(date)); + pw.print(" lastUpdateTime="); + date.setTime(ps.lastUpdateTime); + pw.println(sdf.format(date)); + if (ps.installerPackageName != null) { + pw.print(" installerPackageName="); pw.println(ps.installerPackageName); + } + pw.print(" signatures="); pw.println(ps.signatures); + pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed); + pw.print(" haveGids="); pw.println(ps.haveGids); + pw.print(" pkgFlags=0x"); pw.print(Integer.toHexString(ps.pkgFlags)); + pw.print(" installStatus="); pw.print(ps.installStatus); + pw.print(" stopped="); pw.print(ps.stopped); + pw.print(" enabled="); pw.println(ps.enabled); + if (ps.disabledComponents.size() > 0) { + pw.println(" disabledComponents:"); + for (String s : ps.disabledComponents) { + pw.print(" "); pw.println(s); + } + } + if (ps.enabledComponents.size() > 0) { + pw.println(" enabledComponents:"); + for (String s : ps.enabledComponents) { + pw.print(" "); pw.println(s); + } + } + if (ps.grantedPermissions.size() > 0) { + pw.println(" grantedPermissions:"); + for (String s : ps.grantedPermissions) { + pw.print(" "); pw.println(s); + } + } + } + + printedSomething = false; + if (mRenamedPackages.size() > 0) { + for (final HashMap.Entry<String, String> e : mRenamedPackages.entrySet()) { + if (packageName != null && !packageName.equals(e.getKey()) + && !packageName.equals(e.getValue())) { + continue; + } + if (!printedSomething) { + if (dumpState.onTitlePrinted()) + pw.println(" "); + pw.println("Renamed packages:"); + printedSomething = true; + } + pw.print(" "); + pw.print(e.getKey()); + pw.print(" -> "); + pw.println(e.getValue()); + } + } + + printedSomething = false; + if (mDisabledSysPackages.size() > 0) { + for (final PackageSetting ps : mDisabledSysPackages.values()) { + if (packageName != null && !packageName.equals(ps.realName) + && !packageName.equals(ps.name)) { + continue; + } + if (!printedSomething) { + if (dumpState.onTitlePrinted()) + pw.println(" "); + pw.println("Hidden system packages:"); + printedSomething = true; + } + pw.print(" Package ["); + pw.print(ps.realName != null ? ps.realName : ps.name); + pw.print("] ("); + pw.print(Integer.toHexString(System.identityHashCode(ps))); + pw.println("):"); + if (ps.realName != null) { + pw.print(" compat name="); + pw.println(ps.name); + } + pw.print(" userId="); + pw.println(ps.userId); + pw.print(" sharedUser="); + pw.println(ps.sharedUser); + pw.print(" codePath="); + pw.println(ps.codePathString); + pw.print(" resourcePath="); + pw.println(ps.resourcePathString); + } + } + } + + void dumpPermissionsLPr(PrintWriter pw, String packageName, DumpState dumpState) { + boolean printedSomething = false; + for (BasePermission p : mPermissions.values()) { + if (packageName != null && !packageName.equals(p.sourcePackage)) { + continue; + } + if (!printedSomething) { + if (dumpState.onTitlePrinted()) + pw.println(" "); + pw.println("Permissions:"); + printedSomething = true; + } + pw.print(" Permission ["); pw.print(p.name); pw.print("] ("); + pw.print(Integer.toHexString(System.identityHashCode(p))); + pw.println("):"); + pw.print(" sourcePackage="); pw.println(p.sourcePackage); + pw.print(" uid="); pw.print(p.uid); + pw.print(" gids="); pw.print(PackageManagerService.arrayToString(p.gids)); + pw.print(" type="); pw.print(p.type); + pw.print(" prot="); pw.println(p.protectionLevel); + if (p.packageSetting != null) { + pw.print(" packageSetting="); pw.println(p.packageSetting); + } + if (p.perm != null) { + pw.print(" perm="); pw.println(p.perm); + } + } + } + + void dumpSharedUsersLPr(PrintWriter pw, String packageName, DumpState dumpState) { + boolean printedSomething = false; + for (SharedUserSetting su : mSharedUsers.values()) { + if (packageName != null && su != dumpState.getSharedUser()) { + continue; + } + if (!printedSomething) { + if (dumpState.onTitlePrinted()) + pw.println(" "); + pw.println("Shared users:"); + printedSomething = true; + } + pw.print(" SharedUser ["); + pw.print(su.name); + pw.print("] ("); + pw.print(Integer.toHexString(System.identityHashCode(su))); + pw.println("):"); + pw.print(" userId="); + pw.print(su.userId); + pw.print(" gids="); + pw.println(PackageManagerService.arrayToString(su.gids)); + pw.println(" grantedPermissions:"); + for (String s : su.grantedPermissions) { + pw.print(" "); + pw.println(s); + } + } + } + + void dumpReadMessagesLPr(PrintWriter pw, DumpState dumpState) { + pw.println("Settings parse messages:"); + pw.print(mReadMessages.toString()); + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/pm/SharedUserSetting.java b/services/java/com/android/server/pm/SharedUserSetting.java new file mode 100644 index 0000000..76826ea --- /dev/null +++ b/services/java/com/android/server/pm/SharedUserSetting.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import java.util.HashSet; + +/** + * Settings data for a particular shared user ID we know about. + */ +final class SharedUserSetting extends GrantedPermissions { + final String name; + + int userId; + + final HashSet<PackageSetting> packages = new HashSet<PackageSetting>(); + + final PackageSignatures signatures = new PackageSignatures(); + + SharedUserSetting(String _name, int _pkgFlags) { + super(_pkgFlags); + name = _name; + } + + @Override + public String toString() { + return "SharedUserSetting{" + Integer.toHexString(System.identityHashCode(this)) + " " + + name + "/" + userId + "}"; + } +} diff --git a/services/java/com/android/server/pm/UserManager.java b/services/java/com/android/server/pm/UserManager.java new file mode 100644 index 0000000..76fa5ab --- /dev/null +++ b/services/java/com/android/server/pm/UserManager.java @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import com.android.internal.util.FastXmlSerializer; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.os.Environment; +import android.os.FileUtils; +import android.os.SystemClock; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.util.Xml; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +public class UserManager { + private static final String TAG_NAME = "name"; + + private static final String ATTR_FLAGS = "flags"; + + private static final String ATTR_ID = "id"; + + private static final String TAG_USERS = "users"; + + private static final String TAG_USER = "user"; + + private static final String LOG_TAG = "UserManager"; + + private static final String USER_INFO_DIR = "system" + File.separator + "users"; + private static final String USER_LIST_FILENAME = "userlist.xml"; + + private SparseArray<UserInfo> mUsers; + + private final File mUsersDir; + private final File mUserListFile; + private int[] mUserIds; + + private Installer mInstaller; + private File mBaseUserPath; + + /** + * Available for testing purposes. + */ + UserManager(File dataDir, File baseUserPath) { + mUsersDir = new File(dataDir, USER_INFO_DIR); + mUsersDir.mkdirs(); + mBaseUserPath = baseUserPath; + FileUtils.setPermissions(mUsersDir.toString(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG + |FileUtils.S_IROTH|FileUtils.S_IXOTH, + -1, -1); + mUserListFile = new File(mUsersDir, USER_LIST_FILENAME); + readUserList(); + } + + public UserManager(Installer installer, File baseUserPath) { + this(Environment.getDataDirectory(), baseUserPath); + mInstaller = installer; + } + + public List<UserInfo> getUsers() { + ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size()); + for (int i = 0; i < mUsers.size(); i++) { + users.add(mUsers.valueAt(i)); + } + return users; + } + + /** + * Returns an array of user ids. This array is cached here for quick access, so do not modify or + * cache it elsewhere. + * @return the array of user ids. + */ + int[] getUserIds() { + return mUserIds; + } + + private void readUserList() { + mUsers = new SparseArray<UserInfo>(); + if (!mUserListFile.exists()) { + fallbackToSingleUser(); + return; + } + FileInputStream fis = null; + try { + fis = new FileInputStream(mUserListFile); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + Slog.e(LOG_TAG, "Unable to read user list"); + fallbackToSingleUser(); + return; + } + + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { + String id = parser.getAttributeValue(null, ATTR_ID); + UserInfo user = readUser(Integer.parseInt(id)); + if (user != null) { + mUsers.put(user.id, user); + } + } + } + updateUserIds(); + } catch (IOException ioe) { + fallbackToSingleUser(); + } catch (XmlPullParserException pe) { + fallbackToSingleUser(); + } + } + + private void fallbackToSingleUser() { + // Create the primary user + UserInfo primary = new UserInfo(0, "Primary", + UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); + mUsers.put(0, primary); + updateUserIds(); + + writeUserList(); + writeUser(primary); + } + + /* + * Writes the user file in this format: + * + * <user flags="20039023" id="0"> + * <name>Primary</name> + * </user> + */ + private void writeUser(UserInfo userInfo) { + try { + final File mUserFile = new File(mUsersDir, userInfo.id + ".xml"); + final FileOutputStream fos = new FileOutputStream(mUserFile); + final BufferedOutputStream bos = new BufferedOutputStream(fos); + + // XmlSerializer serializer = XmlUtils.serializerInstance(); + final XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(bos, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startTag(null, TAG_USER); + serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id)); + serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags)); + + serializer.startTag(null, TAG_NAME); + serializer.text(userInfo.name); + serializer.endTag(null, TAG_NAME); + + serializer.endTag(null, TAG_USER); + + serializer.endDocument(); + } catch (IOException ioe) { + Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe); + } + } + + /* + * Writes the user list file in this format: + * + * <users> + * <user id="0"></user> + * <user id="2"></user> + * </users> + */ + private void writeUserList() { + try { + final FileOutputStream fos = new FileOutputStream(mUserListFile); + final BufferedOutputStream bos = new BufferedOutputStream(fos); + + // XmlSerializer serializer = XmlUtils.serializerInstance(); + final XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(bos, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startTag(null, TAG_USERS); + + for (int i = 0; i < mUsers.size(); i++) { + UserInfo user = mUsers.valueAt(i); + serializer.startTag(null, TAG_USER); + serializer.attribute(null, ATTR_ID, Integer.toString(user.id)); + serializer.endTag(null, TAG_USER); + } + + serializer.endTag(null, TAG_USERS); + + serializer.endDocument(); + } catch (IOException ioe) { + Slog.e(LOG_TAG, "Error writing user list"); + } + } + + private UserInfo readUser(int id) { + int flags = 0; + String name = null; + + FileInputStream fis = null; + try { + File userFile = new File(mUsersDir, Integer.toString(id) + ".xml"); + fis = new FileInputStream(userFile); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + Slog.e(LOG_TAG, "Unable to read user " + id); + return null; + } + + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { + String storedId = parser.getAttributeValue(null, ATTR_ID); + if (Integer.parseInt(storedId) != id) { + Slog.e(LOG_TAG, "User id does not match the file name"); + return null; + } + String flagString = parser.getAttributeValue(null, ATTR_FLAGS); + flags = Integer.parseInt(flagString); + + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + } + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) { + type = parser.next(); + if (type == XmlPullParser.TEXT) { + name = parser.getText(); + } + } + } + fis.close(); + + UserInfo userInfo = new UserInfo(id, name, flags); + return userInfo; + + } catch (IOException ioe) { + } catch (XmlPullParserException pe) { + } + return null; + } + + public UserInfo createUser(String name, int flags, List<ApplicationInfo> apps) { + int userId = getNextAvailableId(); + UserInfo userInfo = new UserInfo(userId, name, flags); + File userPath = new File(mBaseUserPath, Integer.toString(userId)); + if (!createPackageFolders(userId, userPath, apps)) { + return null; + } + mUsers.put(userId, userInfo); + writeUserList(); + writeUser(userInfo); + updateUserIds(); + return userInfo; + } + + /** + * Removes a user and all data directories created for that user. This method should be called + * after the user's processes have been terminated. + * @param id the user's id + */ + public void removeUser(int id) { + // Remove from the list + UserInfo userInfo = mUsers.get(id); + if (userInfo != null) { + // Remove this user from the list + mUsers.remove(id); + // Remove user file + File userFile = new File(mUsersDir, id + ".xml"); + userFile.delete(); + // Update the user list + writeUserList(); + // Remove the data directories for all packages for this user + removePackageFolders(id); + updateUserIds(); + } + } + + public void installPackageForAllUsers(String packageName, int uid) { + for (int userId : mUserIds) { + // Don't do it for the primary user, it will become recursive. + if (userId == 0) + continue; + mInstaller.createUserData(packageName, PackageManager.getUid(userId, uid), + userId); + } + } + + public void clearUserDataForAllUsers(String packageName) { + for (int userId : mUserIds) { + // Don't do it for the primary user, it will become recursive. + if (userId == 0) + continue; + mInstaller.clearUserData(packageName, userId); + } + } + + public void removePackageForAllUsers(String packageName) { + for (int userId : mUserIds) { + // Don't do it for the primary user, it will become recursive. + if (userId == 0) + continue; + mInstaller.remove(packageName, userId); + } + } + + /** + * Caches the list of user ids in an array, adjusting the array size when necessary. + */ + private void updateUserIds() { + if (mUserIds == null || mUserIds.length != mUsers.size()) { + mUserIds = new int[mUsers.size()]; + } + for (int i = 0; i < mUsers.size(); i++) { + mUserIds[i] = mUsers.keyAt(i); + } + } + + /** + * Returns the next available user id, filling in any holes in the ids. + * @return + */ + private int getNextAvailableId() { + int i = 0; + while (i < Integer.MAX_VALUE) { + if (mUsers.indexOfKey(i) < 0) { + break; + } + i++; + } + return i; + } + + private boolean createPackageFolders(int id, File userPath, final List<ApplicationInfo> apps) { + // mInstaller may not be available for unit-tests. + if (mInstaller == null || apps == null) return true; + + final long startTime = SystemClock.elapsedRealtime(); + // Create the user path + userPath.mkdir(); + FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG + | FileUtils.S_IXOTH, -1, -1); + + // Create the individual data directories + for (ApplicationInfo app : apps) { + if (app.uid > android.os.Process.FIRST_APPLICATION_UID + && app.uid < PackageManager.PER_USER_RANGE) { + mInstaller.createUserData(app.packageName, + PackageManager.getUid(id, app.uid), id); + } + } + final long stopTime = SystemClock.elapsedRealtime(); + Log.i(LOG_TAG, + "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms"); + return true; + } + + private boolean removePackageFolders(int id) { + // mInstaller may not be available for unit-tests. + if (mInstaller == null) return true; + + mInstaller.removeUserDataDirs(id); + return true; + } +} diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java new file mode 100644 index 0000000..3791cc4 --- /dev/null +++ b/services/java/com/android/server/usb/UsbDeviceManager.java @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions an + * limitations under the License. + */ + +package com.android.server.usb; + +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbManager; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Parcelable; +import android.os.ParcelFileDescriptor; +import android.os.UEventObserver; +import android.provider.Settings; +import android.util.Log; +import android.util.Slog; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * UsbDeviceManager manages USB state in device mode. + */ +public class UsbDeviceManager { + private static final String TAG = UsbDeviceManager.class.getSimpleName(); + private static final boolean LOG = false; + + private static final String USB_CONNECTED_MATCH = + "DEVPATH=/devices/virtual/switch/usb_connected"; + private static final String USB_CONFIGURATION_MATCH = + "DEVPATH=/devices/virtual/switch/usb_configuration"; + private static final String USB_FUNCTIONS_MATCH = + "DEVPATH=/devices/virtual/usb_composite/"; + private static final String USB_CONNECTED_PATH = + "/sys/class/switch/usb_connected/state"; + private static final String USB_CONFIGURATION_PATH = + "/sys/class/switch/usb_configuration/state"; + private static final String USB_COMPOSITE_CLASS_PATH = + "/sys/class/usb_composite"; + + private static final int MSG_UPDATE_STATE = 0; + private static final int MSG_FUNCTION_ENABLED = 1; + private static final int MSG_FUNCTION_DISABLED = 2; + + // Delay for debouncing USB disconnects. + // We often get rapid connect/disconnect events when enabling USB functions, + // which need debouncing. + private static final int UPDATE_DELAY = 1000; + + // current connected and configuration state + private int mConnected; + private int mConfiguration; + + // last broadcasted connected and configuration state + private int mLastConnected = -1; + private int mLastConfiguration = -1; + + // lists of enabled and disabled USB functions + private final ArrayList<String> mEnabledFunctions = new ArrayList<String>(); + private final ArrayList<String> mDisabledFunctions = new ArrayList<String>(); + + private boolean mSystemReady; + + private UsbAccessory mCurrentAccessory; + // USB functions that are enabled by default, to restore after exiting accessory mode + private final ArrayList<String> mDefaultFunctions = new ArrayList<String>(); + + private final Context mContext; + private final Object mLock = new Object(); + private final UsbSettingsManager mSettingsManager; + private final boolean mHasUsbAccessory; + + private final void readCurrentAccessoryLocked() { + if (mHasUsbAccessory) { + String[] strings = nativeGetAccessoryStrings(); + if (strings != null) { + mCurrentAccessory = new UsbAccessory(strings); + Log.d(TAG, "entering USB accessory mode: " + mCurrentAccessory); + if (mSystemReady) { + mSettingsManager.accessoryAttached(mCurrentAccessory); + } + } else { + Log.e(TAG, "nativeGetAccessoryStrings failed"); + } + } + } + + /* + * Handles USB function enable/disable events + */ + private final void functionEnabledLocked(String function, boolean enabled) { + if (enabled) { + if (!mEnabledFunctions.contains(function)) { + mEnabledFunctions.add(function); + } + mDisabledFunctions.remove(function); + + if (UsbManager.USB_FUNCTION_ACCESSORY.equals(function)) { + readCurrentAccessoryLocked(); + } + } else { + if (!mDisabledFunctions.contains(function)) { + mDisabledFunctions.add(function); + } + mEnabledFunctions.remove(function); + } + } + + /* + * Listens for uevent messages from the kernel to monitor the USB state + */ + private final UEventObserver mUEventObserver = new UEventObserver() { + @Override + public void onUEvent(UEventObserver.UEvent event) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Slog.v(TAG, "USB UEVENT: " + event.toString()); + } + + synchronized (mLock) { + String name = event.get("SWITCH_NAME"); + String state = event.get("SWITCH_STATE"); + if (name != null && state != null) { + try { + int intState = Integer.parseInt(state); + if ("usb_connected".equals(name)) { + mConnected = intState; + // trigger an Intent broadcast + if (mSystemReady) { + // debounce disconnects to avoid problems bringing up USB tethering + update(mConnected == 0); + } + } else if ("usb_configuration".equals(name)) { + mConfiguration = intState; + // trigger an Intent broadcast + if (mSystemReady) { + update(mConnected == 0); + } + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Could not parse switch state from event " + event); + } + } else { + String function = event.get("FUNCTION"); + String enabledStr = event.get("ENABLED"); + if (function != null && enabledStr != null) { + // Note: we do not broadcast a change when a function is enabled or disabled. + // We just record the state change for the next broadcast. + int what = ("1".equals(enabledStr) ? + MSG_FUNCTION_ENABLED : MSG_FUNCTION_DISABLED); + Message msg = Message.obtain(mHandler, what); + msg.obj = function; + mHandler.sendMessage(msg); + } + } + } + } + }; + + private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + // handle accessories attached at boot time + synchronized (mLock) { + if (mCurrentAccessory != null) { + mSettingsManager.accessoryAttached(mCurrentAccessory); + } + } + } + }; + + public UsbDeviceManager(Context context, UsbSettingsManager settingsManager) { + mContext = context; + mSettingsManager = settingsManager; + PackageManager pm = mContext.getPackageManager(); + mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY); + + synchronized (mLock) { + init(); // set initial status + + // Watch for USB configuration changes + if (mConfiguration >= 0) { + mUEventObserver.startObserving(USB_CONNECTED_MATCH); + mUEventObserver.startObserving(USB_CONFIGURATION_MATCH); + mUEventObserver.startObserving(USB_FUNCTIONS_MATCH); + } + } + } + + private final void init() { + char[] buffer = new char[1024]; + boolean inAccessoryMode = false; + + // Read initial USB state + mConfiguration = -1; + try { + FileReader file = new FileReader(USB_CONNECTED_PATH); + int len = file.read(buffer, 0, 1024); + file.close(); + mConnected = Integer.valueOf((new String(buffer, 0, len)).trim()); + + file = new FileReader(USB_CONFIGURATION_PATH); + len = file.read(buffer, 0, 1024); + file.close(); + mConfiguration = Integer.valueOf((new String(buffer, 0, len)).trim()); + + } catch (FileNotFoundException e) { + Slog.i(TAG, "This kernel does not have USB configuration switch support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + if (mConfiguration < 0) { + // This may happen in the emulator or devices without USB device mode support + return; + } + + // Read initial list of enabled and disabled functions (device mode) + try { + File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles(); + for (int i = 0; i < files.length; i++) { + File file = new File(files[i], "enable"); + FileReader reader = new FileReader(file); + int len = reader.read(buffer, 0, 1024); + reader.close(); + int value = Integer.valueOf((new String(buffer, 0, len)).trim()); + String functionName = files[i].getName(); + if (value == 1) { + mEnabledFunctions.add(functionName); + if (UsbManager.USB_FUNCTION_ACCESSORY.equals(functionName)) { + // The USB accessory driver is on by default, but it might have been + // enabled before the USB service has initialized. + inAccessoryMode = true; + } else if (!UsbManager.USB_FUNCTION_ADB.equals(functionName)) { + // adb is enabled/disabled automatically by the adbd daemon, + // so don't treat it as a default function. + mDefaultFunctions.add(functionName); + } + } else { + mDisabledFunctions.add(functionName); + } + } + } catch (FileNotFoundException e) { + Slog.w(TAG, "This kernel does not have USB composite class support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + + // handle the case where an accessory switched the driver to accessory mode + // before the framework finished booting + if (inAccessoryMode) { + readCurrentAccessoryLocked(); + + // FIXME - if we booted in accessory mode, then we have no way to figure out + // which functions are enabled by default. + // For now, assume that MTP or mass storage are the only possibilities + if (mDisabledFunctions.contains(UsbManager.USB_FUNCTION_MTP)) { + mDefaultFunctions.add(UsbManager.USB_FUNCTION_MTP); + } else if (mDisabledFunctions.contains(UsbManager.USB_FUNCTION_MASS_STORAGE)) { + mDefaultFunctions.add(UsbManager.USB_FUNCTION_MASS_STORAGE); + } + } + } + + public void systemReady() { + synchronized (mLock) { + update(false); + if (mCurrentAccessory != null) { + Log.d(TAG, "accessoryAttached at systemReady"); + // its still too early to handle accessories, so add a BOOT_COMPLETED receiver + // to handle this later. + mContext.registerReceiver(mBootCompletedReceiver, + new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); + } + mSystemReady = true; + } + } + + /* + * Sends a message to update the USB connected and configured state (device mode). + * If delayed is true, then we add a small delay in sending the message to debounce + * the USB connection when enabling USB tethering. + */ + private final void update(boolean delayed) { + mHandler.removeMessages(MSG_UPDATE_STATE); + mHandler.sendEmptyMessageDelayed(MSG_UPDATE_STATE, delayed ? UPDATE_DELAY : 0); + } + + /* returns the currently attached USB accessory (device mode) */ + public UsbAccessory getCurrentAccessory() { + return mCurrentAccessory; + } + + /* opens the currently attached USB accessory (device mode) */ + public ParcelFileDescriptor openAccessory(UsbAccessory accessory) { + synchronized (mLock) { + if (mCurrentAccessory == null) { + throw new IllegalArgumentException("no accessory attached"); + } + if (!mCurrentAccessory.equals(accessory)) { + Log.e(TAG, accessory.toString() + " does not match current accessory " + + mCurrentAccessory); + throw new IllegalArgumentException("accessory not attached"); + } + mSettingsManager.checkPermission(mCurrentAccessory); + return nativeOpenAccessory(); + } + } + + /* + * This handler is for deferred handling of events related to device mode and accessories. + */ + private final Handler mHandler = new Handler() { + private void addEnabledFunctionsLocked(Intent intent) { + // include state of all USB functions in our extras + for (int i = 0; i < mEnabledFunctions.size(); i++) { + intent.putExtra(mEnabledFunctions.get(i), UsbManager.USB_FUNCTION_ENABLED); + } + for (int i = 0; i < mDisabledFunctions.size(); i++) { + intent.putExtra(mDisabledFunctions.get(i), UsbManager.USB_FUNCTION_DISABLED); + } + } + + @Override + public void handleMessage(Message msg) { + synchronized (mLock) { + switch (msg.what) { + case MSG_UPDATE_STATE: + if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) { + if (mConnected == 0) { + if (UsbManager.isFunctionEnabled( + UsbManager.USB_FUNCTION_ACCESSORY)) { + // make sure accessory mode is off, and restore default functions + Log.d(TAG, "exited USB accessory mode"); + if (!UsbManager.setFunctionEnabled + (UsbManager.USB_FUNCTION_ACCESSORY, false)) { + Log.e(TAG, "could not disable accessory function"); + } + int count = mDefaultFunctions.size(); + for (int i = 0; i < count; i++) { + String function = mDefaultFunctions.get(i); + if (!UsbManager.setFunctionEnabled(function, true)) { + Log.e(TAG, "could not reenable function " + function); + } + } + + if (mCurrentAccessory != null) { + mSettingsManager.accessoryDetached(mCurrentAccessory); + mCurrentAccessory = null; + } + } + } + + final ContentResolver cr = mContext.getContentResolver(); + if (Settings.Secure.getInt(cr, + Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { + Slog.i(TAG, "Device not provisioned, skipping USB broadcast"); + return; + } + + mLastConnected = mConnected; + mLastConfiguration = mConfiguration; + + // send a sticky broadcast containing current USB state + Intent intent = new Intent(UsbManager.ACTION_USB_STATE); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra(UsbManager.USB_CONNECTED, mConnected != 0); + intent.putExtra(UsbManager.USB_CONFIGURATION, mConfiguration); + addEnabledFunctionsLocked(intent); + mContext.sendStickyBroadcast(intent); + } + break; + case MSG_FUNCTION_ENABLED: + case MSG_FUNCTION_DISABLED: + functionEnabledLocked((String)msg.obj, msg.what == MSG_FUNCTION_ENABLED); + break; + } + } + } + }; + + public void dump(FileDescriptor fd, PrintWriter pw) { + synchronized (mLock) { + pw.println(" USB Device State:"); + pw.print(" Enabled Functions: "); + for (int i = 0; i < mEnabledFunctions.size(); i++) { + pw.print(mEnabledFunctions.get(i) + " "); + } + pw.println(""); + pw.print(" Disabled Functions: "); + for (int i = 0; i < mDisabledFunctions.size(); i++) { + pw.print(mDisabledFunctions.get(i) + " "); + } + pw.println(""); + pw.print(" Default Functions: "); + for (int i = 0; i < mDefaultFunctions.size(); i++) { + pw.print(mDefaultFunctions.get(i) + " "); + } + pw.println(""); + pw.println(" mConnected: " + mConnected + ", mConfiguration: " + mConfiguration); + pw.println(" mCurrentAccessory: " + mCurrentAccessory); + } + } + + private native String[] nativeGetAccessoryStrings(); + private native ParcelFileDescriptor nativeOpenAccessory(); +} diff --git a/services/java/com/android/server/usb/UsbHostManager.java b/services/java/com/android/server/usb/UsbHostManager.java new file mode 100644 index 0000000..923b049 --- /dev/null +++ b/services/java/com/android/server/usb/UsbHostManager.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions an + * limitations under the License. + */ + +package com.android.server.usb; + +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.IUsbManager; +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Parcelable; +import android.os.ParcelFileDescriptor; +import android.os.UEventObserver; +import android.provider.Settings; +import android.util.Log; +import android.util.Slog; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileReader; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.List; + +/** + * UsbHostManager manages USB state in host mode. + */ +public class UsbHostManager { + private static final String TAG = UsbHostManager.class.getSimpleName(); + private static final boolean LOG = false; + + // contains all connected USB devices + private final HashMap<String,UsbDevice> mDevices = new HashMap<String,UsbDevice>(); + + // USB busses to exclude from USB host support + private final String[] mHostBlacklist; + + private final Context mContext; + private final Object mLock = new Object(); + private final UsbSettingsManager mSettingsManager; + + public UsbHostManager(Context context, UsbSettingsManager settingsManager) { + mContext = context; + mSettingsManager = settingsManager; + mHostBlacklist = context.getResources().getStringArray( + com.android.internal.R.array.config_usbHostBlacklist); + } + + private boolean isBlackListed(String deviceName) { + int count = mHostBlacklist.length; + for (int i = 0; i < count; i++) { + if (deviceName.startsWith(mHostBlacklist[i])) { + return true; + } + } + return false; + } + + /* returns true if the USB device should not be accessible by applications */ + private boolean isBlackListed(int clazz, int subClass, int protocol) { + // blacklist hubs + if (clazz == UsbConstants.USB_CLASS_HUB) return true; + + // blacklist HID boot devices (mouse and keyboard) + if (clazz == UsbConstants.USB_CLASS_HID && + subClass == UsbConstants.USB_INTERFACE_SUBCLASS_BOOT) { + return true; + } + + return false; + } + + /* Called from JNI in monitorUsbHostBus() to report new USB devices */ + private void usbDeviceAdded(String deviceName, int vendorID, int productID, + int deviceClass, int deviceSubclass, int deviceProtocol, + /* array of quintuples containing id, class, subclass, protocol + and number of endpoints for each interface */ + int[] interfaceValues, + /* array of quadruples containing address, attributes, max packet size + and interval for each endpoint */ + int[] endpointValues) { + + if (isBlackListed(deviceName) || + isBlackListed(deviceClass, deviceSubclass, deviceProtocol)) { + return; + } + + synchronized (mLock) { + if (mDevices.get(deviceName) != null) { + Log.w(TAG, "device already on mDevices list: " + deviceName); + return; + } + + int numInterfaces = interfaceValues.length / 5; + Parcelable[] interfaces = new UsbInterface[numInterfaces]; + try { + // repackage interfaceValues as an array of UsbInterface + int intf, endp, ival = 0, eval = 0; + for (intf = 0; intf < numInterfaces; intf++) { + int interfaceId = interfaceValues[ival++]; + int interfaceClass = interfaceValues[ival++]; + int interfaceSubclass = interfaceValues[ival++]; + int interfaceProtocol = interfaceValues[ival++]; + int numEndpoints = interfaceValues[ival++]; + + Parcelable[] endpoints = new UsbEndpoint[numEndpoints]; + for (endp = 0; endp < numEndpoints; endp++) { + int address = endpointValues[eval++]; + int attributes = endpointValues[eval++]; + int maxPacketSize = endpointValues[eval++]; + int interval = endpointValues[eval++]; + endpoints[endp] = new UsbEndpoint(address, attributes, + maxPacketSize, interval); + } + + // don't allow if any interfaces are blacklisted + if (isBlackListed(interfaceClass, interfaceSubclass, interfaceProtocol)) { + return; + } + interfaces[intf] = new UsbInterface(interfaceId, interfaceClass, + interfaceSubclass, interfaceProtocol, endpoints); + } + } catch (Exception e) { + // beware of index out of bound exceptions, which might happen if + // a device does not set bNumEndpoints correctly + Log.e(TAG, "error parsing USB descriptors", e); + return; + } + + UsbDevice device = new UsbDevice(deviceName, vendorID, productID, + deviceClass, deviceSubclass, deviceProtocol, interfaces); + mDevices.put(deviceName, device); + mSettingsManager.deviceAttached(device); + } + } + + /* Called from JNI in monitorUsbHostBus to report USB device removal */ + private void usbDeviceRemoved(String deviceName) { + synchronized (mLock) { + UsbDevice device = mDevices.remove(deviceName); + if (device != null) { + mSettingsManager.deviceDetached(device); + } + } + } + + public void systemReady() { + synchronized (mLock) { + // Create a thread to call into native code to wait for USB host events. + // This thread will call us back on usbDeviceAdded and usbDeviceRemoved. + Runnable runnable = new Runnable() { + public void run() { + monitorUsbHostBus(); + } + }; + new Thread(null, runnable, "UsbService host thread").start(); + } + } + + /* Returns a list of all currently attached USB devices */ + public void getDeviceList(Bundle devices) { + synchronized (mLock) { + for (String name : mDevices.keySet()) { + devices.putParcelable(name, mDevices.get(name)); + } + } + } + + /* Opens the specified USB device */ + public ParcelFileDescriptor openDevice(String deviceName) { + synchronized (mLock) { + if (isBlackListed(deviceName)) { + throw new SecurityException("USB device is on a restricted bus"); + } + UsbDevice device = mDevices.get(deviceName); + if (device == null) { + // if it is not in mDevices, it either does not exist or is blacklisted + throw new IllegalArgumentException( + "device " + deviceName + " does not exist or is restricted"); + } + mSettingsManager.checkPermission(device); + return nativeOpenDevice(deviceName); + } + } + + public void dump(FileDescriptor fd, PrintWriter pw) { + synchronized (mLock) { + pw.println(" USB Host State:"); + for (String name : mDevices.keySet()) { + pw.println(" " + name + ": " + mDevices.get(name)); + } + } + } + + private native void monitorUsbHostBus(); + private native ParcelFileDescriptor nativeOpenDevice(String deviceName); +} diff --git a/services/java/com/android/server/usb/UsbService.java b/services/java/com/android/server/usb/UsbService.java index a151af0..21e5997 100644 --- a/services/java/com/android/server/usb/UsbService.java +++ b/services/java/com/android/server/usb/UsbService.java @@ -10,608 +10,142 @@ * 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 + * See the License for the specific language governing permissions an * limitations under the License. */ package com.android.server.usb; import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.PackageManager; import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbAccessory; -import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; -import android.hardware.usb.UsbManager; import android.net.Uri; import android.os.Binder; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Parcelable; import android.os.ParcelFileDescriptor; -import android.os.UEventObserver; -import android.provider.Settings; -import android.util.Log; -import android.util.Slog; import java.io.File; import java.io.FileDescriptor; -import java.io.FileNotFoundException; -import java.io.FileReader; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; /** - * UsbService monitors for changes to USB state. - * This includes code for both USB host support (where the android device is the host) - * as well as USB device support (android device is connected to a USB host). - * Accessory mode is a special case of USB device mode, where the android device is - * connected to a USB host that supports the android accessory protocol. + * UsbService manages all USB related state, including both host and device support. + * Host related events and calls are delegated to UsbHostManager, and device related + * support is delegated to UsbDeviceManager. */ public class UsbService extends IUsbManager.Stub { - private static final String TAG = UsbService.class.getSimpleName(); - private static final boolean LOG = false; - - private static final String USB_CONNECTED_MATCH = - "DEVPATH=/devices/virtual/switch/usb_connected"; - private static final String USB_CONFIGURATION_MATCH = - "DEVPATH=/devices/virtual/switch/usb_configuration"; - private static final String USB_FUNCTIONS_MATCH = - "DEVPATH=/devices/virtual/usb_composite/"; - private static final String USB_CONNECTED_PATH = - "/sys/class/switch/usb_connected/state"; - private static final String USB_CONFIGURATION_PATH = - "/sys/class/switch/usb_configuration/state"; - private static final String USB_COMPOSITE_CLASS_PATH = - "/sys/class/usb_composite"; - - private static final int MSG_UPDATE_STATE = 0; - private static final int MSG_FUNCTION_ENABLED = 1; - private static final int MSG_FUNCTION_DISABLED = 2; - - // Delay for debouncing USB disconnects. - // We often get rapid connect/disconnect events when enabling USB functions, - // which need debouncing. - private static final int UPDATE_DELAY = 1000; - - // current connected and configuration state - private int mConnected; - private int mConfiguration; - - // last broadcasted connected and configuration state - private int mLastConnected = -1; - private int mLastConfiguration = -1; - - // lists of enabled and disabled USB functions (for USB device mode) - private final ArrayList<String> mEnabledFunctions = new ArrayList<String>(); - private final ArrayList<String> mDisabledFunctions = new ArrayList<String>(); - - // contains all connected USB devices (for USB host mode) - private final HashMap<String,UsbDevice> mDevices = new HashMap<String,UsbDevice>(); - - // USB busses to exclude from USB host support - private final String[] mHostBlacklist; - - private boolean mSystemReady; - - private UsbAccessory mCurrentAccessory; - // USB functions that are enabled by default, to restore after exiting accessory mode - private final ArrayList<String> mDefaultFunctions = new ArrayList<String>(); - private final Context mContext; - private final Object mLock = new Object(); - private final UsbDeviceSettingsManager mDeviceManager; - private final boolean mHasUsbHost; - private final boolean mHasUsbAccessory; + private UsbDeviceManager mDeviceManager; + private UsbHostManager mHostManager; + private final UsbSettingsManager mSettingsManager; - private final void readCurrentAccessoryLocked() { - if (mHasUsbAccessory) { - String[] strings = nativeGetAccessoryStrings(); - if (strings != null) { - mCurrentAccessory = new UsbAccessory(strings); - Log.d(TAG, "entering USB accessory mode: " + mCurrentAccessory); - if (mSystemReady) { - mDeviceManager.accessoryAttached(mCurrentAccessory); - } - } else { - Log.e(TAG, "nativeGetAccessoryStrings failed"); - } - } - } - - /* - * Handles USB function enable/disable events (device mode) - */ - private final void functionEnabledLocked(String function, boolean enabled) { - if (enabled) { - if (!mEnabledFunctions.contains(function)) { - mEnabledFunctions.add(function); - } - mDisabledFunctions.remove(function); - - if (UsbManager.USB_FUNCTION_ACCESSORY.equals(function)) { - readCurrentAccessoryLocked(); - } - } else { - if (!mDisabledFunctions.contains(function)) { - mDisabledFunctions.add(function); - } - mEnabledFunctions.remove(function); - } - } - - /* - * Listens for uevent messages from the kernel to monitor the USB state (device mode) - */ - private final UEventObserver mUEventObserver = new UEventObserver() { - @Override - public void onUEvent(UEventObserver.UEvent event) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.v(TAG, "USB UEVENT: " + event.toString()); - } - - synchronized (mLock) { - String name = event.get("SWITCH_NAME"); - String state = event.get("SWITCH_STATE"); - if (name != null && state != null) { - try { - int intState = Integer.parseInt(state); - if ("usb_connected".equals(name)) { - mConnected = intState; - // trigger an Intent broadcast - if (mSystemReady) { - // debounce disconnects to avoid problems bringing up USB tethering - update(mConnected == 0); - } - } else if ("usb_configuration".equals(name)) { - mConfiguration = intState; - // trigger an Intent broadcast - if (mSystemReady) { - update(mConnected == 0); - } - } - } catch (NumberFormatException e) { - Slog.e(TAG, "Could not parse switch state from event " + event); - } - } else { - String function = event.get("FUNCTION"); - String enabledStr = event.get("ENABLED"); - if (function != null && enabledStr != null) { - // Note: we do not broadcast a change when a function is enabled or disabled. - // We just record the state change for the next broadcast. - int what = ("1".equals(enabledStr) ? - MSG_FUNCTION_ENABLED : MSG_FUNCTION_DISABLED); - Message msg = Message.obtain(mHandler, what); - msg.obj = function; - mHandler.sendMessage(msg); - } - } - } - } - }; - - private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - // handle accessories attached at boot time - synchronized (mLock) { - if (mCurrentAccessory != null) { - mDeviceManager.accessoryAttached(mCurrentAccessory); - } - } - } - }; public UsbService(Context context) { mContext = context; - mDeviceManager = new UsbDeviceSettingsManager(context); + mSettingsManager = new UsbSettingsManager(context); PackageManager pm = mContext.getPackageManager(); - mHasUsbHost = pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST); - mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY); - - mHostBlacklist = context.getResources().getStringArray( - com.android.internal.R.array.config_usbHostBlacklist); - - synchronized (mLock) { - init(); // set initial status - - // Watch for USB configuration changes - if (mConfiguration >= 0) { - mUEventObserver.startObserving(USB_CONNECTED_MATCH); - mUEventObserver.startObserving(USB_CONFIGURATION_MATCH); - mUEventObserver.startObserving(USB_FUNCTIONS_MATCH); - } - } - } - - private final void init() { - char[] buffer = new char[1024]; - boolean inAccessoryMode = false; - - // Read initial USB state (device mode) - mConfiguration = -1; - try { - FileReader file = new FileReader(USB_CONNECTED_PATH); - int len = file.read(buffer, 0, 1024); - file.close(); - mConnected = Integer.valueOf((new String(buffer, 0, len)).trim()); - - file = new FileReader(USB_CONFIGURATION_PATH); - len = file.read(buffer, 0, 1024); - file.close(); - mConfiguration = Integer.valueOf((new String(buffer, 0, len)).trim()); - - } catch (FileNotFoundException e) { - Slog.i(TAG, "This kernel does not have USB configuration switch support"); - } catch (Exception e) { - Slog.e(TAG, "" , e); - } - if (mConfiguration < 0) { - // This may happen in the emulator or devices without USB device mode support - return; - } - - // Read initial list of enabled and disabled functions (device mode) - try { - File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles(); - for (int i = 0; i < files.length; i++) { - File file = new File(files[i], "enable"); - FileReader reader = new FileReader(file); - int len = reader.read(buffer, 0, 1024); - reader.close(); - int value = Integer.valueOf((new String(buffer, 0, len)).trim()); - String functionName = files[i].getName(); - if (value == 1) { - mEnabledFunctions.add(functionName); - if (UsbManager.USB_FUNCTION_ACCESSORY.equals(functionName)) { - // The USB accessory driver is on by default, but it might have been - // enabled before the USB service has initialized. - inAccessoryMode = true; - } else if (!UsbManager.USB_FUNCTION_ADB.equals(functionName)) { - // adb is enabled/disabled automatically by the adbd daemon, - // so don't treat it as a default function. - mDefaultFunctions.add(functionName); - } - } else { - mDisabledFunctions.add(functionName); - } - } - } catch (FileNotFoundException e) { - Slog.w(TAG, "This kernel does not have USB composite class support"); - } catch (Exception e) { - Slog.e(TAG, "" , e); + if (pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) { + mHostManager = new UsbHostManager(context, mSettingsManager); } - - // handle the case where an accessory switched the driver to accessory mode - // before the framework finished booting - if (inAccessoryMode) { - readCurrentAccessoryLocked(); - - // FIXME - if we booted in accessory mode, then we have no way to figure out - // which functions are enabled by default. - // For now, assume that MTP or mass storage are the only possibilities - if (mDisabledFunctions.contains(UsbManager.USB_FUNCTION_MTP)) { - mDefaultFunctions.add(UsbManager.USB_FUNCTION_MTP); - } else if (mDisabledFunctions.contains(UsbManager.USB_FUNCTION_MASS_STORAGE)) { - mDefaultFunctions.add(UsbManager.USB_FUNCTION_MASS_STORAGE); - } - } - } - - private boolean isBlackListed(String deviceName) { - int count = mHostBlacklist.length; - for (int i = 0; i < count; i++) { - if (deviceName.startsWith(mHostBlacklist[i])) { - return true; - } + if (new File("/sys/class/usb_composite").exists()) { + mDeviceManager = new UsbDeviceManager(context, mSettingsManager); } - return false; - } - - /* returns true if the USB device should not be accessible by applications (host mode) */ - private boolean isBlackListed(int clazz, int subClass, int protocol) { - // blacklist hubs - if (clazz == UsbConstants.USB_CLASS_HUB) return true; - - // blacklist HID boot devices (mouse and keyboard) - if (clazz == UsbConstants.USB_CLASS_HID && - subClass == UsbConstants.USB_INTERFACE_SUBCLASS_BOOT) { - return true; - } - - return false; - } - - /* Called from JNI in monitorUsbHostBus() to report new USB devices (host mode) */ - private void usbDeviceAdded(String deviceName, int vendorID, int productID, - int deviceClass, int deviceSubclass, int deviceProtocol, - /* array of quintuples containing id, class, subclass, protocol - and number of endpoints for each interface */ - int[] interfaceValues, - /* array of quadruples containing address, attributes, max packet size - and interval for each endpoint */ - int[] endpointValues) { - - if (isBlackListed(deviceName) || - isBlackListed(deviceClass, deviceSubclass, deviceProtocol)) { - return; - } - - synchronized (mLock) { - if (mDevices.get(deviceName) != null) { - Log.w(TAG, "device already on mDevices list: " + deviceName); - return; - } - - int numInterfaces = interfaceValues.length / 5; - Parcelable[] interfaces = new UsbInterface[numInterfaces]; - try { - // repackage interfaceValues as an array of UsbInterface - int intf, endp, ival = 0, eval = 0; - for (intf = 0; intf < numInterfaces; intf++) { - int interfaceId = interfaceValues[ival++]; - int interfaceClass = interfaceValues[ival++]; - int interfaceSubclass = interfaceValues[ival++]; - int interfaceProtocol = interfaceValues[ival++]; - int numEndpoints = interfaceValues[ival++]; - - Parcelable[] endpoints = new UsbEndpoint[numEndpoints]; - for (endp = 0; endp < numEndpoints; endp++) { - int address = endpointValues[eval++]; - int attributes = endpointValues[eval++]; - int maxPacketSize = endpointValues[eval++]; - int interval = endpointValues[eval++]; - endpoints[endp] = new UsbEndpoint(address, attributes, - maxPacketSize, interval); - } - - // don't allow if any interfaces are blacklisted - if (isBlackListed(interfaceClass, interfaceSubclass, interfaceProtocol)) { - return; - } - interfaces[intf] = new UsbInterface(interfaceId, interfaceClass, - interfaceSubclass, interfaceProtocol, endpoints); - } - } catch (Exception e) { - // beware of index out of bound exceptions, which might happen if - // a device does not set bNumEndpoints correctly - Log.e(TAG, "error parsing USB descriptors", e); - return; - } - - UsbDevice device = new UsbDevice(deviceName, vendorID, productID, - deviceClass, deviceSubclass, deviceProtocol, interfaces); - mDevices.put(deviceName, device); - mDeviceManager.deviceAttached(device); - } - } - - /* Called from JNI in monitorUsbHostBus to report USB device removal (host mode) */ - private void usbDeviceRemoved(String deviceName) { - synchronized (mLock) { - UsbDevice device = mDevices.remove(deviceName); - if (device != null) { - mDeviceManager.deviceDetached(device); - } - } - } - - private void initHostSupport() { - // Create a thread to call into native code to wait for USB host events. - // This thread will call us back on usbDeviceAdded and usbDeviceRemoved. - Runnable runnable = new Runnable() { - public void run() { - monitorUsbHostBus(); - } - }; - new Thread(null, runnable, "UsbService host thread").start(); } public void systemReady() { - synchronized (mLock) { - if (mHasUsbHost) { - // start monitoring for connected USB devices - initHostSupport(); - } - - update(false); - if (mCurrentAccessory != null) { - Log.d(TAG, "accessoryAttached at systemReady"); - // its still too early to handle accessories, so add a BOOT_COMPLETED receiver - // to handle this later. - mContext.registerReceiver(mBootCompletedReceiver, - new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); - } - mSystemReady = true; + if (mDeviceManager != null) { + mDeviceManager.systemReady(); + } + if (mHostManager != null) { + mHostManager.systemReady(); } - } - - /* - * Sends a message to update the USB connected and configured state (device mode). - * If delayed is true, then we add a small delay in sending the message to debounce - * the USB connection when enabling USB tethering. - */ - private final void update(boolean delayed) { - mHandler.removeMessages(MSG_UPDATE_STATE); - mHandler.sendEmptyMessageDelayed(MSG_UPDATE_STATE, delayed ? UPDATE_DELAY : 0); } /* Returns a list of all currently attached USB devices (host mdoe) */ public void getDeviceList(Bundle devices) { - synchronized (mLock) { - for (String name : mDevices.keySet()) { - devices.putParcelable(name, mDevices.get(name)); - } + if (mHostManager != null) { + mHostManager.getDeviceList(devices); } } /* Opens the specified USB device (host mode) */ public ParcelFileDescriptor openDevice(String deviceName) { - synchronized (mLock) { - if (isBlackListed(deviceName)) { - throw new SecurityException("USB device is on a restricted bus"); - } - UsbDevice device = mDevices.get(deviceName); - if (device == null) { - // if it is not in mDevices, it either does not exist or is blacklisted - throw new IllegalArgumentException( - "device " + deviceName + " does not exist or is restricted"); - } - mDeviceManager.checkPermission(device); - return nativeOpenDevice(deviceName); + if (mHostManager != null) { + return mHostManager.openDevice(deviceName); + } else { + return null; } } /* returns the currently attached USB accessory (device mode) */ public UsbAccessory getCurrentAccessory() { - return mCurrentAccessory; + if (mDeviceManager != null) { + return mDeviceManager.getCurrentAccessory(); + } else { + return null; + } } /* opens the currently attached USB accessory (device mode) */ public ParcelFileDescriptor openAccessory(UsbAccessory accessory) { - synchronized (mLock) { - if (mCurrentAccessory == null) { - throw new IllegalArgumentException("no accessory attached"); - } - if (!mCurrentAccessory.equals(accessory)) { - Log.e(TAG, accessory.toString() + " does not match current accessory " - + mCurrentAccessory); - throw new IllegalArgumentException("accessory not attached"); - } - mDeviceManager.checkPermission(mCurrentAccessory); - return nativeOpenAccessory(); + if (mDeviceManager != null) { + return openAccessory(accessory); + } else { + return null; } } public void setDevicePackage(UsbDevice device, String packageName) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - mDeviceManager.setDevicePackage(device, packageName); + mSettingsManager.setDevicePackage(device, packageName); } public void setAccessoryPackage(UsbAccessory accessory, String packageName) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - mDeviceManager.setAccessoryPackage(accessory, packageName); + mSettingsManager.setAccessoryPackage(accessory, packageName); } public boolean hasDevicePermission(UsbDevice device) { - return mDeviceManager.hasPermission(device); + return mSettingsManager.hasPermission(device); } public boolean hasAccessoryPermission(UsbAccessory accessory) { - return mDeviceManager.hasPermission(accessory); + return mSettingsManager.hasPermission(accessory); } public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) { - mDeviceManager.requestPermission(device, packageName, pi); + mSettingsManager.requestPermission(device, packageName, pi); } public void requestAccessoryPermission(UsbAccessory accessory, String packageName, PendingIntent pi) { - mDeviceManager.requestPermission(accessory, packageName, pi); + mSettingsManager.requestPermission(accessory, packageName, pi); } public void grantDevicePermission(UsbDevice device, int uid) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - mDeviceManager.grantDevicePermission(device, uid); + mSettingsManager.grantDevicePermission(device, uid); } public void grantAccessoryPermission(UsbAccessory accessory, int uid) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - mDeviceManager.grantAccessoryPermission(accessory, uid); + mSettingsManager.grantAccessoryPermission(accessory, uid); } public boolean hasDefaults(String packageName) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - return mDeviceManager.hasDefaults(packageName); + return mSettingsManager.hasDefaults(packageName); } public void clearDefaults(String packageName) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - mDeviceManager.clearDefaults(packageName); + mSettingsManager.clearDefaults(packageName); } - /* - * This handler is for deferred handling of events related to device mode and accessories. - */ - private final Handler mHandler = new Handler() { - private void addEnabledFunctionsLocked(Intent intent) { - // include state of all USB functions in our extras - for (int i = 0; i < mEnabledFunctions.size(); i++) { - intent.putExtra(mEnabledFunctions.get(i), UsbManager.USB_FUNCTION_ENABLED); - } - for (int i = 0; i < mDisabledFunctions.size(); i++) { - intent.putExtra(mDisabledFunctions.get(i), UsbManager.USB_FUNCTION_DISABLED); - } - } - - @Override - public void handleMessage(Message msg) { - synchronized (mLock) { - switch (msg.what) { - case MSG_UPDATE_STATE: - if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) { - if (mConnected == 0) { - if (UsbManager.isFunctionEnabled( - UsbManager.USB_FUNCTION_ACCESSORY)) { - // make sure accessory mode is off, and restore default functions - Log.d(TAG, "exited USB accessory mode"); - if (!UsbManager.setFunctionEnabled - (UsbManager.USB_FUNCTION_ACCESSORY, false)) { - Log.e(TAG, "could not disable accessory function"); - } - int count = mDefaultFunctions.size(); - for (int i = 0; i < count; i++) { - String function = mDefaultFunctions.get(i); - if (!UsbManager.setFunctionEnabled(function, true)) { - Log.e(TAG, "could not reenable function " + function); - } - } - - if (mCurrentAccessory != null) { - mDeviceManager.accessoryDetached(mCurrentAccessory); - mCurrentAccessory = null; - } - } - } - - final ContentResolver cr = mContext.getContentResolver(); - if (Settings.Secure.getInt(cr, - Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { - Slog.i(TAG, "Device not provisioned, skipping USB broadcast"); - return; - } - - mLastConnected = mConnected; - mLastConfiguration = mConfiguration; - - // send a sticky broadcast containing current USB state - Intent intent = new Intent(UsbManager.ACTION_USB_STATE); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra(UsbManager.USB_CONNECTED, mConnected != 0); - intent.putExtra(UsbManager.USB_CONFIGURATION, mConfiguration); - addEnabledFunctionsLocked(intent); - mContext.sendStickyBroadcast(intent); - } - break; - case MSG_FUNCTION_ENABLED: - case MSG_FUNCTION_DISABLED: - functionEnabledLocked((String)msg.obj, msg.what == MSG_FUNCTION_ENABLED); - break; - } - } - } - }; - @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) @@ -622,40 +156,14 @@ public class UsbService extends IUsbManager.Stub { return; } - synchronized (mLock) { - pw.println("USB Manager State:"); - - pw.println(" USB Device State:"); - pw.print(" Enabled Functions: "); - for (int i = 0; i < mEnabledFunctions.size(); i++) { - pw.print(mEnabledFunctions.get(i) + " "); - } - pw.println(""); - pw.print(" Disabled Functions: "); - for (int i = 0; i < mDisabledFunctions.size(); i++) { - pw.print(mDisabledFunctions.get(i) + " "); - } - pw.println(""); - pw.print(" Default Functions: "); - for (int i = 0; i < mDefaultFunctions.size(); i++) { - pw.print(mDefaultFunctions.get(i) + " "); - } - pw.println(""); - pw.println(" mConnected: " + mConnected + ", mConfiguration: " + mConfiguration); - pw.println(" mCurrentAccessory: " + mCurrentAccessory); + pw.println("USB Manager State:"); - pw.println(" USB Host State:"); - for (String name : mDevices.keySet()) { - pw.println(" " + name + ": " + mDevices.get(name)); - } + if (mDeviceManager != null) { mDeviceManager.dump(fd, pw); } + if (mHostManager != null) { + mHostManager.dump(fd, pw); + } + mSettingsManager.dump(fd, pw); } - - // host support - private native void monitorUsbHostBus(); - private native ParcelFileDescriptor nativeOpenDevice(String deviceName); - // accessory support - private native String[] nativeGetAccessoryStrings(); - private native ParcelFileDescriptor nativeOpenAccessory(); } diff --git a/services/java/com/android/server/usb/UsbDeviceSettingsManager.java b/services/java/com/android/server/usb/UsbSettingsManager.java index de0b114..9113677 100644 --- a/services/java/com/android/server/usb/UsbDeviceSettingsManager.java +++ b/services/java/com/android/server/usb/UsbSettingsManager.java @@ -59,9 +59,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -class UsbDeviceSettingsManager { +class UsbSettingsManager { - private static final String TAG = "UsbDeviceSettingsManager"; + private static final String TAG = "UsbSettingsManager"; private static final File sSettingsFile = new File("/data/system/usb_device_manager.xml"); private final Context mContext; @@ -363,7 +363,7 @@ class UsbDeviceSettingsManager { } MyPackageMonitor mPackageMonitor = new MyPackageMonitor(); - public UsbDeviceSettingsManager(Context context) { + public UsbSettingsManager(Context context) { mContext = context; mPackageManager = context.getPackageManager(); synchronized (mLock) { diff --git a/services/java/com/android/server/wm/InputFilter.java b/services/java/com/android/server/wm/InputFilter.java new file mode 100644 index 0000000..8f0001a --- /dev/null +++ b/services/java/com/android/server/wm/InputFilter.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.view.InputEvent; +import android.view.InputEventConsistencyVerifier; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.WindowManagerPolicy; + +/** + * Filters input events before they are dispatched to the system. + * <p> + * At most one input filter can be installed by calling + * {@link WindowManagerService#setInputFilter}. When an input filter is installed, the + * system's behavior changes as follows: + * <ul> + * <li>Input events are first delivered to the {@link WindowManagerPolicy} + * interception methods before queueing as usual. This critical step takes care of managing + * the power state of the device and handling wake keys.</li> + * <li>Input events are then asynchronously delivered to the input filter's + * {@link #onInputEvent(InputEvent)} method instead of being enqueued for dispatch to + * applications as usual. The input filter only receives input events that were + * generated by input device; the input filter will not receive input events that were + * injected into the system by other means, such as by instrumentation.</li> + * <li>The input filter processes and optionally transforms the stream of events. For example, + * it may transform a sequence of motion events representing an accessibility gesture into + * a different sequence of motion events, key presses or other system-level interactions. + * The input filter can send events to be dispatched by calling + * {@link #sendInputEvent(InputEvent)} and passing appropriate policy flags for the + * input event.</li> + * </ul> + * </p> + * <h3>The importance of input event consistency</h3> + * <p> + * The input filter mechanism is very low-level. At a minimum, it needs to ensure that it + * sends an internally consistent stream of input events to the dispatcher. There are + * very important invariants to be maintained. + * </p><p> + * For example, if a key down is sent, a corresponding key up should also be sent eventually. + * Likewise, for touch events, each pointer must individually go down with + * {@link MotionEvent#ACTION_DOWN} or {@link MotionEvent#ACTION_POINTER_DOWN} and then + * individually go up with {@link MotionEvent#ACTION_POINTER_UP} or {@link MotionEvent#ACTION_UP} + * and the sequence of pointer ids used must be consistent throughout the gesture. + * </p><p> + * Sometimes a filter may wish to cancel a previously dispatched key or motion. It should + * use {@link KeyEvent#FLAG_CANCELED} or {@link MotionEvent#ACTION_CANCEL} accordingly. + * </p><p> + * The input filter must take into account the fact that the input events coming from different + * devices or even different sources all consist of distinct streams of input. + * Use {@link InputEvent#getDeviceId()} and {@link InputEvent#getSource()} to identify + * the source of the event and its semantics. There are be multiple sources of keys, + * touches and other input: they must be kept separate. + * </p> + * <h3>Policy flags</h3> + * <p> + * Input events received from the dispatcher and sent to the dispatcher have policy flags + * associated with them. Policy flags control some functions of the dispatcher. + * </p><p> + * The early policy interception decides whether an input event should be delivered + * to applications or dropped. The policy indicates its decision by setting the + * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} policy flag. The input filter may + * sometimes receive events that do not have this flag set. It should take note of + * the fact that the policy intends to drop the event, clean up its state, and + * then send appropriate cancelation events to the dispatcher if needed. + * </p><p> + * For example, suppose the input filter is processing a gesture and one of the touch events + * it receives does not have the {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag set. + * The input filter should clear its internal state about the gesture and then send key or + * motion events to the dispatcher to cancel any keys or pointers that are down. + * </p><p> + * Corollary: Events that set sent to the dispatcher should usually include the + * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag. Otherwise, they will be dropped! + * </p><p> + * It may be prudent to disable automatic key repeating for synthetically generated + * keys by setting the {@link WindowManagerPolicy#FLAG_DISABLE_KEY_REPEAT} policy flag. + * </p> + */ +public abstract class InputFilter { + private static final int MSG_INSTALL = 1; + private static final int MSG_UNINSTALL = 2; + private static final int MSG_INPUT_EVENT = 3; + + private final H mH; + private Host mHost; + + // Consistency verifiers for debugging purposes. + private final InputEventConsistencyVerifier mInboundInputEventConsistencyVerifier = + InputEventConsistencyVerifier.isInstrumentationEnabled() ? + new InputEventConsistencyVerifier(this, + InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT, + "InputFilter#InboundInputEventConsistencyVerifier") : null; + private final InputEventConsistencyVerifier mOutboundInputEventConsistencyVerifier = + InputEventConsistencyVerifier.isInstrumentationEnabled() ? + new InputEventConsistencyVerifier(this, + InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT, + "InputFilter#OutboundInputEventConsistencyVerifier") : null; + + /** + * Creates the input filter. + * + * @param looper The looper to run callbacks on. + */ + public InputFilter(Looper looper) { + mH = new H(looper); + } + + /** + * Called when the input filter is installed. + * This method is guaranteed to be non-reentrant. + * + * @param host The input filter host environment. + */ + final void install(Host host) { + mH.obtainMessage(MSG_INSTALL, host).sendToTarget(); + } + + /** + * Called when the input filter is uninstalled. + * This method is guaranteed to be non-reentrant. + */ + final void uninstall() { + mH.obtainMessage(MSG_UNINSTALL).sendToTarget(); + } + + /** + * Called to enqueue the input event for filtering. + * The event will be recycled after the input filter processes it. + * This method is guaranteed to be non-reentrant. + * + * @param event The input event to enqueue. + */ + final void filterInputEvent(InputEvent event, int policyFlags) { + mH.obtainMessage(MSG_INPUT_EVENT, policyFlags, 0, event).sendToTarget(); + } + + /** + * Sends an input event to the dispatcher. + * + * @param event The input event to publish. + * @param policyFlags The input event policy flags. + */ + public void sendInputEvent(InputEvent event, int policyFlags) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + if (mHost == null) { + throw new IllegalStateException("Cannot send input event because the input filter " + + "is not installed."); + } + if (mOutboundInputEventConsistencyVerifier != null) { + mOutboundInputEventConsistencyVerifier.onInputEvent(event, 0); + } + mHost.sendInputEvent(event, policyFlags); + } + + /** + * Called when an input event has been received from the dispatcher. + * <p> + * The default implementation sends the input event back to the dispatcher, unchanged. + * </p><p> + * The event will be recycled when this method returns. If you want to keep it around, + * make a copy! + * </p> + * + * @param event The input event that was received. + * @param policyFlags The input event policy flags. + */ + public void onInputEvent(InputEvent event, int policyFlags) { + sendInputEvent(event, policyFlags); + } + + /** + * Called when the filter is installed into the dispatch pipeline. + * <p> + * This method is called before the input filter receives any input events. + * The input filter should take this opportunity to prepare itself. + * </p> + */ + public void onInstalled() { + } + + /** + * Called when the filter is uninstalled from the dispatch pipeline. + * <p> + * This method is called after the input filter receives its last input event. + * The input filter should take this opportunity to clean up. + * </p> + */ + public void onUninstalled() { + } + + private final class H extends Handler { + public H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_INSTALL: + mHost = (Host)msg.obj; + if (mInboundInputEventConsistencyVerifier != null) { + mInboundInputEventConsistencyVerifier.reset(); + } + if (mOutboundInputEventConsistencyVerifier != null) { + mOutboundInputEventConsistencyVerifier.reset(); + } + onInstalled(); + break; + + case MSG_UNINSTALL: + try { + onUninstalled(); + } finally { + mHost = null; + } + break; + + case MSG_INPUT_EVENT: { + final InputEvent event = (InputEvent)msg.obj; + try { + if (mInboundInputEventConsistencyVerifier != null) { + mInboundInputEventConsistencyVerifier.onInputEvent(event, 0); + } + onInputEvent(event, msg.arg1); + } finally { + event.recycle(); + } + break; + } + } + } + } + + interface Host { + public void sendInputEvent(InputEvent event, int policyFlags); + } +} diff --git a/services/java/com/android/server/wm/InputManager.java b/services/java/com/android/server/wm/InputManager.java index 4eda684..3095c37 100644 --- a/services/java/com/android/server/wm/InputManager.java +++ b/services/java/com/android/server/wm/InputManager.java @@ -41,6 +41,7 @@ import android.view.PointerIcon; import android.view.Surface; import android.view.ViewConfiguration; import android.view.WindowManager; +import android.view.WindowManagerPolicy; import java.io.File; import java.io.FileNotFoundException; @@ -78,8 +79,10 @@ public class InputManager { private static native void nativeRegisterInputChannel(InputChannel inputChannel, InputWindowHandle inputWindowHandle, boolean monitor); private static native void nativeUnregisterInputChannel(InputChannel inputChannel); + private static native void nativeSetInputFilterEnabled(boolean enable); private static native int nativeInjectInputEvent(InputEvent event, - int injectorPid, int injectorUid, int syncMode, int timeoutMillis); + int injectorPid, int injectorUid, int syncMode, int timeoutMillis, + int policyFlags); private static native void nativeSetInputWindows(InputWindow[] windows); private static native void nativeSetInputDispatchMode(boolean enabled, boolean frozen); private static native void nativeSetSystemUiVisibility(int visibility); @@ -118,6 +121,11 @@ public class InputManager { /** The key is down but is a virtual key press that is being emulated by the system. */ public static final int KEY_STATE_VIRTUAL = 2; + // State for the currently installed input filter. + final Object mInputFilterLock = new Object(); + InputFilter mInputFilter; + InputFilterHost mInputFilterHost; + public InputManager(Context context, WindowManagerService windowManagerService) { this.mContext = context; this.mWindowManagerService = windowManagerService; @@ -272,7 +280,42 @@ public class InputManager { nativeUnregisterInputChannel(inputChannel); } - + + /** + * Sets an input filter that will receive all input events before they are dispatched. + * The input filter may then reinterpret input events or inject new ones. + * + * To ensure consistency, the input dispatcher automatically drops all events + * in progress whenever an input filter is installed or uninstalled. After an input + * filter is uninstalled, it can no longer send input events unless it is reinstalled. + * Any events it attempts to send after it has been uninstalled will be dropped. + * + * @param filter The input filter, or null to remove the current filter. + */ + public void setInputFilter(InputFilter filter) { + synchronized (mInputFilterLock) { + final InputFilter oldFilter = mInputFilter; + if (oldFilter == filter) { + return; // nothing to do + } + + if (oldFilter != null) { + mInputFilter = null; + mInputFilterHost.disconnectLocked(); + mInputFilterHost = null; + oldFilter.uninstall(); + } + + if (filter != null) { + mInputFilter = filter; + mInputFilterHost = new InputFilterHost(); + filter.install(mInputFilterHost); + } + + nativeSetInputFilterEnabled(filter != null); + } + } + /** * Injects an input event into the event system on behalf of an application. * The synchronization mode determines whether the method blocks while waiting for @@ -308,9 +351,10 @@ public class InputManager { throw new IllegalArgumentException("timeoutMillis must be positive"); } - return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis); + return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis, + WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT); } - + /** * Gets information about the input device with the specified id. * @param id The device id. @@ -410,10 +454,31 @@ public class InputManager { } } + private final class InputFilterHost implements InputFilter.Host { + private boolean mDisconnected; + + public void disconnectLocked() { + mDisconnected = true; + } + + public void sendInputEvent(InputEvent event, int policyFlags) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + + synchronized (mInputFilterLock) { + if (!mDisconnected) { + nativeInjectInputEvent(event, 0, 0, INPUT_EVENT_INJECTION_SYNC_NONE, 0, + policyFlags | WindowManagerPolicy.FLAG_FILTERED); + } + } + } + } + /* * Callbacks from native. */ - private class Callbacks { + private final class Callbacks { static final String TAG = "InputManager-Callbacks"; private static final boolean DEBUG_VIRTUAL_KEYS = false; @@ -441,7 +506,19 @@ public class InputManager { return mWindowManagerService.mInputMonitor.notifyANR( inputApplicationHandle, inputWindowHandle); } - + + @SuppressWarnings("unused") + final boolean filterInputEvent(InputEvent event, int policyFlags) { + synchronized (mInputFilterLock) { + if (mInputFilter != null) { + mInputFilter.filterInputEvent(event, policyFlags); + return false; + } + } + event.recycle(); + return true; + } + @SuppressWarnings("unused") public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) { return mWindowManagerService.mInputMonitor.interceptKeyBeforeQueueing( diff --git a/services/java/com/android/server/wm/ViewServer.java b/services/java/com/android/server/wm/ViewServer.java index cebd5e7..70cb26a 100644 --- a/services/java/com/android/server/wm/ViewServer.java +++ b/services/java/com/android/server/wm/ViewServer.java @@ -49,7 +49,7 @@ class ViewServer implements Runnable { // Debug facility private static final String LOG_TAG = "ViewServer"; - private static final String VALUE_PROTOCOL_VERSION = "3"; + private static final String VALUE_PROTOCOL_VERSION = "4"; private static final String VALUE_SERVER_VERSION = "4"; // Protocol commands @@ -73,19 +73,6 @@ class ViewServer implements Runnable { private ExecutorService mThreadPool; /** - * Creates a new ViewServer associated with the specified window manager. - * The server uses the default port {@link #VIEW_SERVER_DEFAULT_PORT}. The server - * is not started by default. - * - * @param windowManager The window manager used to communicate with the views. - * - * @see #start() - */ - ViewServer(WindowManagerService windowManager) { - this(windowManager, VIEW_SERVER_DEFAULT_PORT); - } - - /** * Creates a new ViewServer associated with the specified window manager on the * specified local port. The server is not started by default. * @@ -177,7 +164,7 @@ class ViewServer implements Runnable { // Any uncaught exception will crash the system process try { Socket client = mServer.accept(); - if(mThreadPool != null) { + if (mThreadPool != null) { mThreadPool.submit(new ViewServerWorker(client)); } else { try { @@ -220,6 +207,7 @@ class ViewServer implements Runnable { private Socket mClient; private boolean mNeedWindowListUpdate; private boolean mNeedFocusedWindowUpdate; + public ViewServerWorker(Socket client) { mClient = client; mNeedWindowListUpdate = false; @@ -255,7 +243,7 @@ class ViewServer implements Runnable { result = mWindowManager.viewServerListWindows(mClient); } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) { result = mWindowManager.viewServerGetFocusedWindow(mClient); - } else if(COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) { + } else if (COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) { result = windowManagerAutolistLoop(); } else { result = mWindowManager.viewServerWindowCommand(mClient, @@ -263,7 +251,7 @@ class ViewServer implements Runnable { } if (!result) { - Slog.w(LOG_TAG, "An error occured with the command: " + command); + Slog.w(LOG_TAG, "An error occurred with the command: " + command); } } catch(IOException e) { Slog.w(LOG_TAG, "Connection error: ", e); @@ -321,11 +309,11 @@ class ViewServer implements Runnable { needFocusedWindowUpdate = true; } } - if(needWindowListUpdate) { + if (needWindowListUpdate) { out.write("LIST UPDATE\n"); out.flush(); } - if(needFocusedWindowUpdate) { + if (needFocusedWindowUpdate) { out.write("FOCUS UPDATE\n"); out.flush(); } @@ -337,6 +325,7 @@ class ViewServer implements Runnable { try { out.close(); } catch (IOException e) { + // Ignore } } mWindowManager.removeWindowChangeListener(this); diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 4ff6b06..d95d4c5 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -4631,6 +4631,10 @@ public class WindowManagerService extends IWindowManager.Stub return mInputManager.monitorInput(inputChannelName); } + public void setInputFilter(InputFilter filter) { + mInputManager.setInputFilter(filter); + } + public InputDevice getInputDevice(int deviceId) { return mInputManager.getInputDevice(deviceId); } @@ -8308,6 +8312,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean state = mHoldingScreenWakeLock.isHeld(); if (holding != state) { if (holding) { + mPolicy.screenOnStartedLw(); mHoldingScreenWakeLock.acquire(); } else { mPolicy.screenOnStoppedLw(); |
