diff --git a/magnifier.patch b/magnifier.patch index 6cb0f631..6fc9ddab 100644 --- a/magnifier.patch +++ b/magnifier.patch @@ -2110,7 +2110,7 @@ Change-Id: Ie292174b8790c84ce3701a1e6aadec2355e583d2 if-eqz v1, :cond_0 -+ invoke-direct {p0}, Landroid/widget/Editor;->longClickFeedback()V ++ invoke-direct {p0}, Landroid/widget/TextView;->longClickFeedback()V + const/4 v0, 0x1 diff --git a/src/frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java b/src/frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java new file mode 100644 index 00000000..88a025e3 --- /dev/null +++ b/src/frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java @@ -0,0 +1,1438 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.commands.pm; + +import com.android.internal.content.PackageHelper; + +import android.app.ActivityManagerNative; +import android.content.ComponentName; +import android.content.pm.ApplicationInfo; +import android.content.pm.ContainerEncryptionParams; +import android.content.pm.FeatureInfo; +import android.content.pm.IPackageDataObserver; +import android.content.pm.IPackageDeleteObserver; +import android.content.pm.IPackageInstallObserver; +import android.content.pm.IPackageManager; +import android.content.pm.InstrumentationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.content.pm.UserInfo; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.net.Uri; +import android.os.Binder; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.security.InvalidAlgorithmParameterException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.WeakHashMap; + +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public final class Pm { + IPackageManager mPm; + + private WeakHashMap mResourceCache + = new WeakHashMap(); + + private String[] mArgs; + private int mNextArg; + private String mCurArgData; + + private static final String PM_NOT_RUNNING_ERR = + "Error: Could not access the Package Manager. Is the system running?"; + private static final int ROOT_UID = 0; + + public static void main(String[] args) { + new Pm().run(args); + } + + public void run(String[] args) { + boolean validCommand = false; + if (args.length < 1) { + showUsage(); + return; + } + + mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); + if (mPm == null) { + System.err.println(PM_NOT_RUNNING_ERR); + return; + } + + mArgs = args; + String op = args[0]; + mNextArg = 1; + + if ("list".equals(op)) { + runList(); + return; + } + + if ("path".equals(op)) { + runPath(); + return; + } + + if ("install".equals(op)) { + runInstall(); + return; + } + + if ("uninstall".equals(op)) { + runUninstall(); + return; + } + + if ("clear".equals(op)) { + runClear(); + return; + } + + if ("enable".equals(op)) { + runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED); + return; + } + + if ("disable".equals(op)) { + runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED); + return; + } + + if ("disable-user".equals(op)) { + runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER); + return; + } + + if ("grant".equals(op)) { + runGrantRevokePermission(true); + return; + } + + if ("revoke".equals(op)) { + runGrantRevokePermission(false); + return; + } + + if ("set-permission-enforced".equals(op)) { + runSetPermissionEnforced(); + return; + } + + if ("set-install-location".equals(op)) { + runSetInstallLocation(); + return; + } + + if ("get-install-location".equals(op)) { + runGetInstallLocation(); + return; + } + + if ("create-user".equals(op)) { + runCreateUser(); + return; + } + + if ("remove-user".equals(op)) { + runRemoveUser(); + return; + } + + if ("list-users".equals(op)) { + runListUsers(); + return; + } + + try { + if (args.length == 1) { + if (args[0].equalsIgnoreCase("-l")) { + validCommand = true; + runListPackages(false); + } else if (args[0].equalsIgnoreCase("-lf")){ + validCommand = true; + runListPackages(true); + } + } else if (args.length == 2) { + if (args[0].equalsIgnoreCase("-p")) { + validCommand = true; + displayPackageFilePath(args[1]); + } + } + } finally { + if (validCommand == false) { + if (op != null) { + System.err.println("Error: unknown command '" + op + "'"); + } + showUsage(); + } + } + } + + /** + * Execute the list sub-command. + * + * pm list [package | packages] + * pm list permission-groups + * pm list permissions + * pm list features + * pm list libraries + * pm list instrumentation + */ + private void runList() { + String type = nextArg(); + if (type == null) { + System.err.println("Error: didn't specify type of data to list"); + showUsage(); + return; + } + if ("package".equals(type) || "packages".equals(type)) { + runListPackages(false); + } else if ("permission-groups".equals(type)) { + runListPermissionGroups(); + } else if ("permissions".equals(type)) { + runListPermissions(); + } else if ("features".equals(type)) { + runListFeatures(); + } else if ("libraries".equals(type)) { + runListLibraries(); + } else if ("instrumentation".equals(type)) { + runListInstrumentation(); + } else if ("users".equals(type)) { + runListUsers(); + } else { + System.err.println("Error: unknown list type '" + type + "'"); + showUsage(); + } + } + + /** + * Lists all the installed packages. + */ + private void runListPackages(boolean showApplicationPackage) { + int getFlags = 0; + boolean listDisabled = false, listEnabled = false; + boolean listSystem = false, listThirdParty = false; + boolean listInstaller = false; + try { + String opt; + while ((opt=nextOption()) != null) { + if (opt.equals("-l")) { + // old compat + } else if (opt.equals("-lf")) { + showApplicationPackage = true; + } else if (opt.equals("-f")) { + showApplicationPackage = true; + } else if (opt.equals("-d")) { + listDisabled = true; + } else if (opt.equals("-e")) { + listEnabled = true; + } else if (opt.equals("-s")) { + listSystem = true; + } else if (opt.equals("-3")) { + listThirdParty = true; + } else if (opt.equals("-i")) { + listInstaller = true; + } else if (opt.equals("-u")) { + getFlags |= PackageManager.GET_UNINSTALLED_PACKAGES; + } else { + System.err.println("Error: Unknown option: " + opt); + showUsage(); + return; + } + } + } catch (RuntimeException ex) { + System.err.println("Error: " + ex.toString()); + showUsage(); + return; + } + + String filter = nextArg(); + + try { + final List packages = getInstalledPackages(mPm, getFlags); + + int count = packages.size(); + for (int p = 0 ; p < count ; p++) { + PackageInfo info = packages.get(p); + if (filter != null && !info.packageName.contains(filter)) { + continue; + } + final boolean isSystem = + (info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0; + if ((!listDisabled || !info.applicationInfo.enabled) && + (!listEnabled || info.applicationInfo.enabled) && + (!listSystem || isSystem) && + (!listThirdParty || !isSystem)) { + System.out.print("package:"); + if (showApplicationPackage) { + System.out.print(info.applicationInfo.sourceDir); + System.out.print("="); + } + System.out.print(info.packageName); + if (listInstaller) { + System.out.print(" installer="); + System.out.print(mPm.getInstallerPackageName(info.packageName)); + } + System.out.println(); + } + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + + @SuppressWarnings("unchecked") + private List getInstalledPackages(IPackageManager pm, int flags) + throws RemoteException { + final List packageInfos = new ArrayList(); + PackageInfo lastItem = null; + ParceledListSlice slice; + + do { + final String lastKey = lastItem != null ? lastItem.packageName : null; + slice = pm.getInstalledPackages(flags, lastKey); + lastItem = slice.populateList(packageInfos, PackageInfo.CREATOR); + } while (!slice.isLastSlice()); + + return packageInfos; + } + + /** + * Lists all of the features supported by the current device. + * + * pm list features + */ + private void runListFeatures() { + try { + List list = new ArrayList(); + FeatureInfo[] rawList = mPm.getSystemAvailableFeatures(); + for (int i=0; i() { + public int compare(FeatureInfo o1, FeatureInfo o2) { + if (o1.name == o2.name) return 0; + if (o1.name == null) return -1; + if (o2.name == null) return 1; + return o1.name.compareTo(o2.name); + } + }); + + int count = (list != null) ? list.size() : 0; + for (int p = 0; p < count; p++) { + FeatureInfo fi = list.get(p); + System.out.print("feature:"); + if (fi.name != null) System.out.println(fi.name); + else System.out.println("reqGlEsVersion=0x" + + Integer.toHexString(fi.reqGlEsVersion)); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + + /** + * Lists all of the libraries supported by the current device. + * + * pm list libraries + */ + private void runListLibraries() { + try { + List list = new ArrayList(); + String[] rawList = mPm.getSystemSharedLibraryNames(); + for (int i=0; i() { + public int compare(String o1, String o2) { + if (o1 == o2) return 0; + if (o1 == null) return -1; + if (o2 == null) return 1; + return o1.compareTo(o2); + } + }); + + int count = (list != null) ? list.size() : 0; + for (int p = 0; p < count; p++) { + String lib = list.get(p); + System.out.print("library:"); + System.out.println(lib); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + + /** + * Lists all of the installed instrumentation, or all for a given package + * + * pm list instrumentation [package] [-f] + */ + private void runListInstrumentation() { + int flags = 0; // flags != 0 is only used to request meta-data + boolean showPackage = false; + String targetPackage = null; + + try { + String opt; + while ((opt=nextArg()) != null) { + if (opt.equals("-f")) { + showPackage = true; + } else if (opt.charAt(0) != '-') { + targetPackage = opt; + } else { + System.err.println("Error: Unknown option: " + opt); + showUsage(); + return; + } + } + } catch (RuntimeException ex) { + System.err.println("Error: " + ex.toString()); + showUsage(); + return; + } + + try { + List list = mPm.queryInstrumentation(targetPackage, flags); + + // Sort by target package + Collections.sort(list, new Comparator() { + public int compare(InstrumentationInfo o1, InstrumentationInfo o2) { + return o1.targetPackage.compareTo(o2.targetPackage); + } + }); + + int count = (list != null) ? list.size() : 0; + for (int p = 0; p < count; p++) { + InstrumentationInfo ii = list.get(p); + System.out.print("instrumentation:"); + if (showPackage) { + System.out.print(ii.sourceDir); + System.out.print("="); + } + ComponentName cn = new ComponentName(ii.packageName, ii.name); + System.out.print(cn.flattenToShortString()); + System.out.print(" (target="); + System.out.print(ii.targetPackage); + System.out.println(")"); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + + /** + * Lists all the known permission groups. + */ + private void runListPermissionGroups() { + try { + List pgs = mPm.getAllPermissionGroups(0); + + int count = pgs.size(); + for (int p = 0 ; p < count ; p++) { + PermissionGroupInfo pgi = pgs.get(p); + System.out.print("permission group:"); + System.out.println(pgi.name); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + + private String loadText(PackageItemInfo pii, int res, CharSequence nonLocalized) { + if (nonLocalized != null) { + return nonLocalized.toString(); + } + if (res != 0) { + Resources r = getResources(pii); + if (r != null) { + return r.getString(res); + } + } + return null; + } + + /** + * Lists all the permissions in a group. + */ + private void runListPermissions() { + try { + boolean labels = false; + boolean groups = false; + boolean userOnly = false; + boolean summary = false; + boolean dangerousOnly = false; + String opt; + while ((opt=nextOption()) != null) { + if (opt.equals("-f")) { + labels = true; + } else if (opt.equals("-g")) { + groups = true; + } else if (opt.equals("-s")) { + groups = true; + labels = true; + summary = true; + } else if (opt.equals("-u")) { + userOnly = true; + } else if (opt.equals("-d")) { + dangerousOnly = true; + } else { + System.err.println("Error: Unknown option: " + opt); + showUsage(); + return; + } + } + + String grp = nextOption(); + ArrayList groupList = new ArrayList(); + if (groups) { + List infos = + mPm.getAllPermissionGroups(0); + for (int i=0; i groupList, + boolean groups, boolean labels, boolean summary, + int startProtectionLevel, int endProtectionLevel) + throws RemoteException { + for (int i=0; i 0) System.out.println(""); + if (groupName != null) { + PermissionGroupInfo pgi = mPm.getPermissionGroupInfo( + groupName, 0); + if (summary) { + Resources res = getResources(pgi); + if (res != null) { + System.out.print(loadText(pgi, pgi.labelRes, + pgi.nonLocalizedLabel) + ": "); + } else { + System.out.print(pgi.name + ": "); + + } + } else { + System.out.println((labels ? "+ " : "") + + "group:" + pgi.name); + if (labels) { + System.out.println(" package:" + pgi.packageName); + Resources res = getResources(pgi); + if (res != null) { + System.out.println(" label:" + + loadText(pgi, pgi.labelRes, + pgi.nonLocalizedLabel)); + System.out.println(" description:" + + loadText(pgi, pgi.descriptionRes, + pgi.nonLocalizedDescription)); + } + } + } + } else { + System.out.println(((labels && !summary) + ? "+ " : "") + "ungrouped:"); + } + prefix = " "; + } + List ps = mPm.queryPermissionsByGroup( + groupList.get(i), 0); + int count = ps.size(); + boolean first = true; + for (int p = 0 ; p < count ; p++) { + PermissionInfo pi = ps.get(p); + if (groups && groupName == null && pi.group != null) { + continue; + } + final int base = pi.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; + if (base < startProtectionLevel + || base > endProtectionLevel) { + continue; + } + if (summary) { + if (first) { + first = false; + } else { + System.out.print(", "); + } + Resources res = getResources(pi); + if (res != null) { + System.out.print(loadText(pi, pi.labelRes, + pi.nonLocalizedLabel)); + } else { + System.out.print(pi.name); + } + } else { + System.out.println(prefix + (labels ? "+ " : "") + + "permission:" + pi.name); + if (labels) { + System.out.println(prefix + " package:" + pi.packageName); + Resources res = getResources(pi); + if (res != null) { + System.out.println(prefix + " label:" + + loadText(pi, pi.labelRes, + pi.nonLocalizedLabel)); + System.out.println(prefix + " description:" + + loadText(pi, pi.descriptionRes, + pi.nonLocalizedDescription)); + } + System.out.println(prefix + " protectionLevel:" + + PermissionInfo.protectionToString(pi.protectionLevel)); + } + } + } + + if (summary) { + System.out.println(""); + } + } + } + + private void runPath() { + String pkg = nextArg(); + if (pkg == null) { + System.err.println("Error: no package specified"); + showUsage(); + return; + } + displayPackageFilePath(pkg); + } + + class PackageInstallObserver extends IPackageInstallObserver.Stub { + boolean finished; + int result; + + public void packageInstalled(String name, int status) { + synchronized( this) { + finished = true; + result = status; + notifyAll(); + } + } + } + + /** + * Converts a failure code into a string by using reflection to find a matching constant + * in PackageManager. + */ + private String installFailureToString(int result) { + Field[] fields = PackageManager.class.getFields(); + for (Field f: fields) { + if (f.getType() == int.class) { + int modifiers = f.getModifiers(); + // only look at public final static fields. + if (((modifiers & Modifier.FINAL) != 0) && + ((modifiers & Modifier.PUBLIC) != 0) && + ((modifiers & Modifier.STATIC) != 0)) { + String fieldName = f.getName(); + if (fieldName.startsWith("INSTALL_FAILED_") || + fieldName.startsWith("INSTALL_PARSE_FAILED_")) { + // get the int value and compare it to result. + try { + if (result == f.getInt(null)) { + return fieldName; + } + } catch (IllegalAccessException e) { + // this shouldn't happen since we only look for public static fields. + } + } + } + } + } + + // couldn't find a matching constant? return the value + return Integer.toString(result); + } + + private void runSetInstallLocation() { + int loc; + + String arg = nextArg(); + if (arg == null) { + System.err.println("Error: no install location specified."); + showUsage(); + return; + } + try { + loc = Integer.parseInt(arg); + } catch (NumberFormatException e) { + System.err.println("Error: install location has to be a number."); + showUsage(); + return; + } + try { + if (!mPm.setInstallLocation(loc)) { + System.err.println("Error: install location has to be a number."); + showUsage(); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + + private void runGetInstallLocation() { + try { + int loc = mPm.getInstallLocation(); + String locStr = "invalid"; + if (loc == PackageHelper.APP_INSTALL_AUTO) { + locStr = "auto"; + } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) { + locStr = "internal"; + } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) { + locStr = "external"; + } + System.out.println(loc + "[" + locStr + "]"); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + + private void runInstall() { + int installFlags = 0; + String installerPackageName = null; + + String opt; + + String algo = null; + byte[] iv = null; + byte[] key = null; + + String macAlgo = null; + byte[] macKey = null; + byte[] tag = null; + + while ((opt=nextOption()) != null) { + if (opt.equals("-l")) { + installFlags |= PackageManager.INSTALL_FORWARD_LOCK; + } else if (opt.equals("-r")) { + installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; + } else if (opt.equals("-i")) { + installerPackageName = nextOptionData(); + if (installerPackageName == null) { + System.err.println("Error: no value specified for -i"); + showUsage(); + return; + } + } else if (opt.equals("-t")) { + installFlags |= PackageManager.INSTALL_ALLOW_TEST; + } else if (opt.equals("-s")) { + // Override if -s option is specified. + installFlags |= PackageManager.INSTALL_EXTERNAL; + } else if (opt.equals("-f")) { + // Override if -s option is specified. + installFlags |= PackageManager.INSTALL_INTERNAL; + } else if (opt.equals("--algo")) { + algo = nextOptionData(); + if (algo == null) { + System.err.println("Error: must supply argument for --algo"); + showUsage(); + return; + } + } else if (opt.equals("--iv")) { + iv = hexToBytes(nextOptionData()); + if (iv == null) { + System.err.println("Error: must supply argument for --iv"); + showUsage(); + return; + } + } else if (opt.equals("--key")) { + key = hexToBytes(nextOptionData()); + if (key == null) { + System.err.println("Error: must supply argument for --key"); + showUsage(); + return; + } + } else if (opt.equals("--macalgo")) { + macAlgo = nextOptionData(); + if (macAlgo == null) { + System.err.println("Error: must supply argument for --macalgo"); + showUsage(); + return; + } + } else if (opt.equals("--mackey")) { + macKey = hexToBytes(nextOptionData()); + if (macKey == null) { + System.err.println("Error: must supply argument for --mackey"); + showUsage(); + return; + } + } else if (opt.equals("--tag")) { + tag = hexToBytes(nextOptionData()); + if (tag == null) { + System.err.println("Error: must supply argument for --tag"); + showUsage(); + return; + } + } else { + System.err.println("Error: Unknown option: " + opt); + showUsage(); + return; + } + } + + final ContainerEncryptionParams encryptionParams; + if (algo != null || iv != null || key != null || macAlgo != null || macKey != null + || tag != null) { + if (algo == null || iv == null || key == null) { + System.err.println("Error: all of --algo, --iv, and --key must be specified"); + showUsage(); + return; + } + + if (macAlgo != null || macKey != null || tag != null) { + if (macAlgo == null || macKey == null || tag == null) { + System.err.println("Error: all of --macalgo, --mackey, and --tag must " + + "be specified"); + showUsage(); + return; + } + } + + try { + final SecretKey encKey = new SecretKeySpec(key, "RAW"); + + final SecretKey macSecretKey; + if (macKey == null || macKey.length == 0) { + macSecretKey = null; + } else { + macSecretKey = new SecretKeySpec(macKey, "RAW"); + } + + encryptionParams = new ContainerEncryptionParams(algo, new IvParameterSpec(iv), + encKey, macAlgo, null, macSecretKey, tag, -1, -1, -1); + } catch (InvalidAlgorithmParameterException e) { + e.printStackTrace(); + return; + } + } else { + encryptionParams = null; + } + + final Uri apkURI; + final Uri verificationURI; + + // Populate apkURI, must be present + final String apkFilePath = nextArg(); + System.err.println("\tpkg: " + apkFilePath); + if (apkFilePath != null) { + apkURI = Uri.fromFile(new File(apkFilePath)); + } else { + System.err.println("Error: no package specified"); + showUsage(); + return; + } + + // Populate verificationURI, optionally present + final String verificationFilePath = nextArg(); + if (verificationFilePath != null) { + System.err.println("\tver: " + verificationFilePath); + verificationURI = Uri.fromFile(new File(verificationFilePath)); + } else { + verificationURI = null; + } + + PackageInstallObserver obs = new PackageInstallObserver(); + try { + mPm.installPackageWithVerification(apkURI, obs, installFlags, installerPackageName, + verificationURI, null, encryptionParams); + + synchronized (obs) { + while (!obs.finished) { + try { + obs.wait(); + } catch (InterruptedException e) { + } + } + if (obs.result == PackageManager.INSTALL_SUCCEEDED) { + System.out.println("Success"); + } else { + System.err.println("Failure [" + + installFailureToString(obs.result) + + "]"); + } + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + + /** + * Convert a string containing hex-encoded bytes to a byte array. + * + * @param input String containing hex-encoded bytes + * @return input as an array of bytes + */ + private byte[] hexToBytes(String input) { + if (input == null) { + return null; + } + + final int inputLength = input.length(); + if ((inputLength % 2) != 0) { + System.err.print("Invalid length; must be multiple of 2"); + return null; + } + + final int byteLength = inputLength / 2; + final byte[] output = new byte[byteLength]; + + int inputIndex = 0; + int byteIndex = 0; + while (inputIndex < inputLength) { + output[byteIndex++] = (byte) Integer.parseInt( + input.substring(inputIndex, inputIndex + 2), 16); + inputIndex += 2; + } + + return output; + } + + public void runCreateUser() { + // Need to be run as root + if (Process.myUid() != ROOT_UID) { + System.err.println("Error: create-user must be run as root"); + return; + } + String name; + String arg = nextArg(); + if (arg == null) { + System.err.println("Error: no user name specified."); + showUsage(); + return; + } + name = arg; + try { + if (mPm.createUser(name, 0) == null) { + System.err.println("Error: couldn't create User."); + showUsage(); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + + } + + public void runRemoveUser() { + // Need to be run as root + if (Process.myUid() != ROOT_UID) { + System.err.println("Error: remove-user must be run as root"); + return; + } + int userId; + String arg = nextArg(); + if (arg == null) { + System.err.println("Error: no user id specified."); + showUsage(); + return; + } + try { + userId = Integer.parseInt(arg); + } catch (NumberFormatException e) { + System.err.println("Error: user id has to be a number."); + showUsage(); + return; + } + try { + if (!mPm.removeUser(userId)) { + System.err.println("Error: couldn't remove user."); + showUsage(); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + + public void runListUsers() { + // Need to be run as root + if (Process.myUid() != ROOT_UID) { + System.err.println("Error: list-users must be run as root"); + return; + } + try { + List users = mPm.getUsers(); + if (users == null) { + System.err.println("Error: couldn't get users"); + } else { + System.out.println("Users:"); + for (int i = 0; i < users.size(); i++) { + System.out.println("\t" + users.get(i).toString()); + } + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + class PackageDeleteObserver extends IPackageDeleteObserver.Stub { + boolean finished; + boolean result; + + public void packageDeleted(String packageName, int returnCode) { + synchronized (this) { + finished = true; + result = returnCode == PackageManager.DELETE_SUCCEEDED; + notifyAll(); + } + } + } + + private void runUninstall() { + int unInstallFlags = 0; + + String opt = nextOption(); + if (opt != null && opt.equals("-k")) { + unInstallFlags = PackageManager.DONT_DELETE_DATA; + } + + String pkg = nextArg(); + if (pkg == null) { + System.err.println("Error: no package specified"); + showUsage(); + return; + } + boolean result = deletePackage(pkg, unInstallFlags); + if (result) { + System.out.println("Success"); + } else { + System.out.println("Failure"); + } + } + + private boolean deletePackage(String pkg, int unInstallFlags) { + PackageDeleteObserver obs = new PackageDeleteObserver(); + try { + mPm.deletePackage(pkg, obs, unInstallFlags); + + synchronized (obs) { + while (!obs.finished) { + try { + obs.wait(); + } catch (InterruptedException e) { + } + } + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + return obs.result; + } + + class ClearDataObserver extends IPackageDataObserver.Stub { + boolean finished; + boolean result; + + @Override + public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException { + synchronized (this) { + finished = true; + result = succeeded; + notifyAll(); + } + } + + } + + private void runClear() { + String pkg = nextArg(); + if (pkg == null) { + System.err.println("Error: no package specified"); + showUsage(); + return; + } + + ClearDataObserver obs = new ClearDataObserver(); + try { + if (!ActivityManagerNative.getDefault().clearApplicationUserData(pkg, obs, + Binder.getOrigCallingUser())) { + System.err.println("Failed"); + } + + synchronized (obs) { + while (!obs.finished) { + try { + obs.wait(); + } catch (InterruptedException e) { + } + } + } + + if (obs.result) { + System.err.println("Success"); + } else { + System.err.println("Failed"); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + + private static String enabledSettingToString(int state) { + switch (state) { + case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT: + return "default"; + case PackageManager.COMPONENT_ENABLED_STATE_ENABLED: + return "enabled"; + case PackageManager.COMPONENT_ENABLED_STATE_DISABLED: + return "disabled"; + case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER: + return "disabled-user"; + } + return "unknown"; + } + + private boolean isNumber(String s) { + try { + Integer.parseInt(s); + } catch (NumberFormatException nfe) { + return false; + } + return true; + } + + private void runSetEnabledSetting(int state) { + int userId = 0; + String option = nextOption(); + if (option != null && option.equals("--user")) { + String optionData = nextOptionData(); + if (optionData == null || !isNumber(optionData)) { + System.err.println("Error: no USER_ID specified"); + showUsage(); + return; + } else { + userId = Integer.parseInt(optionData); + } + } + + String pkg = nextArg(); + if (pkg == null) { + System.err.println("Error: no package or component specified"); + showUsage(); + return; + } + ComponentName cn = ComponentName.unflattenFromString(pkg); + if (cn == null) { + try { + mPm.setApplicationEnabledSetting(pkg, state, 0, userId); + System.err.println("Package " + pkg + " new state: " + + enabledSettingToString( + mPm.getApplicationEnabledSetting(pkg, userId))); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } else { + try { + mPm.setComponentEnabledSetting(cn, state, 0, userId); + System.err.println("Component " + cn.toShortString() + " new state: " + + enabledSettingToString( + mPm.getComponentEnabledSetting(cn, userId))); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + } + + private void runGrantRevokePermission(boolean grant) { + String pkg = nextArg(); + if (pkg == null) { + System.err.println("Error: no package specified"); + showUsage(); + return; + } + String perm = nextArg(); + if (perm == null) { + System.err.println("Error: no permission specified"); + showUsage(); + return; + } + try { + if (grant) { + mPm.grantPermission(pkg, perm); + } else { + mPm.revokePermission(pkg, perm); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } catch (IllegalArgumentException e) { + System.err.println("Bad argument: " + e.toString()); + showUsage(); + } catch (SecurityException e) { + System.err.println("Operation not allowed: " + e.toString()); + } + } + + private void runSetPermissionEnforced() { + final String permission = nextArg(); + if (permission == null) { + System.err.println("Error: no permission specified"); + showUsage(); + return; + } + final String enforcedRaw = nextArg(); + if (enforcedRaw == null) { + System.err.println("Error: no enforcement specified"); + showUsage(); + return; + } + final boolean enforced = Boolean.parseBoolean(enforcedRaw); + try { + mPm.setPermissionEnforced(permission, enforced); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } catch (IllegalArgumentException e) { + System.err.println("Bad argument: " + e.toString()); + showUsage(); + } catch (SecurityException e) { + System.err.println("Operation not allowed: " + e.toString()); + } + } + + /** + * Displays the package file for a package. + * @param pckg + */ + private void displayPackageFilePath(String pckg) { + try { + PackageInfo info = mPm.getPackageInfo(pckg, 0, 0); + if (info != null && info.applicationInfo != null) { + System.out.print("package:"); + System.out.println(info.applicationInfo.sourceDir); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + + private Resources getResources(PackageItemInfo pii) { + Resources res = mResourceCache.get(pii.packageName); + if (res != null) return res; + + try { + ApplicationInfo ai = mPm.getApplicationInfo(pii.packageName, 0, 0); + AssetManager am = new AssetManager(); + am.addAssetPath(ai.publicSourceDir); + res = new Resources(am, null, null); + mResourceCache.put(pii.packageName, res); + return res; + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + return null; + } + } + + private String nextOption() { + if (mNextArg >= mArgs.length) { + return null; + } + String arg = mArgs[mNextArg]; + if (!arg.startsWith("-")) { + return null; + } + mNextArg++; + if (arg.equals("--")) { + return null; + } + if (arg.length() > 1 && arg.charAt(1) != '-') { + if (arg.length() > 2) { + mCurArgData = arg.substring(2); + return arg.substring(0, 2); + } else { + mCurArgData = null; + return arg; + } + } + mCurArgData = null; + return arg; + } + + private String nextOptionData() { + if (mCurArgData != null) { + return mCurArgData; + } + if (mNextArg >= mArgs.length) { + return null; + } + String data = mArgs[mNextArg]; + mNextArg++; + return data; + } + + private String nextArg() { + if (mNextArg >= mArgs.length) { + return null; + } + String arg = mArgs[mNextArg]; + mNextArg++; + return arg; + } + + private static void showUsage() { + System.err.println("usage: pm list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [FILTER]"); + System.err.println(" pm list permission-groups"); + System.err.println(" pm list permissions [-g] [-f] [-d] [-u] [GROUP]"); + System.err.println(" pm list instrumentation [-f] [TARGET-PACKAGE]"); + System.err.println(" pm list features"); + System.err.println(" pm list libraries"); + System.err.println(" pm path PACKAGE"); + System.err.println(" pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f]"); + System.err.println(" [--algo --key --iv ] PATH"); + System.err.println(" pm uninstall [-k] PACKAGE"); + System.err.println(" pm clear PACKAGE"); + System.err.println(" pm enable PACKAGE_OR_COMPONENT"); + System.err.println(" pm disable PACKAGE_OR_COMPONENT"); + System.err.println(" pm disable-user PACKAGE_OR_COMPONENT"); + System.err.println(" pm grant PACKAGE PERMISSION"); + System.err.println(" pm revoke PACKAGE PERMISSION"); + System.err.println(" pm set-install-location [0/auto] [1/internal] [2/external]"); + System.err.println(" pm get-install-location"); + System.err.println(" pm set-permission-enforced PERMISSION [true|false]"); + System.err.println(""); + System.err.println("pm list packages: prints all packages, optionally only"); + System.err.println(" those whose package name contains the text in FILTER. Options:"); + System.err.println(" -f: see their associated file."); + System.err.println(" -d: filter to only show disbled packages."); + System.err.println(" -e: filter to only show enabled packages."); + System.err.println(" -s: filter to only show system packages."); + System.err.println(" -3: filter to only show third party packages."); + System.err.println(" -i: see the installer for the packages."); + System.err.println(" -u: also include uninstalled packages."); + System.err.println(""); + System.err.println("pm list permission-groups: prints all known permission groups."); + System.err.println(""); + System.err.println("pm list permissions: prints all known permissions, optionally only"); + System.err.println(" those in GROUP. Options:"); + System.err.println(" -g: organize by group."); + System.err.println(" -f: print all information."); + System.err.println(" -s: short summary."); + System.err.println(" -d: only list dangerous permissions."); + System.err.println(" -u: list only the permissions users will see."); + System.err.println(""); + System.err.println("pm list instrumentation: use to list all test packages; optionally"); + System.err.println(" supply to list the test packages for a particular"); + System.err.println(" application. Options:"); + System.err.println(" -f: list the .apk file for the test package."); + System.err.println(""); + System.err.println("pm list features: prints all features of the system."); + System.err.println(""); + System.err.println("pm path: print the path to the .apk of the given PACKAGE."); + System.err.println(""); + System.err.println("pm install: installs a package to the system. Options:"); + System.err.println(" -l: install the package with FORWARD_LOCK."); + System.err.println(" -r: reinstall an exisiting app, keeping its data."); + System.err.println(" -t: allow test .apks to be installed."); + System.err.println(" -i: specify the installer package name."); + System.err.println(" -s: install package on sdcard."); + System.err.println(" -f: install package on internal flash."); + System.err.println(""); + System.err.println("pm uninstall: removes a package from the system. Options:"); + System.err.println(" -k: keep the data and cache directories around after package removal."); + System.err.println(""); + System.err.println("pm clear: deletes all data associated with a package."); + System.err.println(""); + System.err.println("pm enable, disable, disable-user: these commands change the enabled state"); + System.err.println(" of a given package or component (written as \"package/class\")."); + System.err.println(""); + System.err.println("pm grant, revoke: these commands either grant or revoke permissions"); + System.err.println(" to applications. Only optional permissions the application has"); + System.err.println(" declared can be granted or revoked."); + System.err.println(""); + System.err.println("pm get-install-location: returns the current install location."); + System.err.println(" 0 [auto]: Let system decide the best location"); + System.err.println(" 1 [internal]: Install on internal device storage"); + System.err.println(" 2 [external]: Install on external media"); + System.err.println(""); + System.err.println("pm set-install-location: changes the default install location."); + System.err.println(" NOTE: this is only intended for debugging; using this can cause"); + System.err.println(" applications to break and other undersireable behavior."); + System.err.println(" 0 [auto]: Let system decide the best location"); + System.err.println(" 1 [internal]: Install on internal device storage"); + System.err.println(" 2 [external]: Install on external media"); + } +} diff --git a/src/frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java b/src/frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java new file mode 100644 index 00000000..b6001ebe --- /dev/null +++ b/src/frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java @@ -0,0 +1,614 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accessibilityservice; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityInteractionClient; +import android.view.accessibility.AccessibilityNodeInfo; + +import com.android.internal.os.HandlerCaller; + +/** + * An accessibility service runs in the background and receives callbacks by the system + * when {@link AccessibilityEvent}s are fired. Such events denote some state transition + * in the user interface, for example, the focus has changed, a button has been clicked, + * etc. Such a service can optionally request the capability for querying the content + * of the active window. Development of an accessibility service requires extending this + * class and implementing its abstract methods. + * + *
+ *

Developer Guides

+ *

For more information about creating AccessibilityServices, read the + * Accessibility + * developer guide.

+ *
+ * + *

Lifecycle

+ *

+ * The lifecycle of an accessibility service is managed exclusively by the system and + * follows the established service life cycle. Additionally, starting or stopping an + * accessibility service is triggered exclusively by an explicit user action through + * enabling or disabling it in the device settings. After the system binds to a service it + * calls {@link AccessibilityService#onServiceConnected()}. This method can be + * overriden by clients that want to perform post binding setup. + *

+ *

Declaration

+ *

+ * An accessibility is declared as any other service in an AndroidManifest.xml but it + * must also specify that it handles the "android.accessibilityservice.AccessibilityService" + * {@link android.content.Intent}. Failure to declare this intent will cause the system to + * ignore the accessibility service. Additionally an accessibility service must request the + * {@link android.Manifest.permission#BIND_ACCESSIBILITY_SERVICE} permission to ensure + * that only the system + * can bind to it. Failure to declare this intent will cause the system to ignore the + * accessibility service. Following is an example declaration: + *

+ *
 <service android:name=".MyAccessibilityService"
+ *         android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE>
+ *     <intent-filter>
+ *         <action android:name="android.accessibilityservice.AccessibilityService" />
+ *     </intent-filter>
+ *     . . .
+ * </service>
+ *

Configuration

+ *

+ * An accessibility service can be configured to receive specific types of accessibility events, + * listen only to specific packages, get events from each type only once in a given time frame, + * retrieve window content, specify a settings activity, etc. + *

+ *

+ * There are two approaches for configuring an accessibility service: + *

+ *
    + *
  • + * Providing a {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring + * the service. A service declaration with a meta-data tag is presented below: + *
     <service android:name=".MyAccessibilityService">
    + *     <intent-filter>
    + *         <action android:name="android.accessibilityservice.AccessibilityService" />
    + *     </intent-filter>
    + *     <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice" />
    + * </service>
    + *

    + * Note: This approach enables setting all properties. + *

    + *

    + * For more details refer to {@link #SERVICE_META_DATA} and + * <{@link android.R.styleable#AccessibilityService accessibility-service}>. + *

    + *
  • + *
  • + * Calling {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}. Note + * that this method can be called any time to dynamically change the service configuration. + *

    + * Note: This approach enables setting only dynamically configurable properties: + * {@link AccessibilityServiceInfo#eventTypes}, + * {@link AccessibilityServiceInfo#feedbackType}, + * {@link AccessibilityServiceInfo#flags}, + * {@link AccessibilityServiceInfo#notificationTimeout}, + * {@link AccessibilityServiceInfo#packageNames} + *

    + *

    + * For more details refer to {@link AccessibilityServiceInfo}. + *

    + *
  • + *
+ *

Retrieving window content

+ *

+ * A service can specify in its declaration that it can retrieve the active window + * content which is represented as a tree of {@link AccessibilityNodeInfo}. Note that + * declaring this capability requires that the service declares its configuration via + * an XML resource referenced by {@link #SERVICE_META_DATA}. + *

+ *

+ * For security purposes an accessibility service can retrieve only the content of the + * currently active window. The currently active window is defined as the window from + * which was fired the last event of the following types: + * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}, + * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}, + * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}, + * In other words, the last window that was shown or the last window that the user has touched + * during touch exploration. + *

+ *

+ * The entry point for retrieving window content is through calling + * {@link AccessibilityEvent#getSource() AccessibilityEvent.getSource()} of the last received + * event of the above types or a previous event from the same window + * (see {@link AccessibilityEvent#getWindowId() AccessibilityEvent.getWindowId()}). Invoking + * this method will return an {@link AccessibilityNodeInfo} that can be used to traverse the + * window content which represented as a tree of such objects. + *

+ *

+ * Note An accessibility service may have requested to be notified for + * a subset of the event types, thus be unaware that the active window has changed. Therefore + * accessibility service that would like to retrieve window content should: + *

    + *
  • + * Register for all event types with no notification timeout and keep track for the active + * window by calling {@link AccessibilityEvent#getWindowId()} of the last received event and + * compare this with the {@link AccessibilityNodeInfo#getWindowId()} before calling retrieval + * methods on the latter. + *
  • + *
  • + * Prepare that a retrieval method on {@link AccessibilityNodeInfo} may fail since the + * active window has changed and the service did not get the accessibility event yet. Note + * that it is possible to have a retrieval method failing even adopting the strategy + * specified in the previous bullet because the accessibility event dispatch is asynchronous + * and crosses process boundaries. + *
  • + *
+ *

+ *

Notification strategy

+ *

+ * For each feedback type only one accessibility service is notified. Services are notified + * in the order of registration. Hence, if two services are registered for the same + * feedback type in the same package the first one wins. It is possible however, to + * register a service as the default one for a given feedback type. In such a case this + * service is invoked if no other service was interested in the event. In other words, default + * services do not compete with other services and are notified last regardless of the + * registration order. This enables "generic" accessibility services that work reasonably + * well with most applications to coexist with "polished" ones that are targeted for + * specific applications. + *

+ *

+ * Note: The event notification timeout is useful to avoid propagating + * events to the client too frequently since this is accomplished via an expensive + * interprocess call. One can think of the timeout as a criteria to determine when + * event generation has settled down.

+ *

Event types

+ *
    + *
  • {@link AccessibilityEvent#TYPE_VIEW_CLICKED} + *
  • {@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED} + *
  • {@link AccessibilityEvent#TYPE_VIEW_FOCUSED} + *
  • {@link AccessibilityEvent#TYPE_VIEW_SELECTED} + *
  • {@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED} + *
  • {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED} + *
  • {@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED} + *
  • {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START} + *
  • {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END} + *
  • {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER} + *
  • {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT} + *
  • {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} + *
  • {@link AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED} + *
  • {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} + *
+ *

Feedback types

+ *
    + *
  • {@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE} + *
  • {@link AccessibilityServiceInfo#FEEDBACK_HAPTIC} + *
  • {@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE} + *
  • {@link AccessibilityServiceInfo#FEEDBACK_VISUAL} + *
  • {@link AccessibilityServiceInfo#FEEDBACK_GENERIC} + *
+ * @see AccessibilityEvent + * @see AccessibilityServiceInfo + * @see android.view.accessibility.AccessibilityManager + */ +public abstract class AccessibilityService extends Service { + + /** + * The user has performed a swipe up gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_UP = 1; + + /** + * The user has performed a swipe down gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_DOWN = 2; + + /** + * The user has performed a swipe left gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_LEFT = 3; + + /** + * The user has performed a swipe right gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_RIGHT = 4; + + /** + * The user has performed a swipe left and right gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_LEFT_AND_RIGHT = 5; + + /** + * The user has performed a swipe right and left gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_RIGHT_AND_LEFT = 6; + + /** + * The user has performed a swipe up and down gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_UP_AND_DOWN = 7; + + /** + * The user has performed a swipe down and up gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_DOWN_AND_UP = 8; + + /** + * The user has performed a left and up gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_LEFT_AND_UP = 9; + + /** + * The user has performed a left and down gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_LEFT_AND_DOWN = 10; + + /** + * The user has performed a right and up gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_RIGHT_AND_UP = 11; + + /** + * The user has performed a right and down gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_RIGHT_AND_DOWN = 12; + + /** + * The user has performed an up and left gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_UP_AND_LEFT = 13; + + /** + * The user has performed an up and right gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; + + /** + * The user has performed an down and left gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15; + + /** + * The user has performed an down and right gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16; + + /** + * The {@link Intent} that must be declared as handled by the service. + */ + public static final String SERVICE_INTERFACE = + "android.accessibilityservice.AccessibilityService"; + + /** + * Name under which an AccessibilityService component publishes information + * about itself. This meta-data must reference an XML resource containing an + * <{@link android.R.styleable#AccessibilityService accessibility-service}> + * tag. This is a a sample XML file configuring an accessibility service: + *
 <accessibility-service
+     *     android:accessibilityEventTypes="typeViewClicked|typeViewFocused"
+     *     android:packageNames="foo.bar, foo.baz"
+     *     android:accessibilityFeedbackType="feedbackSpoken"
+     *     android:notificationTimeout="100"
+     *     android:accessibilityFlags="flagDefault"
+     *     android:settingsActivity="foo.bar.TestBackActivity"
+     *     android:canRetrieveWindowContent="true"
+     *     . . .
+     * />
+ */ + public static final String SERVICE_META_DATA = "android.accessibilityservice"; + + /** + * Action to go back. + */ + public static final int GLOBAL_ACTION_BACK = 1; + + /** + * Action to go home. + */ + public static final int GLOBAL_ACTION_HOME = 2; + + /** + * Action to open the recents. + */ + public static final int GLOBAL_ACTION_RECENTS = 3; + + /** + * Action to open the notifications. + */ + public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; + + private static final String LOG_TAG = "AccessibilityService"; + + interface Callbacks { + public void onAccessibilityEvent(AccessibilityEvent event); + public void onInterrupt(); + public void onServiceConnected(); + public void onSetConnectionId(int connectionId); + public boolean onGesture(int gestureId); + } + + private int mConnectionId; + + private AccessibilityServiceInfo mInfo; + + /** + * Callback for {@link android.view.accessibility.AccessibilityEvent}s. + * + * @param event An event. + */ + public abstract void onAccessibilityEvent(AccessibilityEvent event); + + /** + * Callback for interrupting the accessibility feedback. + */ + public abstract void onInterrupt(); + + /** + * This method is a part of the {@link AccessibilityService} lifecycle and is + * called after the system has successfully bound to the service. If is + * convenient to use this method for setting the {@link AccessibilityServiceInfo}. + * + * @see AccessibilityServiceInfo + * @see #setServiceInfo(AccessibilityServiceInfo) + */ + protected void onServiceConnected() { + + } + + /** + * Called by the system when the user performs a specific gesture on the + * touch screen. + * + * Note: To receive gestures an accessibility service must + * request that the device is in touch exploration mode by setting the + * {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} + * flag. + * + * @param gestureId The unique id of the performed gesture. + * + * @return Whether the gesture was handled. + * + * @see #GESTURE_SWIPE_UP + * @see #GESTURE_SWIPE_UP_AND_LEFT + * @see #GESTURE_SWIPE_UP_AND_DOWN + * @see #GESTURE_SWIPE_UP_AND_RIGHT + * @see #GESTURE_SWIPE_DOWN + * @see #GESTURE_SWIPE_DOWN_AND_LEFT + * @see #GESTURE_SWIPE_DOWN_AND_UP + * @see #GESTURE_SWIPE_DOWN_AND_RIGHT + * @see #GESTURE_SWIPE_LEFT + * @see #GESTURE_SWIPE_LEFT_AND_UP + * @see #GESTURE_SWIPE_LEFT_AND_RIGHT + * @see #GESTURE_SWIPE_LEFT_AND_DOWN + * @see #GESTURE_SWIPE_RIGHT + * @see #GESTURE_SWIPE_RIGHT_AND_UP + * @see #GESTURE_SWIPE_RIGHT_AND_LEFT + * @see #GESTURE_SWIPE_RIGHT_AND_DOWN + */ + protected boolean onGesture(int gestureId) { + return false; + } + + /** + * Gets the root node in the currently active window if this service + * can retrieve window content. + * + * @return The root node if this service can retrieve window content. + */ + public AccessibilityNodeInfo getRootInActiveWindow() { + return AccessibilityInteractionClient.getInstance().getRootInActiveWindow(mConnectionId); + } + + /** + * Performs a global action. Such an action can be performed + * at any moment regardless of the current application or user + * location in that application. For example going back, going + * home, opening recents, etc. + * + * @param action The action to perform. + * @return Whether the action was successfully performed. + * + * @see #GLOBAL_ACTION_BACK + * @see #GLOBAL_ACTION_HOME + * @see #GLOBAL_ACTION_NOTIFICATIONS + * @see #GLOBAL_ACTION_RECENTS + */ + public final boolean performGlobalAction(int action) { + IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); + if (connection != null) { + try { + return connection.performGlobalAction(action); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Error while calling performGlobalAction", re); + } + } + return false; + } + + /** + * Gets the an {@link AccessibilityServiceInfo} describing this + * {@link AccessibilityService}. This method is useful if one wants + * to change some of the dynamically configurable properties at + * runtime. + * + * @return The accessibility service info. + * + * @see AccessibilityNodeInfo + */ + public final AccessibilityServiceInfo getServiceInfo() { + IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); + if (connection != null) { + try { + return connection.getServiceInfo(); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re); + } + } + return null; + } + + /** + * Sets the {@link AccessibilityServiceInfo} that describes this service. + *

+ * Note: You can call this method any time but the info will be picked up after + * the system has bound to this service and when this method is called thereafter. + * + * @param info The info. + */ + public final void setServiceInfo(AccessibilityServiceInfo info) { + mInfo = info; + sendServiceInfo(); + } + + /** + * Sets the {@link AccessibilityServiceInfo} for this service if the latter is + * properly set and there is an {@link IAccessibilityServiceConnection} to the + * AccessibilityManagerService. + */ + private void sendServiceInfo() { + IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); + if (mInfo != null && connection != null) { + try { + connection.setServiceInfo(mInfo); + mInfo = null; + AccessibilityInteractionClient.getInstance().clearCache(); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re); + } + } + } + + /** + * Implement to return the implementation of the internal accessibility + * service interface. + */ + @Override + public final IBinder onBind(Intent intent) { + return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() { + @Override + public void onServiceConnected() { + AccessibilityService.this.onServiceConnected(); + } + + @Override + public void onInterrupt() { + AccessibilityService.this.onInterrupt(); + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + AccessibilityService.this.onAccessibilityEvent(event); + } + + @Override + public void onSetConnectionId( int connectionId) { + mConnectionId = connectionId; + } + + @Override + public boolean onGesture(int gestureId) { + return AccessibilityService.this.onGesture(gestureId); + } + }); + } + + /** + * Implements the internal {@link IAccessibilityServiceClient} interface to convert + * incoming calls to it back to calls on an {@link AccessibilityService}. + */ + static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub + implements HandlerCaller.Callback { + + static final int NO_ID = -1; + + private static final int DO_SET_SET_CONNECTION = 10; + private static final int DO_ON_INTERRUPT = 20; + private static final int DO_ON_ACCESSIBILITY_EVENT = 30; + private static final int DO_ON_GESTURE = 40; + + private final HandlerCaller mCaller; + + private final Callbacks mCallback; + + public IAccessibilityServiceClientWrapper(Context context, Looper looper, + Callbacks callback) { + mCallback = callback; + mCaller = new HandlerCaller(context, looper, this); + } + + public void setConnection(IAccessibilityServiceConnection connection, int connectionId) { + Message message = mCaller.obtainMessageIO(DO_SET_SET_CONNECTION, connectionId, + connection); + mCaller.sendMessage(message); + } + + public void onInterrupt() { + Message message = mCaller.obtainMessage(DO_ON_INTERRUPT); + mCaller.sendMessage(message); + } + + public void onAccessibilityEvent(AccessibilityEvent event) { + Message message = mCaller.obtainMessageO(DO_ON_ACCESSIBILITY_EVENT, event); + mCaller.sendMessage(message); + } + + public void onGesture(int gestureId) { + Message message = mCaller.obtainMessageI(DO_ON_GESTURE, gestureId); + mCaller.sendMessage(message); + } + + public void executeMessage(Message message) { + switch (message.what) { + case DO_ON_ACCESSIBILITY_EVENT : + AccessibilityEvent event = (AccessibilityEvent) message.obj; + if (event != null) { + AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event); + mCallback.onAccessibilityEvent(event); + event.recycle(); + } + return; + case DO_ON_INTERRUPT : + mCallback.onInterrupt(); + return; + case DO_SET_SET_CONNECTION : + final int connectionId = message.arg1; + IAccessibilityServiceConnection connection = + (IAccessibilityServiceConnection) message.obj; + if (connection != null) { + AccessibilityInteractionClient.getInstance().addConnection(connectionId, + connection); + mCallback.onSetConnectionId(connectionId); + mCallback.onServiceConnected(); + } else { + AccessibilityInteractionClient.getInstance().removeConnection(connectionId); + mCallback.onSetConnectionId(AccessibilityInteractionClient.NO_ID); + } + return; + case DO_ON_GESTURE : + final int gestureId = message.arg1; + mCallback.onGesture(gestureId); + return; + default : + Log.w(LOG_TAG, "Unknown message type " + message.what); + } + } + } +} diff --git a/src/frameworks/base/core/java/android/accounts/AccountManagerService.java b/src/frameworks/base/core/java/android/accounts/AccountManagerService.java new file mode 100644 index 00000000..079b9bd2 --- /dev/null +++ b/src/frameworks/base/core/java/android/accounts/AccountManagerService.java @@ -0,0 +1,2480 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accounts; + +import android.Manifest; +import android.app.ActivityManager; +import android.app.AppGlobals; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.RegisteredServicesCache; +import android.content.pm.RegisteredServicesCacheListener; +import android.content.pm.UserInfo; +import android.content.res.Resources; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.Binder; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserId; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; +import android.util.SparseArray; + +import com.android.internal.R; +import com.android.internal.util.IndentingPrintWriter; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A system service that provides account, password, and authtoken management for all + * accounts on the device. Some of these calls are implemented with the help of the corresponding + * {@link IAccountAuthenticator} services. This service is not accessed by users directly, + * instead one uses an instance of {@link AccountManager}, which can be accessed as follows: + * AccountManager accountManager = AccountManager.get(context); + * @hide + */ +public class AccountManagerService + extends IAccountManager.Stub + implements RegisteredServicesCacheListener { + private static final String TAG = "AccountManagerService"; + + private static final int TIMEOUT_DELAY_MS = 1000 * 60; + private static final String DATABASE_NAME = "accounts.db"; + private static final int DATABASE_VERSION = 4; + + private final Context mContext; + + private final PackageManager mPackageManager; + + private HandlerThread mMessageThread; + private final MessageHandler mMessageHandler; + + // Messages that can be sent on mHandler + private static final int MESSAGE_TIMED_OUT = 3; + + private final IAccountAuthenticatorCache mAuthenticatorCache; + + private static final String TABLE_ACCOUNTS = "accounts"; + private static final String ACCOUNTS_ID = "_id"; + private static final String ACCOUNTS_NAME = "name"; + private static final String ACCOUNTS_TYPE = "type"; + private static final String ACCOUNTS_TYPE_COUNT = "count(type)"; + private static final String ACCOUNTS_PASSWORD = "password"; + + private static final String TABLE_AUTHTOKENS = "authtokens"; + private static final String AUTHTOKENS_ID = "_id"; + private static final String AUTHTOKENS_ACCOUNTS_ID = "accounts_id"; + private static final String AUTHTOKENS_TYPE = "type"; + private static final String AUTHTOKENS_AUTHTOKEN = "authtoken"; + + private static final String TABLE_GRANTS = "grants"; + private static final String GRANTS_ACCOUNTS_ID = "accounts_id"; + private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type"; + private static final String GRANTS_GRANTEE_UID = "uid"; + + private static final String TABLE_EXTRAS = "extras"; + private static final String EXTRAS_ID = "_id"; + private static final String EXTRAS_ACCOUNTS_ID = "accounts_id"; + private static final String EXTRAS_KEY = "key"; + private static final String EXTRAS_VALUE = "value"; + + private static final String TABLE_META = "meta"; + private static final String META_KEY = "key"; + private static final String META_VALUE = "value"; + + private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION = + new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT}; + private static final Intent ACCOUNTS_CHANGED_INTENT; + + private static final String COUNT_OF_MATCHING_GRANTS = "" + + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS + + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID + + " AND " + GRANTS_GRANTEE_UID + "=?" + + " AND " + GRANTS_AUTH_TOKEN_TYPE + "=?" + + " AND " + ACCOUNTS_NAME + "=?" + + " AND " + ACCOUNTS_TYPE + "=?"; + + private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT = + AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)"; + private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN = {AUTHTOKENS_TYPE, + AUTHTOKENS_AUTHTOKEN}; + + private static final String SELECTION_USERDATA_BY_ACCOUNT = + EXTRAS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)"; + private static final String[] COLUMNS_EXTRAS_KEY_AND_VALUE = {EXTRAS_KEY, EXTRAS_VALUE}; + + private final LinkedHashMap mSessions = new LinkedHashMap(); + private final AtomicInteger mNotificationIds = new AtomicInteger(1); + + static class UserAccounts { + private final int userId; + private final DatabaseHelper openHelper; + private final HashMap, Integer>, Integer> + credentialsPermissionNotificationIds = + new HashMap, Integer>, Integer>(); + private final HashMap signinRequiredNotificationIds = + new HashMap(); + private final Object cacheLock = new Object(); + /** protected by the {@link #cacheLock} */ + private final HashMap accountCache = + new LinkedHashMap(); + /** protected by the {@link #cacheLock} */ + private HashMap> userDataCache = + new HashMap>(); + /** protected by the {@link #cacheLock} */ + private HashMap> authTokenCache = + new HashMap>(); + + UserAccounts(Context context, int userId) { + this.userId = userId; + synchronized (cacheLock) { + openHelper = new DatabaseHelper(context, userId); + } + } + } + + private final SparseArray mUsers = new SparseArray(); + + private static AtomicReference sThis = + new AtomicReference(); + private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{}; + + static { + ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION); + ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + } + + + /** + * This should only be called by system code. One should only call this after the service + * has started. + * @return a reference to the AccountManagerService instance + * @hide + */ + public static AccountManagerService getSingleton() { + return sThis.get(); + } + + public AccountManagerService(Context context) { + this(context, context.getPackageManager(), new AccountAuthenticatorCache(context)); + } + + public AccountManagerService(Context context, PackageManager packageManager, + IAccountAuthenticatorCache authenticatorCache) { + mContext = context; + mPackageManager = packageManager; + + mMessageThread = new HandlerThread("AccountManagerService"); + mMessageThread.start(); + mMessageHandler = new MessageHandler(mMessageThread.getLooper()); + + mAuthenticatorCache = authenticatorCache; + mAuthenticatorCache.setListener(this, null /* Handler */); + + sThis.set(this); + + UserAccounts accounts = initUser(0); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + intentFilter.addDataScheme("package"); + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context1, Intent intent) { + purgeOldGrantsAll(); + } + }, intentFilter); + + IntentFilter userFilter = new IntentFilter(); + userFilter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + onUserRemoved(intent); + } + }, userFilter); + } + + private UserAccounts initUser(int userId) { + synchronized (mUsers) { + UserAccounts accounts = mUsers.get(userId); + if (accounts == null) { + accounts = new UserAccounts(mContext, userId); + mUsers.append(userId, accounts); + purgeOldGrants(accounts); + validateAccountsAndPopulateCache(accounts); + } + return accounts; + } + } + + private void purgeOldGrantsAll() { + synchronized (mUsers) { + for (int i = 0; i < mUsers.size(); i++) { + purgeOldGrants(mUsers.valueAt(i)); + } + } + } + + private void purgeOldGrants(UserAccounts accounts) { + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + final Cursor cursor = db.query(TABLE_GRANTS, + new String[]{GRANTS_GRANTEE_UID}, + null, null, GRANTS_GRANTEE_UID, null, null); + try { + while (cursor.moveToNext()) { + final int uid = cursor.getInt(0); + final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null; + if (packageExists) { + continue; + } + Log.d(TAG, "deleting grants for UID " + uid + + " because its package is no longer installed"); + db.delete(TABLE_GRANTS, GRANTS_GRANTEE_UID + "=?", + new String[]{Integer.toString(uid)}); + } + } finally { + cursor.close(); + } + } + } + + private void validateAccountsAndPopulateCache(UserAccounts accounts) { + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + boolean accountDeleted = false; + Cursor cursor = db.query(TABLE_ACCOUNTS, + new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME}, + null, null, null, null, null); + try { + accounts.accountCache.clear(); + final HashMap> accountNamesByType = + new LinkedHashMap>(); + while (cursor.moveToNext()) { + final long accountId = cursor.getLong(0); + final String accountType = cursor.getString(1); + final String accountName = cursor.getString(2); + if (mAuthenticatorCache.getServiceInfo( + AuthenticatorDescription.newKey(accountType)) == null) { + Log.d(TAG, "deleting account " + accountName + " because type " + + accountType + " no longer has a registered authenticator"); + db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null); + accountDeleted = true; + final Account account = new Account(accountName, accountType); + accounts.userDataCache.remove(account); + accounts.authTokenCache.remove(account); + } else { + ArrayList accountNames = accountNamesByType.get(accountType); + if (accountNames == null) { + accountNames = new ArrayList(); + accountNamesByType.put(accountType, accountNames); + } + accountNames.add(accountName); + } + } + for (Map.Entry> cur + : accountNamesByType.entrySet()) { + final String accountType = cur.getKey(); + final ArrayList accountNames = cur.getValue(); + final Account[] accountsForType = new Account[accountNames.size()]; + int i = 0; + for (String accountName : accountNames) { + accountsForType[i] = new Account(accountName, accountType); + ++i; + } + accounts.accountCache.put(accountType, accountsForType); + } + } finally { + cursor.close(); + if (accountDeleted) { + sendAccountsChangedBroadcast(accounts.userId); + } + } + } + } + + private UserAccounts getUserAccountsForCaller() { + return getUserAccounts(UserId.getCallingUserId()); + } + + protected UserAccounts getUserAccounts(int userId) { + synchronized (mUsers) { + UserAccounts accounts = mUsers.get(userId); + if (accounts == null) { + accounts = initUser(userId); + mUsers.append(userId, accounts); + } + return accounts; + } + } + + private void onUserRemoved(Intent intent) { + int userId = intent.getIntExtra(Intent.EXTRA_USERID, -1); + if (userId < 1) return; + + UserAccounts accounts; + synchronized (mUsers) { + accounts = mUsers.get(userId); + mUsers.remove(userId); + } + if (accounts == null) { + File dbFile = new File(getDatabaseName(userId)); + dbFile.delete(); + return; + } + + synchronized (accounts.cacheLock) { + accounts.openHelper.close(); + File dbFile = new File(getDatabaseName(userId)); + dbFile.delete(); + } + } + + private List getAllUsers() { + try { + return AppGlobals.getPackageManager().getUsers(); + } catch (RemoteException re) { + // Local to system process, shouldn't happen + } + return null; + } + + public void onServiceChanged(AuthenticatorDescription desc, boolean removed) { + // Validate accounts for all users + List users = getAllUsers(); + if (users == null) { + validateAccountsAndPopulateCache(getUserAccountsForCaller()); + } else { + for (UserInfo user : users) { + validateAccountsAndPopulateCache(getUserAccounts(user.id)); + } + } + } + + public String getPassword(Account account) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getPassword: " + account + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + checkAuthenticateAccountsPermission(account); + + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + return readPasswordInternal(accounts, account); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private String readPasswordInternal(UserAccounts accounts, Account account) { + if (account == null) { + return null; + } + + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); + Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD}, + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", + new String[]{account.name, account.type}, null, null, null); + try { + if (cursor.moveToNext()) { + return cursor.getString(0); + } + return null; + } finally { + cursor.close(); + } + } + } + + public String getUserData(Account account, String key) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getUserData: " + account + + ", key " + key + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + if (key == null) throw new IllegalArgumentException("key is null"); + checkAuthenticateAccountsPermission(account); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + return readUserDataInternal(accounts, account, key); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public AuthenticatorDescription[] getAuthenticatorTypes() { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getAuthenticatorTypes: " + + "caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + long identityToken = clearCallingIdentity(); + try { + Collection> + authenticatorCollection = mAuthenticatorCache.getAllServices(); + AuthenticatorDescription[] types = + new AuthenticatorDescription[authenticatorCollection.size()]; + int i = 0; + for (AccountAuthenticatorCache.ServiceInfo authenticator + : authenticatorCollection) { + types[i] = authenticator.type; + i++; + } + return types; + } finally { + restoreCallingIdentity(identityToken); + } + } + + public boolean addAccount(Account account, String password, Bundle extras) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "addAccount: " + account + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + checkAuthenticateAccountsPermission(account); + + UserAccounts accounts = getUserAccountsForCaller(); + // fails if the account already exists + long identityToken = clearCallingIdentity(); + try { + return addAccountInternal(accounts, account, password, extras); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private boolean addAccountInternal(UserAccounts accounts, Account account, String password, + Bundle extras) { + if (account == null) { + return false; + } + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.beginTransaction(); + try { + long numMatches = DatabaseUtils.longForQuery(db, + "select count(*) from " + TABLE_ACCOUNTS + + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", + new String[]{account.name, account.type}); + if (numMatches > 0) { + Log.w(TAG, "insertAccountIntoDatabase: " + account + + ", skipping since the account already exists"); + return false; + } + ContentValues values = new ContentValues(); + values.put(ACCOUNTS_NAME, account.name); + values.put(ACCOUNTS_TYPE, account.type); + values.put(ACCOUNTS_PASSWORD, password); + long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values); + if (accountId < 0) { + Log.w(TAG, "insertAccountIntoDatabase: " + account + + ", skipping the DB insert failed"); + return false; + } + if (extras != null) { + for (String key : extras.keySet()) { + final String value = extras.getString(key); + if (insertExtraLocked(db, accountId, key, value) < 0) { + Log.w(TAG, "insertAccountIntoDatabase: " + account + + ", skipping since insertExtra failed for key " + key); + return false; + } + } + } + db.setTransactionSuccessful(); + insertAccountIntoCacheLocked(accounts, account); + } finally { + db.endTransaction(); + } + sendAccountsChangedBroadcast(accounts.userId); + return true; + } + } + + private long insertExtraLocked(SQLiteDatabase db, long accountId, String key, String value) { + ContentValues values = new ContentValues(); + values.put(EXTRAS_KEY, key); + values.put(EXTRAS_ACCOUNTS_ID, accountId); + values.put(EXTRAS_VALUE, value); + return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values); + } + + public void hasFeatures(IAccountManagerResponse response, + Account account, String[] features) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "hasFeatures: " + account + + ", response " + response + + ", features " + stringArrayToString(features) + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (account == null) throw new IllegalArgumentException("account is null"); + if (features == null) throw new IllegalArgumentException("features is null"); + checkReadAccountsPermission(); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + new TestFeaturesSession(accounts, response, account, features).bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private class TestFeaturesSession extends Session { + private final String[] mFeatures; + private final Account mAccount; + + public TestFeaturesSession(UserAccounts accounts, IAccountManagerResponse response, + Account account, String[] features) { + super(accounts, response, account.type, false /* expectActivityLaunch */, + true /* stripAuthTokenFromResult */); + mFeatures = features; + mAccount = account; + } + + public void run() throws RemoteException { + try { + mAuthenticator.hasFeatures(this, mAccount, mFeatures); + } catch (RemoteException e) { + onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception"); + } + } + + public void onResult(Bundle result) { + IAccountManagerResponse response = getResponseAndClose(); + if (response != null) { + try { + if (result == null) { + response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle"); + return; + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " + + response); + } + final Bundle newResult = new Bundle(); + newResult.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, + result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)); + response.onResult(newResult); + } catch (RemoteException e) { + // if the caller is dead then there is no one to care about remote exceptions + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "failure while notifying response", e); + } + } + } + } + + protected String toDebugString(long now) { + return super.toDebugString(now) + ", hasFeatures" + + ", " + mAccount + + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null); + } + } + + public void removeAccount(IAccountManagerResponse response, Account account) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "removeAccount: " + account + + ", response " + response + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (account == null) throw new IllegalArgumentException("account is null"); + checkManageAccountsPermission(); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + + cancelNotification(getSigninRequiredNotificationId(accounts, account)); + synchronized(accounts.credentialsPermissionNotificationIds) { + for (Pair, Integer> pair: + accounts.credentialsPermissionNotificationIds.keySet()) { + if (account.equals(pair.first.first)) { + int id = accounts.credentialsPermissionNotificationIds.get(pair); + cancelNotification(id); + } + } + } + + try { + new RemoveAccountSession(accounts, response, account).bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private class RemoveAccountSession extends Session { + final Account mAccount; + public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response, + Account account) { + super(accounts, response, account.type, false /* expectActivityLaunch */, + true /* stripAuthTokenFromResult */); + mAccount = account; + } + + protected String toDebugString(long now) { + return super.toDebugString(now) + ", removeAccount" + + ", account " + mAccount; + } + + public void run() throws RemoteException { + mAuthenticator.getAccountRemovalAllowed(this, mAccount); + } + + public void onResult(Bundle result) { + if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT) + && !result.containsKey(AccountManager.KEY_INTENT)) { + final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT); + if (removalAllowed) { + removeAccountInternal(mAccounts, mAccount); + } + IAccountManagerResponse response = getResponseAndClose(); + if (response != null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " + + response); + } + Bundle result2 = new Bundle(); + result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed); + try { + response.onResult(result2); + } catch (RemoteException e) { + // ignore + } + } + } + super.onResult(result); + } + } + + /* For testing */ + protected void removeAccountInternal(Account account) { + removeAccountInternal(getUserAccountsForCaller(), account); + } + + private void removeAccountInternal(UserAccounts accounts, Account account) { + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", + new String[]{account.name, account.type}); + removeAccountFromCacheLocked(accounts, account); + sendAccountsChangedBroadcast(accounts.userId); + } + } + + public void invalidateAuthToken(String accountType, String authToken) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "invalidateAuthToken: accountType " + accountType + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + if (authToken == null) throw new IllegalArgumentException("authToken is null"); + checkManageAccountsOrUseCredentialsPermissions(); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.beginTransaction(); + try { + invalidateAuthTokenLocked(accounts, db, accountType, authToken); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + private void invalidateAuthTokenLocked(UserAccounts accounts, SQLiteDatabase db, + String accountType, String authToken) { + if (authToken == null || accountType == null) { + return; + } + Cursor cursor = db.rawQuery( + "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID + + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME + + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE + + " FROM " + TABLE_ACCOUNTS + + " JOIN " + TABLE_AUTHTOKENS + + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID + + " = " + AUTHTOKENS_ACCOUNTS_ID + + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND " + + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?", + new String[]{authToken, accountType}); + try { + while (cursor.moveToNext()) { + long authTokenId = cursor.getLong(0); + String accountName = cursor.getString(1); + String authTokenType = cursor.getString(2); + db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null); + writeAuthTokenIntoCacheLocked(accounts, db, new Account(accountName, accountType), + authTokenType, null); + } + } finally { + cursor.close(); + } + } + + private boolean saveAuthTokenToDatabase(UserAccounts accounts, Account account, String type, + String authToken) { + if (account == null || type == null) { + return false; + } + cancelNotification(getSigninRequiredNotificationId(accounts, account)); + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.beginTransaction(); + try { + long accountId = getAccountIdLocked(db, account); + if (accountId < 0) { + return false; + } + db.delete(TABLE_AUTHTOKENS, + AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?", + new String[]{type}); + ContentValues values = new ContentValues(); + values.put(AUTHTOKENS_ACCOUNTS_ID, accountId); + values.put(AUTHTOKENS_TYPE, type); + values.put(AUTHTOKENS_AUTHTOKEN, authToken); + if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) { + db.setTransactionSuccessful(); + writeAuthTokenIntoCacheLocked(accounts, db, account, type, authToken); + return true; + } + return false; + } finally { + db.endTransaction(); + } + } + } + + public String peekAuthToken(Account account, String authTokenType) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "peekAuthToken: " + account + + ", authTokenType " + authTokenType + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); + checkAuthenticateAccountsPermission(account); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + return readAuthTokenInternal(accounts, account, authTokenType); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void setAuthToken(Account account, String authTokenType, String authToken) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "setAuthToken: " + account + + ", authTokenType " + authTokenType + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); + checkAuthenticateAccountsPermission(account); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + saveAuthTokenToDatabase(accounts, account, authTokenType, authToken); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void setPassword(Account account, String password) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "setAuthToken: " + account + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + checkAuthenticateAccountsPermission(account); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + setPasswordInternal(accounts, account, password); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private void setPasswordInternal(UserAccounts accounts, Account account, String password) { + if (account == null) { + return; + } + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.beginTransaction(); + try { + final ContentValues values = new ContentValues(); + values.put(ACCOUNTS_PASSWORD, password); + final long accountId = getAccountIdLocked(db, account); + if (accountId >= 0) { + final String[] argsAccountId = {String.valueOf(accountId)}; + db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId); + db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId); + accounts.authTokenCache.remove(account); + db.setTransactionSuccessful(); + } + } finally { + db.endTransaction(); + } + sendAccountsChangedBroadcast(accounts.userId); + } + } + + private void sendAccountsChangedBroadcast(int userId) { + Log.i(TAG, "the accounts changed, sending broadcast of " + + ACCOUNTS_CHANGED_INTENT.getAction()); + mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT, userId); + } + + public void clearPassword(Account account) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "clearPassword: " + account + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + checkManageAccountsPermission(); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + setPasswordInternal(accounts, account, null); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void setUserData(Account account, String key, String value) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "setUserData: " + account + + ", key " + key + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (key == null) throw new IllegalArgumentException("key is null"); + if (account == null) throw new IllegalArgumentException("account is null"); + checkAuthenticateAccountsPermission(account); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + setUserdataInternal(accounts, account, key, value); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private void setUserdataInternal(UserAccounts accounts, Account account, String key, + String value) { + if (account == null || key == null) { + return; + } + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.beginTransaction(); + try { + long accountId = getAccountIdLocked(db, account); + if (accountId < 0) { + return; + } + long extrasId = getExtrasIdLocked(db, accountId, key); + if (extrasId < 0 ) { + extrasId = insertExtraLocked(db, accountId, key, value); + if (extrasId < 0) { + return; + } + } else { + ContentValues values = new ContentValues(); + values.put(EXTRAS_VALUE, value); + if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) { + return; + } + + } + writeUserDataIntoCacheLocked(accounts, db, account, key, value); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + } + + private void onResult(IAccountManagerResponse response, Bundle result) { + if (result == null) { + Log.e(TAG, "the result is unexpectedly null", new Exception()); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " + + response); + } + try { + response.onResult(result); + } catch (RemoteException e) { + // if the caller is dead then there is no one to care about remote + // exceptions + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "failure while notifying response", e); + } + } + } + + public void getAuthTokenLabel(IAccountManagerResponse response, final String accountType, + final String authTokenType) + throws RemoteException { + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); + + final int callingUid = getCallingUid(); + clearCallingIdentity(); + if (callingUid != android.os.Process.SYSTEM_UID) { + throw new SecurityException("can only call from system"); + } + UserAccounts accounts = getUserAccounts(UserId.getUserId(callingUid)); + long identityToken = clearCallingIdentity(); + try { + new Session(accounts, response, accountType, false, + false /* stripAuthTokenFromResult */) { + protected String toDebugString(long now) { + return super.toDebugString(now) + ", getAuthTokenLabel" + + ", " + accountType + + ", authTokenType " + authTokenType; + } + + public void run() throws RemoteException { + mAuthenticator.getAuthTokenLabel(this, authTokenType); + } + + public void onResult(Bundle result) { + if (result != null) { + String label = result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL); + Bundle bundle = new Bundle(); + bundle.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, label); + super.onResult(bundle); + return; + } else { + super.onResult(result); + } + } + }.bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void getAuthToken(IAccountManagerResponse response, final Account account, + final String authTokenType, final boolean notifyOnAuthFailure, + final boolean expectActivityLaunch, Bundle loginOptionsIn) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getAuthToken: " + account + + ", response " + response + + ", authTokenType " + authTokenType + + ", notifyOnAuthFailure " + notifyOnAuthFailure + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (account == null) throw new IllegalArgumentException("account is null"); + if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); + checkBinderPermission(Manifest.permission.USE_CREDENTIALS); + UserAccounts accounts = getUserAccountsForCaller(); + AccountAuthenticatorCache.ServiceInfo authenticatorInfo = + mAuthenticatorCache.getServiceInfo( + AuthenticatorDescription.newKey(account.type)); + final boolean customTokens = + authenticatorInfo != null && authenticatorInfo.type.customTokens; + + // skip the check if customTokens + final int callerUid = Binder.getCallingUid(); + final boolean permissionGranted = customTokens || + permissionIsGranted(account, authTokenType, callerUid); + + final Bundle loginOptions = (loginOptionsIn == null) ? new Bundle() : + loginOptionsIn; + // let authenticator know the identity of the caller + loginOptions.putInt(AccountManager.KEY_CALLER_UID, callerUid); + loginOptions.putInt(AccountManager.KEY_CALLER_PID, Binder.getCallingPid()); + if (notifyOnAuthFailure) { + loginOptions.putBoolean(AccountManager.KEY_NOTIFY_ON_FAILURE, true); + } + + long identityToken = clearCallingIdentity(); + try { + // if the caller has permission, do the peek. otherwise go the more expensive + // route of starting a Session + if (!customTokens && permissionGranted) { + String authToken = readAuthTokenInternal(accounts, account, authTokenType); + if (authToken != null) { + Bundle result = new Bundle(); + result.putString(AccountManager.KEY_AUTHTOKEN, authToken); + result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); + onResult(response, result); + return; + } + } + + new Session(accounts, response, account.type, expectActivityLaunch, + false /* stripAuthTokenFromResult */) { + protected String toDebugString(long now) { + if (loginOptions != null) loginOptions.keySet(); + return super.toDebugString(now) + ", getAuthToken" + + ", " + account + + ", authTokenType " + authTokenType + + ", loginOptions " + loginOptions + + ", notifyOnAuthFailure " + notifyOnAuthFailure; + } + + public void run() throws RemoteException { + // If the caller doesn't have permission then create and return the + // "grant permission" intent instead of the "getAuthToken" intent. + if (!permissionGranted) { + mAuthenticator.getAuthTokenLabel(this, authTokenType); + } else { + mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions); + } + } + + public void onResult(Bundle result) { + if (result != null) { + if (result.containsKey(AccountManager.KEY_AUTH_TOKEN_LABEL)) { + Intent intent = newGrantCredentialsPermissionIntent(account, callerUid, + new AccountAuthenticatorResponse(this), + authTokenType, + result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL)); + Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + onResult(bundle); + return; + } + String authToken = result.getString(AccountManager.KEY_AUTHTOKEN); + if (authToken != null) { + String name = result.getString(AccountManager.KEY_ACCOUNT_NAME); + String type = result.getString(AccountManager.KEY_ACCOUNT_TYPE); + if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) { + onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, + "the type and name should not be empty"); + return; + } + if (!customTokens) { + saveAuthTokenToDatabase(mAccounts, new Account(name, type), + authTokenType, authToken); + } + } + + Intent intent = result.getParcelable(AccountManager.KEY_INTENT); + if (intent != null && notifyOnAuthFailure && !customTokens) { + doNotification(mAccounts, + account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE), + intent); + } + } + super.onResult(result); + } + }.bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private void createNoCredentialsPermissionNotification(Account account, Intent intent) { + int uid = intent.getIntExtra( + GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1); + String authTokenType = intent.getStringExtra( + GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE); + String authTokenLabel = intent.getStringExtra( + GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_LABEL); + + Notification n = new Notification(android.R.drawable.stat_sys_warning, null, + 0 /* when */); + final String titleAndSubtitle = + mContext.getString(R.string.permission_request_notification_with_subtitle, + account.name); + final int index = titleAndSubtitle.indexOf('\n'); + String title = titleAndSubtitle; + String subtitle = ""; + if (index > 0) { + title = titleAndSubtitle.substring(0, index); + subtitle = titleAndSubtitle.substring(index + 1); + } + n.setLatestEventInfo(mContext, + title, subtitle, + PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)); + installNotification(getCredentialPermissionNotificationId(account, authTokenType, uid), n); + } + + String getAccountLabel(String accountType) { + RegisteredServicesCache.ServiceInfo serviceInfo = + mAuthenticatorCache.getServiceInfo( + AuthenticatorDescription.newKey(accountType)); + if (serviceInfo == null) { + throw new IllegalArgumentException("unknown account type: " + accountType); + } + + final Context authContext; + try { + authContext = mContext.createPackageContext( + serviceInfo.type.packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException("unknown account type: " + accountType); + } + try { + return authContext.getString(serviceInfo.type.labelId); + } catch (Resources.NotFoundException e) { + throw new IllegalArgumentException("unknown account type: " + accountType); + } + } + + private Intent newGrantCredentialsPermissionIntent(Account account, int uid, + AccountAuthenticatorResponse response, String authTokenType, String authTokenLabel) { + + Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class); + // See FLAG_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag. + // Since it was set in Eclair+ we can't change it without breaking apps using + // the intent from a non-Activity context. + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addCategory( + String.valueOf(getCredentialPermissionNotificationId(account, authTokenType, uid))); + + intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT, account); + intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE, authTokenType); + intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE, response); + intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, uid); + + return intent; + } + + private Integer getCredentialPermissionNotificationId(Account account, String authTokenType, + int uid) { + Integer id; + UserAccounts accounts = getUserAccounts(UserId.getUserId(uid)); + synchronized (accounts.credentialsPermissionNotificationIds) { + final Pair, Integer> key = + new Pair, Integer>( + new Pair(account, authTokenType), uid); + id = accounts.credentialsPermissionNotificationIds.get(key); + if (id == null) { + id = mNotificationIds.incrementAndGet(); + accounts.credentialsPermissionNotificationIds.put(key, id); + } + } + return id; + } + + private Integer getSigninRequiredNotificationId(UserAccounts accounts, Account account) { + Integer id; + synchronized (accounts.signinRequiredNotificationIds) { + id = accounts.signinRequiredNotificationIds.get(account); + if (id == null) { + id = mNotificationIds.incrementAndGet(); + accounts.signinRequiredNotificationIds.put(account, id); + } + } + return id; + } + + public void addAcount(final IAccountManagerResponse response, final String accountType, + final String authTokenType, final String[] requiredFeatures, + final boolean expectActivityLaunch, final Bundle optionsIn) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "addAccount: accountType " + accountType + + ", response " + response + + ", authTokenType " + authTokenType + + ", requiredFeatures " + stringArrayToString(requiredFeatures) + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + checkManageAccountsPermission(); + + UserAccounts accounts = getUserAccountsForCaller(); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn; + options.putInt(AccountManager.KEY_CALLER_UID, uid); + options.putInt(AccountManager.KEY_CALLER_PID, pid); + + long identityToken = clearCallingIdentity(); + try { + new Session(accounts, response, accountType, expectActivityLaunch, + true /* stripAuthTokenFromResult */) { + public void run() throws RemoteException { + mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures, + options); + } + + protected String toDebugString(long now) { + return super.toDebugString(now) + ", addAccount" + + ", accountType " + accountType + + ", requiredFeatures " + + (requiredFeatures != null + ? TextUtils.join(",", requiredFeatures) + : null); + } + }.bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void confirmCredentials(IAccountManagerResponse response, + final Account account, final Bundle options, final boolean expectActivityLaunch) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "confirmCredentials: " + account + + ", response " + response + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (account == null) throw new IllegalArgumentException("account is null"); + checkManageAccountsPermission(); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + new Session(accounts, response, account.type, expectActivityLaunch, + true /* stripAuthTokenFromResult */) { + public void run() throws RemoteException { + mAuthenticator.confirmCredentials(this, account, options); + } + protected String toDebugString(long now) { + return super.toDebugString(now) + ", confirmCredentials" + + ", " + account; + } + }.bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void updateCredentials(IAccountManagerResponse response, final Account account, + final String authTokenType, final boolean expectActivityLaunch, + final Bundle loginOptions) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "updateCredentials: " + account + + ", response " + response + + ", authTokenType " + authTokenType + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (account == null) throw new IllegalArgumentException("account is null"); + if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); + checkManageAccountsPermission(); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + new Session(accounts, response, account.type, expectActivityLaunch, + true /* stripAuthTokenFromResult */) { + public void run() throws RemoteException { + mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions); + } + protected String toDebugString(long now) { + if (loginOptions != null) loginOptions.keySet(); + return super.toDebugString(now) + ", updateCredentials" + + ", " + account + + ", authTokenType " + authTokenType + + ", loginOptions " + loginOptions; + } + }.bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void editProperties(IAccountManagerResponse response, final String accountType, + final boolean expectActivityLaunch) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "editProperties: accountType " + accountType + + ", response " + response + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + checkManageAccountsPermission(); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + new Session(accounts, response, accountType, expectActivityLaunch, + true /* stripAuthTokenFromResult */) { + public void run() throws RemoteException { + mAuthenticator.editProperties(this, mAccountType); + } + protected String toDebugString(long now) { + return super.toDebugString(now) + ", editProperties" + + ", accountType " + accountType; + } + }.bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private class GetAccountsByTypeAndFeatureSession extends Session { + private final String[] mFeatures; + private volatile Account[] mAccountsOfType = null; + private volatile ArrayList mAccountsWithFeatures = null; + private volatile int mCurrentAccount = 0; + + public GetAccountsByTypeAndFeatureSession(UserAccounts accounts, + IAccountManagerResponse response, String type, String[] features) { + super(accounts, response, type, false /* expectActivityLaunch */, + true /* stripAuthTokenFromResult */); + mFeatures = features; + } + + public void run() throws RemoteException { + synchronized (mAccounts.cacheLock) { + mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType); + } + // check whether each account matches the requested features + mAccountsWithFeatures = new ArrayList(mAccountsOfType.length); + mCurrentAccount = 0; + + checkAccount(); + } + + public void checkAccount() { + if (mCurrentAccount >= mAccountsOfType.length) { + sendResult(); + return; + } + + final IAccountAuthenticator accountAuthenticator = mAuthenticator; + if (accountAuthenticator == null) { + // It is possible that the authenticator has died, which is indicated by + // mAuthenticator being set to null. If this happens then just abort. + // There is no need to send back a result or error in this case since + // that already happened when mAuthenticator was cleared. + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "checkAccount: aborting session since we are no longer" + + " connected to the authenticator, " + toDebugString()); + } + return; + } + try { + accountAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures); + } catch (RemoteException e) { + onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception"); + } + } + + public void onResult(Bundle result) { + mNumResults++; + if (result == null) { + onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle"); + return; + } + if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) { + mAccountsWithFeatures.add(mAccountsOfType[mCurrentAccount]); + } + mCurrentAccount++; + checkAccount(); + } + + public void sendResult() { + IAccountManagerResponse response = getResponseAndClose(); + if (response != null) { + try { + Account[] accounts = new Account[mAccountsWithFeatures.size()]; + for (int i = 0; i < accounts.length; i++) { + accounts[i] = mAccountsWithFeatures.get(i); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " + + response); + } + Bundle result = new Bundle(); + result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts); + response.onResult(result); + } catch (RemoteException e) { + // if the caller is dead then there is no one to care about remote exceptions + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "failure while notifying response", e); + } + } + } + } + + + protected String toDebugString(long now) { + return super.toDebugString(now) + ", getAccountsByTypeAndFeatures" + + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null); + } + } + + /** + * Returns the accounts for a specific user + * @hide + */ + public Account[] getAccounts(int userId) { + checkReadAccountsPermission(); + UserAccounts accounts = getUserAccounts(userId); + long identityToken = clearCallingIdentity(); + try { + synchronized (accounts.cacheLock) { + return getAccountsFromCacheLocked(accounts, null); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + /** + * Returns all the accounts qualified by user. + * @hide + */ + public AccountAndUser[] getAllAccounts() { + ArrayList allAccounts = new ArrayList(); + List users = getAllUsers(); + if (users == null) return new AccountAndUser[0]; + + synchronized(mUsers) { + for (UserInfo user : users) { + UserAccounts userAccounts = getUserAccounts(user.id); + if (userAccounts == null) continue; + synchronized (userAccounts.cacheLock) { + Account[] accounts = getAccountsFromCacheLocked(userAccounts, null); + for (int a = 0; a < accounts.length; a++) { + allAccounts.add(new AccountAndUser(accounts[a], user.id)); + } + } + } + } + AccountAndUser[] accountsArray = new AccountAndUser[allAccounts.size()]; + return allAccounts.toArray(accountsArray); + } + + public Account[] getAccounts(String type) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getAccounts: accountType " + type + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + checkReadAccountsPermission(); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + synchronized (accounts.cacheLock) { + return getAccountsFromCacheLocked(accounts, type); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void getAccountsByFeatures(IAccountManagerResponse response, + String type, String[] features) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getAccounts: accountType " + type + + ", response " + response + + ", features " + stringArrayToString(features) + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (type == null) throw new IllegalArgumentException("accountType is null"); + checkReadAccountsPermission(); + UserAccounts userAccounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + if (features == null || features.length == 0) { + Account[] accounts; + synchronized (userAccounts.cacheLock) { + accounts = getAccountsFromCacheLocked(userAccounts, type); + } + Bundle result = new Bundle(); + result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts); + onResult(response, result); + return; + } + new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features).bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private long getAccountIdLocked(SQLiteDatabase db, Account account) { + Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID}, + "name=? AND type=?", new String[]{account.name, account.type}, null, null, null); + try { + if (cursor.moveToNext()) { + return cursor.getLong(0); + } + return -1; + } finally { + cursor.close(); + } + } + + private long getExtrasIdLocked(SQLiteDatabase db, long accountId, String key) { + Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_ID}, + EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?", + new String[]{key}, null, null, null); + try { + if (cursor.moveToNext()) { + return cursor.getLong(0); + } + return -1; + } finally { + cursor.close(); + } + } + + private abstract class Session extends IAccountAuthenticatorResponse.Stub + implements IBinder.DeathRecipient, ServiceConnection { + IAccountManagerResponse mResponse; + final String mAccountType; + final boolean mExpectActivityLaunch; + final long mCreationTime; + + public int mNumResults = 0; + private int mNumRequestContinued = 0; + private int mNumErrors = 0; + + + IAccountAuthenticator mAuthenticator = null; + + private final boolean mStripAuthTokenFromResult; + protected final UserAccounts mAccounts; + + public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType, + boolean expectActivityLaunch, boolean stripAuthTokenFromResult) { + super(); + if (response == null) throw new IllegalArgumentException("response is null"); + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + mAccounts = accounts; + mStripAuthTokenFromResult = stripAuthTokenFromResult; + mResponse = response; + mAccountType = accountType; + mExpectActivityLaunch = expectActivityLaunch; + mCreationTime = SystemClock.elapsedRealtime(); + synchronized (mSessions) { + mSessions.put(toString(), this); + } + try { + response.asBinder().linkToDeath(this, 0 /* flags */); + } catch (RemoteException e) { + mResponse = null; + binderDied(); + } + } + + IAccountManagerResponse getResponseAndClose() { + if (mResponse == null) { + // this session has already been closed + return null; + } + IAccountManagerResponse response = mResponse; + close(); // this clears mResponse so we need to save the response before this call + return response; + } + + private void close() { + synchronized (mSessions) { + if (mSessions.remove(toString()) == null) { + // the session was already closed, so bail out now + return; + } + } + if (mResponse != null) { + // stop listening for response deaths + mResponse.asBinder().unlinkToDeath(this, 0 /* flags */); + + // clear this so that we don't accidentally send any further results + mResponse = null; + } + cancelTimeout(); + unbind(); + } + + public void binderDied() { + mResponse = null; + close(); + } + + protected String toDebugString() { + return toDebugString(SystemClock.elapsedRealtime()); + } + + protected String toDebugString(long now) { + return "Session: expectLaunch " + mExpectActivityLaunch + + ", connected " + (mAuthenticator != null) + + ", stats (" + mNumResults + "/" + mNumRequestContinued + + "/" + mNumErrors + ")" + + ", lifetime " + ((now - mCreationTime) / 1000.0); + } + + void bind() { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "initiating bind to authenticator type " + mAccountType); + } + if (!bindToAuthenticator(mAccountType)) { + Log.d(TAG, "bind attempt failed for " + toDebugString()); + onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure"); + } + } + + private void unbind() { + if (mAuthenticator != null) { + mAuthenticator = null; + mContext.unbindService(this); + } + } + + public void scheduleTimeout() { + mMessageHandler.sendMessageDelayed( + mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS); + } + + public void cancelTimeout() { + mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this); + } + + public void onServiceConnected(ComponentName name, IBinder service) { + mAuthenticator = IAccountAuthenticator.Stub.asInterface(service); + try { + run(); + } catch (RemoteException e) { + onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, + "remote exception"); + } + } + + public void onServiceDisconnected(ComponentName name) { + mAuthenticator = null; + IAccountManagerResponse response = getResponseAndClose(); + if (response != null) { + try { + response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, + "disconnected"); + } catch (RemoteException e) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Session.onServiceDisconnected: " + + "caught RemoteException while responding", e); + } + } + } + } + + public abstract void run() throws RemoteException; + + public void onTimedOut() { + IAccountManagerResponse response = getResponseAndClose(); + if (response != null) { + try { + response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, + "timeout"); + } catch (RemoteException e) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Session.onTimedOut: caught RemoteException while responding", + e); + } + } + } + } + + public void onResult(Bundle result) { + mNumResults++; + if (result != null && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) { + String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME); + String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE); + if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) { + Account account = new Account(accountName, accountType); + cancelNotification(getSigninRequiredNotificationId(mAccounts, account)); + } + } + IAccountManagerResponse response; + if (mExpectActivityLaunch && result != null + && result.containsKey(AccountManager.KEY_INTENT)) { + response = mResponse; + } else { + response = getResponseAndClose(); + } + if (response != null) { + try { + if (result == null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + + " calling onError() on response " + response); + } + response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, + "null bundle returned"); + } else { + if (mStripAuthTokenFromResult) { + result.remove(AccountManager.KEY_AUTHTOKEN); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + + " calling onResult() on response " + response); + } + response.onResult(result); + } + } catch (RemoteException e) { + // if the caller is dead then there is no one to care about remote exceptions + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "failure while notifying response", e); + } + } + } + } + + public void onRequestContinued() { + mNumRequestContinued++; + } + + public void onError(int errorCode, String errorMessage) { + mNumErrors++; + IAccountManagerResponse response = getResponseAndClose(); + if (response != null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + + " calling onError() on response " + response); + } + try { + response.onError(errorCode, errorMessage); + } catch (RemoteException e) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Session.onError: caught RemoteException while responding", e); + } + } + } else { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Session.onError: already closed"); + } + } + } + + /** + * find the component name for the authenticator and initiate a bind + * if no authenticator or the bind fails then return false, otherwise return true + */ + private boolean bindToAuthenticator(String authenticatorType) { + AccountAuthenticatorCache.ServiceInfo authenticatorInfo = + mAuthenticatorCache.getServiceInfo( + AuthenticatorDescription.newKey(authenticatorType)); + if (authenticatorInfo == null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "there is no authenticator for " + authenticatorType + + ", bailing out"); + } + return false; + } + + Intent intent = new Intent(); + intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT); + intent.setComponent(authenticatorInfo.componentName); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName); + } + if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE, mAccounts.userId)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed"); + } + return false; + } + + + return true; + } + } + + private class MessageHandler extends Handler { + MessageHandler(Looper looper) { + super(looper); + } + + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_TIMED_OUT: + Session session = (Session)msg.obj; + session.onTimedOut(); + break; + + default: + throw new IllegalStateException("unhandled message: " + msg.what); + } + } + } + + private static String getDatabaseName(int userId) { + File systemDir = Environment.getSystemSecureDirectory(); + File databaseFile = new File(systemDir, "users/" + userId + "/" + DATABASE_NAME); + if (userId == 0) { + // Migrate old file, if it exists, to the new location. + // Make sure the new file doesn't already exist. A dummy file could have been + // accidentally created in the old location, causing the new one to become corrupted + // as well. + File oldFile = new File(systemDir, DATABASE_NAME); + if (oldFile.exists() && !databaseFile.exists()) { + // Check for use directory; create if it doesn't exist, else renameTo will fail + File userDir = new File(systemDir, "users/" + userId); + if (!userDir.exists()) { + if (!userDir.mkdirs()) { + throw new IllegalStateException("User dir cannot be created: " + userDir); + } + } + if (!oldFile.renameTo(databaseFile)) { + throw new IllegalStateException("User dir cannot be migrated: " + databaseFile); + } + } + } + return databaseFile.getPath(); + } + + static class DatabaseHelper extends SQLiteOpenHelper { + + public DatabaseHelper(Context context, int userId) { + super(context, AccountManagerService.getDatabaseName(userId), null, DATABASE_VERSION); + } + + /** + * This call needs to be made while the mCacheLock is held. The way to + * ensure this is to get the lock any time a method is called ont the DatabaseHelper + * @param db The database. + */ + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( " + + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + ACCOUNTS_NAME + " TEXT NOT NULL, " + + ACCOUNTS_TYPE + " TEXT NOT NULL, " + + ACCOUNTS_PASSWORD + " TEXT, " + + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); + + db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( " + + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, " + + AUTHTOKENS_TYPE + " TEXT NOT NULL, " + + AUTHTOKENS_AUTHTOKEN + " TEXT, " + + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))"); + + createGrantsTable(db); + + db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( " + + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + EXTRAS_ACCOUNTS_ID + " INTEGER, " + + EXTRAS_KEY + " TEXT NOT NULL, " + + EXTRAS_VALUE + " TEXT, " + + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))"); + + db.execSQL("CREATE TABLE " + TABLE_META + " ( " + + META_KEY + " TEXT PRIMARY KEY NOT NULL, " + + META_VALUE + " TEXT)"); + + createAccountsDeletionTrigger(db); + } + + private void createAccountsDeletionTrigger(SQLiteDatabase db) { + db.execSQL("" + + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS + + " BEGIN" + + " DELETE FROM " + TABLE_AUTHTOKENS + + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" + + " DELETE FROM " + TABLE_EXTRAS + + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" + + " DELETE FROM " + TABLE_GRANTS + + " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" + + " END"); + } + + private void createGrantsTable(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( " + + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, " + + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, " + + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, " + + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + + "," + GRANTS_GRANTEE_UID + "))"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion); + + if (oldVersion == 1) { + // no longer need to do anything since the work is done + // when upgrading from version 2 + oldVersion++; + } + + if (oldVersion == 2) { + createGrantsTable(db); + db.execSQL("DROP TRIGGER " + TABLE_ACCOUNTS + "Delete"); + createAccountsDeletionTrigger(db); + oldVersion++; + } + + if (oldVersion == 3) { + db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_TYPE + + " = 'com.google' WHERE " + ACCOUNTS_TYPE + " == 'com.google.GAIA'"); + oldVersion++; + } + } + + @Override + public void onOpen(SQLiteDatabase db) { + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME); + } + } + + public IBinder onBind(Intent intent) { + return asBinder(); + } + + /** + * Searches array of arguments for the specified string + * @param args array of argument strings + * @param value value to search for + * @return true if the value is contained in the array + */ + private static boolean scanArgs(String[] args, String value) { + if (args != null) { + for (String arg : args) { + if (value.equals(arg)) { + return true; + } + } + } + return false; + } + + protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + fout.println("Permission Denial: can't dump AccountsManager from from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " without permission " + android.Manifest.permission.DUMP); + return; + } + final boolean isCheckinRequest = scanArgs(args, "--checkin") || scanArgs(args, "-c"); + + fout = new IndentingPrintWriter(fout, " "); + int size = mUsers.size(); + for (int i = 0; i < size; i++) { + fout.println("User " + mUsers.keyAt(i) + ":"); + ((IndentingPrintWriter) fout).increaseIndent(); + dumpUser(mUsers.valueAt(i), fd, fout, args, isCheckinRequest); + ((IndentingPrintWriter) fout).decreaseIndent(); + if (i < size - 1) { + fout.println(); + } + } + } + + private void dumpUser(UserAccounts userAccounts, FileDescriptor fd, PrintWriter fout, + String[] args, boolean isCheckinRequest) { + synchronized (userAccounts.cacheLock) { + final SQLiteDatabase db = userAccounts.openHelper.getReadableDatabase(); + + if (isCheckinRequest) { + // This is a checkin request. *Only* upload the account types and the count of each. + Cursor cursor = db.query(TABLE_ACCOUNTS, ACCOUNT_TYPE_COUNT_PROJECTION, + null, null, ACCOUNTS_TYPE, null, null); + try { + while (cursor.moveToNext()) { + // print type,count + fout.println(cursor.getString(0) + "," + cursor.getString(1)); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + } else { + Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */); + fout.println("Accounts: " + accounts.length); + for (Account account : accounts) { + fout.println(" " + account); + } + + fout.println(); + synchronized (mSessions) { + final long now = SystemClock.elapsedRealtime(); + fout.println("Active Sessions: " + mSessions.size()); + for (Session session : mSessions.values()) { + fout.println(" " + session.toDebugString(now)); + } + } + + fout.println(); + mAuthenticatorCache.dump(fd, fout, args); + } + } + } + + private void doNotification(UserAccounts accounts, Account account, CharSequence message, + Intent intent) { + long identityToken = clearCallingIdentity(); + try { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "doNotification: " + message + " intent:" + intent); + } + + if (intent.getComponent() != null && + GrantCredentialsPermissionActivity.class.getName().equals( + intent.getComponent().getClassName())) { + createNoCredentialsPermissionNotification(account, intent); + } else { + final Integer notificationId = getSigninRequiredNotificationId(accounts, account); + intent.addCategory(String.valueOf(notificationId)); + Notification n = new Notification(android.R.drawable.stat_sys_warning, null, + 0 /* when */); + final String notificationTitleFormat = + mContext.getText(R.string.notification_title).toString(); + n.setLatestEventInfo(mContext, + String.format(notificationTitleFormat, account.name), + message, PendingIntent.getActivity( + mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)); + installNotification(notificationId, n); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + protected void installNotification(final int notificationId, final Notification n) { + ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) + .notify(notificationId, n); + } + + protected void cancelNotification(int id) { + long identityToken = clearCallingIdentity(); + try { + ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) + .cancel(id); + } finally { + restoreCallingIdentity(identityToken); + } + } + + /** Succeeds if any of the specified permissions are granted. */ + private void checkBinderPermission(String... permissions) { + final int uid = Binder.getCallingUid(); + + for (String perm : permissions) { + if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, " caller uid " + uid + " has " + perm); + } + return; + } + } + + String msg = "caller uid " + uid + " lacks any of " + TextUtils.join(",", permissions); + Log.w(TAG, " " + msg); + throw new SecurityException(msg); + } + + private boolean inSystemImage(int callerUid) { + String[] packages = mPackageManager.getPackagesForUid(callerUid); + for (String name : packages) { + try { + PackageInfo packageInfo = mPackageManager.getPackageInfo(name, 0 /* flags */); + if (packageInfo != null + && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + return false; + } + + private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) { + final boolean inSystemImage = inSystemImage(callerUid); + final boolean fromAuthenticator = account != null + && hasAuthenticatorUid(account.type, callerUid); + final boolean hasExplicitGrants = account != null + && hasExplicitlyGrantedPermission(account, authTokenType, callerUid); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid " + + callerUid + ", " + account + + ": is authenticator? " + fromAuthenticator + + ", has explicit permission? " + hasExplicitGrants); + } + return fromAuthenticator || hasExplicitGrants || inSystemImage; + } + + private boolean hasAuthenticatorUid(String accountType, int callingUid) { + for (RegisteredServicesCache.ServiceInfo serviceInfo : + mAuthenticatorCache.getAllServices()) { + if (serviceInfo.type.type.equals(accountType)) { + return (serviceInfo.uid == callingUid) || + (mPackageManager.checkSignatures(serviceInfo.uid, callingUid) + == PackageManager.SIGNATURE_MATCH); + } + } + return false; + } + + private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType, + int callerUid) { + if (callerUid == android.os.Process.SYSTEM_UID) { + return true; + } + UserAccounts accounts = getUserAccountsForCaller(); + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); + String[] args = { String.valueOf(callerUid), authTokenType, + account.name, account.type}; + final boolean permissionGranted = + DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args) != 0; + if (!permissionGranted && ActivityManager.isRunningInTestHarness()) { + // TODO: Skip this check when running automated tests. Replace this + // with a more general solution. + Log.d(TAG, "no credentials permission for usage of " + account + ", " + + authTokenType + " by uid " + callerUid + + " but ignoring since device is in test harness."); + return true; + } + return permissionGranted; + } + } + + private void checkCallingUidAgainstAuthenticator(Account account) { + final int uid = Binder.getCallingUid(); + if (account == null || !hasAuthenticatorUid(account.type, uid)) { + String msg = "caller uid " + uid + " is different than the authenticator's uid"; + Log.w(TAG, msg); + throw new SecurityException(msg); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "caller uid " + uid + " is the same as the authenticator's uid"); + } + } + + private void checkAuthenticateAccountsPermission(Account account) { + checkBinderPermission(Manifest.permission.AUTHENTICATE_ACCOUNTS); + checkCallingUidAgainstAuthenticator(account); + } + + private void checkReadAccountsPermission() { + checkBinderPermission(Manifest.permission.GET_ACCOUNTS); + } + + private void checkManageAccountsPermission() { + checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS); + } + + private void checkManageAccountsOrUseCredentialsPermissions() { + checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS, + Manifest.permission.USE_CREDENTIALS); + } + + public void updateAppPermission(Account account, String authTokenType, int uid, boolean value) + throws RemoteException { + final int callingUid = getCallingUid(); + + if (callingUid != android.os.Process.SYSTEM_UID) { + throw new SecurityException(); + } + + if (value) { + grantAppPermission(account, authTokenType, uid); + } else { + revokeAppPermission(account, authTokenType, uid); + } + } + + /** + * Allow callers with the given uid permission to get credentials for account/authTokenType. + *

+ * Although this is public it can only be accessed via the AccountManagerService object + * which is in the system. This means we don't need to protect it with permissions. + * @hide + */ + private void grantAppPermission(Account account, String authTokenType, int uid) { + if (account == null || authTokenType == null) { + Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception()); + return; + } + UserAccounts accounts = getUserAccounts(UserId.getUserId(uid)); + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.beginTransaction(); + try { + long accountId = getAccountIdLocked(db, account); + if (accountId >= 0) { + ContentValues values = new ContentValues(); + values.put(GRANTS_ACCOUNTS_ID, accountId); + values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType); + values.put(GRANTS_GRANTEE_UID, uid); + db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values); + db.setTransactionSuccessful(); + } + } finally { + db.endTransaction(); + } + cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid)); + } + } + + /** + * Don't allow callers with the given uid permission to get credentials for + * account/authTokenType. + *

+ * Although this is public it can only be accessed via the AccountManagerService object + * which is in the system. This means we don't need to protect it with permissions. + * @hide + */ + private void revokeAppPermission(Account account, String authTokenType, int uid) { + if (account == null || authTokenType == null) { + Log.e(TAG, "revokeAppPermission: called with invalid arguments", new Exception()); + return; + } + UserAccounts accounts = getUserAccounts(UserId.getUserId(uid)); + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.beginTransaction(); + try { + long accountId = getAccountIdLocked(db, account); + if (accountId >= 0) { + db.delete(TABLE_GRANTS, + GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND " + + GRANTS_GRANTEE_UID + "=?", + new String[]{String.valueOf(accountId), authTokenType, + String.valueOf(uid)}); + db.setTransactionSuccessful(); + } + } finally { + db.endTransaction(); + } + cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid)); + } + } + + static final private String stringArrayToString(String[] value) { + return value != null ? ("[" + TextUtils.join(",", value) + "]") : null; + } + + private void removeAccountFromCacheLocked(UserAccounts accounts, Account account) { + final Account[] oldAccountsForType = accounts.accountCache.get(account.type); + if (oldAccountsForType != null) { + ArrayList newAccountsList = new ArrayList(); + for (Account curAccount : oldAccountsForType) { + if (!curAccount.equals(account)) { + newAccountsList.add(curAccount); + } + } + if (newAccountsList.isEmpty()) { + accounts.accountCache.remove(account.type); + } else { + Account[] newAccountsForType = new Account[newAccountsList.size()]; + newAccountsForType = newAccountsList.toArray(newAccountsForType); + accounts.accountCache.put(account.type, newAccountsForType); + } + } + accounts.userDataCache.remove(account); + accounts.authTokenCache.remove(account); + } + + /** + * This assumes that the caller has already checked that the account is not already present. + */ + private void insertAccountIntoCacheLocked(UserAccounts accounts, Account account) { + Account[] accountsForType = accounts.accountCache.get(account.type); + int oldLength = (accountsForType != null) ? accountsForType.length : 0; + Account[] newAccountsForType = new Account[oldLength + 1]; + if (accountsForType != null) { + System.arraycopy(accountsForType, 0, newAccountsForType, 0, oldLength); + } + newAccountsForType[oldLength] = account; + accounts.accountCache.put(account.type, newAccountsForType); + } + + protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType) { + if (accountType != null) { + final Account[] accounts = userAccounts.accountCache.get(accountType); + if (accounts == null) { + return EMPTY_ACCOUNT_ARRAY; + } else { + return Arrays.copyOf(accounts, accounts.length); + } + } else { + int totalLength = 0; + for (Account[] accounts : userAccounts.accountCache.values()) { + totalLength += accounts.length; + } + if (totalLength == 0) { + return EMPTY_ACCOUNT_ARRAY; + } + Account[] accounts = new Account[totalLength]; + totalLength = 0; + for (Account[] accountsOfType : userAccounts.accountCache.values()) { + System.arraycopy(accountsOfType, 0, accounts, totalLength, + accountsOfType.length); + totalLength += accountsOfType.length; + } + return accounts; + } + } + + protected void writeUserDataIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db, + Account account, String key, String value) { + HashMap userDataForAccount = accounts.userDataCache.get(account); + if (userDataForAccount == null) { + userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account); + accounts.userDataCache.put(account, userDataForAccount); + } + if (value == null) { + userDataForAccount.remove(key); + } else { + userDataForAccount.put(key, value); + } + } + + protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db, + Account account, String key, String value) { + HashMap authTokensForAccount = accounts.authTokenCache.get(account); + if (authTokensForAccount == null) { + authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account); + accounts.authTokenCache.put(account, authTokensForAccount); + } + if (value == null) { + authTokensForAccount.remove(key); + } else { + authTokensForAccount.put(key, value); + } + } + + protected String readAuthTokenInternal(UserAccounts accounts, Account account, + String authTokenType) { + synchronized (accounts.cacheLock) { + HashMap authTokensForAccount = accounts.authTokenCache.get(account); + if (authTokensForAccount == null) { + // need to populate the cache for this account + final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); + authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account); + accounts.authTokenCache.put(account, authTokensForAccount); + } + return authTokensForAccount.get(authTokenType); + } + } + + protected String readUserDataInternal(UserAccounts accounts, Account account, String key) { + synchronized (accounts.cacheLock) { + HashMap userDataForAccount = accounts.userDataCache.get(account); + if (userDataForAccount == null) { + // need to populate the cache for this account + final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); + userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account); + accounts.userDataCache.put(account, userDataForAccount); + } + return userDataForAccount.get(key); + } + } + + protected HashMap readUserDataForAccountFromDatabaseLocked( + final SQLiteDatabase db, Account account) { + HashMap userDataForAccount = new HashMap(); + Cursor cursor = db.query(TABLE_EXTRAS, + COLUMNS_EXTRAS_KEY_AND_VALUE, + SELECTION_USERDATA_BY_ACCOUNT, + new String[]{account.name, account.type}, + null, null, null); + try { + while (cursor.moveToNext()) { + final String tmpkey = cursor.getString(0); + final String value = cursor.getString(1); + userDataForAccount.put(tmpkey, value); + } + } finally { + cursor.close(); + } + return userDataForAccount; + } + + protected HashMap readAuthTokensForAccountFromDatabaseLocked( + final SQLiteDatabase db, Account account) { + HashMap authTokensForAccount = new HashMap(); + Cursor cursor = db.query(TABLE_AUTHTOKENS, + COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN, + SELECTION_AUTHTOKENS_BY_ACCOUNT, + new String[]{account.name, account.type}, + null, null, null); + try { + while (cursor.moveToNext()) { + final String type = cursor.getString(0); + final String authToken = cursor.getString(1); + authTokensForAccount.put(type, authToken); + } + } finally { + cursor.close(); + } + return authTokensForAccount; + } +} diff --git a/src/frameworks/base/core/java/android/accounts/IAccountAuthenticatorCache.java b/src/frameworks/base/core/java/android/accounts/IAccountAuthenticatorCache.java new file mode 100644 index 00000000..618771f3 --- /dev/null +++ b/src/frameworks/base/core/java/android/accounts/IAccountAuthenticatorCache.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accounts; + +import android.content.pm.RegisteredServicesCache; +import android.content.pm.RegisteredServicesCacheListener; +import android.os.Handler; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Collection; + +/** + * An interface to the Authenticator specialization of RegisteredServicesCache. The use of + * this interface by the AccountManagerService makes it easier to unit test it. + * @hide + */ +public interface IAccountAuthenticatorCache { + /** + * Accessor for the {@link android.content.pm.RegisteredServicesCache.ServiceInfo} that + * matched the specified {@link android.accounts.AuthenticatorDescription} or null + * if none match. + * @param type the authenticator type to return + * @return the {@link android.content.pm.RegisteredServicesCache.ServiceInfo} that + * matches the account type or null if none is present + */ + RegisteredServicesCache.ServiceInfo getServiceInfo( + AuthenticatorDescription type); + + /** + * @return A copy of a Collection of all the current Authenticators. + */ + Collection> getAllServices(); + + /** + * Dumps the state of the cache. See + * {@link android.os.Binder#dump(java.io.FileDescriptor, java.io.PrintWriter, String[])} + */ + void dump(FileDescriptor fd, PrintWriter fout, String[] args); + + /** + * Sets a listener that will be notified whenever the authenticator set changes + * @param listener the listener to notify, or null + * @param handler the {@link Handler} on which the notification will be posted. If null + * the notification will be posted on the main thread. + */ + void setListener(RegisteredServicesCacheListener listener, + Handler handler); +} \ No newline at end of file diff --git a/src/frameworks/base/core/java/android/app/Activity.java b/src/frameworks/base/core/java/android/app/Activity.java new file mode 100644 index 00000000..f20fd337 --- /dev/null +++ b/src/frameworks/base/core/java/android/app/Activity.java @@ -0,0 +1,5200 @@ +/* + * 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 android.app; + +import com.android.internal.app.ActionBarImpl; +import com.android.internal.policy.PolicyManager; + +import android.content.ComponentCallbacks2; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.CursorLoader; +import android.content.IIntentSender; +import android.content.Intent; +import android.content.IntentSender; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.StrictMode; +import android.text.Selection; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.text.method.TextKeyListener; +import android.util.AttributeSet; +import android.util.EventLog; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.view.ActionMode; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.ContextThemeWrapper; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnCreateContextMenuListener; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.ViewManager; +import android.view.Window; +import android.view.WindowManager; +import android.view.WindowManagerImpl; +import android.view.accessibility.AccessibilityEvent; +import android.widget.AdapterView; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * An activity is a single, focused thing that the user can do. Almost all + * activities interact with the user, so the Activity class takes care of + * creating a window for you in which you can place your UI with + * {@link #setContentView}. While activities are often presented to the user + * as full-screen windows, they can also be used in other ways: as floating + * windows (via a theme with {@link android.R.attr#windowIsFloating} set) + * or embedded inside of another activity (using {@link ActivityGroup}). + * + * There are two methods almost all subclasses of Activity will implement: + * + *

    + *
  • {@link #onCreate} is where you initialize your activity. Most + * importantly, here you will usually call {@link #setContentView(int)} + * with a layout resource defining your UI, and using {@link #findViewById} + * to retrieve the widgets in that UI that you need to interact with + * programmatically. + * + *
  • {@link #onPause} is where you deal with the user leaving your + * activity. Most importantly, any changes made by the user should at this + * point be committed (usually to the + * {@link android.content.ContentProvider} holding the data). + *
+ * + *

To be of use with {@link android.content.Context#startActivity Context.startActivity()}, all + * activity classes must have a corresponding + * {@link android.R.styleable#AndroidManifestActivity <activity>} + * declaration in their package's AndroidManifest.xml.

+ * + *

Topics covered here: + *

    + *
  1. Fragments + *
  2. Activity Lifecycle + *
  3. Configuration Changes + *
  4. Starting Activities and Getting Results + *
  5. Saving Persistent State + *
  6. Permissions + *
  7. Process Lifecycle + *
+ * + *
+ *

Developer Guides

+ *

The Activity class is an important part of an application's overall lifecycle, + * and the way activities are launched and put together is a fundamental + * part of the platform's application model. For a detailed perspective on the structure of an + * Android application and how activities behave, please read the + * Application Fundamentals and + * Tasks and Back Stack + * developer guides.

+ * + *

You can also find a detailed discussion about how to create activities in the + * Activities + * developer guide.

+ *
+ * + * + *

Fragments

+ * + *

Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB}, Activity + * implementations can make use of the {@link Fragment} class to better + * modularize their code, build more sophisticated user interfaces for larger + * screens, and help scale their application between small and large screens. + * + * + *

Activity Lifecycle

+ * + *

Activities in the system are managed as an activity stack. + * When a new activity is started, it is placed on the top of the stack + * and becomes the running activity -- the previous activity always remains + * below it in the stack, and will not come to the foreground again until + * the new activity exits.

+ * + *

An activity has essentially four states:

+ *
    + *
  • If an activity in the foreground of the screen (at the top of + * the stack), + * it is active or running.
  • + *
  • If an activity has lost focus but is still visible (that is, a new non-full-sized + * or transparent activity has focus on top of your activity), it + * is paused. A paused activity is completely alive (it + * maintains all state and member information and remains attached to + * the window manager), but can be killed by the system in extreme + * low memory situations. + *
  • If an activity is completely obscured by another activity, + * it is stopped. It still retains all state and member information, + * however, it is no longer visible to the user so its window is hidden + * and it will often be killed by the system when memory is needed + * elsewhere.
  • + *
  • If an activity is paused or stopped, the system can drop the activity + * from memory by either asking it to finish, or simply killing its + * process. When it is displayed again to the user, it must be + * completely restarted and restored to its previous state.
  • + *
+ * + *

The following diagram shows the important state paths of an Activity. + * The square rectangles represent callback methods you can implement to + * perform operations when the Activity moves between states. The colored + * ovals are major states the Activity can be in.

+ * + *

State diagram for an Android Activity Lifecycle.

+ * + *

There are three key loops you may be interested in monitoring within your + * activity: + * + *

    + *
  • The entire lifetime of an activity happens between the first call + * to {@link android.app.Activity#onCreate} through to a single final call + * to {@link android.app.Activity#onDestroy}. An activity will do all setup + * of "global" state in onCreate(), and release all remaining resources in + * onDestroy(). For example, if it has a thread running in the background + * to download data from the network, it may create that thread in onCreate() + * and then stop the thread in onDestroy(). + * + *
  • The visible lifetime of an activity happens between a call to + * {@link android.app.Activity#onStart} until a corresponding call to + * {@link android.app.Activity#onStop}. During this time the user can see the + * activity on-screen, though it may not be in the foreground and interacting + * with the user. Between these two methods you can maintain resources that + * are needed to show the activity to the user. For example, you can register + * a {@link android.content.BroadcastReceiver} in onStart() to monitor for changes + * that impact your UI, and unregister it in onStop() when the user no + * longer sees what you are displaying. The onStart() and onStop() methods + * can be called multiple times, as the activity becomes visible and hidden + * to the user. + * + *
  • The foreground lifetime of an activity happens between a call to + * {@link android.app.Activity#onResume} until a corresponding call to + * {@link android.app.Activity#onPause}. During this time the activity is + * in front of all other activities and interacting with the user. An activity + * can frequently go between the resumed and paused states -- for example when + * the device goes to sleep, when an activity result is delivered, when a new + * intent is delivered -- so the code in these methods should be fairly + * lightweight. + *
+ * + *

The entire lifecycle of an activity is defined by the following + * Activity methods. All of these are hooks that you can override + * to do appropriate work when the activity changes state. All + * activities will implement {@link android.app.Activity#onCreate} + * to do their initial setup; many will also implement + * {@link android.app.Activity#onPause} to commit changes to data and + * otherwise prepare to stop interacting with the user. You should always + * call up to your superclass when implementing these methods.

+ * + *

+ *
+ * public class Activity extends ApplicationContext {
+ *     protected void onCreate(Bundle savedInstanceState);
+ *
+ *     protected void onStart();
+ *     
+ *     protected void onRestart();
+ *
+ *     protected void onResume();
+ *
+ *     protected void onPause();
+ *
+ *     protected void onStop();
+ *
+ *     protected void onDestroy();
+ * }
+ * 
+ * + *

In general the movement through an activity's lifecycle looks like + * this:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Method Description Killable? Next
{@link android.app.Activity#onCreate onCreate()}Called when the activity is first created. + * This is where you should do all of your normal static set up: + * create views, bind data to lists, etc. This method also + * provides you with a Bundle containing the activity's previously + * frozen state, if there was one. + *

Always followed by onStart().

NoonStart()
    {@link android.app.Activity#onRestart onRestart()}Called after your activity has been stopped, prior to it being + * started again. + *

Always followed by onStart()

NoonStart()
{@link android.app.Activity#onStart onStart()}Called when the activity is becoming visible to the user. + *

Followed by onResume() if the activity comes + * to the foreground, or onStop() if it becomes hidden.

NoonResume() or onStop()
    {@link android.app.Activity#onResume onResume()}Called when the activity will start + * interacting with the user. At this point your activity is at + * the top of the activity stack, with user input going to it. + *

Always followed by onPause().

NoonPause()
{@link android.app.Activity#onPause onPause()}Called when the system is about to start resuming a previous + * activity. This is typically used to commit unsaved changes to + * persistent data, stop animations and other things that may be consuming + * CPU, etc. Implementations of this method must be very quick because + * the next activity will not be resumed until this method returns. + *

Followed by either onResume() if the activity + * returns back to the front, or onStop() if it becomes + * invisible to the user.

Pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB}onResume() or
+ * onStop()
{@link android.app.Activity#onStop onStop()}Called when the activity is no longer visible to the user, because + * another activity has been resumed and is covering this one. This + * may happen either because a new activity is being started, an existing + * one is being brought in front of this one, or this one is being + * destroyed. + *

Followed by either onRestart() if + * this activity is coming back to interact with the user, or + * onDestroy() if this activity is going away.

YesonRestart() or
+ * onDestroy()
{@link android.app.Activity#onDestroy onDestroy()}The final call you receive before your + * activity is destroyed. This can happen either because the + * activity is finishing (someone called {@link Activity#finish} on + * it, or because the system is temporarily destroying this + * instance of the activity to save space. You can distinguish + * between these two scenarios with the {@link + * Activity#isFinishing} method.Yesnothing
+ * + *

Note the "Killable" column in the above table -- for those methods that + * are marked as being killable, after that method returns the process hosting the + * activity may killed by the system at any time without another line + * of its code being executed. Because of this, you should use the + * {@link #onPause} method to write any persistent data (such as user edits) + * to storage. In addition, the method + * {@link #onSaveInstanceState(Bundle)} is called before placing the activity + * in such a background state, allowing you to save away any dynamic instance + * state in your activity into the given Bundle, to be later received in + * {@link #onCreate} if the activity needs to be re-created. + * See the Process Lifecycle + * section for more information on how the lifecycle of a process is tied + * to the activities it is hosting. Note that it is important to save + * persistent data in {@link #onPause} instead of {@link #onSaveInstanceState} + * because the latter is not part of the lifecycle callbacks, so will not + * be called in every situation as described in its documentation.

+ * + *

Be aware that these semantics will change slightly between + * applications targeting platforms starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB} + * vs. those targeting prior platforms. Starting with Honeycomb, an application + * is not in the killable state until its {@link #onStop} has returned. This + * impacts when {@link #onSaveInstanceState(Bundle)} may be called (it may be + * safely called after {@link #onPause()} and allows and application to safely + * wait until {@link #onStop()} to save persistent state.

+ * + *

For those methods that are not marked as being killable, the activity's + * process will not be killed by the system starting from the time the method + * is called and continuing after it returns. Thus an activity is in the killable + * state, for example, between after onPause() to the start of + * onResume().

+ * + * + *

Configuration Changes

+ * + *

If the configuration of the device (as defined by the + * {@link Configuration Resources.Configuration} class) changes, + * then anything displaying a user interface will need to update to match that + * configuration. Because Activity is the primary mechanism for interacting + * with the user, it includes special support for handling configuration + * changes.

+ * + *

Unless you specify otherwise, a configuration change (such as a change + * in screen orientation, language, input devices, etc) will cause your + * current activity to be destroyed, going through the normal activity + * lifecycle process of {@link #onPause}, + * {@link #onStop}, and {@link #onDestroy} as appropriate. If the activity + * had been in the foreground or visible to the user, once {@link #onDestroy} is + * called in that instance then a new instance of the activity will be + * created, with whatever savedInstanceState the previous instance had generated + * from {@link #onSaveInstanceState}.

+ * + *

This is done because any application resource, + * including layout files, can change based on any configuration value. Thus + * the only safe way to handle a configuration change is to re-retrieve all + * resources, including layouts, drawables, and strings. Because activities + * must already know how to save their state and re-create themselves from + * that state, this is a convenient way to have an activity restart itself + * with a new configuration.

+ * + *

In some special cases, you may want to bypass restarting of your + * activity based on one or more types of configuration changes. This is + * done with the {@link android.R.attr#configChanges android:configChanges} + * attribute in its manifest. For any types of configuration changes you say + * that you handle there, you will receive a call to your current activity's + * {@link #onConfigurationChanged} method instead of being restarted. If + * a configuration change involves any that you do not handle, however, the + * activity will still be restarted and {@link #onConfigurationChanged} + * will not be called.

+ * + * + *

Starting Activities and Getting Results

+ * + *

The {@link android.app.Activity#startActivity} + * method is used to start a + * new activity, which will be placed at the top of the activity stack. It + * takes a single argument, an {@link android.content.Intent Intent}, + * which describes the activity + * to be executed.

+ * + *

Sometimes you want to get a result back from an activity when it + * ends. For example, you may start an activity that lets the user pick + * a person in a list of contacts; when it ends, it returns the person + * that was selected. To do this, you call the + * {@link android.app.Activity#startActivityForResult(Intent, int)} + * version with a second integer parameter identifying the call. The result + * will come back through your {@link android.app.Activity#onActivityResult} + * method.

+ * + *

When an activity exits, it can call + * {@link android.app.Activity#setResult(int)} + * to return data back to its parent. It must always supply a result code, + * which can be the standard results RESULT_CANCELED, RESULT_OK, or any + * custom values starting at RESULT_FIRST_USER. In addition, it can optionally + * return back an Intent containing any additional data it wants. All of this + * information appears back on the + * parent's Activity.onActivityResult(), along with the integer + * identifier it originally supplied.

+ * + *

If a child activity fails for any reason (such as crashing), the parent + * activity will receive a result with the code RESULT_CANCELED.

+ * + *
+ * public class MyActivity extends Activity {
+ *     ...
+ *
+ *     static final int PICK_CONTACT_REQUEST = 0;
+ *
+ *     protected boolean onKeyDown(int keyCode, KeyEvent event) {
+ *         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ *             // When the user center presses, let them pick a contact.
+ *             startActivityForResult(
+ *                 new Intent(Intent.ACTION_PICK,
+ *                 new Uri("content://contacts")),
+ *                 PICK_CONTACT_REQUEST);
+ *            return true;
+ *         }
+ *         return false;
+ *     }
+ *
+ *     protected void onActivityResult(int requestCode, int resultCode,
+ *             Intent data) {
+ *         if (requestCode == PICK_CONTACT_REQUEST) {
+ *             if (resultCode == RESULT_OK) {
+ *                 // A contact was picked.  Here we will just display it
+ *                 // to the user.
+ *                 startActivity(new Intent(Intent.ACTION_VIEW, data));
+ *             }
+ *         }
+ *     }
+ * }
+ * 
+ * + * + *

Saving Persistent State

+ * + *

There are generally two kinds of persistent state than an activity + * will deal with: shared document-like data (typically stored in a SQLite + * database using a {@linkplain android.content.ContentProvider content provider}) + * and internal state such as user preferences.

+ * + *

For content provider data, we suggest that activities use a + * "edit in place" user model. That is, any edits a user makes are effectively + * made immediately without requiring an additional confirmation step. + * Supporting this model is generally a simple matter of following two rules:

+ * + *
    + *
  • When creating a new document, the backing database entry or file for + * it is created immediately. For example, if the user chooses to write + * a new e-mail, a new entry for that e-mail is created as soon as they + * start entering data, so that if they go to any other activity after + * that point this e-mail will now appear in the list of drafts.

    + *
  • When an activity's onPause() method is called, it should + * commit to the backing content provider or file any changes the user + * has made. This ensures that those changes will be seen by any other + * activity that is about to run. You will probably want to commit + * your data even more aggressively at key times during your + * activity's lifecycle: for example before starting a new + * activity, before finishing your own activity, when the user + * switches between input fields, etc.

    + *
+ * + *

This model is designed to prevent data loss when a user is navigating + * between activities, and allows the system to safely kill an activity (because + * system resources are needed somewhere else) at any time after it has been + * paused. Note this implies + * that the user pressing BACK from your activity does not + * mean "cancel" -- it means to leave the activity with its current contents + * saved away. Canceling edits in an activity must be provided through + * some other mechanism, such as an explicit "revert" or "undo" option.

+ * + *

See the {@linkplain android.content.ContentProvider content package} for + * more information about content providers. These are a key aspect of how + * different activities invoke and propagate data between themselves.

+ * + *

The Activity class also provides an API for managing internal persistent state + * associated with an activity. This can be used, for example, to remember + * the user's preferred initial display in a calendar (day view or week view) + * or the user's default home page in a web browser.

+ * + *

Activity persistent state is managed + * with the method {@link #getPreferences}, + * allowing you to retrieve and + * modify a set of name/value pairs associated with the activity. To use + * preferences that are shared across multiple application components + * (activities, receivers, services, providers), you can use the underlying + * {@link Context#getSharedPreferences Context.getSharedPreferences()} method + * to retrieve a preferences + * object stored under a specific name. + * (Note that it is not possible to share settings data across application + * packages -- for that you will need a content provider.)

+ * + *

Here is an excerpt from a calendar activity that stores the user's + * preferred view mode in its persistent settings:

+ * + *
+ * public class CalendarActivity extends Activity {
+ *     ...
+ *
+ *     static final int DAY_VIEW_MODE = 0;
+ *     static final int WEEK_VIEW_MODE = 1;
+ *
+ *     private SharedPreferences mPrefs;
+ *     private int mCurViewMode;
+ *
+ *     protected void onCreate(Bundle savedInstanceState) {
+ *         super.onCreate(savedInstanceState);
+ *
+ *         SharedPreferences mPrefs = getSharedPreferences();
+ *         mCurViewMode = mPrefs.getInt("view_mode", DAY_VIEW_MODE);
+ *     }
+ *
+ *     protected void onPause() {
+ *         super.onPause();
+ * 
+ *         SharedPreferences.Editor ed = mPrefs.edit();
+ *         ed.putInt("view_mode", mCurViewMode);
+ *         ed.commit();
+ *     }
+ * }
+ * 
+ * + * + *

Permissions

+ * + *

The ability to start a particular Activity can be enforced when it is + * declared in its + * manifest's {@link android.R.styleable#AndroidManifestActivity <activity>} + * tag. By doing so, other applications will need to declare a corresponding + * {@link android.R.styleable#AndroidManifestUsesPermission <uses-permission>} + * element in their own manifest to be able to start that activity. + * + *

When starting an Activity you can set {@link Intent#FLAG_GRANT_READ_URI_PERMISSION + * Intent.FLAG_GRANT_READ_URI_PERMISSION} and/or {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION + * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} on the Intent. This will grant the + * Activity access to the specific URIs in the Intent. Access will remain + * until the Activity has finished (it will remain across the hosting + * process being killed and other temporary destruction). As of + * {@link android.os.Build.VERSION_CODES#GINGERBREAD}, if the Activity + * was already created and a new Intent is being delivered to + * {@link #onNewIntent(Intent)}, any newly granted URI permissions will be added + * to the existing ones it holds. + * + *

See the Security and Permissions + * document for more information on permissions and security in general. + * + * + *

Process Lifecycle

+ * + *

The Android system attempts to keep application process around for as + * long as possible, but eventually will need to remove old processes when + * memory runs low. As described in Activity + * Lifecycle, the decision about which process to remove is intimately + * tied to the state of the user's interaction with it. In general, there + * are four states a process can be in based on the activities running in it, + * listed here in order of importance. The system will kill less important + * processes (the last ones) before it resorts to killing more important + * processes (the first ones). + * + *

    + *
  1. The foreground activity (the activity at the top of the screen + * that the user is currently interacting with) is considered the most important. + * Its process will only be killed as a last resort, if it uses more memory + * than is available on the device. Generally at this point the device has + * reached a memory paging state, so this is required in order to keep the user + * interface responsive. + *

  2. A visible activity (an activity that is visible to the user + * but not in the foreground, such as one sitting behind a foreground dialog) + * is considered extremely important and will not be killed unless that is + * required to keep the foreground activity running. + *

  3. A background activity (an activity that is not visible to + * the user and has been paused) is no longer critical, so the system may + * safely kill its process to reclaim memory for other foreground or + * visible processes. If its process needs to be killed, when the user navigates + * back to the activity (making it visible on the screen again), its + * {@link #onCreate} method will be called with the savedInstanceState it had previously + * supplied in {@link #onSaveInstanceState} so that it can restart itself in the same + * state as the user last left it. + *

  4. An empty process is one hosting no activities or other + * application components (such as {@link Service} or + * {@link android.content.BroadcastReceiver} classes). These are killed very + * quickly by the system as memory becomes low. For this reason, any + * background operation you do outside of an activity must be executed in the + * context of an activity BroadcastReceiver or Service to ensure that the system + * knows it needs to keep your process around. + *

+ * + *

Sometimes an Activity may need to do a long-running operation that exists + * independently of the activity lifecycle itself. An example may be a camera + * application that allows you to upload a picture to a web site. The upload + * may take a long time, and the application should allow the user to leave + * the application will it is executing. To accomplish this, your Activity + * should start a {@link Service} in which the upload takes place. This allows + * the system to properly prioritize your process (considering it to be more + * important than other non-visible applications) for the duration of the + * upload, independent of whether the original activity is paused, stopped, + * or finished. + */ +public class Activity extends ContextThemeWrapper + implements LayoutInflater.Factory2, + Window.Callback, KeyEvent.Callback, + OnCreateContextMenuListener, ComponentCallbacks2 { + private static final String TAG = "Activity"; + private static final boolean DEBUG_LIFECYCLE = false; + + /** Standard activity result: operation canceled. */ + public static final int RESULT_CANCELED = 0; + /** Standard activity result: operation succeeded. */ + public static final int RESULT_OK = -1; + /** Start of user-defined activity results. */ + public static final int RESULT_FIRST_USER = 1; + + private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState"; + private static final String FRAGMENTS_TAG = "android:fragments"; + private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds"; + private static final String SAVED_DIALOGS_TAG = "android:savedDialogs"; + private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_"; + private static final String SAVED_DIALOG_ARGS_KEY_PREFIX = "android:dialog_args_"; + + private static class ManagedDialog { + Dialog mDialog; + Bundle mArgs; + } + private SparseArray mManagedDialogs; + + // set by the thread after the constructor and before onCreate(Bundle savedInstanceState) is called. + private Instrumentation mInstrumentation; + private IBinder mToken; + private int mIdent; + /*package*/ String mEmbeddedID; + private Application mApplication; + /*package*/ Intent mIntent; + private ComponentName mComponent; + /*package*/ ActivityInfo mActivityInfo; + /*package*/ ActivityThread mMainThread; + Activity mParent; + boolean mCalled; + boolean mCheckedForLoaderManager; + boolean mLoadersStarted; + /*package*/ boolean mResumed; + private boolean mStopped; + boolean mFinished; + boolean mStartedActivity; + /** true if the activity is going through a transient pause */ + /*package*/ boolean mTemporaryPause = false; + /** true if the activity is being destroyed in order to recreate it with a new configuration */ + /*package*/ boolean mChangingConfigurations = false; + /*package*/ int mConfigChangeFlags; + /*package*/ Configuration mCurrentConfig; + private SearchManager mSearchManager; + private MenuInflater mMenuInflater; + + static final class NonConfigurationInstances { + Object activity; + HashMap children; + ArrayList fragments; + SparseArray loaders; + } + /* package */ NonConfigurationInstances mLastNonConfigurationInstances; + + private Window mWindow; + + private WindowManager mWindowManager; + /*package*/ View mDecor = null; + /*package*/ boolean mWindowAdded = false; + /*package*/ boolean mVisibleFromServer = false; + /*package*/ boolean mVisibleFromClient = true; + /*package*/ ActionBarImpl mActionBar = null; + private boolean mEnableDefaultActionBarUp; + + private CharSequence mTitle; + private int mTitleColor = 0; + + final FragmentManagerImpl mFragments = new FragmentManagerImpl(); + + SparseArray mAllLoaderManagers; + LoaderManagerImpl mLoaderManager; + + private static final class ManagedCursor { + ManagedCursor(Cursor cursor) { + mCursor = cursor; + mReleased = false; + mUpdated = false; + } + + private final Cursor mCursor; + private boolean mReleased; + private boolean mUpdated; + } + private final ArrayList mManagedCursors = + new ArrayList(); + + // protected by synchronized (this) + int mResultCode = RESULT_CANCELED; + Intent mResultData = null; + + private boolean mTitleReady = false; + + private int mDefaultKeyMode = DEFAULT_KEYS_DISABLE; + private SpannableStringBuilder mDefaultKeySsb = null; + + protected static final int[] FOCUSED_STATE_SET = {com.android.internal.R.attr.state_focused}; + + private final Object mInstanceTracker = StrictMode.trackActivity(this); + + private Thread mUiThread; + final Handler mHandler = new Handler(); + + /** Return the intent that started this activity. */ + public Intent getIntent() { + return mIntent; + } + + /** + * Change the intent returned by {@link #getIntent}. This holds a + * reference to the given intent; it does not copy it. Often used in + * conjunction with {@link #onNewIntent}. + * + * @param newIntent The new Intent object to return from getIntent + * + * @see #getIntent + * @see #onNewIntent + */ + public void setIntent(Intent newIntent) { + mIntent = newIntent; + } + + /** Return the application that owns this activity. */ + public final Application getApplication() { + return mApplication; + } + + /** Is this activity embedded inside of another activity? */ + public final boolean isChild() { + return mParent != null; + } + + /** Return the parent activity if this view is an embedded child. */ + public final Activity getParent() { + return mParent; + } + + /** Retrieve the window manager for showing custom windows. */ + public WindowManager getWindowManager() { + return mWindowManager; + } + + /** + * Retrieve the current {@link android.view.Window} for the activity. + * This can be used to directly access parts of the Window API that + * are not available through Activity/Screen. + * + * @return Window The current window, or null if the activity is not + * visual. + */ + public Window getWindow() { + return mWindow; + } + + /** + * Return the LoaderManager for this fragment, creating it if needed. + */ + public LoaderManager getLoaderManager() { + if (mLoaderManager != null) { + return mLoaderManager; + } + mCheckedForLoaderManager = true; + mLoaderManager = getLoaderManager(-1, mLoadersStarted, true); + return mLoaderManager; + } + + LoaderManagerImpl getLoaderManager(int index, boolean started, boolean create) { + if (mAllLoaderManagers == null) { + mAllLoaderManagers = new SparseArray(); + } + LoaderManagerImpl lm = mAllLoaderManagers.get(index); + if (lm == null) { + if (create) { + lm = new LoaderManagerImpl(this, started); + mAllLoaderManagers.put(index, lm); + } + } else { + lm.updateActivity(this); + } + return lm; + } + + /** + * Calls {@link android.view.Window#getCurrentFocus} on the + * Window of this Activity to return the currently focused view. + * + * @return View The current View with focus or null. + * + * @see #getWindow + * @see android.view.Window#getCurrentFocus + */ + public View getCurrentFocus() { + return mWindow != null ? mWindow.getCurrentFocus() : null; + } + + /** + * Called when the activity is starting. This is where most initialization + * should go: calling {@link #setContentView(int)} to inflate the + * activity's UI, using {@link #findViewById} to programmatically interact + * with widgets in the UI, calling + * {@link #managedQuery(android.net.Uri , String[], String, String[], String)} to retrieve + * cursors for data being displayed, etc. + * + *

You can call {@link #finish} from within this function, in + * which case onDestroy() will be immediately called without any of the rest + * of the activity lifecycle ({@link #onStart}, {@link #onResume}, + * {@link #onPause}, etc) executing. + * + *

Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.

+ * + * @param savedInstanceState If the activity is being re-initialized after + * previously being shut down then this Bundle contains the data it most + * recently supplied in {@link #onSaveInstanceState}. Note: Otherwise it is null. + * + * @see #onStart + * @see #onSaveInstanceState + * @see #onRestoreInstanceState + * @see #onPostCreate + */ + protected void onCreate(Bundle savedInstanceState) { + if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState); + if (mLastNonConfigurationInstances != null) { + mAllLoaderManagers = mLastNonConfigurationInstances.loaders; + } + if (mActivityInfo.parentActivityName != null) { + if (mActionBar == null) { + mEnableDefaultActionBarUp = true; + } else { + mActionBar.setDefaultDisplayHomeAsUpEnabled(true); + } + } + if (savedInstanceState != null) { + Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); + mFragments.restoreAllState(p, mLastNonConfigurationInstances != null + ? mLastNonConfigurationInstances.fragments : null); + } + mFragments.dispatchCreate(); + getApplication().dispatchActivityCreated(this, savedInstanceState); + mCalled = true; + } + + /** + * The hook for {@link ActivityThread} to restore the state of this activity. + * + * Calls {@link #onSaveInstanceState(android.os.Bundle)} and + * {@link #restoreManagedDialogs(android.os.Bundle)}. + * + * @param savedInstanceState contains the saved state + */ + final void performRestoreInstanceState(Bundle savedInstanceState) { + onRestoreInstanceState(savedInstanceState); + restoreManagedDialogs(savedInstanceState); + } + + /** + * This method is called after {@link #onStart} when the activity is + * being re-initialized from a previously saved state, given here in + * savedInstanceState. Most implementations will simply use {@link #onCreate} + * to restore their state, but it is sometimes convenient to do it here + * after all of the initialization has been done or to allow subclasses to + * decide whether to use your default implementation. The default + * implementation of this method performs a restore of any view state that + * had previously been frozen by {@link #onSaveInstanceState}. + * + *

This method is called between {@link #onStart} and + * {@link #onPostCreate}. + * + * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}. + * + * @see #onCreate + * @see #onPostCreate + * @see #onResume + * @see #onSaveInstanceState + */ + protected void onRestoreInstanceState(Bundle savedInstanceState) { + if (mWindow != null) { + Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG); + if (windowState != null) { + mWindow.restoreHierarchyState(windowState); + } + } + } + + /** + * Restore the state of any saved managed dialogs. + * + * @param savedInstanceState The bundle to restore from. + */ + private void restoreManagedDialogs(Bundle savedInstanceState) { + final Bundle b = savedInstanceState.getBundle(SAVED_DIALOGS_TAG); + if (b == null) { + return; + } + + final int[] ids = b.getIntArray(SAVED_DIALOG_IDS_KEY); + final int numDialogs = ids.length; + mManagedDialogs = new SparseArray(numDialogs); + for (int i = 0; i < numDialogs; i++) { + final Integer dialogId = ids[i]; + Bundle dialogState = b.getBundle(savedDialogKeyFor(dialogId)); + if (dialogState != null) { + // Calling onRestoreInstanceState() below will invoke dispatchOnCreate + // so tell createDialog() not to do it, otherwise we get an exception + final ManagedDialog md = new ManagedDialog(); + md.mArgs = b.getBundle(savedDialogArgsKeyFor(dialogId)); + md.mDialog = createDialog(dialogId, dialogState, md.mArgs); + if (md.mDialog != null) { + mManagedDialogs.put(dialogId, md); + onPrepareDialog(dialogId, md.mDialog, md.mArgs); + md.mDialog.onRestoreInstanceState(dialogState); + } + } + } + } + + private Dialog createDialog(Integer dialogId, Bundle state, Bundle args) { + final Dialog dialog = onCreateDialog(dialogId, args); + if (dialog == null) { + return null; + } + dialog.dispatchOnCreate(state); + return dialog; + } + + private static String savedDialogKeyFor(int key) { + return SAVED_DIALOG_KEY_PREFIX + key; + } + + private static String savedDialogArgsKeyFor(int key) { + return SAVED_DIALOG_ARGS_KEY_PREFIX + key; + } + + /** + * Called when activity start-up is complete (after {@link #onStart} + * and {@link #onRestoreInstanceState} have been called). Applications will + * generally not implement this method; it is intended for system + * classes to do final initialization after application code has run. + * + *

Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.

+ * + * @param savedInstanceState If the activity is being re-initialized after + * previously being shut down then this Bundle contains the data it most + * recently supplied in {@link #onSaveInstanceState}. Note: Otherwise it is null. + * @see #onCreate + */ + protected void onPostCreate(Bundle savedInstanceState) { + if (!isChild()) { + mTitleReady = true; + onTitleChanged(getTitle(), getTitleColor()); + } + mCalled = true; + } + + /** + * Called after {@link #onCreate} — or after {@link #onRestart} when + * the activity had been stopped, but is now again being displayed to the + * user. It will be followed by {@link #onResume}. + * + *

Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.

+ * + * @see #onCreate + * @see #onStop + * @see #onResume + */ + protected void onStart() { + if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this); + mCalled = true; + + if (!mLoadersStarted) { + mLoadersStarted = true; + if (mLoaderManager != null) { + mLoaderManager.doStart(); + } else if (!mCheckedForLoaderManager) { + mLoaderManager = getLoaderManager(-1, mLoadersStarted, false); + } + mCheckedForLoaderManager = true; + } + + getApplication().dispatchActivityStarted(this); + } + + /** + * Called after {@link #onStop} when the current activity is being + * re-displayed to the user (the user has navigated back to it). It will + * be followed by {@link #onStart} and then {@link #onResume}. + * + *

For activities that are using raw {@link Cursor} objects (instead of + * creating them through + * {@link #managedQuery(android.net.Uri , String[], String, String[], String)}, + * this is usually the place + * where the cursor should be requeried (because you had deactivated it in + * {@link #onStop}. + * + *

Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.

+ * + * @see #onStop + * @see #onStart + * @see #onResume + */ + protected void onRestart() { + mCalled = true; + } + + /** + * Called after {@link #onRestoreInstanceState}, {@link #onRestart}, or + * {@link #onPause}, for your activity to start interacting with the user. + * This is a good place to begin animations, open exclusive-access devices + * (such as the camera), etc. + * + *

Keep in mind that onResume is not the best indicator that your activity + * is visible to the user; a system window such as the keyguard may be in + * front. Use {@link #onWindowFocusChanged} to know for certain that your + * activity is visible to the user (for example, to resume a game). + * + *

Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.

+ * + * @see #onRestoreInstanceState + * @see #onRestart + * @see #onPostResume + * @see #onPause + */ + protected void onResume() { + if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this); + getApplication().dispatchActivityResumed(this); + mCalled = true; + } + + /** + * Called when activity resume is complete (after {@link #onResume} has + * been called). Applications will generally not implement this method; + * it is intended for system classes to do final setup after application + * resume code has run. + * + *

Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.

+ * + * @see #onResume + */ + protected void onPostResume() { + final Window win = getWindow(); + if (win != null) win.makeActive(); + if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true); + mCalled = true; + } + + /** + * This is called for activities that set launchMode to "singleTop" in + * their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} + * flag when calling {@link #startActivity}. In either case, when the + * activity is re-launched while at the top of the activity stack instead + * of a new instance of the activity being started, onNewIntent() will be + * called on the existing instance with the Intent that was used to + * re-launch it. + * + *

An activity will always be paused before receiving a new intent, so + * you can count on {@link #onResume} being called after this method. + * + *

Note that {@link #getIntent} still returns the original Intent. You + * can use {@link #setIntent} to update it to this new Intent. + * + * @param intent The new intent that was started for the activity. + * + * @see #getIntent + * @see #setIntent + * @see #onResume + */ + protected void onNewIntent(Intent intent) { + } + + /** + * The hook for {@link ActivityThread} to save the state of this activity. + * + * Calls {@link #onSaveInstanceState(android.os.Bundle)} + * and {@link #saveManagedDialogs(android.os.Bundle)}. + * + * @param outState The bundle to save the state to. + */ + final void performSaveInstanceState(Bundle outState) { + onSaveInstanceState(outState); + saveManagedDialogs(outState); + if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState); + } + + /** + * Called to retrieve per-instance state from an activity before being killed + * so that the state can be restored in {@link #onCreate} or + * {@link #onRestoreInstanceState} (the {@link Bundle} populated by this method + * will be passed to both). + * + *

This method is called before an activity may be killed so that when it + * comes back some time in the future it can restore its state. For example, + * if activity B is launched in front of activity A, and at some point activity + * A is killed to reclaim resources, activity A will have a chance to save the + * current state of its user interface via this method so that when the user + * returns to activity A, the state of the user interface can be restored + * via {@link #onCreate} or {@link #onRestoreInstanceState}. + * + *

Do not confuse this method with activity lifecycle callbacks such as + * {@link #onPause}, which is always called when an activity is being placed + * in the background or on its way to destruction, or {@link #onStop} which + * is called before destruction. One example of when {@link #onPause} and + * {@link #onStop} is called and not this method is when a user navigates back + * from activity B to activity A: there is no need to call {@link #onSaveInstanceState} + * on B because that particular instance will never be restored, so the + * system avoids calling it. An example when {@link #onPause} is called and + * not {@link #onSaveInstanceState} is when activity B is launched in front of activity A: + * the system may avoid calling {@link #onSaveInstanceState} on activity A if it isn't + * killed during the lifetime of B since the state of the user interface of + * A will stay intact. + * + *

The default implementation takes care of most of the UI per-instance + * state for you by calling {@link android.view.View#onSaveInstanceState()} on each + * view in the hierarchy that has an id, and by saving the id of the currently + * focused view (all of which is restored by the default implementation of + * {@link #onRestoreInstanceState}). If you override this method to save additional + * information not captured by each individual view, you will likely want to + * call through to the default implementation, otherwise be prepared to save + * all of the state of each view yourself. + * + *

If called, this method will occur before {@link #onStop}. There are + * no guarantees about whether it will occur before or after {@link #onPause}. + * + * @param outState Bundle in which to place your saved state. + * + * @see #onCreate + * @see #onRestoreInstanceState + * @see #onPause + */ + protected void onSaveInstanceState(Bundle outState) { + outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState()); + Parcelable p = mFragments.saveAllState(); + if (p != null) { + outState.putParcelable(FRAGMENTS_TAG, p); + } + getApplication().dispatchActivitySaveInstanceState(this, outState); + } + + /** + * Save the state of any managed dialogs. + * + * @param outState place to store the saved state. + */ + private void saveManagedDialogs(Bundle outState) { + if (mManagedDialogs == null) { + return; + } + + final int numDialogs = mManagedDialogs.size(); + if (numDialogs == 0) { + return; + } + + Bundle dialogState = new Bundle(); + + int[] ids = new int[mManagedDialogs.size()]; + + // save each dialog's bundle, gather the ids + for (int i = 0; i < numDialogs; i++) { + final int key = mManagedDialogs.keyAt(i); + ids[i] = key; + final ManagedDialog md = mManagedDialogs.valueAt(i); + dialogState.putBundle(savedDialogKeyFor(key), md.mDialog.onSaveInstanceState()); + if (md.mArgs != null) { + dialogState.putBundle(savedDialogArgsKeyFor(key), md.mArgs); + } + } + + dialogState.putIntArray(SAVED_DIALOG_IDS_KEY, ids); + outState.putBundle(SAVED_DIALOGS_TAG, dialogState); + } + + + /** + * Called as part of the activity lifecycle when an activity is going into + * the background, but has not (yet) been killed. The counterpart to + * {@link #onResume}. + * + *

When activity B is launched in front of activity A, this callback will + * be invoked on A. B will not be created until A's {@link #onPause} returns, + * so be sure to not do anything lengthy here. + * + *

This callback is mostly used for saving any persistent state the + * activity is editing, to present a "edit in place" model to the user and + * making sure nothing is lost if there are not enough resources to start + * the new activity without first killing this one. This is also a good + * place to do things like stop animations and other things that consume a + * noticeable amount of CPU in order to make the switch to the next activity + * as fast as possible, or to close resources that are exclusive access + * such as the camera. + * + *

In situations where the system needs more memory it may kill paused + * processes to reclaim resources. Because of this, you should be sure + * that all of your state is saved by the time you return from + * this function. In general {@link #onSaveInstanceState} is used to save + * per-instance state in the activity and this method is used to store + * global persistent data (in content providers, files, etc.) + * + *

After receiving this call you will usually receive a following call + * to {@link #onStop} (after the next activity has been resumed and + * displayed), however in some cases there will be a direct call back to + * {@link #onResume} without going through the stopped state. + * + *

Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.

+ * + * @see #onResume + * @see #onSaveInstanceState + * @see #onStop + */ + protected void onPause() { + if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this); + getApplication().dispatchActivityPaused(this); + mCalled = true; + } + + /** + * Called as part of the activity lifecycle when an activity is about to go + * into the background as the result of user choice. For example, when the + * user presses the Home key, {@link #onUserLeaveHint} will be called, but + * when an incoming phone call causes the in-call Activity to be automatically + * brought to the foreground, {@link #onUserLeaveHint} will not be called on + * the activity being interrupted. In cases when it is invoked, this method + * is called right before the activity's {@link #onPause} callback. + * + *

This callback and {@link #onUserInteraction} are intended to help + * activities manage status bar notifications intelligently; specifically, + * for helping activities determine the proper time to cancel a notfication. + * + * @see #onUserInteraction() + */ + protected void onUserLeaveHint() { + } + + /** + * Generate a new thumbnail for this activity. This method is called before + * pausing the activity, and should draw into outBitmap the + * imagery for the desired thumbnail in the dimensions of that bitmap. It + * can use the given canvas, which is configured to draw into the + * bitmap, for rendering if desired. + * + *

The default implementation returns fails and does not draw a thumbnail; + * this will result in the platform creating its own thumbnail if needed. + * + * @param outBitmap The bitmap to contain the thumbnail. + * @param canvas Can be used to render into the bitmap. + * + * @return Return true if you have drawn into the bitmap; otherwise after + * you return it will be filled with a default thumbnail. + * + * @see #onCreateDescription + * @see #onSaveInstanceState + * @see #onPause + */ + public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) { + return false; + } + + /** + * Generate a new description for this activity. This method is called + * before pausing the activity and can, if desired, return some textual + * description of its current state to be displayed to the user. + * + *

The default implementation returns null, which will cause you to + * inherit the description from the previous activity. If all activities + * return null, generally the label of the top activity will be used as the + * description. + * + * @return A description of what the user is doing. It should be short and + * sweet (only a few words). + * + * @see #onCreateThumbnail + * @see #onSaveInstanceState + * @see #onPause + */ + public CharSequence onCreateDescription() { + return null; + } + + /** + * Called when you are no longer visible to the user. You will next + * receive either {@link #onRestart}, {@link #onDestroy}, or nothing, + * depending on later user activity. + * + *

Note that this method may never be called, in low memory situations + * where the system does not have enough memory to keep your activity's + * process running after its {@link #onPause} method is called. + * + *

Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.

+ * + * @see #onRestart + * @see #onResume + * @see #onSaveInstanceState + * @see #onDestroy + */ + protected void onStop() { + if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this); + if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false); + getApplication().dispatchActivityStopped(this); + mCalled = true; + } + + /** + * Perform any final cleanup before an activity is destroyed. This can + * happen either because the activity is finishing (someone called + * {@link #finish} on it, or because the system is temporarily destroying + * this instance of the activity to save space. You can distinguish + * between these two scenarios with the {@link #isFinishing} method. + * + *

Note: do not count on this method being called as a place for + * saving data! For example, if an activity is editing data in a content + * provider, those edits should be committed in either {@link #onPause} or + * {@link #onSaveInstanceState}, not here. This method is usually implemented to + * free resources like threads that are associated with an activity, so + * that a destroyed activity does not leave such things around while the + * rest of its application is still running. There are situations where + * the system will simply kill the activity's hosting process without + * calling this method (or any others) in it, so it should not be used to + * do things that are intended to remain around after the process goes + * away. + * + *

Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.

+ * + * @see #onPause + * @see #onStop + * @see #finish + * @see #isFinishing + */ + protected void onDestroy() { + if (DEBUG_LIFECYCLE) Slog.v(TAG, "onDestroy " + this); + mCalled = true; + + // dismiss any dialogs we are managing. + if (mManagedDialogs != null) { + final int numDialogs = mManagedDialogs.size(); + for (int i = 0; i < numDialogs; i++) { + final ManagedDialog md = mManagedDialogs.valueAt(i); + if (md.mDialog.isShowing()) { + md.mDialog.dismiss(); + } + } + mManagedDialogs = null; + } + + // close any cursors we are managing. + synchronized (mManagedCursors) { + int numCursors = mManagedCursors.size(); + for (int i = 0; i < numCursors; i++) { + ManagedCursor c = mManagedCursors.get(i); + if (c != null) { + c.mCursor.close(); + } + } + mManagedCursors.clear(); + } + + // Close any open search dialog + if (mSearchManager != null) { + mSearchManager.stopSearch(); + } + + getApplication().dispatchActivityDestroyed(this); + } + + /** + * Called by the system when the device configuration changes while your + * activity is running. Note that this will only be called if + * you have selected configurations you would like to handle with the + * {@link android.R.attr#configChanges} attribute in your manifest. If + * any configuration change occurs that is not selected to be reported + * by that attribute, then instead of reporting it the system will stop + * and restart the activity (to have it launched with the new + * configuration). + * + *

At the time that this function has been called, your Resources + * object will have been updated to return resource values matching the + * new configuration. + * + * @param newConfig The new device configuration. + */ + public void onConfigurationChanged(Configuration newConfig) { + if (DEBUG_LIFECYCLE) Slog.v(TAG, "onConfigurationChanged " + this + ": " + newConfig); + mCalled = true; + + mFragments.dispatchConfigurationChanged(newConfig); + + if (mWindow != null) { + // Pass the configuration changed event to the window + mWindow.onConfigurationChanged(newConfig); + } + + if (mActionBar != null) { + // Do this last; the action bar will need to access + // view changes from above. + mActionBar.onConfigurationChanged(newConfig); + } + } + + /** + * If this activity is being destroyed because it can not handle a + * configuration parameter being changed (and thus its + * {@link #onConfigurationChanged(Configuration)} method is + * not being called), then you can use this method to discover + * the set of changes that have occurred while in the process of being + * destroyed. Note that there is no guarantee that these will be + * accurate (other changes could have happened at any time), so you should + * only use this as an optimization hint. + * + * @return Returns a bit field of the configuration parameters that are + * changing, as defined by the {@link android.content.res.Configuration} + * class. + */ + public int getChangingConfigurations() { + return mConfigChangeFlags; + } + + /** + * Retrieve the non-configuration instance data that was previously + * returned by {@link #onRetainNonConfigurationInstance()}. This will + * be available from the initial {@link #onCreate} and + * {@link #onStart} calls to the new instance, allowing you to extract + * any useful dynamic state from the previous instance. + * + *

Note that the data you retrieve here should only be used + * as an optimization for handling configuration changes. You should always + * be able to handle getting a null pointer back, and an activity must + * still be able to restore itself to its previous state (through the + * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this + * function returns null. + * + * @return Returns the object previously returned by + * {@link #onRetainNonConfigurationInstance()}. + * + * @deprecated Use the new {@link Fragment} API + * {@link Fragment#setRetainInstance(boolean)} instead; this is also + * available on older platforms through the Android compatibility package. + */ + @Deprecated + public Object getLastNonConfigurationInstance() { + return mLastNonConfigurationInstances != null + ? mLastNonConfigurationInstances.activity : null; + } + + /** + * Called by the system, as part of destroying an + * activity due to a configuration change, when it is known that a new + * instance will immediately be created for the new configuration. You + * can return any object you like here, including the activity instance + * itself, which can later be retrieved by calling + * {@link #getLastNonConfigurationInstance()} in the new activity + * instance. + * + * If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB} + * or later, consider instead using a {@link Fragment} with + * {@link Fragment#setRetainInstance(boolean) + * Fragment.setRetainInstance(boolean}. + * + *

This function is called purely as an optimization, and you must + * not rely on it being called. When it is called, a number of guarantees + * will be made to help optimize configuration switching: + *

    + *
  • The function will be called between {@link #onStop} and + * {@link #onDestroy}. + *
  • A new instance of the activity will always be immediately + * created after this one's {@link #onDestroy()} is called. In particular, + * no messages will be dispatched during this time (when the returned + * object does not have an activity to be associated with). + *
  • The object you return here will always be available from + * the {@link #getLastNonConfigurationInstance()} method of the following + * activity instance as described there. + *
+ * + *

These guarantees are designed so that an activity can use this API + * to propagate extensive state from the old to new activity instance, from + * loaded bitmaps, to network connections, to evenly actively running + * threads. Note that you should not propagate any data that + * may change based on the configuration, including any data loaded from + * resources such as strings, layouts, or drawables. + * + *

The guarantee of no message handling during the switch to the next + * activity simplifies use with active objects. For example if your retained + * state is an {@link android.os.AsyncTask} you are guaranteed that its + * call back functions (like {@link android.os.AsyncTask#onPostExecute}) will + * not be called from the call here until you execute the next instance's + * {@link #onCreate(Bundle)}. (Note however that there is of course no such + * guarantee for {@link android.os.AsyncTask#doInBackground} since that is + * running in a separate thread.) + * + * @return Return any Object holding the desired state to propagate to the + * next activity instance. + * + * @deprecated Use the new {@link Fragment} API + * {@link Fragment#setRetainInstance(boolean)} instead; this is also + * available on older platforms through the Android compatibility package. + */ + public Object onRetainNonConfigurationInstance() { + return null; + } + + /** + * Retrieve the non-configuration instance data that was previously + * returned by {@link #onRetainNonConfigurationChildInstances()}. This will + * be available from the initial {@link #onCreate} and + * {@link #onStart} calls to the new instance, allowing you to extract + * any useful dynamic state from the previous instance. + * + *

Note that the data you retrieve here should only be used + * as an optimization for handling configuration changes. You should always + * be able to handle getting a null pointer back, and an activity must + * still be able to restore itself to its previous state (through the + * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this + * function returns null. + * + * @return Returns the object previously returned by + * {@link #onRetainNonConfigurationChildInstances()} + */ + HashMap getLastNonConfigurationChildInstances() { + return mLastNonConfigurationInstances != null + ? mLastNonConfigurationInstances.children : null; + } + + /** + * This method is similar to {@link #onRetainNonConfigurationInstance()} except that + * it should return either a mapping from child activity id strings to arbitrary objects, + * or null. This method is intended to be used by Activity framework subclasses that control a + * set of child activities, such as ActivityGroup. The same guarantees and restrictions apply + * as for {@link #onRetainNonConfigurationInstance()}. The default implementation returns null. + */ + HashMap onRetainNonConfigurationChildInstances() { + return null; + } + + NonConfigurationInstances retainNonConfigurationInstances() { + Object activity = onRetainNonConfigurationInstance(); + HashMap children = onRetainNonConfigurationChildInstances(); + ArrayList fragments = mFragments.retainNonConfig(); + boolean retainLoaders = false; + if (mAllLoaderManagers != null) { + // prune out any loader managers that were already stopped and so + // have nothing useful to retain. + for (int i=mAllLoaderManagers.size()-1; i>=0; i--) { + LoaderManagerImpl lm = mAllLoaderManagers.valueAt(i); + if (lm.mRetaining) { + retainLoaders = true; + } else { + lm.doDestroy(); + mAllLoaderManagers.removeAt(i); + } + } + } + if (activity == null && children == null && fragments == null && !retainLoaders) { + return null; + } + + NonConfigurationInstances nci = new NonConfigurationInstances(); + nci.activity = activity; + nci.children = children; + nci.fragments = fragments; + nci.loaders = mAllLoaderManagers; + return nci; + } + + public void onLowMemory() { + if (DEBUG_LIFECYCLE) Slog.v(TAG, "onLowMemory " + this); + mCalled = true; + mFragments.dispatchLowMemory(); + } + + public void onTrimMemory(int level) { + if (DEBUG_LIFECYCLE) Slog.v(TAG, "onTrimMemory " + this + ": " + level); + mCalled = true; + mFragments.dispatchTrimMemory(level); + } + + /** + * Return the FragmentManager for interacting with fragments associated + * with this activity. + */ + public FragmentManager getFragmentManager() { + return mFragments; + } + + void invalidateFragmentIndex(int index) { + //Log.v(TAG, "invalidateFragmentIndex: index=" + index); + if (mAllLoaderManagers != null) { + LoaderManagerImpl lm = mAllLoaderManagers.get(index); + if (lm != null && !lm.mRetaining) { + lm.doDestroy(); + mAllLoaderManagers.remove(index); + } + } + } + + /** + * Called when a Fragment is being attached to this activity, immediately + * after the call to its {@link Fragment#onAttach Fragment.onAttach()} + * method and before {@link Fragment#onCreate Fragment.onCreate()}. + */ + public void onAttachFragment(Fragment fragment) { + } + + /** + * Wrapper around + * {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)} + * that gives the resulting {@link Cursor} to call + * {@link #startManagingCursor} so that the activity will manage its + * lifecycle for you. + * + * If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB} + * or later, consider instead using {@link LoaderManager} instead, available + * via {@link #getLoaderManager()}. + * + *

Warning: Do not call {@link Cursor#close()} on a cursor obtained using + * this method, because the activity will do that for you at the appropriate time. However, if + * you call {@link #stopManagingCursor} on a cursor from a managed query, the system will + * not automatically close the cursor and, in that case, you must call + * {@link Cursor#close()}.

+ * + * @param uri The URI of the content provider to query. + * @param projection List of columns to return. + * @param selection SQL WHERE clause. + * @param sortOrder SQL ORDER BY clause. + * + * @return The Cursor that was returned by query(). + * + * @see ContentResolver#query(android.net.Uri , String[], String, String[], String) + * @see #startManagingCursor + * @hide + * + * @deprecated Use {@link CursorLoader} instead. + */ + @Deprecated + public final Cursor managedQuery(Uri uri, String[] projection, String selection, + String sortOrder) { + Cursor c = getContentResolver().query(uri, projection, selection, null, sortOrder); + if (c != null) { + startManagingCursor(c); + } + return c; + } + + /** + * Wrapper around + * {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)} + * that gives the resulting {@link Cursor} to call + * {@link #startManagingCursor} so that the activity will manage its + * lifecycle for you. + * + * If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB} + * or later, consider instead using {@link LoaderManager} instead, available + * via {@link #getLoaderManager()}. + * + *

Warning: Do not call {@link Cursor#close()} on a cursor obtained using + * this method, because the activity will do that for you at the appropriate time. However, if + * you call {@link #stopManagingCursor} on a cursor from a managed query, the system will + * not automatically close the cursor and, in that case, you must call + * {@link Cursor#close()}.

+ * + * @param uri The URI of the content provider to query. + * @param projection List of columns to return. + * @param selection SQL WHERE clause. + * @param selectionArgs The arguments to selection, if any ?s are pesent + * @param sortOrder SQL ORDER BY clause. + * + * @return The Cursor that was returned by query(). + * + * @see ContentResolver#query(android.net.Uri , String[], String, String[], String) + * @see #startManagingCursor + * + * @deprecated Use {@link CursorLoader} instead. + */ + @Deprecated + public final Cursor managedQuery(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + Cursor c = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder); + if (c != null) { + startManagingCursor(c); + } + return c; + } + + /** + * This method allows the activity to take care of managing the given + * {@link Cursor}'s lifecycle for you based on the activity's lifecycle. + * That is, when the activity is stopped it will automatically call + * {@link Cursor#deactivate} on the given Cursor, and when it is later restarted + * it will call {@link Cursor#requery} for you. When the activity is + * destroyed, all managed Cursors will be closed automatically. + * + * If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB} + * or later, consider instead using {@link LoaderManager} instead, available + * via {@link #getLoaderManager()}. + * + *

Warning: Do not call {@link Cursor#close()} on cursor obtained from + * {@link #managedQuery}, because the activity will do that for you at the appropriate time. + * However, if you call {@link #stopManagingCursor} on a cursor from a managed query, the system + * will not automatically close the cursor and, in that case, you must call + * {@link Cursor#close()}.

+ * + * @param c The Cursor to be managed. + * + * @see #managedQuery(android.net.Uri , String[], String, String[], String) + * @see #stopManagingCursor + * + * @deprecated Use the new {@link android.content.CursorLoader} class with + * {@link LoaderManager} instead; this is also + * available on older platforms through the Android compatibility package. + */ + @Deprecated + public void startManagingCursor(Cursor c) { + synchronized (mManagedCursors) { + mManagedCursors.add(new ManagedCursor(c)); + } + } + + /** + * Given a Cursor that was previously given to + * {@link #startManagingCursor}, stop the activity's management of that + * cursor. + * + *

Warning: After calling this method on a cursor from a managed query, + * the system will not automatically close the cursor and you must call + * {@link Cursor#close()}.

+ * + * @param c The Cursor that was being managed. + * + * @see #startManagingCursor + * + * @deprecated Use the new {@link android.content.CursorLoader} class with + * {@link LoaderManager} instead; this is also + * available on older platforms through the Android compatibility package. + */ + @Deprecated + public void stopManagingCursor(Cursor c) { + synchronized (mManagedCursors) { + final int N = mManagedCursors.size(); + for (int i=0; iThat is, the user does not need to hold down the menu key to execute menu shortcuts. + * + * @see #setDefaultKeyMode + */ + static public final int DEFAULT_KEYS_SHORTCUT = 2; + /** + * Use with {@link #setDefaultKeyMode} to specify that unhandled keystrokes + * will start an application-defined search. (If the application or activity does not + * actually define a search, the the keys will be ignored.) + * + *

See {@link android.app.SearchManager android.app.SearchManager} for more details. + * + * @see #setDefaultKeyMode + */ + static public final int DEFAULT_KEYS_SEARCH_LOCAL = 3; + + /** + * Use with {@link #setDefaultKeyMode} to specify that unhandled keystrokes + * will start a global search (typically web search, but some platforms may define alternate + * methods for global search) + * + *

See {@link android.app.SearchManager android.app.SearchManager} for more details. + * + * @see #setDefaultKeyMode + */ + static public final int DEFAULT_KEYS_SEARCH_GLOBAL = 4; + + /** + * Select the default key handling for this activity. This controls what + * will happen to key events that are not otherwise handled. The default + * mode ({@link #DEFAULT_KEYS_DISABLE}) will simply drop them on the + * floor. Other modes allow you to launch the dialer + * ({@link #DEFAULT_KEYS_DIALER}), execute a shortcut in your options + * menu without requiring the menu key be held down + * ({@link #DEFAULT_KEYS_SHORTCUT}), or launch a search ({@link #DEFAULT_KEYS_SEARCH_LOCAL} + * and {@link #DEFAULT_KEYS_SEARCH_GLOBAL}). + * + *

Note that the mode selected here does not impact the default + * handling of system keys, such as the "back" and "menu" keys, and your + * activity and its views always get a first chance to receive and handle + * all application keys. + * + * @param mode The desired default key mode constant. + * + * @see #DEFAULT_KEYS_DISABLE + * @see #DEFAULT_KEYS_DIALER + * @see #DEFAULT_KEYS_SHORTCUT + * @see #DEFAULT_KEYS_SEARCH_LOCAL + * @see #DEFAULT_KEYS_SEARCH_GLOBAL + * @see #onKeyDown + */ + public final void setDefaultKeyMode(int mode) { + mDefaultKeyMode = mode; + + // Some modes use a SpannableStringBuilder to track & dispatch input events + // This list must remain in sync with the switch in onKeyDown() + switch (mode) { + case DEFAULT_KEYS_DISABLE: + case DEFAULT_KEYS_SHORTCUT: + mDefaultKeySsb = null; // not used in these modes + break; + case DEFAULT_KEYS_DIALER: + case DEFAULT_KEYS_SEARCH_LOCAL: + case DEFAULT_KEYS_SEARCH_GLOBAL: + mDefaultKeySsb = new SpannableStringBuilder(); + Selection.setSelection(mDefaultKeySsb,0); + break; + default: + throw new IllegalArgumentException(); + } + } + + /** + * Called when a key was pressed down and not handled by any of the views + * inside of the activity. So, for example, key presses while the cursor + * is inside a TextView will not trigger the event (unless it is a navigation + * to another object) because TextView handles its own key presses. + * + *

If the focused view didn't want this event, this method is called. + * + *

The default implementation takes care of {@link KeyEvent#KEYCODE_BACK} + * by calling {@link #onBackPressed()}, though the behavior varies based + * on the application compatibility mode: for + * {@link android.os.Build.VERSION_CODES#ECLAIR} or later applications, + * it will set up the dispatch to call {@link #onKeyUp} where the action + * will be performed; for earlier applications, it will perform the + * action immediately in on-down, as those versions of the platform + * behaved. + * + *

Other additional default key handling may be performed + * if configured with {@link #setDefaultKeyMode}. + * + * @return Return true to prevent this event from being propagated + * further, or false to indicate that you have not handled + * this event and it should continue to be propagated. + * @see #onKeyUp + * @see android.view.KeyEvent + */ + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (getApplicationInfo().targetSdkVersion + >= Build.VERSION_CODES.ECLAIR) { + event.startTracking(); + } else { + onBackPressed(); + } + return true; + } + + if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) { + return false; + } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) { + if (getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, + keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE)) { + return true; + } + return false; + } else { + // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_* + boolean clearSpannable = false; + boolean handled; + if ((event.getRepeatCount() != 0) || event.isSystem()) { + clearSpannable = true; + handled = false; + } else { + handled = TextKeyListener.getInstance().onKeyDown( + null, mDefaultKeySsb, keyCode, event); + if (handled && mDefaultKeySsb.length() > 0) { + // something useable has been typed - dispatch it now. + + final String str = mDefaultKeySsb.toString(); + clearSpannable = true; + + switch (mDefaultKeyMode) { + case DEFAULT_KEYS_DIALER: + Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + str)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + break; + case DEFAULT_KEYS_SEARCH_LOCAL: + startSearch(str, false, null, false); + break; + case DEFAULT_KEYS_SEARCH_GLOBAL: + startSearch(str, false, null, true); + break; + } + } + } + if (clearSpannable) { + mDefaultKeySsb.clear(); + mDefaultKeySsb.clearSpans(); + Selection.setSelection(mDefaultKeySsb,0); + } + return handled; + } + } + + /** + * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent) + * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle + * the event). + */ + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + return false; + } + + /** + * Called when a key was released and not handled by any of the views + * inside of the activity. So, for example, key presses while the cursor + * is inside a TextView will not trigger the event (unless it is a navigation + * to another object) because TextView handles its own key presses. + * + *

The default implementation handles KEYCODE_BACK to stop the activity + * and go back. + * + * @return Return true to prevent this event from being propagated + * further, or false to indicate that you have not handled + * this event and it should continue to be propagated. + * @see #onKeyDown + * @see KeyEvent + */ + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (getApplicationInfo().targetSdkVersion + >= Build.VERSION_CODES.ECLAIR) { + if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() + && !event.isCanceled()) { + onBackPressed(); + return true; + } + } + return false; + } + + /** + * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent) + * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle + * the event). + */ + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + return false; + } + + /** + * Called when the activity has detected the user's press of the back + * key. The default implementation simply finishes the current activity, + * but you can override this to do whatever you want. + */ + public void onBackPressed() { + if (!mFragments.popBackStackImmediate()) { + finish(); + } + } + + /** + * Called when a key shortcut event is not handled by any of the views in the Activity. + * Override this method to implement global key shortcuts for the Activity. + * Key shortcuts can also be implemented by setting the + * {@link MenuItem#setShortcut(char, char) shortcut} property of menu items. + * + * @param keyCode The value in event.getKeyCode(). + * @param event Description of the key event. + * @return True if the key shortcut was handled. + */ + public boolean onKeyShortcut(int keyCode, KeyEvent event) { + return false; + } + + /** + * Called when a touch screen event was not handled by any of the views + * under it. This is most useful to process touch events that happen + * outside of your window bounds, where there is no view to receive it. + * + * @param event The touch screen event being processed. + * + * @return Return true if you have consumed the event, false if you haven't. + * The default implementation always returns false. + */ + public boolean onTouchEvent(MotionEvent event) { + if (mWindow.shouldCloseOnTouch(this, event)) { + finish(); + return true; + } + + return false; + } + + /** + * Called when the trackball was moved and not handled by any of the + * views inside of the activity. So, for example, if the trackball moves + * while focus is on a button, you will receive a call here because + * buttons do not normally do anything with trackball events. The call + * here happens before trackball movements are converted to + * DPAD key events, which then get sent back to the view hierarchy, and + * will be processed at the point for things like focus navigation. + * + * @param event The trackball event being processed. + * + * @return Return true if you have consumed the event, false if you haven't. + * The default implementation always returns false. + */ + public boolean onTrackballEvent(MotionEvent event) { + return false; + } + + /** + * Called when a generic motion event was not handled by any of the + * views inside of the activity. + *

+ * Generic motion events describe joystick movements, mouse hovers, track pad + * touches, scroll wheel movements and other input events. The + * {@link MotionEvent#getSource() source} of the motion event specifies + * the class of input that was received. Implementations of this method + * must examine the bits in the source before processing the event. + * The following code example shows how this is done. + *

+ * Generic motion events with source class + * {@link android.view.InputDevice#SOURCE_CLASS_POINTER} + * are delivered to the view under the pointer. All other generic motion events are + * delivered to the focused view. + *

+ * See {@link View#onGenericMotionEvent(MotionEvent)} for an example of how to + * handle this event. + *

+ * + * @param event The generic motion event being processed. + * + * @return Return true if you have consumed the event, false if you haven't. + * The default implementation always returns false. + */ + public boolean onGenericMotionEvent(MotionEvent event) { + return false; + } + + /** + * Called whenever a key, touch, or trackball event is dispatched to the + * activity. Implement this method if you wish to know that the user has + * interacted with the device in some way while your activity is running. + * This callback and {@link #onUserLeaveHint} are intended to help + * activities manage status bar notifications intelligently; specifically, + * for helping activities determine the proper time to cancel a notfication. + * + *

All calls to your activity's {@link #onUserLeaveHint} callback will + * be accompanied by calls to {@link #onUserInteraction}. This + * ensures that your activity will be told of relevant user activity such + * as pulling down the notification pane and touching an item there. + * + *

Note that this callback will be invoked for the touch down action + * that begins a touch gesture, but may not be invoked for the touch-moved + * and touch-up actions that follow. + * + * @see #onUserLeaveHint() + */ + public void onUserInteraction() { + } + + public void onWindowAttributesChanged(WindowManager.LayoutParams params) { + // Update window manager if: we have a view, that view is + // attached to its parent (which will be a RootView), and + // this activity is not embedded. + if (mParent == null) { + View decor = mDecor; + if (decor != null && decor.getParent() != null) { + getWindowManager().updateViewLayout(decor, params); + } + } + } + + public void onContentChanged() { + } + + /** + * Called when the current {@link Window} of the activity gains or loses + * focus. This is the best indicator of whether this activity is visible + * to the user. The default implementation clears the key tracking + * state, so should always be called. + * + *

Note that this provides information about global focus state, which + * is managed independently of activity lifecycles. As such, while focus + * changes will generally have some relation to lifecycle changes (an + * activity that is stopped will not generally get window focus), you + * should not rely on any particular order between the callbacks here and + * those in the other lifecycle methods such as {@link #onResume}. + * + *

As a general rule, however, a resumed activity will have window + * focus... unless it has displayed other dialogs or popups that take + * input focus, in which case the activity itself will not have focus + * when the other windows have it. Likewise, the system may display + * system-level windows (such as the status bar notification panel or + * a system alert) which will temporarily take window input focus without + * pausing the foreground activity. + * + * @param hasFocus Whether the window of this activity has focus. + * + * @see #hasWindowFocus() + * @see #onResume + * @see View#onWindowFocusChanged(boolean) + */ + public void onWindowFocusChanged(boolean hasFocus) { + } + + /** + * Called when the main window associated with the activity has been + * attached to the window manager. + * See {@link View#onAttachedToWindow() View.onAttachedToWindow()} + * for more information. + * @see View#onAttachedToWindow + */ + public void onAttachedToWindow() { + } + + /** + * Called when the main window associated with the activity has been + * detached from the window manager. + * See {@link View#onDetachedFromWindow() View.onDetachedFromWindow()} + * for more information. + * @see View#onDetachedFromWindow + */ + public void onDetachedFromWindow() { + } + + /** + * Returns true if this activity's main window currently has window focus. + * Note that this is not the same as the view itself having focus. + * + * @return True if this activity's main window currently has window focus. + * + * @see #onWindowAttributesChanged(android.view.WindowManager.LayoutParams) + */ + public boolean hasWindowFocus() { + Window w = getWindow(); + if (w != null) { + View d = w.getDecorView(); + if (d != null) { + return d.hasWindowFocus(); + } + } + return false; + } + + /** + * Called to process key events. You can override this to intercept all + * key events before they are dispatched to the window. Be sure to call + * this implementation for key events that should be handled normally. + * + * @param event The key event. + * + * @return boolean Return true if this event was consumed. + */ + public boolean dispatchKeyEvent(KeyEvent event) { + onUserInteraction(); + Window win = getWindow(); + if (win.superDispatchKeyEvent(event)) { + return true; + } + View decor = mDecor; + if (decor == null) decor = win.getDecorView(); + return event.dispatch(this, decor != null + ? decor.getKeyDispatcherState() : null, this); + } + + /** + * Called to process a key shortcut event. + * You can override this to intercept all key shortcut events before they are + * dispatched to the window. Be sure to call this implementation for key shortcut + * events that should be handled normally. + * + * @param event The key shortcut event. + * @return True if this event was consumed. + */ + public boolean dispatchKeyShortcutEvent(KeyEvent event) { + onUserInteraction(); + if (getWindow().superDispatchKeyShortcutEvent(event)) { + return true; + } + return onKeyShortcut(event.getKeyCode(), event); + } + + /** + * Called to process touch screen events. You can override this to + * intercept all touch screen events before they are dispatched to the + * window. Be sure to call this implementation for touch screen events + * that should be handled normally. + * + * @param ev The touch screen event. + * + * @return boolean Return true if this event was consumed. + */ + public boolean dispatchTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + onUserInteraction(); + } + if (getWindow().superDispatchTouchEvent(ev)) { + return true; + } + return onTouchEvent(ev); + } + + /** + * Called to process trackball events. You can override this to + * intercept all trackball events before they are dispatched to the + * window. Be sure to call this implementation for trackball events + * that should be handled normally. + * + * @param ev The trackball event. + * + * @return boolean Return true if this event was consumed. + */ + public boolean dispatchTrackballEvent(MotionEvent ev) { + onUserInteraction(); + if (getWindow().superDispatchTrackballEvent(ev)) { + return true; + } + return onTrackballEvent(ev); + } + + /** + * Called to process generic motion events. You can override this to + * intercept all generic motion events before they are dispatched to the + * window. Be sure to call this implementation for generic motion events + * that should be handled normally. + * + * @param ev The generic motion event. + * + * @return boolean Return true if this event was consumed. + */ + public boolean dispatchGenericMotionEvent(MotionEvent ev) { + onUserInteraction(); + if (getWindow().superDispatchGenericMotionEvent(ev)) { + return true; + } + return onGenericMotionEvent(ev); + } + + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + event.setClassName(getClass().getName()); + event.setPackageName(getPackageName()); + + LayoutParams params = getWindow().getAttributes(); + boolean isFullScreen = (params.width == LayoutParams.MATCH_PARENT) && + (params.height == LayoutParams.MATCH_PARENT); + event.setFullScreen(isFullScreen); + + CharSequence title = getTitle(); + if (!TextUtils.isEmpty(title)) { + event.getText().add(title); + } + + return true; + } + + /** + * Default implementation of + * {@link android.view.Window.Callback#onCreatePanelView} + * for activities. This + * simply returns null so that all panel sub-windows will have the default + * menu behavior. + */ + public View onCreatePanelView(int featureId) { + return null; + } + + /** + * Default implementation of + * {@link android.view.Window.Callback#onCreatePanelMenu} + * for activities. This calls through to the new + * {@link #onCreateOptionsMenu} method for the + * {@link android.view.Window#FEATURE_OPTIONS_PANEL} panel, + * so that subclasses of Activity don't need to deal with feature codes. + */ + public boolean onCreatePanelMenu(int featureId, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + boolean show = onCreateOptionsMenu(menu); + show |= mFragments.dispatchCreateOptionsMenu(menu, getMenuInflater()); + return show; + } + return false; + } + + /** + * Default implementation of + * {@link android.view.Window.Callback#onPreparePanel} + * for activities. This + * calls through to the new {@link #onPrepareOptionsMenu} method for the + * {@link android.view.Window#FEATURE_OPTIONS_PANEL} + * panel, so that subclasses of + * Activity don't need to deal with feature codes. + */ + public boolean onPreparePanel(int featureId, View view, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) { + boolean goforit = onPrepareOptionsMenu(menu); + goforit |= mFragments.dispatchPrepareOptionsMenu(menu); + return goforit; + } + return true; + } + + /** + * {@inheritDoc} + * + * @return The default implementation returns true. + */ + public boolean onMenuOpened(int featureId, Menu menu) { + if (featureId == Window.FEATURE_ACTION_BAR) { + initActionBar(); + if (mActionBar != null) { + mActionBar.dispatchMenuVisibilityChanged(true); + } else { + Log.e(TAG, "Tried to open action bar menu with no action bar"); + } + } + return true; + } + + /** + * Default implementation of + * {@link android.view.Window.Callback#onMenuItemSelected} + * for activities. This calls through to the new + * {@link #onOptionsItemSelected} method for the + * {@link android.view.Window#FEATURE_OPTIONS_PANEL} + * panel, so that subclasses of + * Activity don't need to deal with feature codes. + */ + public boolean onMenuItemSelected(int featureId, MenuItem item) { + switch (featureId) { + case Window.FEATURE_OPTIONS_PANEL: + // Put event logging here so it gets called even if subclass + // doesn't call through to superclass's implmeentation of each + // of these methods below + EventLog.writeEvent(50000, 0, item.getTitleCondensed()); + if (onOptionsItemSelected(item)) { + return true; + } + if (mFragments.dispatchOptionsItemSelected(item)) { + return true; + } + if (item.getItemId() == android.R.id.home && mActionBar != null && + (mActionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + if (mParent == null) { + return onNavigateUp(); + } else { + return mParent.onNavigateUpFromChild(this); + } + } + return false; + + case Window.FEATURE_CONTEXT_MENU: + EventLog.writeEvent(50000, 1, item.getTitleCondensed()); + if (onContextItemSelected(item)) { + return true; + } + return mFragments.dispatchContextItemSelected(item); + + default: + return false; + } + } + + /** + * Default implementation of + * {@link android.view.Window.Callback#onPanelClosed(int, Menu)} for + * activities. This calls through to {@link #onOptionsMenuClosed(Menu)} + * method for the {@link android.view.Window#FEATURE_OPTIONS_PANEL} panel, + * so that subclasses of Activity don't need to deal with feature codes. + * For context menus ({@link Window#FEATURE_CONTEXT_MENU}), the + * {@link #onContextMenuClosed(Menu)} will be called. + */ + public void onPanelClosed(int featureId, Menu menu) { + switch (featureId) { + case Window.FEATURE_OPTIONS_PANEL: + mFragments.dispatchOptionsMenuClosed(menu); + onOptionsMenuClosed(menu); + break; + + case Window.FEATURE_CONTEXT_MENU: + onContextMenuClosed(menu); + break; + + case Window.FEATURE_ACTION_BAR: + initActionBar(); + mActionBar.dispatchMenuVisibilityChanged(false); + break; + } + } + + /** + * Declare that the options menu has changed, so should be recreated. + * The {@link #onCreateOptionsMenu(Menu)} method will be called the next + * time it needs to be displayed. + */ + public void invalidateOptionsMenu() { + mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL); + } + + /** + * Initialize the contents of the Activity's standard options menu. You + * should place your menu items in to menu. + * + *

This is only called once, the first time the options menu is + * displayed. To update the menu every time it is displayed, see + * {@link #onPrepareOptionsMenu}. + * + *

The default implementation populates the menu with standard system + * menu items. These are placed in the {@link Menu#CATEGORY_SYSTEM} group so that + * they will be correctly ordered with application-defined menu items. + * Deriving classes should always call through to the base implementation. + * + *

You can safely hold on to menu (and any items created + * from it), making modifications to it as desired, until the next + * time onCreateOptionsMenu() is called. + * + *

When you add items to the menu, you can implement the Activity's + * {@link #onOptionsItemSelected} method to handle them there. + * + * @param menu The options menu in which you place your items. + * + * @return You must return true for the menu to be displayed; + * if you return false it will not be shown. + * + * @see #onPrepareOptionsMenu + * @see #onOptionsItemSelected + */ + public boolean onCreateOptionsMenu(Menu menu) { + if (mParent != null) { + return mParent.onCreateOptionsMenu(menu); + } + return true; + } + + /** + * Prepare the Screen's standard options menu to be displayed. This is + * called right before the menu is shown, every time it is shown. You can + * use this method to efficiently enable/disable items or otherwise + * dynamically modify the contents. + * + *

The default implementation updates the system menu items based on the + * activity's state. Deriving classes should always call through to the + * base class implementation. + * + * @param menu The options menu as last shown or first initialized by + * onCreateOptionsMenu(). + * + * @return You must return true for the menu to be displayed; + * if you return false it will not be shown. + * + * @see #onCreateOptionsMenu + */ + public boolean onPrepareOptionsMenu(Menu menu) { + if (mParent != null) { + return mParent.onPrepareOptionsMenu(menu); + } + return true; + } + + /** + * This hook is called whenever an item in your options menu is selected. + * The default implementation simply returns false to have the normal + * processing happen (calling the item's Runnable or sending a message to + * its Handler as appropriate). You can use this method for any items + * for which you would like to do processing without those other + * facilities. + * + *

Derived classes should call through to the base class for it to + * perform the default menu handling.

+ * + * @param item The menu item that was selected. + * + * @return boolean Return false to allow normal menu processing to + * proceed, true to consume it here. + * + * @see #onCreateOptionsMenu + */ + public boolean onOptionsItemSelected(MenuItem item) { + if (mParent != null) { + return mParent.onOptionsItemSelected(item); + } + return false; + } + + /** + * This method is called whenever the user chooses to navigate Up within your application's + * activity hierarchy from the action bar. + * + *

If the attribute {@link android.R.attr#parentActivityName parentActivityName} + * was specified in the manifest for this activity or an activity-alias to it, + * default Up navigation will be handled automatically. If any activity + * along the parent chain requires extra Intent arguments, the Activity subclass + * should override the method {@link #onPrepareNavigateUpTaskStack(TaskStackBuilder)} + * to supply those arguments.

+ * + *

See Tasks and Back Stack + * from the developer guide and Navigation + * from the design guide for more information about navigating within your app.

+ * + *

See the {@link TaskStackBuilder} class and the Activity methods + * {@link #getParentActivityIntent()}, {@link #shouldUpRecreateTask(Intent)}, and + * {@link #navigateUpTo(Intent)} for help implementing custom Up navigation. + * The AppNavigation sample application in the Android SDK is also available for reference.

+ * + * @return true if Up navigation completed successfully and this Activity was finished, + * false otherwise. + */ + public boolean onNavigateUp() { + // Automatically handle hierarchical Up navigation if the proper + // metadata is available. + Intent upIntent = getParentActivityIntent(); + if (upIntent != null) { + if (shouldUpRecreateTask(upIntent)) { + TaskStackBuilder b = TaskStackBuilder.create(this); + onCreateNavigateUpTaskStack(b); + onPrepareNavigateUpTaskStack(b); + b.startActivities(); + + // We can't finishAffinity if we have a result. + // Fall back and simply finish the current activity instead. + if (mResultCode != RESULT_CANCELED || mResultData != null) { + // Tell the developer what's going on to avoid hair-pulling. + Log.i(TAG, "onNavigateUp only finishing topmost activity to return a result"); + finish(); + } else { + finishAffinity(); + } + } else { + navigateUpTo(upIntent); + } + return true; + } + return false; + } + + /** + * This is called when a child activity of this one attempts to navigate up. + * The default implementation simply calls onNavigateUp() on this activity (the parent). + * + * @param child The activity making the call. + */ + public boolean onNavigateUpFromChild(Activity child) { + return onNavigateUp(); + } + + /** + * Define the synthetic task stack that will be generated during Up navigation from + * a different task. + * + *

The default implementation of this method adds the parent chain of this activity + * as specified in the manifest to the supplied {@link TaskStackBuilder}. Applications + * may choose to override this method to construct the desired task stack in a different + * way.

+ * + *

This method will be invoked by the default implementation of {@link #onNavigateUp()} + * if {@link #shouldUpRecreateTask(Intent)} returns true when supplied with the intent + * returned by {@link #getParentActivityIntent()}.

+ * + *

Applications that wish to supply extra Intent parameters to the parent stack defined + * by the manifest should override {@link #onPrepareNavigateUpTaskStack(TaskStackBuilder)}.

+ * + * @param builder An empty TaskStackBuilder - the application should add intents representing + * the desired task stack + */ + public void onCreateNavigateUpTaskStack(TaskStackBuilder builder) { + builder.addParentStack(this); + } + + /** + * Prepare the synthetic task stack that will be generated during Up navigation + * from a different task. + * + *

This method receives the {@link TaskStackBuilder} with the constructed series of + * Intents as generated by {@link #onCreateNavigateUpTaskStack(TaskStackBuilder)}. + * If any extra data should be added to these intents before launching the new task, + * the application should override this method and add that data here.

+ * + * @param builder A TaskStackBuilder that has been populated with Intents by + * onCreateNavigateUpTaskStack. + */ + public void onPrepareNavigateUpTaskStack(TaskStackBuilder builder) { + } + + /** + * This hook is called whenever the options menu is being closed (either by the user canceling + * the menu with the back/menu button, or when an item is selected). + * + * @param menu The options menu as last shown or first initialized by + * onCreateOptionsMenu(). + */ + public void onOptionsMenuClosed(Menu menu) { + if (mParent != null) { + mParent.onOptionsMenuClosed(menu); + } + } + + /** + * Programmatically opens the options menu. If the options menu is already + * open, this method does nothing. + */ + public void openOptionsMenu() { + mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null); + } + + /** + * Progammatically closes the options menu. If the options menu is already + * closed, this method does nothing. + */ + public void closeOptionsMenu() { + mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL); + } + + /** + * Called when a context menu for the {@code view} is about to be shown. + * Unlike {@link #onCreateOptionsMenu(Menu)}, this will be called every + * time the context menu is about to be shown and should be populated for + * the view (or item inside the view for {@link AdapterView} subclasses, + * this can be found in the {@code menuInfo})). + *

+ * Use {@link #onContextItemSelected(android.view.MenuItem)} to know when an + * item has been selected. + *

+ * It is not safe to hold onto the context menu after this method returns. + * {@inheritDoc} + */ + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + } + + /** + * Registers a context menu to be shown for the given view (multiple views + * can show the context menu). This method will set the + * {@link OnCreateContextMenuListener} on the view to this activity, so + * {@link #onCreateContextMenu(ContextMenu, View, ContextMenuInfo)} will be + * called when it is time to show the context menu. + * + * @see #unregisterForContextMenu(View) + * @param view The view that should show a context menu. + */ + public void registerForContextMenu(View view) { + view.setOnCreateContextMenuListener(this); + } + + /** + * Prevents a context menu to be shown for the given view. This method will remove the + * {@link OnCreateContextMenuListener} on the view. + * + * @see #registerForContextMenu(View) + * @param view The view that should stop showing a context menu. + */ + public void unregisterForContextMenu(View view) { + view.setOnCreateContextMenuListener(null); + } + + /** + * Programmatically opens the context menu for a particular {@code view}. + * The {@code view} should have been added via + * {@link #registerForContextMenu(View)}. + * + * @param view The view to show the context menu for. + */ + public void openContextMenu(View view) { + view.showContextMenu(); + } + + /** + * Programmatically closes the most recently opened context menu, if showing. + */ + public void closeContextMenu() { + mWindow.closePanel(Window.FEATURE_CONTEXT_MENU); + } + + /** + * This hook is called whenever an item in a context menu is selected. The + * default implementation simply returns false to have the normal processing + * happen (calling the item's Runnable or sending a message to its Handler + * as appropriate). You can use this method for any items for which you + * would like to do processing without those other facilities. + *

+ * Use {@link MenuItem#getMenuInfo()} to get extra information set by the + * View that added this menu item. + *

+ * Derived classes should call through to the base class for it to perform + * the default menu handling. + * + * @param item The context menu item that was selected. + * @return boolean Return false to allow normal context menu processing to + * proceed, true to consume it here. + */ + public boolean onContextItemSelected(MenuItem item) { + if (mParent != null) { + return mParent.onContextItemSelected(item); + } + return false; + } + + /** + * This hook is called whenever the context menu is being closed (either by + * the user canceling the menu with the back/menu button, or when an item is + * selected). + * + * @param menu The context menu that is being closed. + */ + public void onContextMenuClosed(Menu menu) { + if (mParent != null) { + mParent.onContextMenuClosed(menu); + } + } + + /** + * @deprecated Old no-arguments version of {@link #onCreateDialog(int, Bundle)}. + */ + @Deprecated + protected Dialog onCreateDialog(int id) { + return null; + } + + /** + * Callback for creating dialogs that are managed (saved and restored) for you + * by the activity. The default implementation calls through to + * {@link #onCreateDialog(int)} for compatibility. + * + * If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB} + * or later, consider instead using a {@link DialogFragment} instead. + * + *

If you use {@link #showDialog(int)}, the activity will call through to + * this method the first time, and hang onto it thereafter. Any dialog + * that is created by this method will automatically be saved and restored + * for you, including whether it is showing. + * + *

If you would like the activity to manage saving and restoring dialogs + * for you, you should override this method and handle any ids that are + * passed to {@link #showDialog}. + * + *

If you would like an opportunity to prepare your dialog before it is shown, + * override {@link #onPrepareDialog(int, Dialog, Bundle)}. + * + * @param id The id of the dialog. + * @param args The dialog arguments provided to {@link #showDialog(int, Bundle)}. + * @return The dialog. If you return null, the dialog will not be created. + * + * @see #onPrepareDialog(int, Dialog, Bundle) + * @see #showDialog(int, Bundle) + * @see #dismissDialog(int) + * @see #removeDialog(int) + * + * @deprecated Use the new {@link DialogFragment} class with + * {@link FragmentManager} instead; this is also + * available on older platforms through the Android compatibility package. + */ + @Deprecated + protected Dialog onCreateDialog(int id, Bundle args) { + return onCreateDialog(id); + } + + /** + * @deprecated Old no-arguments version of + * {@link #onPrepareDialog(int, Dialog, Bundle)}. + */ + @Deprecated + protected void onPrepareDialog(int id, Dialog dialog) { + dialog.setOwnerActivity(this); + } + + /** + * Provides an opportunity to prepare a managed dialog before it is being + * shown. The default implementation calls through to + * {@link #onPrepareDialog(int, Dialog)} for compatibility. + * + *

+ * Override this if you need to update a managed dialog based on the state + * of the application each time it is shown. For example, a time picker + * dialog might want to be updated with the current time. You should call + * through to the superclass's implementation. The default implementation + * will set this Activity as the owner activity on the Dialog. + * + * @param id The id of the managed dialog. + * @param dialog The dialog. + * @param args The dialog arguments provided to {@link #showDialog(int, Bundle)}. + * @see #onCreateDialog(int, Bundle) + * @see #showDialog(int) + * @see #dismissDialog(int) + * @see #removeDialog(int) + * + * @deprecated Use the new {@link DialogFragment} class with + * {@link FragmentManager} instead; this is also + * available on older platforms through the Android compatibility package. + */ + @Deprecated + protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { + onPrepareDialog(id, dialog); + } + + /** + * Simple version of {@link #showDialog(int, Bundle)} that does not + * take any arguments. Simply calls {@link #showDialog(int, Bundle)} + * with null arguments. + * + * @deprecated Use the new {@link DialogFragment} class with + * {@link FragmentManager} instead; this is also + * available on older platforms through the Android compatibility package. + */ + @Deprecated + public final void showDialog(int id) { + showDialog(id, null); + } + + /** + * Show a dialog managed by this activity. A call to {@link #onCreateDialog(int, Bundle)} + * will be made with the same id the first time this is called for a given + * id. From thereafter, the dialog will be automatically saved and restored. + * + * If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB} + * or later, consider instead using a {@link DialogFragment} instead. + * + *

Each time a dialog is shown, {@link #onPrepareDialog(int, Dialog, Bundle)} will + * be made to provide an opportunity to do any timely preparation. + * + * @param id The id of the managed dialog. + * @param args Arguments to pass through to the dialog. These will be saved + * and restored for you. Note that if the dialog is already created, + * {@link #onCreateDialog(int, Bundle)} will not be called with the new + * arguments but {@link #onPrepareDialog(int, Dialog, Bundle)} will be. + * If you need to rebuild the dialog, call {@link #removeDialog(int)} first. + * @return Returns true if the Dialog was created; false is returned if + * it is not created because {@link #onCreateDialog(int, Bundle)} returns false. + * + * @see Dialog + * @see #onCreateDialog(int, Bundle) + * @see #onPrepareDialog(int, Dialog, Bundle) + * @see #dismissDialog(int) + * @see #removeDialog(int) + * + * @deprecated Use the new {@link DialogFragment} class with + * {@link FragmentManager} instead; this is also + * available on older platforms through the Android compatibility package. + */ + @Deprecated + public final boolean showDialog(int id, Bundle args) { + if (mManagedDialogs == null) { + mManagedDialogs = new SparseArray(); + } + ManagedDialog md = mManagedDialogs.get(id); + if (md == null) { + md = new ManagedDialog(); + md.mDialog = createDialog(id, null, args); + if (md.mDialog == null) { + return false; + } + mManagedDialogs.put(id, md); + } + + md.mArgs = args; + onPrepareDialog(id, md.mDialog, args); + md.mDialog.show(); + return true; + } + + /** + * Dismiss a dialog that was previously shown via {@link #showDialog(int)}. + * + * @param id The id of the managed dialog. + * + * @throws IllegalArgumentException if the id was not previously shown via + * {@link #showDialog(int)}. + * + * @see #onCreateDialog(int, Bundle) + * @see #onPrepareDialog(int, Dialog, Bundle) + * @see #showDialog(int) + * @see #removeDialog(int) + * + * @deprecated Use the new {@link DialogFragment} class with + * {@link FragmentManager} instead; this is also + * available on older platforms through the Android compatibility package. + */ + @Deprecated + public final void dismissDialog(int id) { + if (mManagedDialogs == null) { + throw missingDialog(id); + } + + final ManagedDialog md = mManagedDialogs.get(id); + if (md == null) { + throw missingDialog(id); + } + md.mDialog.dismiss(); + } + + /** + * Creates an exception to throw if a user passed in a dialog id that is + * unexpected. + */ + private IllegalArgumentException missingDialog(int id) { + return new IllegalArgumentException("no dialog with id " + id + " was ever " + + "shown via Activity#showDialog"); + } + + /** + * Removes any internal references to a dialog managed by this Activity. + * If the dialog is showing, it will dismiss it as part of the clean up. + * + *

This can be useful if you know that you will never show a dialog again and + * want to avoid the overhead of saving and restoring it in the future. + * + *

As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}, this function + * will not throw an exception if you try to remove an ID that does not + * currently have an associated dialog.

+ * + * @param id The id of the managed dialog. + * + * @see #onCreateDialog(int, Bundle) + * @see #onPrepareDialog(int, Dialog, Bundle) + * @see #showDialog(int) + * @see #dismissDialog(int) + * + * @deprecated Use the new {@link DialogFragment} class with + * {@link FragmentManager} instead; this is also + * available on older platforms through the Android compatibility package. + */ + @Deprecated + public final void removeDialog(int id) { + if (mManagedDialogs != null) { + final ManagedDialog md = mManagedDialogs.get(id); + if (md != null) { + md.mDialog.dismiss(); + mManagedDialogs.remove(id); + } + } + } + + /** + * This hook is called when the user signals the desire to start a search. + * + *

You can use this function as a simple way to launch the search UI, in response to a + * menu item, search button, or other widgets within your activity. Unless overidden, + * calling this function is the same as calling + * {@link #startSearch startSearch(null, false, null, false)}, which launches + * search for the current activity as specified in its manifest, see {@link SearchManager}. + * + *

You can override this function to force global search, e.g. in response to a dedicated + * search key, or to block search entirely (by simply returning false). + * + * @return Returns {@code true} if search launched, and {@code false} if activity blocks it. + * The default implementation always returns {@code true}. + * + * @see android.app.SearchManager + */ + public boolean onSearchRequested() { + startSearch(null, false, null, false); + return true; + } + + /** + * This hook is called to launch the search UI. + * + *

It is typically called from onSearchRequested(), either directly from + * Activity.onSearchRequested() or from an overridden version in any given + * Activity. If your goal is simply to activate search, it is preferred to call + * onSearchRequested(), which may have been overriden elsewhere in your Activity. If your goal + * is to inject specific data such as context data, it is preferred to override + * onSearchRequested(), so that any callers to it will benefit from the override. + * + * @param initialQuery Any non-null non-empty string will be inserted as + * pre-entered text in the search query box. + * @param selectInitialQuery If true, the intial query will be preselected, which means that + * any further typing will replace it. This is useful for cases where an entire pre-formed + * query is being inserted. If false, the selection point will be placed at the end of the + * inserted query. This is useful when the inserted query is text that the user entered, + * and the user would expect to be able to keep typing. This parameter is only meaningful + * if initialQuery is a non-empty string. + * @param appSearchData An application can insert application-specific + * context here, in order to improve quality or specificity of its own + * searches. This data will be returned with SEARCH intent(s). Null if + * no extra data is required. + * @param globalSearch If false, this will only launch the search that has been specifically + * defined by the application (which is usually defined as a local search). If no default + * search is defined in the current application or activity, global search will be launched. + * If true, this will always launch a platform-global (e.g. web-based) search instead. + * + * @see android.app.SearchManager + * @see #onSearchRequested + */ + public void startSearch(String initialQuery, boolean selectInitialQuery, + Bundle appSearchData, boolean globalSearch) { + ensureSearchManager(); + mSearchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(), + appSearchData, globalSearch); + } + + /** + * Similar to {@link #startSearch}, but actually fires off the search query after invoking + * the search dialog. Made available for testing purposes. + * + * @param query The query to trigger. If empty, the request will be ignored. + * @param appSearchData An application can insert application-specific + * context here, in order to improve quality or specificity of its own + * searches. This data will be returned with SEARCH intent(s). Null if + * no extra data is required. + */ + public void triggerSearch(String query, Bundle appSearchData) { + ensureSearchManager(); + mSearchManager.triggerSearch(query, getComponentName(), appSearchData); + } + + /** + * Request that key events come to this activity. Use this if your + * activity has no views with focus, but the activity still wants + * a chance to process key events. + * + * @see android.view.Window#takeKeyEvents + */ + public void takeKeyEvents(boolean get) { + getWindow().takeKeyEvents(get); + } + + /** + * Enable extended window features. This is a convenience for calling + * {@link android.view.Window#requestFeature getWindow().requestFeature()}. + * + * @param featureId The desired feature as defined in + * {@link android.view.Window}. + * @return Returns true if the requested feature is supported and now + * enabled. + * + * @see android.view.Window#requestFeature + */ + public final boolean requestWindowFeature(int featureId) { + return getWindow().requestFeature(featureId); + } + + /** + * Convenience for calling + * {@link android.view.Window#setFeatureDrawableResource}. + */ + public final void setFeatureDrawableResource(int featureId, int resId) { + getWindow().setFeatureDrawableResource(featureId, resId); + } + + /** + * Convenience for calling + * {@link android.view.Window#setFeatureDrawableUri}. + */ + public final void setFeatureDrawableUri(int featureId, Uri uri) { + getWindow().setFeatureDrawableUri(featureId, uri); + } + + /** + * Convenience for calling + * {@link android.view.Window#setFeatureDrawable(int, Drawable)}. + */ + public final void setFeatureDrawable(int featureId, Drawable drawable) { + getWindow().setFeatureDrawable(featureId, drawable); + } + + /** + * Convenience for calling + * {@link android.view.Window#setFeatureDrawableAlpha}. + */ + public final void setFeatureDrawableAlpha(int featureId, int alpha) { + getWindow().setFeatureDrawableAlpha(featureId, alpha); + } + + /** + * Convenience for calling + * {@link android.view.Window#getLayoutInflater}. + */ + public LayoutInflater getLayoutInflater() { + return getWindow().getLayoutInflater(); + } + + /** + * Returns a {@link MenuInflater} with this context. + */ + public MenuInflater getMenuInflater() { + // Make sure that action views can get an appropriate theme. + if (mMenuInflater == null) { + initActionBar(); + if (mActionBar != null) { + mMenuInflater = new MenuInflater(mActionBar.getThemedContext(), this); + } else { + mMenuInflater = new MenuInflater(this); + } + } + return mMenuInflater; + } + + @Override + protected void onApplyThemeResource(Resources.Theme theme, int resid, + boolean first) { + if (mParent == null) { + super.onApplyThemeResource(theme, resid, first); + } else { + try { + theme.setTo(mParent.getTheme()); + } catch (Exception e) { + // Empty + } + theme.applyStyle(resid, false); + } + } + + /** + * Same as calling {@link #startActivityForResult(Intent, int, Bundle)} + * with no options. + * + * @param intent The intent to start. + * @param requestCode If >= 0, this code will be returned in + * onActivityResult() when the activity exits. + * + * @throws android.content.ActivityNotFoundException + * + * @see #startActivity + */ + public void startActivityForResult(Intent intent, int requestCode) { + startActivityForResult(intent, requestCode, null); + } + + /** + * Launch an activity for which you would like a result when it finished. + * When this activity exits, your + * onActivityResult() method will be called with the given requestCode. + * Using a negative requestCode is the same as calling + * {@link #startActivity} (the activity is not launched as a sub-activity). + * + *

Note that this method should only be used with Intent protocols + * that are defined to return a result. In other protocols (such as + * {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may + * not get the result when you expect. For example, if the activity you + * are launching uses the singleTask launch mode, it will not run in your + * task and thus you will immediately receive a cancel result. + * + *

As a special case, if you call startActivityForResult() with a requestCode + * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your + * activity, then your window will not be displayed until a result is + * returned back from the started activity. This is to avoid visible + * flickering when redirecting to another activity. + * + *

This method throws {@link android.content.ActivityNotFoundException} + * if there was no Activity found to run the given Intent. + * + * @param intent The intent to start. + * @param requestCode If >= 0, this code will be returned in + * onActivityResult() when the activity exits. + * @param options Additional options for how the Activity should be started. + * See {@link android.content.Context#startActivity(Intent, Bundle) + * Context.startActivity(Intent, Bundle)} for more details. + * + * @throws android.content.ActivityNotFoundException + * + * @see #startActivity + */ + public void startActivityForResult(Intent intent, int requestCode, Bundle options) { + if (mParent == null) { + Instrumentation.ActivityResult ar = + mInstrumentation.execStartActivity( + this, mMainThread.getApplicationThread(), mToken, this, + intent, requestCode, options); + if (ar != null) { + mMainThread.sendActivityResult( + mToken, mEmbeddedID, requestCode, ar.getResultCode(), + ar.getResultData()); + } + if (requestCode >= 0) { + // If this start is requesting a result, we can avoid making + // the activity visible until the result is received. Setting + // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the + // activity hidden during this time, to avoid flickering. + // This can only be done when a result is requested because + // that guarantees we will get information back when the + // activity is finished, no matter what happens to it. + mStartedActivity = true; + } + } else { + if (options != null) { + mParent.startActivityFromChild(this, intent, requestCode, options); + } else { + // Note we want to go through this method for compatibility with + // existing applications that may have overridden it. + mParent.startActivityFromChild(this, intent, requestCode); + } + } + } + + /** + * Same as calling {@link #startIntentSenderForResult(IntentSender, int, + * Intent, int, int, int, Bundle)} with no options. + * + * @param intent The IntentSender to launch. + * @param requestCode If >= 0, this code will be returned in + * onActivityResult() when the activity exits. + * @param fillInIntent If non-null, this will be provided as the + * intent parameter to {@link IntentSender#sendIntent}. + * @param flagsMask Intent flags in the original IntentSender that you + * would like to change. + * @param flagsValues Desired values for any bits set in + * flagsMask + * @param extraFlags Always set to 0. + */ + public void startIntentSenderForResult(IntentSender intent, int requestCode, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) + throws IntentSender.SendIntentException { + startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, + flagsValues, extraFlags, null); + } + + /** + * Like {@link #startActivityForResult(Intent, int)}, but allowing you + * to use a IntentSender to describe the activity to be started. If + * the IntentSender is for an activity, that activity will be started + * as if you had called the regular {@link #startActivityForResult(Intent, int)} + * here; otherwise, its associated action will be executed (such as + * sending a broadcast) as if you had called + * {@link IntentSender#sendIntent IntentSender.sendIntent} on it. + * + * @param intent The IntentSender to launch. + * @param requestCode If >= 0, this code will be returned in + * onActivityResult() when the activity exits. + * @param fillInIntent If non-null, this will be provided as the + * intent parameter to {@link IntentSender#sendIntent}. + * @param flagsMask Intent flags in the original IntentSender that you + * would like to change. + * @param flagsValues Desired values for any bits set in + * flagsMask + * @param extraFlags Always set to 0. + * @param options Additional options for how the Activity should be started. + * See {@link android.content.Context#startActivity(Intent, Bundle) + * Context.startActivity(Intent, Bundle)} for more details. If options + * have also been supplied by the IntentSender, options given here will + * override any that conflict with those given by the IntentSender. + */ + public void startIntentSenderForResult(IntentSender intent, int requestCode, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, + Bundle options) throws IntentSender.SendIntentException { + if (mParent == null) { + startIntentSenderForResultInner(intent, requestCode, fillInIntent, + flagsMask, flagsValues, this, options); + } else if (options != null) { + mParent.startIntentSenderFromChild(this, intent, requestCode, + fillInIntent, flagsMask, flagsValues, extraFlags, options); + } else { + // Note we want to go through this call for compatibility with + // existing applications that may have overridden the method. + mParent.startIntentSenderFromChild(this, intent, requestCode, + fillInIntent, flagsMask, flagsValues, extraFlags); + } + } + + private void startIntentSenderForResultInner(IntentSender intent, int requestCode, + Intent fillInIntent, int flagsMask, int flagsValues, Activity activity, + Bundle options) + throws IntentSender.SendIntentException { + try { + String resolvedType = null; + if (fillInIntent != null) { + fillInIntent.setAllowFds(false); + resolvedType = fillInIntent.resolveTypeIfNeeded(getContentResolver()); + } + int result = ActivityManagerNative.getDefault() + .startActivityIntentSender(mMainThread.getApplicationThread(), intent, + fillInIntent, resolvedType, mToken, activity.mEmbeddedID, + requestCode, flagsMask, flagsValues, options); + if (result == ActivityManager.START_CANCELED) { + throw new IntentSender.SendIntentException(); + } + Instrumentation.checkStartActivityResult(result, null); + } catch (RemoteException e) { + } + if (requestCode >= 0) { + // If this start is requesting a result, we can avoid making + // the activity visible until the result is received. Setting + // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the + // activity hidden during this time, to avoid flickering. + // This can only be done when a result is requested because + // that guarantees we will get information back when the + // activity is finished, no matter what happens to it. + mStartedActivity = true; + } + } + + /** + * Same as {@link #startActivity(Intent, Bundle)} with no options + * specified. + * + * @param intent The intent to start. + * + * @throws android.content.ActivityNotFoundException + * + * @see {@link #startActivity(Intent, Bundle)} + * @see #startActivityForResult + */ + @Override + public void startActivity(Intent intent) { + startActivity(intent, null); + } + + /** + * Launch a new activity. You will not receive any information about when + * the activity exits. This implementation overrides the base version, + * providing information about + * the activity performing the launch. Because of this additional + * information, the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag is not + * required; if not specified, the new activity will be added to the + * task of the caller. + * + *

This method throws {@link android.content.ActivityNotFoundException} + * if there was no Activity found to run the given Intent. + * + * @param intent The intent to start. + * @param options Additional options for how the Activity should be started. + * See {@link android.content.Context#startActivity(Intent, Bundle) + * Context.startActivity(Intent, Bundle)} for more details. + * + * @throws android.content.ActivityNotFoundException + * + * @see {@link #startActivity(Intent)} + * @see #startActivityForResult + */ + @Override + public void startActivity(Intent intent, Bundle options) { + if (options != null) { + startActivityForResult(intent, -1, options); + } else { + // Note we want to go through this call for compatibility with + // applications that may have overridden the method. + startActivityForResult(intent, -1); + } + } + + /** + * Same as {@link #startActivities(Intent[], Bundle)} with no options + * specified. + * + * @param intents The intents to start. + * + * @throws android.content.ActivityNotFoundException + * + * @see {@link #startActivities(Intent[], Bundle)} + * @see #startActivityForResult + */ + @Override + public void startActivities(Intent[] intents) { + startActivities(intents, null); + } + + /** + * Launch a new activity. You will not receive any information about when + * the activity exits. This implementation overrides the base version, + * providing information about + * the activity performing the launch. Because of this additional + * information, the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag is not + * required; if not specified, the new activity will be added to the + * task of the caller. + * + *

This method throws {@link android.content.ActivityNotFoundException} + * if there was no Activity found to run the given Intent. + * + * @param intents The intents to start. + * @param options Additional options for how the Activity should be started. + * See {@link android.content.Context#startActivity(Intent, Bundle) + * Context.startActivity(Intent, Bundle)} for more details. + * + * @throws android.content.ActivityNotFoundException + * + * @see {@link #startActivities(Intent[])} + * @see #startActivityForResult + */ + @Override + public void startActivities(Intent[] intents, Bundle options) { + mInstrumentation.execStartActivities(this, mMainThread.getApplicationThread(), + mToken, this, intents, options); + } + + /** + * Same as calling {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)} + * with no options. + * + * @param intent The IntentSender to launch. + * @param fillInIntent If non-null, this will be provided as the + * intent parameter to {@link IntentSender#sendIntent}. + * @param flagsMask Intent flags in the original IntentSender that you + * would like to change. + * @param flagsValues Desired values for any bits set in + * flagsMask + * @param extraFlags Always set to 0. + */ + public void startIntentSender(IntentSender intent, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) + throws IntentSender.SendIntentException { + startIntentSender(intent, fillInIntent, flagsMask, flagsValues, + extraFlags, null); + } + + /** + * Like {@link #startActivity(Intent, Bundle)}, but taking a IntentSender + * to start; see + * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int, Bundle)} + * for more information. + * + * @param intent The IntentSender to launch. + * @param fillInIntent If non-null, this will be provided as the + * intent parameter to {@link IntentSender#sendIntent}. + * @param flagsMask Intent flags in the original IntentSender that you + * would like to change. + * @param flagsValues Desired values for any bits set in + * flagsMask + * @param extraFlags Always set to 0. + * @param options Additional options for how the Activity should be started. + * See {@link android.content.Context#startActivity(Intent, Bundle) + * Context.startActivity(Intent, Bundle)} for more details. If options + * have also been supplied by the IntentSender, options given here will + * override any that conflict with those given by the IntentSender. + */ + public void startIntentSender(IntentSender intent, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, + Bundle options) throws IntentSender.SendIntentException { + if (options != null) { + startIntentSenderForResult(intent, -1, fillInIntent, flagsMask, + flagsValues, extraFlags, options); + } else { + // Note we want to go through this call for compatibility with + // applications that may have overridden the method. + startIntentSenderForResult(intent, -1, fillInIntent, flagsMask, + flagsValues, extraFlags); + } + } + + /** + * Same as calling {@link #startActivityIfNeeded(Intent, int, Bundle)} + * with no options. + * + * @param intent The intent to start. + * @param requestCode If >= 0, this code will be returned in + * onActivityResult() when the activity exits, as described in + * {@link #startActivityForResult}. + * + * @return If a new activity was launched then true is returned; otherwise + * false is returned and you must handle the Intent yourself. + * + * @see #startActivity + * @see #startActivityForResult + */ + public boolean startActivityIfNeeded(Intent intent, int requestCode) { + return startActivityIfNeeded(intent, requestCode, null); + } + + /** + * A special variation to launch an activity only if a new activity + * instance is needed to handle the given Intent. In other words, this is + * just like {@link #startActivityForResult(Intent, int)} except: if you are + * using the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} flag, or + * singleTask or singleTop + * {@link android.R.styleable#AndroidManifestActivity_launchMode launchMode}, + * and the activity + * that handles intent is the same as your currently running + * activity, then a new instance is not needed. In this case, instead of + * the normal behavior of calling {@link #onNewIntent} this function will + * return and you can handle the Intent yourself. + * + *

This function can only be called from a top-level activity; if it is + * called from a child activity, a runtime exception will be thrown. + * + * @param intent The intent to start. + * @param requestCode If >= 0, this code will be returned in + * onActivityResult() when the activity exits, as described in + * {@link #startActivityForResult}. + * @param options Additional options for how the Activity should be started. + * See {@link android.content.Context#startActivity(Intent, Bundle) + * Context.startActivity(Intent, Bundle)} for more details. + * + * @return If a new activity was launched then true is returned; otherwise + * false is returned and you must handle the Intent yourself. + * + * @see #startActivity + * @see #startActivityForResult + */ + public boolean startActivityIfNeeded(Intent intent, int requestCode, Bundle options) { + if (mParent == null) { + int result = ActivityManager.START_RETURN_INTENT_TO_CALLER; + try { + intent.setAllowFds(false); + result = ActivityManagerNative.getDefault() + .startActivity(mMainThread.getApplicationThread(), + intent, intent.resolveTypeIfNeeded(getContentResolver()), + mToken, mEmbeddedID, requestCode, + ActivityManager.START_FLAG_ONLY_IF_NEEDED, null, null, + options); + } catch (RemoteException e) { + // Empty + } + + Instrumentation.checkStartActivityResult(result, intent); + + if (requestCode >= 0) { + // If this start is requesting a result, we can avoid making + // the activity visible until the result is received. Setting + // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the + // activity hidden during this time, to avoid flickering. + // This can only be done when a result is requested because + // that guarantees we will get information back when the + // activity is finished, no matter what happens to it. + mStartedActivity = true; + } + return result != ActivityManager.START_RETURN_INTENT_TO_CALLER; + } + + throw new UnsupportedOperationException( + "startActivityIfNeeded can only be called from a top-level activity"); + } + + /** + * Same as calling {@link #startNextMatchingActivity(Intent, Bundle)} with + * no options. + * + * @param intent The intent to dispatch to the next activity. For + * correct behavior, this must be the same as the Intent that started + * your own activity; the only changes you can make are to the extras + * inside of it. + * + * @return Returns a boolean indicating whether there was another Activity + * to start: true if there was a next activity to start, false if there + * wasn't. In general, if true is returned you will then want to call + * finish() on yourself. + */ + public boolean startNextMatchingActivity(Intent intent) { + return startNextMatchingActivity(intent, null); + } + + /** + * Special version of starting an activity, for use when you are replacing + * other activity components. You can use this to hand the Intent off + * to the next Activity that can handle it. You typically call this in + * {@link #onCreate} with the Intent returned by {@link #getIntent}. + * + * @param intent The intent to dispatch to the next activity. For + * correct behavior, this must be the same as the Intent that started + * your own activity; the only changes you can make are to the extras + * inside of it. + * @param options Additional options for how the Activity should be started. + * See {@link android.content.Context#startActivity(Intent, Bundle) + * Context.startActivity(Intent, Bundle)} for more details. + * + * @return Returns a boolean indicating whether there was another Activity + * to start: true if there was a next activity to start, false if there + * wasn't. In general, if true is returned you will then want to call + * finish() on yourself. + */ + public boolean startNextMatchingActivity(Intent intent, Bundle options) { + if (mParent == null) { + try { + intent.setAllowFds(false); + return ActivityManagerNative.getDefault() + .startNextMatchingActivity(mToken, intent, options); + } catch (RemoteException e) { + // Empty + } + return false; + } + + throw new UnsupportedOperationException( + "startNextMatchingActivity can only be called from a top-level activity"); + } + + /** + * Same as calling {@link #startActivityFromChild(Activity, Intent, int, Bundle)} + * with no options. + * + * @param child The activity making the call. + * @param intent The intent to start. + * @param requestCode Reply request code. < 0 if reply is not requested. + * + * @throws android.content.ActivityNotFoundException + * + * @see #startActivity + * @see #startActivityForResult + */ + public void startActivityFromChild(Activity child, Intent intent, + int requestCode) { + startActivityFromChild(child, intent, requestCode, null); + } + + /** + * This is called when a child activity of this one calls its + * {@link #startActivity} or {@link #startActivityForResult} method. + * + *

This method throws {@link android.content.ActivityNotFoundException} + * if there was no Activity found to run the given Intent. + * + * @param child The activity making the call. + * @param intent The intent to start. + * @param requestCode Reply request code. < 0 if reply is not requested. + * @param options Additional options for how the Activity should be started. + * See {@link android.content.Context#startActivity(Intent, Bundle) + * Context.startActivity(Intent, Bundle)} for more details. + * + * @throws android.content.ActivityNotFoundException + * + * @see #startActivity + * @see #startActivityForResult + */ + public void startActivityFromChild(Activity child, Intent intent, + int requestCode, Bundle options) { + Instrumentation.ActivityResult ar = + mInstrumentation.execStartActivity( + this, mMainThread.getApplicationThread(), mToken, child, + intent, requestCode, options); + if (ar != null) { + mMainThread.sendActivityResult( + mToken, child.mEmbeddedID, requestCode, + ar.getResultCode(), ar.getResultData()); + } + } + + /** + * Same as calling {@link #startActivityFromFragment(Fragment, Intent, int, Bundle)} + * with no options. + * + * @param fragment The fragment making the call. + * @param intent The intent to start. + * @param requestCode Reply request code. < 0 if reply is not requested. + * + * @throws android.content.ActivityNotFoundException + * + * @see Fragment#startActivity + * @see Fragment#startActivityForResult + */ + public void startActivityFromFragment(Fragment fragment, Intent intent, + int requestCode) { + startActivityFromFragment(fragment, intent, requestCode, null); + } + + /** + * This is called when a Fragment in this activity calls its + * {@link Fragment#startActivity} or {@link Fragment#startActivityForResult} + * method. + * + *

This method throws {@link android.content.ActivityNotFoundException} + * if there was no Activity found to run the given Intent. + * + * @param fragment The fragment making the call. + * @param intent The intent to start. + * @param requestCode Reply request code. < 0 if reply is not requested. + * @param options Additional options for how the Activity should be started. + * See {@link android.content.Context#startActivity(Intent, Bundle) + * Context.startActivity(Intent, Bundle)} for more details. + * + * @throws android.content.ActivityNotFoundException + * + * @see Fragment#startActivity + * @see Fragment#startActivityForResult + */ + public void startActivityFromFragment(Fragment fragment, Intent intent, + int requestCode, Bundle options) { + Instrumentation.ActivityResult ar = + mInstrumentation.execStartActivity( + this, mMainThread.getApplicationThread(), mToken, fragment, + intent, requestCode, options); + if (ar != null) { + mMainThread.sendActivityResult( + mToken, fragment.mWho, requestCode, + ar.getResultCode(), ar.getResultData()); + } + } + + /** + * Same as calling {@link #startIntentSenderFromChild(Activity, IntentSender, + * int, Intent, int, int, int, Bundle)} with no options. + */ + public void startIntentSenderFromChild(Activity child, IntentSender intent, + int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, + int extraFlags) + throws IntentSender.SendIntentException { + startIntentSenderFromChild(child, intent, requestCode, fillInIntent, + flagsMask, flagsValues, extraFlags, null); + } + + /** + * Like {@link #startActivityFromChild(Activity, Intent, int)}, but + * taking a IntentSender; see + * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} + * for more information. + */ + public void startIntentSenderFromChild(Activity child, IntentSender intent, + int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, + int extraFlags, Bundle options) + throws IntentSender.SendIntentException { + startIntentSenderForResultInner(intent, requestCode, fillInIntent, + flagsMask, flagsValues, child, options); + } + + /** + * Call immediately after one of the flavors of {@link #startActivity(Intent)} + * or {@link #finish} to specify an explicit transition animation to + * perform next. + * + *

As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN} an alternative + * to using this with starting activities is to supply the desired animation + * information through a {@link ActivityOptions} bundle to + * {@link #startActivity(Intent, Bundle) or a related function. This allows + * you to specify a custom animation even when starting an activity from + * outside the context of the current top activity. + * + * @param enterAnim A resource ID of the animation resource to use for + * the incoming activity. Use 0 for no animation. + * @param exitAnim A resource ID of the animation resource to use for + * the outgoing activity. Use 0 for no animation. + */ + public void overridePendingTransition(int enterAnim, int exitAnim) { + try { + ActivityManagerNative.getDefault().overridePendingTransition( + mToken, getPackageName(), enterAnim, exitAnim); + } catch (RemoteException e) { + } + } + + /** + * Call this to set the result that your activity will return to its + * caller. + * + * @param resultCode The result code to propagate back to the originating + * activity, often RESULT_CANCELED or RESULT_OK + * + * @see #RESULT_CANCELED + * @see #RESULT_OK + * @see #RESULT_FIRST_USER + * @see #setResult(int, Intent) + */ + public final void setResult(int resultCode) { + synchronized (this) { + mResultCode = resultCode; + mResultData = null; + } + } + + /** + * Call this to set the result that your activity will return to its + * caller. + * + *

As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}, the Intent + * you supply here can have {@link Intent#FLAG_GRANT_READ_URI_PERMISSION + * Intent.FLAG_GRANT_READ_URI_PERMISSION} and/or {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION + * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} set. This will grant the + * Activity receiving the result access to the specific URIs in the Intent. + * Access will remain until the Activity has finished (it will remain across the hosting + * process being killed and other temporary destruction) and will be added + * to any existing set of URI permissions it already holds. + * + * @param resultCode The result code to propagate back to the originating + * activity, often RESULT_CANCELED or RESULT_OK + * @param data The data to propagate back to the originating activity. + * + * @see #RESULT_CANCELED + * @see #RESULT_OK + * @see #RESULT_FIRST_USER + * @see #setResult(int) + */ + public final void setResult(int resultCode, Intent data) { + synchronized (this) { + mResultCode = resultCode; + mResultData = data; + } + } + + /** + * Return the name of the package that invoked this activity. This is who + * the data in {@link #setResult setResult()} will be sent to. You can + * use this information to validate that the recipient is allowed to + * receive the data. + * + *

Note: if the calling activity is not expecting a result (that is it + * did not use the {@link #startActivityForResult} + * form that includes a request code), then the calling package will be + * null. + * + * @return The package of the activity that will receive your + * reply, or null if none. + */ + public String getCallingPackage() { + try { + return ActivityManagerNative.getDefault().getCallingPackage(mToken); + } catch (RemoteException e) { + return null; + } + } + + /** + * Return the name of the activity that invoked this activity. This is + * who the data in {@link #setResult setResult()} will be sent to. You + * can use this information to validate that the recipient is allowed to + * receive the data. + * + *

Note: if the calling activity is not expecting a result (that is it + * did not use the {@link #startActivityForResult} + * form that includes a request code), then the calling package will be + * null. + * + * @return String The full name of the activity that will receive your + * reply, or null if none. + */ + public ComponentName getCallingActivity() { + try { + return ActivityManagerNative.getDefault().getCallingActivity(mToken); + } catch (RemoteException e) { + return null; + } + } + + /** + * Control whether this activity's main window is visible. This is intended + * only for the special case of an activity that is not going to show a + * UI itself, but can't just finish prior to onResume() because it needs + * to wait for a service binding or such. Setting this to false allows + * you to prevent your UI from being shown during that time. + * + *

The default value for this is taken from the + * {@link android.R.attr#windowNoDisplay} attribute of the activity's theme. + */ + public void setVisible(boolean visible) { + if (mVisibleFromClient != visible) { + mVisibleFromClient = visible; + if (mVisibleFromServer) { + if (visible) makeVisible(); + else mDecor.setVisibility(View.INVISIBLE); + } + } + } + + void makeVisible() { + if (!mWindowAdded) { + ViewManager wm = getWindowManager(); + wm.addView(mDecor, getWindow().getAttributes()); + mWindowAdded = true; + } + mDecor.setVisibility(View.VISIBLE); + } + + /** + * Check to see whether this activity is in the process of finishing, + * either because you called {@link #finish} on it or someone else + * has requested that it finished. This is often used in + * {@link #onPause} to determine whether the activity is simply pausing or + * completely finishing. + * + * @return If the activity is finishing, returns true; else returns false. + * + * @see #finish + */ + public boolean isFinishing() { + return mFinished; + } + + /** + * Check to see whether this activity is in the process of being destroyed in order to be + * recreated with a new configuration. This is often used in + * {@link #onStop} to determine whether the state needs to be cleaned up or will be passed + * on to the next instance of the activity via {@link #onRetainNonConfigurationInstance()}. + * + * @return If the activity is being torn down in order to be recreated with a new configuration, + * returns true; else returns false. + */ + public boolean isChangingConfigurations() { + return mChangingConfigurations; + } + + /** + * Cause this Activity to be recreated with a new instance. This results + * in essentially the same flow as when the Activity is created due to + * a configuration change -- the current instance will go through its + * lifecycle to {@link #onDestroy} and a new instance then created after it. + */ + public void recreate() { + if (mParent != null) { + throw new IllegalStateException("Can only be called on top-level activity"); + } + if (Looper.myLooper() != mMainThread.getLooper()) { + throw new IllegalStateException("Must be called from main thread"); + } + mMainThread.requestRelaunchActivity(mToken, null, null, 0, false, null, false); + } + + /** + * Call this when your activity is done and should be closed. The + * ActivityResult is propagated back to whoever launched you via + * onActivityResult(). + */ + public void finish() { + if (mParent == null) { + int resultCode; + Intent resultData; + synchronized (this) { + resultCode = mResultCode; + resultData = mResultData; + } + if (false) Log.v(TAG, "Finishing self: token=" + mToken); + try { + if (resultData != null) { + resultData.setAllowFds(false); + } + if (ActivityManagerNative.getDefault() + .finishActivity(mToken, resultCode, resultData)) { + mFinished = true; + } + } catch (RemoteException e) { + // Empty + } + } else { + mParent.finishFromChild(this); + } + } + + /** + * Finish this activity as well as all activities immediately below it + * in the current task that have the same affinity. This is typically + * used when an application can be launched on to another task (such as + * from an ACTION_VIEW of a content type it understands) and the user + * has used the up navigation to switch out of the current task and in + * to its own task. In this case, if the user has navigated down into + * any other activities of the second application, all of those should + * be removed from the original task as part of the task switch. + * + *

Note that this finish does not allow you to deliver results + * to the previous activity, and an exception will be thrown if you are trying + * to do so.

+ */ + public void finishAffinity() { + if (mParent != null) { + throw new IllegalStateException("Can not be called from an embedded activity"); + } + if (mResultCode != RESULT_CANCELED || mResultData != null) { + throw new IllegalStateException("Can not be called to deliver a result"); + } + try { + if (ActivityManagerNative.getDefault().finishActivityAffinity(mToken)) { + mFinished = true; + } + } catch (RemoteException e) { + // Empty + } + } + + /** + * This is called when a child activity of this one calls its + * {@link #finish} method. The default implementation simply calls + * finish() on this activity (the parent), finishing the entire group. + * + * @param child The activity making the call. + * + * @see #finish + */ + public void finishFromChild(Activity child) { + finish(); + } + + /** + * Force finish another activity that you had previously started with + * {@link #startActivityForResult}. + * + * @param requestCode The request code of the activity that you had + * given to startActivityForResult(). If there are multiple + * activities started with this request code, they + * will all be finished. + */ + public void finishActivity(int requestCode) { + if (mParent == null) { + try { + ActivityManagerNative.getDefault() + .finishSubActivity(mToken, mEmbeddedID, requestCode); + } catch (RemoteException e) { + // Empty + } + } else { + mParent.finishActivityFromChild(this, requestCode); + } + } + + /** + * This is called when a child activity of this one calls its + * finishActivity(). + * + * @param child The activity making the call. + * @param requestCode Request code that had been used to start the + * activity. + */ + public void finishActivityFromChild(Activity child, int requestCode) { + try { + ActivityManagerNative.getDefault() + .finishSubActivity(mToken, child.mEmbeddedID, requestCode); + } catch (RemoteException e) { + // Empty + } + } + + /** + * Called when an activity you launched exits, giving you the requestCode + * you started it with, the resultCode it returned, and any additional + * data from it. The resultCode will be + * {@link #RESULT_CANCELED} if the activity explicitly returned that, + * didn't return any result, or crashed during its operation. + * + *

You will receive this call immediately before onResume() when your + * activity is re-starting. + * + * @param requestCode The integer request code originally supplied to + * startActivityForResult(), allowing you to identify who this + * result came from. + * @param resultCode The integer result code returned by the child activity + * through its setResult(). + * @param data An Intent, which can return result data to the caller + * (various data can be attached to Intent "extras"). + * + * @see #startActivityForResult + * @see #createPendingResult + * @see #setResult(int) + */ + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + } + + /** + * Create a new PendingIntent object which you can hand to others + * for them to use to send result data back to your + * {@link #onActivityResult} callback. The created object will be either + * one-shot (becoming invalid after a result is sent back) or multiple + * (allowing any number of results to be sent through it). + * + * @param requestCode Private request code for the sender that will be + * associated with the result data when it is returned. The sender can not + * modify this value, allowing you to identify incoming results. + * @param data Default data to supply in the result, which may be modified + * by the sender. + * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT PendingIntent.FLAG_ONE_SHOT}, + * {@link PendingIntent#FLAG_NO_CREATE PendingIntent.FLAG_NO_CREATE}, + * {@link PendingIntent#FLAG_CANCEL_CURRENT PendingIntent.FLAG_CANCEL_CURRENT}, + * {@link PendingIntent#FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT}, + * or any of the flags as supported by + * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts + * of the intent that can be supplied when the actual send happens. + * + * @return Returns an existing or new PendingIntent matching the given + * parameters. May return null only if + * {@link PendingIntent#FLAG_NO_CREATE PendingIntent.FLAG_NO_CREATE} has been + * supplied. + * + * @see PendingIntent + */ + public PendingIntent createPendingResult(int requestCode, Intent data, + int flags) { + String packageName = getPackageName(); + try { + data.setAllowFds(false); + IIntentSender target = + ActivityManagerNative.getDefault().getIntentSender( + ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, + mParent == null ? mToken : mParent.mToken, + mEmbeddedID, requestCode, new Intent[] { data }, null, flags, null); + return target != null ? new PendingIntent(target) : null; + } catch (RemoteException e) { + // Empty + } + return null; + } + + /** + * Change the desired orientation of this activity. If the activity + * is currently in the foreground or otherwise impacting the screen + * orientation, the screen will immediately be changed (possibly causing + * the activity to be restarted). Otherwise, this will be used the next + * time the activity is visible. + * + * @param requestedOrientation An orientation constant as used in + * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}. + */ + public void setRequestedOrientation(int requestedOrientation) { + if (mParent == null) { + try { + ActivityManagerNative.getDefault().setRequestedOrientation( + mToken, requestedOrientation); + } catch (RemoteException e) { + // Empty + } + } else { + mParent.setRequestedOrientation(requestedOrientation); + } + } + + /** + * Return the current requested orientation of the activity. This will + * either be the orientation requested in its component's manifest, or + * the last requested orientation given to + * {@link #setRequestedOrientation(int)}. + * + * @return Returns an orientation constant as used in + * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}. + */ + public int getRequestedOrientation() { + if (mParent == null) { + try { + return ActivityManagerNative.getDefault() + .getRequestedOrientation(mToken); + } catch (RemoteException e) { + // Empty + } + } else { + return mParent.getRequestedOrientation(); + } + return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + } + + /** + * Return the identifier of the task this activity is in. This identifier + * will remain the same for the lifetime of the activity. + * + * @return Task identifier, an opaque integer. + */ + public int getTaskId() { + try { + return ActivityManagerNative.getDefault() + .getTaskForActivity(mToken, false); + } catch (RemoteException e) { + return -1; + } + } + + /** + * Return whether this activity is the root of a task. The root is the + * first activity in a task. + * + * @return True if this is the root activity, else false. + */ + public boolean isTaskRoot() { + try { + return ActivityManagerNative.getDefault() + .getTaskForActivity(mToken, true) >= 0; + } catch (RemoteException e) { + return false; + } + } + + /** + * Move the task containing this activity to the back of the activity + * stack. The activity's order within the task is unchanged. + * + * @param nonRoot If false then this only works if the activity is the root + * of a task; if true it will work for any activity in + * a task. + * + * @return If the task was moved (or it was already at the + * back) true is returned, else false. + */ + public boolean moveTaskToBack(boolean nonRoot) { + try { + return ActivityManagerNative.getDefault().moveActivityTaskToBack( + mToken, nonRoot); + } catch (RemoteException e) { + // Empty + } + return false; + } + + /** + * Returns class name for this activity with the package prefix removed. + * This is the default name used to read and write settings. + * + * @return The local class name. + */ + public String getLocalClassName() { + final String pkg = getPackageName(); + final String cls = mComponent.getClassName(); + int packageLen = pkg.length(); + if (!cls.startsWith(pkg) || cls.length() <= packageLen + || cls.charAt(packageLen) != '.') { + return cls; + } + return cls.substring(packageLen+1); + } + + /** + * Returns complete component name of this activity. + * + * @return Returns the complete component name for this activity + */ + public ComponentName getComponentName() + { + return mComponent; + } + + /** + * Retrieve a {@link SharedPreferences} object for accessing preferences + * that are private to this activity. This simply calls the underlying + * {@link #getSharedPreferences(String, int)} method by passing in this activity's + * class name as the preferences name. + * + * @param mode Operating mode. Use {@link #MODE_PRIVATE} for the default + * operation, {@link #MODE_WORLD_READABLE} and + * {@link #MODE_WORLD_WRITEABLE} to control permissions. + * + * @return Returns the single SharedPreferences instance that can be used + * to retrieve and modify the preference values. + */ + public SharedPreferences getPreferences(int mode) { + return getSharedPreferences(getLocalClassName(), mode); + } + + private void ensureSearchManager() { + if (mSearchManager != null) { + return; + } + + mSearchManager = new SearchManager(this, null); + } + + @Override + public Object getSystemService(String name) { + if (getBaseContext() == null) { + throw new IllegalStateException( + "System services not available to Activities before onCreate()"); + } + + if (WINDOW_SERVICE.equals(name)) { + return mWindowManager; + } else if (SEARCH_SERVICE.equals(name)) { + ensureSearchManager(); + return mSearchManager; + } + return super.getSystemService(name); + } + + /** + * Change the title associated with this activity. If this is a + * top-level activity, the title for its window will change. If it + * is an embedded activity, the parent can do whatever it wants + * with it. + */ + public void setTitle(CharSequence title) { + mTitle = title; + onTitleChanged(title, mTitleColor); + + if (mParent != null) { + mParent.onChildTitleChanged(this, title); + } + } + + /** + * Change the title associated with this activity. If this is a + * top-level activity, the title for its window will change. If it + * is an embedded activity, the parent can do whatever it wants + * with it. + */ + public void setTitle(int titleId) { + setTitle(getText(titleId)); + } + + public void setTitleColor(int textColor) { + mTitleColor = textColor; + onTitleChanged(mTitle, textColor); + } + + public final CharSequence getTitle() { + return mTitle; + } + + public final int getTitleColor() { + return mTitleColor; + } + + protected void onTitleChanged(CharSequence title, int color) { + if (mTitleReady) { + final Window win = getWindow(); + if (win != null) { + win.setTitle(title); + if (color != 0) { + win.setTitleColor(color); + } + } + } + } + + protected void onChildTitleChanged(Activity childActivity, CharSequence title) { + } + + /** + * Sets the visibility of the progress bar in the title. + *

+ * In order for the progress bar to be shown, the feature must be requested + * via {@link #requestWindowFeature(int)}. + * + * @param visible Whether to show the progress bars in the title. + */ + public final void setProgressBarVisibility(boolean visible) { + getWindow().setFeatureInt(Window.FEATURE_PROGRESS, visible ? Window.PROGRESS_VISIBILITY_ON : + Window.PROGRESS_VISIBILITY_OFF); + } + + /** + * Sets the visibility of the indeterminate progress bar in the title. + *

+ * In order for the progress bar to be shown, the feature must be requested + * via {@link #requestWindowFeature(int)}. + * + * @param visible Whether to show the progress bars in the title. + */ + public final void setProgressBarIndeterminateVisibility(boolean visible) { + getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS, + visible ? Window.PROGRESS_VISIBILITY_ON : Window.PROGRESS_VISIBILITY_OFF); + } + + /** + * Sets whether the horizontal progress bar in the title should be indeterminate (the circular + * is always indeterminate). + *

+ * In order for the progress bar to be shown, the feature must be requested + * via {@link #requestWindowFeature(int)}. + * + * @param indeterminate Whether the horizontal progress bar should be indeterminate. + */ + public final void setProgressBarIndeterminate(boolean indeterminate) { + getWindow().setFeatureInt(Window.FEATURE_PROGRESS, + indeterminate ? Window.PROGRESS_INDETERMINATE_ON : Window.PROGRESS_INDETERMINATE_OFF); + } + + /** + * Sets the progress for the progress bars in the title. + *

+ * In order for the progress bar to be shown, the feature must be requested + * via {@link #requestWindowFeature(int)}. + * + * @param progress The progress for the progress bar. Valid ranges are from + * 0 to 10000 (both inclusive). If 10000 is given, the progress + * bar will be completely filled and will fade out. + */ + public final void setProgress(int progress) { + getWindow().setFeatureInt(Window.FEATURE_PROGRESS, progress + Window.PROGRESS_START); + } + + /** + * Sets the secondary progress for the progress bar in the title. This + * progress is drawn between the primary progress (set via + * {@link #setProgress(int)} and the background. It can be ideal for media + * scenarios such as showing the buffering progress while the default + * progress shows the play progress. + *

+ * In order for the progress bar to be shown, the feature must be requested + * via {@link #requestWindowFeature(int)}. + * + * @param secondaryProgress The secondary progress for the progress bar. Valid ranges are from + * 0 to 10000 (both inclusive). + */ + public final void setSecondaryProgress(int secondaryProgress) { + getWindow().setFeatureInt(Window.FEATURE_PROGRESS, + secondaryProgress + Window.PROGRESS_SECONDARY_START); + } + + /** + * Suggests an audio stream whose volume should be changed by the hardware + * volume controls. + *

+ * The suggested audio stream will be tied to the window of this Activity. + * If the Activity is switched, the stream set here is no longer the + * suggested stream. The client does not need to save and restore the old + * suggested stream value in onPause and onResume. + * + * @param streamType The type of the audio stream whose volume should be + * changed by the hardware volume controls. It is not guaranteed that + * the hardware volume controls will always change this stream's + * volume (for example, if a call is in progress, its stream's volume + * may be changed instead). To reset back to the default, use + * {@link AudioManager#USE_DEFAULT_STREAM_TYPE}. + */ + public final void setVolumeControlStream(int streamType) { + getWindow().setVolumeControlStream(streamType); + } + + /** + * Gets the suggested audio stream whose volume should be changed by the + * harwdare volume controls. + * + * @return The suggested audio stream type whose volume should be changed by + * the hardware volume controls. + * @see #setVolumeControlStream(int) + */ + public final int getVolumeControlStream() { + return getWindow().getVolumeControlStream(); + } + + /** + * Runs the specified action on the UI thread. If the current thread is the UI + * thread, then the action is executed immediately. If the current thread is + * not the UI thread, the action is posted to the event queue of the UI thread. + * + * @param action the action to run on the UI thread + */ + public final void runOnUiThread(Runnable action) { + if (Thread.currentThread() != mUiThread) { + mHandler.post(action); + } else { + action.run(); + } + } + + /** + * Standard implementation of + * {@link android.view.LayoutInflater.Factory#onCreateView} used when + * inflating with the LayoutInflater returned by {@link #getSystemService}. + * This implementation does nothing and is for + * pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB} apps. Newer apps + * should use {@link #onCreateView(View, String, Context, AttributeSet)}. + * + * @see android.view.LayoutInflater#createView + * @see android.view.Window#getLayoutInflater + */ + public View onCreateView(String name, Context context, AttributeSet attrs) { + return null; + } + + /** + * Standard implementation of + * {@link android.view.LayoutInflater.Factory2#onCreateView(View, String, Context, AttributeSet)} + * used when inflating with the LayoutInflater returned by {@link #getSystemService}. + * This implementation handles tags to embed fragments inside + * of the activity. + * + * @see android.view.LayoutInflater#createView + * @see android.view.Window#getLayoutInflater + */ + public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { + if (!"fragment".equals(name)) { + return onCreateView(name, context, attrs); + } + + String fname = attrs.getAttributeValue(null, "class"); + TypedArray a = + context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment); + if (fname == null) { + fname = a.getString(com.android.internal.R.styleable.Fragment_name); + } + int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, View.NO_ID); + String tag = a.getString(com.android.internal.R.styleable.Fragment_tag); + a.recycle(); + + int containerId = parent != null ? parent.getId() : 0; + if (containerId == View.NO_ID && id == View.NO_ID && tag == null) { + throw new IllegalArgumentException(attrs.getPositionDescription() + + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname); + } + + // If we restored from a previous state, we may already have + // instantiated this fragment from the state and should use + // that instance instead of making a new one. + Fragment fragment = id != View.NO_ID ? mFragments.findFragmentById(id) : null; + if (fragment == null && tag != null) { + fragment = mFragments.findFragmentByTag(tag); + } + if (fragment == null && containerId != View.NO_ID) { + fragment = mFragments.findFragmentById(containerId); + } + + if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x" + + Integer.toHexString(id) + " fname=" + fname + + " existing=" + fragment); + if (fragment == null) { + fragment = Fragment.instantiate(this, fname); + fragment.mFromLayout = true; + fragment.mFragmentId = id != 0 ? id : containerId; + fragment.mContainerId = containerId; + fragment.mTag = tag; + fragment.mInLayout = true; + fragment.mFragmentManager = mFragments; + fragment.onInflate(this, attrs, fragment.mSavedFragmentState); + mFragments.addFragment(fragment, true); + + } else if (fragment.mInLayout) { + // A fragment already exists and it is not one we restored from + // previous state. + throw new IllegalArgumentException(attrs.getPositionDescription() + + ": Duplicate id 0x" + Integer.toHexString(id) + + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId) + + " with another fragment for " + fname); + } else { + // This fragment was retained from a previous instance; get it + // going now. + fragment.mInLayout = true; + // If this fragment is newly instantiated (either right now, or + // from last saved state), then give it the attributes to + // initialize itself. + if (!fragment.mRetaining) { + fragment.onInflate(this, attrs, fragment.mSavedFragmentState); + } + mFragments.moveToState(fragment); + } + + if (fragment.mView == null) { + throw new IllegalStateException("Fragment " + fname + + " did not create a view."); + } + if (id != 0) { + fragment.mView.setId(id); + } + if (fragment.mView.getTag() == null) { + fragment.mView.setTag(tag); + } + return fragment.mView; + } + + /** + * Print the Activity's state into the given stream. This gets invoked if + * you run "adb shell dumpsys activity <activity_component_name>". + * + * @param prefix Desired prefix to prepend at each line of output. + * @param fd The raw file descriptor that the dump is being sent to. + * @param writer The PrintWriter to which you should dump your state. This will be + * closed for you after you return. + * @param args additional arguments to the dump request. + */ + public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + writer.print(prefix); writer.print("Local Activity "); + writer.print(Integer.toHexString(System.identityHashCode(this))); + writer.println(" State:"); + String innerPrefix = prefix + " "; + writer.print(innerPrefix); writer.print("mResumed="); + writer.print(mResumed); writer.print(" mStopped="); + writer.print(mStopped); writer.print(" mFinished="); + writer.println(mFinished); + writer.print(innerPrefix); writer.print("mLoadersStarted="); + writer.println(mLoadersStarted); + writer.print(innerPrefix); writer.print("mChangingConfigurations="); + writer.println(mChangingConfigurations); + writer.print(innerPrefix); writer.print("mCurrentConfig="); + writer.println(mCurrentConfig); + if (mLoaderManager != null) { + writer.print(prefix); writer.print("Loader Manager "); + writer.print(Integer.toHexString(System.identityHashCode(mLoaderManager))); + writer.println(":"); + mLoaderManager.dump(prefix + " ", fd, writer, args); + } + mFragments.dump(prefix, fd, writer, args); + } + + /** + * Bit indicating that this activity is "immersive" and should not be + * interrupted by notifications if possible. + * + * This value is initially set by the manifest property + * android:immersive but may be changed at runtime by + * {@link #setImmersive}. + * + * @see android.content.pm.ActivityInfo#FLAG_IMMERSIVE + * @hide + */ + public boolean isImmersive() { + try { + return ActivityManagerNative.getDefault().isImmersive(mToken); + } catch (RemoteException e) { + return false; + } + } + + /** + * Adjust the current immersive mode setting. + * + * Note that changing this value will have no effect on the activity's + * {@link android.content.pm.ActivityInfo} structure; that is, if + * android:immersive is set to true + * in the application's manifest entry for this activity, the {@link + * android.content.pm.ActivityInfo#flags ActivityInfo.flags} member will + * always have its {@link android.content.pm.ActivityInfo#FLAG_IMMERSIVE + * FLAG_IMMERSIVE} bit set. + * + * @see #isImmersive + * @see android.content.pm.ActivityInfo#FLAG_IMMERSIVE + * @hide + */ + public void setImmersive(boolean i) { + try { + ActivityManagerNative.getDefault().setImmersive(mToken, i); + } catch (RemoteException e) { + // pass + } + } + + /** + * Start an action mode. + * + * @param callback Callback that will manage lifecycle events for this context mode + * @return The ContextMode that was started, or null if it was canceled + * + * @see ActionMode + */ + public ActionMode startActionMode(ActionMode.Callback callback) { + return mWindow.getDecorView().startActionMode(callback); + } + + /** + * Give the Activity a chance to control the UI for an action mode requested + * by the system. + * + *

Note: If you are looking for a notification callback that an action mode + * has been started for this activity, see {@link #onActionModeStarted(ActionMode)}.

+ * + * @param callback The callback that should control the new action mode + * @return The new action mode, or null if the activity does not want to + * provide special handling for this action mode. (It will be handled by the system.) + */ + public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { + initActionBar(); + if (mActionBar != null) { + return mActionBar.startActionMode(callback); + } + return null; + } + + /** + * Notifies the Activity that an action mode has been started. + * Activity subclasses overriding this method should call the superclass implementation. + * + * @param mode The new action mode. + */ + public void onActionModeStarted(ActionMode mode) { + } + + /** + * Notifies the activity that an action mode has finished. + * Activity subclasses overriding this method should call the superclass implementation. + * + * @param mode The action mode that just finished. + */ + public void onActionModeFinished(ActionMode mode) { + } + + /** + * Returns true if the app should recreate the task when navigating 'up' from this activity + * by using targetIntent. + * + *

If this method returns false the app can trivially call + * {@link #navigateUpTo(Intent)} using the same parameters to correctly perform + * up navigation. If this method returns false, the app should synthesize a new task stack + * by using {@link TaskStackBuilder} or another similar mechanism to perform up navigation.

+ * + * @param targetIntent An intent representing the target destination for up navigation + * @return true if navigating up should recreate a new task stack, false if the same task + * should be used for the destination + */ + public boolean shouldUpRecreateTask(Intent targetIntent) { + try { + PackageManager pm = getPackageManager(); + ComponentName cn = targetIntent.getComponent(); + if (cn == null) { + cn = targetIntent.resolveActivity(pm); + } + ActivityInfo info = pm.getActivityInfo(cn, 0); + if (info.taskAffinity == null) { + return false; + } + return !ActivityManagerNative.getDefault() + .targetTaskAffinityMatchesActivity(mToken, info.taskAffinity); + } catch (RemoteException e) { + return false; + } catch (NameNotFoundException e) { + return false; + } + } + + /** + * Navigate from this activity to the activity specified by upIntent, finishing this activity + * in the process. If the activity indicated by upIntent already exists in the task's history, + * this activity and all others before the indicated activity in the history stack will be + * finished. + * + *

If the indicated activity does not appear in the history stack, this will finish + * each activity in this task until the root activity of the task is reached, resulting in + * an "in-app home" behavior. This can be useful in apps with a complex navigation hierarchy + * when an activity may be reached by a path not passing through a canonical parent + * activity.

+ * + *

This method should be used when performing up navigation from within the same task + * as the destination. If up navigation should cross tasks in some cases, see + * {@link #shouldUpRecreateTask(Intent)}.

+ * + * @param upIntent An intent representing the target destination for up navigation + * + * @return true if up navigation successfully reached the activity indicated by upIntent and + * upIntent was delivered to it. false if an instance of the indicated activity could + * not be found and this activity was simply finished normally. + */ + public boolean navigateUpTo(Intent upIntent) { + if (mParent == null) { + ComponentName destInfo = upIntent.getComponent(); + if (destInfo == null) { + destInfo = upIntent.resolveActivity(getPackageManager()); + if (destInfo == null) { + return false; + } + upIntent = new Intent(upIntent); + upIntent.setComponent(destInfo); + } + int resultCode; + Intent resultData; + synchronized (this) { + resultCode = mResultCode; + resultData = mResultData; + } + if (resultData != null) { + resultData.setAllowFds(false); + } + try { + return ActivityManagerNative.getDefault().navigateUpTo(mToken, upIntent, + resultCode, resultData); + } catch (RemoteException e) { + return false; + } + } else { + return mParent.navigateUpToFromChild(this, upIntent); + } + } + + /** + * This is called when a child activity of this one calls its + * {@link #navigateUpTo} method. The default implementation simply calls + * navigateUpTo(upIntent) on this activity (the parent). + * + * @param child The activity making the call. + * @param upIntent An intent representing the target destination for up navigation + * + * @return true if up navigation successfully reached the activity indicated by upIntent and + * upIntent was delivered to it. false if an instance of the indicated activity could + * not be found and this activity was simply finished normally. + */ + public boolean navigateUpToFromChild(Activity child, Intent upIntent) { + return navigateUpTo(upIntent); + } + + /** + * Obtain an {@link Intent} that will launch an explicit target activity specified by + * this activity's logical parent. The logical parent is named in the application's manifest + * by the {@link android.R.attr#parentActivityName parentActivityName} attribute. + * Activity subclasses may override this method to modify the Intent returned by + * super.getParentActivityIntent() or to implement a different mechanism of retrieving + * the parent intent entirely. + * + * @return a new Intent targeting the defined parent of this activity or null if + * there is no valid parent. + */ + public Intent getParentActivityIntent() { + final String parentName = mActivityInfo.parentActivityName; + if (TextUtils.isEmpty(parentName)) { + return null; + } + return new Intent().setClassName(this, parentName); + } + + // ------------------ Internal API ------------------ + + final void setParent(Activity parent) { + mParent = parent; + } + + final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, + Application application, Intent intent, ActivityInfo info, CharSequence title, + Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, + Configuration config) { + attach(context, aThread, instr, token, 0, application, intent, info, title, parent, id, + lastNonConfigurationInstances, config); + } + + final void attach(Context context, ActivityThread aThread, + Instrumentation instr, IBinder token, int ident, + Application application, Intent intent, ActivityInfo info, + CharSequence title, Activity parent, String id, + NonConfigurationInstances lastNonConfigurationInstances, + Configuration config) { + attachBaseContext(context); + + mFragments.attachActivity(this); + + mWindow = PolicyManager.makeNewWindow(this); + mWindow.setCallback(this); + mWindow.getLayoutInflater().setPrivateFactory(this); + if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { + mWindow.setSoftInputMode(info.softInputMode); + } + if (info.uiOptions != 0) { + mWindow.setUiOptions(info.uiOptions); + } + mUiThread = Thread.currentThread(); + + mMainThread = aThread; + mInstrumentation = instr; + mToken = token; + mIdent = ident; + mApplication = application; + mIntent = intent; + mComponent = intent.getComponent(); + mActivityInfo = info; + mTitle = title; + mParent = parent; + mEmbeddedID = id; + mLastNonConfigurationInstances = lastNonConfigurationInstances; + + mWindow.setWindowManager(null, mToken, mComponent.flattenToString(), + (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); + if (mParent != null) { + mWindow.setContainer(mParent.getWindow()); + } + mWindowManager = mWindow.getWindowManager(); + mCurrentConfig = config; + } + + /** @hide */ + public final IBinder getActivityToken() { + return mParent != null ? mParent.getActivityToken() : mToken; + } + + final void performCreate(Bundle icicle) { + onCreate(icicle); + mVisibleFromClient = !mWindow.getWindowStyle().getBoolean( + com.android.internal.R.styleable.Window_windowNoDisplay, false); + mFragments.dispatchActivityCreated(); + } + + final void performStart() { + mFragments.noteStateNotSaved(); + mCalled = false; + mFragments.execPendingActions(); + mInstrumentation.callActivityOnStart(this); + if (!mCalled) { + throw new SuperNotCalledException( + "Activity " + mComponent.toShortString() + + " did not call through to super.onStart()"); + } + mFragments.dispatchStart(); + if (mAllLoaderManagers != null) { + for (int i=mAllLoaderManagers.size()-1; i>=0; i--) { + LoaderManagerImpl lm = mAllLoaderManagers.valueAt(i); + lm.finishRetain(); + lm.doReportStart(); + } + } + } + + final void performRestart() { + mFragments.noteStateNotSaved(); + + if (mStopped) { + mStopped = false; + if (mToken != null && mParent == null) { + WindowManagerImpl.getDefault().setStoppedState(mToken, false); + } + + synchronized (mManagedCursors) { + final int N = mManagedCursors.size(); + for (int i=0; i= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + throw new IllegalStateException( + "trying to requery an already closed cursor " + + mc.mCursor); + } + } + mc.mReleased = false; + mc.mUpdated = false; + } + } + } + + mCalled = false; + mInstrumentation.callActivityOnRestart(this); + if (!mCalled) { + throw new SuperNotCalledException( + "Activity " + mComponent.toShortString() + + " did not call through to super.onRestart()"); + } + performStart(); + } + } + + final void performResume() { + performRestart(); + + mFragments.execPendingActions(); + + mLastNonConfigurationInstances = null; + + mCalled = false; + // mResumed is set by the instrumentation + mInstrumentation.callActivityOnResume(this); + if (!mCalled) { + throw new SuperNotCalledException( + "Activity " + mComponent.toShortString() + + " did not call through to super.onResume()"); + } + + // Now really resume, and install the current status bar and menu. + mCalled = false; + + mFragments.dispatchResume(); + mFragments.execPendingActions(); + + onPostResume(); + if (!mCalled) { + throw new SuperNotCalledException( + "Activity " + mComponent.toShortString() + + " did not call through to super.onPostResume()"); + } + } + + final void performPause() { + mFragments.dispatchPause(); + mCalled = false; + onPause(); + mResumed = false; + if (!mCalled && getApplicationInfo().targetSdkVersion + >= android.os.Build.VERSION_CODES.GINGERBREAD) { + throw new SuperNotCalledException( + "Activity " + mComponent.toShortString() + + " did not call through to super.onPause()"); + } + mResumed = false; + } + + final void performUserLeaving() { + onUserInteraction(); + onUserLeaveHint(); + } + + final void performStop() { + if (mLoadersStarted) { + mLoadersStarted = false; + if (mLoaderManager != null) { + if (!mChangingConfigurations) { + mLoaderManager.doStop(); + } else { + mLoaderManager.doRetain(); + } + } + } + + if (!mStopped) { + if (mWindow != null) { + mWindow.closeAllPanels(); + } + + if (mToken != null && mParent == null) { + WindowManagerImpl.getDefault().setStoppedState(mToken, true); + } + + mFragments.dispatchStop(); + + mCalled = false; + mInstrumentation.callActivityOnStop(this); + if (!mCalled) { + throw new SuperNotCalledException( + "Activity " + mComponent.toShortString() + + " did not call through to super.onStop()"); + } + + synchronized (mManagedCursors) { + final int N = mManagedCursors.size(); + for (int i=0; iThe is the size of the application's Dalvik heap if it has + * specified android:largeHeap="true" in its manifest. + */ + public int getLargeMemoryClass() { + return staticGetLargeMemoryClass(); + } + + /** @hide */ + static public int staticGetLargeMemoryClass() { + // Really brain dead right now -- just take this from the configured + // vm heap size, and assume it is in megabytes and thus ends with "m". + String vmHeapSize = SystemProperties.get("dalvik.vm.heapsize", "16m"); + return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1)); + } + + /** + * Used by persistent processes to determine if they are running on a + * higher-end device so should be okay using hardware drawing acceleration + * (which tends to consume a lot more RAM). + * @hide + */ + static public boolean isHighEndGfx(Display display) { + MemInfoReader reader = new MemInfoReader(); + reader.readMemInfo(); + if (reader.getTotalSize() >= (512*1024*1024)) { + // If the device has at least 512MB RAM available to the kernel, + // we can afford the overhead of graphics acceleration. + return true; + } + Point p = new Point(); + display.getRealSize(p); + int pixels = p.x * p.y; + if (pixels >= (1024*600)) { + // If this is a sufficiently large screen, then there are enough + // pixels on it that we'd really like to use hw drawing. + return true; + } + return false; + } + + /** + * Use to decide whether the running device can be considered a "large + * RAM" device. Exactly what memory limit large RAM is will vary, but + * it essentially means there is plenty of RAM to have lots of background + * processes running under decent loads. + * @hide + */ + static public boolean isLargeRAM() { + MemInfoReader reader = new MemInfoReader(); + reader.readMemInfo(); + if (reader.getTotalSize() >= (640*1024*1024)) { + // Currently 640MB RAM available to the kernel is the point at + // which we have plenty of RAM to spare. + return true; + } + return false; + } + + /** + * Information you can retrieve about tasks that the user has most recently + * started or visited. + */ + public static class RecentTaskInfo implements Parcelable { + /** + * If this task is currently running, this is the identifier for it. + * If it is not running, this will be -1. + */ + public int id; + + /** + * The true identifier of this task, valid even if it is not running. + */ + public int persistentId; + + /** + * The original Intent used to launch the task. You can use this + * Intent to re-launch the task (if it is no longer running) or bring + * the current task to the front. + */ + public Intent baseIntent; + + /** + * If this task was started from an alias, this is the actual + * activity component that was initially started; the component of + * the baseIntent in this case is the name of the actual activity + * implementation that the alias referred to. Otherwise, this is null. + */ + public ComponentName origActivity; + + /** + * Description of the task's last state. + */ + public CharSequence description; + + public RecentTaskInfo() { + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); + dest.writeInt(persistentId); + if (baseIntent != null) { + dest.writeInt(1); + baseIntent.writeToParcel(dest, 0); + } else { + dest.writeInt(0); + } + ComponentName.writeToParcel(origActivity, dest); + TextUtils.writeToParcel(description, dest, + Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + } + + public void readFromParcel(Parcel source) { + id = source.readInt(); + persistentId = source.readInt(); + if (source.readInt() != 0) { + baseIntent = Intent.CREATOR.createFromParcel(source); + } else { + baseIntent = null; + } + origActivity = ComponentName.readFromParcel(source); + description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + } + + public static final Creator CREATOR + = new Creator() { + public RecentTaskInfo createFromParcel(Parcel source) { + return new RecentTaskInfo(source); + } + public RecentTaskInfo[] newArray(int size) { + return new RecentTaskInfo[size]; + } + }; + + private RecentTaskInfo(Parcel source) { + readFromParcel(source); + } + } + + /** + * Flag for use with {@link #getRecentTasks}: return all tasks, even those + * that have set their + * {@link android.content.Intent#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS} flag. + */ + public static final int RECENT_WITH_EXCLUDED = 0x0001; + + /** + * Provides a list that does not contain any + * recent tasks that currently are not available to the user. + */ + public static final int RECENT_IGNORE_UNAVAILABLE = 0x0002; + + /** + * Return a list of the tasks that the user has recently launched, with + * the most recent being first and older ones after in order. + * + *

Note: this method is only intended for debugging and presenting + * task management user interfaces. This should never be used for + * core logic in an application, such as deciding between different + * behaviors based on the information found here. Such uses are + * not supported, and will likely break in the future. For + * example, if multiple applications can be actively running at the + * same time, assumptions made about the meaning of the data here for + * purposes of control flow will be incorrect.

+ * + * @param maxNum The maximum number of entries to return in the list. The + * actual number returned may be smaller, depending on how many tasks the + * user has started and the maximum number the system can remember. + * @param flags Information about what to return. May be any combination + * of {@link #RECENT_WITH_EXCLUDED} and {@link #RECENT_IGNORE_UNAVAILABLE}. + * + * @return Returns a list of RecentTaskInfo records describing each of + * the recent tasks. + * + * @throws SecurityException Throws SecurityException if the caller does + * not hold the {@link android.Manifest.permission#GET_TASKS} permission. + */ + public List getRecentTasks(int maxNum, int flags) + throws SecurityException { + try { + return ActivityManagerNative.getDefault().getRecentTasks(maxNum, + flags); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return null; + } + } + + /** + * Information you can retrieve about a particular task that is currently + * "running" in the system. Note that a running task does not mean the + * given task actually has a process it is actively running in; it simply + * means that the user has gone to it and never closed it, but currently + * the system may have killed its process and is only holding on to its + * last state in order to restart it when the user returns. + */ + public static class RunningTaskInfo implements Parcelable { + /** + * A unique identifier for this task. + */ + public int id; + + /** + * The component launched as the first activity in the task. This can + * be considered the "application" of this task. + */ + public ComponentName baseActivity; + + /** + * The activity component at the top of the history stack of the task. + * This is what the user is currently doing. + */ + public ComponentName topActivity; + + /** + * Thumbnail representation of the task's current state. Currently + * always null. + */ + public Bitmap thumbnail; + + /** + * Description of the task's current state. + */ + public CharSequence description; + + /** + * Number of activities in this task. + */ + public int numActivities; + + /** + * Number of activities that are currently running (not stopped + * and persisted) in this task. + */ + public int numRunning; + + public RunningTaskInfo() { + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); + ComponentName.writeToParcel(baseActivity, dest); + ComponentName.writeToParcel(topActivity, dest); + if (thumbnail != null) { + dest.writeInt(1); + thumbnail.writeToParcel(dest, 0); + } else { + dest.writeInt(0); + } + TextUtils.writeToParcel(description, dest, + Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + dest.writeInt(numActivities); + dest.writeInt(numRunning); + } + + public void readFromParcel(Parcel source) { + id = source.readInt(); + baseActivity = ComponentName.readFromParcel(source); + topActivity = ComponentName.readFromParcel(source); + if (source.readInt() != 0) { + thumbnail = Bitmap.CREATOR.createFromParcel(source); + } else { + thumbnail = null; + } + description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + numActivities = source.readInt(); + numRunning = source.readInt(); + } + + public static final Creator CREATOR = new Creator() { + public RunningTaskInfo createFromParcel(Parcel source) { + return new RunningTaskInfo(source); + } + public RunningTaskInfo[] newArray(int size) { + return new RunningTaskInfo[size]; + } + }; + + private RunningTaskInfo(Parcel source) { + readFromParcel(source); + } + } + + /** + * Return a list of the tasks that are currently running, with + * the most recent being first and older ones after in order. Note that + * "running" does not mean any of the task's code is currently loaded or + * activity -- the task may have been frozen by the system, so that it + * can be restarted in its previous state when next brought to the + * foreground. + * + * @param maxNum The maximum number of entries to return in the list. The + * actual number returned may be smaller, depending on how many tasks the + * user has started. + * + * @param flags Optional flags + * @param receiver Optional receiver for delayed thumbnails + * + * @return Returns a list of RunningTaskInfo records describing each of + * the running tasks. + * + * Some thumbnails may not be available at the time of this call. The optional + * receiver may be used to receive those thumbnails. + * + * @throws SecurityException Throws SecurityException if the caller does + * not hold the {@link android.Manifest.permission#GET_TASKS} permission. + * + * @hide + */ + public List getRunningTasks(int maxNum, int flags, IThumbnailReceiver receiver) + throws SecurityException { + try { + return ActivityManagerNative.getDefault().getTasks(maxNum, flags, receiver); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return null; + } + } + + /** + * Return a list of the tasks that are currently running, with + * the most recent being first and older ones after in order. Note that + * "running" does not mean any of the task's code is currently loaded or + * activity -- the task may have been frozen by the system, so that it + * can be restarted in its previous state when next brought to the + * foreground. + * + *

Note: this method is only intended for debugging and presenting + * task management user interfaces. This should never be used for + * core logic in an application, such as deciding between different + * behaviors based on the information found here. Such uses are + * not supported, and will likely break in the future. For + * example, if multiple applications can be actively running at the + * same time, assumptions made about the meaning of the data here for + * purposes of control flow will be incorrect.

+ * + * @param maxNum The maximum number of entries to return in the list. The + * actual number returned may be smaller, depending on how many tasks the + * user has started. + * + * @return Returns a list of RunningTaskInfo records describing each of + * the running tasks. + * + * @throws SecurityException Throws SecurityException if the caller does + * not hold the {@link android.Manifest.permission#GET_TASKS} permission. + */ + public List getRunningTasks(int maxNum) + throws SecurityException { + return getRunningTasks(maxNum, 0, null); + } + + /** + * Remove some end of a task's activity stack that is not part of + * the main application. The selected activities will be finished, so + * they are no longer part of the main task. + * + * @param taskId The identifier of the task. + * @param subTaskIndex The number of the sub-task; this corresponds + * to the index of the thumbnail returned by {@link #getTaskThumbnails(int)}. + * @return Returns true if the sub-task was found and was removed. + * + * @hide + */ + public boolean removeSubTask(int taskId, int subTaskIndex) + throws SecurityException { + try { + return ActivityManagerNative.getDefault().removeSubTask(taskId, subTaskIndex); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return false; + } + } + + /** + * If set, the process of the root activity of the task will be killed + * as part of removing the task. + * @hide + */ + public static final int REMOVE_TASK_KILL_PROCESS = 0x0001; + + /** + * Completely remove the given task. + * + * @param taskId Identifier of the task to be removed. + * @param flags Additional operational flags. May be 0 or + * {@link #REMOVE_TASK_KILL_PROCESS}. + * @return Returns true if the given task was found and removed. + * + * @hide + */ + public boolean removeTask(int taskId, int flags) + throws SecurityException { + try { + return ActivityManagerNative.getDefault().removeTask(taskId, flags); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return false; + } + } + + /** @hide */ + public static class TaskThumbnails implements Parcelable { + public Bitmap mainThumbnail; + + public int numSubThumbbails; + + /** @hide */ + public IThumbnailRetriever retriever; + + public TaskThumbnails() { + } + + public Bitmap getSubThumbnail(int index) { + try { + return retriever.getThumbnail(index); + } catch (RemoteException e) { + return null; + } + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + if (mainThumbnail != null) { + dest.writeInt(1); + mainThumbnail.writeToParcel(dest, 0); + } else { + dest.writeInt(0); + } + dest.writeInt(numSubThumbbails); + dest.writeStrongInterface(retriever); + } + + public void readFromParcel(Parcel source) { + if (source.readInt() != 0) { + mainThumbnail = Bitmap.CREATOR.createFromParcel(source); + } else { + mainThumbnail = null; + } + numSubThumbbails = source.readInt(); + retriever = IThumbnailRetriever.Stub.asInterface(source.readStrongBinder()); + } + + public static final Creator CREATOR = new Creator() { + public TaskThumbnails createFromParcel(Parcel source) { + return new TaskThumbnails(source); + } + public TaskThumbnails[] newArray(int size) { + return new TaskThumbnails[size]; + } + }; + + private TaskThumbnails(Parcel source) { + readFromParcel(source); + } + } + + /** @hide */ + public TaskThumbnails getTaskThumbnails(int id) throws SecurityException { + try { + return ActivityManagerNative.getDefault().getTaskThumbnails(id); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return null; + } + } + + /** + * Flag for {@link #moveTaskToFront(int, int)}: also move the "home" + * activity along with the task, so it is positioned immediately behind + * the task. + */ + public static final int MOVE_TASK_WITH_HOME = 0x00000001; + + /** + * Flag for {@link #moveTaskToFront(int, int)}: don't count this as a + * user-instigated action, so the current activity will not receive a + * hint that the user is leaving. + */ + public static final int MOVE_TASK_NO_USER_ACTION = 0x00000002; + + /** + * Equivalent to calling {@link #moveTaskToFront(int, int, Bundle)} + * with a null options argument. + * + * @param taskId The identifier of the task to be moved, as found in + * {@link RunningTaskInfo} or {@link RecentTaskInfo}. + * @param flags Additional operational flags, 0 or more of + * {@link #MOVE_TASK_WITH_HOME}. + */ + public void moveTaskToFront(int taskId, int flags) { + moveTaskToFront(taskId, flags, null); + } + + /** + * Ask that the task associated with a given task ID be moved to the + * front of the stack, so it is now visible to the user. Requires that + * the caller hold permission {@link android.Manifest.permission#REORDER_TASKS} + * or a SecurityException will be thrown. + * + * @param taskId The identifier of the task to be moved, as found in + * {@link RunningTaskInfo} or {@link RecentTaskInfo}. + * @param flags Additional operational flags, 0 or more of + * {@link #MOVE_TASK_WITH_HOME}. + * @param options Additional options for the operation, either null or + * as per {@link Context#startActivity(Intent, android.os.Bundle) + * Context.startActivity(Intent, Bundle)}. + */ + public void moveTaskToFront(int taskId, int flags, Bundle options) { + try { + ActivityManagerNative.getDefault().moveTaskToFront(taskId, flags, options); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + } + } + + /** + * Information you can retrieve about a particular Service that is + * currently running in the system. + */ + public static class RunningServiceInfo implements Parcelable { + /** + * The service component. + */ + public ComponentName service; + + /** + * If non-zero, this is the process the service is running in. + */ + public int pid; + + /** + * The UID that owns this service. + */ + public int uid; + + /** + * The name of the process this service runs in. + */ + public String process; + + /** + * Set to true if the service has asked to run as a foreground process. + */ + public boolean foreground; + + /** + * The time when the service was first made active, either by someone + * starting or binding to it. This + * is in units of {@link android.os.SystemClock#elapsedRealtime()}. + */ + public long activeSince; + + /** + * Set to true if this service has been explicitly started. + */ + public boolean started; + + /** + * Number of clients connected to the service. + */ + public int clientCount; + + /** + * Number of times the service's process has crashed while the service + * is running. + */ + public int crashCount; + + /** + * The time when there was last activity in the service (either + * explicit requests to start it or clients binding to it). This + * is in units of {@link android.os.SystemClock#uptimeMillis()}. + */ + public long lastActivityTime; + + /** + * If non-zero, this service is not currently running, but scheduled to + * restart at the given time. + */ + public long restarting; + + /** + * Bit for {@link #flags}: set if this service has been + * explicitly started. + */ + public static final int FLAG_STARTED = 1<<0; + + /** + * Bit for {@link #flags}: set if the service has asked to + * run as a foreground process. + */ + public static final int FLAG_FOREGROUND = 1<<1; + + /** + * Bit for {@link #flags): set if the service is running in a + * core system process. + */ + public static final int FLAG_SYSTEM_PROCESS = 1<<2; + + /** + * Bit for {@link #flags): set if the service is running in a + * persistent process. + */ + public static final int FLAG_PERSISTENT_PROCESS = 1<<3; + + /** + * Running flags. + */ + public int flags; + + /** + * For special services that are bound to by system code, this is + * the package that holds the binding. + */ + public String clientPackage; + + /** + * For special services that are bound to by system code, this is + * a string resource providing a user-visible label for who the + * client is. + */ + public int clientLabel; + + public RunningServiceInfo() { + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + ComponentName.writeToParcel(service, dest); + dest.writeInt(pid); + dest.writeInt(uid); + dest.writeString(process); + dest.writeInt(foreground ? 1 : 0); + dest.writeLong(activeSince); + dest.writeInt(started ? 1 : 0); + dest.writeInt(clientCount); + dest.writeInt(crashCount); + dest.writeLong(lastActivityTime); + dest.writeLong(restarting); + dest.writeInt(this.flags); + dest.writeString(clientPackage); + dest.writeInt(clientLabel); + } + + public void readFromParcel(Parcel source) { + service = ComponentName.readFromParcel(source); + pid = source.readInt(); + uid = source.readInt(); + process = source.readString(); + foreground = source.readInt() != 0; + activeSince = source.readLong(); + started = source.readInt() != 0; + clientCount = source.readInt(); + crashCount = source.readInt(); + lastActivityTime = source.readLong(); + restarting = source.readLong(); + flags = source.readInt(); + clientPackage = source.readString(); + clientLabel = source.readInt(); + } + + public static final Creator CREATOR = new Creator() { + public RunningServiceInfo createFromParcel(Parcel source) { + return new RunningServiceInfo(source); + } + public RunningServiceInfo[] newArray(int size) { + return new RunningServiceInfo[size]; + } + }; + + private RunningServiceInfo(Parcel source) { + readFromParcel(source); + } + } + + /** + * Return a list of the services that are currently running. + * + *

Note: this method is only intended for debugging or implementing + * service management type user interfaces.

+ * + * @param maxNum The maximum number of entries to return in the list. The + * actual number returned may be smaller, depending on how many services + * are running. + * + * @return Returns a list of RunningServiceInfo records describing each of + * the running tasks. + */ + public List getRunningServices(int maxNum) + throws SecurityException { + try { + return ActivityManagerNative.getDefault() + .getServices(maxNum, 0); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return null; + } + } + + /** + * Returns a PendingIntent you can start to show a control panel for the + * given running service. If the service does not have a control panel, + * null is returned. + */ + public PendingIntent getRunningServiceControlPanel(ComponentName service) + throws SecurityException { + try { + return ActivityManagerNative.getDefault() + .getRunningServiceControlPanel(service); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return null; + } + } + + /** + * Information you can retrieve about the available memory through + * {@link ActivityManager#getMemoryInfo}. + */ + public static class MemoryInfo implements Parcelable { + /** + * The available memory on the system. This number should not + * be considered absolute: due to the nature of the kernel, a significant + * portion of this memory is actually in use and needed for the overall + * system to run well. + */ + public long availMem; + + /** + * The total memory accessible by the kernel. This is basically the + * RAM size of the device, not including below-kernel fixed allocations + * like DMA buffers, RAM for the baseband CPU, etc. + */ + public long totalMem; + + /** + * The threshold of {@link #availMem} at which we consider memory to be + * low and start killing background services and other non-extraneous + * processes. + */ + public long threshold; + + /** + * Set to true if the system considers itself to currently be in a low + * memory situation. + */ + public boolean lowMemory; + + /** @hide */ + public long hiddenAppThreshold; + /** @hide */ + public long secondaryServerThreshold; + /** @hide */ + public long visibleAppThreshold; + /** @hide */ + public long foregroundAppThreshold; + + public MemoryInfo() { + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(availMem); + dest.writeLong(totalMem); + dest.writeLong(threshold); + dest.writeInt(lowMemory ? 1 : 0); + dest.writeLong(hiddenAppThreshold); + dest.writeLong(secondaryServerThreshold); + dest.writeLong(visibleAppThreshold); + dest.writeLong(foregroundAppThreshold); + } + + public void readFromParcel(Parcel source) { + availMem = source.readLong(); + totalMem = source.readLong(); + threshold = source.readLong(); + lowMemory = source.readInt() != 0; + hiddenAppThreshold = source.readLong(); + secondaryServerThreshold = source.readLong(); + visibleAppThreshold = source.readLong(); + foregroundAppThreshold = source.readLong(); + } + + public static final Creator CREATOR + = new Creator() { + public MemoryInfo createFromParcel(Parcel source) { + return new MemoryInfo(source); + } + public MemoryInfo[] newArray(int size) { + return new MemoryInfo[size]; + } + }; + + private MemoryInfo(Parcel source) { + readFromParcel(source); + } + } + + /** + * Return general information about the memory state of the system. This + * can be used to help decide how to manage your own memory, though note + * that polling is not recommended and + * {@link android.content.ComponentCallbacks2#onTrimMemory(int) + * ComponentCallbacks2.onTrimMemory(int)} is the preferred way to do this. + * Also see {@link #getMyMemoryState} for how to retrieve the current trim + * level of your process as needed, which gives a better hint for how to + * manage its memory. + */ + public void getMemoryInfo(MemoryInfo outInfo) { + try { + ActivityManagerNative.getDefault().getMemoryInfo(outInfo); + } catch (RemoteException e) { + } + } + + /** + * @hide + */ + public boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) { + try { + return ActivityManagerNative.getDefault().clearApplicationUserData(packageName, + observer, Binder.getOrigCallingUser()); + } catch (RemoteException e) { + return false; + } + } + + /** + * Information you can retrieve about any processes that are in an error condition. + */ + public static class ProcessErrorStateInfo implements Parcelable { + /** + * Condition codes + */ + public static final int NO_ERROR = 0; + public static final int CRASHED = 1; + public static final int NOT_RESPONDING = 2; + + /** + * The condition that the process is in. + */ + public int condition; + + /** + * The process name in which the crash or error occurred. + */ + public String processName; + + /** + * The pid of this process; 0 if none + */ + public int pid; + + /** + * The kernel user-ID that has been assigned to this process; + * currently this is not a unique ID (multiple applications can have + * the same uid). + */ + public int uid; + + /** + * The activity name associated with the error, if known. May be null. + */ + public String tag; + + /** + * A short message describing the error condition. + */ + public String shortMsg; + + /** + * A long message describing the error condition. + */ + public String longMsg; + + /** + * The stack trace where the error originated. May be null. + */ + public String stackTrace; + + /** + * to be deprecated: This value will always be null. + */ + public byte[] crashData = null; + + public ProcessErrorStateInfo() { + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(condition); + dest.writeString(processName); + dest.writeInt(pid); + dest.writeInt(uid); + dest.writeString(tag); + dest.writeString(shortMsg); + dest.writeString(longMsg); + dest.writeString(stackTrace); + } + + public void readFromParcel(Parcel source) { + condition = source.readInt(); + processName = source.readString(); + pid = source.readInt(); + uid = source.readInt(); + tag = source.readString(); + shortMsg = source.readString(); + longMsg = source.readString(); + stackTrace = source.readString(); + } + + public static final Creator CREATOR = + new Creator() { + public ProcessErrorStateInfo createFromParcel(Parcel source) { + return new ProcessErrorStateInfo(source); + } + public ProcessErrorStateInfo[] newArray(int size) { + return new ProcessErrorStateInfo[size]; + } + }; + + private ProcessErrorStateInfo(Parcel source) { + readFromParcel(source); + } + } + + /** + * Returns a list of any processes that are currently in an error condition. The result + * will be null if all processes are running properly at this time. + * + * @return Returns a list of ProcessErrorStateInfo records, or null if there are no + * current error conditions (it will not return an empty list). This list ordering is not + * specified. + */ + public List getProcessesInErrorState() { + try { + return ActivityManagerNative.getDefault().getProcessesInErrorState(); + } catch (RemoteException e) { + return null; + } + } + + /** + * Information you can retrieve about a running process. + */ + public static class RunningAppProcessInfo implements Parcelable { + /** + * The name of the process that this object is associated with + */ + public String processName; + + /** + * The pid of this process; 0 if none + */ + public int pid; + + /** + * The user id of this process. + */ + public int uid; + + /** + * All packages that have been loaded into the process. + */ + public String pkgList[]; + + /** + * Constant for {@link #flags}: this is an app that is unable to + * correctly save its state when going to the background, + * so it can not be killed while in the background. + * @hide + */ + public static final int FLAG_CANT_SAVE_STATE = 1<<0; + + /** + * Constant for {@link #flags}: this process is associated with a + * persistent system app. + * @hide + */ + public static final int FLAG_PERSISTENT = 1<<1; + + /** + * Flags of information. May be any of + * {@link #FLAG_CANT_SAVE_STATE}. + * @hide + */ + public int flags; + + /** + * Last memory trim level reported to the process: corresponds to + * the values supplied to {@link android.content.ComponentCallbacks2#onTrimMemory(int) + * ComponentCallbacks2.onTrimMemory(int)}. + */ + public int lastTrimLevel; + + /** + * Constant for {@link #importance}: this is a persistent process. + * Only used when reporting to process observers. + * @hide + */ + public static final int IMPORTANCE_PERSISTENT = 50; + + /** + * Constant for {@link #importance}: this process is running the + * foreground UI. + */ + public static final int IMPORTANCE_FOREGROUND = 100; + + /** + * Constant for {@link #importance}: this process is running something + * that is actively visible to the user, though not in the immediate + * foreground. + */ + public static final int IMPORTANCE_VISIBLE = 200; + + /** + * Constant for {@link #importance}: this process is running something + * that is considered to be actively perceptible to the user. An + * example would be an application performing background music playback. + */ + public static final int IMPORTANCE_PERCEPTIBLE = 130; + + /** + * Constant for {@link #importance}: this process is running an + * application that can not save its state, and thus can't be killed + * while in the background. + * @hide + */ + public static final int IMPORTANCE_CANT_SAVE_STATE = 170; + + /** + * Constant for {@link #importance}: this process is contains services + * that should remain running. + */ + public static final int IMPORTANCE_SERVICE = 300; + + /** + * Constant for {@link #importance}: this process process contains + * background code that is expendable. + */ + public static final int IMPORTANCE_BACKGROUND = 400; + + /** + * Constant for {@link #importance}: this process is empty of any + * actively running code. + */ + public static final int IMPORTANCE_EMPTY = 500; + + /** + * The relative importance level that the system places on this + * process. May be one of {@link #IMPORTANCE_FOREGROUND}, + * {@link #IMPORTANCE_VISIBLE}, {@link #IMPORTANCE_SERVICE}, + * {@link #IMPORTANCE_BACKGROUND}, or {@link #IMPORTANCE_EMPTY}. These + * constants are numbered so that "more important" values are always + * smaller than "less important" values. + */ + public int importance; + + /** + * An additional ordering within a particular {@link #importance} + * category, providing finer-grained information about the relative + * utility of processes within a category. This number means nothing + * except that a smaller values are more recently used (and thus + * more important). Currently an LRU value is only maintained for + * the {@link #IMPORTANCE_BACKGROUND} category, though others may + * be maintained in the future. + */ + public int lru; + + /** + * Constant for {@link #importanceReasonCode}: nothing special has + * been specified for the reason for this level. + */ + public static final int REASON_UNKNOWN = 0; + + /** + * Constant for {@link #importanceReasonCode}: one of the application's + * content providers is being used by another process. The pid of + * the client process is in {@link #importanceReasonPid} and the + * target provider in this process is in + * {@link #importanceReasonComponent}. + */ + public static final int REASON_PROVIDER_IN_USE = 1; + + /** + * Constant for {@link #importanceReasonCode}: one of the application's + * content providers is being used by another process. The pid of + * the client process is in {@link #importanceReasonPid} and the + * target provider in this process is in + * {@link #importanceReasonComponent}. + */ + public static final int REASON_SERVICE_IN_USE = 2; + + /** + * The reason for {@link #importance}, if any. + */ + public int importanceReasonCode; + + /** + * For the specified values of {@link #importanceReasonCode}, this + * is the process ID of the other process that is a client of this + * process. This will be 0 if no other process is using this one. + */ + public int importanceReasonPid; + + /** + * For the specified values of {@link #importanceReasonCode}, this + * is the name of the component that is being used in this process. + */ + public ComponentName importanceReasonComponent; + + /** + * When {@link importanceReasonPid} is non-0, this is the importance + * of the other pid. @hide + */ + public int importanceReasonImportance; + + public RunningAppProcessInfo() { + importance = IMPORTANCE_FOREGROUND; + importanceReasonCode = REASON_UNKNOWN; + } + + public RunningAppProcessInfo(String pProcessName, int pPid, String pArr[]) { + processName = pProcessName; + pid = pPid; + pkgList = pArr; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(processName); + dest.writeInt(pid); + dest.writeInt(uid); + dest.writeStringArray(pkgList); + dest.writeInt(this.flags); + dest.writeInt(lastTrimLevel); + dest.writeInt(importance); + dest.writeInt(lru); + dest.writeInt(importanceReasonCode); + dest.writeInt(importanceReasonPid); + ComponentName.writeToParcel(importanceReasonComponent, dest); + dest.writeInt(importanceReasonImportance); + } + + public void readFromParcel(Parcel source) { + processName = source.readString(); + pid = source.readInt(); + uid = source.readInt(); + pkgList = source.readStringArray(); + flags = source.readInt(); + lastTrimLevel = source.readInt(); + importance = source.readInt(); + lru = source.readInt(); + importanceReasonCode = source.readInt(); + importanceReasonPid = source.readInt(); + importanceReasonComponent = ComponentName.readFromParcel(source); + importanceReasonImportance = source.readInt(); + } + + public static final Creator CREATOR = + new Creator() { + public RunningAppProcessInfo createFromParcel(Parcel source) { + return new RunningAppProcessInfo(source); + } + public RunningAppProcessInfo[] newArray(int size) { + return new RunningAppProcessInfo[size]; + } + }; + + private RunningAppProcessInfo(Parcel source) { + readFromParcel(source); + } + } + + /** + * Returns a list of application processes installed on external media + * that are running on the device. + * + *

Note: this method is only intended for debugging or building + * a user-facing process management UI.

+ * + * @return Returns a list of ApplicationInfo records, or null if none + * This list ordering is not specified. + * @hide + */ + public List getRunningExternalApplications() { + try { + return ActivityManagerNative.getDefault().getRunningExternalApplications(); + } catch (RemoteException e) { + return null; + } + } + + /** + * Returns a list of application processes that are running on the device. + * + *

Note: this method is only intended for debugging or building + * a user-facing process management UI.

+ * + * @return Returns a list of RunningAppProcessInfo records, or null if there are no + * running processes (it will not return an empty list). This list ordering is not + * specified. + */ + public List getRunningAppProcesses() { + try { + return ActivityManagerNative.getDefault().getRunningAppProcesses(); + } catch (RemoteException e) { + return null; + } + } + + /** + * Return global memory state information for the calling process. This + * does not fill in all fields of the {@link RunningAppProcessInfo}. The + * only fields that will be filled in are + * {@link RunningAppProcessInfo#pid}, + * {@link RunningAppProcessInfo#uid}, + * {@link RunningAppProcessInfo#lastTrimLevel}, + * {@link RunningAppProcessInfo#importance}, + * {@link RunningAppProcessInfo#lru}, and + * {@link RunningAppProcessInfo#importanceReasonCode}. + */ + static public void getMyMemoryState(RunningAppProcessInfo outState) { + try { + ActivityManagerNative.getDefault().getMyMemoryState(outState); + } catch (RemoteException e) { + } + } + + /** + * Return information about the memory usage of one or more processes. + * + *

Note: this method is only intended for debugging or building + * a user-facing process management UI.

+ * + * @param pids The pids of the processes whose memory usage is to be + * retrieved. + * @return Returns an array of memory information, one for each + * requested pid. + */ + public Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids) { + try { + return ActivityManagerNative.getDefault().getProcessMemoryInfo(pids); + } catch (RemoteException e) { + return null; + } + } + + /** + * @deprecated This is now just a wrapper for + * {@link #killBackgroundProcesses(String)}; the previous behavior here + * is no longer available to applications because it allows them to + * break other applications by removing their alarms, stopping their + * services, etc. + */ + @Deprecated + public void restartPackage(String packageName) { + killBackgroundProcesses(packageName); + } + + /** + * Have the system immediately kill all background processes associated + * with the given package. This is the same as the kernel killing those + * processes to reclaim memory; the system will take care of restarting + * these processes in the future as needed. + * + *

You must hold the permission + * {@link android.Manifest.permission#KILL_BACKGROUND_PROCESSES} to be able to + * call this method. + * + * @param packageName The name of the package whose processes are to + * be killed. + */ + public void killBackgroundProcesses(String packageName) { + try { + ActivityManagerNative.getDefault().killBackgroundProcesses(packageName); + } catch (RemoteException e) { + } + } + + /** + * Have the system perform a force stop of everything associated with + * the given application package. All processes that share its uid + * will be killed, all services it has running stopped, all activities + * removed, etc. In addition, a {@link Intent#ACTION_PACKAGE_RESTARTED} + * broadcast will be sent, so that any of its registered alarms can + * be stopped, notifications removed, etc. + * + *

You must hold the permission + * {@link android.Manifest.permission#FORCE_STOP_PACKAGES} to be able to + * call this method. + * + * @param packageName The name of the package to be stopped. + * + * @hide This is not available to third party applications due to + * it allowing them to break other applications by stopping their + * services, removing their alarms, etc. + */ + public void forceStopPackage(String packageName) { + try { + ActivityManagerNative.getDefault().forceStopPackage(packageName); + } catch (RemoteException e) { + } + } + + /** + * Get the device configuration attributes. + */ + public ConfigurationInfo getDeviceConfigurationInfo() { + try { + return ActivityManagerNative.getDefault().getDeviceConfigurationInfo(); + } catch (RemoteException e) { + } + return null; + } + + /** + * Get the preferred density of icons for the launcher. This is used when + * custom drawables are created (e.g., for shortcuts). + * + * @return density in terms of DPI + */ + public int getLauncherLargeIconDensity() { + final Resources res = mContext.getResources(); + final int density = res.getDisplayMetrics().densityDpi; + final int sw = res.getConfiguration().smallestScreenWidthDp; + + if (sw < 600) { + // Smaller than approx 7" tablets, use the regular icon size. + return density; + } + + switch (density) { + case DisplayMetrics.DENSITY_LOW: + return DisplayMetrics.DENSITY_MEDIUM; + case DisplayMetrics.DENSITY_MEDIUM: + return DisplayMetrics.DENSITY_HIGH; + case DisplayMetrics.DENSITY_TV: + return DisplayMetrics.DENSITY_XHIGH; + case DisplayMetrics.DENSITY_HIGH: + return DisplayMetrics.DENSITY_XHIGH; + case DisplayMetrics.DENSITY_XHIGH: + return DisplayMetrics.DENSITY_XXHIGH; + case DisplayMetrics.DENSITY_XXHIGH: + return DisplayMetrics.DENSITY_XHIGH * 2; + default: + // The density is some abnormal value. Return some other + // abnormal value that is a reasonable scaling of it. + return (int)((density*1.5f)+.5f); + } + } + + /** + * Get the preferred launcher icon size. This is used when custom drawables + * are created (e.g., for shortcuts). + * + * @return dimensions of square icons in terms of pixels + */ + public int getLauncherLargeIconSize() { + final Resources res = mContext.getResources(); + final int size = res.getDimensionPixelSize(android.R.dimen.app_icon_size); + final int sw = res.getConfiguration().smallestScreenWidthDp; + + if (sw < 600) { + // Smaller than approx 7" tablets, use the regular icon size. + return size; + } + + final int density = res.getDisplayMetrics().densityDpi; + + switch (density) { + case DisplayMetrics.DENSITY_LOW: + return (size * DisplayMetrics.DENSITY_MEDIUM) / DisplayMetrics.DENSITY_LOW; + case DisplayMetrics.DENSITY_MEDIUM: + return (size * DisplayMetrics.DENSITY_HIGH) / DisplayMetrics.DENSITY_MEDIUM; + case DisplayMetrics.DENSITY_TV: + return (size * DisplayMetrics.DENSITY_XHIGH) / DisplayMetrics.DENSITY_HIGH; + case DisplayMetrics.DENSITY_HIGH: + return (size * DisplayMetrics.DENSITY_XHIGH) / DisplayMetrics.DENSITY_HIGH; + case DisplayMetrics.DENSITY_XHIGH: + return (size * DisplayMetrics.DENSITY_XXHIGH) / DisplayMetrics.DENSITY_XHIGH; + case DisplayMetrics.DENSITY_XXHIGH: + return (size * DisplayMetrics.DENSITY_XHIGH*2) / DisplayMetrics.DENSITY_XXHIGH; + default: + // The density is some abnormal value. Return some other + // abnormal value that is a reasonable scaling of it. + return (int)((size*1.5f) + .5f); + } + } + + /** + * Returns "true" if the user interface is currently being messed with + * by a monkey. + */ + public static boolean isUserAMonkey() { + try { + return ActivityManagerNative.getDefault().isUserAMonkey(); + } catch (RemoteException e) { + } + return false; + } + + /** + * Returns "true" if device is running in a test harness. + */ + public static boolean isRunningInTestHarness() { + return SystemProperties.getBoolean("ro.test_harness", false); + } + + /** + * Returns the launch count of each installed package. + * + * @hide + */ + public Map getAllPackageLaunchCounts() { + try { + IUsageStats usageStatsService = IUsageStats.Stub.asInterface( + ServiceManager.getService("usagestats")); + if (usageStatsService == null) { + return new HashMap(); + } + + PkgUsageStats[] allPkgUsageStats = usageStatsService.getAllPkgUsageStats(); + if (allPkgUsageStats == null) { + return new HashMap(); + } + + Map launchCounts = new HashMap(); + for (PkgUsageStats pkgUsageStats : allPkgUsageStats) { + launchCounts.put(pkgUsageStats.packageName, pkgUsageStats.launchCount); + } + + return launchCounts; + } catch (RemoteException e) { + Log.w(TAG, "Could not query launch counts", e); + return new HashMap(); + } + } + + /** @hide */ + public static int checkComponentPermission(String permission, int uid, + int owningUid, boolean exported) { + // Root, system server get to do everything. + if (uid == 0 || uid == Process.SYSTEM_UID) { + return PackageManager.PERMISSION_GRANTED; + } + // Isolated processes don't get any permissions. + if (UserId.isIsolated(uid)) { + return PackageManager.PERMISSION_DENIED; + } + // If there is a uid that owns whatever is being accessed, it has + // blanket access to it regardless of the permissions it requires. + if (owningUid >= 0 && UserId.isSameApp(uid, owningUid)) { + return PackageManager.PERMISSION_GRANTED; + } + // If the target is not exported, then nobody else can get to it. + if (!exported) { + Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid); + return PackageManager.PERMISSION_DENIED; + } + if (permission == null) { + return PackageManager.PERMISSION_GRANTED; + } + try { + return AppGlobals.getPackageManager() + .checkUidPermission(permission, uid); + } catch (RemoteException e) { + // Should never happen, but if it does... deny! + Slog.e(TAG, "PackageManager is dead?!?", e); + } + return PackageManager.PERMISSION_DENIED; + } + + /** + * Returns the usage statistics of each installed package. + * + * @hide + */ + public PkgUsageStats[] getAllPackageUsageStats() { + try { + IUsageStats usageStatsService = IUsageStats.Stub.asInterface( + ServiceManager.getService("usagestats")); + if (usageStatsService != null) { + return usageStatsService.getAllPkgUsageStats(); + } + } catch (RemoteException e) { + Log.w(TAG, "Could not query usage stats", e); + } + return new PkgUsageStats[0]; + } + + /** + * @param userid the user's id. Zero indicates the default user + * @hide + */ + public boolean switchUser(int userid) { + try { + return ActivityManagerNative.getDefault().switchUser(userid); + } catch (RemoteException e) { + return false; + } + } +} diff --git a/src/frameworks/base/core/java/android/app/ActivityThread.java b/src/frameworks/base/core/java/android/app/ActivityThread.java new file mode 100644 index 00000000..7242029a --- /dev/null +++ b/src/frameworks/base/core/java/android/app/ActivityThread.java @@ -0,0 +1,4749 @@ +/* + * 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 android.app; + +import android.app.backup.BackupAgent; +import android.content.BroadcastReceiver; +import android.content.ComponentCallbacks2; +import android.content.ComponentName; +import android.content.ContentProvider; +import android.content.Context; +import android.content.IContentProvider; +import android.content.Intent; +import android.content.IIntentReceiver; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.InstrumentationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ProviderInfo; +import android.content.pm.ServiceInfo; +import android.content.res.AssetManager; +import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDebug; +import android.database.sqlite.SQLiteDebug.DbStats; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.net.IConnectivityManager; +import android.net.Proxy; +import android.net.ProxyProperties; +import android.opengl.GLUtils; +import android.os.AsyncTask; +import android.os.Binder; +import android.os.Bundle; +import android.os.Debug; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.StrictMode; +import android.os.SystemClock; +import android.os.Trace; +import android.os.UserId; +import android.util.AndroidRuntimeException; +import android.util.DisplayMetrics; +import android.util.EventLog; +import android.util.Log; +import android.util.LogPrinter; +import android.util.PrintWriterPrinter; +import android.util.Slog; +import android.view.Display; +import android.view.HardwareRenderer; +import android.view.View; +import android.view.ViewDebug; +import android.view.ViewManager; +import android.view.ViewRootImpl; +import android.view.Window; +import android.view.WindowManager; +import android.view.WindowManagerImpl; +import android.renderscript.RenderScript; + +import com.android.internal.os.BinderInternal; +import com.android.internal.os.RuntimeInit; +import com.android.internal.os.SamplingProfilerIntegration; + +import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.ref.WeakReference; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; +import java.util.regex.Pattern; + +import libcore.io.IoUtils; + +import dalvik.system.CloseGuard; + +final class SuperNotCalledException extends AndroidRuntimeException { + public SuperNotCalledException(String msg) { + super(msg); + } +} + +final class RemoteServiceException extends AndroidRuntimeException { + public RemoteServiceException(String msg) { + super(msg); + } +} + +/** + * This manages the execution of the main thread in an + * application process, scheduling and executing activities, + * broadcasts, and other operations on it as the activity + * manager requests. + * + * {@hide} + */ +public final class ActivityThread { + /** @hide */ + public static final String TAG = "ActivityThread"; + private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565; + static final boolean localLOGV = false; + static final boolean DEBUG_MESSAGES = false; + /** @hide */ + public static final boolean DEBUG_BROADCAST = false; + private static final boolean DEBUG_RESULTS = false; + private static final boolean DEBUG_BACKUP = false; + private static final boolean DEBUG_CONFIGURATION = false; + private static final boolean DEBUG_SERVICE = false; + private static final boolean DEBUG_MEMORY_TRIM = false; + private static final boolean DEBUG_PROVIDER = false; + private static final long MIN_TIME_BETWEEN_GCS = 5*1000; + private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";"); + private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003; + private static final int LOG_ON_PAUSE_CALLED = 30021; + private static final int LOG_ON_RESUME_CALLED = 30022; + + static ContextImpl mSystemContext = null; + + static IPackageManager sPackageManager; + + final ApplicationThread mAppThread = new ApplicationThread(); + final Looper mLooper = Looper.myLooper(); + final H mH = new H(); + final HashMap mActivities + = new HashMap(); + // List of new activities (via ActivityRecord.nextIdle) that should + // be reported when next we idle. + ActivityClientRecord mNewActivities = null; + // Number of activities that are currently visible on-screen. + int mNumVisibleActivities = 0; + final HashMap mServices + = new HashMap(); + AppBindData mBoundApplication; + Profiler mProfiler; + Configuration mConfiguration; + Configuration mCompatConfiguration; + Configuration mResConfiguration; + CompatibilityInfo mResCompatibilityInfo; + Application mInitialApplication; + final ArrayList mAllApplications + = new ArrayList(); + // set of instantiated backup agents, keyed by package name + final HashMap mBackupAgents = new HashMap(); + static final ThreadLocal sThreadLocal = new ThreadLocal(); + Instrumentation mInstrumentation; + String mInstrumentationAppDir = null; + String mInstrumentationAppLibraryDir = null; + String mInstrumentationAppPackage = null; + String mInstrumentedAppDir = null; + String mInstrumentedAppLibraryDir = null; + boolean mSystemThread = false; + boolean mJitEnabled = false; + + // These can be accessed by multiple threads; mPackages is the lock. + // XXX For now we keep around information about all packages we have + // seen, not removing entries from this map. + // NOTE: The activity and window managers need to call in to + // ActivityThread to do things like update resource configurations, + // which means this lock gets held while the activity and window managers + // holds their own lock. Thus you MUST NEVER call back into the activity manager + // or window manager or anything that depends on them while holding this lock. + final HashMap> mPackages + = new HashMap>(); + final HashMap> mResourcePackages + = new HashMap>(); + final HashMap mDisplayMetrics + = new HashMap(); + final HashMap > mActiveResources + = new HashMap >(); + final ArrayList mRelaunchingActivities + = new ArrayList(); + Configuration mPendingConfiguration = null; + + // The lock of mProviderMap protects the following variables. + final HashMap mProviderMap + = new HashMap(); + final HashMap mProviderRefCountMap + = new HashMap(); + final HashMap mLocalProviders + = new HashMap(); + final HashMap mLocalProvidersByName + = new HashMap(); + + final HashMap> mOnPauseListeners + = new HashMap>(); + + final GcIdler mGcIdler = new GcIdler(); + boolean mGcIdlerScheduled = false; + + static Handler sMainThreadHandler; // set once in main() + + Bundle mCoreSettings = null; + + static final class ActivityClientRecord { + IBinder token; + int ident; + Intent intent; + Bundle state; + Activity activity; + Window window; + Activity parent; + String embeddedID; + Activity.NonConfigurationInstances lastNonConfigurationInstances; + boolean paused; + boolean stopped; + boolean hideForNow; + Configuration newConfig; + Configuration createdConfig; + ActivityClientRecord nextIdle; + + String profileFile; + ParcelFileDescriptor profileFd; + boolean autoStopProfiler; + + ActivityInfo activityInfo; + CompatibilityInfo compatInfo; + LoadedApk packageInfo; + + List pendingResults; + List pendingIntents; + + boolean startsNotResumed; + boolean isForward; + int pendingConfigChanges; + boolean onlyLocalRequest; + + View mPendingRemoveWindow; + WindowManager mPendingRemoveWindowManager; + + ActivityClientRecord() { + parent = null; + embeddedID = null; + paused = false; + stopped = false; + hideForNow = false; + nextIdle = null; + } + + public boolean isPreHoneycomb() { + if (activity != null) { + return activity.getApplicationInfo().targetSdkVersion + < android.os.Build.VERSION_CODES.HONEYCOMB; + } + return false; + } + + public String toString() { + ComponentName componentName = intent != null ? intent.getComponent() : null; + return "ActivityRecord{" + + Integer.toHexString(System.identityHashCode(this)) + + " token=" + token + " " + (componentName == null + ? "no component name" : componentName.toShortString()) + + "}"; + } + } + + final class ProviderClientRecord { + final String[] mNames; + final IContentProvider mProvider; + final ContentProvider mLocalProvider; + final IActivityManager.ContentProviderHolder mHolder; + + ProviderClientRecord(String[] names, IContentProvider provider, + ContentProvider localProvider, + IActivityManager.ContentProviderHolder holder) { + mNames = names; + mProvider = provider; + mLocalProvider = localProvider; + mHolder = holder; + } + } + + static final class NewIntentData { + List intents; + IBinder token; + public String toString() { + return "NewIntentData{intents=" + intents + " token=" + token + "}"; + } + } + + static final class ReceiverData extends BroadcastReceiver.PendingResult { + public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras, + boolean ordered, boolean sticky, IBinder token) { + super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky, token); + this.intent = intent; + } + + Intent intent; + ActivityInfo info; + CompatibilityInfo compatInfo; + public String toString() { + return "ReceiverData{intent=" + intent + " packageName=" + + info.packageName + " resultCode=" + getResultCode() + + " resultData=" + getResultData() + " resultExtras=" + + getResultExtras(false) + "}"; + } + } + + static final class CreateBackupAgentData { + ApplicationInfo appInfo; + CompatibilityInfo compatInfo; + int backupMode; + public String toString() { + return "CreateBackupAgentData{appInfo=" + appInfo + + " backupAgent=" + appInfo.backupAgentName + + " mode=" + backupMode + "}"; + } + } + + static final class CreateServiceData { + IBinder token; + ServiceInfo info; + CompatibilityInfo compatInfo; + Intent intent; + public String toString() { + return "CreateServiceData{token=" + token + " className=" + + info.name + " packageName=" + info.packageName + + " intent=" + intent + "}"; + } + } + + static final class BindServiceData { + IBinder token; + Intent intent; + boolean rebind; + public String toString() { + return "BindServiceData{token=" + token + " intent=" + intent + "}"; + } + } + + static final class ServiceArgsData { + IBinder token; + boolean taskRemoved; + int startId; + int flags; + Intent args; + public String toString() { + return "ServiceArgsData{token=" + token + " startId=" + startId + + " args=" + args + "}"; + } + } + + static final class AppBindData { + LoadedApk info; + String processName; + ApplicationInfo appInfo; + List providers; + ComponentName instrumentationName; + Bundle instrumentationArgs; + IInstrumentationWatcher instrumentationWatcher; + int debugMode; + boolean enableOpenGlTrace; + boolean restrictedBackupMode; + boolean persistent; + Configuration config; + CompatibilityInfo compatInfo; + + /** Initial values for {@link Profiler}. */ + String initProfileFile; + ParcelFileDescriptor initProfileFd; + boolean initAutoStopProfiler; + + public String toString() { + return "AppBindData{appInfo=" + appInfo + "}"; + } + } + + static final class Profiler { + String profileFile; + ParcelFileDescriptor profileFd; + boolean autoStopProfiler; + boolean profiling; + boolean handlingProfiling; + public void setProfiler(String file, ParcelFileDescriptor fd) { + if (profiling) { + if (fd != null) { + try { + fd.close(); + } catch (IOException e) { + // Ignore + } + } + return; + } + if (profileFd != null) { + try { + profileFd.close(); + } catch (IOException e) { + // Ignore + } + } + profileFile = file; + profileFd = fd; + } + public void startProfiling() { + if (profileFd == null || profiling) { + return; + } + try { + Debug.startMethodTracing(profileFile, profileFd.getFileDescriptor(), + 8 * 1024 * 1024, 0); + profiling = true; + } catch (RuntimeException e) { + Slog.w(TAG, "Profiling failed on path " + profileFile); + try { + profileFd.close(); + profileFd = null; + } catch (IOException e2) { + Slog.w(TAG, "Failure closing profile fd", e2); + } + } + } + public void stopProfiling() { + if (profiling) { + profiling = false; + Debug.stopMethodTracing(); + if (profileFd != null) { + try { + profileFd.close(); + } catch (IOException e) { + } + } + profileFd = null; + profileFile = null; + } + } + } + + static final class DumpComponentInfo { + ParcelFileDescriptor fd; + IBinder token; + String prefix; + String[] args; + } + + static final class ResultData { + IBinder token; + List results; + public String toString() { + return "ResultData{token=" + token + " results" + results + "}"; + } + } + + static final class ContextCleanupInfo { + ContextImpl context; + String what; + String who; + } + + static final class ProfilerControlData { + String path; + ParcelFileDescriptor fd; + } + + static final class DumpHeapData { + String path; + ParcelFileDescriptor fd; + } + + static final class UpdateCompatibilityData { + String pkg; + CompatibilityInfo info; + } + + private native void dumpGraphicsInfo(FileDescriptor fd); + + private class ApplicationThread extends ApplicationThreadNative { + private static final String HEAP_COLUMN = "%13s %8s %8s %8s %8s %8s %8s"; + private static final String ONE_COUNT_COLUMN = "%21s %8d"; + private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d"; + private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s"; + + // Formatting for checkin service - update version if row format changes + private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1; + + private void updatePendingConfiguration(Configuration config) { + synchronized (mPackages) { + if (mPendingConfiguration == null || + mPendingConfiguration.isOtherSeqNewer(config)) { + mPendingConfiguration = config; + } + } + } + + public final void schedulePauseActivity(IBinder token, boolean finished, + boolean userLeaving, int configChanges) { + queueOrSendMessage( + finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY, + token, + (userLeaving ? 1 : 0), + configChanges); + } + + public final void scheduleStopActivity(IBinder token, boolean showWindow, + int configChanges) { + queueOrSendMessage( + showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE, + token, 0, configChanges); + } + + public final void scheduleWindowVisibility(IBinder token, boolean showWindow) { + queueOrSendMessage( + showWindow ? H.SHOW_WINDOW : H.HIDE_WINDOW, + token); + } + + public final void scheduleSleeping(IBinder token, boolean sleeping) { + queueOrSendMessage(H.SLEEPING, token, sleeping ? 1 : 0); + } + + public final void scheduleResumeActivity(IBinder token, boolean isForward) { + queueOrSendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0); + } + + public final void scheduleSendResult(IBinder token, List results) { + ResultData res = new ResultData(); + res.token = token; + res.results = results; + queueOrSendMessage(H.SEND_RESULT, res); + } + + // we use token to identify this activity without having to send the + // activity itself back to the activity manager. (matters more with ipc) + public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, + ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, + Bundle state, List pendingResults, + List pendingNewIntents, boolean notResumed, boolean isForward, + String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { + ActivityClientRecord r = new ActivityClientRecord(); + + r.token = token; + r.ident = ident; + r.intent = intent; + r.activityInfo = info; + r.compatInfo = compatInfo; + r.state = state; + + r.pendingResults = pendingResults; + r.pendingIntents = pendingNewIntents; + + r.startsNotResumed = notResumed; + r.isForward = isForward; + + r.profileFile = profileName; + r.profileFd = profileFd; + r.autoStopProfiler = autoStopProfiler; + + updatePendingConfiguration(curConfig); + + queueOrSendMessage(H.LAUNCH_ACTIVITY, r); + } + + public final void scheduleRelaunchActivity(IBinder token, + List pendingResults, List pendingNewIntents, + int configChanges, boolean notResumed, Configuration config) { + requestRelaunchActivity(token, pendingResults, pendingNewIntents, + configChanges, notResumed, config, true); + } + + public final void scheduleNewIntent(List intents, IBinder token) { + NewIntentData data = new NewIntentData(); + data.intents = intents; + data.token = token; + + queueOrSendMessage(H.NEW_INTENT, data); + } + + public final void scheduleDestroyActivity(IBinder token, boolean finishing, + int configChanges) { + queueOrSendMessage(H.DESTROY_ACTIVITY, token, finishing ? 1 : 0, + configChanges); + } + + public final void scheduleReceiver(Intent intent, ActivityInfo info, + CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras, + boolean sync) { + ReceiverData r = new ReceiverData(intent, resultCode, data, extras, + sync, false, mAppThread.asBinder()); + r.info = info; + r.compatInfo = compatInfo; + queueOrSendMessage(H.RECEIVER, r); + } + + public final void scheduleCreateBackupAgent(ApplicationInfo app, + CompatibilityInfo compatInfo, int backupMode) { + CreateBackupAgentData d = new CreateBackupAgentData(); + d.appInfo = app; + d.compatInfo = compatInfo; + d.backupMode = backupMode; + + queueOrSendMessage(H.CREATE_BACKUP_AGENT, d); + } + + public final void scheduleDestroyBackupAgent(ApplicationInfo app, + CompatibilityInfo compatInfo) { + CreateBackupAgentData d = new CreateBackupAgentData(); + d.appInfo = app; + d.compatInfo = compatInfo; + + queueOrSendMessage(H.DESTROY_BACKUP_AGENT, d); + } + + public final void scheduleCreateService(IBinder token, + ServiceInfo info, CompatibilityInfo compatInfo) { + CreateServiceData s = new CreateServiceData(); + s.token = token; + s.info = info; + s.compatInfo = compatInfo; + + queueOrSendMessage(H.CREATE_SERVICE, s); + } + + public final void scheduleBindService(IBinder token, Intent intent, + boolean rebind) { + BindServiceData s = new BindServiceData(); + s.token = token; + s.intent = intent; + s.rebind = rebind; + + if (DEBUG_SERVICE) + Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid=" + + Binder.getCallingUid() + " pid=" + Binder.getCallingPid()); + queueOrSendMessage(H.BIND_SERVICE, s); + } + + public final void scheduleUnbindService(IBinder token, Intent intent) { + BindServiceData s = new BindServiceData(); + s.token = token; + s.intent = intent; + + queueOrSendMessage(H.UNBIND_SERVICE, s); + } + + public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId, + int flags ,Intent args) { + ServiceArgsData s = new ServiceArgsData(); + s.token = token; + s.taskRemoved = taskRemoved; + s.startId = startId; + s.flags = flags; + s.args = args; + + queueOrSendMessage(H.SERVICE_ARGS, s); + } + + public final void scheduleStopService(IBinder token) { + queueOrSendMessage(H.STOP_SERVICE, token); + } + + public final void bindApplication(String processName, + ApplicationInfo appInfo, List providers, + ComponentName instrumentationName, String profileFile, + ParcelFileDescriptor profileFd, boolean autoStopProfiler, + Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher, + int debugMode, boolean enableOpenGlTrace, boolean isRestrictedBackupMode, + boolean persistent, Configuration config, CompatibilityInfo compatInfo, + Map services, Bundle coreSettings) { + + if (services != null) { + // Setup the service cache in the ServiceManager + ServiceManager.initServiceCache(services); + } + + setCoreSettings(coreSettings); + + AppBindData data = new AppBindData(); + data.processName = processName; + data.appInfo = appInfo; + data.providers = providers; + data.instrumentationName = instrumentationName; + data.instrumentationArgs = instrumentationArgs; + data.instrumentationWatcher = instrumentationWatcher; + data.debugMode = debugMode; + data.enableOpenGlTrace = enableOpenGlTrace; + data.restrictedBackupMode = isRestrictedBackupMode; + data.persistent = persistent; + data.config = config; + data.compatInfo = compatInfo; + data.initProfileFile = profileFile; + data.initProfileFd = profileFd; + data.initAutoStopProfiler = false; + queueOrSendMessage(H.BIND_APPLICATION, data); + } + + public final void scheduleExit() { + queueOrSendMessage(H.EXIT_APPLICATION, null); + } + + public final void scheduleSuicide() { + queueOrSendMessage(H.SUICIDE, null); + } + + public void requestThumbnail(IBinder token) { + queueOrSendMessage(H.REQUEST_THUMBNAIL, token); + } + + public void scheduleConfigurationChanged(Configuration config) { + updatePendingConfiguration(config); + queueOrSendMessage(H.CONFIGURATION_CHANGED, config); + } + + public void updateTimeZone() { + TimeZone.setDefault(null); + } + + public void clearDnsCache() { + // a non-standard API to get this to libcore + InetAddress.clearDnsCache(); + } + + public void setHttpProxy(String host, String port, String exclList) { + Proxy.setHttpProxySystemProperty(host, port, exclList); + } + + public void processInBackground() { + mH.removeMessages(H.GC_WHEN_IDLE); + mH.sendMessage(mH.obtainMessage(H.GC_WHEN_IDLE)); + } + + public void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args) { + DumpComponentInfo data = new DumpComponentInfo(); + try { + data.fd = ParcelFileDescriptor.dup(fd); + data.token = servicetoken; + data.args = args; + queueOrSendMessage(H.DUMP_SERVICE, data); + } catch (IOException e) { + Slog.w(TAG, "dumpService failed", e); + } + } + + // This function exists to make sure all receiver dispatching is + // correctly ordered, since these are one-way calls and the binder driver + // applies transaction ordering per object for such calls. + public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, + int resultCode, String dataStr, Bundle extras, boolean ordered, + boolean sticky) throws RemoteException { + receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky); + } + + public void scheduleLowMemory() { + queueOrSendMessage(H.LOW_MEMORY, null); + } + + public void scheduleActivityConfigurationChanged(IBinder token) { + queueOrSendMessage(H.ACTIVITY_CONFIGURATION_CHANGED, token); + } + + public void profilerControl(boolean start, String path, ParcelFileDescriptor fd, + int profileType) { + ProfilerControlData pcd = new ProfilerControlData(); + pcd.path = path; + pcd.fd = fd; + queueOrSendMessage(H.PROFILER_CONTROL, pcd, start ? 1 : 0, profileType); + } + + public void dumpHeap(boolean managed, String path, ParcelFileDescriptor fd) { + DumpHeapData dhd = new DumpHeapData(); + dhd.path = path; + dhd.fd = fd; + queueOrSendMessage(H.DUMP_HEAP, dhd, managed ? 1 : 0); + } + + public void setSchedulingGroup(int group) { + // Note: do this immediately, since going into the foreground + // should happen regardless of what pending work we have to do + // and the activity manager will wait for us to report back that + // we are done before sending us to the background. + try { + Process.setProcessGroup(Process.myPid(), group); + } catch (Exception e) { + Slog.w(TAG, "Failed setting process group to " + group, e); + } + } + + public void getMemoryInfo(Debug.MemoryInfo outInfo) { + Debug.getMemoryInfo(outInfo); + } + + public void dispatchPackageBroadcast(int cmd, String[] packages) { + queueOrSendMessage(H.DISPATCH_PACKAGE_BROADCAST, packages, cmd); + } + + public void scheduleCrash(String msg) { + queueOrSendMessage(H.SCHEDULE_CRASH, msg); + } + + public void dumpActivity(FileDescriptor fd, IBinder activitytoken, + String prefix, String[] args) { + DumpComponentInfo data = new DumpComponentInfo(); + try { + data.fd = ParcelFileDescriptor.dup(fd); + data.token = activitytoken; + data.prefix = prefix; + data.args = args; + queueOrSendMessage(H.DUMP_ACTIVITY, data); + } catch (IOException e) { + Slog.w(TAG, "dumpActivity failed", e); + } + } + + public void dumpProvider(FileDescriptor fd, IBinder providertoken, + String[] args) { + DumpComponentInfo data = new DumpComponentInfo(); + try { + data.fd = ParcelFileDescriptor.dup(fd); + data.token = providertoken; + data.args = args; + queueOrSendMessage(H.DUMP_PROVIDER, data); + } catch (IOException e) { + Slog.w(TAG, "dumpProvider failed", e); + } + } + + @Override + public Debug.MemoryInfo dumpMemInfo(FileDescriptor fd, boolean checkin, + boolean all, String[] args) { + FileOutputStream fout = new FileOutputStream(fd); + PrintWriter pw = new PrintWriter(fout); + try { + return dumpMemInfo(pw, checkin, all); + } finally { + pw.flush(); + } + } + + private Debug.MemoryInfo dumpMemInfo(PrintWriter pw, boolean checkin, boolean all) { + long nativeMax = Debug.getNativeHeapSize() / 1024; + long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024; + long nativeFree = Debug.getNativeHeapFreeSize() / 1024; + + Debug.MemoryInfo memInfo = new Debug.MemoryInfo(); + Debug.getMemoryInfo(memInfo); + + if (!all) { + return memInfo; + } + + Runtime runtime = Runtime.getRuntime(); + + long dalvikMax = runtime.totalMemory() / 1024; + long dalvikFree = runtime.freeMemory() / 1024; + long dalvikAllocated = dalvikMax - dalvikFree; + long viewInstanceCount = ViewDebug.getViewInstanceCount(); + long viewRootInstanceCount = ViewDebug.getViewRootImplCount(); + long appContextInstanceCount = Debug.countInstancesOfClass(ContextImpl.class); + long activityInstanceCount = Debug.countInstancesOfClass(Activity.class); + int globalAssetCount = AssetManager.getGlobalAssetCount(); + int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount(); + int binderLocalObjectCount = Debug.getBinderLocalObjectCount(); + int binderProxyObjectCount = Debug.getBinderProxyObjectCount(); + int binderDeathObjectCount = Debug.getBinderDeathObjectCount(); + long openSslSocketCount = Debug.countInstancesOfClass(OpenSSLSocketImpl.class); + SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo(); + + // For checkin, we print one long comma-separated list of values + if (checkin) { + // NOTE: if you change anything significant below, also consider changing + // ACTIVITY_THREAD_CHECKIN_VERSION. + String processName = (mBoundApplication != null) + ? mBoundApplication.processName : "unknown"; + + // Header + pw.print(ACTIVITY_THREAD_CHECKIN_VERSION); pw.print(','); + pw.print(Process.myPid()); pw.print(','); + pw.print(processName); pw.print(','); + + // Heap info - max + pw.print(nativeMax); pw.print(','); + pw.print(dalvikMax); pw.print(','); + pw.print("N/A,"); + pw.print(nativeMax + dalvikMax); pw.print(','); + + // Heap info - allocated + pw.print(nativeAllocated); pw.print(','); + pw.print(dalvikAllocated); pw.print(','); + pw.print("N/A,"); + pw.print(nativeAllocated + dalvikAllocated); pw.print(','); + + // Heap info - free + pw.print(nativeFree); pw.print(','); + pw.print(dalvikFree); pw.print(','); + pw.print("N/A,"); + pw.print(nativeFree + dalvikFree); pw.print(','); + + // Heap info - proportional set size + pw.print(memInfo.nativePss); pw.print(','); + pw.print(memInfo.dalvikPss); pw.print(','); + pw.print(memInfo.otherPss); pw.print(','); + pw.print(memInfo.nativePss + memInfo.dalvikPss + memInfo.otherPss); pw.print(','); + + // Heap info - shared + pw.print(memInfo.nativeSharedDirty); pw.print(','); + pw.print(memInfo.dalvikSharedDirty); pw.print(','); + pw.print(memInfo.otherSharedDirty); pw.print(','); + pw.print(memInfo.nativeSharedDirty + memInfo.dalvikSharedDirty + + memInfo.otherSharedDirty); pw.print(','); + + // Heap info - private + pw.print(memInfo.nativePrivateDirty); pw.print(','); + pw.print(memInfo.dalvikPrivateDirty); pw.print(','); + pw.print(memInfo.otherPrivateDirty); pw.print(','); + pw.print(memInfo.nativePrivateDirty + memInfo.dalvikPrivateDirty + + memInfo.otherPrivateDirty); pw.print(','); + + // Object counts + pw.print(viewInstanceCount); pw.print(','); + pw.print(viewRootInstanceCount); pw.print(','); + pw.print(appContextInstanceCount); pw.print(','); + pw.print(activityInstanceCount); pw.print(','); + + pw.print(globalAssetCount); pw.print(','); + pw.print(globalAssetManagerCount); pw.print(','); + pw.print(binderLocalObjectCount); pw.print(','); + pw.print(binderProxyObjectCount); pw.print(','); + + pw.print(binderDeathObjectCount); pw.print(','); + pw.print(openSslSocketCount); pw.print(','); + + // SQL + pw.print(stats.memoryUsed / 1024); pw.print(','); + pw.print(stats.memoryUsed / 1024); pw.print(','); + pw.print(stats.pageCacheOverflow / 1024); pw.print(','); + pw.print(stats.largestMemAlloc / 1024); + for (int i = 0; i < stats.dbStats.size(); i++) { + DbStats dbStats = stats.dbStats.get(i); + pw.print(','); pw.print(dbStats.dbName); + pw.print(','); pw.print(dbStats.pageSize); + pw.print(','); pw.print(dbStats.dbSize); + pw.print(','); pw.print(dbStats.lookaside); + pw.print(','); pw.print(dbStats.cache); + pw.print(','); pw.print(dbStats.cache); + } + pw.println(); + + return memInfo; + } + + // otherwise, show human-readable format + printRow(pw, HEAP_COLUMN, "", "", "Shared", "Private", "Heap", "Heap", "Heap"); + printRow(pw, HEAP_COLUMN, "", "Pss", "Dirty", "Dirty", "Size", "Alloc", "Free"); + printRow(pw, HEAP_COLUMN, "", "------", "------", "------", "------", "------", + "------"); + printRow(pw, HEAP_COLUMN, "Native", memInfo.nativePss, memInfo.nativeSharedDirty, + memInfo.nativePrivateDirty, nativeMax, nativeAllocated, nativeFree); + printRow(pw, HEAP_COLUMN, "Dalvik", memInfo.dalvikPss, memInfo.dalvikSharedDirty, + memInfo.dalvikPrivateDirty, dalvikMax, dalvikAllocated, dalvikFree); + + int otherPss = memInfo.otherPss; + int otherSharedDirty = memInfo.otherSharedDirty; + int otherPrivateDirty = memInfo.otherPrivateDirty; + + for (int i=0; i 0) { + pw.println(" DATABASES"); + printRow(pw, " %8s %8s %14s %14s %s", "pgsz", "dbsz", "Lookaside(b)", "cache", + "Dbname"); + for (int i = 0; i < N; i++) { + DbStats dbStats = stats.dbStats.get(i); + printRow(pw, DB_INFO_FORMAT, + (dbStats.pageSize > 0) ? String.valueOf(dbStats.pageSize) : " ", + (dbStats.dbSize > 0) ? String.valueOf(dbStats.dbSize) : " ", + (dbStats.lookaside > 0) ? String.valueOf(dbStats.lookaside) : " ", + dbStats.cache, dbStats.dbName); + } + } + + // Asset details. + String assetAlloc = AssetManager.getAssetAllocations(); + if (assetAlloc != null) { + pw.println(" "); + pw.println(" Asset Allocations"); + pw.print(assetAlloc); + } + + return memInfo; + } + + @Override + public void dumpGfxInfo(FileDescriptor fd, String[] args) { + dumpGraphicsInfo(fd); + WindowManagerImpl.getDefault().dumpGfxInfo(fd); + } + + @Override + public void dumpDbInfo(FileDescriptor fd, String[] args) { + PrintWriter pw = new PrintWriter(new FileOutputStream(fd)); + PrintWriterPrinter printer = new PrintWriterPrinter(pw); + SQLiteDebug.dump(printer, args); + pw.flush(); + } + + @Override + public void unstableProviderDied(IBinder provider) { + queueOrSendMessage(H.UNSTABLE_PROVIDER_DIED, provider); + } + + private void printRow(PrintWriter pw, String format, Object...objs) { + pw.println(String.format(format, objs)); + } + + public void setCoreSettings(Bundle coreSettings) { + queueOrSendMessage(H.SET_CORE_SETTINGS, coreSettings); + } + + public void updatePackageCompatibilityInfo(String pkg, CompatibilityInfo info) { + UpdateCompatibilityData ucd = new UpdateCompatibilityData(); + ucd.pkg = pkg; + ucd.info = info; + queueOrSendMessage(H.UPDATE_PACKAGE_COMPATIBILITY_INFO, ucd); + } + + public void scheduleTrimMemory(int level) { + queueOrSendMessage(H.TRIM_MEMORY, null, level); + } + + } + + private class H extends Handler { + public static final int LAUNCH_ACTIVITY = 100; + public static final int PAUSE_ACTIVITY = 101; + public static final int PAUSE_ACTIVITY_FINISHING= 102; + public static final int STOP_ACTIVITY_SHOW = 103; + public static final int STOP_ACTIVITY_HIDE = 104; + public static final int SHOW_WINDOW = 105; + public static final int HIDE_WINDOW = 106; + public static final int RESUME_ACTIVITY = 107; + public static final int SEND_RESULT = 108; + public static final int DESTROY_ACTIVITY = 109; + public static final int BIND_APPLICATION = 110; + public static final int EXIT_APPLICATION = 111; + public static final int NEW_INTENT = 112; + public static final int RECEIVER = 113; + public static final int CREATE_SERVICE = 114; + public static final int SERVICE_ARGS = 115; + public static final int STOP_SERVICE = 116; + public static final int REQUEST_THUMBNAIL = 117; + public static final int CONFIGURATION_CHANGED = 118; + public static final int CLEAN_UP_CONTEXT = 119; + public static final int GC_WHEN_IDLE = 120; + public static final int BIND_SERVICE = 121; + public static final int UNBIND_SERVICE = 122; + public static final int DUMP_SERVICE = 123; + public static final int LOW_MEMORY = 124; + public static final int ACTIVITY_CONFIGURATION_CHANGED = 125; + public static final int RELAUNCH_ACTIVITY = 126; + public static final int PROFILER_CONTROL = 127; + public static final int CREATE_BACKUP_AGENT = 128; + public static final int DESTROY_BACKUP_AGENT = 129; + public static final int SUICIDE = 130; + public static final int REMOVE_PROVIDER = 131; + public static final int ENABLE_JIT = 132; + public static final int DISPATCH_PACKAGE_BROADCAST = 133; + public static final int SCHEDULE_CRASH = 134; + public static final int DUMP_HEAP = 135; + public static final int DUMP_ACTIVITY = 136; + public static final int SLEEPING = 137; + public static final int SET_CORE_SETTINGS = 138; + public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139; + public static final int TRIM_MEMORY = 140; + public static final int DUMP_PROVIDER = 141; + public static final int UNSTABLE_PROVIDER_DIED = 142; + String codeToString(int code) { + if (DEBUG_MESSAGES) { + switch (code) { + case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY"; + case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY"; + case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING"; + case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW"; + case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE"; + case SHOW_WINDOW: return "SHOW_WINDOW"; + case HIDE_WINDOW: return "HIDE_WINDOW"; + case RESUME_ACTIVITY: return "RESUME_ACTIVITY"; + case SEND_RESULT: return "SEND_RESULT"; + case DESTROY_ACTIVITY: return "DESTROY_ACTIVITY"; + case BIND_APPLICATION: return "BIND_APPLICATION"; + case EXIT_APPLICATION: return "EXIT_APPLICATION"; + case NEW_INTENT: return "NEW_INTENT"; + case RECEIVER: return "RECEIVER"; + case CREATE_SERVICE: return "CREATE_SERVICE"; + case SERVICE_ARGS: return "SERVICE_ARGS"; + case STOP_SERVICE: return "STOP_SERVICE"; + case REQUEST_THUMBNAIL: return "REQUEST_THUMBNAIL"; + case CONFIGURATION_CHANGED: return "CONFIGURATION_CHANGED"; + case CLEAN_UP_CONTEXT: return "CLEAN_UP_CONTEXT"; + case GC_WHEN_IDLE: return "GC_WHEN_IDLE"; + case BIND_SERVICE: return "BIND_SERVICE"; + case UNBIND_SERVICE: return "UNBIND_SERVICE"; + case DUMP_SERVICE: return "DUMP_SERVICE"; + case LOW_MEMORY: return "LOW_MEMORY"; + case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED"; + case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY"; + case PROFILER_CONTROL: return "PROFILER_CONTROL"; + case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT"; + case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT"; + case SUICIDE: return "SUICIDE"; + case REMOVE_PROVIDER: return "REMOVE_PROVIDER"; + case ENABLE_JIT: return "ENABLE_JIT"; + case DISPATCH_PACKAGE_BROADCAST: return "DISPATCH_PACKAGE_BROADCAST"; + case SCHEDULE_CRASH: return "SCHEDULE_CRASH"; + case DUMP_HEAP: return "DUMP_HEAP"; + case DUMP_ACTIVITY: return "DUMP_ACTIVITY"; + case SLEEPING: return "SLEEPING"; + case SET_CORE_SETTINGS: return "SET_CORE_SETTINGS"; + case UPDATE_PACKAGE_COMPATIBILITY_INFO: return "UPDATE_PACKAGE_COMPATIBILITY_INFO"; + case TRIM_MEMORY: return "TRIM_MEMORY"; + case DUMP_PROVIDER: return "DUMP_PROVIDER"; + case UNSTABLE_PROVIDER_DIED: return "UNSTABLE_PROVIDER_DIED"; + } + } + return Integer.toString(code); + } + public void handleMessage(Message msg) { + if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); + switch (msg.what) { + case LAUNCH_ACTIVITY: { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); + ActivityClientRecord r = (ActivityClientRecord)msg.obj; + + r.packageInfo = getPackageInfoNoCheck( + r.activityInfo.applicationInfo, r.compatInfo); + handleLaunchActivity(r, null); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } break; + case RELAUNCH_ACTIVITY: { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart"); + ActivityClientRecord r = (ActivityClientRecord)msg.obj; + handleRelaunchActivity(r); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } break; + case PAUSE_ACTIVITY: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); + handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2); + maybeSnapshot(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case PAUSE_ACTIVITY_FINISHING: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); + handlePauseActivity((IBinder)msg.obj, true, msg.arg1 != 0, msg.arg2); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case STOP_ACTIVITY_SHOW: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); + handleStopActivity((IBinder)msg.obj, true, msg.arg2); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case STOP_ACTIVITY_HIDE: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); + handleStopActivity((IBinder)msg.obj, false, msg.arg2); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case SHOW_WINDOW: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow"); + handleWindowVisibility((IBinder)msg.obj, true); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case HIDE_WINDOW: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityHideWindow"); + handleWindowVisibility((IBinder)msg.obj, false); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case RESUME_ACTIVITY: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume"); + handleResumeActivity((IBinder)msg.obj, true, + msg.arg1 != 0); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case SEND_RESULT: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult"); + handleSendResult((ResultData)msg.obj); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case DESTROY_ACTIVITY: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy"); + handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0, + msg.arg2, false); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case BIND_APPLICATION: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication"); + AppBindData data = (AppBindData)msg.obj; + handleBindApplication(data); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case EXIT_APPLICATION: + if (mInitialApplication != null) { + mInitialApplication.onTerminate(); + } + Looper.myLooper().quit(); + break; + case NEW_INTENT: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent"); + handleNewIntent((NewIntentData)msg.obj); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case RECEIVER: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp"); + handleReceiver((ReceiverData)msg.obj); + maybeSnapshot(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case CREATE_SERVICE: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceCreate"); + handleCreateService((CreateServiceData)msg.obj); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case BIND_SERVICE: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind"); + handleBindService((BindServiceData)msg.obj); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case UNBIND_SERVICE: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind"); + handleUnbindService((BindServiceData)msg.obj); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case SERVICE_ARGS: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStart"); + handleServiceArgs((ServiceArgsData)msg.obj); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case STOP_SERVICE: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop"); + handleStopService((IBinder)msg.obj); + maybeSnapshot(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case REQUEST_THUMBNAIL: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestThumbnail"); + handleRequestThumbnail((IBinder)msg.obj); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case CONFIGURATION_CHANGED: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged"); + handleConfigurationChanged((Configuration)msg.obj, null); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case CLEAN_UP_CONTEXT: + ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj; + cci.context.performFinalCleanup(cci.who, cci.what); + break; + case GC_WHEN_IDLE: + scheduleGcIdler(); + break; + case DUMP_SERVICE: + handleDumpService((DumpComponentInfo)msg.obj); + break; + case LOW_MEMORY: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "lowMemory"); + handleLowMemory(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case ACTIVITY_CONFIGURATION_CHANGED: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged"); + handleActivityConfigurationChanged((IBinder)msg.obj); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case PROFILER_CONTROL: + handleProfilerControl(msg.arg1 != 0, (ProfilerControlData)msg.obj, msg.arg2); + break; + case CREATE_BACKUP_AGENT: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupCreateAgent"); + handleCreateBackupAgent((CreateBackupAgentData)msg.obj); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case DESTROY_BACKUP_AGENT: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupDestroyAgent"); + handleDestroyBackupAgent((CreateBackupAgentData)msg.obj); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case SUICIDE: + Process.killProcess(Process.myPid()); + break; + case REMOVE_PROVIDER: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "providerRemove"); + completeRemoveProvider((ProviderRefCount)msg.obj); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case ENABLE_JIT: + ensureJitEnabled(); + break; + case DISPATCH_PACKAGE_BROADCAST: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastPackage"); + handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case SCHEDULE_CRASH: + throw new RemoteServiceException((String)msg.obj); + case DUMP_HEAP: + handleDumpHeap(msg.arg1 != 0, (DumpHeapData)msg.obj); + break; + case DUMP_ACTIVITY: + handleDumpActivity((DumpComponentInfo)msg.obj); + break; + case DUMP_PROVIDER: + handleDumpProvider((DumpComponentInfo)msg.obj); + break; + case SLEEPING: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "sleeping"); + handleSleeping((IBinder)msg.obj, msg.arg1 != 0); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case SET_CORE_SETTINGS: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setCoreSettings"); + handleSetCoreSettings((Bundle) msg.obj); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case UPDATE_PACKAGE_COMPATIBILITY_INFO: + handleUpdatePackageCompatibilityInfo((UpdateCompatibilityData)msg.obj); + break; + case TRIM_MEMORY: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory"); + handleTrimMemory(msg.arg1); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case UNSTABLE_PROVIDER_DIED: + handleUnstableProviderDied((IBinder)msg.obj, false); + break; + } + if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what)); + } + + private void maybeSnapshot() { + if (mBoundApplication != null && SamplingProfilerIntegration.isEnabled()) { + // convert the *private* ActivityThread.PackageInfo to *public* known + // android.content.pm.PackageInfo + String packageName = mBoundApplication.info.mPackageName; + android.content.pm.PackageInfo packageInfo = null; + try { + Context context = getSystemContext(); + if(context == null) { + Log.e(TAG, "cannot get a valid context"); + return; + } + PackageManager pm = context.getPackageManager(); + if(pm == null) { + Log.e(TAG, "cannot get a valid PackageManager"); + return; + } + packageInfo = pm.getPackageInfo( + packageName, PackageManager.GET_ACTIVITIES); + } catch (NameNotFoundException e) { + Log.e(TAG, "cannot get package info for " + packageName, e); + } + SamplingProfilerIntegration.writeSnapshot(mBoundApplication.processName, packageInfo); + } + } + } + + private class Idler implements MessageQueue.IdleHandler { + public final boolean queueIdle() { + ActivityClientRecord a = mNewActivities; + boolean stopProfiling = false; + if (mBoundApplication != null && mProfiler.profileFd != null + && mProfiler.autoStopProfiler) { + stopProfiling = true; + } + if (a != null) { + mNewActivities = null; + IActivityManager am = ActivityManagerNative.getDefault(); + ActivityClientRecord prev; + do { + if (localLOGV) Slog.v( + TAG, "Reporting idle of " + a + + " finished=" + + (a.activity != null && a.activity.mFinished)); + if (a.activity != null && !a.activity.mFinished) { + try { + am.activityIdle(a.token, a.createdConfig, stopProfiling); + a.createdConfig = null; + } catch (RemoteException ex) { + // Ignore + } + } + prev = a; + a = a.nextIdle; + prev.nextIdle = null; + } while (a != null); + } + if (stopProfiling) { + mProfiler.stopProfiling(); + } + ensureJitEnabled(); + return false; + } + } + + final class GcIdler implements MessageQueue.IdleHandler { + public final boolean queueIdle() { + doGcIfNeeded(); + return false; + } + } + + private static class ResourcesKey { + final private String mResDir; + final private float mScale; + final private int mHash; + + ResourcesKey(String resDir, float scale) { + mResDir = resDir; + mScale = scale; + mHash = mResDir.hashCode() << 2 + (int) (mScale * 2); + } + + @Override + public int hashCode() { + return mHash; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ResourcesKey)) { + return false; + } + ResourcesKey peer = (ResourcesKey) obj; + return mResDir.equals(peer.mResDir) && mScale == peer.mScale; + } + } + + public static ActivityThread currentActivityThread() { + return sThreadLocal.get(); + } + + public static String currentPackageName() { + ActivityThread am = currentActivityThread(); + return (am != null && am.mBoundApplication != null) + ? am.mBoundApplication.processName : null; + } + + public static Application currentApplication() { + ActivityThread am = currentActivityThread(); + return am != null ? am.mInitialApplication : null; + } + + public static IPackageManager getPackageManager() { + if (sPackageManager != null) { + //Slog.v("PackageManager", "returning cur default = " + sPackageManager); + return sPackageManager; + } + IBinder b = ServiceManager.getService("package"); + //Slog.v("PackageManager", "default service binder = " + b); + sPackageManager = IPackageManager.Stub.asInterface(b); + //Slog.v("PackageManager", "default service = " + sPackageManager); + return sPackageManager; + } + + DisplayMetrics getDisplayMetricsLocked(CompatibilityInfo ci, boolean forceUpdate) { + DisplayMetrics dm = mDisplayMetrics.get(ci); + if (dm != null && !forceUpdate) { + return dm; + } + if (dm == null) { + dm = new DisplayMetrics(); + mDisplayMetrics.put(ci, dm); + } + Display d = WindowManagerImpl.getDefault(ci).getDefaultDisplay(); + d.getMetrics(dm); + //Slog.i("foo", "New metrics: w=" + metrics.widthPixels + " h=" + // + metrics.heightPixels + " den=" + metrics.density + // + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi); + return dm; + } + + private Configuration mMainThreadConfig = new Configuration(); + Configuration applyConfigCompatMainThread(Configuration config, CompatibilityInfo compat) { + if (config == null) { + return null; + } + if (compat != null && !compat.supportsScreen()) { + mMainThreadConfig.setTo(config); + config = mMainThreadConfig; + compat.applyToConfiguration(config); + } + return config; + } + + /** + * Creates the top level Resources for applications with the given compatibility info. + * + * @param resDir the resource directory. + * @param compInfo the compability info. It will use the default compatibility info when it's + * null. + */ + Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) { + ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale); + Resources r; + synchronized (mPackages) { + // Resources is app scale dependent. + if (false) { + Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + + compInfo.applicationScale); + } + WeakReference wr = mActiveResources.get(key); + r = wr != null ? wr.get() : null; + //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate()); + if (r != null && r.getAssets().isUpToDate()) { + if (false) { + Slog.w(TAG, "Returning cached resources " + r + " " + resDir + + ": appScale=" + r.getCompatibilityInfo().applicationScale); + } + return r; + } + } + + //if (r != null) { + // Slog.w(TAG, "Throwing away out-of-date resources!!!! " + // + r + " " + resDir); + //} + + AssetManager assets = new AssetManager(); + if (assets.addAssetPath(resDir) == 0) { + return null; + } + + //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); + DisplayMetrics metrics = getDisplayMetricsLocked(null, false); + r = new Resources(assets, metrics, getConfiguration(), compInfo); + if (false) { + Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " + + r.getConfiguration() + " appScale=" + + r.getCompatibilityInfo().applicationScale); + } + + synchronized (mPackages) { + WeakReference wr = mActiveResources.get(key); + Resources existing = wr != null ? wr.get() : null; + if (existing != null && existing.getAssets().isUpToDate()) { + // Someone else already created the resources while we were + // unlocked; go ahead and use theirs. + r.getAssets().close(); + return existing; + } + + // XXX need to remove entries when weak references go away + mActiveResources.put(key, new WeakReference(r)); + return r; + } + } + + /** + * Creates the top level resources for the given package. + */ + Resources getTopLevelResources(String resDir, LoadedApk pkgInfo) { + return getTopLevelResources(resDir, pkgInfo.mCompatibilityInfo.get()); + } + + final Handler getHandler() { + return mH; + } + + public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, + int flags) { + synchronized (mPackages) { + WeakReference ref; + if ((flags&Context.CONTEXT_INCLUDE_CODE) != 0) { + ref = mPackages.get(packageName); + } else { + ref = mResourcePackages.get(packageName); + } + LoadedApk packageInfo = ref != null ? ref.get() : null; + //Slog.i(TAG, "getPackageInfo " + packageName + ": " + packageInfo); + //if (packageInfo != null) Slog.i(TAG, "isUptoDate " + packageInfo.mResDir + // + ": " + packageInfo.mResources.getAssets().isUpToDate()); + if (packageInfo != null && (packageInfo.mResources == null + || packageInfo.mResources.getAssets().isUpToDate())) { + if (packageInfo.isSecurityViolation() + && (flags&Context.CONTEXT_IGNORE_SECURITY) == 0) { + throw new SecurityException( + "Requesting code from " + packageName + + " to be run in process " + + mBoundApplication.processName + + "/" + mBoundApplication.appInfo.uid); + } + return packageInfo; + } + } + + ApplicationInfo ai = null; + try { + ai = getPackageManager().getApplicationInfo(packageName, + PackageManager.GET_SHARED_LIBRARY_FILES, UserId.myUserId()); + } catch (RemoteException e) { + // Ignore + } + + if (ai != null) { + return getPackageInfo(ai, compatInfo, flags); + } + + return null; + } + + public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo, + int flags) { + boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0; + boolean securityViolation = includeCode && ai.uid != 0 + && ai.uid != Process.SYSTEM_UID && (mBoundApplication != null + ? !UserId.isSameApp(ai.uid, mBoundApplication.appInfo.uid) + : true); + if ((flags&(Context.CONTEXT_INCLUDE_CODE + |Context.CONTEXT_IGNORE_SECURITY)) + == Context.CONTEXT_INCLUDE_CODE) { + if (securityViolation) { + String msg = "Requesting code from " + ai.packageName + + " (with uid " + ai.uid + ")"; + if (mBoundApplication != null) { + msg = msg + " to be run in process " + + mBoundApplication.processName + " (with uid " + + mBoundApplication.appInfo.uid + ")"; + } + throw new SecurityException(msg); + } + } + return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode); + } + + public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, + CompatibilityInfo compatInfo) { + return getPackageInfo(ai, compatInfo, null, false, true); + } + + public final LoadedApk peekPackageInfo(String packageName, boolean includeCode) { + synchronized (mPackages) { + WeakReference ref; + if (includeCode) { + ref = mPackages.get(packageName); + } else { + ref = mResourcePackages.get(packageName); + } + return ref != null ? ref.get() : null; + } + } + + private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo, + ClassLoader baseLoader, boolean securityViolation, boolean includeCode) { + synchronized (mPackages) { + WeakReference ref; + if (includeCode) { + ref = mPackages.get(aInfo.packageName); + } else { + ref = mResourcePackages.get(aInfo.packageName); + } + LoadedApk packageInfo = ref != null ? ref.get() : null; + if (packageInfo == null || (packageInfo.mResources != null + && !packageInfo.mResources.getAssets().isUpToDate())) { + if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package " + : "Loading resource-only package ") + aInfo.packageName + + " (in " + (mBoundApplication != null + ? mBoundApplication.processName : null) + + ")"); + packageInfo = + new LoadedApk(this, aInfo, compatInfo, this, baseLoader, + securityViolation, includeCode && + (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0); + if (includeCode) { + mPackages.put(aInfo.packageName, + new WeakReference(packageInfo)); + } else { + mResourcePackages.put(aInfo.packageName, + new WeakReference(packageInfo)); + } + } + return packageInfo; + } + } + + ActivityThread() { + } + + public ApplicationThread getApplicationThread() + { + return mAppThread; + } + + public Instrumentation getInstrumentation() + { + return mInstrumentation; + } + + public Configuration getConfiguration() { + return mResConfiguration; + } + + public boolean isProfiling() { + return mProfiler != null && mProfiler.profileFile != null + && mProfiler.profileFd == null; + } + + public String getProfileFilePath() { + return mProfiler.profileFile; + } + + public Looper getLooper() { + return mLooper; + } + + public Application getApplication() { + return mInitialApplication; + } + + public String getProcessName() { + return mBoundApplication.processName; + } + + public ContextImpl getSystemContext() { + synchronized (this) { + if (mSystemContext == null) { + ContextImpl context = + ContextImpl.createSystemContext(this); + LoadedApk info = new LoadedApk(this, "android", context, null, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO); + context.init(info, null, this); + context.getResources().updateConfiguration( + getConfiguration(), getDisplayMetricsLocked( + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, false)); + mSystemContext = context; + //Slog.i(TAG, "Created system resources " + context.getResources() + // + ": " + context.getResources().getConfiguration()); + } + } + return mSystemContext; + } + + public void installSystemApplicationInfo(ApplicationInfo info) { + synchronized (this) { + ContextImpl context = getSystemContext(); + context.init(new LoadedApk(this, "android", context, info, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO), null, this); + + // give ourselves a default profiler + mProfiler = new Profiler(); + } + } + + void ensureJitEnabled() { + if (!mJitEnabled) { + mJitEnabled = true; + dalvik.system.VMRuntime.getRuntime().startJitCompilation(); + } + } + + void scheduleGcIdler() { + if (!mGcIdlerScheduled) { + mGcIdlerScheduled = true; + Looper.myQueue().addIdleHandler(mGcIdler); + } + mH.removeMessages(H.GC_WHEN_IDLE); + } + + void unscheduleGcIdler() { + if (mGcIdlerScheduled) { + mGcIdlerScheduled = false; + Looper.myQueue().removeIdleHandler(mGcIdler); + } + mH.removeMessages(H.GC_WHEN_IDLE); + } + + void doGcIfNeeded() { + mGcIdlerScheduled = false; + final long now = SystemClock.uptimeMillis(); + //Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime() + // + "m now=" + now); + if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) { + //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!"); + BinderInternal.forceGc("bg"); + } + } + + public void registerOnActivityPausedListener(Activity activity, + OnActivityPausedListener listener) { + synchronized (mOnPauseListeners) { + ArrayList list = mOnPauseListeners.get(activity); + if (list == null) { + list = new ArrayList(); + mOnPauseListeners.put(activity, list); + } + list.add(listener); + } + } + + public void unregisterOnActivityPausedListener(Activity activity, + OnActivityPausedListener listener) { + synchronized (mOnPauseListeners) { + ArrayList list = mOnPauseListeners.get(activity); + if (list != null) { + list.remove(listener); + } + } + } + + public final ActivityInfo resolveActivityInfo(Intent intent) { + ActivityInfo aInfo = intent.resolveActivityInfo( + mInitialApplication.getPackageManager(), PackageManager.GET_SHARED_LIBRARY_FILES); + if (aInfo == null) { + // Throw an exception. + Instrumentation.checkStartActivityResult( + ActivityManager.START_CLASS_NOT_FOUND, intent); + } + return aInfo; + } + + public final Activity startActivityNow(Activity parent, String id, + Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state, + Activity.NonConfigurationInstances lastNonConfigurationInstances) { + ActivityClientRecord r = new ActivityClientRecord(); + r.token = token; + r.ident = 0; + r.intent = intent; + r.state = state; + r.parent = parent; + r.embeddedID = id; + r.activityInfo = activityInfo; + r.lastNonConfigurationInstances = lastNonConfigurationInstances; + if (localLOGV) { + ComponentName compname = intent.getComponent(); + String name; + if (compname != null) { + name = compname.toShortString(); + } else { + name = "(Intent " + intent + ").getComponent() returned null"; + } + Slog.v(TAG, "Performing launch: action=" + intent.getAction() + + ", comp=" + name + + ", token=" + token); + } + return performLaunchActivity(r, null); + } + + public final Activity getActivity(IBinder token) { + return mActivities.get(token).activity; + } + + public final void sendActivityResult( + IBinder token, String id, int requestCode, + int resultCode, Intent data) { + if (DEBUG_RESULTS) Slog.v(TAG, "sendActivityResult: id=" + id + + " req=" + requestCode + " res=" + resultCode + " data=" + data); + ArrayList list = new ArrayList(); + list.add(new ResultInfo(id, requestCode, resultCode, data)); + mAppThread.scheduleSendResult(token, list); + } + + // if the thread hasn't started yet, we don't have the handler, so just + // save the messages until we're ready. + private void queueOrSendMessage(int what, Object obj) { + queueOrSendMessage(what, obj, 0, 0); + } + + private void queueOrSendMessage(int what, Object obj, int arg1) { + queueOrSendMessage(what, obj, arg1, 0); + } + + private void queueOrSendMessage(int what, Object obj, int arg1, int arg2) { + synchronized (this) { + if (DEBUG_MESSAGES) Slog.v( + TAG, "SCHEDULE " + what + " " + mH.codeToString(what) + + ": " + arg1 + " / " + obj); + Message msg = Message.obtain(); + msg.what = what; + msg.obj = obj; + msg.arg1 = arg1; + msg.arg2 = arg2; + mH.sendMessage(msg); + } + } + + final void scheduleContextCleanup(ContextImpl context, String who, + String what) { + ContextCleanupInfo cci = new ContextCleanupInfo(); + cci.context = context; + cci.who = who; + cci.what = what; + queueOrSendMessage(H.CLEAN_UP_CONTEXT, cci); + } + + private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { + // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); + + ActivityInfo aInfo = r.activityInfo; + if (r.packageInfo == null) { + r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, + Context.CONTEXT_INCLUDE_CODE); + } + + ComponentName component = r.intent.getComponent(); + if (component == null) { + component = r.intent.resolveActivity( + mInitialApplication.getPackageManager()); + r.intent.setComponent(component); + } + + if (r.activityInfo.targetActivity != null) { + component = new ComponentName(r.activityInfo.packageName, + r.activityInfo.targetActivity); + } + + Activity activity = null; + try { + java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); + activity = mInstrumentation.newActivity( + cl, component.getClassName(), r.intent); + StrictMode.incrementExpectedActivityCount(activity.getClass()); + r.intent.setExtrasClassLoader(cl); + if (r.state != null) { + r.state.setClassLoader(cl); + } + } catch (Exception e) { + if (!mInstrumentation.onException(activity, e)) { + throw new RuntimeException( + "Unable to instantiate activity " + component + + ": " + e.toString(), e); + } + } + + try { + Application app = r.packageInfo.makeApplication(false, mInstrumentation); + + if (localLOGV) Slog.v(TAG, "Performing launch of " + r); + if (localLOGV) Slog.v( + TAG, r + ": app=" + app + + ", appName=" + app.getPackageName() + + ", pkg=" + r.packageInfo.getPackageName() + + ", comp=" + r.intent.getComponent().toShortString() + + ", dir=" + r.packageInfo.getAppDir()); + + if (activity != null) { + ContextImpl appContext = new ContextImpl(); + appContext.init(r.packageInfo, r.token, this); + appContext.setOuterContext(activity); + CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); + Configuration config = new Configuration(mCompatConfiguration); + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " + + r.activityInfo.name + " with config " + config); + activity.attach(appContext, this, getInstrumentation(), r.token, + r.ident, app, r.intent, r.activityInfo, title, r.parent, + r.embeddedID, r.lastNonConfigurationInstances, config); + + if (customIntent != null) { + activity.mIntent = customIntent; + } + r.lastNonConfigurationInstances = null; + activity.mStartedActivity = false; + int theme = r.activityInfo.getThemeResource(); + if (theme != 0) { + activity.setTheme(theme); + } + + activity.mCalled = false; + mInstrumentation.callActivityOnCreate(activity, r.state); + if (!activity.mCalled) { + throw new SuperNotCalledException( + "Activity " + r.intent.getComponent().toShortString() + + " did not call through to super.onCreate()"); + } + r.activity = activity; + r.stopped = true; + if (!r.activity.mFinished) { + activity.performStart(); + r.stopped = false; + } + if (!r.activity.mFinished) { + if (r.state != null) { + mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state); + } + } + if (!r.activity.mFinished) { + activity.mCalled = false; + mInstrumentation.callActivityOnPostCreate(activity, r.state); + if (!activity.mCalled) { + throw new SuperNotCalledException( + "Activity " + r.intent.getComponent().toShortString() + + " did not call through to super.onPostCreate()"); + } + } + } + r.paused = true; + + mActivities.put(r.token, r); + + } catch (SuperNotCalledException e) { + throw e; + + } catch (Exception e) { + if (!mInstrumentation.onException(activity, e)) { + throw new RuntimeException( + "Unable to start activity " + component + + ": " + e.toString(), e); + } + } + + return activity; + } + + private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { + // If we are getting ready to gc after going to the background, well + // we are back active so skip it. + unscheduleGcIdler(); + + if (r.profileFd != null) { + mProfiler.setProfiler(r.profileFile, r.profileFd); + mProfiler.startProfiling(); + mProfiler.autoStopProfiler = r.autoStopProfiler; + } + + // Make sure we are running with the most recent config. + handleConfigurationChanged(null, null); + + if (localLOGV) Slog.v( + TAG, "Handling launch of " + r); + Activity a = performLaunchActivity(r, customIntent); + + if (a != null) { + r.createdConfig = new Configuration(mConfiguration); + Bundle oldState = r.state; + handleResumeActivity(r.token, false, r.isForward); + + if (!r.activity.mFinished && r.startsNotResumed) { + // The activity manager actually wants this one to start out + // paused, because it needs to be visible but isn't in the + // foreground. We accomplish this by going through the + // normal startup (because activities expect to go through + // onResume() the first time they run, before their window + // is displayed), and then pausing it. However, in this case + // we do -not- need to do the full pause cycle (of freezing + // and such) because the activity manager assumes it can just + // retain the current state it has. + try { + r.activity.mCalled = false; + mInstrumentation.callActivityOnPause(r.activity); + // We need to keep around the original state, in case + // we need to be created again. But we only do this + // for pre-Honeycomb apps, which always save their state + // when pausing, so we can not have them save their state + // when restarting from a paused state. For HC and later, + // we want to (and can) let the state be saved as the normal + // part of stopping the activity. + if (r.isPreHoneycomb()) { + r.state = oldState; + } + if (!r.activity.mCalled) { + throw new SuperNotCalledException( + "Activity " + r.intent.getComponent().toShortString() + + " did not call through to super.onPause()"); + } + + } catch (SuperNotCalledException e) { + throw e; + + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to pause activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + r.paused = true; + } + } else { + // If there was an error, for any reason, tell the activity + // manager to stop us. + try { + ActivityManagerNative.getDefault() + .finishActivity(r.token, Activity.RESULT_CANCELED, null); + } catch (RemoteException ex) { + // Ignore + } + } + } + + private void deliverNewIntents(ActivityClientRecord r, + List intents) { + final int N = intents.size(); + for (int i=0; i intents) { + ActivityClientRecord r = mActivities.get(token); + if (r != null) { + final boolean resumed = !r.paused; + if (resumed) { + r.activity.mTemporaryPause = true; + mInstrumentation.callActivityOnPause(r.activity); + } + deliverNewIntents(r, intents); + if (resumed) { + r.activity.performResume(); + r.activity.mTemporaryPause = false; + } + } + } + + private void handleNewIntent(NewIntentData data) { + performNewIntents(data.token, data.intents); + } + + private static final ThreadLocal sCurrentBroadcastIntent = new ThreadLocal(); + + /** + * Return the Intent that's currently being handled by a + * BroadcastReceiver on this thread, or null if none. + * @hide + */ + public static Intent getIntentBeingBroadcast() { + return sCurrentBroadcastIntent.get(); + } + + private void handleReceiver(ReceiverData data) { + // If we are getting ready to gc after going to the background, well + // we are back active so skip it. + unscheduleGcIdler(); + + String component = data.intent.getComponent().getClassName(); + + LoadedApk packageInfo = getPackageInfoNoCheck( + data.info.applicationInfo, data.compatInfo); + + IActivityManager mgr = ActivityManagerNative.getDefault(); + + BroadcastReceiver receiver; + try { + java.lang.ClassLoader cl = packageInfo.getClassLoader(); + data.intent.setExtrasClassLoader(cl); + data.setExtrasClassLoader(cl); + receiver = (BroadcastReceiver)cl.loadClass(component).newInstance(); + } catch (Exception e) { + if (DEBUG_BROADCAST) Slog.i(TAG, + "Finishing failed broadcast to " + data.intent.getComponent()); + data.sendFinished(mgr); + throw new RuntimeException( + "Unable to instantiate receiver " + component + + ": " + e.toString(), e); + } + + try { + Application app = packageInfo.makeApplication(false, mInstrumentation); + + if (localLOGV) Slog.v( + TAG, "Performing receive of " + data.intent + + ": app=" + app + + ", appName=" + app.getPackageName() + + ", pkg=" + packageInfo.getPackageName() + + ", comp=" + data.intent.getComponent().toShortString() + + ", dir=" + packageInfo.getAppDir()); + + ContextImpl context = (ContextImpl)app.getBaseContext(); + sCurrentBroadcastIntent.set(data.intent); + receiver.setPendingResult(data); + receiver.onReceive(context.getReceiverRestrictedContext(), + data.intent); + } catch (Exception e) { + if (DEBUG_BROADCAST) Slog.i(TAG, + "Finishing failed broadcast to " + data.intent.getComponent()); + data.sendFinished(mgr); + if (!mInstrumentation.onException(receiver, e)) { + throw new RuntimeException( + "Unable to start receiver " + component + + ": " + e.toString(), e); + } + } finally { + sCurrentBroadcastIntent.set(null); + } + + if (receiver.getPendingResult() != null) { + data.finish(); + } + } + + // Instantiate a BackupAgent and tell it that it's alive + private void handleCreateBackupAgent(CreateBackupAgentData data) { + if (DEBUG_BACKUP) Slog.v(TAG, "handleCreateBackupAgent: " + data); + + // no longer idle; we have backup work to do + unscheduleGcIdler(); + + // instantiate the BackupAgent class named in the manifest + LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + String packageName = packageInfo.mPackageName; + if (mBackupAgents.get(packageName) != null) { + Slog.d(TAG, "BackupAgent " + " for " + packageName + + " already exists"); + return; + } + + BackupAgent agent = null; + String classname = data.appInfo.backupAgentName; + + // full backup operation but no app-supplied agent? use the default implementation + if (classname == null && (data.backupMode == IApplicationThread.BACKUP_MODE_FULL + || data.backupMode == IApplicationThread.BACKUP_MODE_RESTORE_FULL)) { + classname = "android.app.backup.FullBackupAgent"; + } + + try { + IBinder binder = null; + try { + if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname); + + java.lang.ClassLoader cl = packageInfo.getClassLoader(); + agent = (BackupAgent) cl.loadClass(classname).newInstance(); + + // set up the agent's context + ContextImpl context = new ContextImpl(); + context.init(packageInfo, null, this); + context.setOuterContext(agent); + agent.attach(context); + + agent.onCreate(); + binder = agent.onBind(); + mBackupAgents.put(packageName, agent); + } catch (Exception e) { + // If this is during restore, fail silently; otherwise go + // ahead and let the user see the crash. + Slog.e(TAG, "Agent threw during creation: " + e); + if (data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE + && data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE_FULL) { + throw e; + } + // falling through with 'binder' still null + } + + // tell the OS that we're live now + try { + ActivityManagerNative.getDefault().backupAgentCreated(packageName, binder); + } catch (RemoteException e) { + // nothing to do. + } + } catch (Exception e) { + throw new RuntimeException("Unable to create BackupAgent " + + classname + ": " + e.toString(), e); + } + } + + // Tear down a BackupAgent + private void handleDestroyBackupAgent(CreateBackupAgentData data) { + if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data); + + LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + String packageName = packageInfo.mPackageName; + BackupAgent agent = mBackupAgents.get(packageName); + if (agent != null) { + try { + agent.onDestroy(); + } catch (Exception e) { + Slog.w(TAG, "Exception thrown in onDestroy by backup agent of " + data.appInfo); + e.printStackTrace(); + } + mBackupAgents.remove(packageName); + } else { + Slog.w(TAG, "Attempt to destroy unknown backup agent " + data); + } + } + + private void handleCreateService(CreateServiceData data) { + // If we are getting ready to gc after going to the background, well + // we are back active so skip it. + unscheduleGcIdler(); + + LoadedApk packageInfo = getPackageInfoNoCheck( + data.info.applicationInfo, data.compatInfo); + Service service = null; + try { + java.lang.ClassLoader cl = packageInfo.getClassLoader(); + service = (Service) cl.loadClass(data.info.name).newInstance(); + } catch (Exception e) { + if (!mInstrumentation.onException(service, e)) { + throw new RuntimeException( + "Unable to instantiate service " + data.info.name + + ": " + e.toString(), e); + } + } + + try { + if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name); + + ContextImpl context = new ContextImpl(); + context.init(packageInfo, null, this); + + Application app = packageInfo.makeApplication(false, mInstrumentation); + context.setOuterContext(service); + service.attach(context, this, data.info.name, data.token, app, + ActivityManagerNative.getDefault()); + service.onCreate(); + mServices.put(data.token, service); + try { + ActivityManagerNative.getDefault().serviceDoneExecuting( + data.token, 0, 0, 0); + } catch (RemoteException e) { + // nothing to do. + } + } catch (Exception e) { + if (!mInstrumentation.onException(service, e)) { + throw new RuntimeException( + "Unable to create service " + data.info.name + + ": " + e.toString(), e); + } + } + } + + private void handleBindService(BindServiceData data) { + Service s = mServices.get(data.token); + if (DEBUG_SERVICE) + Slog.v(TAG, "handleBindService s=" + s + " rebind=" + data.rebind); + if (s != null) { + try { + data.intent.setExtrasClassLoader(s.getClassLoader()); + try { + if (!data.rebind) { + IBinder binder = s.onBind(data.intent); + ActivityManagerNative.getDefault().publishService( + data.token, data.intent, binder); + } else { + s.onRebind(data.intent); + ActivityManagerNative.getDefault().serviceDoneExecuting( + data.token, 0, 0, 0); + } + ensureJitEnabled(); + } catch (RemoteException ex) { + } + } catch (Exception e) { + if (!mInstrumentation.onException(s, e)) { + throw new RuntimeException( + "Unable to bind to service " + s + + " with " + data.intent + ": " + e.toString(), e); + } + } + } + } + + private void handleUnbindService(BindServiceData data) { + Service s = mServices.get(data.token); + if (s != null) { + try { + data.intent.setExtrasClassLoader(s.getClassLoader()); + boolean doRebind = s.onUnbind(data.intent); + try { + if (doRebind) { + ActivityManagerNative.getDefault().unbindFinished( + data.token, data.intent, doRebind); + } else { + ActivityManagerNative.getDefault().serviceDoneExecuting( + data.token, 0, 0, 0); + } + } catch (RemoteException ex) { + } + } catch (Exception e) { + if (!mInstrumentation.onException(s, e)) { + throw new RuntimeException( + "Unable to unbind to service " + s + + " with " + data.intent + ": " + e.toString(), e); + } + } + } + } + + private void handleDumpService(DumpComponentInfo info) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); + try { + Service s = mServices.get(info.token); + if (s != null) { + PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd.getFileDescriptor())); + s.dump(info.fd.getFileDescriptor(), pw, info.args); + pw.flush(); + } + } finally { + IoUtils.closeQuietly(info.fd); + StrictMode.setThreadPolicy(oldPolicy); + } + } + + private void handleDumpActivity(DumpComponentInfo info) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); + try { + ActivityClientRecord r = mActivities.get(info.token); + if (r != null && r.activity != null) { + PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd.getFileDescriptor())); + r.activity.dump(info.prefix, info.fd.getFileDescriptor(), pw, info.args); + pw.flush(); + } + } finally { + IoUtils.closeQuietly(info.fd); + StrictMode.setThreadPolicy(oldPolicy); + } + } + + private void handleDumpProvider(DumpComponentInfo info) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); + try { + ProviderClientRecord r = mLocalProviders.get(info.token); + if (r != null && r.mLocalProvider != null) { + PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd.getFileDescriptor())); + r.mLocalProvider.dump(info.fd.getFileDescriptor(), pw, info.args); + pw.flush(); + } + } finally { + IoUtils.closeQuietly(info.fd); + StrictMode.setThreadPolicy(oldPolicy); + } + } + + private void handleServiceArgs(ServiceArgsData data) { + Service s = mServices.get(data.token); + if (s != null) { + try { + if (data.args != null) { + data.args.setExtrasClassLoader(s.getClassLoader()); + } + int res; + if (!data.taskRemoved) { + res = s.onStartCommand(data.args, data.flags, data.startId); + } else { + s.onTaskRemoved(data.args); + res = Service.START_TASK_REMOVED_COMPLETE; + } + + QueuedWork.waitToFinish(); + + try { + ActivityManagerNative.getDefault().serviceDoneExecuting( + data.token, 1, data.startId, res); + } catch (RemoteException e) { + // nothing to do. + } + ensureJitEnabled(); + } catch (Exception e) { + if (!mInstrumentation.onException(s, e)) { + throw new RuntimeException( + "Unable to start service " + s + + " with " + data.args + ": " + e.toString(), e); + } + } + } + } + + private void handleStopService(IBinder token) { + Service s = mServices.remove(token); + if (s != null) { + try { + if (localLOGV) Slog.v(TAG, "Destroying service " + s); + s.onDestroy(); + Context context = s.getBaseContext(); + if (context instanceof ContextImpl) { + final String who = s.getClassName(); + ((ContextImpl) context).scheduleFinalCleanup(who, "Service"); + } + + QueuedWork.waitToFinish(); + + try { + ActivityManagerNative.getDefault().serviceDoneExecuting( + token, 0, 0, 0); + } catch (RemoteException e) { + // nothing to do. + } + } catch (Exception e) { + if (!mInstrumentation.onException(s, e)) { + throw new RuntimeException( + "Unable to stop service " + s + + ": " + e.toString(), e); + } + } + } + //Slog.i(TAG, "Running services: " + mServices); + } + + public final ActivityClientRecord performResumeActivity(IBinder token, + boolean clearHide) { + ActivityClientRecord r = mActivities.get(token); + if (localLOGV) Slog.v(TAG, "Performing resume of " + r + + " finished=" + r.activity.mFinished); + if (r != null && !r.activity.mFinished) { + if (clearHide) { + r.hideForNow = false; + r.activity.mStartedActivity = false; + } + try { + if (r.pendingIntents != null) { + deliverNewIntents(r, r.pendingIntents); + r.pendingIntents = null; + } + if (r.pendingResults != null) { + deliverResults(r, r.pendingResults); + r.pendingResults = null; + } + r.activity.performResume(); + + EventLog.writeEvent(LOG_ON_RESUME_CALLED, + r.activity.getComponentName().getClassName()); + + r.paused = false; + r.stopped = false; + r.state = null; + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to resume activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + } + return r; + } + + static final void cleanUpPendingRemoveWindows(ActivityClientRecord r) { + if (r.mPendingRemoveWindow != null) { + r.mPendingRemoveWindowManager.removeViewImmediate(r.mPendingRemoveWindow); + IBinder wtoken = r.mPendingRemoveWindow.getWindowToken(); + if (wtoken != null) { + WindowManagerImpl.getDefault().closeAll(wtoken, + r.activity.getClass().getName(), "Activity"); + } + } + r.mPendingRemoveWindow = null; + r.mPendingRemoveWindowManager = null; + } + + final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { + // If we are getting ready to gc after going to the background, well + // we are back active so skip it. + unscheduleGcIdler(); + + ActivityClientRecord r = performResumeActivity(token, clearHide); + + if (r != null) { + final Activity a = r.activity; + + if (localLOGV) Slog.v( + TAG, "Resume " + r + " started activity: " + + a.mStartedActivity + ", hideForNow: " + r.hideForNow + + ", finished: " + a.mFinished); + + final int forwardBit = isForward ? + WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; + + // If the window hasn't yet been added to the window manager, + // and this guy didn't finish itself or start another activity, + // then go ahead and add the window. + boolean willBeVisible = !a.mStartedActivity; + if (!willBeVisible) { + try { + willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible( + a.getActivityToken()); + } catch (RemoteException e) { + } + } + if (r.window == null && !a.mFinished && willBeVisible) { + r.window = r.activity.getWindow(); + View decor = r.window.getDecorView(); + decor.setVisibility(View.INVISIBLE); + ViewManager wm = a.getWindowManager(); + WindowManager.LayoutParams l = r.window.getAttributes(); + a.mDecor = decor; + l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; + l.softInputMode |= forwardBit; + if (a.mVisibleFromClient) { + a.mWindowAdded = true; + wm.addView(decor, l); + } + + // If the window has already been added, but during resume + // we started another activity, then don't yet make the + // window visible. + } else if (!willBeVisible) { + if (localLOGV) Slog.v( + TAG, "Launch " + r + " mStartedActivity set"); + r.hideForNow = true; + } + + // Get rid of anything left hanging around. + cleanUpPendingRemoveWindows(r); + + // The window is now visible if it has been added, we are not + // simply finishing, and we are not starting another activity. + if (!r.activity.mFinished && willBeVisible + && r.activity.mDecor != null && !r.hideForNow) { + if (r.newConfig != null) { + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity " + + r.activityInfo.name + " with newConfig " + r.newConfig); + performConfigurationChanged(r.activity, r.newConfig); + freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.newConfig)); + r.newConfig = null; + } + if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + + isForward); + WindowManager.LayoutParams l = r.window.getAttributes(); + if ((l.softInputMode + & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) + != forwardBit) { + l.softInputMode = (l.softInputMode + & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) + | forwardBit; + if (r.activity.mVisibleFromClient) { + ViewManager wm = a.getWindowManager(); + View decor = r.window.getDecorView(); + wm.updateViewLayout(decor, l); + } + } + r.activity.mVisibleFromServer = true; + mNumVisibleActivities++; + if (r.activity.mVisibleFromClient) { + r.activity.makeVisible(); + } + } + + if (!r.onlyLocalRequest) { + r.nextIdle = mNewActivities; + mNewActivities = r; + if (localLOGV) Slog.v( + TAG, "Scheduling idle handler for " + r); + Looper.myQueue().addIdleHandler(new Idler()); + } + r.onlyLocalRequest = false; + + } else { + // If an exception was thrown when trying to resume, then + // just end this activity. + try { + ActivityManagerNative.getDefault() + .finishActivity(token, Activity.RESULT_CANCELED, null); + } catch (RemoteException ex) { + } + } + } + + private int mThumbnailWidth = -1; + private int mThumbnailHeight = -1; + private Bitmap mAvailThumbnailBitmap = null; + private Canvas mThumbnailCanvas = null; + + private Bitmap createThumbnailBitmap(ActivityClientRecord r) { + Bitmap thumbnail = mAvailThumbnailBitmap; + try { + if (thumbnail == null) { + int w = mThumbnailWidth; + int h; + if (w < 0) { + Resources res = r.activity.getResources(); + mThumbnailHeight = h = + res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height); + + mThumbnailWidth = w = + res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width); + } else { + h = mThumbnailHeight; + } + + // On platforms where we don't want thumbnails, set dims to (0,0) + if ((w > 0) && (h > 0)) { + thumbnail = Bitmap.createBitmap(w, h, THUMBNAIL_FORMAT); + thumbnail.eraseColor(0); + } + } + + if (thumbnail != null) { + Canvas cv = mThumbnailCanvas; + if (cv == null) { + mThumbnailCanvas = cv = new Canvas(); + } + + cv.setBitmap(thumbnail); + if (!r.activity.onCreateThumbnail(thumbnail, cv)) { + mAvailThumbnailBitmap = thumbnail; + thumbnail = null; + } + cv.setBitmap(null); + } + + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to create thumbnail of " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + thumbnail = null; + } + + return thumbnail; + } + + private void handlePauseActivity(IBinder token, boolean finished, + boolean userLeaving, int configChanges) { + ActivityClientRecord r = mActivities.get(token); + if (r != null) { + //Slog.v(TAG, "userLeaving=" + userLeaving + " handling pause of " + r); + if (userLeaving) { + performUserLeavingActivity(r); + } + + r.activity.mConfigChangeFlags |= configChanges; + performPauseActivity(token, finished, r.isPreHoneycomb()); + + // Make sure any pending writes are now committed. + if (r.isPreHoneycomb()) { + QueuedWork.waitToFinish(); + } + + // Tell the activity manager we have paused. + try { + ActivityManagerNative.getDefault().activityPaused(token); + } catch (RemoteException ex) { + } + } + } + + final void performUserLeavingActivity(ActivityClientRecord r) { + mInstrumentation.callActivityOnUserLeaving(r.activity); + } + + final Bundle performPauseActivity(IBinder token, boolean finished, + boolean saveState) { + ActivityClientRecord r = mActivities.get(token); + return r != null ? performPauseActivity(r, finished, saveState) : null; + } + + final Bundle performPauseActivity(ActivityClientRecord r, boolean finished, + boolean saveState) { + if (r.paused) { + if (r.activity.mFinished) { + // If we are finishing, we won't call onResume() in certain cases. + // So here we likewise don't want to call onPause() if the activity + // isn't resumed. + return null; + } + RuntimeException e = new RuntimeException( + "Performing pause of activity that is not resumed: " + + r.intent.getComponent().toShortString()); + Slog.e(TAG, e.getMessage(), e); + } + Bundle state = null; + if (finished) { + r.activity.mFinished = true; + } + try { + // Next have the activity save its current state and managed dialogs... + if (!r.activity.mFinished && saveState) { + state = new Bundle(); + state.setAllowFds(false); + mInstrumentation.callActivityOnSaveInstanceState(r.activity, state); + r.state = state; + } + // Now we are idle. + r.activity.mCalled = false; + mInstrumentation.callActivityOnPause(r.activity); + EventLog.writeEvent(LOG_ON_PAUSE_CALLED, r.activity.getComponentName().getClassName()); + if (!r.activity.mCalled) { + throw new SuperNotCalledException( + "Activity " + r.intent.getComponent().toShortString() + + " did not call through to super.onPause()"); + } + + } catch (SuperNotCalledException e) { + throw e; + + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to pause activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + r.paused = true; + + // Notify any outstanding on paused listeners + ArrayList listeners; + synchronized (mOnPauseListeners) { + listeners = mOnPauseListeners.remove(r.activity); + } + int size = (listeners != null ? listeners.size() : 0); + for (int i = 0; i < size; i++) { + listeners.get(i).onPaused(r.activity); + } + + return state; + } + + final void performStopActivity(IBinder token, boolean saveState) { + ActivityClientRecord r = mActivities.get(token); + performStopActivityInner(r, null, false, saveState); + } + + private static class StopInfo implements Runnable { + ActivityClientRecord activity; + Bundle state; + Bitmap thumbnail; + CharSequence description; + + @Override public void run() { + // Tell activity manager we have been stopped. + try { + if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Reporting activity stopped: " + activity); + ActivityManagerNative.getDefault().activityStopped( + activity.token, state, thumbnail, description); + } catch (RemoteException ex) { + } + } + } + + private static final class ProviderRefCount { + public final IActivityManager.ContentProviderHolder holder; + public final ProviderClientRecord client; + public int stableCount; + public int unstableCount; + + // When this is set, the stable and unstable ref counts are 0 and + // we have a pending operation scheduled to remove the ref count + // from the activity manager. On the activity manager we are still + // holding an unstable ref, though it is not reflected in the counts + // here. + public boolean removePending; + + ProviderRefCount(IActivityManager.ContentProviderHolder inHolder, + ProviderClientRecord inClient, int sCount, int uCount) { + holder = inHolder; + client = inClient; + stableCount = sCount; + unstableCount = uCount; + } + } + + /** + * Core implementation of stopping an activity. Note this is a little + * tricky because the server's meaning of stop is slightly different + * than our client -- for the server, stop means to save state and give + * it the result when it is done, but the window may still be visible. + * For the client, we want to call onStop()/onStart() to indicate when + * the activity's UI visibillity changes. + */ + private void performStopActivityInner(ActivityClientRecord r, + StopInfo info, boolean keepShown, boolean saveState) { + if (localLOGV) Slog.v(TAG, "Performing stop of " + r); + Bundle state = null; + if (r != null) { + if (!keepShown && r.stopped) { + if (r.activity.mFinished) { + // If we are finishing, we won't call onResume() in certain + // cases. So here we likewise don't want to call onStop() + // if the activity isn't resumed. + return; + } + RuntimeException e = new RuntimeException( + "Performing stop of activity that is not resumed: " + + r.intent.getComponent().toShortString()); + Slog.e(TAG, e.getMessage(), e); + } + + if (info != null) { + try { + // First create a thumbnail for the activity... + // For now, don't create the thumbnail here; we are + // doing that by doing a screen snapshot. + info.thumbnail = null; //createThumbnailBitmap(r); + info.description = r.activity.onCreateDescription(); + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to save state of activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + } + + // Next have the activity save its current state and managed dialogs... + if (!r.activity.mFinished && saveState) { + if (r.state == null) { + state = new Bundle(); + state.setAllowFds(false); + mInstrumentation.callActivityOnSaveInstanceState(r.activity, state); + r.state = state; + } else { + state = r.state; + } + } + + if (!keepShown) { + try { + // Now we are idle. + r.activity.performStop(); + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to stop activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + r.stopped = true; + } + + r.paused = true; + } + } + + private void updateVisibility(ActivityClientRecord r, boolean show) { + View v = r.activity.mDecor; + if (v != null) { + if (show) { + if (!r.activity.mVisibleFromServer) { + r.activity.mVisibleFromServer = true; + mNumVisibleActivities++; + if (r.activity.mVisibleFromClient) { + r.activity.makeVisible(); + } + } + if (r.newConfig != null) { + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating activity vis " + + r.activityInfo.name + " with new config " + r.newConfig); + performConfigurationChanged(r.activity, r.newConfig); + freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.newConfig)); + r.newConfig = null; + } + } else { + if (r.activity.mVisibleFromServer) { + r.activity.mVisibleFromServer = false; + mNumVisibleActivities--; + v.setVisibility(View.INVISIBLE); + } + } + } + } + + private void handleStopActivity(IBinder token, boolean show, int configChanges) { + ActivityClientRecord r = mActivities.get(token); + r.activity.mConfigChangeFlags |= configChanges; + + StopInfo info = new StopInfo(); + performStopActivityInner(r, info, show, true); + + if (localLOGV) Slog.v( + TAG, "Finishing stop of " + r + ": show=" + show + + " win=" + r.window); + + updateVisibility(r, show); + + // Make sure any pending writes are now committed. + if (!r.isPreHoneycomb()) { + QueuedWork.waitToFinish(); + } + + // Schedule the call to tell the activity manager we have + // stopped. We don't do this immediately, because we want to + // have a chance for any other pending work (in particular memory + // trim requests) to complete before you tell the activity + // manager to proceed and allow us to go fully into the background. + info.activity = r; + info.state = r.state; + mH.post(info); + } + + final void performRestartActivity(IBinder token) { + ActivityClientRecord r = mActivities.get(token); + if (r.stopped) { + r.activity.performRestart(); + r.stopped = false; + } + } + + private void handleWindowVisibility(IBinder token, boolean show) { + ActivityClientRecord r = mActivities.get(token); + + if (r == null) { + Log.w(TAG, "handleWindowVisibility: no activity for token " + token); + return; + } + + if (!show && !r.stopped) { + performStopActivityInner(r, null, show, false); + } else if (show && r.stopped) { + // If we are getting ready to gc after going to the background, well + // we are back active so skip it. + unscheduleGcIdler(); + + r.activity.performRestart(); + r.stopped = false; + } + if (r.activity.mDecor != null) { + if (false) Slog.v( + TAG, "Handle window " + r + " visibility: " + show); + updateVisibility(r, show); + } + } + + private void handleSleeping(IBinder token, boolean sleeping) { + ActivityClientRecord r = mActivities.get(token); + + if (r == null) { + Log.w(TAG, "handleSleeping: no activity for token " + token); + return; + } + + if (sleeping) { + if (!r.stopped && !r.isPreHoneycomb()) { + try { + // Now we are idle. + r.activity.performStop(); + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to stop activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + r.stopped = true; + } + + // Make sure any pending writes are now committed. + if (!r.isPreHoneycomb()) { + QueuedWork.waitToFinish(); + } + + // Tell activity manager we slept. + try { + ActivityManagerNative.getDefault().activitySlept(r.token); + } catch (RemoteException ex) { + } + } else { + if (r.stopped && r.activity.mVisibleFromServer) { + r.activity.performRestart(); + r.stopped = false; + } + } + } + + private void handleSetCoreSettings(Bundle coreSettings) { + synchronized (mPackages) { + mCoreSettings = coreSettings; + } + } + + private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) { + LoadedApk apk = peekPackageInfo(data.pkg, false); + if (apk != null) { + apk.mCompatibilityInfo.set(data.info); + } + apk = peekPackageInfo(data.pkg, true); + if (apk != null) { + apk.mCompatibilityInfo.set(data.info); + } + handleConfigurationChanged(mConfiguration, data.info); + WindowManagerImpl.getDefault().reportNewConfiguration(mConfiguration); + } + + private void deliverResults(ActivityClientRecord r, List results) { + final int N = results.size(); + for (int i=0; i