diff options
290 files changed, 16736 insertions, 2863 deletions
@@ -74,6 +74,7 @@ LOCAL_SRC_FILES += \ core/java/android/app/ISearchManager.aidl \ core/java/android/app/ISearchManagerCallback.aidl \ core/java/android/app/IServiceConnection.aidl \ + core/java/android/app/IStopUserCallback.aidl \ core/java/android/app/IThumbnailReceiver.aidl \ core/java/android/app/IThumbnailRetriever.aidl \ core/java/android/app/ITransientNotification.aidl \ @@ -115,6 +116,7 @@ LOCAL_SRC_FILES += \ core/java/android/database/IContentObserver.aidl \ core/java/android/hardware/ISerialManager.aidl \ core/java/android/hardware/display/IDisplayManager.aidl \ + core/java/android/hardware/display/IDisplayManagerCallback.aidl \ core/java/android/hardware/input/IInputManager.aidl \ core/java/android/hardware/input/IInputDevicesChangedListener.aidl \ core/java/android/hardware/usb/IUsbManager.aidl \ diff --git a/api/current.txt b/api/current.txt index 720681a..7d1c203 100644 --- a/api/current.txt +++ b/api/current.txt @@ -76,6 +76,7 @@ package android { field public static final java.lang.String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS"; field public static final java.lang.String NET_TUNNELING = "android.permission.NET_TUNNELING"; field public static final java.lang.String NFC = "android.permission.NFC"; + field public static final java.lang.String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT"; field public static final deprecated java.lang.String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY"; field public static final java.lang.String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS"; field public static final java.lang.String READ_CALENDAR = "android.permission.READ_CALENDAR"; @@ -9996,7 +9997,8 @@ package android.hardware { package android.hardware.display { public final class DisplayManager { - method public android.view.Display getDisplay(int, android.content.Context); + method public android.view.Display getDisplay(int); + method public android.view.Display[] getDisplays(); method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler); method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener); } @@ -12606,6 +12608,7 @@ package android.net { method public static final android.net.NetworkInfo.DetailedState[] values(); enum_constant public static final android.net.NetworkInfo.DetailedState AUTHENTICATING; enum_constant public static final android.net.NetworkInfo.DetailedState BLOCKED; + enum_constant public static final android.net.NetworkInfo.DetailedState CAPTIVE_PORTAL_CHECK; enum_constant public static final android.net.NetworkInfo.DetailedState CONNECTED; enum_constant public static final android.net.NetworkInfo.DetailedState CONNECTING; enum_constant public static final android.net.NetworkInfo.DetailedState DISCONNECTED; @@ -20438,6 +20441,9 @@ package android.telephony { public abstract class CellSignalStrength implements android.os.Parcelable { method public int describeContents(); method public abstract boolean equals(java.lang.Object); + method public abstract int getAsuLevel(); + method public abstract int getDbm(); + method public abstract int getLevel(); method public abstract int hashCode(); method public abstract void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -20445,11 +20451,16 @@ package android.telephony { public class CellSignalStrengthCdma extends android.telephony.CellSignalStrength implements android.os.Parcelable { method public boolean equals(java.lang.Object); + method public int getAsuLevel(); method public int getCdmaDbm(); method public int getCdmaEcio(); + method public int getCdmaLevel(); + method public int getDbm(); method public int getEvdoDbm(); method public int getEvdoEcio(); + method public int getEvdoLevel(); method public int getEvdoSnr(); + method public int getLevel(); method public int hashCode(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -20457,6 +20468,9 @@ package android.telephony { public class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable { method public boolean equals(java.lang.Object); + method public int getAsuLevel(); + method public int getDbm(); + method public int getLevel(); method public int hashCode(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -20464,6 +20478,9 @@ package android.telephony { public class CellSignalStrengthLte extends android.telephony.CellSignalStrength implements android.os.Parcelable { method public boolean equals(java.lang.Object); + method public int getAsuLevel(); + method public int getDbm(); + method public int getLevel(); method public int getTimingAdvance(); method public int hashCode(); method public void writeToParcel(android.os.Parcel, int); @@ -23437,6 +23454,7 @@ package android.view { method public int getRotation(); method public void getSize(android.graphics.Point); method public deprecated int getWidth(); + method public boolean isValid(); field public static final int DEFAULT_DISPLAY = 0; // 0x0 } @@ -24427,7 +24445,7 @@ package android.view { method public android.graphics.Canvas lockCanvas(android.graphics.Rect) throws java.lang.IllegalArgumentException, android.view.Surface.OutOfResourcesException; method public void readFromParcel(android.os.Parcel); method public void release(); - method public void unlockCanvas(android.graphics.Canvas); + method public deprecated void unlockCanvas(android.graphics.Canvas); method public void unlockCanvasAndPost(android.graphics.Canvas); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -29565,6 +29583,7 @@ package android.widget { public class ViewAnimator extends android.widget.FrameLayout { ctor public ViewAnimator(android.content.Context); ctor public ViewAnimator(android.content.Context, android.util.AttributeSet); + method public boolean getAnimateFirstView(); method public android.view.View getCurrentView(); method public int getDisplayedChild(); method public android.view.animation.Animation getInAnimation(); diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 47d6a02..7f3dbe5 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -31,7 +31,6 @@ import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; -import android.os.Binder; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -141,6 +140,8 @@ public class Am { runToUri(true); } else if (op.equals("switch-user")) { runSwitchUser(); + } else if (op.equals("stop-user")) { + runStopUser(); } else { throw new IllegalArgumentException("Unknown command: " + op); } @@ -323,7 +324,6 @@ public class Am { mUserId = Integer.parseInt(nextArgRequired()); } else { System.err.println("Error: Unknown option: " + opt); - showUsage(); return null; } } @@ -594,7 +594,6 @@ public class Am { no_window_animation = true; } else { System.err.println("Error: Unknown option: " + opt); - showUsage(); return; } } @@ -738,7 +737,6 @@ public class Am { persistent = true; } else { System.err.println("Error: Unknown option: " + opt); - showUsage(); return; } } @@ -752,13 +750,27 @@ public class Am { } private void runSwitchUser() throws Exception { - if (android.os.Process.myUid() != 0) { - throw new RuntimeException("switchuser can only be run as root"); - } String user = nextArgRequired(); mAm.switchUser(Integer.parseInt(user)); } + private void runStopUser() throws Exception { + String user = nextArgRequired(); + int res = mAm.stopUser(Integer.parseInt(user), null); + if (res != ActivityManager.USER_OP_SUCCESS) { + String txt = ""; + switch (res) { + case ActivityManager.USER_OP_IS_CURRENT: + txt = " (Can't stop current user)"; + break; + case ActivityManager.USER_OP_UNKNOWN_USER: + txt = " (Unknown user " + user + ")"; + break; + } + System.err.println("Switch failed: " + res + txt); + } + } + class MyActivityController extends IActivityController.Stub { final String mGdbPort; @@ -1047,7 +1059,6 @@ public class Am { gdbPort = nextArgRequired(); } else { System.err.println("Error: Unknown option: " + opt); - showUsage(); return; } } @@ -1065,7 +1076,6 @@ public class Am { enabled = false; } else { System.err.println("Error: enabled mode must be 'on' or 'off' at " + mode); - showUsage(); return; } @@ -1090,7 +1100,6 @@ public class Am { int div = size.indexOf('x'); if (div <= 0 || div >= (size.length()-1)) { System.err.println("Error: bad size " + size); - showUsage(); return; } String mstr = size.substring(0, div); @@ -1100,7 +1109,6 @@ public class Am { n = Integer.parseInt(nstr); } catch (NumberFormatException e) { System.err.println("Error: bad number " + e); - showUsage(); return; } } @@ -1139,12 +1147,10 @@ public class Am { density = Integer.parseInt(densityStr); } catch (NumberFormatException e) { System.err.println("Error: bad number " + e); - showUsage(); return; } if (density < 72) { System.err.println("Error: density must be >= 72"); - showUsage(); return; } } @@ -1345,6 +1351,7 @@ public class Am { " am to-uri [INTENT]\n" + " am to-intent-uri [INTENT]\n" + " am switch-user <USER_ID>\n" + + " am stop-user <USER_ID>\n" + "\n" + "am start: start an Activity. Options are:\n" + " -D: enable debugging\n" + @@ -1403,6 +1410,12 @@ public class Am { "\n" + "am to-intent-uri: print the given Intent specification as an intent: URI.\n" + "\n" + + "am switch-user: switch to put USER_ID in the foreground, starting" + + " execution of that user if it is currently stopped.\n" + + "\n" + + "am stop-user: stop execution of USER_ID, not allowing it to run any" + + " code until a later explicit switch to it.\n" + + "\n" + "<INTENT> specifications include these flags and arguments:\n" + " [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" + " [-c <CATEGORY> [-c <CATEGORY>] ...]\n" + diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 2471a2e..8511735 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -38,6 +38,7 @@ #include <ui/DisplayInfo.h> #include <ui/FramebufferNativeWindow.h> +#include <gui/ISurfaceComposer.h> #include <gui/Surface.h> #include <gui/SurfaceComposerClient.h> @@ -216,14 +217,16 @@ status_t BootAnimation::initTexture(void* buffer, size_t len) status_t BootAnimation::readyToRun() { mAssets.addDefaultAssets(); + sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay( + ISurfaceComposer::eDisplayIdMain)); DisplayInfo dinfo; - status_t status = SurfaceComposerClient::getDisplayInfo(0, &dinfo); + status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo); if (status) return -1; // create the native surface - sp<SurfaceControl> control = session()->createSurface( - 0, dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565); + sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"), + dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565); SurfaceComposerClient::openGlobalTransaction(); control->setLayer(0x40000000); diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c index a52f74a..ab64747 100644 --- a/cmds/installd/commands.c +++ b/cmds/installd/commands.c @@ -241,7 +241,6 @@ int clone_persona_data(uid_t src_persona, uid_t target_persona, int copy) { char src_data_dir[PKG_PATH_MAX]; char pkg_path[PKG_PATH_MAX]; - char media_path[PATH_MAX]; DIR *d; struct dirent *de; struct stat s; @@ -250,9 +249,6 @@ int clone_persona_data(uid_t src_persona, uid_t target_persona, int copy) if (create_persona_path(src_data_dir, src_persona)) { return -1; } - if (create_persona_media_path(media_path, (userid_t) target_persona) == -1) { - return -1; - } d = opendir(src_data_dir); if (d != NULL) { @@ -281,10 +277,10 @@ int clone_persona_data(uid_t src_persona, uid_t target_persona, int copy) closedir(d); } - // ensure /data/media/<user_id> exists - if (ensure_dir(media_path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) { + if (ensure_media_user_dirs((userid_t) target_persona) == -1) { return -1; } + return 0; } diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c index d51004a..d559639 100644 --- a/cmds/installd/installd.c +++ b/cmds/installd/installd.c @@ -333,19 +333,16 @@ int initialize_globals() { int initialize_directories() { int res = -1; - int version = 0; - FILE* file; // Read current filesystem layout version to handle upgrade paths char version_path[PATH_MAX]; - if (snprintf(version_path, PATH_MAX, "%s.layout_version", android_data_dir.path) > PATH_MAX) { - return -1; - } - file = fopen(version_path, "r"); - if (file != NULL) { - fscanf(file, "%d", &version); - fclose(file); + snprintf(version_path, PATH_MAX, "%s.layout_version", android_data_dir.path); + + int oldVersion; + if (fs_read_atomic_int(version_path, &oldVersion) == -1) { + oldVersion = 0; } + int version = oldVersion; // /data/user char *user_data_dir = build_string2(android_data_dir.path, SECONDARY_USER_PREFIX); @@ -376,16 +373,12 @@ int initialize_directories() { } } - // /data/media/0 - char owner_media_dir[PATH_MAX]; - create_persona_media_path(owner_media_dir, 0); - if (version == 0) { // Introducing multi-user, so migrate /data/media contents into /data/media/0 - ALOGD("Migrating /data/media for multi-user"); + ALOGD("Upgrading /data/media for multi-user"); // Ensure /data/media - if (ensure_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) { + if (fs_prepare_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) { goto fail; } @@ -402,10 +395,14 @@ int initialize_directories() { } // Create /data/media again - if (ensure_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) { + if (fs_prepare_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) { goto fail; } + // /data/media/0 + char owner_media_dir[PATH_MAX]; + snprintf(owner_media_dir, PATH_MAX, "%s0", android_media_dir.path); + // Move any owner data into place if (access(media_tmp_dir, F_OK) == 0) { if (rename(media_tmp_dir, owner_media_dir) == -1) { @@ -433,8 +430,7 @@ int initialize_directories() { // /data/media/<user_id> snprintf(user_media_dir, PATH_MAX, "%s%s", android_media_dir.path, name); - if (ensure_dir(user_media_dir, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) { - ALOGE("Failed to ensure %s: %s", user_media_dir, strerror(errno)); + if (fs_prepare_dir(user_media_dir, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) { goto fail; } } @@ -445,22 +441,46 @@ int initialize_directories() { version = 1; } - // Ensure /data/media/0 is always ready - if (ensure_dir(owner_media_dir, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) { - goto fail; + // /data/media/obb + char media_obb_dir[PATH_MAX]; + snprintf(media_obb_dir, PATH_MAX, "%sobb", android_media_dir.path); + + if (version == 1) { + // Introducing /data/media/obb for sharing OBB across users; migrate + // any existing OBB files from owner. + ALOGD("Upgrading to shared /data/media/obb"); + + // /data/media/0/Android/obb + char owner_obb_path[PATH_MAX]; + snprintf(owner_obb_path, PATH_MAX, "%s0/Android/obb", android_media_dir.path); + + // Only move if target doesn't already exist + if (access(media_obb_dir, F_OK) != 0 && access(owner_obb_path, F_OK) == 0) { + if (rename(owner_obb_path, media_obb_dir) == -1) { + ALOGE("Failed to move OBB from owner: %s", strerror(errno)); + goto fail; + } + } + + version = 2; } - // Persist our current version - file = fopen(version_path, "w"); - if (file != NULL) { - fprintf(file, "%d", version); - fsync(fileno(file)); - fclose(file); - } else { - ALOGE("Failed to save version to %s: %s", version_path, strerror(errno)); + if (ensure_media_user_dirs(0) == -1) { + ALOGE("Failed to setup media for user 0"); + goto fail; + } + if (fs_prepare_dir(media_obb_dir, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) { goto fail; } + // Persist layout version if changed + if (version != oldVersion) { + if (fs_write_atomic_int(version_path, version) == -1) { + ALOGE("Failed to save version to %s: %s", version_path, strerror(errno)); + goto fail; + } + } + // Success! res = 0; diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h index 3201427..5b81d2c 100644 --- a/cmds/installd/installd.h +++ b/cmds/installd/installd.h @@ -32,6 +32,7 @@ #include <sys/types.h> #include <sys/wait.h> +#include <cutils/fs.h> #include <cutils/sockets.h> #include <cutils/log.h> #include <cutils/properties.h> diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c index 80247f1..625a35e 100644 --- a/cmds/installd/utils.c +++ b/cmds/installd/utils.c @@ -991,39 +991,14 @@ char *build_string3(char *s1, char *s2, char *s3) { return result; } -/* Ensure that directory exists with given mode and owners. */ -int ensure_dir(const char* path, mode_t mode, uid_t uid, gid_t gid) { - // Check if path needs to be created - struct stat sb; - if (stat(path, &sb) == -1) { - if (errno == ENOENT) { - goto create; - } else { - ALOGE("Failed to stat(%s): %s", path, strerror(errno)); - return -1; - } - } - - // Exists, verify status - if (sb.st_mode == mode || sb.st_uid == uid || sb.st_gid == gid) { - return 0; - } else { - goto fixup; - } - -create: - if (mkdir(path, mode) == -1) { - ALOGE("Failed to mkdir(%s): %s", path, strerror(errno)); - return -1; - } +/* Ensure that /data/media directories are prepared for given user. */ +int ensure_media_user_dirs(userid_t userid) { + char media_user_path[PATH_MAX]; + char path[PATH_MAX]; -fixup: - if (chown(path, uid, gid) == -1) { - ALOGE("Failed to chown(%s, %d, %d): %s", path, uid, gid, strerror(errno)); - return -1; - } - if (chmod(path, mode) == -1) { - ALOGE("Failed to chown(%s, %d): %s", path, mode, strerror(errno)); + // Ensure /data/media/<userid> exists + create_persona_media_path(media_user_path, userid); + if (fs_prepare_dir(media_user_path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) { return -1; } diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index 8cc4e69..e621ceb 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -178,11 +178,6 @@ public final class Pm { return; } - if ("list-users".equals(op)) { - runListUsers(); - return; - } - try { if (args.length == 1) { if (args[0].equalsIgnoreCase("-l")) { @@ -222,7 +217,6 @@ public final class Pm { 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)) { @@ -241,7 +235,6 @@ public final class Pm { runListUsers(); } else { System.err.println("Error: unknown list type '" + type + "'"); - showUsage(); } } @@ -276,13 +269,11 @@ public final class Pm { 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; } @@ -431,13 +422,11 @@ public final class Pm { targetPackage = opt; } else { System.err.println("Error: Unknown option: " + opt); - showUsage(); return; } } } catch (RuntimeException ex) { System.err.println("Error: " + ex.toString()); - showUsage(); return; } @@ -529,7 +518,6 @@ public final class Pm { dangerousOnly = true; } else { System.err.println("Error: Unknown option: " + opt); - showUsage(); return; } } @@ -678,7 +666,6 @@ public final class Pm { String pkg = nextArg(); if (pkg == null) { System.err.println("Error: no package specified"); - showUsage(); return; } displayPackageFilePath(pkg); @@ -736,20 +723,17 @@ public final class Pm { 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()); @@ -800,7 +784,6 @@ public final class Pm { installerPackageName = nextOptionData(); if (installerPackageName == null) { System.err.println("Error: no value specified for -i"); - showUsage(); return; } } else if (opt.equals("-t")) { @@ -817,61 +800,52 @@ public final class Pm { 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 if (opt.equals("--originating-uri")) { originatingUriString = nextOptionData(); if (originatingUriString == null) { System.err.println("Error: must supply argument for --originating-uri"); - showUsage(); return; } } else if (opt.equals("--referrer")) { referrer = nextOptionData(); if (referrer == null) { System.err.println("Error: must supply argument for --referrer"); - showUsage(); return; } } else { System.err.println("Error: Unknown option: " + opt); - showUsage(); return; } } @@ -881,7 +855,6 @@ public final class Pm { || tag != null) { if (algo == null || iv == null || key == null) { System.err.println("Error: all of --algo, --iv, and --key must be specified"); - showUsage(); return; } @@ -889,7 +862,6 @@ public final class Pm { if (macAlgo == null || macKey == null || tag == null) { System.err.println("Error: all of --macalgo, --mackey, and --tag must " + "be specified"); - showUsage(); return; } } @@ -938,7 +910,6 @@ public final class Pm { apkURI = Uri.fromFile(new File(apkFilePath)); } else { System.err.println("Error: no package specified"); - showUsage(); return; } @@ -1012,23 +983,16 @@ public final class Pm { } 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 (mUm.createUser(name, 0) == null) { System.err.println("Error: couldn't create User."); - showUsage(); } } catch (RemoteException e) { System.err.println(e.toString()); @@ -1038,29 +1002,21 @@ public final class Pm { } 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(); + System.err.println("Error: user id '" + arg + "' is not a number."); return; } try { if (!mUm.removeUser(userId)) { - System.err.println("Error: couldn't remove user."); - showUsage(); + System.err.println("Error: couldn't remove user #" + userId + "."); } } catch (RemoteException e) { System.err.println(e.toString()); @@ -1069,11 +1025,6 @@ public final class Pm { } 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<UserInfo> users = mUm.getUsers(); if (users == null) { @@ -1521,6 +1472,8 @@ public final class Pm { System.err.println(""); System.err.println("pm list features: prints all features of the system."); System.err.println(""); + System.err.println("pm list users: prints all users on 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:"); @@ -1557,5 +1510,11 @@ public final class Pm { System.err.println(" 2 [external]: Install on external media"); System.err.println(""); System.err.println("pm trim-caches: trim cache files to reach the given free space."); + System.err.println(""); + System.err.println("pm create-user: create a new user with the given USER_NAME,"); + System.err.println(" printing the new user identifier of the user."); + System.err.println(""); + System.err.println("pm remove-user: remove the user with the given USER_IDENTIFIER,"); + System.err.println(" deleting all data associated with that user"); } } diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp index 46e41e3..a1ea81a 100644 --- a/cmds/screencap/screencap.cpp +++ b/cmds/screencap/screencap.cpp @@ -25,6 +25,7 @@ #include <binder/IMemory.h> #include <gui/SurfaceComposerClient.h> +#include <gui/ISurfaceComposer.h> #include <SkImageEncoder.h> #include <SkBitmap.h> @@ -33,15 +34,18 @@ using namespace android; +static uint32_t DEFAULT_DISPLAY_ID = ISurfaceComposer::eDisplayIdMain; + static void usage(const char* pname) { fprintf(stderr, - "usage: %s [-hp] [FILENAME]\n" + "usage: %s [-hp] [-d display-id] [FILENAME]\n" " -h: this message\n" " -p: save the file as a png.\n" + " -d: specify the display id to capture, default %d.\n" "If FILENAME ends with .png it will be saved as a png.\n" "If FILENAME is not given, the results will be printed to stdout.\n", - pname + pname, DEFAULT_DISPLAY_ID ); } @@ -87,12 +91,16 @@ int main(int argc, char** argv) { const char* pname = argv[0]; bool png = false; + int32_t displayId = DEFAULT_DISPLAY_ID; int c; - while ((c = getopt(argc, argv, "ph")) != -1) { + while ((c = getopt(argc, argv, "phd:")) != -1) { switch (c) { case 'p': png = true; break; + case 'd': + displayId = atoi(optarg); + break; case '?': case 'h': usage(pname); @@ -131,7 +139,8 @@ int main(int argc, char** argv) size_t size = 0; ScreenshotClient screenshot; - if (screenshot.update() == NO_ERROR) { + sp<IBinder> display = SurfaceComposerClient::getBuiltInDisplay(displayId); + if (display != NULL && screenshot.update(display) == NO_ERROR) { base = screenshot.getPixels(); w = screenshot.getWidth(); h = screenshot.getHeight(); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index e644db4..26d8c17 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -31,6 +31,7 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Point; import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; import android.os.Binder; import android.os.Bundle; import android.os.Debug; @@ -210,6 +211,15 @@ public class ActivityManager { */ public static final int INTENT_SENDER_SERVICE = 4; + /** @hide User operation call: success! */ + public static final int USER_OP_SUCCESS = 0; + + /** @hide User operation call: given user id is not known. */ + public static final int USER_OP_UNKNOWN_USER = -1; + + /** @hide User operation call: given user id is the current user, can't be stopped. */ + public static final int USER_OP_IS_CURRENT = -2; + /*package*/ ActivityManager(Context context, Handler handler) { mContext = context; mHandler = handler; @@ -376,7 +386,8 @@ public class ActivityManager { return true; } - Display display = DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); + Display display = DisplayManagerGlobal.getInstance().getRealDisplay( + Display.DEFAULT_DISPLAY); Point p = new Point(); display.getRealSize(p); int pixels = p.x * p.y; diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index adc9434..05c009f 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1570,6 +1570,17 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case STOP_USER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int userid = data.readInt(); + IStopUserCallback callback = IStopUserCallback.Stub.asInterface( + data.readStrongBinder()); + int result = stopUser(userid, callback); + reply.writeNoException(); + reply.writeInt(result); + return true; + } + case GET_CURRENT_USER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); UserInfo userInfo = getCurrentUser(); @@ -3756,11 +3767,25 @@ class ActivityManagerProxy implements IActivityManager return result; } + public int stopUser(int userid, IStopUserCallback callback) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(userid); + data.writeStrongInterface(callback); + mRemote.transact(STOP_USER_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + reply.recycle(); + data.recycle(); + return result; + } + public UserInfo getCurrentUser() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); - mRemote.transact(SWITCH_USER_TRANSACTION, data, reply, 0); + mRemote.transact(GET_CURRENT_USER_TRANSACTION, data, reply, 0); reply.readException(); UserInfo userInfo = UserInfo.CREATOR.createFromParcel(reply); reply.recycle(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b8e16c5..4a1bf75 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -43,6 +43,7 @@ import android.database.sqlite.SQLiteDebug.DbStats; import android.graphics.Bitmap; import android.graphics.Canvas; import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; import android.net.IConnectivityManager; import android.net.Proxy; import android.net.ProxyProperties; @@ -1557,7 +1558,7 @@ public final class ActivityThread { return dm; } - DisplayManager displayManager = DisplayManager.getInstance(); + DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance(); if (displayManager == null) { // may be null early in system startup dm = new DisplayMetrics(); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 32086d7..efe4b7b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -349,10 +349,11 @@ class ContextImpl extends Context { return InputManager.getInstance(); }}); - registerService(DISPLAY_SERVICE, new StaticServiceFetcher() { - public Object createStaticService() { - return DisplayManager.getInstance(); - }}); + registerService(DISPLAY_SERVICE, new ServiceFetcher() { + @Override + public Object createService(ContextImpl ctx) { + return new DisplayManager(ctx.getOuterContext()); + }}); registerService(INPUT_METHOD_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index c3e911e..70d8445 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -331,6 +331,7 @@ public interface IActivityManager extends IInterface { // Multi-user APIs public boolean switchUser(int userid) throws RemoteException; + public int stopUser(int userid, IStopUserCallback callback) throws RemoteException; public UserInfo getCurrentUser() throws RemoteException; public boolean removeSubTask(int taskId, int subTaskIndex) throws RemoteException; @@ -611,4 +612,5 @@ public interface IActivityManager extends IInterface { int UNSTABLE_PROVIDER_DIED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+150; int IS_INTENT_SENDER_AN_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+151; int START_ACTIVITY_AS_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+152; + int STOP_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+153; } diff --git a/core/java/android/app/IStopUserCallback.aidl b/core/java/android/app/IStopUserCallback.aidl new file mode 100644 index 0000000..19ac1d5 --- /dev/null +++ b/core/java/android/app/IStopUserCallback.aidl @@ -0,0 +1,27 @@ +/* +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.app; + +/** + * Callback to find out when we have finished stopping a user. + * {@hide} + */ +interface IStopUserCallback +{ + void userStopped(int userId); + void userStopAborted(int userId); +} diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 17d404d..f817fb4 100755 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -75,6 +75,7 @@ import java.util.UUID; public final class BluetoothAdapter { private static final String TAG = "BluetoothAdapter"; private static final boolean DBG = true; + private static final boolean VDBG = false; /** * Sentinel error value for this class. Guaranteed to not equal any other @@ -465,7 +466,7 @@ public final class BluetoothAdapter { if (mService != null) { int state= mService.getState(); - if (DBG) Log.d(TAG, "" + hashCode() + ": getState(). Returning " + state); + if (VDBG) Log.d(TAG, "" + hashCode() + ": getState(). Returning " + state); return state; } // TODO(BT) there might be a small gap during STATE_TURNING_ON that diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java index b2b5d81..30406e9 100644 --- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java +++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java @@ -133,6 +133,11 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker { return true; } + @Override + public void captivePortalCheckComplete() { + // not implemented + } + /** * Re-enable connectivity to a network after a {@link #teardown()}. */ diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 06edf32..53e0a75 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -27,7 +27,6 @@ import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; -import android.media.RemoteControlClient; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -2287,6 +2286,15 @@ public class Intent implements Parcelable, Cloneable { "android.intent.action.USER_ADDED"; /** + * Broadcast sent to the system when a user is stopped. Carries an extra EXTRA_USER_HANDLE that has + * the userHandle of the user. This is similar to {@link #ACTION_PACKAGE_RESTARTED}, + * but for an entire user instead of a specific package. + * @hide + */ + public static final String ACTION_USER_STOPPED = + "android.intent.action.USER_STOPPED"; + + /** * Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USER_HANDLE that has * the userHandle of the user. * @hide diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 0d99d3f..0aa094f 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2319,6 +2319,9 @@ public abstract class PackageManager { * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra * @param verificationCode either {@link PackageManager#VERIFICATION_ALLOW} * or {@link PackageManager#VERIFICATION_REJECT}. + * @throws SecurityException if the caller does not have the + * {@link android.Manifest.permission#PACKAGE_VERIFICATION_AGENT} + * permission. */ public abstract void verifyPendingInstall(int id, int verificationCode); @@ -2342,9 +2345,11 @@ public abstract class PackageManager { * @param millisecondsToDelay the amount of time requested for the timeout. * Must be positive and less than * {@link PackageManager#MAXIMUM_VERIFICATION_TIMEOUT}. - * * @throws IllegalArgumentException if {@code millisecondsToDelay} is out * of bounds or {@code verificationCodeAtTimeout} is unknown. + * @throws SecurityException if the caller does not have the + * {@link android.Manifest.permission#PACKAGE_VERIFICATION_AGENT} + * permission. */ public abstract void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay); diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 4d9077f..829620b 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -26,6 +26,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; +import android.text.TextUtils; import android.view.Surface; import android.view.SurfaceHolder; @@ -34,7 +35,6 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.StringTokenizer; import java.util.concurrent.locks.ReentrantLock; /** @@ -1905,7 +1905,7 @@ public class Camera { private HashMap<String, String> mMap; private Parameters() { - mMap = new HashMap<String, String>(); + mMap = new HashMap<String, String>(64); } /** @@ -1929,7 +1929,7 @@ public class Camera { * semi-colon delimited key-value pairs */ public String flatten() { - StringBuilder flattened = new StringBuilder(); + StringBuilder flattened = new StringBuilder(128); for (String k : mMap.keySet()) { flattened.append(k); flattened.append("="); @@ -1952,9 +1952,9 @@ public class Camera { public void unflatten(String flattened) { mMap.clear(); - StringTokenizer tokenizer = new StringTokenizer(flattened, ";"); - while (tokenizer.hasMoreElements()) { - String kv = tokenizer.nextToken(); + TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(';'); + splitter.setString(flattened); + for (String kv : splitter) { int pos = kv.indexOf('='); if (pos == -1) { continue; @@ -3488,11 +3488,11 @@ public class Camera { private ArrayList<String> split(String str) { if (str == null) return null; - // Use StringTokenizer because it is faster than split. - StringTokenizer tokenizer = new StringTokenizer(str, ","); + TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(','); + splitter.setString(str); ArrayList<String> substrings = new ArrayList<String>(); - while (tokenizer.hasMoreElements()) { - substrings.add(tokenizer.nextToken()); + for (String s : splitter) { + substrings.add(s); } return substrings; } @@ -3502,11 +3502,11 @@ public class Camera { private ArrayList<Integer> splitInt(String str) { if (str == null) return null; - StringTokenizer tokenizer = new StringTokenizer(str, ","); + TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(','); + splitter.setString(str); ArrayList<Integer> substrings = new ArrayList<Integer>(); - while (tokenizer.hasMoreElements()) { - String token = tokenizer.nextToken(); - substrings.add(Integer.parseInt(token)); + for (String s : splitter) { + substrings.add(Integer.parseInt(s)); } if (substrings.size() == 0) return null; return substrings; @@ -3515,11 +3515,11 @@ public class Camera { private void splitInt(String str, int[] output) { if (str == null) return; - StringTokenizer tokenizer = new StringTokenizer(str, ","); + TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(','); + splitter.setString(str); int index = 0; - while (tokenizer.hasMoreElements()) { - String token = tokenizer.nextToken(); - output[index++] = Integer.parseInt(token); + for (String s : splitter) { + output[index++] = Integer.parseInt(s); } } @@ -3527,11 +3527,11 @@ public class Camera { private void splitFloat(String str, float[] output) { if (str == null) return; - StringTokenizer tokenizer = new StringTokenizer(str, ","); + TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(','); + splitter.setString(str); int index = 0; - while (tokenizer.hasMoreElements()) { - String token = tokenizer.nextToken(); - output[index++] = Float.parseFloat(token); + for (String s : splitter) { + output[index++] = Float.parseFloat(s); } } @@ -3558,10 +3558,11 @@ public class Camera { private ArrayList<Size> splitSize(String str) { if (str == null) return null; - StringTokenizer tokenizer = new StringTokenizer(str, ","); + TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(','); + splitter.setString(str); ArrayList<Size> sizeList = new ArrayList<Size>(); - while (tokenizer.hasMoreElements()) { - Size size = strToSize(tokenizer.nextToken()); + for (String s : splitter) { + Size size = strToSize(s); if (size != null) sizeList.add(size); } if (sizeList.size() == 0) return null; diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 98d2f69..74996da 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -18,20 +18,12 @@ package android.hardware.display; import android.content.Context; import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.util.Log; +import android.util.SparseArray; import android.view.CompatibilityInfoHolder; import android.view.Display; -import android.view.DisplayInfo; - -import java.util.ArrayList; /** - * Manages the properties, media routing and power state of attached displays. + * Manages the properties of attached displays. * <p> * Get an instance of this class by calling * {@link android.content.Context#getSystemService(java.lang.String) @@ -43,110 +35,79 @@ public final class DisplayManager { private static final String TAG = "DisplayManager"; private static final boolean DEBUG = false; - private static final int MSG_DISPLAY_ADDED = 1; - private static final int MSG_DISPLAY_REMOVED = 2; - private static final int MSG_DISPLAY_CHANGED = 3; - - private static DisplayManager sInstance; - - private final IDisplayManager mDm; - - // Guarded by mDisplayLock - private final Object mDisplayLock = new Object(); - private final ArrayList<DisplayListenerDelegate> mDisplayListeners = - new ArrayList<DisplayListenerDelegate>(); + private final Context mContext; + private final DisplayManagerGlobal mGlobal; + private final Object mLock = new Object(); + private final SparseArray<Display> mDisplays = new SparseArray<Display>(); - private DisplayManager(IDisplayManager dm) { - mDm = dm; + /** @hide */ + public DisplayManager(Context context) { + mContext = context; + mGlobal = DisplayManagerGlobal.getInstance(); } /** - * Gets an instance of the display manager. + * Gets information about a logical display. * - * @return The display manager instance, may be null early in system startup - * before the display manager has been fully initialized. + * The display metrics may be adjusted to provide compatibility + * for legacy applications. * - * @hide + * @param displayId The logical display id. + * @return The display object, or null if there is no valid display with the given id. */ - public static DisplayManager getInstance() { - synchronized (DisplayManager.class) { - if (sInstance == null) { - IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE); - if (b != null) { - sInstance = new DisplayManager(IDisplayManager.Stub.asInterface(b)); - } - } - return sInstance; + public Display getDisplay(int displayId) { + synchronized (mLock) { + return getOrCreateDisplayLocked(displayId, false /*assumeValid*/); } } /** - * Get information about a particular logical display. + * Gets all currently valid logical displays. * - * @param displayId The logical display id. - * @param outInfo A structure to populate with the display info. - * @return True if the logical display exists, false otherwise. - * @hide + * @return An array containing all displays. */ - public boolean getDisplayInfo(int displayId, DisplayInfo outInfo) { - try { - return mDm.getDisplayInfo(displayId, outInfo); - } catch (RemoteException ex) { - Log.e(TAG, "Could not get display information from display manager.", ex); - return false; + public Display[] getDisplays() { + int[] displayIds = mGlobal.getDisplayIds(); + int expectedCount = displayIds.length; + Display[] displays = new Display[expectedCount]; + synchronized (mLock) { + int actualCount = 0; + for (int i = 0; i < expectedCount; i++) { + Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/); + if (display != null) { + displays[actualCount++] = display; + } + } + if (actualCount != expectedCount) { + Display[] oldDisplays = displays; + displays = new Display[actualCount]; + System.arraycopy(oldDisplays, 0, displays, 0, actualCount); + } } + return displays; } - /** - * Gets information about a logical display. - * - * The display metrics may be adjusted to provide compatibility - * for legacy applications. - * - * @param displayId The logical display id. - * @param applicationContext The application context from which to obtain - * compatible metrics. - * @return The display object. - */ - public Display getDisplay(int displayId, Context applicationContext) { - if (applicationContext == null) { - throw new IllegalArgumentException("applicationContext must not be null"); + private Display getOrCreateDisplayLocked(int displayId, boolean assumeValid) { + Display display = mDisplays.get(displayId); + if (display == null) { + display = mGlobal.getCompatibleDisplay(displayId, + getCompatibilityInfoForDisplayLocked(displayId)); + if (display != null) { + mDisplays.put(displayId, display); + } + } else if (!assumeValid && !display.isValid()) { + display = null; } + return display; + } + private CompatibilityInfoHolder getCompatibilityInfoForDisplayLocked(int displayId) { CompatibilityInfoHolder cih = null; if (displayId == Display.DEFAULT_DISPLAY) { - cih = applicationContext.getCompatibilityInfo(); + cih = mContext.getCompatibilityInfo(); } - return getCompatibleDisplay(displayId, cih); - } - - /** - * Gets information about a logical display. - * - * The display metrics may be adjusted to provide compatibility - * for legacy applications. - * - * @param displayId The logical display id. - * @param cih The compatibility info, or null if none is required. - * @return The display object. - * - * @hide - */ - public Display getCompatibleDisplay(int displayId, CompatibilityInfoHolder cih) { - return new Display(displayId, cih); - } - - /** - * Gets information about a logical display without applying any compatibility metrics. - * - * @param displayId The logical display id. - * @return The display object. - * - * @hide - */ - public Display getRealDisplay(int displayId) { - return getCompatibleDisplay(displayId, null); + return cih; } /** @@ -160,16 +121,7 @@ public final class DisplayManager { * @see #unregisterDisplayListener */ public void registerDisplayListener(DisplayListener listener, Handler handler) { - if (listener == null) { - throw new IllegalArgumentException("listener must not be null"); - } - - synchronized (mDisplayLock) { - int index = findDisplayListenerLocked(listener); - if (index < 0) { - mDisplayListeners.add(new DisplayListenerDelegate(listener, handler)); - } - } + mGlobal.registerDisplayListener(listener, handler); } /** @@ -180,28 +132,7 @@ public final class DisplayManager { * @see #registerDisplayListener */ public void unregisterDisplayListener(DisplayListener listener) { - if (listener == null) { - throw new IllegalArgumentException("listener must not be null"); - } - - synchronized (mDisplayLock) { - int index = findDisplayListenerLocked(listener); - if (index >= 0) { - DisplayListenerDelegate d = mDisplayListeners.get(index); - d.removeCallbacksAndMessages(null); - mDisplayListeners.remove(index); - } - } - } - - private int findDisplayListenerLocked(DisplayListener listener) { - final int numListeners = mDisplayListeners.size(); - for (int i = 0; i < numListeners; i++) { - if (mDisplayListeners.get(i).mListener == listener) { - return i; - } - } - return -1; + mGlobal.unregisterDisplayListener(listener); } /** @@ -210,7 +141,8 @@ public final class DisplayManager { public interface DisplayListener { /** * Called whenever a logical display has been added to the system. - * Use {@link DisplayManager#getDisplay} to get more information about the display. + * Use {@link DisplayManager#getDisplay} to get more information about + * the display. * * @param displayId The id of the logical display that was added. */ @@ -230,28 +162,4 @@ public final class DisplayManager { */ void onDisplayChanged(int displayId); } - - private static final class DisplayListenerDelegate extends Handler { - public final DisplayListener mListener; - - public DisplayListenerDelegate(DisplayListener listener, Handler handler) { - super(handler != null ? handler.getLooper() : Looper.myLooper()); - mListener = listener; - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_DISPLAY_ADDED: - mListener.onDisplayAdded(msg.arg1); - break; - case MSG_DISPLAY_REMOVED: - mListener.onDisplayRemoved(msg.arg1); - break; - case MSG_DISPLAY_CHANGED: - mListener.onDisplayChanged(msg.arg1); - break; - } - } - } } diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java new file mode 100644 index 0000000..69c0319 --- /dev/null +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +import android.content.Context; +import android.hardware.display.DisplayManager.DisplayListener; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; +import android.util.SparseArray; +import android.view.CompatibilityInfoHolder; +import android.view.Display; +import android.view.DisplayInfo; + +import java.util.ArrayList; + +/** + * Manager communication with the display manager service on behalf of + * an application process. You're probably looking for {@link DisplayManager}. + * + * @hide + */ +public final class DisplayManagerGlobal { + private static final String TAG = "DisplayManager"; + private static final boolean DEBUG = false; + + public static final int EVENT_DISPLAY_ADDED = 1; + public static final int EVENT_DISPLAY_CHANGED = 2; + public static final int EVENT_DISPLAY_REMOVED = 3; + + private static DisplayManagerGlobal sInstance; + + private final Object mLock = new Object(); + + private final IDisplayManager mDm; + + private DisplayManagerCallback mCallback; + private final ArrayList<DisplayListenerDelegate> mDisplayListeners = + new ArrayList<DisplayListenerDelegate>(); + + private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>(); + private int[] mDisplayIdCache; + + private DisplayManagerGlobal(IDisplayManager dm) { + mDm = dm; + } + + /** + * Gets an instance of the display manager global singleton. + * + * @return The display manager instance, may be null early in system startup + * before the display manager has been fully initialized. + */ + public static DisplayManagerGlobal getInstance() { + synchronized (DisplayManagerGlobal.class) { + if (sInstance == null) { + IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE); + if (b != null) { + sInstance = new DisplayManagerGlobal(IDisplayManager.Stub.asInterface(b)); + } + } + return sInstance; + } + } + + /** + * Get information about a particular logical display. + * + * @param displayId The logical display id. + * @return Information about the specified display, or null if it does not exist. + * This object belongs to an internal cache and should be treated as if it were immutable. + */ + public DisplayInfo getDisplayInfo(int displayId) { + try { + synchronized (mLock) { + DisplayInfo info = mDisplayInfoCache.get(displayId); + if (info != null) { + return info; + } + + info = mDm.getDisplayInfo(displayId); + if (info == null) { + return null; + } + if (DEBUG) { + Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info); + } + + mDisplayInfoCache.put(displayId, info); + registerCallbackIfNeededLocked(); + return info; + } + } catch (RemoteException ex) { + Log.e(TAG, "Could not get display information from display manager.", ex); + return null; + } + } + + /** + * Gets all currently valid logical display ids. + * + * @return An array containing all display ids. + */ + public int[] getDisplayIds() { + try { + synchronized (mLock) { + if (mDisplayIdCache == null) { + mDisplayIdCache = mDm.getDisplayIds(); + registerCallbackIfNeededLocked(); + } + return mDisplayIdCache; + } + } catch (RemoteException ex) { + Log.e(TAG, "Could not get display ids from display manager.", ex); + return new int[] { Display.DEFAULT_DISPLAY }; + } + } + + /** + * Gets information about a logical display. + * + * The display metrics may be adjusted to provide compatibility + * for legacy applications. + * + * @param displayId The logical display id. + * @param cih The compatibility info, or null if none is required. + * @return The display object, or null if there is no display with the given id. + */ + public Display getCompatibleDisplay(int displayId, CompatibilityInfoHolder cih) { + DisplayInfo displayInfo = getDisplayInfo(displayId); + if (displayInfo == null) { + return null; + } + return new Display(this, displayId, displayInfo, cih); + } + + /** + * Gets information about a logical display without applying any compatibility metrics. + * + * @param displayId The logical display id. + * @return The display object, or null if there is no display with the given id. + */ + public Display getRealDisplay(int displayId) { + return getCompatibleDisplay(displayId, null); + } + + public void registerDisplayListener(DisplayListener listener, Handler handler) { + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + synchronized (mLock) { + int index = findDisplayListenerLocked(listener); + if (index < 0) { + mDisplayListeners.add(new DisplayListenerDelegate(listener, handler)); + registerCallbackIfNeededLocked(); + } + } + } + + public void unregisterDisplayListener(DisplayListener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + synchronized (mLock) { + int index = findDisplayListenerLocked(listener); + if (index >= 0) { + DisplayListenerDelegate d = mDisplayListeners.get(index); + d.clearEvents(); + mDisplayListeners.remove(index); + } + } + } + + private int findDisplayListenerLocked(DisplayListener listener) { + final int numListeners = mDisplayListeners.size(); + for (int i = 0; i < numListeners; i++) { + if (mDisplayListeners.get(i).mListener == listener) { + return i; + } + } + return -1; + } + + private void registerCallbackIfNeededLocked() { + if (mCallback == null) { + mCallback = new DisplayManagerCallback(); + try { + mDm.registerCallback(mCallback); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to register callback with display manager service.", ex); + mCallback = null; + } + } + } + + private void handleDisplayEvent(int displayId, int event) { + synchronized (mLock) { + mDisplayInfoCache.remove(displayId); + + if (event == EVENT_DISPLAY_ADDED || event == EVENT_DISPLAY_REMOVED) { + mDisplayIdCache = null; + } + + final int numListeners = mDisplayListeners.size(); + for (int i = 0; i < numListeners; i++) { + mDisplayListeners.get(i).sendDisplayEvent(displayId, event); + } + } + } + + private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { + @Override + public void onDisplayEvent(int displayId, int event) { + if (DEBUG) { + Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event); + } + handleDisplayEvent(displayId, event); + } + } + + private static final class DisplayListenerDelegate extends Handler { + public final DisplayListener mListener; + + public DisplayListenerDelegate(DisplayListener listener, Handler handler) { + super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/); + mListener = listener; + } + + public void sendDisplayEvent(int displayId, int event) { + Message msg = obtainMessage(event, displayId, 0); + sendMessage(msg); + } + + public void clearEvents() { + removeCallbacksAndMessages(null); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_DISPLAY_ADDED: + mListener.onDisplayAdded(msg.arg1); + break; + case EVENT_DISPLAY_CHANGED: + mListener.onDisplayChanged(msg.arg1); + break; + case EVENT_DISPLAY_REMOVED: + mListener.onDisplayRemoved(msg.arg1); + break; + } + } + } +} diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index fd8c35f..d802aa1 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -16,9 +16,13 @@ package android.hardware.display; +import android.hardware.display.IDisplayManagerCallback; import android.view.DisplayInfo; /** @hide */ interface IDisplayManager { - boolean getDisplayInfo(int displayId, out DisplayInfo outInfo); + DisplayInfo getDisplayInfo(int displayId); + int[] getDisplayIds(); + + void registerCallback(in IDisplayManagerCallback callback); } diff --git a/core/java/android/hardware/display/IDisplayManagerCallback.aidl b/core/java/android/hardware/display/IDisplayManagerCallback.aidl new file mode 100644 index 0000000..c50e3fb --- /dev/null +++ b/core/java/android/hardware/display/IDisplayManagerCallback.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +/** @hide */ +interface IDisplayManagerCallback { + oneway void onDisplayEvent(int displayId, int event); +} diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java index 99bd647..4b60f07 100644 --- a/core/java/android/net/BaseNetworkStateTracker.java +++ b/core/java/android/net/BaseNetworkStateTracker.java @@ -96,6 +96,11 @@ public abstract class BaseNetworkStateTracker implements NetworkStateTracker { } @Override + public void captivePortalCheckComplete() { + // not implemented + } + + @Override public boolean setRadio(boolean turnOn) { // Base tracker doesn't handle radios return true; diff --git a/core/java/android/net/CaptivePortalTracker.java b/core/java/android/net/CaptivePortalTracker.java new file mode 100644 index 0000000..aa392d0 --- /dev/null +++ b/core/java/android/net/CaptivePortalTracker.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.net.ConnectivityManager; +import android.net.IConnectivityManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.provider.Settings; +import android.util.Log; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.Inet4Address; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.android.internal.R; + +/** + * This class allows captive portal detection + * @hide + */ +public class CaptivePortalTracker { + private static final boolean DBG = true; + private static final String TAG = "CaptivePortalTracker"; + + private static final String DEFAULT_SERVER = "clients3.google.com"; + private static final String NOTIFICATION_ID = "CaptivePortal.Notification"; + + private static final int SOCKET_TIMEOUT_MS = 10000; + + private String mServer; + private String mUrl; + private boolean mNotificationShown = false; + private boolean mIsCaptivePortalCheckEnabled = false; + private InternalHandler mHandler; + private IConnectivityManager mConnService; + private Context mContext; + private NetworkInfo mNetworkInfo; + private boolean mIsCaptivePortal = false; + + private static final int DETECT_PORTAL = 0; + private static final int HANDLE_CONNECT = 1; + + /** + * Activity Action: Switch to the captive portal network + * <p>Input: Nothing. + * <p>Output: Nothing. + */ + public static final String ACTION_SWITCH_TO_CAPTIVE_PORTAL + = "android.net.SWITCH_TO_CAPTIVE_PORTAL"; + + private CaptivePortalTracker(Context context, NetworkInfo info, IConnectivityManager cs) { + mContext = context; + mNetworkInfo = info; + mConnService = cs; + + HandlerThread handlerThread = new HandlerThread("CaptivePortalThread"); + handlerThread.start(); + mHandler = new InternalHandler(handlerThread.getLooper()); + mHandler.obtainMessage(DETECT_PORTAL).sendToTarget(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_SWITCH_TO_CAPTIVE_PORTAL); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + + mContext.registerReceiver(mReceiver, filter); + + mServer = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.CAPTIVE_PORTAL_SERVER); + if (mServer == null) mServer = DEFAULT_SERVER; + + mIsCaptivePortalCheckEnabled = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1; + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(ACTION_SWITCH_TO_CAPTIVE_PORTAL)) { + notifyPortalCheckComplete(); + } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + NetworkInfo info = intent.getParcelableExtra( + ConnectivityManager.EXTRA_NETWORK_INFO); + mHandler.obtainMessage(HANDLE_CONNECT, info).sendToTarget(); + } + } + }; + + public static CaptivePortalTracker detect(Context context, NetworkInfo info, + IConnectivityManager cs) { + CaptivePortalTracker captivePortal = new CaptivePortalTracker(context, info, cs); + return captivePortal; + } + + private class InternalHandler extends Handler { + public InternalHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case DETECT_PORTAL: + InetAddress server = lookupHost(mServer); + if (server != null) { + requestRouteToHost(server); + if (isCaptivePortal(server)) { + if (DBG) log("Captive portal " + mNetworkInfo); + setNotificationVisible(true); + mIsCaptivePortal = true; + break; + } + } + notifyPortalCheckComplete(); + quit(); + break; + case HANDLE_CONNECT: + NetworkInfo info = (NetworkInfo) msg.obj; + if (info.getType() != mNetworkInfo.getType()) break; + + if (info.getState() == NetworkInfo.State.CONNECTED || + info.getState() == NetworkInfo.State.DISCONNECTED) { + setNotificationVisible(false); + } + + /* Connected to a captive portal */ + if (info.getState() == NetworkInfo.State.CONNECTED && + mIsCaptivePortal) { + launchBrowser(); + quit(); + } + break; + default: + loge("Unhandled message " + msg); + break; + } + } + + private void quit() { + mIsCaptivePortal = false; + getLooper().quit(); + mContext.unregisterReceiver(mReceiver); + } + } + + private void launchBrowser() { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mUrl)); + intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } + + private void notifyPortalCheckComplete() { + try { + mConnService.captivePortalCheckComplete(mNetworkInfo); + } catch(RemoteException e) { + e.printStackTrace(); + } + } + + private void requestRouteToHost(InetAddress server) { + try { + mConnService.requestRouteToHostAddress(mNetworkInfo.getType(), + server.getAddress()); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + /** + * Do a URL fetch on a known server to see if we get the data we expect + */ + private boolean isCaptivePortal(InetAddress server) { + HttpURLConnection urlConnection = null; + if (!mIsCaptivePortalCheckEnabled) return false; + + mUrl = "http://" + server.getHostAddress() + "/generate_204"; + try { + URL url = new URL(mUrl); + urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.setInstanceFollowRedirects(false); + urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setUseCaches(false); + urlConnection.getInputStream(); + // we got a valid response, but not from the real google + return urlConnection.getResponseCode() != 204; + } catch (IOException e) { + if (DBG) log("Probably not a portal: exception " + e); + return false; + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + } + } + + private InetAddress lookupHost(String hostname) { + InetAddress inetAddress[]; + try { + inetAddress = InetAddress.getAllByName(hostname); + } catch (UnknownHostException e) { + return null; + } + + for (InetAddress a : inetAddress) { + if (a instanceof Inet4Address) return a; + } + return null; + } + + private void setNotificationVisible(boolean visible) { + // if it should be hidden and it is already hidden, then noop + if (!visible && !mNotificationShown) { + return; + } + + Resources r = Resources.getSystem(); + NotificationManager notificationManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); + + if (visible) { + CharSequence title = r.getString(R.string.wifi_available_sign_in, 0); + CharSequence details = r.getString(R.string.wifi_available_sign_in_detailed, + mNetworkInfo.getExtraInfo()); + + Notification notification = new Notification(); + notification.when = 0; + notification.icon = com.android.internal.R.drawable.stat_notify_wifi_in_range; + notification.flags = Notification.FLAG_AUTO_CANCEL; + notification.contentIntent = PendingIntent.getBroadcast(mContext, 0, + new Intent(CaptivePortalTracker.ACTION_SWITCH_TO_CAPTIVE_PORTAL), 0); + + notification.tickerText = title; + notification.setLatestEventInfo(mContext, title, details, notification.contentIntent); + + notificationManager.notify(NOTIFICATION_ID, 1, notification); + } else { + notificationManager.cancel(NOTIFICATION_ID, 1); + } + mNotificationShown = visible; + } + + private static void log(String s) { + Log.d(TAG, s); + } + + private static void loge(String s) { + Log.e(TAG, s); + } + +} diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 60bf4d6..a570473 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -921,4 +921,15 @@ public class ConnectivityManager { return false; } } + + /** + * {@hide} + */ + public void captivePortalCheckComplete(NetworkInfo info) { + try { + mService.captivePortalCheckComplete(info); + } catch (RemoteException e) { + } + } + } diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java index cc3e34f..874e80a 100644 --- a/core/java/android/net/DhcpStateMachine.java +++ b/core/java/android/net/DhcpStateMachine.java @@ -92,10 +92,12 @@ public class DhcpStateMachine extends StateMachine { /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates * success/failure */ public static final int CMD_POST_DHCP_ACTION = BASE + 5; + /* Notification from DHCP state machine before quitting */ + public static final int CMD_ON_QUIT = BASE + 6; /* Command from controller to indicate DHCP discovery/renewal can continue * after pre DHCP action is complete */ - public static final int CMD_PRE_DHCP_ACTION_COMPLETE = BASE + 6; + public static final int CMD_PRE_DHCP_ACTION_COMPLETE = BASE + 7; /* Message.arg1 arguments to CMD_POST_DHCP notification */ public static final int DHCP_SUCCESS = 1; @@ -172,6 +174,10 @@ public class DhcpStateMachine extends StateMachine { quit(); } + protected void onQuitting() { + mController.sendMessage(CMD_ON_QUIT); + } + class DefaultState extends State { @Override public void exit() { diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java index ccd96ff..39440c2 100644 --- a/core/java/android/net/DummyDataStateTracker.java +++ b/core/java/android/net/DummyDataStateTracker.java @@ -119,6 +119,10 @@ public class DummyDataStateTracker implements NetworkStateTracker { return true; } + public void captivePortalCheckComplete() { + // not implemented + } + /** * Record the detailed state of a network, and if it is a * change from the previous state, send a notification to diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java index c690430..c52aa9e 100644 --- a/core/java/android/net/EthernetDataTracker.java +++ b/core/java/android/net/EthernetDataTracker.java @@ -274,6 +274,11 @@ public class EthernetDataTracker implements NetworkStateTracker { return mLinkUp; } + @Override + public void captivePortalCheckComplete() { + // not implemented + } + /** * Turn the wireless radio off for a network. * @param turnOn {@code true} to turn the radio on, {@code false} diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 3614045..056fa03 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -124,4 +124,6 @@ interface IConnectivityManager LegacyVpnInfo getLegacyVpnInfo(); boolean updateLockdownVpn(); + + void captivePortalCheckComplete(in NetworkInfo info); } diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java index d59fa6a..b35d61c 100644 --- a/core/java/android/net/MobileDataStateTracker.java +++ b/core/java/android/net/MobileDataStateTracker.java @@ -381,6 +381,11 @@ public class MobileDataStateTracker implements NetworkStateTracker { return (setEnableApn(mApnType, false) != PhoneConstants.APN_REQUEST_FAILED); } + @Override + public void captivePortalCheckComplete() { + // not implemented + } + /** * Record the detailed state of a network, and if it is a * change from the previous state, send a notification to diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java index 0bc6b58..0b23cb7 100644 --- a/core/java/android/net/NetworkInfo.java +++ b/core/java/android/net/NetworkInfo.java @@ -79,7 +79,9 @@ public class NetworkInfo implements Parcelable { /** Access to this network is blocked. */ BLOCKED, /** Link has poor connectivity. */ - VERIFYING_POOR_LINK + VERIFYING_POOR_LINK, + /** Checking if network is a captive portal */ + CAPTIVE_PORTAL_CHECK, } /** @@ -97,6 +99,7 @@ public class NetworkInfo implements Parcelable { stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING); stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING); stateMap.put(DetailedState.VERIFYING_POOR_LINK, State.CONNECTING); + stateMap.put(DetailedState.CAPTIVE_PORTAL_CHECK, State.CONNECTING); stateMap.put(DetailedState.CONNECTED, State.CONNECTED); stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED); stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING); diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java index eae89f1..0a0c1e0 100644 --- a/core/java/android/net/NetworkStateTracker.java +++ b/core/java/android/net/NetworkStateTracker.java @@ -123,6 +123,11 @@ public interface NetworkStateTracker { public boolean reconnect(); /** + * Ready to switch on to the network after captive portal check + */ + public void captivePortalCheckComplete(); + + /** * Turn the wireless radio off for a network. * @param turnOn {@code true} to turn the radio on, {@code false} */ diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index fb5263d..65d3f2b 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -329,7 +329,7 @@ public class VpnService extends Service { throw new IllegalArgumentException("Bad address"); } - mAddresses.append(String.format(" %s/%d", address.getHostAddress(), prefixLength)); + mAddresses.append(' ' + address.getHostAddress() + '/' + prefixLength); return this; } diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index 4e2b5c0..0f9be9c 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -412,6 +412,50 @@ public class Handler { } /** + * Runs the specified task synchronously. + * + * If the current thread is the same as the handler thread, then the runnable + * runs immediately without being enqueued. Otherwise, posts the runnable + * to the handler and waits for it to complete before returning. + * + * This method is dangerous! Improper use can result in deadlocks. + * Never call this method while any locks are held or use it in a + * possibly re-entrant manner. + * + * This method is occasionally useful in situations where a background thread + * must synchronously await completion of a task that must run on the + * handler's thread. However, this problem is often a symptom of bad design. + * Consider improving the design (if possible) before resorting to this method. + * + * One example of where you might want to use this method is when you just + * set up a Handler thread and need to perform some initialization steps on + * it before continuing execution. + * + * @param r The Runnable that will be executed synchronously. + * + * @return Returns true if the Runnable was successfully executed. + * Returns false on failure, usually because the + * looper processing the message queue is exiting. + * + * @hide This method is prone to abuse and should probably not be in the API. + * If we ever do make it part of the API, we might want to rename it to something + * less funny like runUnsafe(). + */ + public final boolean runWithScissors(final Runnable r) { + if (r == null) { + throw new IllegalArgumentException("runnable must not be null"); + } + + if (Looper.myLooper() == mLooper) { + r.run(); + return true; + } + + BlockingRunnable br = new BlockingRunnable(r); + return br.postAndWait(this); + } + + /** * Remove any pending posts of Runnable r that are in the message queue. */ public final void removeCallbacks(Runnable r) @@ -678,4 +722,41 @@ public class Handler { final Callback mCallback; final boolean mAsynchronous; IMessenger mMessenger; + + private static final class BlockingRunnable implements Runnable { + private final Runnable mTask; + private boolean mDone; + + public BlockingRunnable(Runnable task) { + mTask = task; + } + + @Override + public void run() { + try { + mTask.run(); + } finally { + synchronized (this) { + mDone = true; + notifyAll(); + } + } + } + + public boolean postAndWait(Handler handler) { + if (!handler.post(this)) { + return false; + } + + synchronized (this) { + while (!mDone) { + try { + wait(); + } catch (InterruptedException ex) { + } + } + } + return true; + } + } } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 851b8df..d5fca4d 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -584,6 +584,8 @@ public class Process { } if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER) { argsForZygote.add("--mount-external-multiuser"); + } else if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL) { + argsForZygote.add("--mount-external-multiuser-all"); } argsForZygote.add("--target-sdk-version=" + targetSdkVersion); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1f6f0dd..b4841b1 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3279,13 +3279,6 @@ public final class Settings { /** - * ms delay before rechecking a connect SSID for walled garden with a http download. - * @hide - */ - public static final String WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS = - "wifi_watchdog_walled_garden_interval_ms"; - - /** * Number of ARP pings per check. * @hide */ @@ -3322,23 +3315,6 @@ public final class Settings { "wifi_suspend_optimizations_enabled"; /** - * Setting to turn off walled garden test on Wi-Fi. Feature is enabled by default and - * the setting needs to be set to 0 to disable it. - * @hide - */ - public static final String WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED = - "wifi_watchdog_walled_garden_test_enabled"; - - /** - * The URL used for walled garden check upon a new conection. WifiWatchdogService - * fetches the URL and checks to see if {@link #WIFI_WATCHDOG_WALLED_GARDEN_PATTERN} - * is not part of the title string to notify the user on the presence of a walled garden. - * @hide - */ - public static final String WIFI_WATCHDOG_WALLED_GARDEN_URL = - "wifi_watchdog_walled_garden_url"; - - /** * The maximum number of times we will retry a connection to an access * point for which we have failed in acquiring an IP address from DHCP. * A value of N means that we will make N+1 connection attempts in all. @@ -3362,6 +3338,21 @@ public final class Settings { public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name"; /** + * Setting to turn off captive portal detection. Feature is enabled by default and + * the setting needs to be set to 0 to disable it. + * @hide + */ + public static final String CAPTIVE_PORTAL_DETECTION_ENABLED = + "captive_portal_detection_enabled"; + + /** + * The server used for captive portal detection upon a new conection. A 204 response + * code from the server is used for validation. + * @hide + */ + public static final String CAPTIVE_PORTAL_SERVER = "captive_portal_server"; + + /** * Maximum amount of time in milliseconds to hold a wakelock while waiting for mobile * data connectivity to be established after a disconnect from Wi-Fi. */ @@ -4360,6 +4351,25 @@ public final class Settings { public static final String SMS_SHORT_CODES_PREFIX = "sms_short_codes_"; /** + * Overlay display devices setting. + * The associated value is a specially formatted string that describes the + * size and density of simulated secondary display devices. + * <p> + * Format: {width}x{height}/{dpi};... + * </p><p> + * Example: + * <ul> + * <li><code>1280x720/213</code>: make one overlay that is 1280x720 at 213dpi.</li> + * <li><code>1920x1080/320;1280x720/213</code>: make two overlays, the first + * at 1080p and the second at 720p.</li> + * <li>If the value is empty, then no overlay display devices are created.</li> + * </ul></p> + * + * @hide + */ + public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices"; + + /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear diff --git a/core/java/android/speech/tts/BlockingAudioTrack.java b/core/java/android/speech/tts/BlockingAudioTrack.java index 47e2129..186cb49 100644 --- a/core/java/android/speech/tts/BlockingAudioTrack.java +++ b/core/java/android/speech/tts/BlockingAudioTrack.java @@ -69,7 +69,8 @@ class BlockingAudioTrack { // Need to be seen by stop() which can be called from another thread. mAudioTrack will be // set to null only after waitAndRelease(). - private volatile AudioTrack mAudioTrack; + private Object mAudioTrackLock = new Object(); + private AudioTrack mAudioTrack; private volatile boolean mStopped; BlockingAudioTrack(int streamType, int sampleRate, @@ -93,7 +94,9 @@ class BlockingAudioTrack { public boolean init() { AudioTrack track = createStreamingAudioTrack(); - mAudioTrack = track; + synchronized (mAudioTrackLock) { + mAudioTrack = track; + } if (track == null) { return false; @@ -103,24 +106,34 @@ class BlockingAudioTrack { } public void stop() { - AudioTrack track = mAudioTrack; - if (track != null) { - track.stop(); + synchronized (mAudioTrackLock) { + if (mAudioTrack != null) { + mAudioTrack.stop(); + } + mStopped = true; } - mStopped = true; } public int write(byte[] data) { - if (mAudioTrack == null || mStopped) { + AudioTrack track = null; + synchronized (mAudioTrackLock) { + track = mAudioTrack; + } + + if (track == null || mStopped) { return -1; } - final int bytesWritten = writeToAudioTrack(mAudioTrack, data); + final int bytesWritten = writeToAudioTrack(track, data); + mBytesWritten += bytesWritten; return bytesWritten; } public void waitAndRelease() { - AudioTrack track = mAudioTrack; + AudioTrack track = null; + synchronized (mAudioTrackLock) { + track = mAudioTrack; + } if (track == null) { if (DBG) Log.d(TAG, "Audio track null [duplicate call to waitAndRelease ?]"); return; @@ -152,8 +165,10 @@ class BlockingAudioTrack { // all data from the audioTrack has been sent to the mixer, so // it's safe to release at this point. if (DBG) Log.d(TAG, "Releasing audio track [" + track.hashCode() + "]"); + synchronized(mAudioTrackLock) { + mAudioTrack = null; + } track.release(); - mAudioTrack = null; } diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 392d1f2..6848606 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -17,6 +17,7 @@ package android.view; import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -166,8 +167,7 @@ public final class Choreographer { mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null; mLastFrameTimeNanos = Long.MIN_VALUE; - Display d = DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); - mFrameIntervalNanos = (long)(1000000000 / d.getRefreshRate()); + mFrameIntervalNanos = (long)(1000000000 / getRefreshRate()); mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; for (int i = 0; i <= CALLBACK_LAST; i++) { @@ -175,6 +175,12 @@ public final class Choreographer { } } + private static float getRefreshRate() { + DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo( + Display.DEFAULT_DISPLAY); + return di.refreshRate; + } + /** * Gets the choreographer for the calling thread. Must be called from * a thread that already has a {@link android.os.Looper} associated with it. diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 6f8ca13..ec635a2 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -19,7 +19,7 @@ package android.view; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; -import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; import android.os.SystemClock; import android.util.DisplayMetrics; import android.util.Log; @@ -49,10 +49,14 @@ import android.util.Log; */ public final class Display { private static final String TAG = "Display"; + private static final boolean DEBUG = false; + private final DisplayManagerGlobal mGlobal; private final int mDisplayId; private final CompatibilityInfoHolder mCompatibilityInfo; - private final DisplayInfo mDisplayInfo = new DisplayInfo(); + + private DisplayInfo mDisplayInfo; // never null + private boolean mIsValid; // Temporary display metrics structure used for compatibility mode. private final DisplayMetrics mTempMetrics = new DisplayMetrics(); @@ -80,9 +84,14 @@ public final class Display { * * @hide */ - public Display(int displayId, CompatibilityInfoHolder compatibilityInfo) { + public Display(DisplayManagerGlobal global, + int displayId, DisplayInfo displayInfo /*not null*/, + CompatibilityInfoHolder compatibilityInfo) { + mGlobal = global; mDisplayId = displayId; + mDisplayInfo = displayInfo; mCompatibilityInfo = compatibilityInfo; + mIsValid = true; } /** @@ -97,15 +106,37 @@ public final class Display { } /** + * Returns true if this display is still valid, false if the display has been removed. + * + * If the display is invalid, then the methods of this class will + * continue to report the most recently observed display information. + * However, it is unwise (and rather fruitless) to continue using a + * {@link Display} object after the display's demise. + * + * It's possible for a display that was previously invalid to become + * valid again if a display with the same id is reconnected. + * + * @return True if the display is still valid. + */ + public boolean isValid() { + synchronized (this) { + updateDisplayInfoLocked(); + return mIsValid; + } + } + + /** * Gets a full copy of the display information. * * @param outDisplayInfo The object to receive the copy of the display information. + * @return True if the display is still valid. * @hide */ - public void getDisplayInfo(DisplayInfo outDisplayInfo) { + public boolean getDisplayInfo(DisplayInfo outDisplayInfo) { synchronized (this) { updateDisplayInfoLocked(); outDisplayInfo.copyFrom(mDisplayInfo); + return mIsValid; } } @@ -366,9 +397,25 @@ public final class Display { } private void updateDisplayInfoLocked() { - // TODO: only refresh the display information when needed - if (!DisplayManager.getInstance().getDisplayInfo(mDisplayId, mDisplayInfo)) { - Log.e(TAG, "Could not get information about logical display " + mDisplayId); + // Note: The display manager caches display info objects on our behalf. + DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId); + if (newInfo == null) { + // Preserve the old mDisplayInfo after the display is removed. + if (mIsValid) { + mIsValid = false; + if (DEBUG) { + Log.d(TAG, "Logical display " + mDisplayId + " was removed."); + } + } + } else { + // Use the new display info. (It might be the same object if nothing changed.) + mDisplayInfo = newInfo; + if (!mIsValid) { + mIsValid = true; + if (DEBUG) { + Log.d(TAG, "Logical display " + mDisplayId + " was recreated."); + } + } } } @@ -390,7 +437,7 @@ public final class Display { updateDisplayInfoLocked(); mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo); return "Display id " + mDisplayId + ": " + mDisplayInfo - + ", " + mTempMetrics; + + ", " + mTempMetrics + ", isValid=" + mIsValid; } } } diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java index 6c2e540..0b138c2 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -66,7 +66,7 @@ public abstract class DisplayEventReceiver { @Override protected void finalize() throws Throwable { try { - dispose(); + dispose(true); } finally { super.finalize(); } @@ -76,9 +76,17 @@ public abstract class DisplayEventReceiver { * Disposes the receiver. */ public void dispose() { + dispose(false); + } + + private void dispose(boolean finalized) { if (mCloseGuard != null) { + if (finalized) { + mCloseGuard.warnIfOpen(); + } mCloseGuard.close(); } + if (mReceiverPtr != 0) { nativeDispose(mReceiverPtr); mReceiverPtr = 0; diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index e38f245..593e8c4 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -138,6 +138,10 @@ public final class DisplayInfo implements Parcelable { public DisplayInfo() { } + public DisplayInfo(DisplayInfo other) { + copyFrom(other); + } + private DisplayInfo(Parcel source) { readFromParcel(source); } diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 0114a41..23337f0 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -226,17 +226,12 @@ public class GestureDetector { */ private boolean mIsDoubleTapping; - private float mLastMotionY; - private float mLastMotionX; + private float mLastFocusX; + private float mLastFocusY; + private float mDownFocusX; + private float mDownFocusY; private boolean mIsLongpressEnabled; - - /** - * True if we are at a target API level of >= Froyo or the developer can - * explicitly set it. If true, input events with > 1 pointer will be ignored - * so we can work side by side with multitouch gesture detectors. - */ - private boolean mIgnoreMultitouch; /** * Determines speed during touch scrolling @@ -349,8 +344,16 @@ public class GestureDetector { * @throws NullPointerException if {@code listener} is null. */ public GestureDetector(Context context, OnGestureListener listener, Handler handler) { - this(context, listener, handler, context != null && - context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO); + if (handler != null) { + mHandler = new GestureHandler(handler); + } else { + mHandler = new GestureHandler(); + } + mListener = listener; + if (listener instanceof OnDoubleTapListener) { + setOnDoubleTapListener((OnDoubleTapListener) listener); + } + init(context); } /** @@ -362,31 +365,19 @@ public class GestureDetector { * @param listener the listener invoked for all the callbacks, this must * not be null. * @param handler the handler to use - * @param ignoreMultitouch whether events involving more than one pointer should - * be ignored. * * @throws NullPointerException if {@code listener} is null. */ public GestureDetector(Context context, OnGestureListener listener, Handler handler, - boolean ignoreMultitouch) { - if (handler != null) { - mHandler = new GestureHandler(handler); - } else { - mHandler = new GestureHandler(); - } - mListener = listener; - if (listener instanceof OnDoubleTapListener) { - setOnDoubleTapListener((OnDoubleTapListener) listener); - } - init(context, ignoreMultitouch); + boolean unused) { + this(context, listener, handler); } - private void init(Context context, boolean ignoreMultitouch) { + private void init(Context context) { if (mListener == null) { throw new NullPointerException("OnGestureListener must not be null"); } mIsLongpressEnabled = true; - mIgnoreMultitouch = ignoreMultitouch; // Fallback to support pre-donuts releases int touchSlop, doubleTapSlop, doubleTapTouchSlop; @@ -456,34 +447,40 @@ public class GestureDetector { } final int action = ev.getAction(); - final float y = ev.getY(); - final float x = ev.getX(); if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); + final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP; + final int skipIndex = pointerUp ? ev.getActionIndex() : -1; + + // Determine focal point + float sumX = 0, sumY = 0; + final int count = ev.getPointerCount(); + for (int i = 0; i < count; i++) { + if (skipIndex == i) continue; + sumX += ev.getX(i); + sumY += ev.getY(i); + } + final int div = pointerUp ? count - 1 : count; + final float focusX = sumX / div; + final float focusY = sumY / div; + boolean handled = false; switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_POINTER_DOWN: - if (mIgnoreMultitouch) { - // Multitouch event - abort. - cancel(); - } + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; + // Cancel long press and taps + cancelTaps(); break; case MotionEvent.ACTION_POINTER_UP: - // Ending a multitouch gesture and going back to 1 finger - if (mIgnoreMultitouch && ev.getPointerCount() == 2) { - int index = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK) - >> MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) ? 1 : 0; - mLastMotionX = ev.getX(index); - mLastMotionY = ev.getY(index); - mVelocityTracker.recycle(); - mVelocityTracker = VelocityTracker.obtain(); - } + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; break; case MotionEvent.ACTION_DOWN: @@ -504,8 +501,8 @@ public class GestureDetector { } } - mLastMotionX = x; - mLastMotionY = y; + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; if (mCurrentDownEvent != null) { mCurrentDownEvent.recycle(); } @@ -525,22 +522,22 @@ public class GestureDetector { break; case MotionEvent.ACTION_MOVE: - if (mInLongPress || (mIgnoreMultitouch && ev.getPointerCount() > 1)) { + if (mInLongPress) { break; } - final float scrollX = mLastMotionX - x; - final float scrollY = mLastMotionY - y; + final float scrollX = mLastFocusX - focusX; + final float scrollY = mLastFocusY - focusY; if (mIsDoubleTapping) { // Give the move events of the double-tap handled |= mDoubleTapListener.onDoubleTapEvent(ev); } else if (mAlwaysInTapRegion) { - final int deltaX = (int) (x - mCurrentDownEvent.getX()); - final int deltaY = (int) (y - mCurrentDownEvent.getY()); + final int deltaX = (int) (focusX - mDownFocusX); + final int deltaY = (int) (focusY - mDownFocusY); int distance = (deltaX * deltaX) + (deltaY * deltaY); if (distance > mTouchSlopSquare) { handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); - mLastMotionX = x; - mLastMotionY = y; + mLastFocusX = focusX; + mLastFocusY = focusY; mAlwaysInTapRegion = false; mHandler.removeMessages(TAP); mHandler.removeMessages(SHOW_PRESS); @@ -551,8 +548,8 @@ public class GestureDetector { } } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); - mLastMotionX = x; - mLastMotionY = y; + mLastFocusX = focusX; + mLastFocusY = focusY; } break; @@ -571,9 +568,10 @@ public class GestureDetector { // A fling must travel the minimum tap distance final VelocityTracker velocityTracker = mVelocityTracker; + final int pointerId = ev.getPointerId(0); velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); - final float velocityY = velocityTracker.getYVelocity(); - final float velocityX = velocityTracker.getXVelocity(); + final float velocityY = velocityTracker.getYVelocity(pointerId); + final float velocityX = velocityTracker.getXVelocity(pointerId); if ((Math.abs(velocityY) > mMinimumFlingVelocity) || (Math.abs(velocityX) > mMinimumFlingVelocity)){ @@ -622,6 +620,18 @@ public class GestureDetector { } } + private void cancelTaps() { + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + mHandler.removeMessages(TAP); + mIsDoubleTapping = false; + mAlwaysInTapRegion = false; + mAlwaysInBiggerTapRegion = false; + if (mInLongPress) { + mInLongPress = false; + } + } + private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, MotionEvent secondDown) { if (!mAlwaysInBiggerTapRegion) { diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java index 9c56782..117c101 100644 --- a/core/java/android/view/InputEventReceiver.java +++ b/core/java/android/view/InputEventReceiver.java @@ -73,7 +73,7 @@ public abstract class InputEventReceiver { @Override protected void finalize() throws Throwable { try { - dispose(); + dispose(true); } finally { super.finalize(); } @@ -83,9 +83,17 @@ public abstract class InputEventReceiver { * Disposes the receiver. */ public void dispose() { + dispose(false); + } + + private void dispose(boolean finalized) { if (mCloseGuard != null) { + if (finalized) { + mCloseGuard.warnIfOpen(); + } mCloseGuard.close(); } + if (mReceiverPtr != 0) { nativeDispose(mReceiverPtr); mReceiverPtr = 0; diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java index 73f94bc..dc36088 100644 --- a/core/java/android/view/ScaleGestureDetector.java +++ b/core/java/android/view/ScaleGestureDetector.java @@ -17,14 +17,13 @@ package android.view; import android.content.Context; -import android.util.DisplayMetrics; import android.util.FloatMath; -import android.util.Log; /** - * Detects transformation gestures involving more than one pointer ("multitouch") - * using the supplied {@link MotionEvent}s. The {@link OnScaleGestureListener} - * callback will notify users when a particular gesture event has occurred. + * Detects scaling transformation gestures using the supplied {@link MotionEvent}s. + * The {@link OnScaleGestureListener} callback will notify users when a particular + * gesture event has occurred. + * * This class should only be used with {@link MotionEvent}s reported via touch. * * To use this class: @@ -121,43 +120,21 @@ public class ScaleGestureDetector { } } - /** - * This value is the threshold ratio between our previous combined pressure - * and the current combined pressure. We will only fire an onScale event if - * the computed ratio between the current and previous event pressures is - * greater than this value. When pressure decreases rapidly between events - * the position values can often be imprecise, as it usually indicates - * that the user is in the process of lifting a pointer off of the device. - * Its value was tuned experimentally. - */ - private static final float PRESSURE_THRESHOLD = 0.67f; - private final Context mContext; private final OnScaleGestureListener mListener; - private boolean mGestureInProgress; - - private MotionEvent mPrevEvent; - private MotionEvent mCurrEvent; private float mFocusX; private float mFocusY; - private float mPrevFingerDiffX; - private float mPrevFingerDiffY; - private float mCurrFingerDiffX; - private float mCurrFingerDiffY; - private float mCurrLen; - private float mPrevLen; - private float mScaleFactor; - private float mCurrPressure; - private float mPrevPressure; - private long mTimeDelta; - - private boolean mInvalidGesture; - - // Pointer IDs currently responsible for the two fingers controlling the gesture - private int mActiveId0; - private int mActiveId1; - private boolean mActive0MostRecent; + + private float mCurrSpan; + private float mPrevSpan; + private float mCurrSpanX; + private float mCurrSpanY; + private float mPrevSpanX; + private float mPrevSpanY; + private long mCurrTime; + private long mPrevTime; + private boolean mInProgress; /** * Consistency verifier for debugging purposes. @@ -171,6 +148,18 @@ public class ScaleGestureDetector { mListener = listener; } + /** + * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener} + * when appropriate. + * + * <p>Applications should pass a complete and consistent event stream to this method. + * A complete and consistent event stream involves all MotionEvents from the initial + * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p> + * + * @param event The event to process + * @return true if the event was processed and the detector wants to receive the + * rest of the MotionEvents in this event stream. + */ public boolean onTouchEvent(MotionEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); @@ -178,265 +167,110 @@ public class ScaleGestureDetector { final int action = event.getActionMasked(); - if (action == MotionEvent.ACTION_DOWN) { - reset(); // Start fresh - } - - boolean handled = true; - if (mInvalidGesture) { - handled = false; - } else if (!mGestureInProgress) { - switch (action) { - case MotionEvent.ACTION_DOWN: { - mActiveId0 = event.getPointerId(0); - mActive0MostRecent = true; - } - break; - - case MotionEvent.ACTION_UP: - reset(); - break; - - case MotionEvent.ACTION_POINTER_DOWN: { - // We have a new multi-finger gesture - if (mPrevEvent != null) mPrevEvent.recycle(); - mPrevEvent = MotionEvent.obtain(event); - mTimeDelta = 0; - - int index1 = event.getActionIndex(); - int index0 = event.findPointerIndex(mActiveId0); - mActiveId1 = event.getPointerId(index1); - if (index0 < 0 || index0 == index1) { - // Probably someone sending us a broken event stream. - index0 = findNewActiveIndex(event, mActiveId1, -1); - mActiveId0 = event.getPointerId(index0); - } - mActive0MostRecent = false; - - setContext(event); - - mGestureInProgress = mListener.onScaleBegin(this); - break; - } + final boolean streamComplete = action == MotionEvent.ACTION_UP || + action == MotionEvent.ACTION_CANCEL; + if (action == MotionEvent.ACTION_DOWN || streamComplete) { + // Reset any scale in progress with the listener. + // If it's an ACTION_DOWN we're beginning a new event stream. + // This means the app probably didn't give us all the events. Shame on it. + if (mInProgress) { + mListener.onScaleEnd(this); + mInProgress = false; } - } else { - // Transform gesture in progress - attempt to handle it - switch (action) { - case MotionEvent.ACTION_POINTER_DOWN: { - // End the old gesture and begin a new one with the most recent two fingers. - mListener.onScaleEnd(this); - final int oldActive0 = mActiveId0; - final int oldActive1 = mActiveId1; - reset(); - - mPrevEvent = MotionEvent.obtain(event); - mActiveId0 = mActive0MostRecent ? oldActive0 : oldActive1; - mActiveId1 = event.getPointerId(event.getActionIndex()); - mActive0MostRecent = false; - - int index0 = event.findPointerIndex(mActiveId0); - if (index0 < 0 || mActiveId0 == mActiveId1) { - // Probably someone sending us a broken event stream. - Log.e(TAG, "Got " + MotionEvent.actionToString(action) + - " with bad state while a gesture was in progress. " + - "Did you forget to pass an event to " + - "ScaleGestureDetector#onTouchEvent?"); - index0 = findNewActiveIndex(event, mActiveId1, -1); - mActiveId0 = event.getPointerId(index0); - } - - setContext(event); - - mGestureInProgress = mListener.onScaleBegin(this); - } - break; - - case MotionEvent.ACTION_POINTER_UP: { - final int pointerCount = event.getPointerCount(); - final int actionIndex = event.getActionIndex(); - final int actionId = event.getPointerId(actionIndex); - - boolean gestureEnded = false; - if (pointerCount > 2) { - if (actionId == mActiveId0) { - final int newIndex = findNewActiveIndex(event, mActiveId1, actionIndex); - if (newIndex >= 0) { - mListener.onScaleEnd(this); - mActiveId0 = event.getPointerId(newIndex); - mActive0MostRecent = true; - mPrevEvent = MotionEvent.obtain(event); - setContext(event); - mGestureInProgress = mListener.onScaleBegin(this); - } else { - gestureEnded = true; - } - } else if (actionId == mActiveId1) { - final int newIndex = findNewActiveIndex(event, mActiveId0, actionIndex); - if (newIndex >= 0) { - mListener.onScaleEnd(this); - mActiveId1 = event.getPointerId(newIndex); - mActive0MostRecent = false; - mPrevEvent = MotionEvent.obtain(event); - setContext(event); - mGestureInProgress = mListener.onScaleBegin(this); - } else { - gestureEnded = true; - } - } - mPrevEvent.recycle(); - mPrevEvent = MotionEvent.obtain(event); - setContext(event); - } else { - gestureEnded = true; - } - - if (gestureEnded) { - // Gesture ended - setContext(event); - - // Set focus point to the remaining finger - final int activeId = actionId == mActiveId0 ? mActiveId1 : mActiveId0; - final int index = event.findPointerIndex(activeId); - mFocusX = event.getX(index); - mFocusY = event.getY(index); - - mListener.onScaleEnd(this); - reset(); - mActiveId0 = activeId; - mActive0MostRecent = true; - } - } - break; - - case MotionEvent.ACTION_CANCEL: - mListener.onScaleEnd(this); - reset(); - break; - - case MotionEvent.ACTION_UP: - reset(); - break; - - case MotionEvent.ACTION_MOVE: { - setContext(event); - - // Only accept the event if our relative pressure is within - // a certain limit - this can help filter shaky data as a - // finger is lifted. - if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) { - final boolean updatePrevious = mListener.onScale(this); - - if (updatePrevious) { - mPrevEvent.recycle(); - mPrevEvent = MotionEvent.obtain(event); - } - } - } - break; + + if (streamComplete) { + return true; } } - if (!handled && mInputEventConsistencyVerifier != null) { - mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); + final boolean configChanged = + action == MotionEvent.ACTION_POINTER_UP || + action == MotionEvent.ACTION_POINTER_DOWN; + final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP; + final int skipIndex = pointerUp ? event.getActionIndex() : -1; + + // Determine focal point + float sumX = 0, sumY = 0; + final int count = event.getPointerCount(); + for (int i = 0; i < count; i++) { + if (skipIndex == i) continue; + sumX += event.getX(i); + sumY += event.getY(i); } - return handled; - } - - private int findNewActiveIndex(MotionEvent ev, int otherActiveId, int removedPointerIndex) { - final int pointerCount = ev.getPointerCount(); - - // It's ok if this isn't found and returns -1, it simply won't match. - final int otherActiveIndex = ev.findPointerIndex(otherActiveId); - - // Pick a new id and update tracking state. - for (int i = 0; i < pointerCount; i++) { - if (i != removedPointerIndex && i != otherActiveIndex) { - return i; - } + final int div = pointerUp ? count - 1 : count; + final float focusX = sumX / div; + final float focusY = sumY / div; + + // Determine average deviation from focal point + float devSumX = 0, devSumY = 0; + for (int i = 0; i < count; i++) { + if (skipIndex == i) continue; + devSumX += Math.abs(event.getX(i) - focusX); + devSumY += Math.abs(event.getY(i) - focusY); } - return -1; - } - - private void setContext(MotionEvent curr) { - if (mCurrEvent != null) { - mCurrEvent.recycle(); + final float devX = devSumX / div; + final float devY = devSumY / div; + + // Span is the average distance between touch points through the focal point; + // i.e. the diameter of the circle with a radius of the average deviation from + // the focal point. + final float spanX = devX * 2; + final float spanY = devY * 2; + final float span = FloatMath.sqrt(spanX * spanX + spanY * spanY); + + // Dispatch begin/end events as needed. + // If the configuration changes, notify the app to reset its current state by beginning + // a fresh scale event stream. + if (mInProgress && (span == 0 || configChanged)) { + mListener.onScaleEnd(this); + mInProgress = false; + } + if (configChanged) { + mPrevSpanX = mCurrSpanX = spanX; + mPrevSpanY = mCurrSpanY = spanY; + mPrevSpan = mCurrSpan = span; + } + if (!mInProgress && span != 0) { + mFocusX = focusX; + mFocusY = focusY; + mInProgress = mListener.onScaleBegin(this); } - mCurrEvent = MotionEvent.obtain(curr); - - mCurrLen = -1; - mPrevLen = -1; - mScaleFactor = -1; - - final MotionEvent prev = mPrevEvent; - final int prevIndex0 = prev.findPointerIndex(mActiveId0); - final int prevIndex1 = prev.findPointerIndex(mActiveId1); - final int currIndex0 = curr.findPointerIndex(mActiveId0); - final int currIndex1 = curr.findPointerIndex(mActiveId1); + // Handle motion; focal point and span/scale factor are changing. + if (action == MotionEvent.ACTION_MOVE) { + mCurrSpanX = spanX; + mCurrSpanY = spanY; + mCurrSpan = span; + mFocusX = focusX; + mFocusY = focusY; + + boolean updatePrev = true; + if (mInProgress) { + updatePrev = mListener.onScale(this); + } - if (prevIndex0 < 0 || prevIndex1 < 0 || currIndex0 < 0 || currIndex1 < 0) { - mInvalidGesture = true; - Log.e(TAG, "Invalid MotionEvent stream detected.", new Throwable()); - if (mGestureInProgress) { - mListener.onScaleEnd(this); + if (updatePrev) { + mPrevSpanX = mCurrSpanX; + mPrevSpanY = mCurrSpanY; + mPrevSpan = mCurrSpan; } - return; } - final float px0 = prev.getX(prevIndex0); - final float py0 = prev.getY(prevIndex0); - final float px1 = prev.getX(prevIndex1); - final float py1 = prev.getY(prevIndex1); - final float cx0 = curr.getX(currIndex0); - final float cy0 = curr.getY(currIndex0); - final float cx1 = curr.getX(currIndex1); - final float cy1 = curr.getY(currIndex1); - - final float pvx = px1 - px0; - final float pvy = py1 - py0; - final float cvx = cx1 - cx0; - final float cvy = cy1 - cy0; - mPrevFingerDiffX = pvx; - mPrevFingerDiffY = pvy; - mCurrFingerDiffX = cvx; - mCurrFingerDiffY = cvy; - - mFocusX = cx0 + cvx * 0.5f; - mFocusY = cy0 + cvy * 0.5f; - mTimeDelta = curr.getEventTime() - prev.getEventTime(); - mCurrPressure = curr.getPressure(currIndex0) + curr.getPressure(currIndex1); - mPrevPressure = prev.getPressure(prevIndex0) + prev.getPressure(prevIndex1); - } - - private void reset() { - if (mPrevEvent != null) { - mPrevEvent.recycle(); - mPrevEvent = null; - } - if (mCurrEvent != null) { - mCurrEvent.recycle(); - mCurrEvent = null; - } - mGestureInProgress = false; - mActiveId0 = -1; - mActiveId1 = -1; - mInvalidGesture = false; + return true; } /** - * Returns {@code true} if a two-finger scale gesture is in progress. - * @return {@code true} if a scale gesture is in progress, {@code false} otherwise. + * Returns {@code true} if a scale gesture is in progress. */ public boolean isInProgress() { - return mGestureInProgress; + return mInProgress; } /** * Get the X coordinate of the current gesture's focal point. - * If a gesture is in progress, the focal point is directly between - * the two pointers forming the gesture. - * If a gesture is ending, the focal point is the location of the - * remaining pointer on the screen. + * If a gesture is in progress, the focal point is between + * each of the pointers forming the gesture. + * * If {@link #isInProgress()} would return false, the result of this * function is undefined. * @@ -448,10 +282,9 @@ public class ScaleGestureDetector { /** * Get the Y coordinate of the current gesture's focal point. - * If a gesture is in progress, the focal point is directly between - * the two pointers forming the gesture. - * If a gesture is ending, the focal point is the location of the - * remaining pointer on the screen. + * If a gesture is in progress, the focal point is between + * each of the pointers forming the gesture. + * * If {@link #isInProgress()} would return false, the result of this * function is undefined. * @@ -462,73 +295,63 @@ public class ScaleGestureDetector { } /** - * Return the current distance between the two pointers forming the - * gesture in progress. + * Return the average distance between each of the pointers forming the + * gesture in progress through the focal point. * * @return Distance between pointers in pixels. */ public float getCurrentSpan() { - if (mCurrLen == -1) { - final float cvx = mCurrFingerDiffX; - final float cvy = mCurrFingerDiffY; - mCurrLen = FloatMath.sqrt(cvx*cvx + cvy*cvy); - } - return mCurrLen; + return mCurrSpan; } /** - * Return the current x distance between the two pointers forming the - * gesture in progress. + * Return the average X distance between each of the pointers forming the + * gesture in progress through the focal point. * * @return Distance between pointers in pixels. */ public float getCurrentSpanX() { - return mCurrFingerDiffX; + return mCurrSpanX; } /** - * Return the current y distance between the two pointers forming the - * gesture in progress. + * Return the average Y distance between each of the pointers forming the + * gesture in progress through the focal point. * * @return Distance between pointers in pixels. */ public float getCurrentSpanY() { - return mCurrFingerDiffY; + return mCurrSpanY; } /** - * Return the previous distance between the two pointers forming the - * gesture in progress. + * Return the previous average distance between each of the pointers forming the + * gesture in progress through the focal point. * * @return Previous distance between pointers in pixels. */ public float getPreviousSpan() { - if (mPrevLen == -1) { - final float pvx = mPrevFingerDiffX; - final float pvy = mPrevFingerDiffY; - mPrevLen = FloatMath.sqrt(pvx*pvx + pvy*pvy); - } - return mPrevLen; + return mPrevSpan; } /** - * Return the previous x distance between the two pointers forming the - * gesture in progress. + * Return the previous average X distance between each of the pointers forming the + * gesture in progress through the focal point. * * @return Previous distance between pointers in pixels. */ public float getPreviousSpanX() { - return mPrevFingerDiffX; + return mPrevSpanX; } /** - * Return the previous y distance between the two pointers forming the - * gesture in progress. + * Return the previous average Y distance between each of the pointers forming the + * gesture in progress through the focal point. * * @return Previous distance between pointers in pixels. */ public float getPreviousSpanY() { - return mPrevFingerDiffY; + return mPrevSpanY; } /** @@ -539,10 +362,7 @@ public class ScaleGestureDetector { * @return The current scaling factor. */ public float getScaleFactor() { - if (mScaleFactor == -1) { - mScaleFactor = getCurrentSpan() / getPreviousSpan(); - } - return mScaleFactor; + return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1; } /** @@ -552,7 +372,7 @@ public class ScaleGestureDetector { * @return Time difference since the last scaling event in milliseconds. */ public long getTimeDelta() { - return mTimeDelta; + return mCurrTime - mPrevTime; } /** @@ -561,6 +381,6 @@ public class ScaleGestureDetector { * @return Current event time in milliseconds. */ public long getEventTime() { - return mCurrEvent.getEventTime(); + return mCurrTime; } } diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index a6d1a3f..cf1767d 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -16,8 +16,16 @@ package android.view; +import dalvik.system.CloseGuard; + import android.content.res.CompatibilityInfo.Translator; -import android.graphics.*; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.SurfaceTexture; +import android.os.IBinder; import android.os.Parcelable; import android.os.Parcel; import android.os.SystemProperties; @@ -27,206 +35,187 @@ import android.util.Log; * Handle onto a raw buffer that is being managed by the screen compositor. */ public class Surface implements Parcelable { - private static final String LOG_TAG = "Surface"; - private static final boolean DEBUG_RELEASE = false; - - /* orientations for setOrientation() */ - public static final int ROTATION_0 = 0; - public static final int ROTATION_90 = 1; - public static final int ROTATION_180 = 2; - public static final int ROTATION_270 = 3; + private static final String TAG = "Surface"; - private static final boolean headless = "1".equals( + private static final boolean HEADLESS = "1".equals( SystemProperties.get("ro.config.headless", "0")); - private static void checkHeadless() { - if(headless) { - throw new UnsupportedOperationException("Device is headless"); + public static final Parcelable.Creator<Surface> CREATOR = + new Parcelable.Creator<Surface>() { + public Surface createFromParcel(Parcel source) { + try { + Surface s = new Surface(); + s.readFromParcel(source); + return s; + } catch (Exception e) { + Log.e(TAG, "Exception creating surface from parcel", e); + return null; + } } - } - /** - * Create Surface from a {@link SurfaceTexture}. - * - * Images drawn to the Surface will be made available to the {@link - * SurfaceTexture}, which can attach them an OpenGL ES texture via {@link - * SurfaceTexture#updateTexImage}. - * - * @param surfaceTexture The {@link SurfaceTexture} that is updated by this - * Surface. - */ - public Surface(SurfaceTexture surfaceTexture) { - checkHeadless(); - - if (DEBUG_RELEASE) { - mCreationStack = new Exception(); + public Surface[] newArray(int size) { + return new Surface[size]; } - mCanvas = new CompatibleCanvas(); - initFromSurfaceTexture(surfaceTexture); - } + }; /** - * Does this object hold a valid surface? Returns true if it holds - * a physical surface, so lockCanvas() will succeed. Otherwise - * returns false. + * Rotation constant: 0 degree rotation (natural orientation) */ - public native boolean isValid(); + public static final int ROTATION_0 = 0; - /** Release the local reference to the server-side surface. - * Always call release() when you're done with a Surface. This will - * make the surface invalid. + /** + * Rotation constant: 90 degree rotation. */ - public native void release(); + public static final int ROTATION_90 = 1; - /** draw into a surface */ - public Canvas lockCanvas(Rect dirty) throws OutOfResourcesException, IllegalArgumentException { - /* - * the dirty rectangle may be expanded to the surface's size, if for - * instance it has been resized or if the bits were lost, since the last - * call. - */ - return lockCanvasNative(dirty); - } - - /** unlock the surface and asks a page flip */ - public native void unlockCanvasAndPost(Canvas canvas); - - /** - * unlock the surface. the screen won't be updated until - * post() or postAll() is called + /** + * Rotation constant: 180 degree rotation. */ - public native void unlockCanvas(Canvas canvas); + public static final int ROTATION_180 = 2; - @Override - public String toString() { - return "Surface(name=" + mName + ", identity=" + getIdentity() + ")"; - } - - public int describeContents() { - return 0; - } + /** + * Rotation constant: 270 degree rotation. + */ + public static final int ROTATION_270 = 3; - public native void readFromParcel(Parcel source); - public native void writeToParcel(Parcel dest, int flags); + /* built-in physical display ids (keep in sync with ISurfaceComposer.h) + * these are different from the logical display ids used elsewhere in the framework */ /** - * Exception thrown when a surface couldn't be created or resized + * Built-in physical display id: Main display. + * Use only with {@link #getBuiltInDisplay()}. + * @hide */ - public static class OutOfResourcesException extends Exception { - public OutOfResourcesException() { - } - public OutOfResourcesException(String name) { - super(name); - } - } - - /* - * ----------------------------------------------------------------------- - * No user serviceable parts beyond this point - * ----------------------------------------------------------------------- + public static final int BUILT_IN_DISPLAY_ID_MAIN = 0; + + /** + * Built-in physical display id: Attached HDMI display. + * Use only with {@link #getBuiltInDisplay()}. + * @hide */ + public static final int BUILT_IN_DISPLAY_ID_HDMI = 1; - /* flags used in constructor (keep in sync with ISurfaceComposer.h) */ + /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */ - /** Surface is created hidden @hide */ - public static final int HIDDEN = 0x00000004; + /** + * Surface creation flag: Surface is created hidden + * @hide */ + public static final int HIDDEN = 0x00000004; - /** The surface contains secure content, special measures will - * be taken to disallow the surface's content to be copied from - * another process. In particular, screenshots and VNC servers will + /** + * Surface creation flag: The surface contains secure content, special + * measures will be taken to disallow the surface's content to be copied + * from another process. In particular, screenshots and VNC servers will * be disabled, but other measures can take place, for instance the * surface might not be hardware accelerated. - * @hide*/ - public static final int SECURE = 0x00000080; - - /** Creates a surface where color components are interpreted as - * "non pre-multiplied" by their alpha channel. Of course this flag is - * meaningless for surfaces without an alpha channel. By default - * surfaces are pre-multiplied, which means that each color component is - * already multiplied by its alpha value. In this case the blending - * equation used is: - * + * @hide + */ + public static final int SECURE = 0x00000080; + + /** + * Surface creation flag: Creates a surface where color components are interpreted + * as "non pre-multiplied" by their alpha channel. Of course this flag is + * meaningless for surfaces without an alpha channel. By default + * surfaces are pre-multiplied, which means that each color component is + * already multiplied by its alpha value. In this case the blending + * equation used is: + * * DEST = SRC + DEST * (1-SRC_ALPHA) - * - * By contrast, non pre-multiplied surfaces use the following equation: - * + * + * By contrast, non pre-multiplied surfaces use the following equation: + * * DEST = SRC * SRC_ALPHA * DEST * (1-SRC_ALPHA) - * - * pre-multiplied surfaces must always be used if transparent pixels are - * composited on top of each-other into the surface. A pre-multiplied - * surface can never lower the value of the alpha component of a given - * pixel. - * - * In some rare situations, a non pre-multiplied surface is preferable. - * - * @hide - */ - public static final int NON_PREMULTIPLIED = 0x00000100; - + * + * pre-multiplied surfaces must always be used if transparent pixels are + * composited on top of each-other into the surface. A pre-multiplied + * surface can never lower the value of the alpha component of a given + * pixel. + * + * In some rare situations, a non pre-multiplied surface is preferable. + * @hide + */ + public static final int NON_PREMULTIPLIED = 0x00000100; + /** - * Indicates that the surface must be considered opaque, even if its - * pixel format is set to translucent. This can be useful if an + * Surface creation flag: Indicates that the surface must be considered opaque, + * even if its pixel format is set to translucent. This can be useful if an * application needs full RGBA 8888 support for instance but will * still draw every pixel opaque. - * * @hide */ - public static final int OPAQUE = 0x00000400; - + public static final int OPAQUE = 0x00000400; + /** - * Application requires a hardware-protected path to an + * Surface creation flag: Application requires a hardware-protected path to an * external display sink. If a hardware-protected path is not available, * then this surface will not be displayed on the external sink. - * * @hide */ - public static final int PROTECTED_APP = 0x00000800; + public static final int PROTECTED_APP = 0x00000800; // 0x1000 is reserved for an independent DRM protected flag in framework - /** Creates a normal surface. This is the default. @hide */ + /** + * Surface creation flag: Creates a normal surface. + * This is the default. + * @hide + */ public static final int FX_SURFACE_NORMAL = 0x00000000; - - /** Creates a Blur surface. Everything behind this surface is blurred - * by some amount. The quality and refresh speed of the blur effect - * is not settable or guaranteed. - * It is an error to lock a Blur surface, since it doesn't have - * a backing store. + + /** + * Surface creation flag: Creates a Blur surface. + * Everything behind this surface is blurred by some amount. + * The quality and refresh speed of the blur effect is not settable or guaranteed. + * It is an error to lock a Blur surface, since it doesn't have a backing store. * @hide * @deprecated */ @Deprecated - public static final int FX_SURFACE_BLUR = 0x00010000; - - /** Creates a Dim surface. Everything behind this surface is dimmed - * by the amount specified in {@link #setAlpha}. - * It is an error to lock a Dim surface, since it doesn't have - * a backing store. + public static final int FX_SURFACE_BLUR = 0x00010000; + + /** + * Surface creation flag: Creates a Dim surface. + * Everything behind this surface is dimmed by the amount specified + * in {@link #setAlpha}. It is an error to lock a Dim surface, since it + * doesn't have a backing store. * @hide */ - public static final int FX_SURFACE_DIM = 0x00020000; + public static final int FX_SURFACE_DIM = 0x00020000; - /** @hide */ - public static final int FX_SURFACE_SCREENSHOT = 0x00030000; + /** + * @hide + */ + public static final int FX_SURFACE_SCREENSHOT = 0x00030000; - /** Mask used for FX values above @hide */ - public static final int FX_SURFACE_MASK = 0x000F0000; + /** + * Mask used for FX values above. + * @hide + */ + public static final int FX_SURFACE_MASK = 0x000F0000; /* flags used with setFlags() (keep in sync with ISurfaceComposer.h) */ - /** Hide the surface. Equivalent to calling hide(). @hide */ - public static final int SURFACE_HIDDEN = 0x01; + /** + * Surface flag: Hide the surface. + * Equivalent to calling hide(). + * @hide + */ + public static final int SURFACE_HIDDEN = 0x01; + + private final CloseGuard mCloseGuard = CloseGuard.get(); + private String mName; + + // Note: These fields are accessed by native code. // The mSurfaceControl will only be present for Surfaces used by the window // server or system processes. When this class is parceled we defer to the // mSurfaceControl to do the parceling. Otherwise we parcel the // mNativeSurface. - private int mSurfaceControl; - private int mSaveCount; - private Canvas mCanvas; - private int mNativeSurface; - private int mSurfaceGenerationId; - private String mName; + private int mNativeSurface; // Surface* + private int mNativeSurfaceControl; // SurfaceControl* + private int mGenerationId; // incremented each time mNativeSurface changes + private final Canvas mCanvas = new CompatibleCanvas(); + private int mCanvasSaveCount; // Canvas save count at time of lockCanvas() // The Translator for density compatibility mode. This is used for scaling // the canvas to perform the appropriate density transformation. @@ -236,141 +225,245 @@ public class Surface implements Parcelable { // non compatibility mode. private Matrix mCompatibleMatrix; - private Exception mCreationStack; + private native void nativeCreate(SurfaceSession session, String name, + int w, int h, int format, int flags) + throws OutOfResourcesException; + private native void nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture) + throws OutOfResourcesException; + private native void nativeRelease(); + private native void nativeDestroy(); + + private native boolean nativeIsValid(); + private native int nativeGetIdentity(); + private native boolean nativeIsConsumerRunningBehind(); + + private native Canvas nativeLockCanvas(Rect dirty); + private native void nativeUnlockCanvasAndPost(Canvas canvas); + + private static native Bitmap nativeScreenshot(IBinder displayToken, + int width, int height, int minLayer, int maxLayer, boolean allLayers); + + private static native void nativeOpenTransaction(); + private static native void nativeCloseTransaction(); + + private native void nativeSetLayer(int zorder); + private native void nativeSetPosition(float x, float y); + private native void nativeSetSize(int w, int h); + private native void nativeSetTransparentRegionHint(Region region); + private native void nativeSetAlpha(float alpha); + private native void nativeSetMatrix(float dsdx, float dtdx, float dsdy, float dtdy); + private native void nativeSetFlags(int flags, int mask); + private native void nativeSetWindowCrop(Rect crop); + private native void nativeSetLayerStack(int layerStack); + + private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId); + private static native IBinder nativeCreateDisplay(String name); + private static native void nativeSetDisplaySurface( + IBinder displayToken, SurfaceTexture surfaceTexture); + private static native void nativeSetDisplayLayerStack( + IBinder displayToken, int layerStack); + private static native void nativeSetDisplayOrientation( + IBinder displayToken, int orientation); + private static native void nativeSetDisplayViewport( + IBinder displayToken, Rect viewport); + private static native void nativeSetDisplayFrame( + IBinder displayToken, Rect frame); + private static native boolean nativeGetDisplayInfo( + IBinder displayToken, PhysicalDisplayInfo outInfo); + + private native void nativeCopyFrom(Surface other); + private native void nativeTransferFrom(Surface other); + private native void nativeReadFromParcel(Parcel source); + private native void nativeWriteToParcel(Parcel dest); - /* - * We use a class initializer to allow the native code to cache some - * field offsets. + /** + * Create an empty surface, which will later be filled in by readFromParcel(). + * @hide */ - native private static void nativeClassInit(); - static { nativeClassInit(); } - - /** create a surface with a name @hide */ - public Surface(SurfaceSession s, - int pid, String name, int layerStack, int w, int h, int format, int flags) - throws OutOfResourcesException { + public Surface() { checkHeadless(); - if (DEBUG_RELEASE) { - mCreationStack = new Exception(); + mCloseGuard.open("release"); + } + + /** + * Create a surface with a name. + * + * The surface creation flags specify what kind of surface to create and + * certain options such as whether the surface can be assumed to be opaque + * and whether it should be initially hidden. Surfaces should always be + * created with the {@link #HIDDEN} flag set to ensure that they are not + * made visible prematurely before all of the surface's properties have been + * configured. + * + * Good practice is to first create the surface with the {@link #HIDDEN} flag + * specified, open a transaction, set the surface layer, layer stack, alpha, + * and position, call {@link #show} if appropriate, and close the transaction. + * + * @param session The surface session, must not be null. + * @param name The surface name, must not be null. + * @param w The surface initial width. + * @param h The surface initial height. + * @param flags The surface creation flags. Should always include {@link #HIDDEN} + * in the creation flags. + * @hide + */ + public Surface(SurfaceSession session, + String name, int w, int h, int format, int flags) + throws OutOfResourcesException { + if (session == null) { + throw new IllegalArgumentException("session must not be null"); + } + if (name == null) { + throw new IllegalArgumentException("name must not be null"); + } + + if ((flags & HIDDEN) == 0) { + Log.w(TAG, "Surfaces should always be created with the HIDDEN flag set " + + "to ensure that they are not made visible prematurely before " + + "all of the surface's properties have been configured. " + + "Set the other properties and make the surface visible within " + + "a transaction. New surface name: " + name, + new Throwable()); } - mCanvas = new CompatibleCanvas(); - init(s, pid, name, layerStack, w, h, format, flags); + + checkHeadless(); + mName = name; + nativeCreate(session, name, w, h, format, flags); + + mCloseGuard.open("release"); } /** - * Create an empty surface, which will later be filled in by - * readFromParcel(). - * @hide + * Create Surface from a {@link SurfaceTexture}. + * + * Images drawn to the Surface will be made available to the {@link + * SurfaceTexture}, which can attach them to an OpenGL ES texture via {@link + * SurfaceTexture#updateTexImage}. + * + * @param surfaceTexture The {@link SurfaceTexture} that is updated by this + * Surface. */ - public Surface() { + public Surface(SurfaceTexture surfaceTexture) { + if (surfaceTexture == null) { + throw new IllegalArgumentException("surfaceTexture must not be null"); + } + checkHeadless(); - if (DEBUG_RELEASE) { - mCreationStack = new Exception(); + mName = surfaceTexture.toString(); + try { + nativeCreateFromSurfaceTexture(surfaceTexture); + } catch (OutOfResourcesException ex) { + // We can't throw OutOfResourcesException because it would be an API change. + throw new RuntimeException(ex); } - mCanvas = new CompatibleCanvas(); + + mCloseGuard.open("release"); } - private Surface(Parcel source) throws OutOfResourcesException { - init(source); + @Override + protected void finalize() throws Throwable { + try { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + nativeRelease(); + } finally { + super.finalize(); + } } /** - * Copy another surface to this one. This surface now holds a reference - * to the same data as the original surface, and is -not- the owner. - * This is for use by the window manager when returning a window surface - * back from a client, converting it from the representation being managed - * by the window manager to the representation the client uses to draw - * in to it. + * Release the local reference to the server-side surface. + * Always call release() when you're done with a Surface. + * This will make the surface invalid. + */ + public void release() { + nativeRelease(); + mCloseGuard.close(); + } + + /** + * Free all server-side state associated with this surface and + * release this object's reference. This method can only be + * called from the process that created the service. * @hide */ - public native void copyFrom(Surface o); + public void destroy() { + nativeDestroy(); + mCloseGuard.close(); + } /** - * Transfer the native state from 'o' to this surface, releasing it - * from 'o'. This is for use in the client side for drawing into a - * surface; not guaranteed to work on the window manager side. - * This is for use by the client to move the underlying surface from - * one Surface object to another, in particular in SurfaceFlinger. - * @hide. + * Returns true if this object holds a valid surface. + * + * @return True if it holds a physical surface, so lockCanvas() will succeed. + * Otherwise returns false. */ - public native void transferFrom(Surface o); + public boolean isValid() { + return nativeIsValid(); + } - /** @hide */ + /** + * Gets the generation number of this surface, incremented each time + * the native surface contained within this object changes. + * + * @return The current generation number. + * @hide + */ public int getGenerationId() { - return mSurfaceGenerationId; + return mGenerationId; } - /** - * Whether the consumer of this Surface is running behind the producer; - * that is, isConsumerRunningBehind() returns true if the consumer is more - * than one buffer ahead of the producer. + * Returns true if the consumer of this Surface is running behind the producer. + * + * @return True if the consumer is more than one buffer ahead of the producer. * @hide */ - public native boolean isConsumerRunningBehind(); + public boolean isConsumerRunningBehind() { + return nativeIsConsumerRunningBehind(); + } /** - * A Canvas class that can handle the compatibility mode. This does two - * things differently. - * <ul> - * <li>Returns the width and height of the target metrics, rather than - * native. For example, the canvas returns 320x480 even if an app is running - * in WVGA high density. - * <li>Scales the matrix in setMatrix by the application scale, except if - * the matrix looks like obtained from getMatrix. This is a hack to handle - * the case that an application uses getMatrix to keep the original matrix, - * set matrix of its own, then set the original matrix back. There is no - * perfect solution that works for all cases, and there are a lot of cases - * that this model does not work, but we hope this works for many apps. - * </ul> + * Gets a {@link Canvas} for drawing into this surface. + * + * After drawing into the provided {@link Canvas}, the caller should + * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface. + * + * @param dirty A rectangle that represents the dirty region that the caller wants + * to redraw. This function may choose to expand the dirty rectangle if for example + * the surface has been resized or if the previous contents of the surface were + * not available. The caller should redraw the entire dirty region as represented + * by the contents of the dirty rect upon return from this function. + * The caller may also pass <code>null</code> instead, in the case where the + * entire surface should be redrawn. + * @return A canvas for drawing into the surface. */ - private class CompatibleCanvas extends Canvas { - // A temp matrix to remember what an application obtained via {@link getMatrix} - private Matrix mOrigMatrix = null; - - @Override - public int getWidth() { - int w = super.getWidth(); - if (mCompatibilityTranslator != null) { - w = (int)(w * mCompatibilityTranslator.applicationInvertedScale + .5f); - } - return w; - } - - @Override - public int getHeight() { - int h = super.getHeight(); - if (mCompatibilityTranslator != null) { - h = (int)(h * mCompatibilityTranslator.applicationInvertedScale + .5f); - } - return h; - } + public Canvas lockCanvas(Rect dirty) + throws OutOfResourcesException, IllegalArgumentException { + return nativeLockCanvas(dirty); + } - @Override - public void setMatrix(Matrix matrix) { - if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) { - // don't scale the matrix if it's not compatibility mode, or - // the matrix was obtained from getMatrix. - super.setMatrix(matrix); - } else { - Matrix m = new Matrix(mCompatibleMatrix); - m.preConcat(matrix); - super.setMatrix(m); - } - } + /** + * Posts the new contents of the {@link Canvas} to the surface and + * releases the {@link Canvas}. + * + * @param canvas The canvas previously obtained from {@link #lockCanvas}. + */ + public void unlockCanvasAndPost(Canvas canvas) { + nativeUnlockCanvasAndPost(canvas); + } - @Override - public void getMatrix(Matrix m) { - super.getMatrix(m); - if (mOrigMatrix == null) { - mOrigMatrix = new Matrix(); - } - mOrigMatrix.set(m); - } + /** + * @deprecated This API has been removed and is not supported. Do not use. + */ + @Deprecated + public void unlockCanvas(Canvas canvas) { + throw new UnsupportedOperationException(); } /** @@ -384,20 +477,6 @@ public class Surface implements Parcelable { mCompatibleMatrix.setScale(appScale, appScale); } } - - /** Free all server-side state associated with this surface and - * release this object's reference. @hide */ - public native void destroy(); - - private native Canvas lockCanvasNative(Rect dirty) throws OutOfResourcesException; - - /** - * set the orientation of the given display. - * @param display - * @param orientation - * @hide - */ - public static native void setOrientation(int display, int orientation); /** * Like {@link #screenshot(int, int, int, int)} but includes all @@ -405,8 +484,12 @@ public class Surface implements Parcelable { * * @hide */ - public static native Bitmap screenshot(int width, int height); - + public static Bitmap screenshot(int width, int height) { + // TODO: should take the display as a parameter + IBinder displayToken = getBuiltInDisplay(BUILT_IN_DISPLAY_ID_MAIN); + return nativeScreenshot(displayToken, width, height, 0, 0, true); + } + /** * Copy the current screen contents into a bitmap and return it. * @@ -418,91 +501,318 @@ public class Surface implements Parcelable { * include in the screenshot. * @param maxLayer The highest (top-most Z order) surface layer to * include in the screenshot. - * @return Returns a Bitmap containing the screen contents. + * @return Returns a Bitmap containing the screen contents, or null + * if an error occurs. * * @hide */ - public static native Bitmap screenshot(int width, int height, int minLayer, int maxLayer); + public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer) { + // TODO: should take the display as a parameter + IBinder displayToken = getBuiltInDisplay(BUILT_IN_DISPLAY_ID_MAIN); + return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false); + } - /* * set surface parameters. * needs to be inside open/closeTransaction block */ - + /** start a transaction @hide */ - public static native void openTransaction(); + public static void openTransaction() { + nativeOpenTransaction(); + } + /** end a transaction @hide */ - public static native void closeTransaction(); + public static void closeTransaction() { + nativeCloseTransaction(); + } + /** @hide */ - public native void setLayer(int zorder); + public void setLayer(int zorder) { + nativeSetLayer(zorder); + } + /** @hide */ - public void setPosition(int x, int y) { setPosition((float)x, (float)y); } + public void setPosition(int x, int y) { + nativeSetPosition((float)x, (float)y); + } + /** @hide */ - public native void setPosition(float x, float y); + public void setPosition(float x, float y) { + nativeSetPosition(x, y); + } + /** @hide */ - public native void setSize(int w, int h); + public void setSize(int w, int h) { + nativeSetSize(w, h); + } + /** @hide */ - public native void hide(); + public void hide() { + nativeSetFlags(SURFACE_HIDDEN, SURFACE_HIDDEN); + } + /** @hide */ - public native void show(); + public void show() { + nativeSetFlags(0, SURFACE_HIDDEN); + } + /** @hide */ - public native void setTransparentRegionHint(Region region); + public void setTransparentRegionHint(Region region) { + nativeSetTransparentRegionHint(region); + } + /** @hide */ - public native void setAlpha(float alpha); + public void setAlpha(float alpha) { + nativeSetAlpha(alpha); + } + /** @hide */ - public native void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy); + public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) { + nativeSetMatrix(dsdx, dtdx, dsdy, dtdy); + } + /** @hide */ - public native void setFlags(int flags, int mask); + public void setFlags(int flags, int mask) { + nativeSetFlags(flags, mask); + } + /** @hide */ - public native void setWindowCrop(Rect crop); + public void setWindowCrop(Rect crop) { + nativeSetWindowCrop(crop); + } + /** @hide */ - public native void setLayerStack(int layerStack); + public void setLayerStack(int layerStack) { + nativeSetLayerStack(layerStack); + } + /** @hide */ + public static IBinder getBuiltInDisplay(int builtInDisplayId) { + return nativeGetBuiltInDisplay(builtInDisplayId); + } - - public static final Parcelable.Creator<Surface> CREATOR - = new Parcelable.Creator<Surface>() - { - public Surface createFromParcel(Parcel source) { - try { - return new Surface(source); - } catch (Exception e) { - Log.e(LOG_TAG, "Exception creating surface from parcel", e); - } - return null; + /** @hide */ + public static IBinder createDisplay(String name) { + if (name == null) { + throw new IllegalArgumentException("name must not be null"); } + return nativeCreateDisplay(name); + } - public Surface[] newArray(int size) { - return new Surface[size]; + /** @hide */ + public static void setDisplaySurface(IBinder displayToken, SurfaceTexture surfaceTexture) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); } - }; + nativeSetDisplaySurface(displayToken, surfaceTexture); + } + + /** @hide */ + public static void setDisplayLayerStack(IBinder displayToken, int layerStack) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + nativeSetDisplayLayerStack(displayToken, layerStack); + } + + /** @hide */ + public static void setDisplayOrientation(IBinder displayToken, int orientation) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + nativeSetDisplayOrientation(displayToken, orientation); + } + + /** @hide */ + public static void setDisplayViewport(IBinder displayToken, Rect viewport) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + if (viewport == null) { + throw new IllegalArgumentException("viewport must not be null"); + } + nativeSetDisplayViewport(displayToken, viewport); + } + + /** @hide */ + public static void setDisplayFrame(IBinder displayToken, Rect frame) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + if (frame == null) { + throw new IllegalArgumentException("frame must not be null"); + } + nativeSetDisplayFrame(displayToken, frame); + } + + /** @hide */ + public static boolean getDisplayInfo(IBinder displayToken, PhysicalDisplayInfo outInfo) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + if (outInfo == null) { + throw new IllegalArgumentException("outInfo must not be null"); + } + return nativeGetDisplayInfo(displayToken, outInfo); + } + + /** + * Copy another surface to this one. This surface now holds a reference + * to the same data as the original surface, and is -not- the owner. + * This is for use by the window manager when returning a window surface + * back from a client, converting it from the representation being managed + * by the window manager to the representation the client uses to draw + * in to it. + * @hide + */ + public void copyFrom(Surface other) { + if (other == null) { + throw new IllegalArgumentException("other must not be null"); + } + if (other != this) { + nativeCopyFrom(other); + } + } + + /** + * Transfer the native state from 'other' to this surface, releasing it + * from 'other'. This is for use in the client side for drawing into a + * surface; not guaranteed to work on the window manager side. + * This is for use by the client to move the underlying surface from + * one Surface object to another, in particular in SurfaceFlinger. + * @hide. + */ + public void transferFrom(Surface other) { + if (other == null) { + throw new IllegalArgumentException("other must not be null"); + } + if (other != this) { + nativeTransferFrom(other); + } + } @Override - protected void finalize() throws Throwable { - try { - super.finalize(); - } finally { - if (mNativeSurface != 0 || mSurfaceControl != 0) { - if (DEBUG_RELEASE) { - Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() (" - + mNativeSurface + ", " + mSurfaceControl + ")", mCreationStack); - } else { - Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() (" - + mNativeSurface + ", " + mSurfaceControl + ")"); - } - } - release(); + public int describeContents() { + return 0; + } + + public void readFromParcel(Parcel source) { + if (source == null) { + throw new IllegalArgumentException("source must not be null"); } + + mName = source.readString(); + nativeReadFromParcel(source); } - - private native void init(SurfaceSession s, - int pid, String name, int layerStack, int w, int h, int format, int flags) - throws OutOfResourcesException; - private native void init(Parcel source) throws OutOfResourcesException; + @Override + public void writeToParcel(Parcel dest, int flags) { + if (dest == null) { + throw new IllegalArgumentException("dest must not be null"); + } - private native void initFromSurfaceTexture(SurfaceTexture surfaceTexture); + dest.writeString(mName); + nativeWriteToParcel(dest); + if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) { + release(); + } + } - private native int getIdentity(); + @Override + public String toString() { + return "Surface(name=" + mName + ", identity=" + nativeGetIdentity() + ")"; + } + + private static void checkHeadless() { + if (HEADLESS) { + throw new UnsupportedOperationException("Device is headless"); + } + } + + /** + * Exception thrown when a surface couldn't be created or resized. + */ + public static class OutOfResourcesException extends Exception { + public OutOfResourcesException() { + } + + public OutOfResourcesException(String name) { + super(name); + } + } + + /** + * Describes the properties of a physical display. + * @hide + */ + public static final class PhysicalDisplayInfo { + // TODO: redesign this + public int width; + public int height; + public float refreshRate; + public float density; + public float xDpi; + public float yDpi; + } + + /** + * A Canvas class that can handle the compatibility mode. + * This does two things differently. + * <ul> + * <li>Returns the width and height of the target metrics, rather than + * native. For example, the canvas returns 320x480 even if an app is running + * in WVGA high density. + * <li>Scales the matrix in setMatrix by the application scale, except if + * the matrix looks like obtained from getMatrix. This is a hack to handle + * the case that an application uses getMatrix to keep the original matrix, + * set matrix of its own, then set the original matrix back. There is no + * perfect solution that works for all cases, and there are a lot of cases + * that this model does not work, but we hope this works for many apps. + * </ul> + */ + private final class CompatibleCanvas extends Canvas { + // A temp matrix to remember what an application obtained via {@link getMatrix} + private Matrix mOrigMatrix = null; + + @Override + public int getWidth() { + int w = super.getWidth(); + if (mCompatibilityTranslator != null) { + w = (int)(w * mCompatibilityTranslator.applicationInvertedScale + .5f); + } + return w; + } + + @Override + public int getHeight() { + int h = super.getHeight(); + if (mCompatibilityTranslator != null) { + h = (int)(h * mCompatibilityTranslator.applicationInvertedScale + .5f); + } + return h; + } + + @Override + public void setMatrix(Matrix matrix) { + if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) { + // don't scale the matrix if it's not compatibility mode, or + // the matrix was obtained from getMatrix. + super.setMatrix(matrix); + } else { + Matrix m = new Matrix(mCompatibleMatrix); + m.preConcat(matrix); + super.setMatrix(m); + } + } + + @Override + public void getMatrix(Matrix m) { + super.getMatrix(m); + if (mOrigMatrix == null) { + mOrigMatrix = new Matrix(); + } + mOrigMatrix.set(m); + } + } } diff --git a/core/java/android/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java index 2a04675..0dfd94a 100644 --- a/core/java/android/view/SurfaceSession.java +++ b/core/java/android/view/SurfaceSession.java @@ -16,34 +16,44 @@ package android.view; - /** * An instance of this class represents a connection to the surface - * flinger, in which you can create one or more Surface instances that will + * flinger, from which you can create one or more Surface instances that will * be composited to the screen. * {@hide} */ -public class SurfaceSession { +public final class SurfaceSession { + // Note: This field is accessed by native code. + private int mNativeClient; // SurfaceComposerClient* + + private static native int nativeCreate(); + private static native void nativeDestroy(int ptr); + private static native void nativeKill(int ptr); + /** Create a new connection with the surface flinger. */ public SurfaceSession() { - init(); + mNativeClient = nativeCreate(); } - /** Forcibly detach native resources associated with this object. - * Unlike destroy(), after this call any surfaces that were created - * from the session will no longer work. The session itself is destroyed. - */ - public native void kill(); - /* no user serviceable parts here ... */ @Override protected void finalize() throws Throwable { - destroy(); + try { + if (mNativeClient != 0) { + nativeDestroy(mNativeClient); + } + } finally { + super.finalize(); + } + } + + /** + * Forcibly detach native resources associated with this object. + * Unlike destroy(), after this call any surfaces that were created + * from the session will no longer work. + */ + public void kill() { + nativeKill(mNativeClient); } - - private native void init(); - private native void destroy(); - - private int mClient; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index b1f5e9e..745e1b8 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -40,6 +40,7 @@ import android.graphics.Shader; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -4925,6 +4926,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Returns the delegate for implementing accessibility support via + * composition. For more details see {@link AccessibilityDelegate}. + * + * @return The delegate, or null if none set. + * + * @hide + */ + public AccessibilityDelegate getAccessibilityDelegate() { + return mAccessibilityDelegate; + } + + /** * Sets a delegate for implementing accessibility support via compositon as * opposed to inheritance. The delegate's primary use is for implementing * backwards compatible widgets. For more details see {@link AccessibilityDelegate}. @@ -7347,7 +7360,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, outRect.bottom -= insets.bottom; return; } - Display d = DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); + // The view is not attached to a display so we don't have a context. + // Make a best guess about the display size. + Display d = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); d.getRectSize(outRect); } @@ -17610,23 +17625,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // use use a height of 1, and then wack the matrix each time we // actually use it. shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP); - paint.setShader(shader); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + this.host = host; } public void setFadeColor(int color) { - if (color != 0 && color != mLastColor) { + if (color != mLastColor) { mLastColor = color; - color |= 0xFF000000; - shader = new LinearGradient(0, 0, 0, 1, color | 0xFF000000, - color & 0x00FFFFFF, Shader.TileMode.CLAMP); - - paint.setShader(shader); - // Restore the default transfer mode (src_over) - paint.setXfermode(null); + if (color != 0) { + shader = new LinearGradient(0, 0, 0, 1, color | 0xFF000000, + color & 0x00FFFFFF, Shader.TileMode.CLAMP); + paint.setShader(shader); + // Restore the default transfer mode (src_over) + paint.setXfermode(null); + } else { + shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP); + paint.setShader(shader); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 725d9b5..ffd495e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -214,6 +214,9 @@ public final class ViewRootImpl implements ViewParent, boolean mTraversalScheduled; int mTraversalBarrier; boolean mWillDrawSoon; + /** Set to true while in performTraversals for detecting when die(true) is called from internal + * callbacks such as onMeasure, onPreDraw, onDraw and deferring doDie() until later. */ + boolean mIsInTraversal; boolean mFitSystemWindowsRequested; boolean mLayoutRequested; boolean mFirst; @@ -1104,6 +1107,7 @@ public final class ViewRootImpl implements ViewParent, if (host == null || !mAdded) return; + mIsInTraversal = true; mWillDrawSoon = true; boolean windowSizeMayChange = false; boolean newSurface = false; @@ -1842,6 +1846,8 @@ public final class ViewRootImpl implements ViewParent, mPendingTransitions.clear(); } } + + mIsInTraversal = false; } private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { @@ -3956,7 +3962,9 @@ public final class ViewRootImpl implements ViewParent, } public void die(boolean immediate) { - if (immediate) { + // Make sure we do execute immediately if we are in the middle of a traversal or the damage + // done by dispatchDetachedFromWindow will cause havoc on return. + if (immediate && !mIsInTraversal) { doDie(); } else { if (!mIsDrawing) { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 123d9e7..f30952c 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -179,7 +179,8 @@ public interface WindowManager extends ViewManager { @ViewDebug.IntToString(from = TYPE_BOOT_PROGRESS, to = "TYPE_BOOT_PROGRESS"), @ViewDebug.IntToString(from = TYPE_HIDDEN_NAV_CONSUMER, to = "TYPE_HIDDEN_NAV_CONSUMER"), @ViewDebug.IntToString(from = TYPE_DREAM, to = "TYPE_DREAM"), - @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL, to = "TYPE_NAVIGATION_BAR_PANEL") + @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL, to = "TYPE_NAVIGATION_BAR_PANEL"), + @ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY, to = "TYPE_DISPLAY_OVERLAY") }) public int type; @@ -435,6 +436,12 @@ public interface WindowManager extends ViewManager { public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25; /** + * Window type: Display overlay window. Used to simulate secondary display devices. + * @hide + */ + public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26; + + /** * End of types of system windows. */ public static final int LAST_SYSTEM_WINDOW = 2999; diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index bf061df..aa9179f 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -52,8 +52,9 @@ public final class WindowManagerImpl implements WindowManager { private final Window mParentWindow; public WindowManagerImpl(Context context, int displayId) { + DisplayManager dm = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); mContext = context; - mDisplay = DisplayManager.getInstance().getDisplay(displayId, mContext); + mDisplay = dm.getDisplay(displayId); mParentWindow = null; } diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java index 7dfb5bb..fd73fda 100644 --- a/core/java/android/webkit/AccessibilityInjector.java +++ b/core/java/android/webkit/AccessibilityInjector.java @@ -279,6 +279,7 @@ class AccessibilityInjector { } if (!shouldInjectJavaScript(url)) { + mAccessibilityScriptInjected = false; toggleFallbackAccessibilityInjector(true); return; } @@ -292,6 +293,23 @@ class AccessibilityInjector { } /** + * Adjusts the accessibility injection state to reflect changes in the + * JavaScript enabled state. + * + * @param enabled Whether JavaScript is enabled. + */ + public void updateJavaScriptEnabled(boolean enabled) { + if (enabled) { + addAccessibilityApisIfNecessary(); + } else { + removeAccessibilityApisIfNecessary(); + } + + // We have to reload the page after adding or removing APIs. + mWebView.reload(); + } + + /** * Toggles the non-JavaScript method for handling accessibility. * * @param enabled {@code true} to enable the non-JavaScript method, or diff --git a/core/java/android/webkit/BrowserDownloadListener.java b/core/java/android/webkit/BrowserDownloadListener.java new file mode 100644 index 0000000..724cc62 --- /dev/null +++ b/core/java/android/webkit/BrowserDownloadListener.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.webkit; + +/** + * An abstract download listener that allows passing extra information as + * part of onDownloadStart callback. + * @hide + */ +public abstract class BrowserDownloadListener implements DownloadListener { + + /** + * Notify the host application that a file should be downloaded + * @param url The full url to the content that should be downloaded + * @param userAgent the user agent to be used for the download. + * @param contentDisposition Content-disposition http header, if + * present. + * @param mimetype The mimetype of the content reported by the server + * @param referer The referer associated with this url + * @param contentLength The file size reported by the server + */ + public abstract void onDownloadStart(String url, String userAgent, + String contentDisposition, String mimetype, String referer, + long contentLength); + + + /** + * Notify the host application that a file should be downloaded + * @param url The full url to the content that should be downloaded + * @param userAgent the user agent to be used for the download. + * @param contentDisposition Content-disposition http header, if + * present. + * @param mimetype The mimetype of the content reported by the server + * @param contentLength The file size reported by the server + */ + @Override + public void onDownloadStart(String url, String userAgent, + String contentDisposition, String mimetype, long contentLength) { + + onDownloadStart(url, userAgent, contentDisposition, mimetype, null, + contentLength); + } +} diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index fe812af..1b23b18 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -1137,7 +1137,7 @@ class BrowserFrame extends Handler { * DownloadListener. */ private void downloadStart(String url, String userAgent, - String contentDisposition, String mimeType, long contentLength) { + String contentDisposition, String mimeType, String referer, long contentLength) { // This will only work if the url ends with the filename if (mimeType.isEmpty()) { try { @@ -1157,7 +1157,7 @@ class BrowserFrame extends Handler { mKeyStoreHandler = new KeyStoreHandler(mimeType); } else { mCallbackProxy.onDownloadStart(url, userAgent, - contentDisposition, mimeType, contentLength); + contentDisposition, mimeType, referer, contentLength); } } diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java index 6b87ded..b47cba8 100644 --- a/core/java/android/webkit/CallbackProxy.java +++ b/core/java/android/webkit/CallbackProxy.java @@ -452,10 +452,16 @@ class CallbackProxy extends Handler { String contentDisposition = msg.getData().getString("contentDisposition"); String mimetype = msg.getData().getString("mimetype"); + String referer = msg.getData().getString("referer"); Long contentLength = msg.getData().getLong("contentLength"); - mDownloadListener.onDownloadStart(url, userAgent, - contentDisposition, mimetype, contentLength); + if (mDownloadListener instanceof BrowserDownloadListener) { + ((BrowserDownloadListener) mDownloadListener).onDownloadStart(url, + userAgent, contentDisposition, mimetype, referer, contentLength); + } else { + mDownloadListener.onDownloadStart(url, userAgent, + contentDisposition, mimetype, contentLength); + } } break; @@ -1179,7 +1185,8 @@ class CallbackProxy extends Handler { * return false. */ public boolean onDownloadStart(String url, String userAgent, - String contentDisposition, String mimetype, long contentLength) { + String contentDisposition, String mimetype, String referer, + long contentLength) { // Do an unsynchronized quick check to avoid posting if no callback has // been set. if (mDownloadListener == null) { @@ -1192,6 +1199,7 @@ class CallbackProxy extends Handler { bundle.putString("url", url); bundle.putString("userAgent", userAgent); bundle.putString("mimetype", mimetype); + bundle.putString("referer", referer); bundle.putLong("contentLength", contentLength); bundle.putString("contentDisposition", contentDisposition); sendMessage(msg); diff --git a/core/java/android/webkit/WebSettingsClassic.java b/core/java/android/webkit/WebSettingsClassic.java index d1f8b4b..1bbe7bb 100644 --- a/core/java/android/webkit/WebSettingsClassic.java +++ b/core/java/android/webkit/WebSettingsClassic.java @@ -1122,6 +1122,7 @@ public class WebSettingsClassic extends WebSettings { if (mJavaScriptEnabled != flag) { mJavaScriptEnabled = flag; postSync(); + mWebView.updateJavaScriptEnabled(flag); } } diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index 9df4852..591b87f 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -687,6 +687,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // It's used to dismiss the dialog in destroy if not done before. private AlertDialog mListBoxDialog = null; + // Reference to the save password dialog so it can be dimissed in + // destroy if not done before. + private AlertDialog mSavePasswordDialog = null; + static final String LOGTAG = "webview"; private ZoomManager mZoomManager; @@ -1370,7 +1374,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc if (action == MotionEvent.ACTION_POINTER_DOWN) { cancelTouch(); action = MotionEvent.ACTION_DOWN; - } else if (action == MotionEvent.ACTION_POINTER_UP && ev.getPointerCount() >= 2) { + } else if (action == MotionEvent.ACTION_POINTER_UP) { // set mLastTouchX/Y to the remaining points for multi-touch. mLastTouchX = Math.round(x); mLastTouchY = Math.round(y); @@ -1633,6 +1637,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mZoomManager.updateMultiTouchSupport(context); } + void updateJavaScriptEnabled(boolean enabled) { + if (isAccessibilityInjectionEnabled()) { + getAccessibilityInjector().updateJavaScriptEnabled(enabled); + } + } + private void init() { OnTrimMemoryListener.init(mContext); mWebView.setWillNotDraw(false); @@ -1830,7 +1840,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc neverRemember.getData().putString("password", password); neverRemember.obj = resumeMsg; - new AlertDialog.Builder(mContext) + mSavePasswordDialog = new AlertDialog.Builder(mContext) .setTitle(com.android.internal.R.string.save_password_label) .setMessage(com.android.internal.R.string.save_password_message) .setPositiveButton(com.android.internal.R.string.save_password_notnow, @@ -1841,6 +1851,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc resumeMsg.sendToTarget(); mResumeMsg = null; } + mSavePasswordDialog = null; } }) .setNeutralButton(com.android.internal.R.string.save_password_remember, @@ -1851,6 +1862,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc remember.sendToTarget(); mResumeMsg = null; } + mSavePasswordDialog = null; } }) .setNegativeButton(com.android.internal.R.string.save_password_never, @@ -1861,6 +1873,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc neverRemember.sendToTarget(); mResumeMsg = null; } + mSavePasswordDialog = null; } }) .setOnCancelListener(new OnCancelListener() { @@ -1870,6 +1883,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc resumeMsg.sendToTarget(); mResumeMsg = null; } + mSavePasswordDialog = null; } }).show(); // Return true so that WebViewCore will pause while the dialog is @@ -2109,6 +2123,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mListBoxDialog.dismiss(); mListBoxDialog = null; } + if (mSavePasswordDialog != null) { + mSavePasswordDialog.dismiss(); + mSavePasswordDialog = null; + } if (mWebViewCore != null) { // Tell WebViewCore to destroy itself synchronized (this) { @@ -6981,6 +6999,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc @Override public boolean requestFocus(int direction, Rect previouslyFocusedRect) { + // Check if we are destroyed + if (mWebViewCore == null) return false; // FIXME: If a subwindow is showing find, and the user touches the // background window, it can steal focus. if (mFindIsUp) return false; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 423135f..920d44f 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -2158,7 +2158,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (mAccessibilityDelegate == null) { mAccessibilityDelegate = new ListItemAccessibilityDelegate(); } - child.setAccessibilityDelegate(mAccessibilityDelegate); + if (child.getAccessibilityDelegate() == null) { + child.setAccessibilityDelegate(mAccessibilityDelegate); + } } return child; diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java index fc35f05..f76ab2b 100644 --- a/core/java/android/widget/MediaController.java +++ b/core/java/android/widget/MediaController.java @@ -477,7 +477,8 @@ public class MediaController extends FrameLayout { return true; } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP - || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) { + || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE + || keyCode == KeyEvent.KEYCODE_CAMERA) { // don't show the controls for volume adjustment return super.dispatchKeyEvent(event); } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) { diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index 053ade7..e8bf9d9 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -305,12 +305,14 @@ public class Toast { private static class TN extends ITransientNotification.Stub { final Runnable mShow = new Runnable() { + @Override public void run() { handleShow(); } }; final Runnable mHide = new Runnable() { + @Override public void run() { handleHide(); // Don't do this in handleHide() because it is also invoked by handleShow() @@ -329,7 +331,7 @@ public class Toast { View mView; View mNextView; - + WindowManager mWM; TN() { @@ -350,6 +352,7 @@ public class Toast { /** * schedule handleShow into the right thread */ + @Override public void show() { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.post(mShow); @@ -358,6 +361,7 @@ public class Toast { /** * schedule handleHide into the right thread */ + @Override public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); @@ -370,7 +374,8 @@ public class Toast { // remove the old view if necessary handleHide(); mView = mNextView; - mWM = (WindowManager)mView.getContext().getSystemService(Context.WINDOW_SERVICE); + mWM = (WindowManager)mView.getContext().getApplicationContext() + .getSystemService(Context.WINDOW_SERVICE); // We can resolve the Gravity here by using the Locale for getting // the layout direction final Configuration config = mView.getContext().getResources().getConfiguration(); diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java index 6a68240..eee914e 100644 --- a/core/java/android/widget/ViewAnimator.java +++ b/core/java/android/widget/ViewAnimator.java @@ -329,8 +329,21 @@ public class ViewAnimator extends FrameLayout { } /** + * Returns whether the current View should be animated the first time the ViewAnimator + * is displayed. + * + * @return true if the current View will be animated the first time it is displayed, + * false otherwise. + * + * @see #setAnimateFirstView(boolean) + */ + public boolean getAnimateFirstView() { + return mAnimateFirstTime; + } + + /** * Indicates whether the current View should be animated the first time - * the ViewAnimation is displayed. + * the ViewAnimator is displayed. * * @param animate True to animate the current View the first time it is displayed, * false otherwise. diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java index 650681a..3477a90 100644 --- a/core/java/com/android/internal/content/PackageMonitor.java +++ b/core/java/com/android/internal/content/PackageMonitor.java @@ -49,6 +49,7 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { sPackageFilt.addAction(Intent.ACTION_UID_REMOVED); sPackageFilt.addDataScheme("package"); sNonDataFilt.addAction(Intent.ACTION_UID_REMOVED); + sNonDataFilt.addAction(Intent.ACTION_USER_STOPPED); sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); } @@ -136,6 +137,9 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { return false; } + + public void onHandleUserStop(Intent intent, int userHandle) { + } public void onUidRemoved(int uid) { } @@ -307,6 +311,10 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { intent.getIntExtra(Intent.EXTRA_UID, 0), true); } else if (Intent.ACTION_UID_REMOVED.equals(action)) { onUidRemoved(intent.getIntExtra(Intent.EXTRA_UID, 0)); + } else if (Intent.ACTION_USER_STOPPED.equals(action)) { + if (intent.hasExtra(Intent.EXTRA_USER_HANDLE)) { + onHandleUserStop(intent, intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); + } } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); mAppearingPackages = pkgList; diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index d6f1807..d24513a 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -529,6 +529,8 @@ class ZygoteConnection { niceName = arg.substring(arg.indexOf('=') + 1); } else if (arg.equals("--mount-external-multiuser")) { mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER; + } else if (arg.equals("--mount-external-multiuser-all")) { + mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL; } else { break; } diff --git a/core/jni/Android.mk b/core/jni/Android.mk index f950d3d..9d45479 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -46,6 +46,7 @@ LOCAL_SRC_FILES:= \ android_emoji_EmojiFactory.cpp \ android_view_DisplayEventReceiver.cpp \ android_view_Surface.cpp \ + android_view_SurfaceSession.cpp \ android_view_TextureView.cpp \ android_view_InputChannel.cpp \ android_view_InputDevice.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 0c88297..55563a8 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -121,6 +121,7 @@ extern int register_android_view_GLES20DisplayList(JNIEnv* env); extern int register_android_view_GLES20Canvas(JNIEnv* env); extern int register_android_view_HardwareRenderer(JNIEnv* env); extern int register_android_view_Surface(JNIEnv* env); +extern int register_android_view_SurfaceSession(JNIEnv* env); extern int register_android_view_TextureView(JNIEnv* env); extern int register_android_database_CursorWindow(JNIEnv* env); extern int register_android_database_SQLiteConnection(JNIEnv* env); @@ -1094,6 +1095,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_view_GLES20Canvas), REG_JNI(register_android_view_HardwareRenderer), REG_JNI(register_android_view_Surface), + REG_JNI(register_android_view_SurfaceSession), REG_JNI(register_android_view_TextureView), REG_JNI(register_com_google_android_gles_jni_EGLImpl), REG_JNI(register_com_google_android_gles_jni_GLImpl), diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 3c27caf..1bba5b4 100644 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -298,8 +298,18 @@ static bool Bitmap_compress(JNIEnv* env, jobject clazz, SkBitmap* bitmap, }
bool success = false;
- SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
- if (NULL != strm) {
+ if (NULL != bitmap) {
+ SkAutoLockPixels alp(*bitmap);
+
+ if (NULL == bitmap->getPixels()) {
+ return false;
+ }
+
+ SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
+ if (NULL == strm) {
+ return false;
+ }
+
SkImageEncoder* encoder = SkImageEncoder::Create(fm);
if (NULL != encoder) {
success = encoder->encodeStream(strm, *bitmap, quality);
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp index 074afa3..21162f4 100644 --- a/core/jni/android_app_NativeActivity.cpp +++ b/core/jni/android_app_NativeActivity.cpp @@ -436,7 +436,7 @@ struct NativeCode : public ANativeActivity { void setSurface(jobject _surface) { if (_surface != NULL) { - nativeWindow = android_Surface_getNativeWindow(env, _surface); + nativeWindow = android_view_Surface_getNativeWindow(env, _surface); } else { nativeWindow = NULL; } diff --git a/core/jni/android_opengl_EGL14.cpp b/core/jni/android_opengl_EGL14.cpp index c271aeb..b1664c6 100644 --- a/core/jni/android_opengl_EGL14.cpp +++ b/core/jni/android_opengl_EGL14.cpp @@ -549,7 +549,7 @@ not_valid_surface: goto exit; } - window = android::android_Surface_getNativeWindow(_env, win); + window = android::android_view_Surface_getNativeWindow(_env, win); if (window == NULL) goto not_valid_surface; diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index bada329..4d5e680 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -18,15 +18,19 @@ #include <stdio.h> +#include "android_os_Parcel.h" #include "android_util_Binder.h" #include "android/graphics/GraphicsJNI.h" +#include "android/graphics/Region.h" #include <binder/IMemory.h> +#include <gui/ISurfaceComposer.h> #include <gui/Surface.h> #include <gui/SurfaceComposerClient.h> #include <gui/SurfaceTexture.h> +#include <ui/DisplayInfo.h> #include <ui/Rect.h> #include <ui/Region.h> @@ -41,127 +45,135 @@ #include "JNIHelp.h" #include <android_runtime/AndroidRuntime.h> #include <android_runtime/android_view_Surface.h> +#include <android_runtime/android_view_SurfaceSession.h> #include <android_runtime/android_graphics_SurfaceTexture.h> #include <utils/misc.h> +#include <ScopedUtfChars.h> + // ---------------------------------------------------------------------------- namespace android { -enum { - // should match Parcelable.java - PARCELABLE_WRITE_RETURN_VALUE = 0x0001 -}; - -// ---------------------------------------------------------------------------- - static const char* const OutOfResourcesException = "android/view/Surface$OutOfResourcesException"; -const char* const kSurfaceSessionClassPathName = "android/view/SurfaceSession"; -const char* const kSurfaceClassPathName = "android/view/Surface"; +static struct { + jclass clazz; + jfieldID mNativeSurface; + jfieldID mNativeSurfaceControl; + jfieldID mGenerationId; + jfieldID mCanvas; + jfieldID mCanvasSaveCount; +} gSurfaceClassInfo; + +static struct { + jfieldID left; + jfieldID top; + jfieldID right; + jfieldID bottom; +} gRectClassInfo; + +static struct { + jfieldID mNativeCanvas; + jfieldID mSurfaceFormat; +} gCanvasClassInfo; + +static struct { + jfieldID width; + jfieldID height; + jfieldID refreshRate; + jfieldID density; + jfieldID xDpi; + jfieldID yDpi; +} gPhysicalDisplayInfoClassInfo; -struct sso_t { - jfieldID client; -}; -static sso_t sso; - -struct so_t { - jfieldID surfaceControl; - jfieldID surfaceGenerationId; - jfieldID surface; - jfieldID saveCount; - jfieldID canvas; -}; -static so_t so; -struct ro_t { - jfieldID l; - jfieldID t; - jfieldID r; - jfieldID b; -}; -static ro_t ro; +class ScreenshotPixelRef : public SkPixelRef { +public: + ScreenshotPixelRef(SkColorTable* ctable) { + fCTable = ctable; + SkSafeRef(ctable); + setImmutable(); + } -struct po_t { - jfieldID x; - jfieldID y; -}; -static po_t po; + virtual ~ScreenshotPixelRef() { + SkSafeUnref(fCTable); + } -struct co_t { - jfieldID surfaceFormat; -}; -static co_t co; + status_t update(const sp<IBinder>& display, int width, int height, + int minLayer, int maxLayer, bool allLayers) { + status_t res = (width > 0 && height > 0) + ? (allLayers + ? mScreenshot.update(display, width, height) + : mScreenshot.update(display, width, height, minLayer, maxLayer)) + : mScreenshot.update(display); + if (res != NO_ERROR) { + return res; + } -struct no_t { - jfieldID native_canvas; - jfieldID native_region; - jfieldID native_parcel; -}; -static no_t no; + return NO_ERROR; + } + uint32_t getWidth() const { + return mScreenshot.getWidth(); + } -// ---------------------------------------------------------------------------- -// ---------------------------------------------------------------------------- -// ---------------------------------------------------------------------------- + uint32_t getHeight() const { + return mScreenshot.getHeight(); + } -static void SurfaceSession_init(JNIEnv* env, jobject clazz) -{ - sp<SurfaceComposerClient> client = new SurfaceComposerClient; - client->incStrong(clazz); - env->SetIntField(clazz, sso.client, (int)client.get()); -} + uint32_t getStride() const { + return mScreenshot.getStride(); + } -static void SurfaceSession_destroy(JNIEnv* env, jobject clazz) -{ - SurfaceComposerClient* client = - (SurfaceComposerClient*)env->GetIntField(clazz, sso.client); - if (client != 0) { - client->decStrong(clazz); - env->SetIntField(clazz, sso.client, 0); + uint32_t getFormat() const { + return mScreenshot.getFormat(); } -} -static void SurfaceSession_kill(JNIEnv* env, jobject clazz) -{ - SurfaceComposerClient* client = - (SurfaceComposerClient*)env->GetIntField(clazz, sso.client); - if (client != 0) { - client->dispose(); - client->decStrong(clazz); - env->SetIntField(clazz, sso.client, 0); +protected: + // overrides from SkPixelRef + virtual void* onLockPixels(SkColorTable** ct) { + *ct = fCTable; + return (void*)mScreenshot.getPixels(); } -} + + virtual void onUnlockPixels() { + } + +private: + ScreenshotClient mScreenshot; + SkColorTable* fCTable; + + typedef SkPixelRef INHERITED; +}; + // ---------------------------------------------------------------------------- -static sp<SurfaceControl> getSurfaceControl(JNIEnv* env, jobject clazz) -{ - SurfaceControl* const p = - (SurfaceControl*)env->GetIntField(clazz, so.surfaceControl); - return sp<SurfaceControl>(p); +static sp<SurfaceControl> getSurfaceControl(JNIEnv* env, jobject surfaceObj) { + return reinterpret_cast<SurfaceControl*>( + env->GetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurfaceControl)); } -static void setSurfaceControl(JNIEnv* env, jobject clazz, - const sp<SurfaceControl>& surface) -{ - SurfaceControl* const p = - (SurfaceControl*)env->GetIntField(clazz, so.surfaceControl); +static void setSurfaceControl(JNIEnv* env, jobject surfaceObj, + const sp<SurfaceControl>& surface) { + SurfaceControl* const p = reinterpret_cast<SurfaceControl*>( + env->GetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurfaceControl)); if (surface.get()) { - surface->incStrong(clazz); + surface->incStrong(surfaceObj); } if (p) { - p->decStrong(clazz); + p->decStrong(surfaceObj); } - env->SetIntField(clazz, so.surfaceControl, (int)surface.get()); + env->SetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurfaceControl, + reinterpret_cast<jint>(surface.get())); } -static sp<Surface> getSurface(JNIEnv* env, jobject clazz) -{ - sp<Surface> result(Surface_getSurface(env, clazz)); - if (result == 0) { +static sp<Surface> getSurface(JNIEnv* env, jobject surfaceObj) { + sp<Surface> result(android_view_Surface_getSurface(env, surfaceObj)); + if (result == NULL) { /* * if this method is called from the WindowManager's process, it means * the client is is not remote, and therefore is allowed to have @@ -170,94 +182,81 @@ static sp<Surface> getSurface(JNIEnv* env, jobject clazz) * process. */ - SurfaceControl* const control = - (SurfaceControl*)env->GetIntField(clazz, so.surfaceControl); + SurfaceControl* const control = reinterpret_cast<SurfaceControl*>( + env->GetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurfaceControl)); if (control) { result = control->getSurface(); - if (result != 0) { - result->incStrong(clazz); - env->SetIntField(clazz, so.surface, (int)result.get()); + if (result != NULL) { + result->incStrong(surfaceObj); + env->SetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurface, + reinterpret_cast<jint>(result.get())); } } } return result; } -sp<ANativeWindow> android_Surface_getNativeWindow( - JNIEnv* env, jobject clazz) { - return getSurface(env, clazz); +sp<ANativeWindow> android_view_Surface_getNativeWindow(JNIEnv* env, jobject surfaceObj) { + return getSurface(env, surfaceObj); } -bool android_Surface_isInstanceOf(JNIEnv* env, jobject obj) { - jclass surfaceClass = env->FindClass(kSurfaceClassPathName); - return env->IsInstanceOf(obj, surfaceClass); +bool android_view_Surface_isInstanceOf(JNIEnv* env, jobject obj) { + return env->IsInstanceOf(obj, gSurfaceClassInfo.clazz); } -sp<Surface> Surface_getSurface(JNIEnv* env, jobject clazz) { - sp<Surface> surface((Surface*)env->GetIntField(clazz, so.surface)); - return surface; +sp<Surface> android_view_Surface_getSurface(JNIEnv* env, jobject surfaceObj) { + return reinterpret_cast<Surface*>( + env->GetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurface)); } -void setSurface(JNIEnv* env, jobject clazz, const sp<Surface>& surface) -{ - Surface* const p = (Surface*)env->GetIntField(clazz, so.surface); +static void setSurface(JNIEnv* env, jobject surfaceObj, const sp<Surface>& surface) { + Surface* const p = reinterpret_cast<Surface*>( + env->GetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurface)); if (surface.get()) { - surface->incStrong(clazz); + surface->incStrong(surfaceObj); } if (p) { - p->decStrong(clazz); + p->decStrong(surfaceObj); } - env->SetIntField(clazz, so.surface, (int)surface.get()); + env->SetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurface, + reinterpret_cast<jint>(surface.get())); + // This test is conservative and it would be better to compare the ISurfaces if (p && p != surface.get()) { - jint generationId = env->GetIntField(clazz, so.surfaceGenerationId); + jint generationId = env->GetIntField(surfaceObj, + gSurfaceClassInfo.mGenerationId); generationId++; - env->SetIntField(clazz, so.surfaceGenerationId, generationId); + env->SetIntField(surfaceObj, + gSurfaceClassInfo.mGenerationId, generationId); } } // ---------------------------------------------------------------------------- -static void Surface_init( - JNIEnv* env, jobject clazz, - jobject session, - jint, jstring jname, jint layerStack, jint w, jint h, jint format, jint flags) -{ - if (session == NULL) { - doThrowNPE(env); - return; - } - - SurfaceComposerClient* client = - (SurfaceComposerClient*)env->GetIntField(session, sso.client); +static void nativeCreate(JNIEnv* env, jobject surfaceObj, jobject sessionObj, + jstring nameStr, jint w, jint h, jint format, jint flags) { + ScopedUtfChars name(env, nameStr); + sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj)); - sp<SurfaceControl> surface; - if (jname == NULL) { - surface = client->createSurface(layerStack, w, h, format, flags); - } else { - const jchar* str = env->GetStringCritical(jname, 0); - const String8 name(str, env->GetStringLength(jname)); - env->ReleaseStringCritical(jname, str); - surface = client->createSurface(name, layerStack, w, h, format, flags); - } - - if (surface == 0) { + sp<SurfaceControl> surface = client->createSurface( + String8(name.c_str()), w, h, format, flags); + if (surface == NULL) { jniThrowException(env, OutOfResourcesException, NULL); return; } - setSurfaceControl(env, clazz, surface); -} -static void Surface_initFromSurfaceTexture( - JNIEnv* env, jobject clazz, jobject jst) -{ - sp<SurfaceTexture> st(SurfaceTexture_getSurfaceTexture(env, jst)); + setSurfaceControl(env, surfaceObj, surface); +} +static void nativeCreateFromSurfaceTexture(JNIEnv* env, jobject surfaceObj, + jobject surfaceTextureObj) { + sp<SurfaceTexture> st(SurfaceTexture_getSurfaceTexture(env, surfaceTextureObj)); if (st == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "SurfaceTexture has already been released"); return; } + sp<ISurfaceTexture> bq = st->getBufferQueue(); sp<Surface> surface(new Surface(bq)); @@ -265,72 +264,62 @@ static void Surface_initFromSurfaceTexture( jniThrowException(env, OutOfResourcesException, NULL); return; } - setSurfaceControl(env, clazz, NULL); - setSurface(env, clazz, surface); -} -static void Surface_initParcel(JNIEnv* env, jobject clazz, jobject argParcel) -{ - Parcel* parcel = (Parcel*)env->GetIntField(argParcel, no.native_parcel); - if (parcel == NULL) { - doThrowNPE(env); - return; - } - - sp<Surface> sur(Surface::readFromParcel(*parcel)); - setSurface(env, clazz, sur); + setSurface(env, surfaceObj, surface); } -static jint Surface_getIdentity(JNIEnv* env, jobject clazz) -{ - const sp<SurfaceControl>& control(getSurfaceControl(env, clazz)); - if (control != 0) return (jint) control->getIdentity(); - const sp<Surface>& surface(getSurface(env, clazz)); - if (surface != 0) return (jint) surface->getIdentity(); - return -1; +static void nativeRelease(JNIEnv* env, jobject surfaceObj) { + setSurfaceControl(env, surfaceObj, NULL); + setSurface(env, surfaceObj, NULL); } -static void Surface_destroy(JNIEnv* env, jobject clazz, uintptr_t *ostack) -{ - const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz)); - if (SurfaceControl::isValid(surface)) { - surface->clear(); +static void nativeDestroy(JNIEnv* env, jobject surfaceObj) { + sp<SurfaceControl> surfaceControl(getSurfaceControl(env, surfaceObj)); + if (SurfaceControl::isValid(surfaceControl)) { + surfaceControl->clear(); } - setSurfaceControl(env, clazz, 0); - setSurface(env, clazz, 0); + setSurfaceControl(env, surfaceObj, NULL); + setSurface(env, surfaceObj, NULL); } -static void Surface_release(JNIEnv* env, jobject clazz, uintptr_t *ostack) -{ - setSurfaceControl(env, clazz, 0); - setSurface(env, clazz, 0); -} - -static jboolean Surface_isValid(JNIEnv* env, jobject clazz) -{ - const sp<SurfaceControl>& surfaceControl(getSurfaceControl(env, clazz)); - if (surfaceControl != 0) { +static jboolean nativeIsValid(JNIEnv* env, jobject surfaceObj) { + sp<SurfaceControl> surfaceControl(getSurfaceControl(env, surfaceObj)); + if (surfaceControl != NULL) { return SurfaceControl::isValid(surfaceControl) ? JNI_TRUE : JNI_FALSE; } - const sp<Surface>& surface(getSurface(env, clazz)); + + sp<Surface> surface(getSurface(env, surfaceObj)); return Surface::isValid(surface) ? JNI_TRUE : JNI_FALSE; } -static jboolean Surface_isConsumerRunningBehind(JNIEnv* env, jobject clazz) -{ - int value = 0; - const sp<Surface>& surface(getSurface(env, clazz)); +static jint nativeGetIdentity(JNIEnv* env, jobject surfaceObj) { + sp<SurfaceControl> control(getSurfaceControl(env, surfaceObj)); + if (control != NULL) { + return jint(control->getIdentity()); + } + + sp<Surface> surface(getSurface(env, surfaceObj)); + if (surface != NULL) { + return jint(surface->getIdentity()); + } + + return -1; +} + +static jboolean nativeIsConsumerRunningBehind(JNIEnv* env, jobject surfaceObj) { + sp<Surface> surface(getSurface(env, surfaceObj)); if (!Surface::isValid(surface)) { doThrowIAE(env); - return 0; + return JNI_FALSE; } - ANativeWindow* anw = static_cast<ANativeWindow *>(surface.get()); + + int value = 0; + ANativeWindow* anw = static_cast<ANativeWindow*>(surface.get()); anw->query(anw, NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &value); - return (jboolean)value; + return value; } -static inline SkBitmap::Config convertPixelFormat(PixelFormat format) -{ +static inline SkBitmap::Config convertPixelFormat(PixelFormat format) { /* note: if PIXEL_FORMAT_RGBX_8888 means that all alpha bytes are 0xFF, then we can map to SkBitmap::kARGB_8888_Config, and optionally call bitmap.setIsOpaque(true) on the resulting SkBitmap (as an accelerator) @@ -345,44 +334,44 @@ static inline SkBitmap::Config convertPixelFormat(PixelFormat format) } } -static jobject Surface_lockCanvas(JNIEnv* env, jobject clazz, jobject dirtyRect) -{ - const sp<Surface>& surface(getSurface(env, clazz)); +static jobject nativeLockCanvas(JNIEnv* env, jobject surfaceObj, jobject dirtyRectObj) { + sp<Surface> surface(getSurface(env, surfaceObj)); if (!Surface::isValid(surface)) { doThrowIAE(env); - return 0; + return NULL; } // get dirty region Region dirtyRegion; - if (dirtyRect) { + if (dirtyRectObj) { Rect dirty; - dirty.left = env->GetIntField(dirtyRect, ro.l); - dirty.top = env->GetIntField(dirtyRect, ro.t); - dirty.right = env->GetIntField(dirtyRect, ro.r); - dirty.bottom= env->GetIntField(dirtyRect, ro.b); + dirty.left = env->GetIntField(dirtyRectObj, gRectClassInfo.left); + dirty.top = env->GetIntField(dirtyRectObj, gRectClassInfo.top); + dirty.right = env->GetIntField(dirtyRectObj, gRectClassInfo.right); + dirty.bottom = env->GetIntField(dirtyRectObj, gRectClassInfo.bottom); if (!dirty.isEmpty()) { dirtyRegion.set(dirty); } } else { - dirtyRegion.set(Rect(0x3FFF,0x3FFF)); + dirtyRegion.set(Rect(0x3FFF, 0x3FFF)); } Surface::SurfaceInfo info; status_t err = surface->lock(&info, &dirtyRegion); if (err < 0) { const char* const exception = (err == NO_MEMORY) ? - OutOfResourcesException : - "java/lang/IllegalArgumentException"; + OutOfResourcesException : + "java/lang/IllegalArgumentException"; jniThrowException(env, exception, NULL); - return 0; + return NULL; } // Associate a SkCanvas object to this surface - jobject canvas = env->GetObjectField(clazz, so.canvas); - env->SetIntField(canvas, co.surfaceFormat, info.format); + jobject canvasObj = env->GetObjectField(surfaceObj, gSurfaceClassInfo.mCanvas); + env->SetIntField(canvasObj, gCanvasClassInfo.mSurfaceFormat, info.format); - SkCanvas* nativeCanvas = (SkCanvas*)env->GetIntField(canvas, no.native_canvas); + SkCanvas* nativeCanvas = reinterpret_cast<SkCanvas*>( + env->GetIntField(canvasObj, gCanvasClassInfo.mNativeCanvas)); SkBitmap bitmap; ssize_t bpr = info.s * bytesPerPixel(info.format); bitmap.setConfig(convertPixelFormat(info.format), info.w, info.h, bpr); @@ -413,38 +402,38 @@ static jobject Surface_lockCanvas(JNIEnv* env, jobject clazz, jobject dirtyRect) nativeCanvas->clipRegion(clipReg); int saveCount = nativeCanvas->save(); - env->SetIntField(clazz, so.saveCount, saveCount); + env->SetIntField(surfaceObj, gSurfaceClassInfo.mCanvasSaveCount, saveCount); - if (dirtyRect) { + if (dirtyRectObj) { const Rect& bounds(dirtyRegion.getBounds()); - env->SetIntField(dirtyRect, ro.l, bounds.left); - env->SetIntField(dirtyRect, ro.t, bounds.top); - env->SetIntField(dirtyRect, ro.r, bounds.right); - env->SetIntField(dirtyRect, ro.b, bounds.bottom); + env->SetIntField(dirtyRectObj, gRectClassInfo.left, bounds.left); + env->SetIntField(dirtyRectObj, gRectClassInfo.top, bounds.top); + env->SetIntField(dirtyRectObj, gRectClassInfo.right, bounds.right); + env->SetIntField(dirtyRectObj, gRectClassInfo.bottom, bounds.bottom); } - return canvas; + return canvasObj; } -static void Surface_unlockCanvasAndPost( - JNIEnv* env, jobject clazz, jobject argCanvas) -{ - jobject canvas = env->GetObjectField(clazz, so.canvas); - if (env->IsSameObject(canvas, argCanvas) == JNI_FALSE) { +static void nativeUnlockCanvasAndPost(JNIEnv* env, jobject surfaceObj, jobject canvasObj) { + jobject ownCanvasObj = env->GetObjectField(surfaceObj, gSurfaceClassInfo.mCanvas); + if (!env->IsSameObject(ownCanvasObj, canvasObj)) { doThrowIAE(env); return; } - const sp<Surface>& surface(getSurface(env, clazz)); - if (!Surface::isValid(surface)) + sp<Surface> surface(getSurface(env, surfaceObj)); + if (!Surface::isValid(surface)) { return; + } // detach the canvas from the surface - SkCanvas* nativeCanvas = (SkCanvas*)env->GetIntField(canvas, no.native_canvas); - int saveCount = env->GetIntField(clazz, so.saveCount); + SkCanvas* nativeCanvas = reinterpret_cast<SkCanvas*>( + env->GetIntField(canvasObj, gCanvasClassInfo.mNativeCanvas)); + int saveCount = env->GetIntField(surfaceObj, gSurfaceClassInfo.mCanvasSaveCount); nativeCanvas->restoreToCount(saveCount); nativeCanvas->setBitmapDevice(SkBitmap()); - env->SetIntField(clazz, so.saveCount, 0); + env->SetIntField(surfaceObj, gSurfaceClassInfo.mCanvasSaveCount, 0); // unlock surface status_t err = surface->unlockAndPost(); @@ -453,98 +442,18 @@ static void Surface_unlockCanvasAndPost( } } -static void Surface_unlockCanvas( - JNIEnv* env, jobject clazz, jobject argCanvas) -{ - // XXX: this API has been removed - doThrowIAE(env); -} - -static void Surface_openTransaction( - JNIEnv* env, jobject clazz) -{ - SurfaceComposerClient::openGlobalTransaction(); -} - -static void Surface_closeTransaction( - JNIEnv* env, jobject clazz) -{ - SurfaceComposerClient::closeGlobalTransaction(); -} - -static void Surface_setOrientation( - JNIEnv* env, jobject clazz, jint display, jint orientation) -{ - int err = SurfaceComposerClient::setOrientation(display, orientation, 0); - if (err < 0) { - doThrowIAE(env); +static jobject nativeScreenshot(JNIEnv* env, jclass clazz, jobject displayTokenObj, + jint width, jint height, jint minLayer, jint maxLayer, bool allLayers) { + sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj); + if (displayToken == NULL) { + return NULL; } -} -class ScreenshotPixelRef : public SkPixelRef { -public: - ScreenshotPixelRef(SkColorTable* ctable) { - fCTable = ctable; - SkSafeRef(ctable); - setImmutable(); - } - virtual ~ScreenshotPixelRef() { - SkSafeUnref(fCTable); - } - - status_t update(int width, int height, int minLayer, int maxLayer, bool allLayers) { - status_t res = (width > 0 && height > 0) - ? (allLayers - ? mScreenshot.update(width, height) - : mScreenshot.update(width, height, minLayer, maxLayer)) - : mScreenshot.update(); - if (res != NO_ERROR) { - return res; - } - - return NO_ERROR; - } - - uint32_t getWidth() const { - return mScreenshot.getWidth(); - } - - uint32_t getHeight() const { - return mScreenshot.getHeight(); - } - - uint32_t getStride() const { - return mScreenshot.getStride(); - } - - uint32_t getFormat() const { - return mScreenshot.getFormat(); - } - -protected: - // overrides from SkPixelRef - virtual void* onLockPixels(SkColorTable** ct) { - *ct = fCTable; - return (void*)mScreenshot.getPixels(); - } - - virtual void onUnlockPixels() { - } - -private: - ScreenshotClient mScreenshot; - SkColorTable* fCTable; - - typedef SkPixelRef INHERITED; -}; - -static jobject doScreenshot(JNIEnv* env, jobject clazz, jint width, jint height, - jint minLayer, jint maxLayer, bool allLayers) -{ ScreenshotPixelRef* pixels = new ScreenshotPixelRef(NULL); - if (pixels->update(width, height, minLayer, maxLayer, allLayers) != NO_ERROR) { + if (pixels->update(displayToken, width, height, + minLayer, maxLayer, allLayers) != NO_ERROR) { delete pixels; - return 0; + return NULL; } uint32_t w = pixels->getWidth(); @@ -571,94 +480,68 @@ static jobject doScreenshot(JNIEnv* env, jobject clazz, jint width, jint height, return GraphicsJNI::createBitmap(env, bitmap, false, NULL); } -static jobject Surface_screenshotAll(JNIEnv* env, jobject clazz, jint width, jint height) -{ - return doScreenshot(env, clazz, width, height, 0, 0, true); +static void nativeOpenTransaction(JNIEnv* env, jclass clazz) { + SurfaceComposerClient::openGlobalTransaction(); } -static jobject Surface_screenshot(JNIEnv* env, jobject clazz, jint width, jint height, - jint minLayer, jint maxLayer) -{ - return doScreenshot(env, clazz, width, height, minLayer, maxLayer, false); +static void nativeCloseTransaction(JNIEnv* env, jclass clazz) { + SurfaceComposerClient::closeGlobalTransaction(); } -static void Surface_setLayer( - JNIEnv* env, jobject clazz, jint zorder) -{ - const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz)); - if (surface == 0) return; +static void nativeSetLayer(JNIEnv* env, jobject surfaceObj, jint zorder) { + sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj)); + if (surface == NULL) return; + status_t err = surface->setLayer(zorder); - if (err<0 && err!=NO_INIT) { + if (err < 0 && err != NO_INIT) { doThrowIAE(env); } } -static void Surface_setPosition( - JNIEnv* env, jobject clazz, jfloat x, jfloat y) -{ - const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz)); - if (surface == 0) return; +static void nativeSetPosition(JNIEnv* env, jobject surfaceObj, jfloat x, jfloat y) { + sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj)); + if (surface == NULL) return; + status_t err = surface->setPosition(x, y); - if (err<0 && err!=NO_INIT) { + if (err < 0 && err != NO_INIT) { doThrowIAE(env); } } -static void Surface_setSize( - JNIEnv* env, jobject clazz, jint w, jint h) -{ - const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz)); - if (surface == 0) return; +static void nativeSetSize(JNIEnv* env, jobject surfaceObj, jint w, jint h) { + sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj)); + if (surface == NULL) return; + status_t err = surface->setSize(w, h); - if (err<0 && err!=NO_INIT) { + if (err < 0 && err != NO_INIT) { doThrowIAE(env); } } -static void Surface_hide( - JNIEnv* env, jobject clazz) -{ - const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz)); - if (surface == 0) return; - status_t err = surface->hide(); - if (err<0 && err!=NO_INIT) { - doThrowIAE(env); - } -} +static void nativeSetFlags(JNIEnv* env, jobject surfaceObj, jint flags, jint mask) { + sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj)); + if (surface == NULL) return; -static void Surface_show( - JNIEnv* env, jobject clazz) -{ - const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz)); - if (surface == 0) return; - status_t err = surface->show(); - if (err<0 && err!=NO_INIT) { + status_t err = surface->setFlags(flags, mask); + if (err < 0 && err != NO_INIT) { doThrowIAE(env); } } -static void Surface_setFlags( - JNIEnv* env, jobject clazz, jint flags, jint mask) -{ - const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz)); - if (surface == 0) return; - status_t err = surface->setFlags(flags, mask); - if (err<0 && err!=NO_INIT) { +static void nativeSetTransparentRegionHint(JNIEnv* env, jobject surfaceObj, jobject regionObj) { + sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj)); + if (surface == NULL) return; + + SkRegion* region = android_graphics_Region_getSkRegion(env, regionObj); + if (!region) { doThrowIAE(env); + return; } -} - -static void Surface_setTransparentRegion( - JNIEnv* env, jobject clazz, jobject argRegion) -{ - const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz)); - if (surface == 0) return; - SkRegion* nativeRegion = (SkRegion*)env->GetIntField(argRegion, no.native_region); - const SkIRect& b(nativeRegion->getBounds()); + const SkIRect& b(region->getBounds()); Region reg(Rect(b.fLeft, b.fTop, b.fRight, b.fBottom)); - if (nativeRegion->isComplex()) { - SkRegion::Iterator it(*nativeRegion); + if (region->isComplex()) { + SkRegion::Iterator it(*region); while (!it.done()) { const SkIRect& r(it.rect()); reg.addRectUnchecked(r.fLeft, r.fTop, r.fRight, r.fBottom); @@ -667,130 +550,197 @@ static void Surface_setTransparentRegion( } status_t err = surface->setTransparentRegionHint(reg); - if (err<0 && err!=NO_INIT) { + if (err < 0 && err != NO_INIT) { doThrowIAE(env); } } -static void Surface_setAlpha( - JNIEnv* env, jobject clazz, jfloat alpha) -{ - const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz)); - if (surface == 0) return; +static void nativeSetAlpha(JNIEnv* env, jobject surfaceObj, jfloat alpha) { + sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj)); + if (surface == NULL) return; + status_t err = surface->setAlpha(alpha); - if (err<0 && err!=NO_INIT) { + if (err < 0 && err != NO_INIT) { doThrowIAE(env); } } -static void Surface_setMatrix( - JNIEnv* env, jobject clazz, - jfloat dsdx, jfloat dtdx, jfloat dsdy, jfloat dtdy) -{ - const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz)); - if (surface == 0) return; +static void nativeSetMatrix(JNIEnv* env, jobject surfaceObj, + jfloat dsdx, jfloat dtdx, jfloat dsdy, jfloat dtdy) { + sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj)); + if (surface == NULL) return; + status_t err = surface->setMatrix(dsdx, dtdx, dsdy, dtdy); - if (err<0 && err!=NO_INIT) { + if (err < 0 && err != NO_INIT) { doThrowIAE(env); } } -static void Surface_setWindowCrop(JNIEnv* env, jobject thiz, jobject crop) -{ - const sp<SurfaceControl>& surface(getSurfaceControl(env, thiz)); - if (surface == 0) return; - - Rect nativeCrop; - if (crop) { - nativeCrop.left = env->GetIntField(crop, ro.l); - nativeCrop.top = env->GetIntField(crop, ro.t); - nativeCrop.right = env->GetIntField(crop, ro.r); - nativeCrop.bottom= env->GetIntField(crop, ro.b); +static void nativeSetWindowCrop(JNIEnv* env, jobject surfaceObj, jobject cropObj) { + const sp<SurfaceControl>& surface(getSurfaceControl(env, surfaceObj)); + if (surface == NULL) return; + + Rect crop; + if (cropObj) { + crop.left = env->GetIntField(cropObj, gRectClassInfo.left); + crop.top = env->GetIntField(cropObj, gRectClassInfo.top); + crop.right = env->GetIntField(cropObj, gRectClassInfo.right); + crop.bottom = env->GetIntField(cropObj, gRectClassInfo.bottom); } else { - nativeCrop.left = nativeCrop.top = nativeCrop.right = - nativeCrop.bottom = 0; + crop.left = crop.top = crop.right = crop.bottom = 0; } - status_t err = surface->setCrop(nativeCrop); - if (err<0 && err!=NO_INIT) { + status_t err = surface->setCrop(crop); + if (err < 0 && err != NO_INIT) { doThrowIAE(env); } } -static void Surface_setLayerStack(JNIEnv* env, jobject thiz, jint layerStack) -{ - const sp<SurfaceControl>& surface(getSurfaceControl(env, thiz)); - if (surface == 0) return; +static void nativeSetLayerStack(JNIEnv* env, jobject surfaceObj, jint layerStack) { + sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj)); + if (surface == NULL) return; - // TODO(mathias): Everything. + status_t err = surface->setLayerStack(layerStack); + if (err < 0 && err != NO_INIT) { + doThrowIAE(env); + } } -// ---------------------------------------------------------------------------- +static jobject nativeGetBuiltInDisplay(JNIEnv* env, jclass clazz, jint id) { + sp<IBinder> token(SurfaceComposerClient::getBuiltInDisplay(id)); + return javaObjectForIBinder(env, token); +} -static void Surface_copyFrom( - JNIEnv* env, jobject clazz, jobject other) -{ - if (clazz == other) +static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj) { + ScopedUtfChars name(env, nameObj); + // TODO: pass the name to SF. + sp<IBinder> token(SurfaceComposerClient::createDisplay()); + return javaObjectForIBinder(env, token); +} + +static void nativeSetDisplaySurface(JNIEnv* env, jclass clazz, + jobject tokenObj, jobject surfaceTextureObj) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); + if (token == NULL) return; + + if (!surfaceTextureObj) { + SurfaceComposerClient::setDisplaySurface(token, NULL); return; + } - if (other == NULL) { - doThrowNPE(env); + sp<SurfaceTexture> st(SurfaceTexture_getSurfaceTexture(env, surfaceTextureObj)); + if (st == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "SurfaceTexture has already been released"); return; } + sp<ISurfaceTexture> bq = st->getBufferQueue(); + SurfaceComposerClient::setDisplaySurface(token, bq); +} + +static void nativeSetDisplayLayerStack(JNIEnv* env, jclass clazz, + jobject tokenObj, jint layerStack) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); + if (token == NULL) return; + + SurfaceComposerClient::setDisplayLayerStack(token, layerStack); +} + +static void nativeSetDisplayOrientation(JNIEnv* env, jclass clazz, + jobject tokenObj, jint orientation) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); + if (token == NULL) return; + + SurfaceComposerClient::setDisplayOrientation(token, orientation); +} + +static void nativeSetDisplayViewport(JNIEnv* env, jclass clazz, + jobject tokenObj, jobject rectObj) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); + if (token == NULL) return; + + Rect rect; + rect.left = env->GetIntField(rectObj, gRectClassInfo.left); + rect.top = env->GetIntField(rectObj, gRectClassInfo.top); + rect.right = env->GetIntField(rectObj, gRectClassInfo.right); + rect.bottom = env->GetIntField(rectObj, gRectClassInfo.bottom); + SurfaceComposerClient::setDisplayViewport(token, rect); +} + +static void nativeSetDisplayFrame(JNIEnv* env, jclass clazz, + jobject tokenObj, jobject rectObj) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); + if (token == NULL) return; + + Rect rect; + rect.left = env->GetIntField(rectObj, gRectClassInfo.left); + rect.top = env->GetIntField(rectObj, gRectClassInfo.top); + rect.right = env->GetIntField(rectObj, gRectClassInfo.right); + rect.bottom = env->GetIntField(rectObj, gRectClassInfo.bottom); + SurfaceComposerClient::setDisplayFrame(token, rect); +} + +static jboolean nativeGetDisplayInfo(JNIEnv* env, jclass clazz, + jobject tokenObj, jobject infoObj) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); + if (token == NULL) return JNI_FALSE; + + DisplayInfo info; + if (SurfaceComposerClient::getDisplayInfo(token, &info)) { + return JNI_FALSE; + } + + env->SetIntField(infoObj, gPhysicalDisplayInfoClassInfo.width, info.w); + env->SetIntField(infoObj, gPhysicalDisplayInfoClassInfo.height, info.h); + env->SetFloatField(infoObj, gPhysicalDisplayInfoClassInfo.refreshRate, info.fps); + env->SetFloatField(infoObj, gPhysicalDisplayInfoClassInfo.density, info.density); + env->SetFloatField(infoObj, gPhysicalDisplayInfoClassInfo.xDpi, info.xdpi); + env->SetFloatField(infoObj, gPhysicalDisplayInfoClassInfo.yDpi, info.ydpi); + return JNI_TRUE; +} + +// ---------------------------------------------------------------------------- + +static void nativeCopyFrom(JNIEnv* env, jobject surfaceObj, jobject otherObj) { /* * This is used by the WindowManagerService just after constructing * a Surface and is necessary for returning the Surface reference to * the caller. At this point, we should only have a SurfaceControl. */ - const sp<SurfaceControl>& surface = getSurfaceControl(env, clazz); - const sp<SurfaceControl>& rhs = getSurfaceControl(env, other); - if (!SurfaceControl::isSameSurface(surface, rhs)) { + sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj)); + sp<SurfaceControl> other(getSurfaceControl(env, otherObj)); + if (!SurfaceControl::isSameSurface(surface, other)) { // we reassign the surface only if it's a different one // otherwise we would loose our client-side state. - setSurfaceControl(env, clazz, rhs); + setSurfaceControl(env, surfaceObj, other); } } -static void Surface_transferFrom( - JNIEnv* env, jobject clazz, jobject other) -{ - if (clazz == other) - return; - - if (other == NULL) { - doThrowNPE(env); - return; - } - - sp<SurfaceControl> control(getSurfaceControl(env, other)); - sp<Surface> surface(Surface_getSurface(env, other)); - setSurfaceControl(env, clazz, control); - setSurface(env, clazz, surface); - setSurfaceControl(env, other, 0); - setSurface(env, other, 0); +static void nativeTransferFrom(JNIEnv* env, jobject surfaceObj, jobject otherObj) { + sp<SurfaceControl> control(getSurfaceControl(env, otherObj)); + sp<Surface> surface(android_view_Surface_getSurface(env, otherObj)); + setSurfaceControl(env, surfaceObj, control); + setSurface(env, surfaceObj, surface); + setSurfaceControl(env, otherObj, NULL); + setSurface(env, otherObj, NULL); } -static void Surface_readFromParcel( - JNIEnv* env, jobject clazz, jobject argParcel) -{ - Parcel* parcel = (Parcel*)env->GetIntField( argParcel, no.native_parcel); +static void nativeReadFromParcel(JNIEnv* env, jobject surfaceObj, jobject parcelObj) { + Parcel* parcel = parcelForJavaObject(env, parcelObj); if (parcel == NULL) { doThrowNPE(env); return; } - sp<Surface> sur(Surface::readFromParcel(*parcel)); - setSurface(env, clazz, sur); + sp<Surface> surface(Surface::readFromParcel(*parcel)); + setSurfaceControl(env, surfaceObj, NULL); + setSurface(env, surfaceObj, surface); } -static void Surface_writeToParcel( - JNIEnv* env, jobject clazz, jobject argParcel, jint flags) -{ - Parcel* parcel = (Parcel*)env->GetIntField( - argParcel, no.native_parcel); - +static void nativeWriteToParcel(JNIEnv* env, jobject surfaceObj, jobject parcelObj) { + Parcel* parcel = parcelForJavaObject(env, parcelObj); if (parcel == NULL) { doThrowNPE(env); return; @@ -802,110 +752,125 @@ static void Surface_writeToParcel( // available we let it parcel itself. Finally, if the Surface is also // NULL we fall back to using the SurfaceControl path which sends an // empty surface; this matches legacy behavior. - const sp<SurfaceControl>& control(getSurfaceControl(env, clazz)); + sp<SurfaceControl> control(getSurfaceControl(env, surfaceObj)); if (control != NULL) { SurfaceControl::writeSurfaceToParcel(control, parcel); } else { - sp<Surface> surface(Surface_getSurface(env, clazz)); + sp<Surface> surface(android_view_Surface_getSurface(env, surfaceObj)); if (surface != NULL) { Surface::writeToParcel(surface, parcel); } else { SurfaceControl::writeSurfaceToParcel(NULL, parcel); } } - if (flags & PARCELABLE_WRITE_RETURN_VALUE) { - setSurfaceControl(env, clazz, NULL); - setSurface(env, clazz, NULL); - } } // ---------------------------------------------------------------------------- -// ---------------------------------------------------------------------------- -// ---------------------------------------------------------------------------- - -static void nativeClassInit(JNIEnv* env, jclass clazz); - -static JNINativeMethod gSurfaceSessionMethods[] = { - {"init", "()V", (void*)SurfaceSession_init }, - {"destroy", "()V", (void*)SurfaceSession_destroy }, - {"kill", "()V", (void*)SurfaceSession_kill }, -}; static JNINativeMethod gSurfaceMethods[] = { - {"nativeClassInit", "()V", (void*)nativeClassInit }, - {"init", "(Landroid/view/SurfaceSession;ILjava/lang/String;IIIII)V", (void*)Surface_init }, - {"init", "(Landroid/os/Parcel;)V", (void*)Surface_initParcel }, - {"initFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)V", (void*)Surface_initFromSurfaceTexture }, - {"getIdentity", "()I", (void*)Surface_getIdentity }, - {"destroy", "()V", (void*)Surface_destroy }, - {"release", "()V", (void*)Surface_release }, - {"copyFrom", "(Landroid/view/Surface;)V", (void*)Surface_copyFrom }, - {"transferFrom", "(Landroid/view/Surface;)V", (void*)Surface_transferFrom }, - {"isValid", "()Z", (void*)Surface_isValid }, - {"lockCanvasNative", "(Landroid/graphics/Rect;)Landroid/graphics/Canvas;", (void*)Surface_lockCanvas }, - {"unlockCanvasAndPost", "(Landroid/graphics/Canvas;)V", (void*)Surface_unlockCanvasAndPost }, - {"unlockCanvas", "(Landroid/graphics/Canvas;)V", (void*)Surface_unlockCanvas }, - {"openTransaction", "()V", (void*)Surface_openTransaction }, - {"closeTransaction", "()V", (void*)Surface_closeTransaction }, - {"setOrientation", "(II)V", (void*)Surface_setOrientation }, - {"screenshot", "(II)Landroid/graphics/Bitmap;", (void*)Surface_screenshotAll }, - {"screenshot", "(IIII)Landroid/graphics/Bitmap;", (void*)Surface_screenshot }, - {"setLayer", "(I)V", (void*)Surface_setLayer }, - {"setPosition", "(FF)V",(void*)Surface_setPosition }, - {"setSize", "(II)V",(void*)Surface_setSize }, - {"hide", "()V", (void*)Surface_hide }, - {"show", "()V", (void*)Surface_show }, - {"setFlags", "(II)V",(void*)Surface_setFlags }, - {"setTransparentRegionHint","(Landroid/graphics/Region;)V", (void*)Surface_setTransparentRegion }, - {"setAlpha", "(F)V", (void*)Surface_setAlpha }, - {"setMatrix", "(FFFF)V", (void*)Surface_setMatrix }, - {"readFromParcel", "(Landroid/os/Parcel;)V", (void*)Surface_readFromParcel }, - {"writeToParcel", "(Landroid/os/Parcel;I)V", (void*)Surface_writeToParcel }, - {"isConsumerRunningBehind", "()Z", (void*)Surface_isConsumerRunningBehind }, - {"setWindowCrop", "(Landroid/graphics/Rect;)V", (void*)Surface_setWindowCrop }, - {"setLayerStack", "(I)V", (void*)Surface_setLayerStack }, + {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIII)V", + (void*)nativeCreate }, + {"nativeCreateFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)V", + (void*)nativeCreateFromSurfaceTexture }, + {"nativeRelease", "()V", + (void*)nativeRelease }, + {"nativeDestroy", "()V", + (void*)nativeDestroy }, + {"nativeIsValid", "()Z", + (void*)nativeIsValid }, + {"nativeGetIdentity", "()I", + (void*)nativeGetIdentity }, + {"nativeIsConsumerRunningBehind", "()Z", + (void*)nativeIsConsumerRunningBehind }, + {"nativeLockCanvas", "(Landroid/graphics/Rect;)Landroid/graphics/Canvas;", + (void*)nativeLockCanvas }, + {"nativeUnlockCanvasAndPost", "(Landroid/graphics/Canvas;)V", + (void*)nativeUnlockCanvasAndPost }, + {"nativeScreenshot", "(Landroid/os/IBinder;IIIIZ)Landroid/graphics/Bitmap;", + (void*)nativeScreenshot }, + {"nativeOpenTransaction", "()V", + (void*)nativeOpenTransaction }, + {"nativeCloseTransaction", "()V", + (void*)nativeCloseTransaction }, + {"nativeSetLayer", "(I)V", + (void*)nativeSetLayer }, + {"nativeSetPosition", "(FF)V", + (void*)nativeSetPosition }, + {"nativeSetSize", "(II)V", + (void*)nativeSetSize }, + {"nativeSetTransparentRegionHint", "(Landroid/graphics/Region;)V", + (void*)nativeSetTransparentRegionHint }, + {"nativeSetAlpha", "(F)V", + (void*)nativeSetAlpha }, + {"nativeSetMatrix", "(FFFF)V", + (void*)nativeSetMatrix }, + {"nativeSetFlags", "(II)V", + (void*)nativeSetFlags }, + {"nativeSetWindowCrop", "(Landroid/graphics/Rect;)V", + (void*)nativeSetWindowCrop }, + {"nativeSetLayerStack", "(I)V", + (void*)nativeSetLayerStack }, + {"nativeGetBuiltInDisplay", "(I)Landroid/os/IBinder;", + (void*)nativeGetBuiltInDisplay }, + {"nativeCreateDisplay", "(Ljava/lang/String;)Landroid/os/IBinder;", + (void*)nativeCreateDisplay }, + {"nativeSetDisplaySurface", "(Landroid/os/IBinder;Landroid/graphics/SurfaceTexture;)V", + (void*)nativeSetDisplaySurface }, + {"nativeSetDisplayLayerStack", "(Landroid/os/IBinder;I)V", + (void*)nativeSetDisplayLayerStack }, + {"nativeSetDisplayOrientation", "(Landroid/os/IBinder;I)V", + (void*)nativeSetDisplayOrientation }, + {"nativeSetDisplayViewport", "(Landroid/os/IBinder;Landroid/graphics/Rect;)V", + (void*)nativeSetDisplayViewport }, + {"nativeSetDisplayFrame", "(Landroid/os/IBinder;Landroid/graphics/Rect;)V", + (void*)nativeSetDisplayFrame }, + {"nativeGetDisplayInfo", "(Landroid/os/IBinder;Landroid/view/Surface$PhysicalDisplayInfo;)Z", + (void*)nativeGetDisplayInfo }, + {"nativeCopyFrom", "(Landroid/view/Surface;)V", + (void*)nativeCopyFrom }, + {"nativeTransferFrom", "(Landroid/view/Surface;)V", + (void*)nativeTransferFrom }, + {"nativeReadFromParcel", "(Landroid/os/Parcel;)V", + (void*)nativeReadFromParcel }, + {"nativeWriteToParcel", "(Landroid/os/Parcel;)V", + (void*)nativeWriteToParcel }, }; -void nativeClassInit(JNIEnv* env, jclass clazz) -{ - so.surface = env->GetFieldID(clazz, ANDROID_VIEW_SURFACE_JNI_ID, "I"); - so.surfaceGenerationId = env->GetFieldID(clazz, "mSurfaceGenerationId", "I"); - so.surfaceControl = env->GetFieldID(clazz, "mSurfaceControl", "I"); - so.saveCount = env->GetFieldID(clazz, "mSaveCount", "I"); - so.canvas = env->GetFieldID(clazz, "mCanvas", "Landroid/graphics/Canvas;"); - - jclass surfaceSession = env->FindClass("android/view/SurfaceSession"); - sso.client = env->GetFieldID(surfaceSession, "mClient", "I"); - - jclass canvas = env->FindClass("android/graphics/Canvas"); - no.native_canvas = env->GetFieldID(canvas, "mNativeCanvas", "I"); - co.surfaceFormat = env->GetFieldID(canvas, "mSurfaceFormat", "I"); - - jclass region = env->FindClass("android/graphics/Region"); - no.native_region = env->GetFieldID(region, "mNativeRegion", "I"); - - jclass parcel = env->FindClass("android/os/Parcel"); - no.native_parcel = env->GetFieldID(parcel, "mNativePtr", "I"); - - jclass rect = env->FindClass("android/graphics/Rect"); - ro.l = env->GetFieldID(rect, "left", "I"); - ro.t = env->GetFieldID(rect, "top", "I"); - ro.r = env->GetFieldID(rect, "right", "I"); - ro.b = env->GetFieldID(rect, "bottom", "I"); - - jclass point = env->FindClass("android/graphics/Point"); - po.x = env->GetFieldID(point, "x", "I"); - po.y = env->GetFieldID(point, "y", "I"); -} - int register_android_view_Surface(JNIEnv* env) { - int err; - err = AndroidRuntime::registerNativeMethods(env, kSurfaceSessionClassPathName, - gSurfaceSessionMethods, NELEM(gSurfaceSessionMethods)); - - err |= AndroidRuntime::registerNativeMethods(env, kSurfaceClassPathName, + int err = AndroidRuntime::registerNativeMethods(env, "android/view/Surface", gSurfaceMethods, NELEM(gSurfaceMethods)); + + jclass clazz = env->FindClass("android/view/Surface"); + gSurfaceClassInfo.clazz = jclass(env->NewGlobalRef(clazz)); + gSurfaceClassInfo.mNativeSurface = + env->GetFieldID(gSurfaceClassInfo.clazz, ANDROID_VIEW_SURFACE_JNI_ID, "I"); + gSurfaceClassInfo.mNativeSurfaceControl = + env->GetFieldID(gSurfaceClassInfo.clazz, "mNativeSurfaceControl", "I"); + gSurfaceClassInfo.mGenerationId = + env->GetFieldID(gSurfaceClassInfo.clazz, "mGenerationId", "I"); + gSurfaceClassInfo.mCanvas = + env->GetFieldID(gSurfaceClassInfo.clazz, "mCanvas", "Landroid/graphics/Canvas;"); + gSurfaceClassInfo.mCanvasSaveCount = + env->GetFieldID(gSurfaceClassInfo.clazz, "mCanvasSaveCount", "I"); + + clazz = env->FindClass("android/graphics/Canvas"); + gCanvasClassInfo.mNativeCanvas = env->GetFieldID(clazz, "mNativeCanvas", "I"); + gCanvasClassInfo.mSurfaceFormat = env->GetFieldID(clazz, "mSurfaceFormat", "I"); + + clazz = env->FindClass("android/graphics/Rect"); + gRectClassInfo.left = env->GetFieldID(clazz, "left", "I"); + gRectClassInfo.top = env->GetFieldID(clazz, "top", "I"); + gRectClassInfo.right = env->GetFieldID(clazz, "right", "I"); + gRectClassInfo.bottom = env->GetFieldID(clazz, "bottom", "I"); + + clazz = env->FindClass("android/view/Surface$PhysicalDisplayInfo"); + gPhysicalDisplayInfoClassInfo.width = env->GetFieldID(clazz, "width", "I"); + gPhysicalDisplayInfoClassInfo.height = env->GetFieldID(clazz, "height", "I"); + gPhysicalDisplayInfoClassInfo.refreshRate = env->GetFieldID(clazz, "refreshRate", "F"); + gPhysicalDisplayInfoClassInfo.density = env->GetFieldID(clazz, "density", "F"); + gPhysicalDisplayInfoClassInfo.xDpi = env->GetFieldID(clazz, "xDpi", "F"); + gPhysicalDisplayInfoClassInfo.yDpi = env->GetFieldID(clazz, "yDpi", "F"); return err; } diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp new file mode 100644 index 0000000..1494bc5 --- /dev/null +++ b/core/jni/android_view_SurfaceSession.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "SurfaceSession" + +#include "JNIHelp.h" + +#include <android_runtime/AndroidRuntime.h> +#include <android_runtime/android_view_SurfaceSession.h> +#include <utils/Log.h> +#include <utils/RefBase.h> + +#include <gui/SurfaceComposerClient.h> + +namespace android { + +static struct { + jfieldID mNativeClient; +} gSurfaceSessionClassInfo; + + +sp<SurfaceComposerClient> android_view_SurfaceSession_getClient( + JNIEnv* env, jobject surfaceSessionObj) { + return reinterpret_cast<SurfaceComposerClient*>( + env->GetIntField(surfaceSessionObj, gSurfaceSessionClassInfo.mNativeClient)); +} + + +static jint nativeCreate(JNIEnv* env, jclass clazz) { + SurfaceComposerClient* client = new SurfaceComposerClient(); + client->incStrong(clazz); + return reinterpret_cast<jint>(client); +} + +static void nativeDestroy(JNIEnv* env, jclass clazz, jint ptr) { + SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr); + client->decStrong(clazz); +} + +static void nativeKill(JNIEnv* env, jclass clazz, jint ptr) { + SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr); + client->dispose(); +} + + +static JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + { "nativeCreate", "()I", + (void*)nativeCreate }, + { "nativeDestroy", "(I)V", + (void*)nativeDestroy }, + { "nativeKill", "(I)V", + (void*)nativeKill } +}; + +int register_android_view_SurfaceSession(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "android/view/SurfaceSession", + gMethods, NELEM(gMethods)); + LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); + + jclass clazz = env->FindClass("android/view/SurfaceSession"); + gSurfaceSessionClassInfo.mNativeClient = env->GetFieldID(clazz, "mNativeClient", "I"); + return 0; +} + +} // namespace android diff --git a/core/jni/com_google_android_gles_jni_EGLImpl.cpp b/core/jni/com_google_android_gles_jni_EGLImpl.cpp index 9c6c7de..f8904bd 100644 --- a/core/jni/com_google_android_gles_jni_EGLImpl.cpp +++ b/core/jni/com_google_android_gles_jni_EGLImpl.cpp @@ -326,7 +326,7 @@ not_valid_surface: return 0; } - window = android_Surface_getNativeWindow(_env, native_window); + window = android_view_Surface_getNativeWindow(_env, native_window); if (window == NULL) goto not_valid_surface; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 1c9b440..abb9c0f 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -62,6 +62,7 @@ <protected-broadcast android:name="android.intent.action.MASTER_CLEAR_NOTIFICATION" /> <protected-broadcast android:name="android.intent.action.USER_ADDED" /> <protected-broadcast android:name="android.intent.action.USER_REMOVED" /> + <protected-broadcast android:name="android.intent.action.USER_STOPPED" /> <protected-broadcast android:name="android.intent.action.USER_SWITCHED" /> <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE" /> @@ -724,6 +725,13 @@ android:description="@string/permdesc_mediaStorageWrite" android:protectionLevel="signature|system" /> + <!-- Allows an application to access all multi-user external storage @hide --> + <permission android:name="android.permission.ACCESS_ALL_EXTERNAL_STORAGE" + android:permissionGroup="android.permission-group.DEVELOPMENT_TOOLS" + android:label="@string/permlab_sdcardAccessAll" + android:description="@string/permdesc_sdcardAccessAll" + android:protectionLevel="signature" /> + <!-- ============================================ --> <!-- Permissions for low-level system interaction --> <!-- ============================================ --> @@ -1662,7 +1670,6 @@ <!-- Package verifier needs to have this permission before the PackageManager will trust it to verify packages. - @hide --> <permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" android:label="@string/permlab_packageVerificationAgent" diff --git a/core/res/res/anim/keyguard_security_animate_in.xml b/core/res/res/anim/keyguard_security_animate_in.xml new file mode 100644 index 0000000..6e1e17a --- /dev/null +++ b/core/res/res/anim/keyguard_security_animate_in.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> + + <scale + android:interpolator="@android:anim/decelerate_interpolator" + android:fromXScale="0.0" + android:toXScale="1.0" + android:fromYScale="1.0" + android:toYScale="1.0" + android:pivotX="50%" + android:pivotY="50%" + android:fillEnabled="true" + android:fillAfter="true" + android:duration="@integer/flip_duration" + android:startOffset="@integer/flip_duration" /> + +</set> + diff --git a/core/res/res/anim/keyguard_security_animate_out.xml b/core/res/res/anim/keyguard_security_animate_out.xml new file mode 100644 index 0000000..5d65cd0 --- /dev/null +++ b/core/res/res/anim/keyguard_security_animate_out.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> + + <scale + android:interpolator="@android:anim/accelerate_interpolator" + android:fromXScale="1.0" + android:toXScale="0.0" + android:fromYScale="1.0" + android:toYScale="1.0" + android:pivotX="50%" + android:pivotY="50%" + android:fillEnabled="true" + android:fillAfter="true" + android:duration="@integer/flip_duration" /> + +</set> + diff --git a/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_left.9.png b/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_left.9.png Binary files differnew file mode 100644 index 0000000..c30eb1c --- /dev/null +++ b/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_left.9.png diff --git a/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_right.9.png b/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_right.9.png Binary files differnew file mode 100644 index 0000000..e5d5771 --- /dev/null +++ b/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_right.9.png diff --git a/core/res/res/layout-land/keyguard_host_view.xml b/core/res/res/layout-land/keyguard_host_view.xml new file mode 100644 index 0000000..b404155 --- /dev/null +++ b/core/res/res/layout-land/keyguard_host_view.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- This is the host view that generally contains two sub views: the widget view + and the security view. --> +<com.android.internal.policy.impl.keyguard.KeyguardHostView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_host_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal"> + + <com.android.internal.policy.impl.keyguard.KeyguardWidgetPager + android:id="@+id/app_widget_container" + android:layout_width="0dip" + android:layout_height="match_parent" + android:layout_weight="1" + android:visibility="gone"> + + <!-- TODO: Remove this once supported as a widget --> + <include layout="@layout/keyguard_status_view"/> + </com.android.internal.policy.impl.keyguard.KeyguardWidgetPager> + + + <ViewFlipper + android:id="@+id/view_flipper" + android:layout_width="0dip" + android:layout_height="match_parent" + android:layout_weight="1" + android:gravity="center"> + + <include layout="@layout/keyguard_selector_view"/> + <include layout="@layout/keyguard_account_view"/> + <include layout="@layout/keyguard_pattern_view"/> + <include layout="@layout/keyguard_password_view"/> + <include layout="@layout/keyguard_sim_pin_view"/> + <include layout="@layout/keyguard_sim_puk_view"/> + <include layout="@layout/keyguard_face_unlock_view"/> + + </ViewFlipper> + +</com.android.internal.policy.impl.keyguard.KeyguardHostView> diff --git a/core/res/res/layout-port/keyguard_host_view.xml b/core/res/res/layout-port/keyguard_host_view.xml new file mode 100644 index 0000000..5dc2225 --- /dev/null +++ b/core/res/res/layout-port/keyguard_host_view.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- This is the host view that generally contains two sub views: the widget view + and the security view. --> +<com.android.internal.policy.impl.keyguard.KeyguardHostView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_host_view" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_horizontal" + android:clipChildren="false"> + + <ViewFlipper + android:id="@+id/view_flipper" + android:layout_height="match_parent" + android:gravity="center"> + + <include layout="@layout/keyguard_selector_view"/> + <include layout="@layout/keyguard_account_view"/> + <include layout="@layout/keyguard_pattern_view"/> + <include layout="@layout/keyguard_password_view"/> + <include layout="@layout/keyguard_sim_pin_view"/> + <include layout="@layout/keyguard_sim_puk_view"/> + <include layout="@layout/keyguard_face_unlock_view"/> + + </ViewFlipper> + +</com.android.internal.policy.impl.keyguard.KeyguardHostView> + diff --git a/core/res/res/layout-sw600dp-land/keyguard_host_view.xml b/core/res/res/layout-sw600dp-land/keyguard_host_view.xml new file mode 100644 index 0000000..e77f584 --- /dev/null +++ b/core/res/res/layout-sw600dp-land/keyguard_host_view.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- This is the host view that generally contains two sub views: the widget view + and the security view. --> +<com.android.internal.policy.impl.keyguard.KeyguardHostView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_host_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal"> + + <com.android.internal.policy.impl.keyguard.KeyguardWidgetPager + android:id="@+id/app_widget_container" + android:layout_width="0dip" + android:layout_height="match_parent" + android:layout_weight="1" + android:visibility="gone"> + + <!-- TODO: Remove this once supported as a widget --> + <include layout="@layout/keyguard_status_view"/> + + </com.android.internal.policy.impl.keyguard.KeyguardWidgetPager> + + <FrameLayout + android:layout_width="0dip" + android:layout_height="match_parent" + android:layout_weight="1" + android:gravity="center"> + + <ViewFlipper + android:id="@+id/view_flipper" + android:layout_width="@dimen/kg_security_view_width" + android:layout_height="match_parent" + android:layout_gravity="center" + android:layout_weight="1" + android:gravity="center"> + + <include layout="@layout/keyguard_selector_view"/> + <include layout="@layout/keyguard_account_view"/> + <include layout="@layout/keyguard_pattern_view"/> + <include layout="@layout/keyguard_password_view"/> + <include layout="@layout/keyguard_sim_pin_view"/> + <include layout="@layout/keyguard_sim_puk_view"/> + <include layout="@layout/keyguard_face_unlock_view"/> + + </ViewFlipper> + + </FrameLayout> + +</com.android.internal.policy.impl.keyguard.KeyguardHostView> diff --git a/core/res/res/layout-sw600dp-port/keyguard_host_view.xml b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml new file mode 100644 index 0000000..50636f1 --- /dev/null +++ b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- This is the host view that generally contains two sub views: the widget view + and the security view. --> +<com.android.internal.policy.impl.keyguard.KeyguardHostView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_host_view" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_horizontal" + android:clipChildren="false"> + + <com.android.internal.policy.impl.keyguard.KeyguardWidgetPager + android:id="@+id/app_widget_container" + android:layout_width="match_parent" + android:layout_height="0dip" + android:layout_weight="1" + android:visibility="gone"> + + <!-- TODO: Remove this once supported as a widget --> + <include layout="@layout/keyguard_status_view"/> + + </com.android.internal.policy.impl.keyguard.KeyguardWidgetPager> + + <ViewFlipper + android:id="@+id/view_flipper" + android:layout_width="@dimen/kg_security_view_width" + android:layout_height="0dip" + android:layout_weight="1" + android:layout_gravity="center"> + + <include layout="@layout/keyguard_selector_view"/> + <include layout="@layout/keyguard_account_view"/> + <include layout="@layout/keyguard_pattern_view"/> + <include layout="@layout/keyguard_password_view"/> + <include layout="@layout/keyguard_sim_pin_view"/> + <include layout="@layout/keyguard_sim_puk_view"/> + <include layout="@layout/keyguard_face_unlock_view"/> + + </ViewFlipper> + +</com.android.internal.policy.impl.keyguard.KeyguardHostView> + diff --git a/core/res/res/layout/keyguard_account_view.xml b/core/res/res/layout/keyguard_account_view.xml new file mode 100644 index 0000000..481f0c1 --- /dev/null +++ b/core/res/res/layout/keyguard_account_view.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<com.android.internal.policy.impl.keyguard.KeyguardAccountView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_account_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <include layout="@layout/keyguard_navigation"/> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="0dip" + android:layout_weight="1"> + + <EditText + android:id="@+id/login" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dip" + android:layout_marginStart="7dip" + android:layout_marginEnd="7dip" + android:layout_alignParentTop="true" + android:hint="@string/kg_login_username_hint" + android:inputType="textEmailAddress" + /> + + <EditText + android:id="@+id/password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/login" + android:layout_marginTop="15dip" + android:layout_marginStart="7dip" + android:layout_marginEnd="7dip" + android:inputType="textPassword" + android:hint="@string/kg_login_password_hint" + android:nextFocusRight="@+id/ok" + android:nextFocusDown="@+id/ok" + /> + + <!-- ok below password, aligned to right of screen --> + <Button + android:id="@+id/ok" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="7dip" + android:layout_alignParentEnd="true" + android:layout_alignParentBottom="true" + android:text="@string/kg_login_submit_button" + /> + + </RelativeLayout> + +</com.android.internal.policy.impl.keyguard.KeyguardAccountView> diff --git a/core/res/res/layout/keyguard_face_unlock_view.xml b/core/res/res/layout/keyguard_face_unlock_view.xml new file mode 100644 index 0000000..572c013 --- /dev/null +++ b/core/res/res/layout/keyguard_face_unlock_view.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- This is the screen that shows the 9 circle unlock widget and instructs + the user how to unlock their device, or make an emergency call. This + is the portrait layout. --> +<com.android.internal.policy.impl.keyguard.KeyguardFaceUnlockView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_face_unlock_view" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <!-- TODO --> + +</com.android.internal.policy.impl.keyguard.KeyguardFaceUnlockView> diff --git a/core/res/res/layout/keyguard_navigation.xml b/core/res/res/layout/keyguard_navigation.xml new file mode 100644 index 0000000..569f93d --- /dev/null +++ b/core/res/res/layout/keyguard_navigation.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2008, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="left"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <TextView + android:id="@+id/back" + android:layout_width="20dip" + android:layout_height="wrap_content" + android:textSize="28dp" + android:text="@string/kg_temp_back_string" /> + + <!-- message area for security screen --> + <TextView + android:id="@+id/message_area" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + android:singleLine="true" + android:ellipsize="marquee" + android:layout_marginEnd="6dip" + android:layout_marginStart="6dip" + android:textAppearance="?android:attr/textAppearanceMedium"/> + + </LinearLayout> + + <!-- This is currently only uses for pattern unlock --> + <Button android:id="@+id/forgot_password_button" + android:layout_gravity="right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size" + android:drawableLeft="@*android:drawable/lockscreen_forgot_password_button" + android:drawablePadding="0dip" + android:visibility="gone"/> + +</LinearLayout> diff --git a/core/res/res/layout/keyguard_password_view.xml b/core/res/res/layout/keyguard_password_view.xml new file mode 100644 index 0000000..b9a70e5 --- /dev/null +++ b/core/res/res/layout/keyguard_password_view.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2008, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<com.android.internal.policy.impl.keyguard.KeyguardPasswordView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_password_view" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_horizontal"> + + <include layout="@layout/keyguard_navigation"/> + + <Space + android:layout_width="match_parent" + android:layout_height="0dip" + android:layout_weight="1"/> + + <!-- Password entry field --> + <!-- Note: the entire container is styled to look like the edit field, + since the backspace/IME switcher looks better inside --> + <LinearLayout + android:layout_gravity="center_vertical|fill_horizontal" + android:layout_width="match_parent" + android:orientation="horizontal" + android:background="@*android:drawable/lockscreen_password_field_dark" + android:layout_marginStart="16dip" + android:layout_marginEnd="16dip"> + + <EditText android:id="@+id/passwordEntry" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center_horizontal" + android:layout_gravity="center_vertical" + android:layout_marginStart="@*android:dimen/keyguard_lockscreen_pin_margin_left" + android:singleLine="true" + android:textStyle="normal" + android:inputType="textPassword" + android:textSize="36sp" + android:background="@null" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="#ffffffff" + android:imeOptions="flagForceAscii|actionDone" + /> + + <!-- This delete button is only visible for numeric PIN entry --> + <ImageButton android:id="@+id/delete_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@*android:drawable/ic_input_delete" + android:clickable="true" + android:padding="8dip" + android:layout_gravity="center_vertical" + android:background="?android:attr/selectableItemBackground" + android:visibility="gone" + /> + + <ImageView android:id="@+id/switch_ime_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@*android:drawable/ic_lockscreen_ime" + android:clickable="true" + android:padding="8dip" + android:layout_gravity="center" + android:background="?android:attr/selectableItemBackground" + android:visibility="gone" + /> + + </LinearLayout> + + <!-- Numeric keyboard --> + <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard" + android:layout_width="match_parent" + android:layout_marginStart="4dip" + android:layout_marginEnd="4dip" + android:paddingTop="4dip" + android:paddingBottom="4dip" + android:background="#40000000" + android:keyBackground="@*android:drawable/btn_keyboard_key_ics" + android:visibility="gone" + android:clickable="true" + /> + +</com.android.internal.policy.impl.keyguard.KeyguardPasswordView> diff --git a/core/res/res/layout/keyguard_pattern_view.xml b/core/res/res/layout/keyguard_pattern_view.xml new file mode 100644 index 0000000..9cba609 --- /dev/null +++ b/core/res/res/layout/keyguard_pattern_view.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- This is the screen that shows the 9 circle unlock widget and instructs + the user how to unlock their device, or make an emergency call. This + is the portrait layout. --> +<com.android.internal.policy.impl.keyguard.KeyguardPatternView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_pattern_view" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <GridLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_horizontal"> + + <include layout="@layout/keyguard_navigation"/> + + <Space android:layout_gravity="fill" /> + + <!-- We need MATCH_PARENT here only to force the size of the parent to be passed to + the pattern view for it to compute its size. This is an unusual case, caused by + LockPatternView's requirement to maintain a square aspect ratio based on the width + of the screen. --> + <com.android.internal.widget.LockPatternView + android:id="@+id/lockPatternView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginEnd="8dip" + android:layout_marginBottom="4dip" + android:layout_marginStart="8dip" + android:layout_gravity="center_horizontal" + /> + + </GridLayout> + +</com.android.internal.policy.impl.keyguard.KeyguardPatternView> diff --git a/core/res/res/layout/keyguard_selector_view.xml b/core/res/res/layout/keyguard_selector_view.xml new file mode 100644 index 0000000..d516369 --- /dev/null +++ b/core/res/res/layout/keyguard_selector_view.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- This is the selector widget that allows the user to select an action. --> +<com.android.internal.policy.impl.keyguard.KeyguardSelectorView + xmlns:prvandroid="http://schemas.android.com/apk/prv/res/android" + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_selector_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <com.android.internal.policy.impl.keyguard.KeyguardWidgetPager + android:id="@+id/app_widget_container" + android:layout_width="match_parent" + android:layout_height="0dip" + android:layout_weight="1" + android:visibility="gone"> + <!-- TODO: Remove this once supported as a widget --> + <include layout="@layout/keyguard_status_view"/> + </com.android.internal.policy.impl.keyguard.KeyguardWidgetPager> + + <RelativeLayout + android:layout_width="wrap_content" + android:layout_height="0dip" + android:layout_weight="1" + android:layout_gravity="center" + android:gravity="center"> + + <com.android.internal.widget.multiwaveview.GlowPadView + android:id="@+id/glow_pad_view" + android:orientation="horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:focusable="true" + + prvandroid:targetDrawables="@*android:array/lockscreen_targets_with_camera" + prvandroid:targetDescriptions="@*android:array/lockscreen_target_descriptions_with_camera" + prvandroid:directionDescriptions="@*android:array/lockscreen_direction_descriptions" + prvandroid:handleDrawable="@*android:drawable/ic_lockscreen_handle" + prvandroid:outerRingDrawable="@*android:drawable/ic_lockscreen_outerring" + prvandroid:outerRadius="@*android:dimen/glowpadview_target_placement_radius" + prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius" + prvandroid:snapMargin="@*android:dimen/glowpadview_snap_margin" + prvandroid:feedbackCount="1" + prvandroid:vibrationDuration="20" + prvandroid:glowRadius="@*android:dimen/glowpadview_glow_radius" + prvandroid:pointDrawable="@*android:drawable/ic_lockscreen_glowdot"/> + + <Button + android:id="@+id/emergency_call_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end" + android:drawableLeft="@*android:drawable/lockscreen_emergency_button" + android:text="@string/kg_emergency_call_label" + style="?android:attr/buttonBarButtonStyle" + android:drawablePadding="8dip" + android:layout_alignRight="@id/glow_pad_view" + android:layout_alignTop="@id/glow_pad_view" + /> + + </RelativeLayout> + +</com.android.internal.policy.impl.keyguard.KeyguardSelectorView> + diff --git a/core/res/res/layout/keyguard_sim_pin_view.xml b/core/res/res/layout/keyguard_sim_pin_view.xml new file mode 100644 index 0000000..122484a --- /dev/null +++ b/core/res/res/layout/keyguard_sim_pin_view.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<!-- This is the SIM PIN view that allows the user to enter a SIM PIN to unlock the device. --> +<com.android.internal.policy.impl.keyguard.KeyguardSimPinView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_sim_pin_view" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_horizontal"> + + <include layout="@layout/keyguard_navigation"/> + + <Space + android:layout_width="match_parent" + android:layout_height="0dip" + android:layout_weight="1"/> + + <!-- Password entry field --> + <!-- Note: the entire container is styled to look like the edit field, + since the backspace/IME switcher looks better inside --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginEnd="6dip" + android:layout_marginStart="6dip" + android:gravity="center_vertical" + android:background="@android:drawable/edit_text"> + + <!-- displays dots as user enters pin --> + <EditText android:id="@+id/sim_pin_entry" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + android:maxLines="1" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceLargeInverse" + android:textColor="@*android:color/primary_text_holo_light" + android:textStyle="bold" + android:inputType="textPassword" + android:imeOptions="flagForceAscii|actionDone" + /> + + <ImageButton android:id="@+id/delete_button" + android:src="@android:drawable/ic_input_delete" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="-3dip" + android:layout_marginBottom="-3dip" + /> + </LinearLayout> + + <!-- Numeric keyboard --> + <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard" + android:layout_width="match_parent" + android:layout_marginStart="4dip" + android:layout_marginEnd="4dip" + android:paddingTop="4dip" + android:paddingBottom="4dip" + android:background="#80ffffff" + android:keyBackground="@*android:drawable/btn_keyboard_key_ics" + android:clickable="true" + /> + +</com.android.internal.policy.impl.keyguard.KeyguardSimPinView> diff --git a/core/res/res/layout/keyguard_sim_puk_view.xml b/core/res/res/layout/keyguard_sim_puk_view.xml new file mode 100644 index 0000000..8bb76c1 --- /dev/null +++ b/core/res/res/layout/keyguard_sim_puk_view.xml @@ -0,0 +1,133 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2008, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<com.android.internal.policy.impl.keyguard.KeyguardSimPukView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_sim_puk_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center_horizontal"> + + <include layout="@layout/keyguard_navigation"/> + + <Space + android:layout_width="match_parent" + android:layout_height="0dip" + android:layout_weight="1"/> + + <LinearLayout android:id="@+id/topDisplayGroup" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <LinearLayout + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_weight="1" + android:layout_height="match_parent" + android:paddingEnd="0dip" + android:layout_marginEnd="10dip" + android:layout_marginStart="10dip"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginEnd="6dip" + android:layout_marginStart="6dip" + android:gravity="center_vertical" + android:background="@*android:drawable/edit_text"> + + <!-- displays dots as user enters puk --> + <EditText android:id="@+id/sim_puk_entry" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + android:maxLines="1" + android:textStyle="bold" + android:inputType="textPassword" + android:textColor="#000" + android:hint="@string/kg_puk_enter_puk_hint" + /> + + <ImageButton android:id="@+id/puk_delete_button" + android:src="@*android:drawable/ic_input_delete" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="-3dip" + android:layout_marginBottom="-3dip" + /> + + </LinearLayout> + + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginEnd="6dip" + android:layout_marginStart="6dip" + android:gravity="center_vertical" + android:background="@*android:drawable/edit_text"> + + <!-- displays dots as user enters new pin --> + <EditText android:id="@+id/sim_pin_entry" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + android:maxLines="1" + android:textStyle="bold" + android:inputType="textPassword" + android:textColor="#000" + android:hint="@string/kg_puk_enter_pin_hint" + /> + + <ImageButton android:id="@+id/pin_delete_button" + android:src="@*android:drawable/ic_input_delete" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="-3dip" + android:layout_marginBottom="-3dip" + /> + </LinearLayout> + + </LinearLayout> + + </LinearLayout> + + </LinearLayout> + + <!-- Numeric keyboard --> + <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard" + android:layout_width="match_parent" + android:layout_marginStart="4dip" + android:layout_marginEnd="4dip" + android:paddingTop="4dip" + android:paddingBottom="4dip" + android:background="#80ffffff" + android:keyBackground="@*android:drawable/btn_keyboard_key_ics" + android:clickable="true" + /> + +</com.android.internal.policy.impl.keyguard.KeyguardSimPukView> diff --git a/core/res/res/layout/keyguard_status_view.xml b/core/res/res/layout/keyguard_status_view.xml new file mode 100644 index 0000000..f8d05b7 --- /dev/null +++ b/core/res/res/layout/keyguard_status_view.xml @@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 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. +*/ +--> + +<!-- This is a view that shows general status information in Keyguard. --> +<com.android.internal.policy.impl.keyguard.KeyguardWidgetFrame + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center_horizontal"> + + <com.android.internal.policy.impl.keyguard.KeyguardStatusView + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_horizontal"> + + <com.android.internal.widget.DigitalClock android:id="@+id/time" + android:layout_marginTop="@*android:dimen/keyguard_lockscreen_status_line_clockfont_top_margin" + android:layout_marginBottom="12dip" + android:layout_marginEnd="@*android:dimen/keyguard_lockscreen_status_line_font_right_margin" + android:layout_gravity="end"> + + <!-- Because we can't have multi-tone fonts, we render two TextViews, one on + top of the other. Hence the redundant layout... --> + <TextView android:id="@*android:id/timeDisplayBackground" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="none" + android:textSize="@*android:dimen/keyguard_lockscreen_clock_font_size" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_marginBottom="6dip" + android:textColor="@*android:color/lockscreen_clock_background" + /> + + <TextView android:id="@*android:id/timeDisplayForeground" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="none" + android:textSize="@*android:dimen/keyguard_lockscreen_clock_font_size" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_marginBottom="6dip" + android:textColor="@*android:color/lockscreen_clock_foreground" + android:layout_alignStart="@*android:id/timeDisplayBackground" + android:layout_alignTop="@*android:id/timeDisplayBackground" + /> + + </com.android.internal.widget.DigitalClock> + + <LinearLayout + android:orientation="horizontal" + android:layout_gravity="end" + android:layout_marginEnd="@*android:dimen/keyguard_lockscreen_status_line_font_right_margin"> + + <TextView + android:id="@*android:id/date" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="marquee" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size" + /> + + <TextView + android:id="@*android:id/alarm_status" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dip" + android:singleLine="true" + android:ellipsize="marquee" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size" + android:drawablePadding="4dip" + /> + + </LinearLayout> + + <TextView + android:id="@*android:id/status1" + android:layout_gravity="end" + android:layout_marginEnd="@*android:dimen/keyguard_lockscreen_status_line_font_right_margin" + android:singleLine="true" + android:ellipsize="marquee" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size" + android:drawablePadding="4dip" + /> + + <TextView + android:id="@*android:id/carrier" + android:layout_gravity="end" + android:layout_marginEnd="@*android:dimen/keyguard_lockscreen_status_line_font_right_margin" + android:singleLine="true" + android:ellipsize="marquee" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size" + android:textColor="?android:attr/textColorSecondary" + /> + + </com.android.internal.policy.impl.keyguard.KeyguardStatusView> +</com.android.internal.policy.impl.keyguard.KeyguardWidgetFrame>
\ No newline at end of file diff --git a/core/res/res/layout/overlay_display_window.xml b/core/res/res/layout/overlay_display_window.xml new file mode 100644 index 0000000..25c792a --- /dev/null +++ b/core/res/res/layout/overlay_display_window.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <TextureView android:id="@+id/overlay_display_window_texture" + android:layout_width="0px" + android:layout_height="0px" /> + <TextView android:id="@+id/overlay_display_window_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|center_horizontal" /> +</FrameLayout> diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index 560a6a8..7aa8bf0 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN is geaktiveer deur <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Raak om die netwerk te bestuur."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Gekoppel aan <xliff:g id="SESSION">%s</xliff:g>. Raak om die netwerk te bestuur."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Kies lêer"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Geen lêer gekies nie"</string> <string name="reset" msgid="2448168080964209908">"Stel terug"</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index fc6d4b8..3068fb9 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -225,7 +225,7 @@ <string name="permlab_interactAcrossUsersFull" msgid="2567734285545074105">"በተለያዩ ተጠቃሚዎች መካከል መስተጋብር ለመፍጠር ሙሉ ፍቃድ"</string> <string name="permdesc_interactAcrossUsersFull" msgid="376841368395502366">"በተለያዩ ተጠቃሚዎች ላይ ሊኖሩ የሚችሉ መስተጋብሮችን ሁሉ ይፈቅዳል።"</string> <string name="permlab_manageUsers" msgid="1676150911672282428">"ተጠቃሚዎችን ያስተዳድሩ"</string> - <string name="permdesc_manageUsers" msgid="8409306667645355638">"መተግበሪያዎች በመሣሪያዎች ላይ ያሉ ተጠቃሚዎችን እንዲያቀናብር ያስችለዋል፣ መጠየቅን፣ መፍጠርንና መሰረዝን ጨምሮ።"</string> + <string name="permdesc_manageUsers" msgid="8409306667645355638">"መተግበሪያዎች በመሣሪያው ላይ ያሉ ተጠቃሚዎችን እንዲያቀናብር ያስችለዋል፣ መጠየቅን፣ መፍጠርንና መሰረዝን ጨምሮ።"</string> <string name="permlab_getDetailedTasks" msgid="6229468674753529501">"እየሄዱ ስላሉ የመተግበሪያዎች ዝርዝሮች አምጣ"</string> <string name="permdesc_getDetailedTasks" msgid="153824741440717599">"መተግበሪያው በአሁኑ ጊዜ እየተካሄዱ ስላሉና በቅርብ ጊዜ ስለተካሄዱ ተግባሮች መረጃ ዝርዝር እንዲያወጣ ይፈቅድለታል። ተንኮል-አዘል መተግበሪያዎች ስለ ሌሎች መተግበሪያዎች የግል መረጃ ሊያገኙ ይችላሉ።"</string> <string name="permlab_reorderTasks" msgid="2018575526934422779">"አሂድ ትግበራዎችን ድጋሚ ደርድር"</string> @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN በ<xliff:g id="APP">%s</xliff:g>ገብሯል"</string> <string name="vpn_text" msgid="3011306607126450322">"አውታረመረብ ለማደራጀት ንካ።"</string> <string name="vpn_text_long" msgid="6407351006249174473">"ለ<xliff:g id="SESSION">%s</xliff:g>የተገናኘ። አውታረመረቡን ለማደራጀት ንካ።"</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"ፋይል ምረጥ"</string> <string name="no_file_chosen" msgid="6363648562170759465">"ምንም ፋይል አልተመረጠም"</string> <string name="reset" msgid="2448168080964209908">"ዳግም አስጀምር"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index d3b8f04..df3d114 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"تم تنشيط VPN بواسطة <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"المس لإدارة الشبكة."</string> <string name="vpn_text_long" msgid="6407351006249174473">"تم الاتصال بـ <xliff:g id="SESSION">%s</xliff:g>. المس لإدارة الشبكة."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"اختيار ملف"</string> <string name="no_file_chosen" msgid="6363648562170759465">"لم يتم اختيار أي ملف"</string> <string name="reset" msgid="2448168080964209908">"إعادة تعيين"</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index d801628..55621b3 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN актывуецца прыкладаннем <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Дакраніцеся, каб кіраваць сеткай."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Падлучаны да сеанса \"<xliff:g id="SESSION">%s</xliff:g>\". Дакраніцеся, каб кiраваць сеткай."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Выберыце файл"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Файл не выбраны"</string> <string name="reset" msgid="2448168080964209908">"Скінуць"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 7e7c4e6..4aea25f 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN е активирана от <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Докоснете за управление на мрежата."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Свързана със: <xliff:g id="SESSION">%s</xliff:g>. Докоснете, за да управлявате мрежата."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Избор на файл"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Няма избран файл"</string> <string name="reset" msgid="2448168080964209908">"Повторно задаване"</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 0781217..097e86c 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"<xliff:g id="APP">%s</xliff:g> ha activat VPN"</string> <string name="vpn_text" msgid="3011306607126450322">"Toca per gestionar la xarxa."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Connectat a <xliff:g id="SESSION">%s</xliff:g>. Toca per gestionar la xarxa."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Trieu un fitxer"</string> <string name="no_file_chosen" msgid="6363648562170759465">"No s\'ha escollit cap fitxer"</string> <string name="reset" msgid="2448168080964209908">"Reinicia"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 50cd013..c7f3f88 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"Aplikace <xliff:g id="APP">%s</xliff:g> aktivovala síť VPN"</string> <string name="vpn_text" msgid="3011306607126450322">"Dotykem zobrazíte správu sítě."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Připojeno k relaci <xliff:g id="SESSION">%s</xliff:g>. Dotykem můžete síť spravovat."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Zvolit soubor"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Není vybrán žádný soubor"</string> <string name="reset" msgid="2448168080964209908">"Resetovat"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index ea40879..d390c3f 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN aktiveres af <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Tryk for at administrere netværket."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Forbundet til <xliff:g id="SESSION">%s</xliff:g>. Tryk for at administrere netværket."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Vælg fil"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string> <string name="reset" msgid="2448168080964209908">"Nulstil"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index a0280c8..1bd213e 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN wurde von <xliff:g id="APP">%s</xliff:g> aktiviert."</string> <string name="vpn_text" msgid="3011306607126450322">"Zum Verwalten des Netzwerks berühren"</string> <string name="vpn_text_long" msgid="6407351006249174473">"Verbunden mit <xliff:g id="SESSION">%s</xliff:g>. Zum Verwalten des Netzwerks berühren"</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Datei auswählen"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Keine ausgewählt"</string> <string name="reset" msgid="2448168080964209908">"Zurücksetzen"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 82f0c62..75ceaed 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"Το VPN ενεργοποιήθηκε από την εφαρμογή <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Αγγίξτε για τη διαχείριση του δικτύου."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Συνδέθηκε με <xliff:g id="SESSION">%s</xliff:g>. Αγγίξτε για τη διαχείριση του δικτύου."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Επιλογή αρχείου"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Δεν έχει επιλεγεί αρχείο"</string> <string name="reset" msgid="2448168080964209908">"Επαναφορά"</string> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 4f7b087..4fe04fb 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN is activated by <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Touch to manage the network."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Connected to <xliff:g id="SESSION">%s</xliff:g>. Touch to manage the network."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Choose file"</string> <string name="no_file_chosen" msgid="6363648562170759465">"No file chosen"</string> <string name="reset" msgid="2448168080964209908">"Reset"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 00d8617..170e2f1 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -224,8 +224,8 @@ <string name="permdesc_interactAcrossUsers" msgid="364670963623385786">"Permite que la aplicación lleve a cabo acciones entre los diferentes usuarios del dispositivo. Las aplicaciones maliciosas pueden utilizar este permiso para infringir la protección entre usuarios."</string> <string name="permlab_interactAcrossUsersFull" msgid="2567734285545074105">"Licencia completa para interactuar con los usuarios"</string> <string name="permdesc_interactAcrossUsersFull" msgid="376841368395502366">"Permite todas las interacciones posibles con los usuarios."</string> - <string name="permlab_manageUsers" msgid="1676150911672282428">"Administrar usuarios"</string> - <string name="permdesc_manageUsers" msgid="8409306667645355638">"Permite a las aplicaciones administrar a los usuarios del dispositivo, incluidas la creación y la eliminación de consultas."</string> + <string name="permlab_manageUsers" msgid="1676150911672282428">"administrar usuarios"</string> + <string name="permdesc_manageUsers" msgid="8409306667645355638">"Permite a las aplicaciones administrar los usuarios del dispositivo, lo que incluye buscarlos, crearlos y eliminarlos."</string> <string name="permlab_getDetailedTasks" msgid="6229468674753529501">"recuperar información sobre las aplicaciones en ejecución"</string> <string name="permdesc_getDetailedTasks" msgid="153824741440717599">"Permite que la aplicación recupere información detallada sobre tareas en ejecución y recientemente ejecutadas. Las aplicaciones malintencionadas pueden hallar información privada sobre otras aplicaciones."</string> <string name="permlab_reorderTasks" msgid="2018575526934422779">"reorganizar aplicaciones en ejecución"</string> @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN está activado por <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Toca para administrar la red."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Conectado a <xliff:g id="SESSION">%s</xliff:g>. Toca para administrar la red."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Elegir archivo"</string> <string name="no_file_chosen" msgid="6363648562170759465">"No se seleccionó un archivo."</string> <string name="reset" msgid="2448168080964209908">"Restablecer"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 5e63311..02551e3 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN activada por <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Toca para administrar la red."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Conectado a <xliff:g id="SESSION">%s</xliff:g>. Toca para administrar la red."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Seleccionar archivo"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Archivo no seleccionado"</string> <string name="reset" msgid="2448168080964209908">"Restablecer"</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 8280b61..c15fae7 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN-i aktiveeris <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Võrgu haldamiseks puudutage."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Ühendatud seansiga <xliff:g id="SESSION">%s</xliff:g>. Võrgu haldamiseks puudutage."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Valige fail"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Ühtegi faili pole valitud"</string> <string name="reset" msgid="2448168080964209908">"Lähtesta"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 0dba1bd..c55eb07 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN توسط <xliff:g id="APP">%s</xliff:g> فعال شده است"</string> <string name="vpn_text" msgid="3011306607126450322">"برای مدیریت شبکه لمس کنید."</string> <string name="vpn_text_long" msgid="6407351006249174473">"به <xliff:g id="SESSION">%s</xliff:g> وصل شد. برای مدیریت شبکه لمس کنید."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"انتخاب فایل"</string> <string name="no_file_chosen" msgid="6363648562170759465">"هیچ فایلی انتخاب نشد"</string> <string name="reset" msgid="2448168080964209908">"بازنشانی"</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index f6ab9f8..ee536a6 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"<xliff:g id="APP">%s</xliff:g> on aktivoinut VPN-yhteyden"</string> <string name="vpn_text" msgid="3011306607126450322">"Voit hallinnoida verkkoa koskettamalla."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Yhdistetty: <xliff:g id="SESSION">%s</xliff:g>. Hallinnoi verkkoa koskettamalla."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Valitse tiedosto"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Ei valittua tiedostoa"</string> <string name="reset" msgid="2448168080964209908">"Palauta"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index d4d938b..63a19f4 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN activé par <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Appuyez ici pour gérer le réseau."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Connecté à <xliff:g id="SESSION">%s</xliff:g>. Appuyez ici pour gérer le réseau."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Sélectionner un fichier"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Aucun fichier sélectionné"</string> <string name="reset" msgid="2448168080964209908">"Réinitialiser"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index b491e97..b2ab326 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -468,7 +468,7 @@ <string name="permdesc_factoryTest" product="default" msgid="8136644990319244802">"फ़ोन हार्डवेयर में पूर्ण पहुंच की अनुमति देते हुए निम्न-स्तर निर्माता परीक्षण के रूप में चलाएं. केवल तभी उपलब्ध जब कोई फ़ोन निर्माता परीक्षण मोड में चल रहा हो."</string> <string name="permlab_setWallpaper" msgid="6627192333373465143">"वॉलपेपर सेट करें"</string> <string name="permdesc_setWallpaper" msgid="7373447920977624745">"एप्लिकेशन को सिस्टम वॉलपेपर सेट करने देता है."</string> - <string name="permlab_setWallpaperHints" msgid="3278608165977736538">"अपने वॉलपेपर का आकार समायोजित करें"</string> + <string name="permlab_setWallpaperHints" msgid="3278608165977736538">"अपने वॉलपेपर का आकार एडजस्ट करें"</string> <string name="permdesc_setWallpaperHints" msgid="8235784384223730091">"एप्लिकेशन को सिस्टम वॉलपेपर आकार संकेत सेट करने देता है."</string> <string name="permlab_masterClear" msgid="2315750423139697397">"फ़ैक्ट्री डिफ़ॉल्ट पर सिस्टम रीसेट करें"</string> <string name="permdesc_masterClear" msgid="3665380492633910226">"एप्लिकेशन को सभी डेटा, कॉन्फ़िगरेशन, और इंस्टॉल एप्लिकेशन मिटाकर, सिस्टम को पूरी तरह उसकी फ़ैक्टरी सेटिंग पर रीसेट करने देता है."</string> @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN को <xliff:g id="APP">%s</xliff:g> द्वारा सक्रिय किया गया है"</string> <string name="vpn_text" msgid="3011306607126450322">"नेटवर्क प्रबंधित करने के लिए स्पर्श करें."</string> <string name="vpn_text_long" msgid="6407351006249174473">"<xliff:g id="SESSION">%s</xliff:g> से कनेक्ट किया गया. नेटवर्क प्रबंधित करने के लिए स्पर्श करें."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"फ़ाइल चुनें"</string> <string name="no_file_chosen" msgid="6363648562170759465">"कोई फ़ाइल चुनी नहीं गई"</string> <string name="reset" msgid="2448168080964209908">"रीसेट करें"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index aef3bdf..2dd59e0 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"Aplikacija <xliff:g id="APP">%s</xliff:g> aktivirala je VPN"</string> <string name="vpn_text" msgid="3011306607126450322">"Dodirnite za upravljanje mrežom."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Povezan sa sesijom <xliff:g id="SESSION">%s</xliff:g>. Dodirnite za upravljanje mrežom."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Odaberite datoteku"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Nema odabranih datoteka"</string> <string name="reset" msgid="2448168080964209908">"Ponovo postavi"</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index 90bd85e..69dae8a 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"A(z) <xliff:g id="APP">%s</xliff:g> aktiválta a VPN-t"</string> <string name="vpn_text" msgid="3011306607126450322">"Érintse meg a hálózat kezeléséhez."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Csatlakozva ide: <xliff:g id="SESSION">%s</xliff:g>. Érintse meg a hálózat kezeléséhez."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Fájl kiválasztása"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Nincs fájl kiválasztva"</string> <string name="reset" msgid="2448168080964209908">"Alaphelyzet"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 34e6a96..f8e8fc1 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN diaktifkan oleh <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Sentuh untuk mengelola jaringan."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Tersambung ke <xliff:g id="SESSION">%s</xliff:g>. Sentuh untuk mengelola jaringan."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Pilih file"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Tidak ada file yang dipilih"</string> <string name="reset" msgid="2448168080964209908">"Setel ulang"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index a693dc8..9a7c439 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN attivata da <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Tocca per gestire la rete."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Collegata a <xliff:g id="SESSION">%s</xliff:g>. Tocca per gestire la rete."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Scegli file"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Nessun file è stato scelto"</string> <string name="reset" msgid="2448168080964209908">"Reimposta"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index a318907..2ae5206 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN מופעל על ידי <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"גע כדי לנהל את הרשת."</string> <string name="vpn_text_long" msgid="6407351006249174473">"מחובר אל <xliff:g id="SESSION">%s</xliff:g>. גע כדי לנהל את הרשת."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"בחר קובץ"</string> <string name="no_file_chosen" msgid="6363648562170759465">"לא נבחר קובץ"</string> <string name="reset" msgid="2448168080964209908">"איפוס"</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index b7a9c3b..e94ebe7 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPNが<xliff:g id="APP">%s</xliff:g>により有効化されました"</string> <string name="vpn_text" msgid="3011306607126450322">"タップしてネットワークを管理します。"</string> <string name="vpn_text_long" msgid="6407351006249174473">"<xliff:g id="SESSION">%s</xliff:g>に接続しました。ネットワークを管理するにはタップしてください。"</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"ファイルを選択"</string> <string name="no_file_chosen" msgid="6363648562170759465">"ファイルが選択されていません"</string> <string name="reset" msgid="2448168080964209908">"リセット"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 0193ca2..4609a80 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -794,7 +794,7 @@ <string name="autofill_department" msgid="5343279462564453309">"지역"</string> <string name="autofill_prefecture" msgid="2028499485065800419">"현"</string> <string name="autofill_parish" msgid="8202206105468820057">"군"</string> - <string name="autofill_area" msgid="3547409050889952423">"구역"</string> + <string name="autofill_area" msgid="3547409050889952423">"주소"</string> <string name="autofill_emirate" msgid="2893880978835698818">"에미리트"</string> <string name="permlab_readHistoryBookmarks" msgid="3775265775405106983">"웹 북마크 및 기록 읽기"</string> <string name="permdesc_readHistoryBookmarks" msgid="8462378226600439658">"앱이 브라우저가 방문한 모든 URL의 기록과 모든 브라우저 북마크를 읽을 수 있도록 허용합니다. 참고: 이 권한은 타사 브라우저 또는 브라우저 기능을 가진 기타 애플리케이션에 적용되지 않습니다."</string> @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN이 <xliff:g id="APP">%s</xliff:g>에 의해 활성화됨"</string> <string name="vpn_text" msgid="3011306607126450322">"네트워크를 관리하려면 터치하세요."</string> <string name="vpn_text_long" msgid="6407351006249174473">"<xliff:g id="SESSION">%s</xliff:g>에 연결되어 있습니다. 네트워크를 관리하려면 터치하세요."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"파일 선택"</string> <string name="no_file_chosen" msgid="6363648562170759465">"파일을 선택하지 않았습니다."</string> <string name="reset" msgid="2448168080964209908">"초기화"</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index b778d03..ad2e4cd 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN suaktyvino „<xliff:g id="APP">%s</xliff:g>“"</string> <string name="vpn_text" msgid="3011306607126450322">"Palieskite, kad valdytumėte tinklą."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Prisijungta prie <xliff:g id="SESSION">%s</xliff:g>. Jei norite valdyti tinklą, palieskite."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Pasirinkti failą"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Nepasirinktas joks failas"</string> <string name="reset" msgid="2448168080964209908">"Atstatyti"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 4b78bd4..5a5c14b 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -225,7 +225,7 @@ <string name="permlab_interactAcrossUsersFull" msgid="2567734285545074105">"pilna licence ar atļauju darboties visos lietotāju kontos"</string> <string name="permdesc_interactAcrossUsersFull" msgid="376841368395502366">"Ļauj veikt jebkādas darbības visos lietotāju kontos."</string> <string name="permlab_manageUsers" msgid="1676150911672282428">"Lietotāju pārvaldība"</string> - <string name="permdesc_manageUsers" msgid="8409306667645355638">"Ļauj lietotnēm pārvaldīt ierīces lietotājus, tostarp izveidot un dzēst lietotājus vai veidot vaicājumus."</string> + <string name="permdesc_manageUsers" msgid="8409306667645355638">"Ļauj lietotnēm pārvaldīt ierīces lietotājus, tostarp izveidot un dzēst lietotājus un veidot vaicājumus."</string> <string name="permlab_getDetailedTasks" msgid="6229468674753529501">"Informācijas izguve par izmantotajām lietotnēm"</string> <string name="permdesc_getDetailedTasks" msgid="153824741440717599">"Ļauj lietotnei izgūt informāciju par šobrīd un nesen veiktajiem uzdevumiem. Ļaunprātīgas lietotnes var atklāt privātu informāciju par citām lietotnēm."</string> <string name="permlab_reorderTasks" msgid="2018575526934422779">"pārkārtot izmantotās lietotnes"</string> @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"Lietojumprogramma <xliff:g id="APP">%s</xliff:g> aktivizēja VPN."</string> <string name="vpn_text" msgid="3011306607126450322">"Pieskarieties, lai pārvaldītu tīklu."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Ir izveidots savienojums ar <xliff:g id="SESSION">%s</xliff:g>. Pieskarieties, lai pārvaldītu tīklu."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Izvēlēties failu"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Neviens fails nav izvēlēts"</string> <string name="reset" msgid="2448168080964209908">"Atiestatīt"</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index 782c2a7..2ac167f 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN diaktifkan oleh <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Sentuh untuk mengurus rangkaian."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Bersambung ke <xliff:g id="SESSION">%s</xliff:g>. Sentuh untuk mengurus rangkaian."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Pilih fail"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Tiada fail dipilih"</string> <string name="reset" msgid="2448168080964209908">"Tetapkan semula"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 3a4212b..7012793 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN er aktivert av <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Trykk for å administrere nettverket."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Koblet til <xliff:g id="SESSION">%s</xliff:g>. Trykk for å administrere nettverket."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Velg fil"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string> <string name="reset" msgid="2448168080964209908">"Tilbakestill"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 89e52a9..8178980 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN wordt geactiveerd door <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Raak aan om het netwerk te beheren."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Verbonden met <xliff:g id="SESSION">%s</xliff:g>. Tik om het netwerk te beheren."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Bestand kiezen"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Geen bestand geselecteerd"</string> <string name="reset" msgid="2448168080964209908">"Opnieuw instellen"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index f0dcc2f..c907fe6 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"Obsługa sieci VPN została włączona przez aplikację <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Dotknij, aby zarządzać siecią."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Nawiązano połączenie z: <xliff:g id="SESSION">%s</xliff:g>. Dotknij, aby zarządzać siecią."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Wybierz plik"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Nie wybrano pliku"</string> <string name="reset" msgid="2448168080964209908">"Resetuj"</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 18c25a3..7541db8 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"A VPN foi ativada pelo <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Toque para gerir a rede."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Ligado a <xliff:g id="SESSION">%s</xliff:g>. Toque para gerir a rede."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Escolher ficheiro"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Não foi selecionado nenhum ficheiro"</string> <string name="reset" msgid="2448168080964209908">"Repor"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 794af68..d8a5bba 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"A VPN está ativada por <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Toque para gerenciar a rede."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Conectado a <xliff:g id="SESSION">%s</xliff:g>. Toque para gerenciar a rede."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Escolher arquivo"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Nenhum arquivo escolhido"</string> <string name="reset" msgid="2448168080964209908">"Redefinir"</string> diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml index a093bfb..9ffa164 100644 --- a/core/res/res/values-rm/strings.xml +++ b/core/res/res/values-rm/strings.xml @@ -1799,6 +1799,14 @@ <skip /> <!-- no translation found for vpn_text_long (6407351006249174473) --> <skip /> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Tscherner ina datoteca"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Nagina datoteca tschernida"</string> <string name="reset" msgid="2448168080964209908">"Reinizialisar"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 6f6272d..23c1491 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN este activată de <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Atingeţi pentru a gestiona reţeaua."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Conectat la <xliff:g id="SESSION">%s</xliff:g>. Atingeţi pentru a gestiona reţeaua."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Alegeţi un fişier"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Nu au fost găsite fişiere"</string> <string name="reset" msgid="2448168080964209908">"Resetaţi"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index a9c0701..98dc250 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"Сеть VPN активирована приложением <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Нажмите, чтобы открыть настройки."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Сеть VPN подключена: <xliff:g id="SESSION">%s</xliff:g>. Нажмите, чтобы открыть настройки."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Выбрать файл"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Не выбран файл"</string> <string name="reset" msgid="2448168080964209908">"Сбросить"</string> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 6240be3..37395a8 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"Aplikáciu <xliff:g id="APP">%s</xliff:g> aktivovala sieť VPN"</string> <string name="vpn_text" msgid="3011306607126450322">"Dotykom môžete spravovať sieť."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Pripojené k relácii <xliff:g id="SESSION">%s</xliff:g>. Po dotyku môžete sieť spravovať."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Zvoliť súbor"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Nie je vybratý žiadny súbor"</string> <string name="reset" msgid="2448168080964209908">"Obnoviť"</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 1ede6b7..48fa35d 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN je aktiviral program <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Dotaknite se, če želite upravljati omrežje."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Vzpostavljena povezava s sejo <xliff:g id="SESSION">%s</xliff:g>. Dotaknite se, če želite upravljati omrežje."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Izberi datoteko"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Nobena datoteka ni izbrana"</string> <string name="reset" msgid="2448168080964209908">"Ponastavi"</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 4ddde21..259cb15 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"Апликација <xliff:g id="APP">%s</xliff:g> је активирала VPN"</string> <string name="vpn_text" msgid="3011306607126450322">"Додирните да бисте управљали мрежом."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Повезано са сесијом <xliff:g id="SESSION">%s</xliff:g>. Додирните да бисте управљали мрежом."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Одабери датотеку"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Није изабрана ниједна датотека"</string> <string name="reset" msgid="2448168080964209908">"Поново постави"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 493a0bb..10bdd18 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN aktiveras av <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Tryck om du vill hantera nätverket."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Ansluten till <xliff:g id="SESSION">%s</xliff:g>. Knacka lätt om du vill hantera nätverket."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Välj fil"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil har valts"</string> <string name="reset" msgid="2448168080964209908">"Återställ"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 759000d..5f20617 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN imeamilishwa na <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Gusa ili kudhibiti mtandao."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Imeunganishwa kwa <xliff:g id="SESSION">%s</xliff:g>. Gusa ili kudhibiti mtandao."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Chagua faili"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Hakuna faili iliyochaguliwa"</string> <string name="reset" msgid="2448168080964209908">"Weka upya"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index efb1d6f..40d4eb6 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"เปิดใช้งาน VPN โดย <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"แตะเพื่อจัดการเครือข่าย"</string> <string name="vpn_text_long" msgid="6407351006249174473">"เชื่อมต่อกับ <xliff:g id="SESSION">%s</xliff:g> แตะเพื่อจัดการเครือข่าย"</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"เลือกไฟล์"</string> <string name="no_file_chosen" msgid="6363648562170759465">"ไม่ได้เลือกไฟล์ไว้"</string> <string name="reset" msgid="2448168080964209908">"รีเซ็ต"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index 261a0b9..036a30e 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"Isinaaktibo ang VPN ng <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Pindutin upang pamahalaan ang network."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Nakakonekta sa <xliff:g id="SESSION">%s</xliff:g>. Pindutin upang pamahalaan ang network."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Pumili ng file"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Walang napiling file"</string> <string name="reset" msgid="2448168080964209908">"I-reset"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index c60535f..2848eea 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN, <xliff:g id="APP">%s</xliff:g> tarafından etkinleştirildi"</string> <string name="vpn_text" msgid="3011306607126450322">"Ağı yönetmek için dokunun."</string> <string name="vpn_text_long" msgid="6407351006249174473">"<xliff:g id="SESSION">%s</xliff:g> oturumuna bağlandı. Ağı yönetmek için dokunun."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Dosya seç"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Seçili dosya yok"</string> <string name="reset" msgid="2448168080964209908">"Sıfırla"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index a8633e8..ee3f8da 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"Мережу VPN активовано програмою <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Торкніться, щоб керувати мережею."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Під’єднано до сеансу <xliff:g id="SESSION">%s</xliff:g>. Торкніться, щоб керувати мережею."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Виберіть файл"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Не вибрано файл"</string> <string name="reset" msgid="2448168080964209908">"Віднов."</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 012a47a..b5c17ae 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"VPN được <xliff:g id="APP">%s</xliff:g> kích hoạt"</string> <string name="vpn_text" msgid="3011306607126450322">"Chạm để quản lý mạng."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Đã kết nối với <xliff:g id="SESSION">%s</xliff:g>. Chạm để quản lý mạng."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Chọn tệp"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Không có tệp nào được chọn"</string> <string name="reset" msgid="2448168080964209908">"Đặt lại"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index e982c75..0caef06 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"“<xliff:g id="APP">%s</xliff:g>”已激活 VPN"</string> <string name="vpn_text" msgid="3011306607126450322">"触摸可管理网络。"</string> <string name="vpn_text_long" msgid="6407351006249174473">"已连接到“<xliff:g id="SESSION">%s</xliff:g>”。触摸可管理网络。"</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"选择文件"</string> <string name="no_file_chosen" msgid="6363648562170759465">"未选定任何文件"</string> <string name="reset" msgid="2448168080964209908">"重置"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 36eba9f..2935256 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"<xliff:g id="APP">%s</xliff:g> 已啟用 VPN"</string> <string name="vpn_text" msgid="3011306607126450322">"輕觸即可管理網路。"</string> <string name="vpn_text_long" msgid="6407351006249174473">"已連線至 <xliff:g id="SESSION">%s</xliff:g>,輕觸即可管理網路。"</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"選擇檔案"</string> <string name="no_file_chosen" msgid="6363648562170759465">"未選擇任何檔案"</string> <string name="reset" msgid="2448168080964209908">"重設"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 78b287c..8360097 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -1167,6 +1167,14 @@ <string name="vpn_title_long" msgid="6400714798049252294">"i-VPN ivuswe ngu <xliff:g id="APP">%s</xliff:g>"</string> <string name="vpn_text" msgid="3011306607126450322">"Thinta ukuze wengamele inethiwekhi."</string> <string name="vpn_text_long" msgid="6407351006249174473">"Ixhumeke ku-.<xliff:g id="SESSION">%s</xliff:g> Thinta ukuze ulawule inethiwekhi."</string> + <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) --> + <skip /> + <!-- no translation found for vpn_lockdown_connected (8202679674819213931) --> + <skip /> + <!-- no translation found for vpn_lockdown_error (6009249814034708175) --> + <skip /> + <!-- no translation found for vpn_lockdown_reset (5365010427963548932) --> + <skip /> <string name="upload_file" msgid="2897957172366730416">"Khetha ifayela"</string> <string name="no_file_chosen" msgid="6363648562170759465">"Ayikho ifayela ekhethiwe"</string> <string name="reset" msgid="2448168080964209908">"Setha kabusha"</string> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 5d8d397..209fff0 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5706,4 +5706,22 @@ <attr name="minHeight" /> </declare-styleable> + <!-- PagedView specific attributes. These attributes are used to customize + a PagedView view in XML files. --> + <declare-styleable name="PagedView"> + <!-- A spacing override for the icons within a page --> + <attr name="pageLayoutWidthGap" format="dimension" /> + <attr name="pageLayoutHeightGap" format="dimension" /> + <!-- The padding of the pages that are dynamically created per page --> + <attr name="pageLayoutPaddingTop" format="dimension" /> + <attr name="pageLayoutPaddingBottom" format="dimension" /> + <attr name="pageLayoutPaddingLeft" format="dimension" /> + <attr name="pageLayoutPaddingRight" format="dimension" /> + <!-- The space between adjacent pages of the PagedView. --> + <attr name="pageSpacing" format="dimension" /> + <!-- The padding for the scroll indicator area --> + <attr name="scrollIndicatorPaddingLeft" format="dimension" /> + <attr name="scrollIndicatorPaddingRight" format="dimension" /> + </declare-styleable> + </resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index f30943a..372a1ee 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -237,4 +237,20 @@ <dimen name="notification_title_text_size">18dp</dimen> <!-- Size of smaller notification text (see TextAppearance.StatusBar.EventContent.Line2, Info, Time) --> <dimen name="notification_subtext_size">12dp</dimen> + + <!-- Keyguard dimensions --> + <!-- Width of security view in keyguard. --> + <dimen name="kg_security_view_width">500dp</dimen> + + <!-- Height of security view in keyguard. --> + <dimen name="kg_security_view_height">0dp</dimen> + + <!-- Width of widget view in keyguard. --> + <dimen name="kg_widget_view_width">0dp</dimen> + + <!-- Height of widget view in keyguard. --> + <dimen name="kg_widget_view_height">0dp</dimen> + + <!-- Padding surrounding each widget page --> + <dimen name="kg_widget_page_padding">10dp</dimen> </resources> diff --git a/core/res/res/values/integers.xml b/core/res/res/values/integers.xml new file mode 100644 index 0000000..603fd7e --- /dev/null +++ b/core/res/res/values/integers.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<resources> + <integer name="flip_duration">300</integer> +</resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index d761980..6414df8 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -120,6 +120,8 @@ <java-symbol type="id" name="old_app_action" /> <java-symbol type="id" name="old_app_description" /> <java-symbol type="id" name="old_app_icon" /> + <java-symbol type="id" name="overlay_display_window_texture" /> + <java-symbol type="id" name="overlay_display_window_title" /> <java-symbol type="id" name="package_label" /> <java-symbol type="id" name="packages_list" /> <java-symbol type="id" name="pause" /> @@ -479,7 +481,9 @@ <java-symbol type="string" name="decline" /> <java-symbol type="string" name="default_text_encoding" /> <java-symbol type="string" name="description_target_unlock_tablet" /> - <java-symbol type="string" name="display_manager_built_in_display" /> + <java-symbol type="string" name="display_manager_built_in_display_name" /> + <java-symbol type="string" name="display_manager_overlay_display_name" /> + <java-symbol type="string" name="display_manager_overlay_display_title" /> <java-symbol type="string" name="double_tap_toast" /> <java-symbol type="string" name="elapsed_time_short_format_h_mm_ss" /> <java-symbol type="string" name="elapsed_time_short_format_mm_ss" /> @@ -1093,6 +1097,7 @@ <java-symbol type="layout" name="list_menu_item_radio" /> <java-symbol type="layout" name="locale_picker_item" /> <java-symbol type="layout" name="media_controller" /> + <java-symbol type="layout" name="overlay_display_window" /> <java-symbol type="layout" name="preference" /> <java-symbol type="layout" name="preference_header_item" /> <java-symbol type="layout" name="preference_list_content" /> @@ -1203,6 +1208,8 @@ <java-symbol type="anim" name="dock_left_exit" /> <java-symbol type="anim" name="dock_right_enter" /> <java-symbol type="anim" name="dock_right_exit" /> + <java-symbol type="anim" name="keyguard_security_animate_in" /> + <java-symbol type="anim" name="keyguard_security_animate_out" /> <java-symbol type="array" name="config_keyboardTapVibePattern" /> <java-symbol type="array" name="config_longPressVibePattern" /> <java-symbol type="array" name="config_safeModeDisabledVibePattern" /> @@ -1230,6 +1237,7 @@ <java-symbol type="dimen" name="navigation_bar_height_landscape" /> <java-symbol type="dimen" name="navigation_bar_width" /> <java-symbol type="dimen" name="status_bar_height" /> + <java-symbol type="dimen" name="kg_widget_page_padding" /> <java-symbol type="drawable" name="ic_jog_dial_sound_off" /> <java-symbol type="drawable" name="ic_jog_dial_sound_on" /> <java-symbol type="drawable" name="ic_jog_dial_unlock" /> @@ -1247,6 +1255,8 @@ <java-symbol type="drawable" name="jog_tab_target_yellow" /> <java-symbol type="drawable" name="menu_background" /> <java-symbol type="drawable" name="stat_sys_secure" /> + <java-symbol type="drawable" name="kg_widget_overscroll_layer_left" /> + <java-symbol type="drawable" name="kg_widget_overscroll_layer_right" /> <java-symbol type="id" name="action_mode_bar_stub" /> <java-symbol type="id" name="alarm_status" /> <java-symbol type="id" name="backspace" /> @@ -1299,6 +1309,28 @@ <java-symbol type="id" name="two" /> <java-symbol type="id" name="unlock_widget" /> <java-symbol type="id" name="zero" /> + <java-symbol type="id" name="message_area" /> + <java-symbol type="id" name="keyguard_selector_view" /> + <java-symbol type="id" name="keyguard_pattern_view" /> + <java-symbol type="id" name="keyguard_password_view" /> + <java-symbol type="id" name="keyguard_face_unlock_view" /> + <java-symbol type="id" name="keyguard_sim_pin_view" /> + <java-symbol type="id" name="keyguard_sim_puk_view" /> + <java-symbol type="id" name="keyguard_account_view" /> + <java-symbol type="id" name="app_widget_container" /> + <java-symbol type="id" name="view_flipper" /> + <java-symbol type="id" name="emergency_call_button" /> + <java-symbol type="id" name="keyguard_host_view" /> + <java-symbol type="id" name="delete_button" /> + <java-symbol type="id" name="lockPatternView" /> + <java-symbol type="id" name="forgot_password_button" /> + <java-symbol type="id" name="glow_pad_view" /> + <java-symbol type="id" name="sim_pin_entry" /> + <java-symbol type="id" name="delete_button" /> + <java-symbol type="id" name="sim_puk_entry" /> + <java-symbol type="id" name="sim_pin_entry" /> + <java-symbol type="id" name="puk_delete_button" /> + <java-symbol type="id" name="pin_delete_button" /> <java-symbol type="integer" name="config_carDockRotation" /> <java-symbol type="integer" name="config_defaultUiModeType" /> <java-symbol type="integer" name="config_deskDockRotation" /> @@ -1328,6 +1360,7 @@ <java-symbol type="layout" name="screen_simple_overlay_action_mode" /> <java-symbol type="layout" name="screen_title" /> <java-symbol type="layout" name="screen_title_icons" /> + <java-symbol type="layout" name="keyguard_host_view" /> <java-symbol type="string" name="abbrev_wday_month_day_no_year" /> <java-symbol type="string" name="android_upgrading_title" /> <java-symbol type="string" name="bugreport_title" /> @@ -1381,6 +1414,33 @@ <java-symbol type="style" name="Animation.LockScreen" /> <java-symbol type="style" name="Theme.Dialog.RecentApplications" /> <java-symbol type="style" name="Theme.ExpandedMenu" /> + <java-symbol type="string" name="kg_emergency_call_label" /> + <java-symbol type="string" name="kg_forgot_pattern_button_text" /> + <java-symbol type="string" name="kg_wrong_pattern" /> + <java-symbol type="string" name="kg_wrong_password" /> + <java-symbol type="string" name="kg_wrong_pin" /> + <java-symbol type="string" name="kg_too_many_failed_attempts_countdown" /> + <java-symbol type="string" name="kg_pattern_instructions" /> + <java-symbol type="string" name="kg_sim_pin_instructions" /> + <java-symbol type="string" name="kg_pin_instructions" /> + <java-symbol type="string" name="kg_password_instructions" /> + <java-symbol type="string" name="kg_puk_enter_puk_hint" /> + <java-symbol type="string" name="kg_puk_enter_pin_hint" /> + <java-symbol type="string" name="kg_sim_unlock_progress_dialog_message" /> + <java-symbol type="string" name="kg_password_wrong_pin_code" /> + <java-symbol type="string" name="kg_invalid_sim_pin_hint" /> + <java-symbol type="string" name="kg_invalid_sim_puk_hint" /> + <java-symbol type="string" name="kg_sim_puk_recovery_hint" /> + <java-symbol type="string" name="kg_invalid_puk" /> + <java-symbol type="string" name="kg_login_too_many_attempts" /> + <java-symbol type="string" name="kg_login_instructions" /> + <java-symbol type="string" name="kg_login_username_hint" /> + <java-symbol type="string" name="kg_login_password_hint" /> + <java-symbol type="string" name="kg_login_submit_button" /> + <java-symbol type="string" name="kg_login_invalid_input" /> + <java-symbol type="string" name="kg_login_account_recovery_hint" /> + <java-symbol type="string" name="kg_login_checking_password" /> + <!-- From services --> <java-symbol type="anim" name="screen_rotate_0_enter" /> @@ -3692,5 +3752,5 @@ <public type="attr" name="listPreferredItemPaddingStart" /> <public type="attr" name="listPreferredItemPaddingEnd" /> <public type="attr" name="singleUser" /> - + </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index e77dde7..3178af0 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1616,6 +1616,11 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] --> <string name="permdesc_mediaStorageWrite" product="default">Allows the app to modify the contents of the internal media storage.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] --> + <string name="permlab_sdcardAccessAll">access external storage of all users</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_sdcardAccessAll">Allows the app to access external storage for all users.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_cache_filesystem">access the cache filesystem</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> @@ -3651,6 +3656,42 @@ <!-- Display manager service --> <!-- Name of the built-in display. [CHAR LIMIT=50] --> - <string name="display_manager_built_in_display">Built-in Screen</string> + <string name="display_manager_built_in_display_name">Built-in Screen</string> + + <!-- Name of the N'th overlay display for testing. [CHAR LIMIT=50] --> + <string name="display_manager_overlay_display_name">Overlay #<xliff:g id="id">%1$d</xliff:g></string> + + <!-- Title text to show within the overlay. [CHAR LIMIT=50] --> + <string name="display_manager_overlay_display_title">Overlay #<xliff:g id="id">%1$d</xliff:g>: <xliff:g id="width">%2$d</xliff:g>x<xliff:g id="height">%3$d</xliff:g>, <xliff:g id="dpi">%4$d</xliff:g> dpi</string> + + <!-- Keyguard strings --> + <string name="kg_emergency_call_label">Emergency call</string> + <string name="kg_forgot_pattern_button_text">Forgot Pattern</string> + <string name="kg_wrong_pattern">Wrong Pattern</string> + <string name="kg_wrong_password">Wrong Password</string> + <string name="kg_wrong_pin">Wrong PIN</string> + <string name="kg_too_many_failed_attempts_countdown">Too many attempts</string> + <string name="kg_pattern_instructions">Draw your pattern</string> + <string name="kg_sim_pin_instructions">Enter SIM PIN</string> + <string name="kg_pin_instructions">Enter PIN</string> + <string name="kg_password_instructions">Enter Password</string> + <string name="kg_puk_enter_puk_hint">PUK code</string> + <string name="kg_puk_enter_pin_hint">New PIN code</string> + <string name="kg_sim_unlock_progress_dialog_message">Unlocking SIM card\u2026</string> + <string name="kg_password_wrong_pin_code">Incorrect PIN code.</string> + <string name="kg_invalid_sim_pin_hint">Type a PIN that is 4 to 8 numbers.</string> + <string name="kg_invalid_sim_puk_hint">Type a PUK that is 8 numbers or longer.</string> + <string name="kg_sim_puk_recovery_hint">Type PUK and new PIN code</string> + <string name="kg_invalid_puk">The PUK you typed isn\'t correct.</string> + + <string name="kg_login_too_many_attempts">Too many pattern attempts</string> + <string name="kg_login_instructions">To unlock, sign in with your Google account.</string> + <string name="kg_login_username_hint">Username (email)</string> + <string name="kg_login_password_hint">Password</string> + <string name="kg_login_submit_button">Sign in</string> + <string name="kg_login_invalid_input">Invalid username or password.</string> + <string name="kg_login_account_recovery_hint">Forgot your username or password\?\nVisit <b>google.com/accounts/recovery</b>.</string> + <string name="kg_login_checking_password">Checking\u2026</string> + <string name="kg_temp_back_string"> < </string> <!-- TODO: remove this --> </resources> diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 1b69daf..a19b9b4 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -173,7 +173,10 @@ <assign-permission name="android.permission.SET_SCREEN_COMPATIBILITY" uid="shell" /> <assign-permission name="android.permission.READ_EXTERNAL_STORAGE" uid="shell" /> <assign-permission name="android.permission.WRITE_EXTERNAL_STORAGE" uid="shell" /> - + <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="shell" /> + <assign-permission name="android.permission.INTERACT_ACROSS_USERS_FULL" uid="shell" /> + <assign-permission name="android.permission.MANAGE_USERS" uid="shell" /> + <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" /> <assign-permission name="android.permission.ACCESS_DRM" uid="media" /> <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" /> diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp index a073c1a..2109a01 100644 --- a/graphics/jni/android_renderscript_RenderScript.cpp +++ b/graphics/jni/android_renderscript_RenderScript.cpp @@ -230,7 +230,7 @@ nContextSetSurface(JNIEnv *_env, jobject _this, RsContext con, jint width, jint if (wnd == NULL) { } else { - window = android_Surface_getNativeWindow(_env, wnd).get(); + window = android_view_Surface_getNativeWindow(_env, wnd).get(); } rsContextSetSurface(con, width, height, window); @@ -494,7 +494,7 @@ nAllocationSetSurface(JNIEnv *_env, jobject _this, RsContext con, RsAllocation a sp<Surface> s; if (sur != 0) { - s = Surface_getSurface(_env, sur); + s = android_view_Surface_getSurface(_env, sur); } rsAllocationSetSurface(con, alloc, static_cast<ANativeWindow *>(s.get())); diff --git a/include/android_runtime/android_view_Surface.h b/include/android_runtime/android_view_Surface.h index fb0b057..e50186d 100644 --- a/include/android_runtime/android_view_Surface.h +++ b/include/android_runtime/android_view_Surface.h @@ -25,12 +25,15 @@ namespace android { class Surface; -extern sp<ANativeWindow> android_Surface_getNativeWindow( - JNIEnv* env, jobject clazz); -extern bool android_Surface_isInstanceOf(JNIEnv* env, jobject obj); +/* Gets the underlying ANativeWindow for a Surface. */ +extern sp<ANativeWindow> android_view_Surface_getNativeWindow( + JNIEnv* env, jobject surfaceObj); + +/* Returns true if the object is an instance of Surface. */ +extern bool android_view_Surface_isInstanceOf(JNIEnv* env, jobject obj); /* Gets the underlying Surface from a Surface Java object. */ -extern sp<Surface> Surface_getSurface(JNIEnv* env, jobject thiz); +extern sp<Surface> android_view_Surface_getSurface(JNIEnv* env, jobject surfaceObj); } // namespace android diff --git a/include/android_runtime/android_view_SurfaceSession.h b/include/android_runtime/android_view_SurfaceSession.h new file mode 100644 index 0000000..3748f6c --- /dev/null +++ b/include/android_runtime/android_view_SurfaceSession.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_VIEW_SURFACE_SESSION_H +#define _ANDROID_VIEW_SURFACE_SESSION_H + +#include "jni.h" + +namespace android { + +class SurfaceComposerClient; + +/* Gets the underlying SurfaceComposerClient for a SurfaceSession. */ +extern sp<SurfaceComposerClient> android_view_SurfaceSession_getClient( + JNIEnv* env, jobject surfaceSessionObj); + +} // namespace android + +#endif // _ANDROID_VIEW_SURFACE_SESSION_H diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 27e198c..6b08e7f 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -328,19 +328,19 @@ void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float glyph->mCacheTexture); } -CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit) { +CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool precaching) { CachedGlyphInfo* cachedGlyph = NULL; ssize_t index = mCachedGlyphs.indexOfKey(textUnit); if (index >= 0) { cachedGlyph = mCachedGlyphs.valueAt(index); } else { - cachedGlyph = cacheGlyph(paint, textUnit); + cachedGlyph = cacheGlyph(paint, textUnit, precaching); } // Is the glyph still in texture cache? if (!cachedGlyph->mIsValid) { const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit); - updateGlyphCache(paint, skiaGlyph, cachedGlyph); + updateGlyphCache(paint, skiaGlyph, cachedGlyph, precaching); } return cachedGlyph; @@ -438,7 +438,7 @@ void Font::precache(SkPaint* paint, const char* text, int numGlyphs) { break; } - CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph); + CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph, true); glyphsCount++; } @@ -529,7 +529,8 @@ void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len } } -void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph) { +void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph, + bool precaching) { glyph->mAdvanceX = skiaGlyph.fAdvanceX; glyph->mAdvanceY = skiaGlyph.fAdvanceY; glyph->mBitmapLeft = skiaGlyph.fLeft; @@ -542,7 +543,7 @@ void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyp // Get the bitmap for the glyph paint->findImage(skiaGlyph); - mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY); + mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY, precaching); if (!glyph->mIsValid) { return; @@ -567,7 +568,7 @@ void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyp mState->mUploadTexture = true; } -CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph) { +CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching) { CachedGlyphInfo* newGlyph = new CachedGlyphInfo(); mCachedGlyphs.add(glyph, newGlyph); @@ -575,7 +576,7 @@ CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph) { newGlyph->mGlyphIndex = skiaGlyph.fID; newGlyph->mIsValid = false; - updateGlyphCache(paint, skiaGlyph, newGlyph); + updateGlyphCache(paint, skiaGlyph, newGlyph, precaching); return newGlyph; } @@ -762,7 +763,7 @@ CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph, } void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, - uint32_t* retOriginX, uint32_t* retOriginY) { + uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) { checkInit(); cachedGlyph->mIsValid = false; // If the glyph is too tall, don't cache it @@ -779,15 +780,16 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY); - // If the new glyph didn't fit, flush the state so far and invalidate everything if (!cacheTexture) { - flushAllAndInvalidate(); - - // Try to fit it again - cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY); + if (!precaching) { + // If the new glyph didn't fit and we are not just trying to precache it, + // clear out the cache and try again + flushAllAndInvalidate(); + cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY); + } - // if we still don't fit, something is wrong and we shouldn't draw if (!cacheTexture) { + // either the glyph didn't fit or we're precaching and will cache it when we draw return; } } diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index febae17..8d0d21d 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -243,8 +243,9 @@ protected: void invalidateTextureCache(CacheTexture *cacheTexture = NULL); - CachedGlyphInfo* cacheGlyph(SkPaint* paint, glyph_t glyph); - void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph); + CachedGlyphInfo* cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching); + void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph, + bool precaching); void measureCachedGlyph(CachedGlyphInfo* glyph, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, @@ -258,7 +259,7 @@ protected: void drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset, SkPathMeasure& measure, SkPoint* position, SkVector* tangent); - CachedGlyphInfo* getCachedGlyph(SkPaint* paint, glyph_t textUnit); + CachedGlyphInfo* getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool precaching = false); static glyph_t nextGlyph(const uint16_t** srcPtr) { const uint16_t* src = *srcPtr; @@ -364,7 +365,7 @@ protected: void initTextTexture(); CacheTexture* createCacheTexture(int width, int height, bool allocate); void cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, - uint32_t *retOriginX, uint32_t *retOriginY); + uint32_t *retOriginX, uint32_t *retOriginY, bool precaching); CacheTexture* cacheBitmapInTexture(const SkGlyph& glyph, uint32_t* startX, uint32_t* startY); void flushAllAndInvalidate(); diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp index 726b57c7..2e4e349 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -217,10 +217,12 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions, float amount = (pos - start) / distance; float oppAmount = 1.0f - amount; - *p++ = uint8_t(startR * oppAmount + endR * amount); - *p++ = uint8_t(startG * oppAmount + endG * amount); - *p++ = uint8_t(startB * oppAmount + endB * amount); - *p++ = uint8_t(startA * oppAmount + endA * amount); + const float alpha = startA * oppAmount + endA * amount; + const float a = alpha / 255.0f; + *p++ = uint8_t(a * (startR * oppAmount + endR * amount)); + *p++ = uint8_t(a * (startG * oppAmount + endG * amount)); + *p++ = uint8_t(a * (startB * oppAmount + endB * amount)); + *p++ = uint8_t(alpha); } for (int i = 1; i < GRADIENT_TEXTURE_HEIGHT; i++) { diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 849c556..8da9f66 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -440,7 +440,7 @@ int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom, mode = SkXfermode::kSrcOver_Mode; } - createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags, previousFbo); + createLayer(left, top, right, bottom, alpha, mode, flags, previousFbo); } return count; @@ -508,44 +508,56 @@ int OpenGLRenderer::saveLayerAlpha(float left, float top, float right, float bot * buffer is left untouched until the first drawing operation. Only when * something actually gets drawn are the layers regions cleared. */ -bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top, - float right, float bottom, int alpha, SkXfermode::Mode mode, - int flags, GLuint previousFbo) { +bool OpenGLRenderer::createLayer(float left, float top, float right, float bottom, + int alpha, SkXfermode::Mode mode, int flags, GLuint previousFbo) { LAYER_LOGD("Requesting layer %.2fx%.2f", right - left, bottom - top); LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize()); const bool fboLayer = flags & SkCanvas::kClipToLayer_SaveFlag; // Window coordinates of the layer + Rect clip; Rect bounds(left, top, right, bottom); - if (!fboLayer) { - mSnapshot->transform->mapRect(bounds); - - // Layers only make sense if they are in the framebuffer's bounds - if (bounds.intersect(*snapshot->clipRect)) { - // We cannot work with sub-pixels in this case - bounds.snapToPixelBoundaries(); - - // When the layer is not an FBO, we may use glCopyTexImage so we - // need to make sure the layer does not extend outside the bounds - // of the framebuffer - if (!bounds.intersect(snapshot->previous->viewport)) { - bounds.setEmpty(); - } - } else { + Rect untransformedBounds(bounds); + mSnapshot->transform->mapRect(bounds); + + // Layers only make sense if they are in the framebuffer's bounds + if (bounds.intersect(*mSnapshot->clipRect)) { + // We cannot work with sub-pixels in this case + bounds.snapToPixelBoundaries(); + + // When the layer is not an FBO, we may use glCopyTexImage so we + // need to make sure the layer does not extend outside the bounds + // of the framebuffer + if (!bounds.intersect(mSnapshot->previous->viewport)) { bounds.setEmpty(); + } else if (fboLayer) { + clip.set(bounds); + mat4 inverse; + inverse.loadInverse(*mSnapshot->transform); + inverse.mapRect(clip); + clip.snapToPixelBoundaries(); + if (clip.intersect(untransformedBounds)) { + clip.translate(-left, -top); + bounds.set(untransformedBounds); + } else { + clip.setEmpty(); + } } + } else { + bounds.setEmpty(); } if (bounds.isEmpty() || bounds.getWidth() > mCaches.maxTextureSize || - bounds.getHeight() > mCaches.maxTextureSize) { - snapshot->empty = fboLayer; + bounds.getHeight() > mCaches.maxTextureSize || + (fboLayer && clip.isEmpty())) { + mSnapshot->empty = fboLayer; } else { - snapshot->invisible = snapshot->invisible || (alpha <= ALPHA_THRESHOLD && fboLayer); + mSnapshot->invisible = mSnapshot->invisible || (alpha <= ALPHA_THRESHOLD && fboLayer); } // Bail out if we won't draw in this snapshot - if (snapshot->invisible || snapshot->empty) { + if (mSnapshot->invisible || mSnapshot->empty) { return false; } @@ -563,23 +575,23 @@ bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top, layer->setBlend(true); // Save the layer in the snapshot - snapshot->flags |= Snapshot::kFlagIsLayer; - snapshot->layer = layer; + mSnapshot->flags |= Snapshot::kFlagIsLayer; + mSnapshot->layer = layer; if (fboLayer) { - return createFboLayer(layer, bounds, snapshot, previousFbo); + return createFboLayer(layer, bounds, clip, previousFbo); } else { // Copy the framebuffer into the layer layer->bindTexture(); if (!bounds.isEmpty()) { if (layer->isEmpty()) { glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, - bounds.left, snapshot->height - bounds.bottom, + bounds.left, mSnapshot->height - bounds.bottom, layer->getWidth(), layer->getHeight(), 0); layer->setEmpty(false); } else { glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, - snapshot->height - bounds.bottom, bounds.getWidth(), bounds.getHeight()); + mSnapshot->height - bounds.bottom, bounds.getWidth(), bounds.getHeight()); } // Enqueue the buffer coordinates to clear the corresponding region later @@ -590,35 +602,20 @@ bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top, return true; } -bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, sp<Snapshot> snapshot, - GLuint previousFbo) { +bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip, GLuint previousFbo) { layer->setFbo(mCaches.fboCache.get()); - snapshot->region = &snapshot->layer->region; - snapshot->flags |= Snapshot::kFlagFboTarget; - - Rect clip(bounds); - snapshot->transform->mapRect(clip); - clip.intersect(*snapshot->clipRect); - clip.snapToPixelBoundaries(); - clip.intersect(snapshot->previous->viewport); + mSnapshot->region = &mSnapshot->layer->region; + mSnapshot->flags |= Snapshot::kFlagFboTarget; - mat4 inverse; - inverse.loadInverse(*mSnapshot->transform); - - inverse.mapRect(clip); - clip.snapToPixelBoundaries(); - clip.intersect(bounds); - clip.translate(-bounds.left, -bounds.top); - - snapshot->flags |= Snapshot::kFlagIsFboLayer; - snapshot->fbo = layer->getFbo(); - snapshot->resetTransform(-bounds.left, -bounds.top, 0.0f); - snapshot->resetClip(clip.left, clip.top, clip.right, clip.bottom); - snapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight()); - snapshot->height = bounds.getHeight(); - snapshot->flags |= Snapshot::kFlagDirtyOrtho; - snapshot->orthoMatrix.load(mOrthoMatrix); + mSnapshot->flags |= Snapshot::kFlagIsFboLayer; + mSnapshot->fbo = layer->getFbo(); + mSnapshot->resetTransform(-bounds.left, -bounds.top, 0.0f); + mSnapshot->resetClip(clip.left, clip.top, clip.right, clip.bottom); + mSnapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight()); + mSnapshot->height = bounds.getHeight(); + mSnapshot->flags |= Snapshot::kFlagDirtyOrtho; + mSnapshot->orthoMatrix.load(mOrthoMatrix); // Bind texture to FBO glBindFramebuffer(GL_FRAMEBUFFER, layer->getFbo()); @@ -1403,10 +1400,6 @@ void OpenGLRenderer::setupDrawAALine(GLvoid* vertices, GLvoid* widthCoords, int boundaryWidthSlot = mCaches.currentProgram->getUniform("boundaryWidth"); glUniform1f(boundaryWidthSlot, boundaryWidthProportion); - - // Setting the inverse value saves computations per-fragment in the shader - int inverseBoundaryWidthSlot = mCaches.currentProgram->getUniform("inverseBoundaryWidth"); - glUniform1f(inverseBoundaryWidthSlot, 1.0f / boundaryWidthProportion); } void OpenGLRenderer::finishDrawAALine(const int widthSlot, const int lengthSlot) { @@ -1810,15 +1803,13 @@ void OpenGLRenderer::drawAARect(float left, float top, float right, float bottom float width = right - left; float height = bottom - top; - float boundaryWidthProportion = (width != 0) ? (2 * boundarySizeX) / width : 0; - float boundaryHeightProportion = (height != 0) ? (2 * boundarySizeY) / height : 0; + float boundaryWidthProportion = .5 - ((width != 0) ? (2 * boundarySizeX) / width : 0); + float boundaryHeightProportion = .5 - ((height != 0) ? (2 * boundarySizeY) / height : 0); setupDrawAALine((void*) aaVertices, widthCoords, lengthCoords, boundaryWidthProportion, widthSlot, lengthSlot); int boundaryLengthSlot = mCaches.currentProgram->getUniform("boundaryLength"); - int inverseBoundaryLengthSlot = mCaches.currentProgram->getUniform("inverseBoundaryLength"); glUniform1f(boundaryLengthSlot, boundaryHeightProportion); - glUniform1f(inverseBoundaryLengthSlot, (1.0f / boundaryHeightProportion)); AAVertex::set(aaVertices++, left, bottom, 1, 1); AAVertex::set(aaVertices++, left, top, 1, 0); @@ -1955,9 +1946,7 @@ status_t OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { Vertex* prevVertex = NULL; int boundaryLengthSlot = -1; - int inverseBoundaryLengthSlot = -1; int boundaryWidthSlot = -1; - int inverseBoundaryWidthSlot = -1; for (int i = 0; i < count; i += 4) { // a = start point, b = end point @@ -2060,22 +2049,16 @@ status_t OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { if (boundaryWidthSlot < 0) { boundaryWidthSlot = mCaches.currentProgram->getUniform("boundaryWidth"); - inverseBoundaryWidthSlot = - mCaches.currentProgram->getUniform("inverseBoundaryWidth"); } glUniform1f(boundaryWidthSlot, boundaryWidthProportion); - glUniform1f(inverseBoundaryWidthSlot, (1 / boundaryWidthProportion)); } if (boundaryLengthSlot < 0) { boundaryLengthSlot = mCaches.currentProgram->getUniform("boundaryLength"); - inverseBoundaryLengthSlot = - mCaches.currentProgram->getUniform("inverseBoundaryLength"); } glUniform1f(boundaryLengthSlot, boundaryLengthProportion); - glUniform1f(inverseBoundaryLengthSlot, (1 / boundaryLengthProportion)); if (prevAAVertex != NULL) { // Issue two repeat vertices to create degenerate triangles to bridge diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index d3b98a4..2369f47 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -380,7 +380,7 @@ private: * * @return True if the layer was successfully created, false otherwise */ - bool createLayer(sp<Snapshot> snapshot, float left, float top, float right, float bottom, + bool createLayer(float left, float top, float right, float bottom, int alpha, SkXfermode::Mode mode, int flags, GLuint previousFbo); /** @@ -391,8 +391,7 @@ private: * @param bounds The bounds of the layer * @param previousFbo The name of the current framebuffer */ - bool createFboLayer(Layer* layer, Rect& bounds, sp<Snapshot> snapshot, - GLuint previousFbo); + bool createFboLayer(Layer* layer, Rect& bounds, Rect& clip, GLuint previousFbo); /** * Compose the specified layer as a region. diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp index d67bfbe..8a9a2ac 100644 --- a/libs/hwui/ProgramCache.cpp +++ b/libs/hwui/ProgramCache.cpp @@ -129,9 +129,7 @@ const char* gFS_Uniforms_Color = "uniform vec4 color;\n"; const char* gFS_Uniforms_AA = "uniform float boundaryWidth;\n" - "uniform float inverseBoundaryWidth;\n" - "uniform float boundaryLength;\n" - "uniform float inverseBoundaryLength;\n"; + "uniform float boundaryLength;\n"; const char* gFS_Header_Uniforms_PointHasBitmap = "uniform vec2 textureDimension;\n" "uniform float pointSize;\n"; @@ -242,16 +240,9 @@ const char* gFS_Main_ModulateColor = const char* gFS_Main_ModulateColor_ApplyGamma = " fragColor *= pow(color.a, gamma);\n"; const char* gFS_Main_AccountForAA = - " if (widthProportion < boundaryWidth) {\n" - " fragColor *= (widthProportion * inverseBoundaryWidth);\n" - " } else if (widthProportion > (1.0 - boundaryWidth)) {\n" - " fragColor *= ((1.0 - widthProportion) * inverseBoundaryWidth);\n" - " }\n" - " if (lengthProportion < boundaryLength) {\n" - " fragColor *= (lengthProportion * inverseBoundaryLength);\n" - " } else if (lengthProportion > (1.0 - boundaryLength)) {\n" - " fragColor *= ((1.0 - lengthProportion) * inverseBoundaryLength);\n" - " }\n"; + " fragColor *= (1.0 - smoothstep(boundaryWidth, 0.5, abs(0.5 - widthProportion)))\n" + " * (1.0 - smoothstep(boundaryLength, 0.5, abs(0.5 - lengthProportion)));\n"; + const char* gFS_Main_FetchTexture[2] = { // Don't modulate " fragColor = texture2D(sampler, outTexCoords);\n", diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp index 8916efd..9013fd5 100644 --- a/libs/hwui/SkiaShader.cpp +++ b/libs/hwui/SkiaShader.cpp @@ -46,11 +46,12 @@ static inline bool isPowerOfTwo(unsigned int n) { } static inline void bindUniformColor(int slot, uint32_t color) { + const float a = ((color >> 24) & 0xff) / 255.0f; glUniform4f(slot, - ((color >> 16) & 0xff) / 255.0f, - ((color >> 8) & 0xff) / 255.0f, - ((color ) & 0xff) / 255.0f, - ((color >> 24) & 0xff) / 255.0f); + a * ((color >> 16) & 0xff) / 255.0f, + a * ((color >> 8) & 0xff) / 255.0f, + a * ((color ) & 0xff) / 255.0f, + a); } /////////////////////////////////////////////////////////////////////////////// @@ -154,10 +155,6 @@ void SkiaBitmapShader::setupProgram(Program* program, const mat4& modelView, // Uniforms bindTexture(texture, mWrapS, mWrapT); - // Assume linear here; we should really check the transform in - // ::updateTransforms() but we don't have the texture object - // available at that point. The optimization is not worth the - // effort for now. texture->setFilter(GL_LINEAR); glUniform1i(program->getUniform("bitmapSampler"), textureSlot); @@ -166,14 +163,6 @@ void SkiaBitmapShader::setupProgram(Program* program, const mat4& modelView, glUniform2f(program->getUniform("textureDimension"), 1.0f / width, 1.0f / height); } -void SkiaBitmapShader::updateTransforms(Program* program, const mat4& modelView, - const Snapshot& snapshot) { - mat4 textureTransform; - computeScreenSpaceMatrix(textureTransform, modelView); - glUniformMatrix4fv(program->getUniform("textureTransform"), 1, - GL_FALSE, &textureTransform.data[0]); -} - /////////////////////////////////////////////////////////////////////////////// // Linear gradient shader /////////////////////////////////////////////////////////////////////////////// @@ -257,13 +246,6 @@ void SkiaLinearGradientShader::setupProgram(Program* program, const mat4& modelV glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]); } -void SkiaLinearGradientShader::updateTransforms(Program* program, const mat4& modelView, - const Snapshot& snapshot) { - mat4 screenSpace; - computeScreenSpaceMatrix(screenSpace, modelView); - glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]); -} - /////////////////////////////////////////////////////////////////////////////// // Circular gradient shader /////////////////////////////////////////////////////////////////////////////// @@ -384,13 +366,6 @@ void SkiaSweepGradientShader::setupProgram(Program* program, const mat4& modelVi glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]); } -void SkiaSweepGradientShader::updateTransforms(Program* program, const mat4& modelView, - const Snapshot& snapshot) { - mat4 screenSpace; - computeScreenSpaceMatrix(screenSpace, modelView); - glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]); -} - /////////////////////////////////////////////////////////////////////////////// // Compose shader /////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h index a710b86..2687592 100644 --- a/libs/hwui/SkiaShader.h +++ b/libs/hwui/SkiaShader.h @@ -82,10 +82,6 @@ struct SkiaShader { mGradientCache = gradientCache; } - virtual void updateTransforms(Program* program, const mat4& modelView, - const Snapshot& snapshot) { - } - uint32_t getGenerationId() { return mGenerationId; } @@ -148,7 +144,6 @@ struct SkiaBitmapShader: public SkiaShader { void describe(ProgramDescription& description, const Extensions& extensions); void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot, GLuint* textureUnit); - void updateTransforms(Program* program, const mat4& modelView, const Snapshot& snapshot); private: SkiaBitmapShader() { @@ -172,7 +167,6 @@ struct SkiaLinearGradientShader: public SkiaShader { void describe(ProgramDescription& description, const Extensions& extensions); void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot, GLuint* textureUnit); - void updateTransforms(Program* program, const mat4& modelView, const Snapshot& snapshot); private: SkiaLinearGradientShader() { @@ -197,7 +191,6 @@ struct SkiaSweepGradientShader: public SkiaShader { virtual void describe(ProgramDescription& description, const Extensions& extensions); void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot, GLuint* textureUnit); - void updateTransforms(Program* program, const mat4& modelView, const Snapshot& snapshot); protected: SkiaSweepGradientShader(Type type, float x, float y, uint32_t* colors, float* positions, diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 84fb0dd..c98fcd3 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -3294,7 +3294,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // sent if none of these devices is connected. int mBecomingNoisyIntentDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | - AudioSystem.DEVICE_OUT_ALL_A2DP; + AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_AUX_DIGITAL | + AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | + AudioSystem.DEVICE_OUT_ALL_USB; // must be called before removing the device from mConnectedDevices private int checkSendBecomingNoisyIntent(int device, int state) { diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 4941ae5..f91c9a0 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -370,7 +370,7 @@ static void android_media_MediaCodec_native_configure( sp<ISurfaceTexture> surfaceTexture; if (jsurface != NULL) { - sp<Surface> surface(Surface_getSurface(env, jsurface)); + sp<Surface> surface(android_view_Surface_getSurface(env, jsurface)); if (surface != NULL) { surfaceTexture = surface->getSurfaceTexture(); } else { diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index c2a6889..04ba348 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -271,7 +271,7 @@ setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlaye sp<ISurfaceTexture> new_st; if (jsurface) { - sp<Surface> surface(Surface_getSurface(env, jsurface)); + sp<Surface> surface(android_view_Surface_getSurface(env, jsurface)); if (surface != NULL) { new_st = surface->getSurfaceTexture(); new_st->incStrong(thiz); diff --git a/native/android/native_window.cpp b/native/android/native_window.cpp index 99c0fd3..ca0c902 100644 --- a/native/android/native_window.cpp +++ b/native/android/native_window.cpp @@ -25,7 +25,7 @@ using namespace android; ANativeWindow* ANativeWindow_fromSurface(JNIEnv* env, jobject surface) { - sp<ANativeWindow> win = android_Surface_getNativeWindow(env, surface); + sp<ANativeWindow> win = android_view_Surface_getNativeWindow(env, surface); if (win != NULL) { win->incStrong((void*)ANativeWindow_acquire); } diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index 9a80090..875d2c9 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -46,6 +46,8 @@ <bool name="def_netstats_enabled">true</bool> <bool name="def_usb_mass_storage_enabled">true</bool> <bool name="def_wifi_on">false</bool> + <!-- 0 == default, 1 == never while plugged, 2 == never --> + <integer name="def_wifi_sleep_policy">0</integer> <bool name="def_networks_available_notification_on">true</bool> <bool name="def_backup_enabled">false</bool> @@ -158,7 +160,7 @@ <!-- Whether the feature activates when docked (SCREENSAVER_ACTIVATE_ON_DOCK) --> <bool name="def_screensaver_activate_on_dock">true</bool> <!-- Whether the feature activates when docked (SCREENSAVER_ACTIVATE_ON_SLEEP) --> - <bool name="def_screensaver_activate_on_sleep">true</bool> + <bool name="def_screensaver_activate_on_sleep">false</bool> <!-- ComponentName of the default screen saver (Settings.Secure.SCREENSAVER_COMPONENT) --> <string name="def_screensaver_component">com.google.android.deskclock/com.android.deskclock.Screensaver</string> </resources> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index db81786..2785991 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -1568,6 +1568,8 @@ public class DatabaseHelper extends SQLiteOpenHelper { loadIntegerSetting(stmt, Settings.System.POINTER_SPEED, R.integer.def_pointer_speed); + loadIntegerSetting(stmt, Settings.System.WIFI_SLEEP_POLICY, + R.integer.def_wifi_sleep_policy); } finally { if (stmt != null) stmt.close(); } diff --git a/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml b/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml index 67e13eb..fbbd7e5 100644 --- a/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml +++ b/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml @@ -143,8 +143,13 @@ <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" - android:layout_height="@dimen/navigation_bar_deadzone_size" + android:layout_height="match_parent" android:layout_width="match_parent" + systemui:minSize="@dimen/navigation_bar_deadzone_size" + systemui:maxSize="@dimen/navigation_bar_deadzone_size_max" + systemui:holdTime="@integer/navigation_bar_deadzone_hold" + systemui:decayTime="@integer/navigation_bar_deadzone_decay" + systemui:orientation="horizontal" android:layout_gravity="top" /> </FrameLayout> @@ -269,8 +274,13 @@ <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" - android:layout_height="@dimen/navigation_bar_deadzone_size" + android:layout_height="match_parent" android:layout_width="match_parent" + systemui:minSize="@dimen/navigation_bar_deadzone_size" + systemui:maxSize="@dimen/navigation_bar_deadzone_size_max" + systemui:holdTime="@integer/navigation_bar_deadzone_hold" + systemui:decayTime="@integer/navigation_bar_deadzone_decay" + systemui:orientation="vertical" android:layout_gravity="top" /> </FrameLayout> diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml index d41040d..33b5dbb 100644 --- a/packages/SystemUI/res/layout/navigation_bar.xml +++ b/packages/SystemUI/res/layout/navigation_bar.xml @@ -147,8 +147,13 @@ <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" - android:layout_height="@dimen/navigation_bar_deadzone_size" + android:layout_height="match_parent" android:layout_width="match_parent" + systemui:minSize="@dimen/navigation_bar_deadzone_size" + systemui:maxSize="@dimen/navigation_bar_deadzone_size_max" + systemui:holdTime="@integer/navigation_bar_deadzone_hold" + systemui:decayTime="@integer/navigation_bar_deadzone_decay" + systemui:orientation="horizontal" android:layout_gravity="top" /> </FrameLayout> @@ -276,9 +281,14 @@ <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" - android:layout_width="@dimen/navigation_bar_deadzone_size" android:layout_height="match_parent" - android:layout_gravity="left" + android:layout_width="match_parent" + systemui:minSize="@dimen/navigation_bar_deadzone_size" + systemui:maxSize="@dimen/navigation_bar_deadzone_size_max" + systemui:holdTime="@integer/navigation_bar_deadzone_hold" + systemui:decayTime="@integer/navigation_bar_deadzone_decay" + systemui:orientation="vertical" + android:layout_gravity="top" /> </FrameLayout> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index f34ed8e..36b52e3 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -60,7 +60,7 @@ <string name="always_use_accessory" msgid="1210954576979621596">"ለእዚህ USB ተቀጥላ በነባሪነት ተጠቀም"</string> <string name="usb_debugging_title" msgid="1114766024068112429">"የUSB ማረሚያ ይፈቀድ?"</string> <string name="usb_debugging_message" msgid="719863946976291180">"የUSB ማረም ከዚህ ኮምፒውተር ይፈቀድ?"\n"የእርስዎ RSA ቁልፍ ጣት አሻራ "\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g> ነው"</string> - <string name="usb_debugging_always" msgid="4253099426793114693">"ለእዚህ ኮምፒውተር ሁልጊዜ ፍቀድ"</string> + <string name="usb_debugging_always" msgid="4253099426793114693">"ለዚህ ኮምፒውተር ሁልጊዜ ፍቀድ"</string> <string name="compat_mode_on" msgid="6623839244840638213">"ማያ እንዲሞላ አጉላ"</string> <string name="compat_mode_off" msgid="4434467572461327898">"ማያ ለመሙለት ሳብ"</string> <string name="compat_mode_help_header" msgid="7969493989397529910">"የተኳኋኝነት አጉላ"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index e0aa3f9..87b0088 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -59,7 +59,7 @@ <string name="always_use_device" msgid="1450287437017315906">"Se usa de forma predeterminada para este dispositivo USB."</string> <string name="always_use_accessory" msgid="1210954576979621596">"Se usa de forma predeterminada para este accesorio USB."</string> <string name="usb_debugging_title" msgid="1114766024068112429">"¿Permitir la depuración de USB?"</string> - <string name="usb_debugging_message" msgid="719863946976291180">"¿Quieres permitir la depuración de USB desde esta computadora?"\n"La huella digital de tu clave RSA es"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>."</string> + <string name="usb_debugging_message" msgid="719863946976291180">"¿Quieres permitir la depuración de USB desde esta computadora?"\n"La huella digital de tu clave RSA es:"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> <string name="usb_debugging_always" msgid="4253099426793114693">"Permitir el uso de esta computadora siempre"</string> <string name="compat_mode_on" msgid="6623839244840638213">"Zoom para ocupar la pantalla"</string> <string name="compat_mode_off" msgid="4434467572461327898">"Estirar p/ ocupar la pantalla"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 16e1443..cfc0aec 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -58,8 +58,8 @@ <string name="label_view" msgid="6304565553218192990">"देखें"</string> <string name="always_use_device" msgid="1450287437017315906">"इस USB उपकरण के लिए डिफ़ॉल्ट रूप से उपयोग करें"</string> <string name="always_use_accessory" msgid="1210954576979621596">"इस USB एसेसरी के लिए डिफ़ॉल्ट रूप से उपयोग करें"</string> - <string name="usb_debugging_title" msgid="1114766024068112429">"USB डीबग करने दें?"</string> - <string name="usb_debugging_message" msgid="719863946976291180">"इस कंप्यूटर से USB डीबग करने दें?"\n"आपका RSA कुंजी फ़िंगरप्रिंट यह है:"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> + <string name="usb_debugging_title" msgid="1114766024068112429">"USB डीबगिंग करने दें?"</string> + <string name="usb_debugging_message" msgid="719863946976291180">"इस कंप्यूटर से USB डीबगिंग करने दें?"\n"आपका RSA कुंजी फ़िंगरप्रिंट यह है:"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> <string name="usb_debugging_always" msgid="4253099426793114693">"इस कंप्यूटर को हमेशा अनुमति दें"</string> <string name="compat_mode_on" msgid="6623839244840638213">"स्क्रीन भरने हेतु ज़ूम करें"</string> <string name="compat_mode_off" msgid="4434467572461327898">"स्क्रीन को भरने के लिए खींचें"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 830b73c..7150c94 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -58,8 +58,8 @@ <string name="label_view" msgid="6304565553218192990">"Prikaži"</string> <string name="always_use_device" msgid="1450287437017315906">"Koristi se prema zadanim postavkama za ovaj USB uređaj"</string> <string name="always_use_accessory" msgid="1210954576979621596">"Koristi se prema zadanim postavkama za ovaj USB pribor"</string> - <string name="usb_debugging_title" msgid="1114766024068112429">"Omogućiti rješavanje programske pogreške na USB-u?"</string> - <string name="usb_debugging_message" msgid="719863946976291180">"Omogućiti rješavanje programske pogreške na USB-u na ovom računalu?"\n"Vaš je otisak prsta RSA ključa"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> + <string name="usb_debugging_title" msgid="1114766024068112429">"Omogućiti USB Debugging?"</string> + <string name="usb_debugging_message" msgid="719863946976291180">"Omogućiti USB Debugging na ovom računalu?"\n"Vaš je otisak prsta RSA ključa"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> <string name="usb_debugging_always" msgid="4253099426793114693">"Uvijek dopusti ovom računalu"</string> <string name="compat_mode_on" msgid="6623839244840638213">"Zumiraj i ispuni zaslon"</string> <string name="compat_mode_off" msgid="4434467572461327898">"Rastegni i ispuni zaslon"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 59730af..f820464 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -58,8 +58,8 @@ <string name="label_view" msgid="6304565553218192990">"הצג"</string> <string name="always_use_device" msgid="1450287437017315906">"השתמש כברירת מחדל עבור מכשיר USB זה"</string> <string name="always_use_accessory" msgid="1210954576979621596">"השתמש כברירת מחדל עבור אביזר USB זה"</string> - <string name="usb_debugging_title" msgid="1114766024068112429">"האם לאפשר ניקוי באגים ב-USB?"</string> - <string name="usb_debugging_message" msgid="719863946976291180">"האם להרשות ניקוי באגים ב-USB ממחשב זה?"\n"טביעת האצבע של מפתח ה-RSA שלך היא"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> + <string name="usb_debugging_title" msgid="1114766024068112429">"האם לאפשר ניפוי באגים ב-USB?"</string> + <string name="usb_debugging_message" msgid="719863946976291180">"האם להרשות ניפוי באגים ב-USB ממחשב זה?"\n"טביעת האצבע של מפתח ה-RSA שלך היא"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> <string name="usb_debugging_always" msgid="4253099426793114693">"הרשה תמיד במחשב זה"</string> <string name="compat_mode_on" msgid="6623839244840638213">"הגדל תצוגה כדי למלא את המסך"</string> <string name="compat_mode_off" msgid="4434467572461327898">"מתח כדי למלא את המסך"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index a6fec13..768b1c8 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -58,7 +58,7 @@ <string name="always_use_accessory" msgid="1210954576979621596">"Tumia kama chaguo-msingi ya kifuasi hiki cha USB"</string> <string name="usb_debugging_title" msgid="1114766024068112429">"Ruhusu Utatuaji USB?"</string> <string name="usb_debugging_message" msgid="719863946976291180">"Ruhusu Utatuaji wa USB kutoka kwenye kompyuta hii?"\n"Kitufe chako RSA cha alama ya kidole ni "\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> - <string name="usb_debugging_always" msgid="4253099426793114693">"Kila wakati ruhusu kompyuta hii"</string> + <string name="usb_debugging_always" msgid="4253099426793114693">"Ruhusu kompyuta hii kila wakati"</string> <string name="compat_mode_on" msgid="6623839244840638213">"Kuza ili kujaza skrini"</string> <string name="compat_mode_off" msgid="4434467572461327898">"Tanua ili kujaza skrini"</string> <string name="compat_mode_help_header" msgid="7969493989397529910">"Kukuza kwa Utangamanifu"</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index c24fa36..fb06e1a 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -59,7 +59,7 @@ <string name="always_use_device" msgid="1450287437017315906">"ใช้ค่าเริ่มต้นสำหรับอุปกรณ์ USB นี้"</string> <string name="always_use_accessory" msgid="1210954576979621596">"ใช้ค่าเริ่มต้นสำหรับอุปกรณ์เสริม USB นี้"</string> <string name="usb_debugging_title" msgid="1114766024068112429">"อนุญาตการแก้ไขข้อบกพร่องของ USB หรือไม่"</string> - <string name="usb_debugging_message" msgid="719863946976291180">"อนุญาตการแก้ไขข้อบกพร่องของ USB จากคอมพิวเตอร์เครื่องนี้หรือไ่ม่"\n"ลายนิ้วมือคีย์ RSA ของคุณคือ"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> + <string name="usb_debugging_message" msgid="719863946976291180">"อนุญาตการแก้ไขข้อบกพร่องของ USB จากคอมพิวเตอร์เครื่องนี้หรือไม่"\n"ลายนิ้วมือคีย์ RSA ของคุณคือ"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> <string name="usb_debugging_always" msgid="4253099426793114693">"อนุญาตคอมพิวเตอร์เครื่องนี้เสมอ"</string> <string name="compat_mode_on" msgid="6623839244840638213">"ขยายจนเต็มหน้าจอ"</string> <string name="compat_mode_off" msgid="4434467572461327898">"ยืดจนเต็มหน้าจอ"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index c642c87..f9d2b3c 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -59,7 +59,7 @@ <string name="always_use_device" msgid="1450287437017315906">"Sử dụng theo mặc định cho thiết bị USB này"</string> <string name="always_use_accessory" msgid="1210954576979621596">"Sử dụng theo mặc định cho phụ kiện USB này"</string> <string name="usb_debugging_title" msgid="1114766024068112429">"Cho phép gỡ lỗi USB?"</string> - <string name="usb_debugging_message" msgid="719863946976291180">"Cho phép gỡ lỗi USB từ máy tính này?"\n"Tệp tham chiếu chính của RSA của bạn là"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> + <string name="usb_debugging_message" msgid="719863946976291180">"Cho phép gỡ lỗi USB từ máy tính này?"\n"Dấu tay khóa RSA của bạn là"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> <string name="usb_debugging_always" msgid="4253099426793114693">"Luôn cho phép máy tính này"</string> <string name="compat_mode_on" msgid="6623839244840638213">"T.phóng để lấp đầy m.hình"</string> <string name="compat_mode_off" msgid="4434467572461327898">"Giãn ra để lấp đầy m.hình"</string> diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index 48fb21f..047570f 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -35,5 +35,17 @@ <declare-styleable name="RecentsPanelView"> <attr name="recentItemLayout" format="reference" /> </declare-styleable> + <declare-styleable name="DeadZone"> + <attr name="minSize" format="dimension" /> + <attr name="maxSize" format="dimension" /> + <attr name="holdTime" format="integer" /> + <attr name="decayTime" format="integer" /> + <attr name="orientation" /> + </declare-styleable> + + <attr name="orientation"> + <enum name="horizontal" value="0" /> + <enum name="vertical" value="1" /> + </attr> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 1cd7904..13622e6 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -72,5 +72,11 @@ <!-- Whether we're using the tablet-optimized recents interface (we use this value at runtime for some things) --> <integer name="status_bar_recents_bg_gradient_degrees">90</integer> + + <!-- decay duration (from size_max -> size), in ms --> + <integer name="navigation_bar_deadzone_hold">333</integer> + <integer name="navigation_bar_deadzone_decay">333</integer> + + <bool name="config_dead_zone_flash">false</bool> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 94465e2..0d7cdb1 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -63,6 +63,8 @@ <!-- thickness (height) of the dead zone at the top of the navigation bar, reducing false presses on navbar buttons; approx 2mm --> <dimen name="navigation_bar_deadzone_size">12dp</dimen> + <!-- size of the dead zone when touches have recently occurred elsewhere on screen --> + <dimen name="navigation_bar_deadzone_size_max">32dp</dimen> <!-- Height of notification icons in the status bar --> <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java index 4281ccf..2a225d9 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java @@ -337,8 +337,7 @@ public class RecentTasksLoader implements View.OnTouchListener { mContext.getSystemService(Context.ACTIVITY_SERVICE); final List<ActivityManager.RecentTaskInfo> recentTasks = - am.getRecentTasksForUser(MAX_TASKS, - ActivityManager.RECENT_IGNORE_UNAVAILABLE, UserHandle.USER_CURRENT); + am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE); int numTasks = recentTasks.size(); ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_HOME).resolveActivityInfo(pm, 0); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 33973b6..dcc2e57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -45,13 +45,12 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.R; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.DelegateViewHelper; +import com.android.systemui.statusbar.policy.DeadZone; public class NavigationBarView extends LinearLayout { final static boolean DEBUG = false; final static String TAG = "PhoneStatusBar/NavigationBarView"; - final static boolean DEBUG_DEADZONE = false; - final static boolean NAVBAR_ALWAYS_AT_RIGHT = true; final static boolean ANIMATE_HIDE_TRANSITION = false; // turned off because it introduces unsightly delay when videos goes to full screen @@ -71,6 +70,7 @@ public class NavigationBarView extends LinearLayout { private Drawable mBackIcon, mBackLandIcon, mBackAltIcon, mBackAltLandIcon; private DelegateViewHelper mDelegateHelper; + private DeadZone mDeadZone; // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288) final static boolean WORKAROUND_INVALID_LAYOUT = true; @@ -109,10 +109,14 @@ public class NavigationBarView extends LinearLayout { @Override public boolean onTouchEvent(MotionEvent event) { + if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) { + mDeadZone.poke(event); + } if (mDelegateHelper != null) { - mDelegateHelper.onInterceptTouchEvent(event); + boolean ret = mDelegateHelper.onInterceptTouchEvent(event); + if (ret) return true; } - return true; + return super.onTouchEvent(event); } @Override @@ -335,15 +339,13 @@ public class NavigationBarView extends LinearLayout { mCurrentView = mRotatedViews[rot]; mCurrentView.setVisibility(View.VISIBLE); + mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone); + // force the low profile & disabled states into compliance setLowProfile(mLowProfile, false, true /* force */); setDisabledFlags(mDisabledFlags, true /* force */); setMenuVisibility(mShowMenu, true /* force */); - if (DEBUG_DEADZONE) { - mCurrentView.findViewById(R.id.deadzone).setBackgroundColor(0x808080FF); - } - if (DEBUG) { Slog.d(TAG, "reorient(): rot=" + mDisplay.getRotation()); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 9d2678a..9b4ee38 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -639,6 +639,7 @@ public class PhoneStatusBar extends BaseStatusBar { | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.OPAQUE); // this will allow the navbar to run in an overlay on devices that support this diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java index 19fbe96..e5ef5fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java @@ -16,26 +16,150 @@ package com.android.systemui.statusbar.policy; +import android.animation.ObjectAnimator; import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.os.SystemClock; import android.util.AttributeSet; +import android.util.Slog; import android.view.MotionEvent; import android.view.View; import com.android.systemui.R; public class DeadZone extends View { + public static final String TAG = "DeadZone"; + + public static final boolean DEBUG = false; + public static final int HORIZONTAL = 0; + public static final int VERTICAL = 1; + + private static final boolean CHATTY = true; // print to logcat when we eat a click + + private boolean mShouldFlash; + private float mFlashFrac = 0f; + + private int mSizeMax; + private int mSizeMin; + // Upon activity elsewhere in the UI, the dead zone will hold steady for + // mHold ms, then move back over the course of mDecay ms + private int mHold, mDecay; + private boolean mVertical; + private long mLastPokeTime; + + private final Runnable mDebugFlash = new Runnable() { + @Override + public void run() { + ObjectAnimator.ofFloat(DeadZone.this, "flash", 1f, 0f).setDuration(150).start(); + } + }; + public DeadZone(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DeadZone(Context context, AttributeSet attrs, int defStyle) { super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DeadZone, + defStyle, 0); + + mHold = a.getInteger(R.styleable.DeadZone_holdTime, 0); + mDecay = a.getInteger(R.styleable.DeadZone_decayTime, 0); + + mSizeMin = a.getDimensionPixelSize(R.styleable.DeadZone_minSize, 0); + mSizeMax = a.getDimensionPixelSize(R.styleable.DeadZone_maxSize, 0); + + int index = a.getInt(R.styleable.DeadZone_orientation, -1); + mVertical = (index == VERTICAL); + + if (DEBUG) + Slog.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold + + (mVertical ? " vertical" : " horizontal")); + + setFlashOnTouchCapture(context.getResources().getBoolean(R.bool.config_dead_zone_flash)); + } + + static float lerp(float a, float b, float f) { + return (b - a) * f + a; + } + + private float getSize(long now) { + if (mSizeMax == 0) + return 0; + long dt = (now - mLastPokeTime); + if (dt > mHold + mDecay) + return mSizeMin; + if (dt < mHold) + return mSizeMax; + return (int) lerp(mSizeMax, mSizeMin, (float) (dt - mHold) / mDecay); } - // I made you a touch event + public void setFlashOnTouchCapture(boolean dbg) { + mShouldFlash = dbg; + mFlashFrac = 0f; + postInvalidate(); + } + + // I made you a touch event... @Override - public boolean onTouchEvent (MotionEvent event) { - return true; // but I eated it + public boolean onTouchEvent(MotionEvent event) { + if (DEBUG) { + Slog.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction())); + } + + final int action = event.getAction(); + if (action == MotionEvent.ACTION_OUTSIDE) { + poke(event); + } else if (action == MotionEvent.ACTION_DOWN) { + if (DEBUG) { + Slog.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY()); + } + int size = (int) getSize(event.getEventTime()); + if ((mVertical && event.getX() < size) || event.getY() < size) { + if (CHATTY) { + Slog.v(TAG, "consuming errant click: (" + event.getX() + "," + event.getY() + ")"); + } + if (mShouldFlash) { + post(mDebugFlash); + postInvalidate(); + } + return true; // ...but I eated it + } + } + return false; + } + + public void poke(MotionEvent event) { + mLastPokeTime = event.getEventTime(); + if (DEBUG) + Slog.v(TAG, "poked! size=" + getSize(mLastPokeTime)); + postInvalidate(); + } + + public void setFlash(float f) { + mFlashFrac = f; + postInvalidate(); } -} + public float getFlash() { + return mFlashFrac; + } + + @Override + public void onDraw(Canvas can) { + if (!mShouldFlash || mFlashFrac <= 0f) { + return; + } + + final int size = (int) getSize(SystemClock.uptimeMillis()); + can.clipRect(0, 0, mVertical ? size : can.getWidth(), mVertical ? can.getHeight() : size); + final float frac = DEBUG ? (mFlashFrac - 0.5f) + 0.5f : mFlashFrac; + can.drawARGB((int) (frac * 0xFF), 0xDD, 0xEE, 0xAA); + + if (DEBUG && size > mSizeMin) + // crazy aggressive redrawing here, for debugging only + postInvalidateDelayed(100); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java index 8365d08..d94c6b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java @@ -988,6 +988,8 @@ public class NetworkController extends BroadcastReceiver { combinedActivityIconId = mMobileActivityIconId; combinedSignalIconId = mDataSignalIconId; // set by updateDataIcon() mContentDescriptionCombinedSignal = mContentDescriptionDataType; + } else { + mMobileActivityIconId = 0; } } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 209ad38..2b6e856 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -61,6 +61,8 @@ import android.provider.Settings; import com.android.internal.R; import com.android.internal.policy.PolicyManager; +import com.android.internal.policy.impl.keyguard.KeyguardViewManager; +import com.android.internal.policy.impl.keyguard.KeyguardViewMediator; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.telephony.ITelephony; import com.android.internal.widget.PointerLocationView; @@ -108,6 +110,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVE import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_DRAG; import static android.view.WindowManager.LayoutParams.TYPE_DREAM; import static android.view.WindowManager.LayoutParams.TYPE_HIDDEN_NAV_CONSUMER; @@ -218,14 +221,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final int NAVIGATION_BAR_PANEL_LAYER = 20; // system-level error dialogs static final int SYSTEM_ERROR_LAYER = 21; + // used to simulate secondary display devices + static final int DISPLAY_OVERLAY_LAYER = 22; // the drag layer: input for drag-and-drop is associated with this window, // which sits above all other focusable windows - static final int DRAG_LAYER = 22; - static final int SECURE_SYSTEM_OVERLAY_LAYER = 23; - static final int BOOT_PROGRESS_LAYER = 24; + static final int DRAG_LAYER = 23; + static final int SECURE_SYSTEM_OVERLAY_LAYER = 24; + static final int BOOT_PROGRESS_LAYER = 25; // the (mouse) pointer layer - static final int POINTER_LAYER = 25; - static final int HIDDEN_NAV_CONSUMER_LAYER = 26; + static final int POINTER_LAYER = 26; + static final int HIDDEN_NAV_CONSUMER_LAYER = 27; static final int APPLICATION_MEDIA_SUBLAYER = -2; static final int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1; @@ -861,7 +866,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0")); if (!mHeadless) { // don't create KeyguardViewMediator if headless - mKeyguardMediator = new KeyguardViewMediator(context, this); + mKeyguardMediator = new KeyguardViewMediator(context, null); } mHandler = new PolicyHandler(); mOrientationListener = new MyOrientationListener(mContext); @@ -1327,6 +1332,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { return SCREENSAVER_LAYER; case TYPE_UNIVERSE_BACKGROUND: return UNIVERSE_BACKGROUND_LAYER; + case TYPE_DISPLAY_OVERLAY: + return DISPLAY_OVERLAY_LAYER; } Log.e(TAG, "Unknown window type: " + type); return APPLICATION_LAYER; @@ -3134,7 +3141,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public void onServiceDisconnected(ComponentName name) {} }; - if (mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)) { + if (mContext.bindService( + intent, conn, Context.BIND_AUTO_CREATE, UserHandle.USER_CURRENT)) { mScreenshotConnection = conn; mHandler.postDelayed(mScreenshotTimeout, 10000); } diff --git a/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard/BiometricSensorUnlock.java index f476f82..39afaa2 100644 --- a/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/BiometricSensorUnlock.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard; import android.view.View; diff --git a/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java new file mode 100644 index 0000000..31d138b --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java @@ -0,0 +1,542 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl.keyguard; + +import com.android.internal.policy.IFaceLockCallback; +import com.android.internal.policy.IFaceLockInterface; +import com.android.internal.widget.LockPatternUtils; + +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; +import android.view.View; + +public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback { + + private static final boolean DEBUG = false; + private static final String TAG = "FULLockscreen"; + + private final Context mContext; + private final LockPatternUtils mLockPatternUtils; + + // TODO: is mServiceRunning needed or can we just use mIsRunning or check if mService is null? + private boolean mServiceRunning = false; + // TODO: now that the code has been restructure to do almost all operations from a handler, this + // lock may no longer be necessary. + private final Object mServiceRunningLock = new Object(); + private IFaceLockInterface mService; + private boolean mBoundToService = false; + private View mFaceUnlockView; + + private Handler mHandler; + private final int MSG_SHOW_FACE_UNLOCK_VIEW = 0; + private final int MSG_HIDE_FACE_UNLOCK_VIEW = 1; + private final int MSG_SERVICE_CONNECTED = 2; + private final int MSG_SERVICE_DISCONNECTED = 3; + private final int MSG_UNLOCK = 4; + private final int MSG_CANCEL = 5; + private final int MSG_REPORT_FAILED_ATTEMPT = 6; + private final int MSG_EXPOSE_FALLBACK = 7; + private final int MSG_POKE_WAKELOCK = 8; + + // TODO: This was added for the purpose of adhering to what the biometric interface expects + // the isRunning() function to return. However, it is probably not necessary to have both + // mRunning and mServiceRunning. I'd just rather wait to change that logic. + private volatile boolean mIsRunning = false; + + // Long enough to stay visible while the service starts + // Short enough to not have to wait long for backup if service fails to start or crashes + // The service can take a couple of seconds to start on the first try after boot + private final int SERVICE_STARTUP_VIEW_TIMEOUT = 3000; + + // So the user has a consistent amount of time when brought to the backup method from Face + // Unlock + private final int BACKUP_LOCK_TIMEOUT = 5000; + + KeyguardSecurityCallback mKeyguardScreenCallback; + + /** + * Stores some of the structures that Face Unlock will need to access and creates the handler + * will be used to execute messages on the UI thread. + */ + public FaceUnlock(Context context, KeyguardSecurityCallback keyguardScreenCallback) { + mContext = context; + mLockPatternUtils = new LockPatternUtils(context); + mKeyguardScreenCallback = keyguardScreenCallback; + mHandler = new Handler(this); + } + + /** + * Stores and displays the view that Face Unlock is allowed to draw within. + * TODO: since the layout object will eventually be shared by multiple biometric unlock + * methods, we will have to add our other views (background, cancel button) here. + */ + public void initializeView(View biometricUnlockView) { + Log.d(TAG, "initializeView()"); + mFaceUnlockView = biometricUnlockView; + } + + /** + * Indicates whether Face Unlock is currently running. + */ + public boolean isRunning() { + return mIsRunning; + } + + /** + * Sets the Face Unlock view to visible, hiding it after the specified amount of time. If + * timeoutMillis is 0, no hide is performed. Called on the UI thread. + */ + public void show(long timeoutMillis) { + if (DEBUG) Log.d(TAG, "show()"); + if (mHandler.getLooper() != Looper.myLooper()) { + Log.e(TAG, "show() called off of the UI thread"); + } + + removeDisplayMessages(); + if (mFaceUnlockView != null) { + mFaceUnlockView.setVisibility(View.VISIBLE); + } + if (timeoutMillis > 0) { + mHandler.sendEmptyMessageDelayed(MSG_HIDE_FACE_UNLOCK_VIEW, timeoutMillis); + } + } + + /** + * Hides the Face Unlock view. + */ + public void hide() { + if (DEBUG) Log.d(TAG, "hide()"); + // Remove messages to prevent a delayed show message from undo-ing the hide + removeDisplayMessages(); + mHandler.sendEmptyMessage(MSG_HIDE_FACE_UNLOCK_VIEW); + } + + /** + * Binds to the Face Unlock service. Face Unlock will be started when the bind completes. The + * Face Unlock view is displayed to hide the backup lock while the service is starting up. + * Called on the UI thread. + */ + public boolean start() { + if (DEBUG) Log.d(TAG, "start()"); + if (mHandler.getLooper() != Looper.myLooper()) { + Log.e(TAG, "start() called off of the UI thread"); + } + + if (mIsRunning) { + Log.w(TAG, "start() called when already running"); + } + + // Show Face Unlock view, but only for a little bit so lockpattern will become visible if + // Face Unlock fails to start or crashes + // This must show before bind to guarantee that Face Unlock has a place to display + show(SERVICE_STARTUP_VIEW_TIMEOUT); + if (!mBoundToService) { + Log.d(TAG, "Binding to Face Unlock service"); + mContext.bindService(new Intent(IFaceLockInterface.class.getName()), + mConnection, + Context.BIND_AUTO_CREATE, + mLockPatternUtils.getCurrentUser()); + mBoundToService = true; + } else { + Log.w(TAG, "Attempt to bind to Face Unlock when already bound"); + } + + mIsRunning = true; + return true; + } + + /** + * Stops Face Unlock and unbinds from the service. Called on the UI thread. + */ + public boolean stop() { + if (DEBUG) Log.d(TAG, "stop()"); + if (mHandler.getLooper() != Looper.myLooper()) { + Log.e(TAG, "stop() called off of the UI thread"); + } + + boolean mWasRunning = mIsRunning; + stopUi(); + + if (mBoundToService) { + if (mService != null) { + try { + mService.unregisterCallback(mFaceUnlockCallback); + } catch (RemoteException e) { + // Not much we can do + } + } + Log.d(TAG, "Unbinding from Face Unlock service"); + mContext.unbindService(mConnection); + mBoundToService = false; + } else { + // This is usually not an error when this happens. Sometimes we will tell it to + // unbind multiple times because it's called from both onWindowFocusChanged and + // onDetachedFromWindow. + if (DEBUG) Log.d(TAG, "Attempt to unbind from Face Unlock when not bound"); + } + mIsRunning = false; + return mWasRunning; + } + + /** + * Frees up resources used by Face Unlock and stops it if it is still running. + */ + public void cleanUp() { + if (DEBUG) Log.d(TAG, "cleanUp()"); + if (mService != null) { + try { + mService.unregisterCallback(mFaceUnlockCallback); + } catch (RemoteException e) { + // Not much we can do + } + stopUi(); + mService = null; + } + } + + /** + * Returns the Device Policy Manager quality for Face Unlock, which is BIOMETRIC_WEAK. + */ + public int getQuality() { + return DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK; + } + + /** + * Handles messages such that everything happens on the UI thread in a deterministic order. + * Calls from the Face Unlock service come from binder threads. Calls from lockscreen typically + * come from the UI thread. This makes sure there are no race conditions between those calls. + */ + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_SHOW_FACE_UNLOCK_VIEW: + handleShowFaceUnlockView(); + break; + case MSG_HIDE_FACE_UNLOCK_VIEW: + handleHideFaceUnlockView(); + break; + case MSG_SERVICE_CONNECTED: + handleServiceConnected(); + break; + case MSG_SERVICE_DISCONNECTED: + handleServiceDisconnected(); + break; + case MSG_UNLOCK: + handleUnlock(); + break; + case MSG_CANCEL: + handleCancel(); + break; + case MSG_REPORT_FAILED_ATTEMPT: + handleReportFailedAttempt(); + break; + case MSG_EXPOSE_FALLBACK: + handleExposeFallback(); + break; + case MSG_POKE_WAKELOCK: + handlePokeWakelock(msg.arg1); + break; + default: + Log.e(TAG, "Unhandled message"); + return false; + } + return true; + } + + /** + * Sets the Face Unlock view to visible, thus covering the backup lock. + */ + void handleShowFaceUnlockView() { + if (DEBUG) Log.d(TAG, "handleShowFaceUnlockView()"); + if (mFaceUnlockView != null) { + mFaceUnlockView.setVisibility(View.VISIBLE); + } else { + Log.e(TAG, "mFaceUnlockView is null in handleShowFaceUnlockView()"); + } + } + + /** + * Sets the Face Unlock view to invisible, thus exposing the backup lock. + */ + void handleHideFaceUnlockView() { + if (DEBUG) Log.d(TAG, "handleHideFaceUnlockView()"); + if (mFaceUnlockView != null) { + mFaceUnlockView.setVisibility(View.INVISIBLE); + } else { + Log.e(TAG, "mFaceUnlockView is null in handleHideFaceUnlockView()"); + } + } + + /** + * Tells the service to start its UI via an AIDL interface. Called when the + * onServiceConnected() callback is received. + */ + void handleServiceConnected() { + Log.d(TAG, "handleServiceConnected()"); + + // It is possible that an unbind has occurred in the time between the bind and when this + // function is reached. If an unbind has already occurred, proceeding on to call startUi() + // can result in a fatal error. Note that the onServiceConnected() callback is + // asynchronous, so this possibility would still exist if we executed this directly in + // onServiceConnected() rather than using a handler. + if (!mBoundToService) { + Log.d(TAG, "Dropping startUi() in handleServiceConnected() because no longer bound"); + return; + } + + try { + mService.registerCallback(mFaceUnlockCallback); + } catch (RemoteException e) { + Log.e(TAG, "Caught exception connecting to Face Unlock: " + e.toString()); + mService = null; + mBoundToService = false; + mIsRunning = false; + return; + } + + if (mFaceUnlockView != null) { + IBinder windowToken = mFaceUnlockView.getWindowToken(); + if (windowToken != null) { + // When switching between portrait and landscape view while Face Unlock is running, + // the screen will eventually go dark unless we poke the wakelock when Face Unlock + // is restarted. + mKeyguardScreenCallback.userActivity(0); + + int[] position; + position = new int[2]; + mFaceUnlockView.getLocationInWindow(position); + startUi(windowToken, position[0], position[1], mFaceUnlockView.getWidth(), + mFaceUnlockView.getHeight()); + } else { + Log.e(TAG, "windowToken is null in handleServiceConnected()"); + } + } + } + + /** + * Called when the onServiceDisconnected() callback is received. This should not happen during + * normal operation. It indicates an error has occurred. + */ + void handleServiceDisconnected() { + Log.e(TAG, "handleServiceDisconnected()"); + // TODO: this lock may no longer be needed now that everything is being called from a + // handler + synchronized (mServiceRunningLock) { + mService = null; + mServiceRunning = false; + } + mBoundToService = false; + mIsRunning = false; + } + + /** + * Stops the Face Unlock service and tells the device to grant access to the user. Shows the + * Face Unlock view to keep the backup lock covered while the device unlocks. + */ + void handleUnlock() { + if (DEBUG) Log.d(TAG, "handleUnlock()"); + removeDisplayMessages(); + if (mFaceUnlockView != null) { + mFaceUnlockView.setVisibility(View.VISIBLE); + } else { + Log.e(TAG, "mFaceUnlockView is null in handleUnlock()"); + } + stop(); + mKeyguardScreenCallback.reportSuccessfulUnlockAttempt(); + mKeyguardScreenCallback.dismiss(true); + } + + /** + * Stops the Face Unlock service and exposes the backup lock. + */ + void handleCancel() { + if (DEBUG) Log.d(TAG, "handleCancel()"); + if (mFaceUnlockView != null) { + mFaceUnlockView.setVisibility(View.INVISIBLE); + } else { + Log.e(TAG, "mFaceUnlockView is null in handleCancel()"); + } + stop(); + mKeyguardScreenCallback.userActivity(BACKUP_LOCK_TIMEOUT); + } + + /** + * Increments the number of failed Face Unlock attempts. + */ + void handleReportFailedAttempt() { + if (DEBUG) Log.d(TAG, "handleReportFailedAttempt()"); + mKeyguardScreenCallback.reportFailedUnlockAttempt(); + } + + /** + * Hides the Face Unlock view to expose the backup lock. Called when the Face Unlock service UI + * is started, indicating there is no need to continue displaying the underlying view because + * the service UI is now covering the backup lock. + */ + void handleExposeFallback() { + if (DEBUG) Log.d(TAG, "handleExposeFallback()"); + if (mFaceUnlockView != null) { + mFaceUnlockView.setVisibility(View.INVISIBLE); + } else { + Log.e(TAG, "mFaceUnlockView is null in handleExposeFallback()"); + } + } + + /** + * Pokes the wakelock to keep the screen alive and active for a specific amount of time. + */ + void handlePokeWakelock(int millis) { + mKeyguardScreenCallback.userActivity(millis); + } + + /** + * Removes show and hide messages from the message queue. Called to prevent delayed show/hide + * messages from undoing a new message. + */ + private void removeDisplayMessages() { + mHandler.removeMessages(MSG_SHOW_FACE_UNLOCK_VIEW); + mHandler.removeMessages(MSG_HIDE_FACE_UNLOCK_VIEW); + } + + /** + * Implements service connection methods. + */ + private ServiceConnection mConnection = new ServiceConnection() { + /** + * Called when the Face Unlock service connects after calling bind(). + */ + public void onServiceConnected(ComponentName className, IBinder iservice) { + Log.d(TAG, "Connected to Face Unlock service"); + mService = IFaceLockInterface.Stub.asInterface(iservice); + mHandler.sendEmptyMessage(MSG_SERVICE_CONNECTED); + } + + /** + * Called if the Face Unlock service unexpectedly disconnects. This indicates an error. + */ + public void onServiceDisconnected(ComponentName className) { + Log.e(TAG, "Unexpected disconnect from Face Unlock service"); + mHandler.sendEmptyMessage(MSG_SERVICE_DISCONNECTED); + } + }; + + /** + * Tells the Face Unlock service to start displaying its UI and start processing. + */ + private void startUi(IBinder windowToken, int x, int y, int w, int h) { + if (DEBUG) Log.d(TAG, "startUi()"); + synchronized (mServiceRunningLock) { + if (!mServiceRunning) { + Log.d(TAG, "Starting Face Unlock"); + try { + mService.startUi(windowToken, x, y, w, h, + mLockPatternUtils.isBiometricWeakLivelinessEnabled()); + } catch (RemoteException e) { + Log.e(TAG, "Caught exception starting Face Unlock: " + e.toString()); + return; + } + mServiceRunning = true; + } else { + Log.w(TAG, "startUi() attempted while running"); + } + } + } + + /** + * Tells the Face Unlock service to stop displaying its UI and stop processing. + */ + private void stopUi() { + if (DEBUG) Log.d(TAG, "stopUi()"); + // Note that attempting to stop Face Unlock when it's not running is not an issue. + // Face Unlock can return, which stops it and then we try to stop it when the + // screen is turned off. That's why we check. + synchronized (mServiceRunningLock) { + if (mServiceRunning) { + Log.d(TAG, "Stopping Face Unlock"); + try { + mService.stopUi(); + } catch (RemoteException e) { + Log.e(TAG, "Caught exception stopping Face Unlock: " + e.toString()); + } + mServiceRunning = false; + } else { + // This is usually not an error when this happens. Sometimes we will tell it to + // stop multiple times because it's called from both onWindowFocusChanged and + // onDetachedFromWindow. + if (DEBUG) Log.d(TAG, "stopUi() attempted while not running"); + } + } + } + + /** + * Implements the AIDL biometric unlock service callback interface. + */ + private final IFaceLockCallback mFaceUnlockCallback = new IFaceLockCallback.Stub() { + /** + * Called when Face Unlock wants to grant access to the user. + */ + public void unlock() { + if (DEBUG) Log.d(TAG, "unlock()"); + mHandler.sendEmptyMessage(MSG_UNLOCK); + } + + /** + * Called when Face Unlock wants to go to the backup. + */ + public void cancel() { + if (DEBUG) Log.d(TAG, "cancel()"); + mHandler.sendEmptyMessage(MSG_CANCEL); + } + + /** + * Called when Face Unlock wants to increment the number of failed attempts. + */ + public void reportFailedAttempt() { + if (DEBUG) Log.d(TAG, "reportFailedAttempt()"); + mHandler.sendEmptyMessage(MSG_REPORT_FAILED_ATTEMPT); + } + + /** + * Called when the Face Unlock service starts displaying the UI, indicating that the backup + * unlock can be exposed because the Face Unlock service is now covering the backup with its + * UI. + **/ + public void exposeFallback() { + if (DEBUG) Log.d(TAG, "exposeFallback()"); + mHandler.sendEmptyMessage(MSG_EXPOSE_FALLBACK); + } + + /** + * Called when Face Unlock wants to keep the screen alive and active for a specific amount + * of time. + */ + public void pokeWakelock(int millis) { + if (DEBUG) Log.d(TAG, "pokeWakelock() for " + millis + "ms"); + Message message = mHandler.obtainMessage(MSG_POKE_WAKELOCK, millis, -1); + mHandler.sendMessage(message); + } + + }; +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java new file mode 100644 index 0000000..1e73c5b --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.policy.impl.keyguard; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.os.Bundle; +import android.text.Editable; +import android.text.InputFilter; +import android.text.LoginFilter; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.R; + +import java.io.IOException; + +/** + * When the user forgets their password a bunch of times, we fall back on their + * account's login/password to unlock the phone (and reset their lock pattern). + */ +public class KeyguardAccountView extends LinearLayout implements KeyguardSecurityView, + View.OnClickListener, TextWatcher { + private static final int AWAKE_POKE_MILLIS = 30000; + private static final String LOCK_PATTERN_PACKAGE = "com.android.settings"; + private static final String LOCK_PATTERN_CLASS = LOCK_PATTERN_PACKAGE + ".ChooseLockGeneric"; + + private KeyguardSecurityCallback mCallback; + private LockPatternUtils mLockPatternUtils; + private EditText mLogin; + private EditText mPassword; + private Button mOk; + public boolean mEnableFallback; + private KeyguardNavigationManager mNavigationManager; + + /** + * Shown while making asynchronous check of password. + */ + private ProgressDialog mCheckingDialog; + + public KeyguardAccountView(Context context) { + this(context, null, 0); + } + + public KeyguardAccountView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public KeyguardAccountView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mLockPatternUtils = new LockPatternUtils(getContext()); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mNavigationManager = new KeyguardNavigationManager(this); + + mLogin = (EditText) findViewById(R.id.login); + mLogin.setFilters(new InputFilter[] { new LoginFilter.UsernameFilterGeneric() } ); + mLogin.addTextChangedListener(this); + + mPassword = (EditText) findViewById(R.id.password); + mPassword.addTextChangedListener(this); + + mOk = (Button) findViewById(R.id.ok); + mOk.setOnClickListener(this); + reset(); + } + + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mCallback = callback; + } + + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + public KeyguardSecurityCallback getCallback() { + return mCallback; + } + + + public void afterTextChanged(Editable s) { + } + + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (mCallback != null) { + mCallback.userActivity(AWAKE_POKE_MILLIS); + } + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, + Rect previouslyFocusedRect) { + // send focus to the login field + return mLogin.requestFocus(direction, previouslyFocusedRect); + } + + public boolean needsInput() { + return true; + } + + public void reset() { + // start fresh + mLogin.setText(""); + mPassword.setText(""); + mLogin.requestFocus(); + mNavigationManager.setMessage(mLockPatternUtils.isPermanentlyLocked() ? + R.string.kg_login_too_many_attempts : R.string.kg_login_instructions); + } + + /** {@inheritDoc} */ + public void cleanUp() { + if (mCheckingDialog != null) { + mCheckingDialog.hide(); + } + mCallback = null; + mLockPatternUtils = null; + } + + public void onClick(View v) { + mCallback.userActivity(0); + if (v == mOk) { + asyncCheckPassword(); + } + } + + private void postOnCheckPasswordResult(final boolean success) { + // ensure this runs on UI thread + mLogin.post(new Runnable() { + public void run() { + if (success) { + // clear out forgotten password + mLockPatternUtils.setPermanentlyLocked(false); + mLockPatternUtils.setLockPatternEnabled(false); + mLockPatternUtils.saveLockPattern(null); + + // launch the 'choose lock pattern' activity so + // the user can pick a new one if they want to + Intent intent = new Intent(); + intent.setClassName(LOCK_PATTERN_PACKAGE, LOCK_PATTERN_CLASS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + mCallback.reportSuccessfulUnlockAttempt(); + + // dismiss keyguard + mCallback.dismiss(true); + } else { + mNavigationManager.setMessage(R.string.kg_login_invalid_input); + mPassword.setText(""); + mCallback.reportFailedUnlockAttempt(); + } + } + }); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + if (mLockPatternUtils.isPermanentlyLocked()) { + mCallback.dismiss(false); + } else { + // TODO: mCallback.forgotPattern(false); + } + return true; + } + return super.dispatchKeyEvent(event); + } + + /** + * Given the string the user entered in the 'username' field, find + * the stored account that they probably intended. Prefer, in order: + * + * - an exact match for what was typed, or + * - a case-insensitive match for what was typed, or + * - if they didn't include a domain, an exact match of the username, or + * - if they didn't include a domain, a case-insensitive + * match of the username. + * + * If there is a tie for the best match, choose neither -- + * the user needs to be more specific. + * + * @return an account name from the database, or null if we can't + * find a single best match. + */ + private Account findIntendedAccount(String username) { + Account[] accounts = AccountManager.get(mContext).getAccountsByType("com.google"); + + // Try to figure out which account they meant if they + // typed only the username (and not the domain), or got + // the case wrong. + + Account bestAccount = null; + int bestScore = 0; + for (Account a: accounts) { + int score = 0; + if (username.equals(a.name)) { + score = 4; + } else if (username.equalsIgnoreCase(a.name)) { + score = 3; + } else if (username.indexOf('@') < 0) { + int i = a.name.indexOf('@'); + if (i >= 0) { + String aUsername = a.name.substring(0, i); + if (username.equals(aUsername)) { + score = 2; + } else if (username.equalsIgnoreCase(aUsername)) { + score = 1; + } + } + } + if (score > bestScore) { + bestAccount = a; + bestScore = score; + } else if (score == bestScore) { + bestAccount = null; + } + } + return bestAccount; + } + + private void asyncCheckPassword() { + mCallback.userActivity(AWAKE_POKE_MILLIS); + final String login = mLogin.getText().toString(); + final String password = mPassword.getText().toString(); + Account account = findIntendedAccount(login); + if (account == null) { + postOnCheckPasswordResult(false); + return; + } + getProgressDialog().show(); + Bundle options = new Bundle(); + options.putString(AccountManager.KEY_PASSWORD, password); + AccountManager.get(mContext).confirmCredentials(account, options, null /* activity */, + new AccountManagerCallback<Bundle>() { + public void run(AccountManagerFuture<Bundle> future) { + try { + mCallback.userActivity(AWAKE_POKE_MILLIS); + final Bundle result = future.getResult(); + final boolean verified = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT); + postOnCheckPasswordResult(verified); + } catch (OperationCanceledException e) { + postOnCheckPasswordResult(false); + } catch (IOException e) { + postOnCheckPasswordResult(false); + } catch (AuthenticatorException e) { + postOnCheckPasswordResult(false); + } finally { + mLogin.post(new Runnable() { + public void run() { + getProgressDialog().hide(); + } + }); + } + } + }, null /* handler */); + } + + private Dialog getProgressDialog() { + if (mCheckingDialog == null) { + mCheckingDialog = new ProgressDialog(mContext); + mCheckingDialog.setMessage( + mContext.getString(R.string.kg_login_checking_password)); + mCheckingDialog.setIndeterminate(true); + mCheckingDialog.setCancelable(false); + mCheckingDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + return mCheckingDialog; + } + + @Override + public void onPause() { + + } + + @Override + public void onResume() { + reset(); + } + +} + diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java new file mode 100644 index 0000000..843151b --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.policy.impl.keyguard; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +import com.android.internal.widget.LockPatternUtils; + +public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecurityView { + + // Long enough to stay visible while dialer comes up + // Short enough to not be visible if the user goes back immediately + private static final int BIOMETRIC_AREA_EMERGENCY_DIALER_TIMEOUT = 1000; + private KeyguardSecurityCallback mKeyguardSecurityCallback; + private LockPatternUtils mLockPatternUtils; + + public KeyguardFaceUnlockView(Context context) { + this(context, null); + } + + public KeyguardFaceUnlockView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mKeyguardSecurityCallback = callback; + } + + @Override + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + @Override + public void reset() { + + } + + @Override + public void onPause() { + + } + + @Override + public void onResume() { + + } + + @Override + public boolean needsInput() { + return false; + } + + @Override + public KeyguardSecurityCallback getCallback() { + return mKeyguardSecurityCallback; + } + + // TODO + // public void onRefreshBatteryInfo(BatteryStatus status) { + // // When someone plugs in or unplugs the device, we hide the biometric sensor area and + // // suppress its startup for the next onScreenTurnedOn(). Since plugging/unplugging + // // causes the screen to turn on, the biometric unlock would start if it wasn't + // // suppressed. + // // + // // However, if the biometric unlock is already running, we do not want to interrupt it. + // final boolean pluggedIn = status.isPluggedIn(); + // if (mBiometricUnlock != null && mPluggedIn != pluggedIn + // && !mBiometricUnlock.isRunning()) { + // mBiometricUnlock.stop(); + // mBiometricUnlock.hide(); + // mSuppressBiometricUnlock = true; + // } + // mPluggedIn = pluggedIn; + // } + + // We need to stop the biometric unlock when a phone call comes in + // @Override + // public void onPhoneStateChanged(int phoneState) { + // if (DEBUG) Log.d(TAG, "phone state: " + phoneState); + // if (phoneState == TelephonyManager.CALL_STATE_RINGING) { + // mSuppressBiometricUnlock = true; + // mBiometricUnlock.stop(); + // mBiometricUnlock.hide(); + // } + // } + + // @Override + // public void onUserSwitched(int userId) { + // if (mBiometricUnlock != null) { + // mBiometricUnlock.stop(); + // } + // mLockPatternUtils.setCurrentUser(userId); + // updateScreen(getInitialMode(), true); + // } + + // /** + // * This returns false if there is any condition that indicates that the biometric unlock should + // * not be used before the next time the unlock screen is recreated. In other words, if this + // * returns false there is no need to even construct the biometric unlock. + // */ + // private boolean useBiometricUnlock() { + // final ShowingMode unlockMode = getUnlockMode(); + // final boolean backupIsTimedOut = (mUpdateMonitor.getFailedAttempts() >= + // LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT); + // return (mLockPatternUtils.usingBiometricWeak() && + // mLockPatternUtils.isBiometricWeakInstalled() && + // !mUpdateMonitor.getMaxBiometricUnlockAttemptsReached() && + // !backupIsTimedOut && + // (unlockMode == ShowingMode.Pattern || unlockMode == ShowingMode.Password)); + // } + + // private void initializeBiometricUnlockView(View view) { + // boolean restartBiometricUnlock = false; + // + // if (mBiometricUnlock != null) { + // restartBiometricUnlock = mBiometricUnlock.stop(); + // } + // + // // Prevents biometric unlock from coming up immediately after a phone call or if there + // // is a dialog on top of lockscreen. It is only updated if the screen is off because if the + // // screen is on it's either because of an orientation change, or when it first boots. + // // In both those cases, we don't want to override the current value of + // // mSuppressBiometricUnlock and instead want to use the previous value. + // if (!mScreenOn) { + // mSuppressBiometricUnlock = + // mUpdateMonitor.getPhoneState() != TelephonyManager.CALL_STATE_IDLE + // || mHasDialog; + // } + // + // // If the biometric unlock is not being used, we don't bother constructing it. Then we can + // // simply check if it is null when deciding whether we should make calls to it. + // mBiometricUnlock = null; + // if (useBiometricUnlock()) { + // // TODO: make faceLockAreaView a more general biometricUnlockView + // // We will need to add our Face Unlock specific child views programmatically in + // // initializeView rather than having them in the XML files. + // View biometricUnlockView = view.findViewById( + // com.android.internal.R.id.faceLockAreaView); + // if (biometricUnlockView != null) { + // mBiometricUnlock = new FaceUnlock(mContext, mUpdateMonitor, mLockPatternUtils, + // mKeyguardScreenCallback); + // mBiometricUnlock.initializeView(biometricUnlockView); + // + // // If this is being called because the screen turned off, we want to cover the + // // backup lock so it is covered when the screen turns back on. + // if (!mScreenOn) mBiometricUnlock.show(0); + // } else { + // Log.w(TAG, "Couldn't find biometric unlock view"); + // } + // } + // + // if (mBiometricUnlock != null && restartBiometricUnlock) { + // maybeStartBiometricUnlock(); + // } + // } + + // /** + // * Starts the biometric unlock if it should be started based on a number of factors including + // * the mSuppressBiometricUnlock flag. If it should not be started, it hides the biometric + // * unlock area. + // */ + // private void maybeStartBiometricUnlock() { + // if (mBiometricUnlock != null) { + // final boolean backupIsTimedOut = (mUpdateMonitor.getFailedAttempts() >= + // LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT); + // if (!mSuppressBiometricUnlock + // && mUpdateMonitor.getPhoneState() == TelephonyManager.CALL_STATE_IDLE + // && !mUpdateMonitor.getMaxBiometricUnlockAttemptsReached() + // && !backupIsTimedOut) { + // mBiometricUnlock.start(); + // } else { + // mBiometricUnlock.hide(); + // } + // } + //} + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java new file mode 100644 index 0000000..d74a5e7 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java @@ -0,0 +1,488 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl.keyguard; + +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.appwidget.AppWidgetHost; +import android.appwidget.AppWidgetHostView; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.Context; +import android.content.Intent; +import android.content.IntentSender; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.telephony.TelephonyManager; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; +import android.widget.Button; +import android.widget.ViewFlipper; +import android.widget.RemoteViews.OnClickHandler; + +import com.android.internal.policy.impl.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.R; + +import java.io.File; +import java.util.ArrayList; + +public class KeyguardHostView extends KeyguardViewBase { + // Use this to debug all of keyguard + public static boolean DEBUG; + + static final int APPWIDGET_HOST_ID = 0x4B455947; + private static final String KEYGUARD_WIDGET_PREFS = "keyguard_widget_prefs"; + + // time after launching EmergencyDialer before the screen goes blank. + private static final int EMERGENCY_CALL_TIMEOUT = 10000; + + // intent action for launching emergency dialer activity. + static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL"; + + private static final String TAG = "KeyguardViewHost"; + + private static final int SECURITY_SELECTOR_ID = R.id.keyguard_selector_view; + private static final int SECURITY_PATTERN_ID = R.id.keyguard_pattern_view; + private static final int SECURITY_PASSWORD_ID = R.id.keyguard_password_view; + private static final int SECURITY_BIOMETRIC_ID = R.id.keyguard_face_unlock_view; + private static final int SECURITY_SIM_PIN_ID = R.id.keyguard_sim_pin_view; + private static final int SECURITY_SIM_PUK_ID = R.id.keyguard_sim_puk_view; + private static final int SECURITY_ACCOUNT_ID = R.id.keyguard_account_view; + + private AppWidgetHost mAppWidgetHost; + private KeyguardWidgetPager mAppWidgetContainer; + private ViewFlipper mViewFlipper; + private Button mEmergencyDialerButton; + private boolean mEnableMenuKey; + private boolean mScreenOn; + private boolean mIsVerifyUnlockOnly; + private int mCurrentSecurityId = SECURITY_SELECTOR_ID; + + // KeyguardSecurityViews + final private int [] mViewIds = { + SECURITY_SELECTOR_ID, + SECURITY_PATTERN_ID, + SECURITY_PASSWORD_ID, + SECURITY_BIOMETRIC_ID, + SECURITY_SIM_PIN_ID, + SECURITY_SIM_PUK_ID, + SECURITY_ACCOUNT_ID, + }; + + private ArrayList<View> mViews = new ArrayList<View>(mViewIds.length); + + protected Runnable mLaunchRunnable; + + protected int mFailedAttempts; + private LockPatternUtils mLockPatternUtils; + + private KeyguardSecurityModel mSecurityModel; + + public KeyguardHostView(Context context) { + this(context, null); + } + + public KeyguardHostView(Context context, AttributeSet attrs) { + super(context, attrs); + mAppWidgetHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID, mOnClickHandler); + mSecurityModel = new KeyguardSecurityModel(mContext); + + // The following enables the MENU key to work for testing automation + mEnableMenuKey = shouldEnableMenuKey(); + setFocusable(true); + setFocusableInTouchMode(true); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + mCallback.keyguardDoneDrawing(); + } + + @Override + protected void onFinishInflate() { + mAppWidgetContainer = (KeyguardWidgetPager) findViewById(R.id.app_widget_container); + mAppWidgetContainer.setVisibility(VISIBLE); + + // View Flipper + mViewFlipper = (ViewFlipper) findViewById(R.id.view_flipper); + mViewFlipper.setInAnimation(AnimationUtils.loadAnimation(mContext, + R.anim.keyguard_security_animate_in)); + mViewFlipper.setOutAnimation(AnimationUtils.loadAnimation(mContext, + R.anim.keyguard_security_animate_out)); + + // Initialize all security views + for (int i = 0; i < mViewIds.length; i++) { + View view = findViewById(mViewIds[i]); + mViews.add(view); + if (view != null) { + ((KeyguardSecurityView) view).setKeyguardCallback(mCallback); + } else { + Log.v("*********", "Can't find view id " + mViewIds[i]); + } + } + + // Enable emergency dialer button + mEmergencyDialerButton = (Button) findViewById(R.id.emergency_call_button); + mEmergencyDialerButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + takeEmergencyCallAction(); + } + }); + } + + void setLockPatternUtils(LockPatternUtils utils) { + mSecurityModel.setLockPatternUtils(utils); + mLockPatternUtils = utils; + for (int i = 0; i < mViews.size(); i++) { + KeyguardSecurityView ksv = (KeyguardSecurityView) mViews.get(i); + if (ksv != null) { + ksv.setLockPatternUtils(utils); + } else { + Log.w(TAG, "**** ksv was null at " + i); + } + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mAppWidgetHost.startListening(); + populateWidgets(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mAppWidgetHost.stopListening(); + } + + AppWidgetHost getAppWidgetHost() { + return mAppWidgetHost; + } + + void addWidget(AppWidgetHostView view) { + mAppWidgetContainer.addWidget(view); + } + + private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() { + + public void userActivity(long timeout) { + mViewMediatorCallback.pokeWakelock(timeout); + } + + public void dismiss(boolean authenticated) { + showNextSecurityScreenOrFinish(authenticated); + } + + public boolean isVerifyUnlockOnly() { + // TODO + return false; + } + + public void reportSuccessfulUnlockAttempt() { + KeyguardUpdateMonitor.getInstance(mContext).clearFailedAttempts(); + } + + public void reportFailedUnlockAttempt() { + // TODO: handle biometric attempt differently. + KeyguardUpdateMonitor.getInstance(mContext).reportFailedAttempt(); + } + + public int getFailedAttempts() { + return KeyguardUpdateMonitor.getInstance(mContext).getFailedAttempts(); + } + + public void showBackupUnlock() { + // TODO + } + + public void keyguardDoneDrawing() { + mViewMediatorCallback.keyguardDoneDrawing(); + } + + @Override + public void setOnDismissRunnable(Runnable runnable) { + KeyguardHostView.this.setOnDismissRunnable(runnable); + } + + }; + + public void takeEmergencyCallAction() { + mCallback.userActivity(EMERGENCY_CALL_TIMEOUT); + if (TelephonyManager.getDefault().getCallState() + == TelephonyManager.CALL_STATE_OFFHOOK) { + mLockPatternUtils.resumeCall(); + } else { + Intent intent = new Intent(ACTION_EMERGENCY_DIAL); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + getContext().startActivity(intent); + } + } + + protected void showNextSecurityScreenOrFinish(boolean authenticated) { + boolean finish = false; + if (SECURITY_SELECTOR_ID == mCurrentSecurityId) { + int realSecurityId = getSecurityViewIdForMode(mSecurityModel.getSecurityMode()); + if (realSecurityId == mCurrentSecurityId) { + finish = true; // no security required + } else { + showSecurityScreen(realSecurityId); // switch to the "real" security view + } + } else if (authenticated) { + if ((mCurrentSecurityId == SECURITY_PATTERN_ID + || mCurrentSecurityId == SECURITY_PASSWORD_ID + || mCurrentSecurityId == SECURITY_ACCOUNT_ID)) { + finish = true; + } + } else { + // Not authenticated but we were asked to dismiss so go back to selector screen. + showSecurityScreen(SECURITY_SELECTOR_ID); + } + if (finish) { + // If there's a pending runnable because the user interacted with a widget + // and we're leaving keyguard, then run it. + if (mLaunchRunnable != null) { + mLaunchRunnable.run(); + mViewFlipper.setDisplayedChild(0); + mLaunchRunnable = null; + } + mViewMediatorCallback.keyguardDone(true); + } + } + + private OnClickHandler mOnClickHandler = new OnClickHandler() { + @Override + public boolean onClickHandler(final View view, + final android.app.PendingIntent pendingIntent, + final Intent fillInIntent) { + if (pendingIntent.isActivity()) { + setOnDismissRunnable(new Runnable() { + public void run() { + try { + // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? + Context context = view.getContext(); + ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(view, + 0, 0, + view.getMeasuredWidth(), view.getMeasuredHeight()); + context.startIntentSender( + pendingIntent.getIntentSender(), fillInIntent, + Intent.FLAG_ACTIVITY_NEW_TASK, + Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle()); + } catch (IntentSender.SendIntentException e) { + android.util.Log.e(TAG, "Cannot send pending intent: ", e); + } catch (Exception e) { + android.util.Log.e(TAG, "Cannot send pending intent due to " + + "unknown exception: ", e); + } + } + }); + + mCallback.dismiss(false); + return true; + } else { + return super.onClickHandler(view, pendingIntent, fillInIntent); + } + }; + }; + + @Override + public void reset() { + requestFocus(); + } + + /** + * Sets a runnable to run when keyguard is dismissed + * @param runnable + */ + protected void setOnDismissRunnable(Runnable runnable) { + mLaunchRunnable = runnable; + } + + private KeyguardSecurityView getSecurityView(int securitySelectorId) { + final int children = mViewFlipper.getChildCount(); + for (int child = 0; child < children; child++) { + if (mViewFlipper.getChildAt(child).getId() == securitySelectorId) { + return ((KeyguardSecurityView)mViewFlipper.getChildAt(child)); + } + } + return null; + } + + private void showSecurityScreen(int securityViewId) { + + if (securityViewId == mCurrentSecurityId) return; + + KeyguardSecurityView oldView = getSecurityView(mCurrentSecurityId); + KeyguardSecurityView newView = getSecurityView(securityViewId); + + // Emulate Activity life cycle + oldView.onPause(); + newView.onResume(); + + mViewMediatorCallback.setNeedsInput(newView.needsInput()); + mCurrentSecurityId = securityViewId; + + // Find and show this child. + final int childCount = mViewFlipper.getChildCount(); + for (int i = 0; i < childCount; i++) { + if (securityViewId == mViewFlipper.getChildAt(i).getId()) { + mViewFlipper.setDisplayedChild(i); + break; + } + } + } + + @Override + public void onScreenTurnedOn() { + if (DEBUG) Log.d(TAG, "screen on"); + mScreenOn = true; + showSecurityScreen(mCurrentSecurityId); + } + + @Override + public void onScreenTurnedOff() { + if (DEBUG) Log.d(TAG, "screen off"); + mScreenOn = false; + showSecurityScreen(SECURITY_SELECTOR_ID); + } + + @Override + public void show() { + onScreenTurnedOn(); + } + + private boolean isSecure() { + SecurityMode mode = mSecurityModel.getSecurityMode(); + switch (mode) { + case Pattern: + return mLockPatternUtils.isLockPatternEnabled(); + case Password: + return mLockPatternUtils.isLockPasswordEnabled(); + case SimPin: + case SimPuk: + case Account: + return true; + case None: + return false; + default: + throw new IllegalStateException("Unknown security mode " + mode); + } + } + + @Override + public void wakeWhenReadyTq(int keyCode) { + if (DEBUG) Log.d(TAG, "onWakeKey"); + if (keyCode == KeyEvent.KEYCODE_MENU && isSecure()) { + if (DEBUG) Log.d(TAG, "switching screens to unlock screen because wake key was MENU"); + showSecurityScreen(SECURITY_SELECTOR_ID); + mViewMediatorCallback.pokeWakelock(); + } else { + if (DEBUG) Log.d(TAG, "poking wake lock immediately"); + mViewMediatorCallback.pokeWakelock(); + } + } + + @Override + public void verifyUnlock() { + SecurityMode securityMode = mSecurityModel.getSecurityMode(); + if (securityMode == KeyguardSecurityModel.SecurityMode.None) { + mViewMediatorCallback.keyguardDone(true); + } else if (securityMode != KeyguardSecurityModel.SecurityMode.Pattern + && securityMode != KeyguardSecurityModel.SecurityMode.Password) { + // can only verify unlock when in pattern/password mode + mViewMediatorCallback.keyguardDone(false); + } else { + // otherwise, go to the unlock screen, see if they can verify it + mIsVerifyUnlockOnly = true; + showSecurityScreen(getSecurityViewIdForMode(securityMode)); + } + } + + private int getSecurityViewIdForMode(SecurityMode securityMode) { + switch (securityMode) { + case None: return SECURITY_SELECTOR_ID; + case Pattern: return SECURITY_PATTERN_ID; + case Password: return SECURITY_PASSWORD_ID; + case Biometric: return SECURITY_BIOMETRIC_ID; + case Account: return SECURITY_ACCOUNT_ID; + case SimPin: return SECURITY_SIM_PIN_ID; + case SimPuk: return SECURITY_SIM_PUK_ID; + } + return 0; + } + + private void addWidget(int appId) { + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); + AppWidgetProviderInfo appWidgetInfo = appWidgetManager.getAppWidgetInfo(appId); + AppWidgetHostView view = getAppWidgetHost().createView(mContext, appId, appWidgetInfo); + addWidget(view); + } + + private void populateWidgets() { + SharedPreferences prefs = mContext.getSharedPreferences( + KEYGUARD_WIDGET_PREFS, Context.MODE_PRIVATE); + for (String key : prefs.getAll().keySet()) { + int appId = prefs.getInt(key, -1); + if (appId != -1) { + Log.w(TAG, "populate: adding " + key); + addWidget(appId); + } else { + Log.w(TAG, "populate: can't find " + key); + } + } + } + + @Override + public void cleanUp() { + + } + + /** + * In general, we enable unlocking the insecure keyguard with the menu key. However, there are + * some cases where we wish to disable it, notably when the menu button placement or technology + * is prone to false positives. + * + * @return true if the menu key should be enabled + */ + private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key"; + private boolean shouldEnableMenuKey() { + final Resources res = getResources(); + final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen); + final boolean isTestHarness = ActivityManager.isRunningInTestHarness(); + final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists(); + return !configDisabled || isTestHarness || fileOverride; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_MENU && mEnableMenuKey) { + showNextSecurityScreenOrFinish(false); + return true; + } else { + return super.onKeyDown(keyCode, event); + } + } + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java new file mode 100644 index 0000000..d3feced --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl.keyguard; + +import android.view.View; +import android.widget.TextView; + +import com.android.internal.R; + +public class KeyguardNavigationManager { + + private TextView mMessageArea; + private KeyguardSecurityView mKeyguardSecurityView; + + public KeyguardNavigationManager(KeyguardSecurityView view) { + mKeyguardSecurityView = view; + mMessageArea = (TextView) ((View) view).findViewById(R.id.message_area); + mMessageArea.setSelected(true); // Make marquee work + mMessageArea.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + mKeyguardSecurityView.getCallback().dismiss(false); + } + }); + } + + public void setMessage(CharSequence msg) { + mMessageArea.setText(msg); + } + + public void setMessage(int resId) { + if (resId != 0) { + mMessageArea.setText(resId); + } else { + mMessageArea.setText(""); + } + } + + public void setMessage(int resId, Object... formatArgs) { + if (resId != 0) { + mMessageArea.setText(mMessageArea.getContext().getString(resId, formatArgs)); + } else { + mMessageArea.setText(""); + } + } +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java new file mode 100644 index 0000000..6938561 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl.keyguard; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +import com.android.internal.R; +import com.android.internal.widget.LockPatternUtils; +import java.util.List; + +import android.app.admin.DevicePolicyManager; +import android.content.res.Configuration; +import android.graphics.Rect; + +import com.android.internal.widget.PasswordEntryKeyboardView; + +import android.os.CountDownTimer; +import android.os.SystemClock; +import android.security.KeyStore; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.text.method.DigitsKeyListener; +import android.text.method.TextKeyListener; +import android.view.KeyEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + +import com.android.internal.widget.PasswordEntryKeyboardHelper; +/** + * Displays a dialer-like interface or alphanumeric (latin-1) key entry for the user to enter + * an unlock password + */ + +public class KeyguardPasswordView extends LinearLayout + implements KeyguardSecurityView, OnEditorActionListener { + private KeyguardSecurityCallback mCallback; + private EditText mPasswordEntry; + private LockPatternUtils mLockPatternUtils; + private PasswordEntryKeyboardView mKeyboardView; + private PasswordEntryKeyboardHelper mKeyboardHelper; + private boolean mIsAlpha; + private KeyguardNavigationManager mNavigationManager; + + // To avoid accidental lockout due to events while the device in in the pocket, ignore + // any passwords with length less than or equal to this length. + private static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3; + + public KeyguardPasswordView(Context context) { + super(context); + } + + public KeyguardPasswordView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mCallback = callback; + } + + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + public void reset() { + // start fresh + mPasswordEntry.setText(""); + mPasswordEntry.requestFocus(); + + // if the user is currently locked out, enforce it. + long deadline = mLockPatternUtils.getLockoutAttemptDeadline(); + if (deadline != 0) { + handleAttemptLockout(deadline); + } else { + mNavigationManager.setMessage( + mIsAlpha ? R.string.kg_password_instructions : R.string.kg_pin_instructions); + } + } + + @Override + protected void onFinishInflate() { + mLockPatternUtils = new LockPatternUtils(mContext); // TODO: use common one + + mNavigationManager = new KeyguardNavigationManager(this); + + final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(); + mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == quality + || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == quality + || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == quality; + + mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard); + mPasswordEntry = (EditText) findViewById(R.id.passwordEntry); + mPasswordEntry.setOnEditorActionListener(this); + + mKeyboardHelper = new PasswordEntryKeyboardHelper(mContext, mKeyboardView, this, false); + mKeyboardHelper.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled()); + + boolean imeOrDeleteButtonVisible = false; + if (mIsAlpha) { + // We always use the system IME for alpha keyboard, so hide lockscreen's soft keyboard + mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA); + mKeyboardView.setVisibility(View.GONE); + } else { + // Use lockscreen's numeric keyboard if the physical keyboard isn't showing + mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC); + mKeyboardView.setVisibility(getResources().getConfiguration().hardKeyboardHidden + == Configuration.HARDKEYBOARDHIDDEN_NO ? View.INVISIBLE : View.VISIBLE); + + // The delete button is of the PIN keyboard itself in some (e.g. tablet) layouts, + // not a separate view + View pinDelete = findViewById(R.id.delete_button); + if (pinDelete != null) { + pinDelete.setVisibility(View.VISIBLE); + imeOrDeleteButtonVisible = true; + pinDelete.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mKeyboardHelper.handleBackspace(); + } + }); + } + } + + mPasswordEntry.requestFocus(); + + // This allows keyboards with overlapping qwerty/numeric keys to choose just numeric keys. + if (mIsAlpha) { + mPasswordEntry.setKeyListener(TextKeyListener.getInstance()); + mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_VARIATION_PASSWORD); + } else { + mPasswordEntry.setKeyListener(DigitsKeyListener.getInstance()); + mPasswordEntry.setInputType(InputType.TYPE_CLASS_NUMBER + | InputType.TYPE_NUMBER_VARIATION_PASSWORD); + } + + // Poke the wakelock any time the text is selected or modified + mPasswordEntry.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mCallback.userActivity(0); // TODO: customize timeout for text? + } + }); + + mPasswordEntry.addTextChangedListener(new TextWatcher() { + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + public void afterTextChanged(Editable s) { + mCallback.userActivity(0); + } + }); + + // If there's more than one IME, enable the IME switcher button + View switchImeButton = findViewById(R.id.switch_ime_button); + final InputMethodManager imm = (InputMethodManager) getContext().getSystemService( + Context.INPUT_METHOD_SERVICE); + if (mIsAlpha && switchImeButton != null && hasMultipleEnabledIMEsOrSubtypes(imm, false)) { + switchImeButton.setVisibility(View.VISIBLE); + imeOrDeleteButtonVisible = true; + switchImeButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mCallback.userActivity(0); // Leave the screen on a bit longer + imm.showInputMethodPicker(); + } + }); + } + + // If no icon is visible, reset the left margin on the password field so the text is + // still centered. + if (!imeOrDeleteButtonVisible) { + android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams(); + if (params instanceof MarginLayoutParams) { + ((MarginLayoutParams)params).leftMargin = 0; + mPasswordEntry.setLayoutParams(params); + } + } + } + + /** + * Method adapted from com.android.inputmethod.latin.Utils + * + * @param imm The input method manager + * @param shouldIncludeAuxiliarySubtypes + * @return true if we have multiple IMEs to choose from + */ + private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, + final boolean shouldIncludeAuxiliarySubtypes) { + final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList(); + + // Number of the filtered IMEs + int filteredImisCount = 0; + + for (InputMethodInfo imi : enabledImis) { + // We can return true immediately after we find two or more filtered IMEs. + if (filteredImisCount > 1) return true; + final List<InputMethodSubtype> subtypes = + imm.getEnabledInputMethodSubtypeList(imi, true); + // IMEs that have no subtypes should be counted. + if (subtypes.isEmpty()) { + ++filteredImisCount; + continue; + } + + int auxCount = 0; + for (InputMethodSubtype subtype : subtypes) { + if (subtype.isAuxiliary()) { + ++auxCount; + } + } + final int nonAuxCount = subtypes.size() - auxCount; + + // IMEs that have one or more non-auxiliary subtypes should be counted. + // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary + // subtypes should be counted as well. + if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { + ++filteredImisCount; + continue; + } + } + + return filteredImisCount > 1 + // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled + // input method subtype (The current IME should be LatinIME.) + || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1; + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + // send focus to the password field + return mPasswordEntry.requestFocus(direction, previouslyFocusedRect); + } + + private void verifyPasswordAndUnlock() { + String entry = mPasswordEntry.getText().toString(); + boolean wrongPassword = true; + if (mLockPatternUtils.checkPassword(entry)) { + mCallback.reportSuccessfulUnlockAttempt(); + KeyStore.getInstance().password(entry); + mCallback.dismiss(true); + wrongPassword = false; + } else if (entry.length() > MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT ) { + // to avoid accidental lockout, only count attempts that are long enough to be a + // real password. This may require some tweaking. + mCallback.reportFailedUnlockAttempt(); + if (0 == (mCallback.getFailedAttempts() + % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { + long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); + handleAttemptLockout(deadline); + } + } + mNavigationManager.setMessage(wrongPassword ? + (mIsAlpha ? R.string.kg_wrong_password : R.string.kg_wrong_pin) : 0); + mPasswordEntry.setText(""); + } + + // Prevent user from using the PIN/Password entry until scheduled deadline. + private void handleAttemptLockout(long elapsedRealtimeDeadline) { + mPasswordEntry.setEnabled(false); + mKeyboardView.setEnabled(false); + long elapsedRealtime = SystemClock.elapsedRealtime(); + new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) { + + @Override + public void onTick(long millisUntilFinished) { + int secondsRemaining = (int) (millisUntilFinished / 1000); + mNavigationManager.setMessage( + R.string.kg_too_many_failed_attempts_countdown, secondsRemaining); + } + + @Override + public void onFinish() { + mPasswordEntry.setEnabled(true); + mKeyboardView.setEnabled(true); + } + }.start(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + mCallback.userActivity(0); + return false; + } + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + // Check if this was the result of hitting the enter key + if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT) { + verifyPasswordAndUnlock(); + return true; + } + return false; + } + + @Override + public boolean needsInput() { + return mIsAlpha; + } + + @Override + public void onPause() { + + } + + @Override + public void onResume() { + reset(); + } + + @Override + public KeyguardSecurityCallback getCallback() { + return mCallback; + } + +} + diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java new file mode 100644 index 0000000..a95cfcb --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.policy.impl.keyguard; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.content.Context; +import android.os.Bundle; +import android.os.CountDownTimer; +import android.os.SystemClock; +import android.security.KeyStore; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockPatternView; +import com.android.internal.R; + +import java.io.IOException; +import java.util.List; + +public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView { + + private static final String TAG = "SecurityPatternView"; + private static final boolean DEBUG = false; + + // how long before we clear the wrong pattern + private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000; + + // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK + private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000; + + // how long we stay awake after the user hits the first dot. + private static final int UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS = 2000; + + // how many cells the user has to cross before we poke the wakelock + private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; + + private int mFailedPatternAttemptsSinceLastTimeout = 0; + private int mTotalFailedPatternAttempts = 0; + private CountDownTimer mCountdownTimer = null; + private LockPatternUtils mLockPatternUtils; + private LockPatternView mLockPatternView; + private Button mForgotPatternButton; + private KeyguardSecurityCallback mCallback; + private boolean mEnableFallback; + private KeyguardNavigationManager mNavigationManager; + + /** + * Keeps track of the last time we poked the wake lock during dispatching of the touch event. + * Initialized to something guaranteed to make us poke the wakelock when the user starts + * drawing the pattern. + * @see #dispatchTouchEvent(android.view.MotionEvent) + */ + private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS; + + /** + * Useful for clearing out the wrong pattern after a delay + */ + private Runnable mCancelPatternRunnable = new Runnable() { + public void run() { + mLockPatternView.clearPattern(); + } + }; + + enum FooterMode { + Normal, + ForgotLockPattern, + VerifyUnlocked + } + + public KeyguardPatternView(Context context) { + this(context, null); + } + + public KeyguardPatternView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mCallback = callback; + } + + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mNavigationManager = new KeyguardNavigationManager(this); + mLockPatternUtils = mLockPatternUtils == null + ? new LockPatternUtils(mContext) : mLockPatternUtils; + + mLockPatternView = (LockPatternView) findViewById(R.id.lockPatternView); + mLockPatternView.setSaveEnabled(false); + mLockPatternView.setFocusable(false); + mLockPatternView.setOnPatternListener(new UnlockPatternListener()); + + // stealth mode will be the same for the life of this screen + mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled()); + + // vibrate mode will be the same for the life of this screen + mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); + + mForgotPatternButton = (Button) findViewById(R.id.forgot_password_button); + mForgotPatternButton.setText(R.string.kg_forgot_pattern_button_text); + mForgotPatternButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mCallback.showBackupUnlock(); + } + }); + + setFocusableInTouchMode(true); + + maybeEnableFallback(mContext); + } + + private void updateFooter(FooterMode mode) { + switch (mode) { + case Normal: + if (DEBUG) Log.d(TAG, "mode normal"); + mForgotPatternButton.setVisibility(View.GONE); + break; + case ForgotLockPattern: + if (DEBUG) Log.d(TAG, "mode ForgotLockPattern"); + mForgotPatternButton.setVisibility(View.VISIBLE); + break; + case VerifyUnlocked: + if (DEBUG) Log.d(TAG, "mode VerifyUnlocked"); + mForgotPatternButton.setVisibility(View.GONE); + } + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + final boolean result = super.dispatchTouchEvent(ev); + // as long as the user is entering a pattern (i.e sending a touch event that was handled + // by this screen), keep poking the wake lock so that the screen will stay on. + final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime; + if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) { + mLastPokeTime = SystemClock.elapsedRealtime(); + } + return result; + } + + public void reset() { + // reset lock pattern + mLockPatternView.enableInput(); + mLockPatternView.setEnabled(true); + mLockPatternView.clearPattern(); + + // if the user is currently locked out, enforce it. + long deadline = mLockPatternUtils.getLockoutAttemptDeadline(); + if (deadline != 0) { + handleAttemptLockout(deadline); + } else { + mNavigationManager.setMessage(R.string.kg_pattern_instructions); + } + + // the footer depends on how many total attempts the user has failed + if (mCallback.isVerifyUnlockOnly()) { + updateFooter(FooterMode.VerifyUnlocked); + } else if (mEnableFallback && + (mTotalFailedPatternAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { + updateFooter(FooterMode.ForgotLockPattern); + } else { + updateFooter(FooterMode.Normal); + } + + } + + /** TODO: hook this up */ + public void cleanUp() { + if (DEBUG) Log.v(TAG, "Cleanup() called on " + this); + mLockPatternUtils = null; + mLockPatternView.setOnPatternListener(null); + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + if (hasWindowFocus) { + // when timeout dialog closes we want to update our state + reset(); + } + } + + private class UnlockPatternListener implements LockPatternView.OnPatternListener { + + public void onPatternStart() { + mLockPatternView.removeCallbacks(mCancelPatternRunnable); + } + + public void onPatternCleared() { + } + + public void onPatternCellAdded(List<LockPatternView.Cell> pattern) { + // To guard against accidental poking of the wakelock, look for + // the user actually trying to draw a pattern of some minimal length. + if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { + mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS); + } else { + // Give just a little extra time if they hit one of the first few dots + mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS); + } + } + + public void onPatternDetected(List<LockPatternView.Cell> pattern) { + if (mLockPatternUtils.checkPattern(pattern)) { + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); + mCallback.dismiss(true); // keyguardDone(true) + KeyStore.getInstance().password(LockPatternUtils.patternToString(pattern)); + } else { + if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { + mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS); + } + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); + if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { + mTotalFailedPatternAttempts++; + mFailedPatternAttemptsSinceLastTimeout++; + mCallback.reportFailedUnlockAttempt(); + } + if (mFailedPatternAttemptsSinceLastTimeout + >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) { + long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); + handleAttemptLockout(deadline); + } else { + mNavigationManager.setMessage(R.string.kg_wrong_pattern); + mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); + } + } + } + } + + private void maybeEnableFallback(Context context) { + // Ask the account manager if we have an account that can be used as a + // fallback in case the user forgets his pattern. + AccountAnalyzer accountAnalyzer = new AccountAnalyzer(AccountManager.get(context)); + accountAnalyzer.start(); + } + + private class AccountAnalyzer implements AccountManagerCallback<Bundle> { + private final AccountManager mAccountManager; + private final Account[] mAccounts; + private int mAccountIndex; + + private AccountAnalyzer(AccountManager accountManager) { + mAccountManager = accountManager; + mAccounts = accountManager.getAccountsByType("com.google"); + } + + private void next() { + // if we are ready to enable the fallback or if we depleted the list of accounts + // then finish and get out + if (mAccountIndex >= mAccounts.length) { + mEnableFallback = true; + return; + } + + // lookup the confirmCredentials intent for the current account + mAccountManager.confirmCredentials(mAccounts[mAccountIndex], null, null, this, null); + } + + public void start() { + mEnableFallback = false; + mAccountIndex = 0; + next(); + } + + public void run(AccountManagerFuture<Bundle> future) { + try { + Bundle result = future.getResult(); + if (result.getParcelable(AccountManager.KEY_INTENT) != null) { + mEnableFallback = true; + } + } catch (OperationCanceledException e) { + // just skip the account if we are unable to query it + } catch (IOException e) { + // just skip the account if we are unable to query it + } catch (AuthenticatorException e) { + // just skip the account if we are unable to query it + } finally { + mAccountIndex++; + next(); + } + } + } + + private void handleAttemptLockout(long elapsedRealtimeDeadline) { + mLockPatternView.clearPattern(); + mLockPatternView.setEnabled(false); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) { + + @Override + public void onTick(long millisUntilFinished) { + final int secondsRemaining = (int) (millisUntilFinished / 1000); + mNavigationManager.setMessage( + R.string.kg_too_many_failed_attempts_countdown,secondsRemaining); + } + + @Override + public void onFinish() { + mLockPatternView.setEnabled(true); + mNavigationManager.setMessage(R.string.kg_pattern_instructions); + // TODO mUnlockIcon.setVisibility(View.VISIBLE); + mFailedPatternAttemptsSinceLastTimeout = 0; + if (mEnableFallback) { + updateFooter(FooterMode.ForgotLockPattern); + } else { + updateFooter(FooterMode.Normal); + } + } + + }.start(); + } + + @Override + public boolean needsInput() { + return false; + } + + @Override + public void onPause() { + if (mCountdownTimer != null) { + mCountdownTimer.cancel(); + mCountdownTimer = null; + } + } + + @Override + public void onResume() { + reset(); + } + + @Override + public KeyguardSecurityCallback getCallback() { + return mCallback; + } + +} + + + diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java new file mode 100644 index 0000000..36342a5 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.policy.impl.keyguard; + +public interface KeyguardSecurityCallback { + + /** + * Dismiss the given security screen. + * @param securityVerified true if the user correctly entered credentials for the given screen. + */ + void dismiss(boolean securityVerified); + + /** + * Manually report user activity to keep the device awake. If timeout is 0, + * uses user-defined timeout. + * @param timeout + */ + void userActivity(long timeout); + + /** + * Checks if keyguard is in "verify credentials" mode. + * @return true if user has been asked to verify security. + */ + boolean isVerifyUnlockOnly(); + + /** + * Call when user correctly enters their credentials + */ + void reportSuccessfulUnlockAttempt(); + + /** + * Call when the user incorrectly enters their credentials + */ + void reportFailedUnlockAttempt(); + + /** + * Gets the number of attempts thus far as reported by {@link #reportFailedUnlockAttempt()} + * @return number of failed attempts + */ + int getFailedAttempts(); + + /** + * Shows the backup unlock for the current method. If none available, this call is a NOP. + */ + void showBackupUnlock(); + + /** + * Used to inform keyguard when the current view is done drawing. + */ + void keyguardDoneDrawing(); + + /** + * Sets a runnable to launch after the user enters their + * @param runnable + */ + void setOnDismissRunnable(Runnable runnable); + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java new file mode 100644 index 0000000..d041dd3 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.policy.impl.keyguard; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; + +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.widget.LockPatternUtils; + +public class KeyguardSecurityModel { + /** + * The different types of security available for {@link Mode#UnlockScreen}. + * @see com.android.internal.policy.impl.LockPatternKeyguardView#getUnlockMode() + */ + enum SecurityMode { + None, // No security enabled + Pattern, // Unlock by drawing a pattern. + Password, // Unlock by entering a password or PIN + Biometric, // Unlock with a biometric key (e.g. finger print or face unlock) + Account, // Unlock by entering an account's login and password. + SimPin, // Unlock by entering a sim pin. + SimPuk // Unlock by entering a sim puk + } + + private Context mContext; + private LockPatternUtils mLockPatternUtils; + + KeyguardSecurityModel(Context context) { + mContext = context; + mLockPatternUtils = new LockPatternUtils(context); + } + + void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + SecurityMode getSecurityMode() { + KeyguardUpdateMonitor mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); + final IccCardConstants.State simState = mUpdateMonitor.getSimState(); + if (simState == IccCardConstants.State.PIN_REQUIRED) { + return SecurityMode.SimPin; + } else if (simState == IccCardConstants.State.PUK_REQUIRED) { + return SecurityMode.SimPuk; + } else { + final int mode = mLockPatternUtils.getKeyguardStoredPasswordQuality(); + switch (mode) { + case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: + return mLockPatternUtils.isLockPasswordEnabled() ? + SecurityMode.Password : SecurityMode.None; + + case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: + case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED: + if (mLockPatternUtils.isLockPatternEnabled()) { + return mLockPatternUtils.isPermanentlyLocked() ? + SecurityMode.Account : SecurityMode.Pattern; + } else { + return SecurityMode.None; + } + default: + throw new IllegalStateException("Unknown unlock mode:" + mode); + } + } + } + + SecurityMode getBackupFor(SecurityMode mode) { + return SecurityMode.None; // TODO: handle biometric unlock, etc. + } +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java new file mode 100644 index 0000000..d80c1db --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.policy.impl.keyguard; + +import com.android.internal.widget.LockPatternUtils; + +public interface KeyguardSecurityView { + /** + * Interface back to keyguard to tell it when security + * @param callback + */ + void setKeyguardCallback(KeyguardSecurityCallback callback); + + /** + * Set {@link LockPatternUtils} object. Useful for providing a mock interface. + * @param utils + */ + void setLockPatternUtils(LockPatternUtils utils); + + /** + * Reset the view and prepare to take input. This should do things like clearing the + * password or pattern and clear error messages. + */ + void reset(); + + /** + * Emulate activity life cycle within the view. When called, the view should clean up + * and prepare to be removed. + */ + void onPause(); + + /** + * Emulate activity life cycle within this view. When called, the view should prepare itself + * to be shown. + */ + void onResume(); + + /** + * Inquire whether this view requires IME (keyboard) interaction. + * + * @return true if IME interaction is required. + */ + boolean needsInput(); + + /** + * Get {@link KeyguardSecurityCallback} for the given object + * @return KeyguardSecurityCallback + */ + KeyguardSecurityCallback getCallback(); + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java new file mode 100644 index 0000000..a38b247 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.policy.impl.keyguard; + +import android.animation.ObjectAnimator; +import android.app.ActivityManagerNative; +import android.app.SearchManager; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.MediaStore; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Slog; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; + +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.multiwaveview.GlowPadView; +import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener; +import com.android.internal.R; + +public class KeyguardSelectorView extends LinearLayout implements KeyguardSecurityView { + private static final boolean DEBUG = KeyguardHostView.DEBUG; + private static final String TAG = "SecuritySelectorView"; + private static final String ASSIST_ICON_METADATA_NAME = + "com.android.systemui.action_assist_icon"; + private KeyguardSecurityCallback mCallback; + private GlowPadView mGlowPadView; + private Button mEmergencyCallButton; + private ObjectAnimator mAnim; + private boolean mCameraDisabled; + private boolean mSearchDisabled; + private LockPatternUtils mLockPatternUtils; + + OnTriggerListener mOnTriggerListener = new OnTriggerListener() { + + public void onTrigger(View v, int target) { + final int resId = mGlowPadView.getResourceIdForTarget(target); + switch (resId) { + case com.android.internal.R.drawable.ic_action_assist_generic: + Intent assistIntent = + ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, UserHandle.USER_CURRENT); + if (assistIntent != null) { + launchActivity(assistIntent, false); + } else { + Log.w(TAG, "Failed to get intent for assist activity"); + } + mCallback.userActivity(0); + break; + + case com.android.internal.R.drawable.ic_lockscreen_camera: + launchCamera(); + mCallback.userActivity(0); + break; + + case com.android.internal.R.drawable.ic_lockscreen_unlock_phantom: + case com.android.internal.R.drawable.ic_lockscreen_unlock: + mCallback.dismiss(false); + break; + } + } + + public void onReleased(View v, int handle) { + doTransition(mEmergencyCallButton, 1.0f); + } + + public void onGrabbed(View v, int handle) { + doTransition(mEmergencyCallButton, 0.0f); + } + + public void onGrabbedStateChange(View v, int handle) { + + } + + public void onFinishFinalAnimation() { + + } + + }; + + KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { + + private boolean mEmergencyDialerDisableBecauseSimLocked; + + @Override + public void onDevicePolicyManagerStateChanged() { + updateTargets(); + } + + @Override + public void onSimStateChanged(IccCardConstants.State simState) { + // Some carriers aren't capable of handling emergency calls while the SIM is locked + mEmergencyDialerDisableBecauseSimLocked = KeyguardUpdateMonitor.isSimLocked(simState) + && !mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked(); + updateTargets(); + } + + void onPhoneStateChanged(int phoneState) { + if (mEmergencyCallButton != null) { + mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked(); + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton, + phoneState, !mEmergencyDialerDisableBecauseSimLocked); + } + }; + }; + + public KeyguardSelectorView(Context context) { + this(context, null); + } + + protected void launchCamera() { + if (mLockPatternUtils.isSecure()) { + // Launch the secure version of the camera + launchActivity(new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE), true); + } else { + // Launch the normal camera + launchActivity(new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA), false); + } + } + + public KeyguardSelectorView(Context context, AttributeSet attrs) { + super(context, attrs); + mLockPatternUtils = new LockPatternUtils(getContext()); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mGlowPadView = (GlowPadView) findViewById(R.id.glow_pad_view); + mGlowPadView.setOnTriggerListener(mOnTriggerListener); + mEmergencyCallButton = (Button) findViewById(R.id.emergency_call_button); + KeyguardUpdateMonitor.getInstance(getContext()).registerCallback(mInfoCallback); + updateTargets(); + } + + public boolean isTargetPresent(int resId) { + return mGlowPadView.getTargetPosition(resId) != -1; + } + + private void updateTargets() { + boolean disabledByAdmin = mLockPatternUtils.getDevicePolicyManager() + .getCameraDisabled(null); + final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(getContext()); + boolean disabledBySimState = monitor.isSimLocked(); + boolean cameraTargetPresent = + isTargetPresent(com.android.internal.R.drawable.ic_lockscreen_camera); + boolean searchTargetPresent = + isTargetPresent(com.android.internal.R.drawable.ic_action_assist_generic); + + if (disabledByAdmin) { + Log.v(TAG, "Camera disabled by Device Policy"); + } else if (disabledBySimState) { + Log.v(TAG, "Camera disabled by Sim State"); + } + boolean searchActionAvailable = + ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, UserHandle.USER_CURRENT) != null; + mCameraDisabled = disabledByAdmin || disabledBySimState || !cameraTargetPresent; + mSearchDisabled = disabledBySimState || !searchActionAvailable || !searchTargetPresent; + updateResources(); + } + + public void updateResources() { + // Update the search icon with drawable from the search .apk + if (!mSearchDisabled) { + Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, UserHandle.USER_CURRENT); + if (intent != null) { + // XXX Hack. We need to substitute the icon here but haven't formalized + // the public API. The "_google" metadata will be going away, so + // DON'T USE IT! + ComponentName component = intent.getComponent(); + boolean replaced = mGlowPadView.replaceTargetDrawablesIfPresent(component, + ASSIST_ICON_METADATA_NAME + "_google", + com.android.internal.R.drawable.ic_action_assist_generic); + + if (!replaced && !mGlowPadView.replaceTargetDrawablesIfPresent(component, + ASSIST_ICON_METADATA_NAME, + com.android.internal.R.drawable.ic_action_assist_generic)) { + Slog.w(TAG, "Couldn't grab icon from package " + component); + } + } + } + + mGlowPadView.setEnableTarget(com.android.internal.R.drawable + .ic_lockscreen_camera, !mCameraDisabled); + mGlowPadView.setEnableTarget(com.android.internal.R.drawable + .ic_action_assist_generic, !mSearchDisabled); + } + + void doTransition(Object v, float to) { + if (mAnim != null) { + mAnim.cancel(); + } + mAnim = ObjectAnimator.ofFloat(mEmergencyCallButton, "alpha", to); + mAnim.start(); + } + + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mCallback = callback; + } + + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + /** + * Launches the said intent for the current foreground user. + * @param intent + * @param showsWhileLocked true if the activity can be run on top of keyguard. + * See {@link WindowManager#FLAG_SHOW_WHEN_LOCKED} + */ + private void launchActivity(final Intent intent, boolean showsWhileLocked) { + intent.setFlags( + Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_SINGLE_TOP + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + boolean isSecure = mLockPatternUtils.isSecure(); + if (!isSecure || showsWhileLocked) { + if (!isSecure) try { + ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); + } catch (RemoteException e) { + Log.w(TAG, "can't dismiss keyguard on launch"); + } + try { + mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); + } catch (ActivityNotFoundException e) { + Log.w(TAG, "Activity not found for intent + " + intent.getAction()); + } + } else { + // Create a runnable to start the activity and ask the user to enter their + // credentials. + mCallback.setOnDismissRunnable(new Runnable() { + @Override + public void run() { + mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); + } + }); + mCallback.dismiss(false); + } + } + + @Override + public void reset() { + mGlowPadView.reset(false); + } + + @Override + public boolean needsInput() { + return false; + } + + @Override + public void onPause() { + KeyguardUpdateMonitor.getInstance(getContext()).removeCallback(mInfoCallback); + } + + @Override + public void onResume() { + KeyguardUpdateMonitor.getInstance(getContext()).registerCallback(mInfoCallback); + } + + @Override + public KeyguardSecurityCallback getCallback() { + return mCallback; + } + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java new file mode 100644 index 0000000..294ea5c --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl.keyguard; + +import android.app.Activity; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.graphics.Rect; +import android.os.RemoteException; +import android.os.ServiceManager; + +import com.android.internal.telephony.ITelephony; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.PasswordEntryKeyboardHelper; +import com.android.internal.widget.PasswordEntryKeyboardView; +import com.android.internal.R; + +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + +/** + * Displays a dialer like interface to unlock the SIM PIN. + */ +public class KeyguardSimPinView extends LinearLayout + implements KeyguardSecurityView, OnEditorActionListener { + + private static final int DIGIT_PRESS_WAKE_MILLIS = 5000; + + private EditText mPinEntry; + private ProgressDialog mSimUnlockProgressDialog = null; + private KeyguardSecurityCallback mCallback; + private PasswordEntryKeyboardView mKeyboardView; + private PasswordEntryKeyboardHelper mKeyboardHelper; + private LockPatternUtils mLockPatternUtils; + private KeyguardNavigationManager mNavigationManager; + + public KeyguardSimPinView(Context context) { + this(context, null); + } + + public KeyguardSimPinView(Context context, AttributeSet attrs) { + super(context, attrs); + mLockPatternUtils = new LockPatternUtils(getContext()); + } + + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mCallback = callback; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mNavigationManager = new KeyguardNavigationManager(this); + + mPinEntry = (EditText) findViewById(R.id.sim_pin_entry); + mPinEntry.setOnEditorActionListener(this); + + mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard); + mKeyboardHelper = new PasswordEntryKeyboardHelper(mContext, mKeyboardView, this, false); + mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC); + mKeyboardHelper.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled()); + + final View deleteButton = findViewById(R.id.delete_button); + if (deleteButton != null) { + deleteButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mKeyboardHelper.handleBackspace(); + } + }); + } + + setFocusableInTouchMode(true); + reset(); + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + return mPinEntry.requestFocus(direction, previouslyFocusedRect); + } + + public void reset() { + // start fresh + mNavigationManager.setMessage(R.string.kg_sim_pin_instructions); + + // make sure that the number of entered digits is consistent when we + // erase the SIM unlock code, including orientation changes. + mPinEntry.setText(""); + mPinEntry.requestFocus(); + } + + /** {@inheritDoc} */ + public void cleanUp() { + // dismiss the dialog. + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.dismiss(); + mSimUnlockProgressDialog = null; + } + } + + /** + * Since the IPC can block, we want to run the request in a separate thread + * with a callback. + */ + private abstract class CheckSimPin extends Thread { + private final String mPin; + + protected CheckSimPin(String pin) { + mPin = pin; + } + + abstract void onSimLockChangedResponse(boolean success); + + @Override + public void run() { + try { + final boolean result = ITelephony.Stub.asInterface(ServiceManager + .checkService("phone")).supplyPin(mPin); + post(new Runnable() { + public void run() { + onSimLockChangedResponse(result); + } + }); + } catch (RemoteException e) { + post(new Runnable() { + public void run() { + onSimLockChangedResponse(false); + } + }); + } + } + } + + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + // Check if this was the result of hitting the enter key + mCallback.userActivity(DIGIT_PRESS_WAKE_MILLIS); + if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT) { + checkPin(); + return true; + } + return false; + } + + private Dialog getSimUnlockProgressDialog() { + if (mSimUnlockProgressDialog == null) { + mSimUnlockProgressDialog = new ProgressDialog(mContext); + mSimUnlockProgressDialog.setMessage( + mContext.getString(R.string.kg_sim_unlock_progress_dialog_message)); + mSimUnlockProgressDialog.setIndeterminate(true); + mSimUnlockProgressDialog.setCancelable(false); + if (!(mContext instanceof Activity)) { + mSimUnlockProgressDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + } + return mSimUnlockProgressDialog; + } + + private void checkPin() { + if (mPinEntry.getText().length() < 4) { + // otherwise, display a message to the user, and don't submit. + mNavigationManager.setMessage(R.string.kg_invalid_sim_pin_hint); + mPinEntry.setText(""); + mCallback.userActivity(0); + return; + } + + getSimUnlockProgressDialog().show(); + + new CheckSimPin(mPinEntry.getText().toString()) { + void onSimLockChangedResponse(final boolean success) { + post(new Runnable() { + public void run() { + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.hide(); + } + if (success) { + // before closing the keyguard, report back that the sim is unlocked + // so it knows right away. + KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(); + mCallback.dismiss(false); // + } else { + mNavigationManager.setMessage(R.string.kg_password_wrong_pin_code); + mPinEntry.setText(""); + } + mCallback.userActivity(0); + } + }); + } + }.start(); + } + + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + public boolean needsInput() { + return false; // This view provides its own keypad + } + + public void onPause() { + + } + + public void onResume() { + reset(); + } + + public KeyguardSecurityCallback getCallback() { + return mCallback; + } + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java new file mode 100644 index 0000000..801dfc3 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.policy.impl.keyguard; + +import android.app.Activity; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.text.Editable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + +import com.android.internal.telephony.ITelephony; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.PasswordEntryKeyboardHelper; +import com.android.internal.widget.PasswordEntryKeyboardView; +import com.android.internal.R; + +public class KeyguardSimPukView extends LinearLayout implements View.OnClickListener, + View.OnFocusChangeListener, KeyguardSecurityView, OnEditorActionListener { + + private static final int DIGIT_PRESS_WAKE_MILLIS = 5000; + + private TextView mPukText; + private TextView mPinText; + private TextView mFocusedEntry; + + private View mDelPukButton; + private View mDelPinButton; + + private ProgressDialog mSimUnlockProgressDialog = null; + private KeyguardSecurityCallback mCallback; + + private KeyguardNavigationManager mNavigationManager; + + private PasswordEntryKeyboardView mKeyboardView; + + private PasswordEntryKeyboardHelper mKeyboardHelper; + + private LockPatternUtils mLockPatternUtils; + + public KeyguardSimPukView(Context context) { + this(context, null); + } + + public KeyguardSimPukView(Context context, AttributeSet attrs) { + super(context, attrs); + mLockPatternUtils = new LockPatternUtils(getContext()); + } + + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mCallback = callback; + mLockPatternUtils = new LockPatternUtils(getContext()); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mNavigationManager = new KeyguardNavigationManager(this); + + mPukText = (TextView) findViewById(R.id.sim_puk_entry); + mPukText.setOnEditorActionListener(this); + mPinText = (TextView) findViewById(R.id.sim_pin_entry); + mPinText.setOnEditorActionListener(this); + mDelPukButton = findViewById(R.id.puk_delete_button); + mDelPukButton.setOnClickListener(this); + mDelPinButton = findViewById(R.id.pin_delete_button); + mDelPinButton.setOnClickListener(this); + + mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard); + mKeyboardHelper = new PasswordEntryKeyboardHelper(mContext, mKeyboardView, this, false); + mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC); + mKeyboardHelper.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled()); + + mNavigationManager.setMessage(R.string.kg_sim_puk_recovery_hint); + + mPinText.setFocusableInTouchMode(true); + mPinText.setOnFocusChangeListener(this); + mPukText.setFocusableInTouchMode(true); + mPukText.setOnFocusChangeListener(this); + + setFocusableInTouchMode(true); + + reset(); + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + return mPukText.requestFocus(direction, previouslyFocusedRect); + } + + public boolean needsInput() { + return false; // This view provides its own keypad + } + + public void onPause() { + + } + + public void onResume() { + reset(); + } + + /** {@inheritDoc} */ + public void cleanUp() { + // dismiss the dialog. + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.dismiss(); + mSimUnlockProgressDialog = null; + } + } + + /** + * Since the IPC can block, we want to run the request in a separate thread + * with a callback. + */ + private abstract class CheckSimPuk extends Thread { + + private final String mPin, mPuk; + + protected CheckSimPuk(String puk, String pin) { + mPuk = puk; + mPin = pin; + } + + abstract void onSimLockChangedResponse(boolean success); + + @Override + public void run() { + try { + final boolean result = ITelephony.Stub.asInterface(ServiceManager + .checkService("phone")).supplyPuk(mPuk, mPin); + + post(new Runnable() { + public void run() { + onSimLockChangedResponse(result); + } + }); + } catch (RemoteException e) { + post(new Runnable() { + public void run() { + onSimLockChangedResponse(false); + } + }); + } + } + } + + public void onClick(View v) { + if (v == mDelPukButton) { + if (mFocusedEntry != mPukText) + mPukText.requestFocus(); + final Editable digits = mPukText.getEditableText(); + final int len = digits.length(); + if (len > 0) { + digits.delete(len-1, len); + } + } else if (v == mDelPinButton) { + if (mFocusedEntry != mPinText) + mPinText.requestFocus(); + final Editable digits = mPinText.getEditableText(); + final int len = digits.length(); + if (len > 0) { + digits.delete(len-1, len); + } + } + mCallback.userActivity(DIGIT_PRESS_WAKE_MILLIS); + } + + @Override + public void onFocusChange(View view, boolean hasFocus) { + if (hasFocus) + mFocusedEntry = (TextView) view; + } + + private Dialog getSimUnlockProgressDialog() { + if (mSimUnlockProgressDialog == null) { + mSimUnlockProgressDialog = new ProgressDialog(mContext); + mSimUnlockProgressDialog.setMessage(mContext.getString( + R.string.kg_sim_unlock_progress_dialog_message)); + mSimUnlockProgressDialog.setIndeterminate(true); + mSimUnlockProgressDialog.setCancelable(false); + if (!(mContext instanceof Activity)) { + mSimUnlockProgressDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + } + return mSimUnlockProgressDialog; + } + + private void checkPuk() { + // make sure the puk is at least 8 digits long. + if (mPukText.getText().length() < 8) { + // otherwise, display a message to the user, and don't submit. + mNavigationManager.setMessage(R.string.kg_invalid_sim_puk_hint); + mPukText.setText(""); + return; + } + + // make sure the PIN is between 4 and 8 digits + if (mPinText.getText().length() < 4 + || mPinText.getText().length() > 8) { + // otherwise, display a message to the user, and don't submit. + mNavigationManager.setMessage(R.string.kg_invalid_sim_pin_hint); + mPinText.setText(""); + return; + } + + getSimUnlockProgressDialog().show(); + + new CheckSimPuk(mPukText.getText().toString(), + mPinText.getText().toString()) { + void onSimLockChangedResponse(final boolean success) { + mPinText.post(new Runnable() { + public void run() { + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.hide(); + } + if (success) { + mCallback.dismiss(true); + } else { + mNavigationManager.setMessage(R.string.kg_invalid_puk); + mPukText.setText(""); + mPinText.setText(""); + } + } + }); + } + }.start(); + } + + @Override + public boolean onEditorAction(TextView view, int actionId, KeyEvent event) { + // Check if this was the result of hitting the enter key + mCallback.userActivity(DIGIT_PRESS_WAKE_MILLIS); + if (actionId == EditorInfo.IME_NULL + || actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT) { + if (view == mPukText && mPukText.getText().length() < 8) { + mNavigationManager.setMessage(R.string.kg_invalid_sim_puk_hint); + mPukText.setText(""); + mPukText.requestFocus(); + return true; + } else if (view == mPinText) { + if (mPinText.getText().length() < 4 || mPinText.getText().length() > 8) { + mNavigationManager.setMessage(R.string.kg_invalid_sim_pin_hint); + mPinText.setText(""); + mPinText.requestFocus(); + } else { + checkPuk(); + } + return true; + } + } + return false; + } + + @Override + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + @Override + public void reset() { + mNavigationManager.setMessage(R.string.kg_sim_puk_recovery_hint); + mPinText.setText(""); + mPukText.setText(""); + mPukText.requestFocus(); + } + + @Override + public KeyguardSecurityCallback getCallback() { + return mCallback; + } + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java new file mode 100644 index 0000000..d6ce967 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl.keyguard; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.GridLayout; + +public class KeyguardStatusView extends GridLayout { + public KeyguardStatusView(Context context) { + this(context, null, 0); + } + + public KeyguardStatusView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public KeyguardStatusView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + // StatusView manages all of the widgets in this view. + new KeyguardStatusViewManager(this); + } + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java new file mode 100644 index 0000000..06ed88a --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java @@ -0,0 +1,570 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl.keyguard; + +import com.android.internal.R; +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.widget.DigitalClock; +import com.android.internal.widget.LockPatternUtils; + +import java.util.Date; + +import libcore.util.MutableInt; + +import android.content.ContentResolver; +import android.content.Context; +import android.provider.Settings; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +/*** + * Manages a number of views inside of LockScreen layouts. See below for a list of widgets + */ +class KeyguardStatusViewManager { + private static final boolean DEBUG = false; + private static final String TAG = "KeyguardStatusView"; + + public static final int LOCK_ICON = 0; // R.drawable.ic_lock_idle_lock; + public static final int ALARM_ICON = R.drawable.ic_lock_idle_alarm; + public static final int CHARGING_ICON = 0; //R.drawable.ic_lock_idle_charging; + public static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery; + + private static final int INSTRUCTION_TEXT = 10; + private static final int CARRIER_TEXT = 11; + private static final int CARRIER_HELP_TEXT = 12; + private static final int HELP_MESSAGE_TEXT = 13; + private static final int OWNER_INFO = 14; + private static final int BATTERY_INFO = 15; + + private StatusMode mStatus; + private String mDateFormatString; + + // Views that this class controls. + // NOTE: These may be null in some LockScreen screens and should protect from NPE + private TextView mCarrierView; + private TextView mDateView; + private TextView mStatus1View; + private TextView mOwnerInfoView; + private TextView mAlarmStatusView; + + // Top-level container view for above views + private View mContainer; + + // are we showing battery information? + private boolean mShowingBatteryInfo = false; + + // last known plugged in state + private boolean mPluggedIn = false; + + // last known battery level + private int mBatteryLevel = 100; + + // last known SIM state + protected IccCardConstants.State mSimState; + + private LockPatternUtils mLockPatternUtils; + private KeyguardUpdateMonitor mUpdateMonitor; + + // Shadowed text values + private CharSequence mCarrierText; + private CharSequence mCarrierHelpText; + private String mHelpMessageText; + private String mInstructionText; + private CharSequence mOwnerInfoText; + private boolean mShowingStatus; + private CharSequence mPlmn; + private CharSequence mSpn; + protected int mPhoneState; + private DigitalClock mDigitalClock; + protected boolean mBatteryCharged; + protected boolean mBatteryIsLow; + private boolean mEmergencyButtonEnabledBecauseSimLocked; + private Button mEmergencyCallButton; + private boolean mEmergencyCallButtonEnabledInScreen; + + /** + * + * @param view the containing view of all widgets + * @param updateMonitor the update monitor to use + * @param lockPatternUtils lock pattern util object + * @param callback used to invoke emergency dialer + * @param emergencyButtonEnabledInScreen whether emergency button is enabled by default + */ + public KeyguardStatusViewManager(View view) { + if (DEBUG) Log.v(TAG, "KeyguardStatusViewManager()"); + mContainer = view; + mDateFormatString = getContext().getString(R.string.abbrev_wday_month_day_no_year); + mLockPatternUtils = new LockPatternUtils(view.getContext()); + mUpdateMonitor = KeyguardUpdateMonitor.getInstance(view.getContext()); + + mCarrierView = (TextView) findViewById(R.id.carrier); + mDateView = (TextView) findViewById(R.id.date); + mStatus1View = (TextView) findViewById(R.id.status1); + mAlarmStatusView = (TextView) findViewById(R.id.alarm_status); + mOwnerInfoView = (TextView) findViewById(R.id.propertyOf); + mDigitalClock = (DigitalClock) findViewById(R.id.time); + + // Registering this callback immediately updates the battery state, among other things. + mUpdateMonitor.registerCallback(mInfoCallback); + + resetStatusInfo(); + refreshDate(); + updateOwnerInfo(); + + // Required to get Marquee to work. + final View scrollableViews[] = { mCarrierView, mDateView, mStatus1View, mOwnerInfoView, + mAlarmStatusView }; + for (View v : scrollableViews) { + if (v != null) { + v.setSelected(true); + } + } + } + + void setInstructionText(String string) { + mInstructionText = string; + update(INSTRUCTION_TEXT, string); + } + + void setCarrierText(CharSequence string) { + mCarrierText = string; + update(CARRIER_TEXT, string); + } + + void setOwnerInfo(CharSequence string) { + mOwnerInfoText = string; + update(OWNER_INFO, string); + } + + /** + * Sets the carrier help text message, if view is present. Carrier help text messages are + * typically for help dealing with SIMS and connectivity. + * + * @param resId resource id of the message + */ + public void setCarrierHelpText(int resId) { + mCarrierHelpText = getText(resId); + update(CARRIER_HELP_TEXT, mCarrierHelpText); + } + + private CharSequence getText(int resId) { + return resId == 0 ? null : getContext().getText(resId); + } + + /** + * Unlock help message. This is typically for help with unlock widgets, e.g. "wrong password" + * or "try again." + * + * @param textResId + * @param lockIcon + */ + public void setHelpMessage(int textResId, int lockIcon) { + final CharSequence tmp = getText(textResId); + mHelpMessageText = tmp == null ? null : tmp.toString(); + update(HELP_MESSAGE_TEXT, mHelpMessageText); + } + + private void update(int what, CharSequence string) { + updateStatusLines(mShowingStatus); + } + + public void onPause() { + if (DEBUG) Log.v(TAG, "onPause()"); + mUpdateMonitor.removeCallback(mInfoCallback); + } + + /** {@inheritDoc} */ + public void onResume() { + if (DEBUG) Log.v(TAG, "onResume()"); + + // First update the clock, if present. + if (mDigitalClock != null) { + mDigitalClock.updateTime(); + } + + mUpdateMonitor.registerCallback(mInfoCallback); + resetStatusInfo(); + } + + void resetStatusInfo() { + mInstructionText = null; + updateStatusLines(true); + } + + /** + * Update the status lines based on these rules: + * AlarmStatus: Alarm state always gets it's own line. + * Status1 is shared between help, battery status and generic unlock instructions, + * prioritized in that order. + * @param showStatusLines status lines are shown if true + */ + void updateStatusLines(boolean showStatusLines) { + if (DEBUG) Log.v(TAG, "updateStatusLines(" + showStatusLines + ")"); + mShowingStatus = showStatusLines; + updateAlarmInfo(); + updateOwnerInfo(); + updateStatus1(); + updateCarrierText(); + } + + private void updateAlarmInfo() { + if (mAlarmStatusView != null) { + String nextAlarm = mLockPatternUtils.getNextAlarm(); + boolean showAlarm = mShowingStatus && !TextUtils.isEmpty(nextAlarm); + mAlarmStatusView.setText(nextAlarm); + mAlarmStatusView.setCompoundDrawablesWithIntrinsicBounds(ALARM_ICON, 0, 0, 0); + mAlarmStatusView.setVisibility(showAlarm ? View.VISIBLE : View.GONE); + } + } + + private void updateOwnerInfo() { + final ContentResolver res = getContext().getContentResolver(); + final boolean ownerInfoEnabled = Settings.Secure.getInt(res, + Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1) != 0; + mOwnerInfoText = ownerInfoEnabled ? + Settings.Secure.getString(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO) : null; + if (mOwnerInfoView != null) { + mOwnerInfoView.setText(mOwnerInfoText); + mOwnerInfoView.setVisibility(TextUtils.isEmpty(mOwnerInfoText) ? View.GONE:View.VISIBLE); + } + } + + private void updateStatus1() { + if (mStatus1View != null) { + MutableInt icon = new MutableInt(0); + CharSequence string = getPriorityTextMessage(icon); + mStatus1View.setText(string); + mStatus1View.setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0); + mStatus1View.setVisibility(mShowingStatus ? View.VISIBLE : View.INVISIBLE); + } + } + + private void updateCarrierText() { + mCarrierView.setText(mCarrierText); + } + + private CharSequence getAltTextMessage(MutableInt icon) { + // If we have replaced the status area with a single widget, then this code + // prioritizes what to show in that space when all transient messages are gone. + CharSequence string = null; + if (mShowingBatteryInfo) { + // Battery status + if (mPluggedIn) { + // Charging, charged or waiting to charge. + string = getContext().getString(mBatteryCharged ? R.string.lockscreen_charged + :R.string.lockscreen_plugged_in, mBatteryLevel); + icon.value = CHARGING_ICON; + } else if (mBatteryIsLow) { + // Battery is low + string = getContext().getString(R.string.lockscreen_low_battery); + icon.value = BATTERY_LOW_ICON; + } + } else { + string = mCarrierText; + } + return string; + } + + private CharSequence getPriorityTextMessage(MutableInt icon) { + CharSequence string = null; + if (!TextUtils.isEmpty(mInstructionText)) { + // Instructions only + string = mInstructionText; + icon.value = LOCK_ICON; + } else if (mShowingBatteryInfo) { + // Battery status + if (mPluggedIn) { + // Charging, charged or waiting to charge. + string = getContext().getString(mBatteryCharged ? R.string.lockscreen_charged + :R.string.lockscreen_plugged_in, mBatteryLevel); + icon.value = CHARGING_ICON; + } else if (mBatteryIsLow) { + // Battery is low + string = getContext().getString(R.string.lockscreen_low_battery); + icon.value = BATTERY_LOW_ICON; + } + } else if (mOwnerInfoView == null && mOwnerInfoText != null) { + string = mOwnerInfoText; + } + return string; + } + + void refreshDate() { + if (mDateView != null) { + mDateView.setText(DateFormat.format(mDateFormatString, new Date())); + } + } + + /** + * Determine the current status of the lock screen given the sim state and other stuff. + */ + public StatusMode getStatusForIccState(IccCardConstants.State simState) { + // Since reading the SIM may take a while, we assume it is present until told otherwise. + if (simState == null) { + return StatusMode.Normal; + } + + final boolean missingAndNotProvisioned = (!mUpdateMonitor.isDeviceProvisioned() + && (simState == IccCardConstants.State.ABSENT || + simState == IccCardConstants.State.PERM_DISABLED)); + + // Assume we're NETWORK_LOCKED if not provisioned + simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState; + switch (simState) { + case ABSENT: + return StatusMode.SimMissing; + case NETWORK_LOCKED: + return StatusMode.SimMissingLocked; + case NOT_READY: + return StatusMode.SimMissing; + case PIN_REQUIRED: + return StatusMode.SimLocked; + case PUK_REQUIRED: + return StatusMode.SimPukLocked; + case READY: + return StatusMode.Normal; + case PERM_DISABLED: + return StatusMode.SimPermDisabled; + case UNKNOWN: + return StatusMode.SimMissing; + } + return StatusMode.SimMissing; + } + + private Context getContext() { + return mContainer.getContext(); + } + + /** + * Update carrier text, carrier help and emergency button to match the current status based + * on SIM state. + * + * @param simState + */ + private void updateCarrierStateWithSimStatus(IccCardConstants.State simState) { + if (DEBUG) Log.d(TAG, "updateCarrierTextWithSimStatus(), simState = " + simState); + + CharSequence carrierText = null; + int carrierHelpTextId = 0; + mEmergencyButtonEnabledBecauseSimLocked = false; + mStatus = getStatusForIccState(simState); + mSimState = simState; + switch (mStatus) { + case Normal: + carrierText = makeCarierString(mPlmn, mSpn); + break; + + case NetworkLocked: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.lockscreen_network_locked_message), + mPlmn); + carrierHelpTextId = R.string.lockscreen_instructions_when_pattern_disabled; + break; + + case SimMissing: + // Shows "No SIM card | Emergency calls only" on devices that are voice-capable. + // This depends on mPlmn containing the text "Emergency calls only" when the radio + // has some connectivity. Otherwise, it should be null or empty and just show + // "No SIM card" + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.lockscreen_missing_sim_message_short), + mPlmn); + carrierHelpTextId = R.string.lockscreen_missing_sim_instructions_long; + break; + + case SimPermDisabled: + carrierText = getContext().getText( + R.string.lockscreen_permanent_disabled_sim_message_short); + carrierHelpTextId = R.string.lockscreen_permanent_disabled_sim_instructions; + mEmergencyButtonEnabledBecauseSimLocked = true; + break; + + case SimMissingLocked: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.lockscreen_missing_sim_message_short), + mPlmn); + carrierHelpTextId = R.string.lockscreen_missing_sim_instructions; + mEmergencyButtonEnabledBecauseSimLocked = true; + break; + + case SimLocked: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.lockscreen_sim_locked_message), + mPlmn); + mEmergencyButtonEnabledBecauseSimLocked = true; + break; + + case SimPukLocked: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.lockscreen_sim_puk_locked_message), + mPlmn); + if (!mLockPatternUtils.isPukUnlockScreenEnable()) { + // This means we're showing the PUK unlock screen + mEmergencyButtonEnabledBecauseSimLocked = true; + } + break; + } + + setCarrierText(carrierText); + setCarrierHelpText(carrierHelpTextId); + updateEmergencyCallButtonState(mPhoneState); + } + + + /* + * Add emergencyCallMessage to carrier string only if phone supports emergency calls. + */ + private CharSequence makeCarrierStringOnEmergencyCapable( + CharSequence simMessage, CharSequence emergencyCallMessage) { + if (mLockPatternUtils.isEmergencyCallCapable()) { + return makeCarierString(simMessage, emergencyCallMessage); + } + return simMessage; + } + + private View findViewById(int id) { + return mContainer.findViewById(id); + } + + /** + * The status of this lock screen. Primarily used for widgets on LockScreen. + */ + enum StatusMode { + /** + * Normal case (sim card present, it's not locked) + */ + Normal(true), + + /** + * The sim card is 'network locked'. + */ + NetworkLocked(true), + + /** + * The sim card is missing. + */ + SimMissing(false), + + /** + * The sim card is missing, and this is the device isn't provisioned, so we don't let + * them get past the screen. + */ + SimMissingLocked(false), + + /** + * The sim card is PUK locked, meaning they've entered the wrong sim unlock code too many + * times. + */ + SimPukLocked(false), + + /** + * The sim card is locked. + */ + SimLocked(true), + + /** + * The sim card is permanently disabled due to puk unlock failure + */ + SimPermDisabled(false); + + private final boolean mShowStatusLines; + + StatusMode(boolean mShowStatusLines) { + this.mShowStatusLines = mShowStatusLines; + } + + /** + * @return Whether the status lines (battery level and / or next alarm) are shown while + * in this state. Mostly dictated by whether this is room for them. + */ + public boolean shouldShowStatusLines() { + return mShowStatusLines; + } + } + + private void updateEmergencyCallButtonState(int phoneState) { + if (mEmergencyCallButton != null) { + boolean enabledBecauseSimLocked = + mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked() + && mEmergencyButtonEnabledBecauseSimLocked; + boolean shown = mEmergencyCallButtonEnabledInScreen || enabledBecauseSimLocked; + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton, + phoneState, shown); + } + } + + private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { + + @Override + public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { + mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow(); + mPluggedIn = status.isPluggedIn(); + mBatteryLevel = status.level; + mBatteryCharged = status.isCharged(); + mBatteryIsLow = status.isBatteryLow(); + final MutableInt tmpIcon = new MutableInt(0); + update(BATTERY_INFO, getAltTextMessage(tmpIcon)); + } + + @Override + public void onTimeChanged() { + refreshDate(); + } + + @Override + public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) { + mPlmn = plmn; + mSpn = spn; + updateCarrierStateWithSimStatus(mSimState); + } + + @Override + public void onPhoneStateChanged(int phoneState) { + mPhoneState = phoneState; + updateEmergencyCallButtonState(phoneState); + } + + @Override + public void onSimStateChanged(IccCardConstants.State simState) { + updateCarrierStateWithSimStatus(simState); + } + }; + + /** + * Performs concentenation of PLMN/SPN + * @param plmn + * @param spn + * @return + */ + private static CharSequence makeCarierString(CharSequence plmn, CharSequence spn) { + final boolean plmnValid = !TextUtils.isEmpty(plmn); + final boolean spnValid = !TextUtils.isEmpty(spn); + if (plmnValid && spnValid) { + return plmn + "|" + spn; + } else if (plmnValid) { + return plmn; + } else if (spnValid) { + return spn; + } else { + return ""; + } + } +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java new file mode 100644 index 0000000..dad0dff --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java @@ -0,0 +1,710 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl.keyguard; + +import android.app.admin.DevicePolicyManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import static android.os.BatteryManager.BATTERY_STATUS_FULL; +import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; +import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN; +import static android.os.BatteryManager.EXTRA_STATUS; +import static android.os.BatteryManager.EXTRA_PLUGGED; +import static android.os.BatteryManager.EXTRA_LEVEL; +import static android.os.BatteryManager.EXTRA_HEALTH; +import android.media.AudioManager; +import android.os.BatteryManager; +import android.os.Handler; +import android.os.Message; +import android.provider.Settings; + +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.telephony.TelephonyIntents; + +import android.telephony.TelephonyManager; +import android.util.Log; +import com.android.internal.R; +import com.google.android.collect.Lists; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +/** + * Watches for updates that may be interesting to the keyguard, and provides + * the up to date information as well as a registration for callbacks that care + * to be updated. + * + * Note: under time crunch, this has been extended to include some stuff that + * doesn't really belong here. see {@link #handleBatteryUpdate} where it shutdowns + * the device, and {@link #getFailedAttempts()}, {@link #reportFailedAttempt()} + * and {@link #clearFailedAttempts()}. Maybe we should rename this 'KeyguardContext'... + */ +public class KeyguardUpdateMonitor { + + private static final String TAG = "KeyguardUpdateMonitor"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_SIM_STATES = DEBUG || false; + private static final int FAILED_BIOMETRIC_UNLOCK_ATTEMPTS_BEFORE_BACKUP = 3; + private static final int LOW_BATTERY_THRESHOLD = 20; + + // Callback messages + private static final int MSG_TIME_UPDATE = 301; + private static final int MSG_BATTERY_UPDATE = 302; + private static final int MSG_CARRIER_INFO_UPDATE = 303; + private static final int MSG_SIM_STATE_CHANGE = 304; + private static final int MSG_RINGER_MODE_CHANGED = 305; + private static final int MSG_PHONE_STATE_CHANGED = 306; + private static final int MSG_CLOCK_VISIBILITY_CHANGED = 307; + private static final int MSG_DEVICE_PROVISIONED = 308; + protected static final int MSG_DPM_STATE_CHANGED = 309; + protected static final int MSG_USER_SWITCHED = 310; + protected static final int MSG_USER_REMOVED = 311; + + private static KeyguardUpdateMonitor sInstance; + + private final Context mContext; + + // Telephony state + private IccCardConstants.State mSimState = IccCardConstants.State.READY; + private CharSequence mTelephonyPlmn; + private CharSequence mTelephonySpn; + private int mRingMode; + private int mPhoneState; + + // Device provisioning state + private boolean mDeviceProvisioned; + + // Battery status + private BatteryStatus mBatteryStatus; + + // Password attempts + private int mFailedAttempts = 0; + private int mFailedBiometricUnlockAttempts = 0; + + private boolean mClockVisible; + + private ArrayList<WeakReference<KeyguardUpdateMonitorCallback>> + mCallbacks = Lists.newArrayList(); + private ContentObserver mContentObserver; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_TIME_UPDATE: + handleTimeUpdate(); + break; + case MSG_BATTERY_UPDATE: + handleBatteryUpdate((BatteryStatus) msg.obj); + break; + case MSG_CARRIER_INFO_UPDATE: + handleCarrierInfoUpdate(); + break; + case MSG_SIM_STATE_CHANGE: + handleSimStateChange((SimArgs) msg.obj); + break; + case MSG_RINGER_MODE_CHANGED: + handleRingerModeChange(msg.arg1); + break; + case MSG_PHONE_STATE_CHANGED: + handlePhoneStateChanged((String)msg.obj); + break; + case MSG_CLOCK_VISIBILITY_CHANGED: + handleClockVisibilityChanged(); + break; + case MSG_DEVICE_PROVISIONED: + handleDeviceProvisioned(); + break; + case MSG_DPM_STATE_CHANGED: + handleDevicePolicyManagerStateChanged(); + break; + case MSG_USER_SWITCHED: + handleUserSwitched(msg.arg1); + break; + case MSG_USER_REMOVED: + handleUserRemoved(msg.arg1); + break; + } + } + }; + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (DEBUG) Log.d(TAG, "received broadcast " + action); + + if (Intent.ACTION_TIME_TICK.equals(action) + || Intent.ACTION_TIME_CHANGED.equals(action) + || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_TIME_UPDATE)); + } else if (TelephonyIntents.SPN_STRINGS_UPDATED_ACTION.equals(action)) { + mTelephonyPlmn = getTelephonyPlmnFrom(intent); + mTelephonySpn = getTelephonySpnFrom(intent); + mHandler.sendMessage(mHandler.obtainMessage(MSG_CARRIER_INFO_UPDATE)); + } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { + final int status = intent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN); + final int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0); + final int level = intent.getIntExtra(EXTRA_LEVEL, 0); + final int health = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN); + final Message msg = mHandler.obtainMessage( + MSG_BATTERY_UPDATE, new BatteryStatus(status, level, plugged, health)); + mHandler.sendMessage(msg); + } else if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) { + if (DEBUG_SIM_STATES) { + Log.v(TAG, "action " + action + " state" + + intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE)); + } + mHandler.sendMessage(mHandler.obtainMessage( + MSG_SIM_STATE_CHANGE, SimArgs.fromIntent(intent))); + } else if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED, + intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1), 0)); + } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) { + String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); + mHandler.sendMessage(mHandler.obtainMessage(MSG_PHONE_STATE_CHANGED, state)); + } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED + .equals(action)) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_DPM_STATE_CHANGED)); + } else if (Intent.ACTION_USER_SWITCHED.equals(action)) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHED, + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0)); + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_REMOVED, + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0)); + } + } + }; + + /** + * When we receive a + * {@link com.android.internal.telephony.TelephonyIntents#ACTION_SIM_STATE_CHANGED} broadcast, + * and then pass a result via our handler to {@link KeyguardUpdateMonitor#handleSimStateChange}, + * we need a single object to pass to the handler. This class helps decode + * the intent and provide a {@link SimCard.State} result. + */ + private static class SimArgs { + public final IccCardConstants.State simState; + + SimArgs(IccCardConstants.State state) { + simState = state; + } + + static SimArgs fromIntent(Intent intent) { + IccCardConstants.State state; + if (!TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) { + throw new IllegalArgumentException("only handles intent ACTION_SIM_STATE_CHANGED"); + } + String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE); + if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) { + final String absentReason = intent + .getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON); + + if (IccCardConstants.INTENT_VALUE_ABSENT_ON_PERM_DISABLED.equals( + absentReason)) { + state = IccCardConstants.State.PERM_DISABLED; + } else { + state = IccCardConstants.State.ABSENT; + } + } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) { + state = IccCardConstants.State.READY; + } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) { + final String lockedReason = intent + .getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON); + if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) { + state = IccCardConstants.State.PIN_REQUIRED; + } else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) { + state = IccCardConstants.State.PUK_REQUIRED; + } else { + state = IccCardConstants.State.UNKNOWN; + } + } else if (IccCardConstants.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) { + state = IccCardConstants.State.NETWORK_LOCKED; + } else { + state = IccCardConstants.State.UNKNOWN; + } + return new SimArgs(state); + } + + public String toString() { + return simState.toString(); + } + } + + /* package */ static class BatteryStatus { + public final int status; + public final int level; + public final int plugged; + public final int health; + public BatteryStatus(int status, int level, int plugged, int health) { + this.status = status; + this.level = level; + this.plugged = plugged; + this.health = health; + } + + /** + * Determine whether the device is plugged in (USB or power). + * @return true if the device is plugged in. + */ + boolean isPluggedIn() { + return plugged == BatteryManager.BATTERY_PLUGGED_AC + || plugged == BatteryManager.BATTERY_PLUGGED_USB; + } + + /** + * Whether or not the device is charged. Note that some devices never return 100% for + * battery level, so this allows either battery level or status to determine if the + * battery is charged. + * @return true if the device is charged + */ + public boolean isCharged() { + return status == BATTERY_STATUS_FULL || level >= 100; + } + + /** + * Whether battery is low and needs to be charged. + * @return true if battery is low + */ + public boolean isBatteryLow() { + return level < LOW_BATTERY_THRESHOLD; + } + + } + + public static KeyguardUpdateMonitor getInstance(Context context) { + if (sInstance == null) { + sInstance = new KeyguardUpdateMonitor(context); + } + return sInstance; + } + + private KeyguardUpdateMonitor(Context context) { + mContext = context; + + mDeviceProvisioned = Settings.Secure.getInt( + mContext.getContentResolver(), Settings.Secure.DEVICE_PROVISIONED, 0) != 0; + + // Since device can't be un-provisioned, we only need to register a content observer + // to update mDeviceProvisioned when we are... + if (!mDeviceProvisioned) { + watchForDeviceProvisioning(); + } + + // Take a guess at initial SIM state, battery status and PLMN until we get an update + mSimState = IccCardConstants.State.NOT_READY; + mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, 100, 0, 0); + mTelephonyPlmn = getDefaultPlmn(); + + // Watch for interesting updates + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_TIME_TICK); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); + filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION); + filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); + filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); + filter.addAction(Intent.ACTION_USER_SWITCHED); + filter.addAction(Intent.ACTION_USER_REMOVED); + context.registerReceiver(mBroadcastReceiver, filter); + } + + private void watchForDeviceProvisioning() { + mContentObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + mDeviceProvisioned = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.DEVICE_PROVISIONED, 0) != 0; + if (mDeviceProvisioned) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_DEVICE_PROVISIONED)); + } + if (DEBUG) Log.d(TAG, "DEVICE_PROVISIONED state = " + mDeviceProvisioned); + } + }; + + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.DEVICE_PROVISIONED), + false, mContentObserver); + + // prevent a race condition between where we check the flag and where we register the + // observer by grabbing the value once again... + boolean provisioned = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.DEVICE_PROVISIONED, 0) != 0; + if (provisioned != mDeviceProvisioned) { + mDeviceProvisioned = provisioned; + if (mDeviceProvisioned) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_DEVICE_PROVISIONED)); + } + } + } + + /** + * Handle {@link #MSG_DPM_STATE_CHANGED} + */ + protected void handleDevicePolicyManagerStateChanged() { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onDevicePolicyManagerStateChanged(); + } + } + } + + /** + * Handle {@link #MSG_USER_SWITCHED} + */ + protected void handleUserSwitched(int userId) { + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onUserSwitched(userId); + } + } + } + + /** + * Handle {@link #MSG_USER_SWITCHED} + */ + protected void handleUserRemoved(int userId) { + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onUserRemoved(userId); + } + } + } + + /** + * Handle {@link #MSG_DEVICE_PROVISIONED} + */ + protected void handleDeviceProvisioned() { + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onDeviceProvisioned(); + } + } + if (mContentObserver != null) { + // We don't need the observer anymore... + mContext.getContentResolver().unregisterContentObserver(mContentObserver); + mContentObserver = null; + } + } + + /** + * Handle {@link #MSG_PHONE_STATE_CHANGED} + */ + protected void handlePhoneStateChanged(String newState) { + if (DEBUG) Log.d(TAG, "handlePhoneStateChanged(" + newState + ")"); + if (TelephonyManager.EXTRA_STATE_IDLE.equals(newState)) { + mPhoneState = TelephonyManager.CALL_STATE_IDLE; + } else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(newState)) { + mPhoneState = TelephonyManager.CALL_STATE_OFFHOOK; + } else if (TelephonyManager.EXTRA_STATE_RINGING.equals(newState)) { + mPhoneState = TelephonyManager.CALL_STATE_RINGING; + } + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onPhoneStateChanged(mPhoneState); + } + } + } + + /** + * Handle {@link #MSG_RINGER_MODE_CHANGED} + */ + protected void handleRingerModeChange(int mode) { + if (DEBUG) Log.d(TAG, "handleRingerModeChange(" + mode + ")"); + mRingMode = mode; + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onRingerModeChanged(mode); + } + } + } + + /** + * Handle {@link #MSG_TIME_UPDATE} + */ + private void handleTimeUpdate() { + if (DEBUG) Log.d(TAG, "handleTimeUpdate"); + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onTimeChanged(); + } + } + } + + /** + * Handle {@link #MSG_BATTERY_UPDATE} + */ + private void handleBatteryUpdate(BatteryStatus status) { + if (DEBUG) Log.d(TAG, "handleBatteryUpdate"); + final boolean batteryUpdateInteresting = isBatteryUpdateInteresting(mBatteryStatus, status); + mBatteryStatus = status; + if (batteryUpdateInteresting) { + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onRefreshBatteryInfo(status); + } + } + } + } + + /** + * Handle {@link #MSG_CARRIER_INFO_UPDATE} + */ + private void handleCarrierInfoUpdate() { + if (DEBUG) Log.d(TAG, "handleCarrierInfoUpdate: plmn = " + mTelephonyPlmn + + ", spn = " + mTelephonySpn); + + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn); + } + } + } + + /** + * Handle {@link #MSG_SIM_STATE_CHANGE} + */ + private void handleSimStateChange(SimArgs simArgs) { + final IccCardConstants.State state = simArgs.simState; + + if (DEBUG) { + Log.d(TAG, "handleSimStateChange: intentValue = " + simArgs + " " + + "state resolved to " + state.toString()); + } + + if (state != IccCardConstants.State.UNKNOWN && state != mSimState) { + mSimState = state; + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onSimStateChanged(state); + } + } + } + } + + /** + * Handle {@link #MSG_CLOCK_VISIBILITY_CHANGED} + */ + private void handleClockVisibilityChanged() { + if (DEBUG) Log.d(TAG, "handleClockVisibilityChanged()"); + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onClockVisibilityChanged(); + } + } + } + + private static boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) { + final boolean nowPluggedIn = current.isPluggedIn(); + final boolean wasPluggedIn = old.isPluggedIn(); + final boolean stateChangedWhilePluggedIn = + wasPluggedIn == true && nowPluggedIn == true + && (old.status != current.status); + + // change in plug state is always interesting + if (wasPluggedIn != nowPluggedIn || stateChangedWhilePluggedIn) { + return true; + } + + // change in battery level while plugged in + if (nowPluggedIn && old.level != current.level) { + return true; + } + + // change where battery needs charging + if (!nowPluggedIn && current.isBatteryLow() && current.level != old.level) { + return true; + } + return false; + } + + /** + * @param intent The intent with action {@link TelephonyIntents#SPN_STRINGS_UPDATED_ACTION} + * @return The string to use for the plmn, or null if it should not be shown. + */ + private CharSequence getTelephonyPlmnFrom(Intent intent) { + if (intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) { + final String plmn = intent.getStringExtra(TelephonyIntents.EXTRA_PLMN); + return (plmn != null) ? plmn : getDefaultPlmn(); + } + return null; + } + + /** + * @return The default plmn (no service) + */ + private CharSequence getDefaultPlmn() { + return mContext.getResources().getText(R.string.lockscreen_carrier_default); + } + + /** + * @param intent The intent with action {@link Telephony.Intents#SPN_STRINGS_UPDATED_ACTION} + * @return The string to use for the plmn, or null if it should not be shown. + */ + private CharSequence getTelephonySpnFrom(Intent intent) { + if (intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) { + final String spn = intent.getStringExtra(TelephonyIntents.EXTRA_SPN); + if (spn != null) { + return spn; + } + } + return null; + } + + /** + * Remove the given observer's callback. + * + * @param observer The observer to remove + */ + public void removeCallback(Object observer) { + mCallbacks.remove(observer); + } + + /** + * Register to receive notifications about general keyguard information + * (see {@link InfoCallback}. + * @param callback The callback. + */ + public void registerCallback(KeyguardUpdateMonitorCallback callback) { + if (!mCallbacks.contains(callback)) { + mCallbacks.add(new WeakReference<KeyguardUpdateMonitorCallback>(callback)); + // Notify listener of the current state + callback.onRefreshBatteryInfo(mBatteryStatus); + callback.onTimeChanged(); + callback.onRingerModeChanged(mRingMode); + callback.onPhoneStateChanged(mPhoneState); + callback.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn); + callback.onClockVisibilityChanged(); + callback.onSimStateChanged(mSimState); + } else { + if (DEBUG) Log.e(TAG, "Object tried to add another callback", + new Exception("Called by")); + } + + // Clean up any unused references + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + if (mCallbacks.get(i).get() == null) { + mCallbacks.remove(i); + } + } + } + + public void reportClockVisible(boolean visible) { + mClockVisible = visible; + mHandler.obtainMessage(MSG_CLOCK_VISIBILITY_CHANGED).sendToTarget(); + } + + public IccCardConstants.State getSimState() { + return mSimState; + } + + /** + * Report that the user successfully entered the SIM PIN or PUK/SIM PIN so we + * have the information earlier than waiting for the intent + * broadcast from the telephony code. + * + * NOTE: Because handleSimStateChange() invokes callbacks immediately without going + * through mHandler, this *must* be called from the UI thread. + */ + public void reportSimUnlocked() { + handleSimStateChange(new SimArgs(IccCardConstants.State.READY)); + } + + public CharSequence getTelephonyPlmn() { + return mTelephonyPlmn; + } + + public CharSequence getTelephonySpn() { + return mTelephonySpn; + } + + /** + * @return Whether the device is provisioned (whether they have gone through + * the setup wizard) + */ + public boolean isDeviceProvisioned() { + return mDeviceProvisioned; + } + + public int getFailedAttempts() { + return mFailedAttempts; + } + + public void clearFailedAttempts() { + mFailedAttempts = 0; + mFailedBiometricUnlockAttempts = 0; + } + + public void reportFailedAttempt() { + mFailedAttempts++; + } + + public boolean isClockVisible() { + return mClockVisible; + } + + public int getPhoneState() { + return mPhoneState; + } + + public void reportFailedBiometricUnlockAttempt() { + mFailedBiometricUnlockAttempts++; + } + + public boolean getMaxBiometricUnlockAttemptsReached() { + return mFailedBiometricUnlockAttempts >= FAILED_BIOMETRIC_UNLOCK_ATTEMPTS_BEFORE_BACKUP; + } + + public boolean isSimLocked() { + return isSimLocked(mSimState); + } + + public static boolean isSimLocked(IccCardConstants.State state) { + return state == IccCardConstants.State.PIN_REQUIRED + || state == IccCardConstants.State.PUK_REQUIRED + || state == IccCardConstants.State.PERM_DISABLED; + } + + public boolean isSimPinSecure() { + return isSimPinSecure(mSimState); + } + + public static boolean isSimPinSecure(IccCardConstants.State state) { + final IccCardConstants.State simState = state; + return (simState == IccCardConstants.State.PIN_REQUIRED + || simState == IccCardConstants.State.PUK_REQUIRED + || simState == IccCardConstants.State.PERM_DISABLED); + } +} diff --git a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitorCallback.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java index d791419..3d65e68 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitorCallback.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard; import android.app.admin.DevicePolicyManager; import android.media.AudioManager; -import com.android.internal.policy.impl.KeyguardUpdateMonitor.BatteryStatus; import com.android.internal.telephony.IccCardConstants; /** @@ -31,7 +30,7 @@ class KeyguardUpdateMonitorCallback { * * @param status current battery status */ - void onRefreshBatteryInfo(BatteryStatus status) { } + void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { } /** * Called once per minute or when the time changes. diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java new file mode 100644 index 0000000..ad5de0e --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java @@ -0,0 +1,254 @@ +/* + * 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.internal.policy.impl.keyguard; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.media.IAudioService; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.telephony.TelephonyManager; +import android.view.KeyEvent; +import android.widget.LinearLayout; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Slog; + +/** + * Base class for keyguard view. {@link #reset} is where you should + * reset the state of your view. Use the {@link KeyguardViewCallback} via + * {@link #getCallback()} to send information back (such as poking the wake lock, + * or finishing the keyguard). + * + * Handles intercepting of media keys that still work when the keyguard is + * showing. + */ +public abstract class KeyguardViewBase extends LinearLayout { + + private static final int BACKGROUND_COLOR = 0x70000000; + private AudioManager mAudioManager; + private TelephonyManager mTelephonyManager = null; + protected KeyguardViewMediator.ViewMediatorCallback mViewMediatorCallback; + + // Whether the volume keys should be handled by keyguard. If true, then + // they will be handled here for specific media types such as music, otherwise + // the audio service will bring up the volume dialog. + private static final boolean KEYGUARD_MANAGES_VOLUME = true; + + // This is a faster way to draw the background on devices without hardware acceleration + private static final Drawable mBackgroundDrawable = new Drawable() { + @Override + public void draw(Canvas canvas) { + canvas.drawColor(BACKGROUND_COLOR, PorterDuff.Mode.SRC); + } + + @Override + public void setAlpha(int alpha) { + } + + @Override + public void setColorFilter(ColorFilter cf) { + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + }; + + public KeyguardViewBase(Context context) { + this(context, null); + } + + public KeyguardViewBase(Context context, AttributeSet attrs) { + super(context, attrs); + resetBackground(); + } + + public void resetBackground() { + setBackground(mBackgroundDrawable); + } + + /** + * Called when you need to reset the state of your view. + */ + abstract public void reset(); + + /** + * Called when the screen turned off. + */ + abstract public void onScreenTurnedOff(); + + /** + * Called when the screen turned on. + */ + abstract public void onScreenTurnedOn(); + + /** + * Called when the view needs to be shown. + */ + abstract public void show(); + + /** + * Called when a key has woken the device to give us a chance to adjust our + * state according the the key. We are responsible for waking the device + * (by poking the wake lock) once we are ready. + * + * The 'Tq' suffix is per the documentation in {@link android.view.WindowManagerPolicy}. + * Be sure not to take any action that takes a long time; any significant + * action should be posted to a handler. + * + * @param keyCode The wake key, which may be relevant for configuring the + * keyguard. May be {@link KeyEvent#KEYCODE_UNKNOWN} if waking for a reason + * other than a key press. + */ + abstract public void wakeWhenReadyTq(int keyCode); + + /** + * Verify that the user can get past the keyguard securely. This is called, + * for example, when the phone disables the keyguard but then wants to launch + * something else that requires secure access. + * + * The result will be propogated back via {@link KeyguardViewCallback#keyguardDone(boolean)} + */ + abstract public void verifyUnlock(); + + /** + * Called before this view is being removed. + */ + abstract public void cleanUp(); + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (interceptMediaKey(event)) { + return true; + } + return super.dispatchKeyEvent(event); + } + + /** + * Allows the media keys to work when the keyguard is showing. + * The media keys should be of no interest to the actual keyguard view(s), + * so intercepting them here should not be of any harm. + * @param event The key event + * @return whether the event was consumed as a media key. + */ + private boolean interceptMediaKey(KeyEvent event) { + final int keyCode = event.getKeyCode(); + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (keyCode) { + case KeyEvent.KEYCODE_MEDIA_PLAY: + case KeyEvent.KEYCODE_MEDIA_PAUSE: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + /* Suppress PLAY/PAUSE toggle when phone is ringing or + * in-call to avoid music playback */ + if (mTelephonyManager == null) { + mTelephonyManager = (TelephonyManager) getContext().getSystemService( + Context.TELEPHONY_SERVICE); + } + if (mTelephonyManager != null && + mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) { + return true; // suppress key event + } + case KeyEvent.KEYCODE_MUTE: + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_MEDIA_STOP: + case KeyEvent.KEYCODE_MEDIA_NEXT: + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + case KeyEvent.KEYCODE_MEDIA_REWIND: + case KeyEvent.KEYCODE_MEDIA_RECORD: + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: { + handleMediaKeyEvent(event); + return true; + } + + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_VOLUME_MUTE: { + if (KEYGUARD_MANAGES_VOLUME) { + synchronized (this) { + if (mAudioManager == null) { + mAudioManager = (AudioManager) getContext().getSystemService( + Context.AUDIO_SERVICE); + } + } + // Volume buttons should only function for music (local or remote). + // TODO: Actually handle MUTE. + mAudioManager.adjustLocalOrRemoteStreamVolume( + AudioManager.STREAM_MUSIC, + keyCode == KeyEvent.KEYCODE_VOLUME_UP + ? AudioManager.ADJUST_RAISE + : AudioManager.ADJUST_LOWER); + // Don't execute default volume behavior + return true; + } else { + return false; + } + } + } + } else if (event.getAction() == KeyEvent.ACTION_UP) { + switch (keyCode) { + case KeyEvent.KEYCODE_MUTE: + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_MEDIA_PLAY: + case KeyEvent.KEYCODE_MEDIA_PAUSE: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_MEDIA_STOP: + case KeyEvent.KEYCODE_MEDIA_NEXT: + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + case KeyEvent.KEYCODE_MEDIA_REWIND: + case KeyEvent.KEYCODE_MEDIA_RECORD: + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: { + handleMediaKeyEvent(event); + return true; + } + } + } + return false; + } + + void handleMediaKeyEvent(KeyEvent keyEvent) { + IAudioService audioService = IAudioService.Stub.asInterface( + ServiceManager.checkService(Context.AUDIO_SERVICE)); + if (audioService != null) { + try { + audioService.dispatchMediaKeyEvent(keyEvent); + } catch (RemoteException e) { + Log.e("KeyguardViewBase", "dispatchMediaKeyEvent threw exception " + e); + } + } else { + Slog.w("KeyguardViewBase", "Unable to find IAudioService for media key event"); + } + } + + @Override + public void dispatchSystemUiVisibilityChanged(int visibility) { + super.dispatchSystemUiVisibilityChanged(visibility); + setSystemUiVisibility(STATUS_BAR_DISABLE_BACK); + } + + public void setViewMediatorCallback( + KeyguardViewMediator.ViewMediatorCallback viewMediatorCallback) { + mViewMediatorCallback = viewMediatorCallback; + } + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java new file mode 100644 index 0000000..61003bf --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java @@ -0,0 +1,318 @@ +/* + * 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.internal.policy.impl.keyguard; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.os.IBinder; +import android.os.SystemProperties; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewManager; +import android.view.WindowManager; +import android.widget.FrameLayout; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.R; + +/** + * Manages creating, showing, hiding and resetting the keyguard. Calls back + * via {@link com.android.internal.policy.impl.KeyguardViewCallback} to poke + * the wake lock and report that the keyguard is done, which is in turn, + * reported to this class by the current {@link KeyguardViewBase}. + */ +public class KeyguardViewManager { + private final static boolean DEBUG = false; + private static String TAG = "KeyguardViewManager"; + + private final Context mContext; + private final ViewManager mViewManager; + private final KeyguardViewMediator.ViewMediatorCallback mViewMediatorCallback; + + private WindowManager.LayoutParams mWindowLayoutParams; + private boolean mNeedsInput = false; + + private FrameLayout mKeyguardHost; + private KeyguardHostView mKeyguardView; + + private boolean mScreenOn = false; + private LockPatternUtils mLockPatternUtils; + + public interface ShowListener { + void onShown(IBinder windowToken); + }; + + /** + * @param context Used to create views. + * @param viewManager Keyguard will be attached to this. + * @param callback Used to notify of changes. + * @param lockPatternUtils + */ + public KeyguardViewManager(Context context, ViewManager viewManager, + KeyguardViewMediator.ViewMediatorCallback callback, + LockPatternUtils lockPatternUtils) { + mContext = context; + mViewManager = viewManager; + mViewMediatorCallback = callback; + mLockPatternUtils = lockPatternUtils; + } + + /** + * Show the keyguard. Will handle creating and attaching to the view manager + * lazily. + */ + public synchronized void show() { + if (DEBUG) Log.d(TAG, "show(); mKeyguardView==" + mKeyguardView); + + boolean enableScreenRotation = shouldEnableScreenRotation(); + + maybeCreateKeyguardLocked(enableScreenRotation); + maybeEnableScreenRotation(enableScreenRotation); + + // Disable aspects of the system/status/navigation bars that are not appropriate or + // useful for the lockscreen but can be re-shown by dialogs or SHOW_WHEN_LOCKED activities. + // Other disabled bits are handled by the KeyguardViewMediator talking directly to the + // status bar service. + int visFlags = View.STATUS_BAR_DISABLE_BACK | View.STATUS_BAR_DISABLE_HOME; + if (DEBUG) Log.v(TAG, "KGVM: Set visibility on " + mKeyguardHost + " to " + visFlags); + mKeyguardHost.setSystemUiVisibility(visFlags); + + mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); + mKeyguardHost.setVisibility(View.VISIBLE); + mKeyguardView.show(); + mKeyguardView.requestFocus(); + } + + private boolean shouldEnableScreenRotation() { + Resources res = mContext.getResources(); + return SystemProperties.getBoolean("lockscreen.rot_override",false) + || res.getBoolean(com.android.internal.R.bool.config_enableLockScreenRotation); + } + + class ViewManagerHost extends FrameLayout { + public ViewManagerHost(Context context) { + super(context); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + maybeCreateKeyguardLocked(shouldEnableScreenRotation()); + } + } + + private void maybeCreateKeyguardLocked(boolean enableScreenRotation) { + final boolean isActivity = (mContext instanceof Activity); // for test activity + + if (mKeyguardHost == null) { + if (DEBUG) Log.d(TAG, "keyguard host is null, creating it..."); + + mKeyguardHost = new ViewManagerHost(mContext); + + int flags = WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN + | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER + | WindowManager.LayoutParams.FLAG_SLIPPERY; + + if (!mNeedsInput) { + flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + } + if (ActivityManager.isHighEndGfx()) { + flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + } + + final int stretch = ViewGroup.LayoutParams.MATCH_PARENT; + final int type = isActivity ? WindowManager.LayoutParams.TYPE_APPLICATION + : WindowManager.LayoutParams.TYPE_KEYGUARD; + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + stretch, stretch, type, flags, PixelFormat.TRANSLUCENT); + lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + lp.windowAnimations = com.android.internal.R.style.Animation_LockScreen; + if (ActivityManager.isHighEndGfx()) { + lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + lp.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED; + } + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY; + lp.setTitle(isActivity ? "KeyguardMock" : "Keyguard"); + mWindowLayoutParams = lp; + mViewManager.addView(mKeyguardHost, lp); + } + inflateKeyguardView(); + mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); + } + + private void inflateKeyguardView() { + if (mKeyguardView != null) { + mKeyguardHost.removeView(mKeyguardView); + } + final LayoutInflater inflater = LayoutInflater.from(mContext); + View view = inflater.inflate(R.layout.keyguard_host_view, mKeyguardHost, true); + mKeyguardView = (KeyguardHostView) view.findViewById(R.id.keyguard_host_view); + mKeyguardView.setLockPatternUtils(mLockPatternUtils); + mKeyguardView.setViewMediatorCallback(mViewMediatorCallback); + + if (mScreenOn) { + mKeyguardView.show(); + } + } + + private void maybeEnableScreenRotation(boolean enableScreenRotation) { + // TODO: move this outside + if (enableScreenRotation) { + if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen On!"); + mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER; + } else { + if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen Off!"); + mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; + } + mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); + } + + public void setNeedsInput(boolean needsInput) { + mNeedsInput = needsInput; + if (mWindowLayoutParams != null) { + if (needsInput) { + mWindowLayoutParams.flags &= + ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + } else { + mWindowLayoutParams.flags |= + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + } + + try { + mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); + } catch (java.lang.IllegalArgumentException e) { + // TODO: Ensure this method isn't called on views that are changing... + Log.w(TAG,"Can't update input method on " + mKeyguardHost + " window not attached"); + } + } + } + + /** + * Reset the state of the view. + */ + public synchronized void reset() { + if (DEBUG) Log.d(TAG, "reset()"); + if (mKeyguardView != null) { + mKeyguardView.reset(); + } + } + + public synchronized void onScreenTurnedOff() { + if (DEBUG) Log.d(TAG, "onScreenTurnedOff()"); + mScreenOn = false; + if (mKeyguardView != null) { + mKeyguardView.onScreenTurnedOff(); + } + } + + public synchronized void onScreenTurnedOn( + final KeyguardViewManager.ShowListener showListener) { + if (DEBUG) Log.d(TAG, "onScreenTurnedOn()"); + mScreenOn = true; + if (mKeyguardView != null) { + mKeyguardView.onScreenTurnedOn(); + + // Caller should wait for this window to be shown before turning + // on the screen. + if (mKeyguardHost.getVisibility() == View.VISIBLE) { + // Keyguard may be in the process of being shown, but not yet + // updated with the window manager... give it a chance to do so. + mKeyguardHost.post(new Runnable() { + public void run() { + if (mKeyguardHost.getVisibility() == View.VISIBLE) { + showListener.onShown(mKeyguardHost.getWindowToken()); + } else { + showListener.onShown(null); + } + } + }); + } else { + showListener.onShown(null); + } + } else { + showListener.onShown(null); + } + } + + public synchronized void verifyUnlock() { + if (DEBUG) Log.d(TAG, "verifyUnlock()"); + show(); + mKeyguardView.verifyUnlock(); + } + + /** + * A key has woken the device. We use this to potentially adjust the state + * of the lock screen based on the key. + * + * The 'Tq' suffix is per the documentation in {@link android.view.WindowManagerPolicy}. + * Be sure not to take any action that takes a long time; any significant + * action should be posted to a handler. + * + * @param keyCode The wake key. May be {@link KeyEvent#KEYCODE_UNKNOWN} if waking + * for a reason other than a key press. + */ + public boolean wakeWhenReadyTq(int keyCode) { + if (DEBUG) Log.d(TAG, "wakeWhenReady(" + keyCode + ")"); + if (mKeyguardView != null) { + mKeyguardView.wakeWhenReadyTq(keyCode); + return true; + } else { + Log.w(TAG, "mKeyguardView is null in wakeWhenReadyTq"); + return false; + } + } + + /** + * Hides the keyguard view + */ + public synchronized void hide() { + if (DEBUG) Log.d(TAG, "hide()"); + + if (mKeyguardHost != null) { + mKeyguardHost.setVisibility(View.GONE); + // Don't do this right away, so we can let the view continue to animate + // as it goes away. + if (mKeyguardView != null) { + final KeyguardViewBase lastView = mKeyguardView; + mKeyguardView = null; + mKeyguardHost.postDelayed(new Runnable() { + public void run() { + synchronized (KeyguardViewManager.this) { + lastView.cleanUp(); + mKeyguardHost.removeView(lastView); + } + } + }, 500); + } + } + } + + /** + * @return Whether the keyguard is showing + */ + public synchronized boolean isShowing() { + return (mKeyguardHost != null && mKeyguardHost.getVisibility() == View.VISIBLE); + } +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java new file mode 100644 index 0000000..d6733ea --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java @@ -0,0 +1,1342 @@ +/* + * 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.internal.policy.impl.keyguard; + +import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; + +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.widget.LockPatternUtils; + +import android.app.Activity; +import android.app.ActivityManagerNative; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.StatusBarManager; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.media.SoundPool; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.util.EventLog; +import android.util.Log; +import android.view.KeyEvent; +import android.view.WindowManager; +import android.view.WindowManagerPolicy; + + +/** + * Mediates requests related to the keyguard. This includes queries about the + * state of the keyguard, power management events that effect whether the keyguard + * should be shown or reset, callbacks to the phone window manager to notify + * it of when the keyguard is showing, and events from the keyguard view itself + * stating that the keyguard was succesfully unlocked. + * + * Note that the keyguard view is shown when the screen is off (as appropriate) + * so that once the screen comes on, it will be ready immediately. + * + * Example queries about the keyguard: + * - is {movement, key} one that should wake the keygaurd? + * - is the keyguard showing? + * - are input events restricted due to the state of the keyguard? + * + * Callbacks to the phone window manager: + * - the keyguard is showing + * + * Example external events that translate to keyguard view changes: + * - screen turned off -> reset the keyguard, and show it so it will be ready + * next time the screen turns on + * - keyboard is slid open -> if the keyguard is not secure, hide it + * + * Events from the keyguard view: + * - user succesfully unlocked keyguard -> hide keyguard view, and no longer + * restrict input events. + * + * Note: in addition to normal power managment events that effect the state of + * whether the keyguard should be showing, external apps and services may request + * that the keyguard be disabled via {@link #setKeyguardEnabled(boolean)}. When + * false, this will override all other conditions for turning on the keyguard. + * + * Threading and synchronization: + * This class is created by the initialization routine of the {@link WindowManagerPolicy}, + * and runs on its thread. The keyguard UI is created from that thread in the + * constructor of this class. The apis may be called from other threads, including the + * {@link com.android.server.input.InputManagerService}'s and {@link android.view.WindowManager}'s. + * Therefore, methods on this class are synchronized, and any action that is pointed + * directly to the keyguard UI is posted to a {@link Handler} to ensure it is taken on the UI + * thread of the keyguard. + */ +public class KeyguardViewMediator { + private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000; + private final static boolean DEBUG = false; + private final static boolean DBG_WAKE = false; + + private final static String TAG = "KeyguardViewMediator"; + + private static final String DELAYED_KEYGUARD_ACTION = + "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD"; + + // used for handler messages + private static final int TIMEOUT = 1; + private static final int SHOW = 2; + private static final int HIDE = 3; + private static final int RESET = 4; + private static final int VERIFY_UNLOCK = 5; + private static final int NOTIFY_SCREEN_OFF = 6; + private static final int NOTIFY_SCREEN_ON = 7; + private static final int WAKE_WHEN_READY = 8; + private static final int KEYGUARD_DONE = 9; + private static final int KEYGUARD_DONE_DRAWING = 10; + private static final int KEYGUARD_DONE_AUTHENTICATING = 11; + private static final int SET_HIDDEN = 12; + private static final int KEYGUARD_TIMEOUT = 13; + + /** + * The default amount of time we stay awake (used for all key input) + */ + protected static final int AWAKE_INTERVAL_DEFAULT_MS = 10000; + + /** + * How long to wait after the screen turns off due to timeout before + * turning on the keyguard (i.e, the user has this much time to turn + * the screen back on without having to face the keyguard). + */ + private static final int KEYGUARD_LOCK_AFTER_DELAY_DEFAULT = 5000; + + /** + * How long we'll wait for the {@link KeyguardViewCallback#keyguardDoneDrawing()} + * callback before unblocking a call to {@link #setKeyguardEnabled(boolean)} + * that is reenabling the keyguard. + */ + private static final int KEYGUARD_DONE_DRAWING_TIMEOUT_MS = 2000; + + /** + * Allow the user to expand the status bar when the keyguard is engaged + * (without a pattern or password). + */ + private static final boolean ENABLE_INSECURE_STATUS_BAR_EXPAND = true; + + /** The stream type that the lock sounds are tied to. */ + private int mMasterStreamType; + + private Context mContext; + private AlarmManager mAlarmManager; + private AudioManager mAudioManager; + private StatusBarManager mStatusBarManager; + private boolean mShowLockIcon; + private boolean mShowingLockIcon; + + private boolean mSystemReady; + + // Whether the next call to playSounds() should be skipped. Defaults to + // true because the first lock (on boot) should be silent. + private boolean mSuppressNextLockSound = true; + + + /** High level access to the power manager for WakeLocks */ + private PowerManager mPM; + + /** + * Used to keep the device awake while the keyguard is showing, i.e for + * calls to {@link #pokeWakelock()} + */ + private PowerManager.WakeLock mWakeLock; + + /** + * Used to keep the device awake while to ensure the keyguard finishes opening before + * we sleep. + */ + private PowerManager.WakeLock mShowKeyguardWakeLock; + + /** + * Does not turn on screen, held while a call to {@link KeyguardViewManager#wakeWhenReadyTq(int)} + * is called to make sure the device doesn't sleep before it has a chance to poke + * the wake lock. + * @see #wakeWhenReadyLocked(int) + */ + private PowerManager.WakeLock mWakeAndHandOff; + + private KeyguardViewManager mKeyguardViewManager; + + // these are protected by synchronized (this) + + /** + * External apps (like the phone app) can tell us to disable the keygaurd. + */ + private boolean mExternallyEnabled = true; + + /** + * Remember if an external call to {@link #setKeyguardEnabled} with value + * false caused us to hide the keyguard, so that we need to reshow it once + * the keygaurd is reenabled with another call with value true. + */ + private boolean mNeedToReshowWhenReenabled = false; + + // cached value of whether we are showing (need to know this to quickly + // answer whether the input should be restricted) + private boolean mShowing = false; + + // true if the keyguard is hidden by another window + private boolean mHidden = false; + + /** + * Helps remember whether the screen has turned on since the last time + * it turned off due to timeout. see {@link #onScreenTurnedOff(int)} + */ + private int mDelayedShowingSequence; + + private int mWakelockSequence; + + /** + * If the user has disabled the keyguard, then requests to exit, this is + * how we'll ultimately let them know whether it was successful. We use this + * var being non-null as an indicator that there is an in progress request. + */ + private WindowManagerPolicy.OnKeyguardExitResult mExitSecureCallback; + + // the properties of the keyguard + + private KeyguardUpdateMonitor mUpdateMonitor; + + private boolean mScreenOn; + + // last known state of the cellular connection + private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE; + + /** + * we send this intent when the keyguard is dismissed. + */ + private Intent mUserPresentIntent; + + /** + * {@link #setKeyguardEnabled} waits on this condition when it reenables + * the keyguard. + */ + private boolean mWaitingUntilKeyguardVisible = false; + private LockPatternUtils mLockPatternUtils; + + private SoundPool mLockSounds; + private int mLockSoundId; + private int mUnlockSoundId; + private int mLockSoundStreamId; + + /** + * The volume applied to the lock/unlock sounds. + */ + private final float mLockSoundVolume; + + /** + * The callback used by the keyguard view to tell the {@link KeyguardViewMediator} + * various things. + */ + public interface ViewMediatorCallback { + + /** + * Request the wakelock to be poked for the default amount of time. + */ + void pokeWakelock(); + + /** + * Request the wakelock to be poked for a specific amount of time. + * @param millis The amount of time in millis. + */ + void pokeWakelock(long millis); + + /** + * Report that the keyguard is done. + * @param authenticated Whether the user securely got past the keyguard. + * the only reason for this to be false is if the keyguard was instructed + * to appear temporarily to verify the user is supposed to get past the + * keyguard, and the user fails to do so. + */ + void keyguardDone(boolean authenticated); + + /** + * Report that the keyguard is done drawing. + */ + void keyguardDoneDrawing(); + + /** + * Tell ViewMediator that the current view needs IME input + * @param needsInput + */ + void setNeedsInput(boolean needsInput); + } + + KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { + + @Override + public void onUserSwitched(int userId) { + mLockPatternUtils.setCurrentUser(userId); + synchronized (KeyguardViewMediator.this) { + resetStateLocked(); + } + } + + @Override + public void onUserRemoved(int userId) { + mLockPatternUtils.removeUser(userId); + } + + @Override + void onPhoneStateChanged(int phoneState) { + synchronized (KeyguardViewMediator.this) { + if (TelephonyManager.CALL_STATE_IDLE == phoneState // call ending + && !mScreenOn // screen off + && mExternallyEnabled) { // not disabled by any app + + // note: this is a way to gracefully reenable the keyguard when the call + // ends and the screen is off without always reenabling the keyguard + // each time the screen turns off while in call (and having an occasional ugly + // flicker while turning back on the screen and disabling the keyguard again). + if (DEBUG) Log.d(TAG, "screen is off and call ended, let's make sure the " + + "keyguard is showing"); + doKeyguardLocked(); + } + } + }; + + @Override + public void onClockVisibilityChanged() { + adjustStatusBarLocked(); + } + + @Override + public void onDeviceProvisioned() { + mContext.sendBroadcast(mUserPresentIntent); + } + + @Override + public void onSimStateChanged(IccCardConstants.State simState) { + if (DEBUG) Log.d(TAG, "onSimStateChanged: " + simState); + + switch (simState) { + case NOT_READY: + case ABSENT: + // only force lock screen in case of missing sim if user hasn't + // gone through setup wizard + synchronized (this) { + if (!mUpdateMonitor.isDeviceProvisioned()) { + if (!isShowing()) { + if (DEBUG) Log.d(TAG, "ICC_ABSENT isn't showing," + + " we need to show the keyguard since the " + + "device isn't provisioned yet."); + doKeyguardLocked(); + } else { + resetStateLocked(); + } + } + } + break; + case PIN_REQUIRED: + case PUK_REQUIRED: + synchronized (this) { + if (!isShowing()) { + if (DEBUG) Log.d(TAG, "INTENT_VALUE_ICC_LOCKED and keygaurd isn't " + + "showing; need to show keyguard so user can enter sim pin"); + doKeyguardLocked(); + } else { + resetStateLocked(); + } + } + break; + case PERM_DISABLED: + synchronized (this) { + if (!isShowing()) { + if (DEBUG) Log.d(TAG, "PERM_DISABLED and " + + "keygaurd isn't showing."); + doKeyguardLocked(); + } else { + if (DEBUG) Log.d(TAG, "PERM_DISABLED, resetStateLocked to" + + "show permanently disabled message in lockscreen."); + resetStateLocked(); + } + } + break; + case READY: + synchronized (this) { + if (isShowing()) { + resetStateLocked(); + } + } + break; + } + } + + }; + + ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() { + public void pokeWakelock() { + KeyguardViewMediator.this.pokeWakelock(); + } + + public void pokeWakelock(long holdMs) { + KeyguardViewMediator.this.pokeWakelock(holdMs); + } + + public void keyguardDone(boolean authenticated) { + KeyguardViewMediator.this.keyguardDone(authenticated, true); + } + + public void keyguardDoneDrawing() { + mHandler.sendEmptyMessage(KEYGUARD_DONE_DRAWING); + } + + @Override + public void setNeedsInput(boolean needsInput) { + mKeyguardViewManager.setNeedsInput(needsInput); + } + }; + + public void pokeWakelock() { + pokeWakelock(AWAKE_INTERVAL_DEFAULT_MS); + } + + public void pokeWakelock(long holdMs) { + synchronized (this) { + if (DBG_WAKE) Log.d(TAG, "pokeWakelock(" + holdMs + ")"); + mWakeLock.acquire(); + mHandler.removeMessages(TIMEOUT); + mWakelockSequence++; + Message msg = mHandler.obtainMessage(TIMEOUT, mWakelockSequence, 0); + mHandler.sendMessageDelayed(msg, holdMs); + } + } + + /** + * Construct a KeyguardViewMediator + * @param context + * @param lockPatternUtils optional mock interface for LockPatternUtils + */ + public KeyguardViewMediator(Context context, LockPatternUtils lockPatternUtils) { + mContext = context; + mPM = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + mWakeLock = mPM.newWakeLock( + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "keyguard"); + mWakeLock.setReferenceCounted(false); + mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard"); + mShowKeyguardWakeLock.setReferenceCounted(false); + + mWakeAndHandOff = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "keyguardWakeAndHandOff"); + mWakeAndHandOff.setReferenceCounted(false); + + mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(DELAYED_KEYGUARD_ACTION)); + + mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + + mUpdateMonitor = KeyguardUpdateMonitor.getInstance(context); + + mLockPatternUtils = lockPatternUtils != null + ? lockPatternUtils : new LockPatternUtils(mContext); + + WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); + + mKeyguardViewManager = new KeyguardViewManager(context, wm, mViewMediatorCallback, + mLockPatternUtils); + + mUserPresentIntent = new Intent(Intent.ACTION_USER_PRESENT); + mUserPresentIntent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING + | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + + final ContentResolver cr = mContext.getContentResolver(); + mShowLockIcon = (Settings.System.getInt(cr, "show_status_bar_lock", 0) == 1); + + mScreenOn = mPM.isScreenOn(); + + mLockSounds = new SoundPool(1, AudioManager.STREAM_SYSTEM, 0); + String soundPath = Settings.System.getString(cr, Settings.System.LOCK_SOUND); + if (soundPath != null) { + mLockSoundId = mLockSounds.load(soundPath, 1); + } + if (soundPath == null || mLockSoundId == 0) { + if (DEBUG) Log.d(TAG, "failed to load sound from " + soundPath); + } + soundPath = Settings.System.getString(cr, Settings.System.UNLOCK_SOUND); + if (soundPath != null) { + mUnlockSoundId = mLockSounds.load(soundPath, 1); + } + if (soundPath == null || mUnlockSoundId == 0) { + if (DEBUG) Log.d(TAG, "failed to load sound from " + soundPath); + } + int lockSoundDefaultAttenuation = context.getResources().getInteger( + com.android.internal.R.integer.config_lockSoundVolumeDb); + mLockSoundVolume = (float)Math.pow(10, (float)lockSoundDefaultAttenuation/20); + } + + /** + * Let us know that the system is ready after startup. + */ + public void onSystemReady() { + synchronized (this) { + if (DEBUG) Log.d(TAG, "onSystemReady"); + mSystemReady = true; + mUpdateMonitor.registerCallback(mUpdateCallback); + doKeyguardLocked(); + } + } + + /** + * Called to let us know the screen was turned off. + * @param why either {@link WindowManagerPolicy#OFF_BECAUSE_OF_USER}, + * {@link WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT} or + * {@link WindowManagerPolicy#OFF_BECAUSE_OF_PROX_SENSOR}. + */ + public void onScreenTurnedOff(int why) { + synchronized (this) { + mScreenOn = false; + if (DEBUG) Log.d(TAG, "onScreenTurnedOff(" + why + ")"); + + // Lock immediately based on setting if secure (user has a pin/pattern/password). + // This also "locks" the device when not secure to provide easy access to the + // camera while preventing unwanted input. + final boolean lockImmediately = + mLockPatternUtils.getPowerButtonInstantlyLocks() || !mLockPatternUtils.isSecure(); + + if (mExitSecureCallback != null) { + if (DEBUG) Log.d(TAG, "pending exit secure callback cancelled"); + mExitSecureCallback.onKeyguardExitResult(false); + mExitSecureCallback = null; + if (!mExternallyEnabled) { + hideLocked(); + } + } else if (mShowing) { + notifyScreenOffLocked(); + resetStateLocked(); + } else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT + || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) { + // if the screen turned off because of timeout or the user hit the power button + // and we don't need to lock immediately, set an alarm + // to enable it a little bit later (i.e, give the user a chance + // to turn the screen back on within a certain window without + // having to unlock the screen) + final ContentResolver cr = mContext.getContentResolver(); + + // From DisplaySettings + long displayTimeout = Settings.System.getInt(cr, SCREEN_OFF_TIMEOUT, + KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT); + + // From SecuritySettings + final long lockAfterTimeout = Settings.Secure.getInt(cr, + Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, + KEYGUARD_LOCK_AFTER_DELAY_DEFAULT); + + // From DevicePolicyAdmin + final long policyTimeout = mLockPatternUtils.getDevicePolicyManager() + .getMaximumTimeToLock(null); + + long timeout; + if (policyTimeout > 0) { + // policy in effect. Make sure we don't go beyond policy limit. + displayTimeout = Math.max(displayTimeout, 0); // ignore negative values + timeout = Math.min(policyTimeout - displayTimeout, lockAfterTimeout); + } else { + timeout = lockAfterTimeout; + } + + if (timeout <= 0) { + // Lock now + mSuppressNextLockSound = true; + doKeyguardLocked(); + } else { + // Lock in the future + long when = SystemClock.elapsedRealtime() + timeout; + Intent intent = new Intent(DELAYED_KEYGUARD_ACTION); + intent.putExtra("seq", mDelayedShowingSequence); + PendingIntent sender = PendingIntent.getBroadcast(mContext, + 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, when, sender); + if (DEBUG) Log.d(TAG, "setting alarm to turn off keyguard, seq = " + + mDelayedShowingSequence); + } + } else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR) { + // Do not enable the keyguard if the prox sensor forced the screen off. + } else { + doKeyguardLocked(); + } + } + } + + /** + * Let's us know the screen was turned on. + */ + public void onScreenTurnedOn(KeyguardViewManager.ShowListener showListener) { + synchronized (this) { + mScreenOn = true; + mDelayedShowingSequence++; + if (DEBUG) Log.d(TAG, "onScreenTurnedOn, seq = " + mDelayedShowingSequence); + if (showListener != null) { + notifyScreenOnLocked(showListener); + } + } + } + + /** + * Same semantics as {@link WindowManagerPolicy#enableKeyguard}; provide + * a way for external stuff to override normal keyguard behavior. For instance + * the phone app disables the keyguard when it receives incoming calls. + */ + public void setKeyguardEnabled(boolean enabled) { + synchronized (this) { + if (DEBUG) Log.d(TAG, "setKeyguardEnabled(" + enabled + ")"); + + mExternallyEnabled = enabled; + + if (!enabled && mShowing) { + if (mExitSecureCallback != null) { + if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring"); + // we're in the process of handling a request to verify the user + // can get past the keyguard. ignore extraneous requests to disable / reenable + return; + } + + // hiding keyguard that is showing, remember to reshow later + if (DEBUG) Log.d(TAG, "remembering to reshow, hiding keyguard, " + + "disabling status bar expansion"); + mNeedToReshowWhenReenabled = true; + hideLocked(); + } else if (enabled && mNeedToReshowWhenReenabled) { + // reenabled after previously hidden, reshow + if (DEBUG) Log.d(TAG, "previously hidden, reshowing, reenabling " + + "status bar expansion"); + mNeedToReshowWhenReenabled = false; + + if (mExitSecureCallback != null) { + if (DEBUG) Log.d(TAG, "onKeyguardExitResult(false), resetting"); + mExitSecureCallback.onKeyguardExitResult(false); + mExitSecureCallback = null; + resetStateLocked(); + } else { + showLocked(); + + // block until we know the keygaurd is done drawing (and post a message + // to unblock us after a timeout so we don't risk blocking too long + // and causing an ANR). + mWaitingUntilKeyguardVisible = true; + mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS); + if (DEBUG) Log.d(TAG, "waiting until mWaitingUntilKeyguardVisible is false"); + while (mWaitingUntilKeyguardVisible) { + try { + wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + if (DEBUG) Log.d(TAG, "done waiting for mWaitingUntilKeyguardVisible"); + } + } + } + } + + /** + * @see android.app.KeyguardManager#exitKeyguardSecurely + */ + public void verifyUnlock(WindowManagerPolicy.OnKeyguardExitResult callback) { + synchronized (this) { + if (DEBUG) Log.d(TAG, "verifyUnlock"); + if (!mUpdateMonitor.isDeviceProvisioned()) { + // don't allow this api when the device isn't provisioned + if (DEBUG) Log.d(TAG, "ignoring because device isn't provisioned"); + callback.onKeyguardExitResult(false); + } else if (mExternallyEnabled) { + // this only applies when the user has externally disabled the + // keyguard. this is unexpected and means the user is not + // using the api properly. + Log.w(TAG, "verifyUnlock called when not externally disabled"); + callback.onKeyguardExitResult(false); + } else if (mExitSecureCallback != null) { + // already in progress with someone else + callback.onKeyguardExitResult(false); + } else { + mExitSecureCallback = callback; + verifyUnlockLocked(); + } + } + } + + /** + * Is the keyguard currently showing? + */ + public boolean isShowing() { + return mShowing; + } + + /** + * Is the keyguard currently showing and not being force hidden? + */ + public boolean isShowingAndNotHidden() { + return mShowing && !mHidden; + } + + /** + * Notify us when the keyguard is hidden by another window + */ + public void setHidden(boolean isHidden) { + if (DEBUG) Log.d(TAG, "setHidden " + isHidden); + mHandler.removeMessages(SET_HIDDEN); + Message msg = mHandler.obtainMessage(SET_HIDDEN, (isHidden ? 1 : 0), 0); + mHandler.sendMessage(msg); + } + + /** + * Handles SET_HIDDEN message sent by setHidden() + */ + private void handleSetHidden(boolean isHidden) { + synchronized (KeyguardViewMediator.this) { + if (mHidden != isHidden) { + mHidden = isHidden; + updateActivityLockScreenState(); + adjustUserActivityLocked(); + adjustStatusBarLocked(); + } + } + } + + /** + * Used by PhoneWindowManager to enable the keyguard due to a user activity timeout. + * This must be safe to call from any thread and with any window manager locks held. + */ + public void doKeyguardTimeout() { + mHandler.removeMessages(KEYGUARD_TIMEOUT); + Message msg = mHandler.obtainMessage(KEYGUARD_TIMEOUT); + mHandler.sendMessage(msg); + } + + /** + * Given the state of the keyguard, is the input restricted? + * Input is restricted when the keyguard is showing, or when the keyguard + * was suppressed by an app that disabled the keyguard or we haven't been provisioned yet. + */ + public boolean isInputRestricted() { + return mShowing || mNeedToReshowWhenReenabled || !mUpdateMonitor.isDeviceProvisioned(); + } + + /** + * Enable the keyguard if the settings are appropriate. Return true if all + * work that will happen is done; returns false if the caller can wait for + * the keyguard to be shown. + */ + private void doKeyguardLocked() { + // if another app is disabling us, don't show + if (!mExternallyEnabled) { + if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); + + // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes + // for an occasional ugly flicker in this situation: + // 1) receive a call with the screen on (no keyguard) or make a call + // 2) screen times out + // 3) user hits key to turn screen back on + // instead, we reenable the keyguard when we know the screen is off and the call + // ends (see the broadcast receiver below) + // TODO: clean this up when we have better support at the window manager level + // for apps that wish to be on top of the keyguard + return; + } + + // if the keyguard is already showing, don't bother + if (mKeyguardViewManager.isShowing()) { + if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing"); + return; + } + + // if the setup wizard hasn't run yet, don't show + final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", + false); + final boolean provisioned = mUpdateMonitor.isDeviceProvisioned(); + final IccCardConstants.State state = mUpdateMonitor.getSimState(); + final boolean lockedOrMissing = state.isPinLocked() + || ((state == IccCardConstants.State.ABSENT + || state == IccCardConstants.State.PERM_DISABLED) + && requireSim); + + if (!lockedOrMissing && !provisioned) { + if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned" + + " and the sim is not locked or missing"); + return; + } + + if (mLockPatternUtils.isLockScreenDisabled() && !lockedOrMissing) { + if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off"); + return; + } + + if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen"); + showLocked(); + } + + /** + * Send message to keyguard telling it to reset its state. + * @see #handleReset() + */ + private void resetStateLocked() { + if (DEBUG) Log.d(TAG, "resetStateLocked"); + Message msg = mHandler.obtainMessage(RESET); + mHandler.sendMessage(msg); + } + + /** + * Send message to keyguard telling it to verify unlock + * @see #handleVerifyUnlock() + */ + private void verifyUnlockLocked() { + if (DEBUG) Log.d(TAG, "verifyUnlockLocked"); + mHandler.sendEmptyMessage(VERIFY_UNLOCK); + } + + + /** + * Send a message to keyguard telling it the screen just turned on. + * @see #onScreenTurnedOff(int) + * @see #handleNotifyScreenOff + */ + private void notifyScreenOffLocked() { + if (DEBUG) Log.d(TAG, "notifyScreenOffLocked"); + mHandler.sendEmptyMessage(NOTIFY_SCREEN_OFF); + } + + /** + * Send a message to keyguard telling it the screen just turned on. + * @see #onScreenTurnedOn() + * @see #handleNotifyScreenOn + */ + private void notifyScreenOnLocked(KeyguardViewManager.ShowListener showListener) { + if (DEBUG) Log.d(TAG, "notifyScreenOnLocked"); + Message msg = mHandler.obtainMessage(NOTIFY_SCREEN_ON, showListener); + mHandler.sendMessage(msg); + } + + /** + * Send message to keyguard telling it about a wake key so it can adjust + * its state accordingly and then poke the wake lock when it is ready. + * @param keyCode The wake key. + * @see #handleWakeWhenReady + * @see #onWakeKeyWhenKeyguardShowingTq(int) + */ + private void wakeWhenReadyLocked(int keyCode) { + if (DBG_WAKE) Log.d(TAG, "wakeWhenReadyLocked(" + keyCode + ")"); + + /** + * acquire the handoff lock that will keep the cpu running. this will + * be released once the keyguard has set itself up and poked the other wakelock + * in {@link #handleWakeWhenReady(int)} + */ + mWakeAndHandOff.acquire(); + + Message msg = mHandler.obtainMessage(WAKE_WHEN_READY, keyCode, 0); + mHandler.sendMessage(msg); + } + + /** + * Send message to keyguard telling it to show itself + * @see #handleShow() + */ + private void showLocked() { + if (DEBUG) Log.d(TAG, "showLocked"); + // ensure we stay awake until we are finished displaying the keyguard + mShowKeyguardWakeLock.acquire(); + Message msg = mHandler.obtainMessage(SHOW); + mHandler.sendMessage(msg); + } + + /** + * Send message to keyguard telling it to hide itself + * @see #handleHide() + */ + private void hideLocked() { + if (DEBUG) Log.d(TAG, "hideLocked"); + Message msg = mHandler.obtainMessage(HIDE); + mHandler.sendMessage(msg); + } + + public boolean isSecure() { + return mLockPatternUtils.isSecure() + || KeyguardUpdateMonitor.getInstance(mContext).isSimPinSecure(); + } + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DELAYED_KEYGUARD_ACTION.equals(intent.getAction())) { + final int sequence = intent.getIntExtra("seq", 0); + if (DEBUG) Log.d(TAG, "received DELAYED_KEYGUARD_ACTION with seq = " + + sequence + ", mDelayedShowingSequence = " + mDelayedShowingSequence); + synchronized (KeyguardViewMediator.this) { + if (mDelayedShowingSequence == sequence) { + // Don't play lockscreen SFX if the screen went off due to timeout. + mSuppressNextLockSound = true; + doKeyguardLocked(); + } + } + } + } + }; + + /** + * When a key is received when the screen is off and the keyguard is showing, + * we need to decide whether to actually turn on the screen, and if so, tell + * the keyguard to prepare itself and poke the wake lock when it is ready. + * + * The 'Tq' suffix is per the documentation in {@link WindowManagerPolicy}. + * Be sure not to take any action that takes a long time; any significant + * action should be posted to a handler. + * + * @param keyCode The keycode of the key that woke the device + * @param isDocked True if the device is in the dock + * @return Whether we poked the wake lock (and turned the screen on) + */ + public boolean onWakeKeyWhenKeyguardShowingTq(int keyCode, boolean isDocked) { + if (DEBUG) Log.d(TAG, "onWakeKeyWhenKeyguardShowing(" + keyCode + ")"); + + if (isWakeKeyWhenKeyguardShowing(keyCode, isDocked)) { + // give the keyguard view manager a chance to adjust the state of the + // keyguard based on the key that woke the device before poking + // the wake lock + wakeWhenReadyLocked(keyCode); + return true; + } else { + return false; + } + } + + /** + * When the keyguard is showing we ignore some keys that might otherwise typically + * be considered wake keys. We filter them out here. + * + * {@link KeyEvent#KEYCODE_POWER} is notably absent from this list because it + * is always considered a wake key. + */ + private boolean isWakeKeyWhenKeyguardShowing(int keyCode, boolean isDocked) { + switch (keyCode) { + // ignore volume keys unless docked + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_VOLUME_MUTE: + return isDocked; + + // ignore media and camera keys + case KeyEvent.KEYCODE_MUTE: + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_MEDIA_PLAY: + case KeyEvent.KEYCODE_MEDIA_PAUSE: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_MEDIA_STOP: + case KeyEvent.KEYCODE_MEDIA_NEXT: + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + case KeyEvent.KEYCODE_MEDIA_REWIND: + case KeyEvent.KEYCODE_MEDIA_RECORD: + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: + case KeyEvent.KEYCODE_CAMERA: + return false; + } + return true; + } + + /** + * When a wake motion such as an external mouse movement is received when the screen + * is off and the keyguard is showing, we need to decide whether to actually turn + * on the screen, and if so, tell the keyguard to prepare itself and poke the wake + * lock when it is ready. + * + * The 'Tq' suffix is per the documentation in {@link WindowManagerPolicy}. + * Be sure not to take any action that takes a long time; any significant + * action should be posted to a handler. + * + * @return Whether we poked the wake lock (and turned the screen on) + */ + public boolean onWakeMotionWhenKeyguardShowingTq() { + if (DEBUG) Log.d(TAG, "onWakeMotionWhenKeyguardShowing()"); + + // give the keyguard view manager a chance to adjust the state of the + // keyguard based on the key that woke the device before poking + // the wake lock + wakeWhenReadyLocked(KeyEvent.KEYCODE_UNKNOWN); + return true; + } + + public void keyguardDone(boolean authenticated, boolean wakeup) { + synchronized (this) { + EventLog.writeEvent(70000, 2); + if (DEBUG) Log.d(TAG, "keyguardDone(" + authenticated + ")"); + Message msg = mHandler.obtainMessage(KEYGUARD_DONE); + msg.arg1 = wakeup ? 1 : 0; + mHandler.sendMessage(msg); + + if (authenticated) { + mUpdateMonitor.clearFailedAttempts(); + } + + if (mExitSecureCallback != null) { + mExitSecureCallback.onKeyguardExitResult(authenticated); + mExitSecureCallback = null; + + if (authenticated) { + // after succesfully exiting securely, no need to reshow + // the keyguard when they've released the lock + mExternallyEnabled = true; + mNeedToReshowWhenReenabled = false; + } + } + } + } + + /** + * This handler will be associated with the policy thread, which will also + * be the UI thread of the keyguard. Since the apis of the policy, and therefore + * this class, can be called by other threads, any action that directly + * interacts with the keyguard ui should be posted to this handler, rather + * than called directly. + */ + private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case TIMEOUT: + handleTimeout(msg.arg1); + return ; + case SHOW: + handleShow(); + return ; + case HIDE: + handleHide(); + return ; + case RESET: + handleReset(); + return ; + case VERIFY_UNLOCK: + handleVerifyUnlock(); + return; + case NOTIFY_SCREEN_OFF: + handleNotifyScreenOff(); + return; + case NOTIFY_SCREEN_ON: + handleNotifyScreenOn((KeyguardViewManager.ShowListener)msg.obj); + return; + case WAKE_WHEN_READY: + handleWakeWhenReady(msg.arg1); + return; + case KEYGUARD_DONE: + handleKeyguardDone(msg.arg1 != 0); + return; + case KEYGUARD_DONE_DRAWING: + handleKeyguardDoneDrawing(); + return; + case KEYGUARD_DONE_AUTHENTICATING: + keyguardDone(true, true); + return; + case SET_HIDDEN: + handleSetHidden(msg.arg1 != 0); + break; + case KEYGUARD_TIMEOUT: + synchronized (KeyguardViewMediator.this) { + doKeyguardLocked(); + } + break; + } + } + }; + + /** + * @see #keyguardDone + * @see #KEYGUARD_DONE + */ + private void handleKeyguardDone(boolean wakeup) { + if (DEBUG) Log.d(TAG, "handleKeyguardDone"); + handleHide(); + if (wakeup) { + mPM.wakeUp(SystemClock.uptimeMillis()); + } + mWakeLock.release(); + + if (!(mContext instanceof Activity)) { + final UserHandle currentUser = new UserHandle(mLockPatternUtils.getCurrentUser()); + mContext.sendBroadcastAsUser(mUserPresentIntent, currentUser); + } + } + + /** + * @see #keyguardDoneDrawing + * @see #KEYGUARD_DONE_DRAWING + */ + private void handleKeyguardDoneDrawing() { + synchronized(this) { + if (false) Log.d(TAG, "handleKeyguardDoneDrawing"); + if (mWaitingUntilKeyguardVisible) { + if (DEBUG) Log.d(TAG, "handleKeyguardDoneDrawing: notifying mWaitingUntilKeyguardVisible"); + mWaitingUntilKeyguardVisible = false; + notifyAll(); + + // there will usually be two of these sent, one as a timeout, and one + // as a result of the callback, so remove any remaining messages from + // the queue + mHandler.removeMessages(KEYGUARD_DONE_DRAWING); + } + } + } + + /** + * Handles the message sent by {@link #pokeWakelock} + * @param seq used to determine if anything has changed since the message + * was sent. + * @see #TIMEOUT + */ + private void handleTimeout(int seq) { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleTimeout"); + if (seq == mWakelockSequence) { + mWakeLock.release(); + } + } + } + + private void playSounds(boolean locked) { + // User feedback for keyguard. + + if (mSuppressNextLockSound) { + mSuppressNextLockSound = false; + return; + } + + final ContentResolver cr = mContext.getContentResolver(); + if (Settings.System.getInt(cr, Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) == 1) { + final int whichSound = locked + ? mLockSoundId + : mUnlockSoundId; + mLockSounds.stop(mLockSoundStreamId); + // Init mAudioManager + if (mAudioManager == null) { + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + if (mAudioManager == null) return; + mMasterStreamType = mAudioManager.getMasterStreamType(); + } + // If the stream is muted, don't play the sound + if (mAudioManager.isStreamMute(mMasterStreamType)) return; + + mLockSoundStreamId = mLockSounds.play(whichSound, + mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/); + } + } + + private void updateActivityLockScreenState() { + try { + ActivityManagerNative.getDefault().setLockScreenShown( + mShowing && !mHidden); + } catch (RemoteException e) { + } + } + + /** + * Handle message sent by {@link #showLocked}. + * @see #SHOW + */ + private void handleShow() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleShow"); + if (!mSystemReady) return; + + mKeyguardViewManager.show(); + mShowing = true; + updateActivityLockScreenState(); + adjustUserActivityLocked(); + adjustStatusBarLocked(); + try { + ActivityManagerNative.getDefault().closeSystemDialogs("lock"); + } catch (RemoteException e) { + } + + // Do this at the end to not slow down display of the keyguard. + playSounds(true); + + mShowKeyguardWakeLock.release(); + } + } + + /** + * Handle message sent by {@link #hideLocked()} + * @see #HIDE + */ + private void handleHide() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleHide"); + if (mWakeAndHandOff.isHeld()) { + Log.w(TAG, "attempt to hide the keyguard while waking, ignored"); + return; + } + + // only play "unlock" noises if not on a call (since the incall UI + // disables the keyguard) + if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) { + playSounds(false); + } + + mKeyguardViewManager.hide(); + mShowing = false; + updateActivityLockScreenState(); + adjustUserActivityLocked(); + adjustStatusBarLocked(); + } + } + + private void adjustUserActivityLocked() { + // disable user activity if we are shown and not hidden + if (DEBUG) Log.d(TAG, "adjustUserActivityLocked mShowing: " + mShowing + " mHidden: " + mHidden); + boolean enabled = !mShowing || mHidden; + // FIXME: Replace this with a new timeout control mechanism. + //mRealPowerManager.enableUserActivity(enabled); + if (!enabled && mScreenOn) { + // reinstate our short screen timeout policy + pokeWakelock(); + } + } + + private void adjustStatusBarLocked() { + if (mStatusBarManager == null) { + mStatusBarManager = (StatusBarManager) + mContext.getSystemService(Context.STATUS_BAR_SERVICE); + } + if (mStatusBarManager == null) { + Log.w(TAG, "Could not get status bar manager"); + } else { + if (mShowLockIcon) { + // Give feedback to user when secure keyguard is active and engaged + if (mShowing && isSecure()) { + if (!mShowingLockIcon) { + String contentDescription = mContext.getString( + com.android.internal.R.string.status_bar_device_locked); + mStatusBarManager.setIcon("secure", + com.android.internal.R.drawable.stat_sys_secure, 0, + contentDescription); + mShowingLockIcon = true; + } + } else { + if (mShowingLockIcon) { + mStatusBarManager.removeIcon("secure"); + mShowingLockIcon = false; + } + } + } + + // Disable aspects of the system/status/navigation bars that must not be re-enabled by + // windows that appear on top, ever + int flags = StatusBarManager.DISABLE_NONE; + if (mShowing) { + // disable navigation status bar components (home, recents) if lock screen is up + flags |= StatusBarManager.DISABLE_RECENT; + if (isSecure() || !ENABLE_INSECURE_STATUS_BAR_EXPAND) { + // showing secure lockscreen; disable expanding. + flags |= StatusBarManager.DISABLE_EXPAND; + } + if (isSecure()) { + // showing secure lockscreen; disable ticker. + flags |= StatusBarManager.DISABLE_NOTIFICATION_TICKER; + } + } + + if (DEBUG) { + Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mHidden=" + mHidden + + " isSecure=" + isSecure() + " --> flags=0x" + Integer.toHexString(flags)); + } + + mStatusBarManager.disable(flags); + } + } + + /** + * Handle message sent by {@link #wakeWhenReadyLocked(int)} + * @param keyCode The key that woke the device. + * @see #WAKE_WHEN_READY + */ + private void handleWakeWhenReady(int keyCode) { + synchronized (KeyguardViewMediator.this) { + if (DBG_WAKE) Log.d(TAG, "handleWakeWhenReady(" + keyCode + ")"); + + // this should result in a call to 'poke wakelock' which will set a timeout + // on releasing the wakelock + if (!mKeyguardViewManager.wakeWhenReadyTq(keyCode)) { + // poke wakelock ourselves if keyguard is no longer active + Log.w(TAG, "mKeyguardViewManager.wakeWhenReadyTq did not poke wake lock, so poke it ourselves"); + pokeWakelock(); + } + + /** + * Now that the keyguard is ready and has poked the wake lock, we can + * release the handoff wakelock + */ + mWakeAndHandOff.release(); + + if (!mWakeLock.isHeld()) { + Log.w(TAG, "mWakeLock not held in mKeyguardViewManager.wakeWhenReadyTq"); + } + } + } + + /** + * Handle message sent by {@link #resetStateLocked()} + * @see #RESET + */ + private void handleReset() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleReset"); + mKeyguardViewManager.reset(); + } + } + + /** + * Handle message sent by {@link #verifyUnlock} + * @see #RESET + */ + private void handleVerifyUnlock() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleVerifyUnlock"); + mKeyguardViewManager.verifyUnlock(); + mShowing = true; + updateActivityLockScreenState(); + } + } + + /** + * Handle message sent by {@link #notifyScreenOffLocked()} + * @see #NOTIFY_SCREEN_OFF + */ + private void handleNotifyScreenOff() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleNotifyScreenOff"); + mKeyguardViewManager.onScreenTurnedOff(); + } + } + + /** + * Handle message sent by {@link #notifyScreenOnLocked()} + * @see #NOTIFY_SCREEN_ON + */ + private void handleNotifyScreenOn(KeyguardViewManager.ShowListener showListener) { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleNotifyScreenOn"); + mKeyguardViewManager.onScreenTurnedOn(showListener); + } + } + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java new file mode 100644 index 0000000..d778129 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl.keyguard; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +import com.android.internal.R; + +public class KeyguardWidgetFrame extends FrameLayout { + private final static PorterDuffXfermode sAddBlendMode = + new PorterDuffXfermode(PorterDuff.Mode.ADD); + private static int sWidgetPagePadding; + private static Drawable sLeftOverscrollDrawable; + private static Drawable sRightOverscrollDrawable; + + private Drawable mForegroundDrawable; + private final Rect mForegroundRect = new Rect(); + private int mForegroundAlpha = 0; + + public KeyguardWidgetFrame(Context context) { + this(context, null, 0); + } + + public KeyguardWidgetFrame(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public KeyguardWidgetFrame(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + if (sLeftOverscrollDrawable == null) { + Resources res = context.getResources(); + sLeftOverscrollDrawable = res.getDrawable( + com.android.internal.R.drawable.kg_widget_overscroll_layer_left); + sRightOverscrollDrawable = res.getDrawable( + com.android.internal.R.drawable.kg_widget_overscroll_layer_right); + sWidgetPagePadding = + res.getDimensionPixelSize(com.android.internal.R.dimen.kg_widget_page_padding); + } + setPadding(sWidgetPagePadding, sWidgetPagePadding, sWidgetPagePadding, sWidgetPagePadding); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + if (mForegroundAlpha > 0) { + mForegroundDrawable.setBounds(mForegroundRect); + Paint p = ((NinePatchDrawable) mForegroundDrawable).getPaint(); + p.setXfermode(sAddBlendMode); + mForegroundDrawable.draw(canvas); + p.setXfermode(null); + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mForegroundRect.set(sWidgetPagePadding, sWidgetPagePadding, + w - sWidgetPagePadding, h - sWidgetPagePadding); + } + + void setOverScrollAmount(float r, boolean left) { + if (left && mForegroundDrawable != sLeftOverscrollDrawable) { + mForegroundDrawable = sLeftOverscrollDrawable; + } else if (!left && mForegroundDrawable != sRightOverscrollDrawable) { + mForegroundDrawable = sRightOverscrollDrawable; + } + + mForegroundAlpha = (int) Math.round((r * 255)); + mForegroundDrawable.setAlpha(mForegroundAlpha); + invalidate(); + } +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java new file mode 100644 index 0000000..7d077e2 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.policy.impl.keyguard; + +import android.animation.TimeInterpolator; +import android.appwidget.AppWidgetHostView; +import android.content.Context; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; + +import android.widget.FrameLayout; + +public class KeyguardWidgetPager extends PagedView { + ZInterpolator mZInterpolator = new ZInterpolator(0.5f); + private static float CAMERA_DISTANCE = 10000; + private static float TRANSITION_SCALE_FACTOR = 0.74f; + private static float TRANSITION_PIVOT = 0.65f; + private static float TRANSITION_MAX_ROTATION = 30; + private static final boolean PERFORM_OVERSCROLL_ROTATION = true; + private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f); + private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4); + + public KeyguardWidgetPager(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public KeyguardWidgetPager(Context context) { + this(null, null, 0); + } + + public KeyguardWidgetPager(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /* + * We wrap widgets in a special frame which handles drawing the overscroll foreground. + */ + public void addWidget(AppWidgetHostView widget) { + KeyguardWidgetFrame frame = new KeyguardWidgetFrame(getContext()); + FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT); + lp.gravity = Gravity.CENTER; + // The framework adds a default padding to AppWidgetHostView. We don't need this padding + // for the Keyguard, so we override it to be 0. + widget.setPadding(0, 0, 0, 0); + frame.addView(widget, lp); + addView(frame); + } + + /* + * This interpolator emulates the rate at which the perceived scale of an object changes + * as its distance from a camera increases. When this interpolator is applied to a scale + * animation on a view, it evokes the sense that the object is shrinking due to moving away + * from the camera. + */ + static class ZInterpolator implements TimeInterpolator { + private float focalLength; + + public ZInterpolator(float foc) { + focalLength = foc; + } + + public float getInterpolation(float input) { + return (1.0f - focalLength / (focalLength + input)) / + (1.0f - focalLength / (focalLength + 1.0f)); + } + } + + @Override + protected void overScroll(float amount) { + acceleratedOverScroll(amount); + } + + // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack. + @Override + protected void screenScrolled(int screenCenter) { + super.screenScrolled(screenCenter); + + for (int i = 0; i < getChildCount(); i++) { + View v = getPageAt(i); + if (v != null) { + float scrollProgress = getScrollProgress(screenCenter, v, i); + + float interpolatedProgress = + mZInterpolator.getInterpolation(Math.abs(Math.min(scrollProgress, 0))); + float scale = (1 - interpolatedProgress) + + interpolatedProgress * TRANSITION_SCALE_FACTOR; + float translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth(); + + float alpha; + + if (scrollProgress < 0) { + alpha = scrollProgress < 0 ? mAlphaInterpolator.getInterpolation( + 1 - Math.abs(scrollProgress)) : 1.0f; + } else { + // On large screens we need to fade the page as it nears its leftmost position + alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress); + } + + v.setCameraDistance(mDensity * CAMERA_DISTANCE); + int pageWidth = v.getMeasuredWidth(); + int pageHeight = v.getMeasuredHeight(); + + if (PERFORM_OVERSCROLL_ROTATION) { + if (i == 0 && scrollProgress < 0) { + // Overscroll to the left + v.setPivotX(TRANSITION_PIVOT * pageWidth); + v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); + if (v instanceof KeyguardWidgetFrame) { + ((KeyguardWidgetFrame) v).setOverScrollAmount(Math.abs(scrollProgress), + true); + } + scale = 1.0f; + alpha = 1.0f; + // On the first page, we don't want the page to have any lateral motion + translationX = 0; + } else if (i == getChildCount() - 1 && scrollProgress > 0) { + // Overscroll to the right + v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth); + v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); + scale = 1.0f; + alpha = 1.0f; + if (v instanceof KeyguardWidgetFrame) { + ((KeyguardWidgetFrame) v).setOverScrollAmount(Math.abs(scrollProgress), + false); + } + // On the last page, we don't want the page to have any lateral motion. + translationX = 0; + } else { + v.setPivotY(pageHeight / 2.0f); + v.setPivotX(pageWidth / 2.0f); + v.setRotationY(0f); + } + } + + v.setTranslationX(translationX); + v.setScaleX(scale); + v.setScaleY(scale); + v.setAlpha(alpha); + + // If the view has 0 alpha, we set it to be invisible so as to prevent + // it from accepting touches + if (alpha == 0) { + v.setVisibility(INVISIBLE); + } else if (v.getVisibility() != VISIBLE) { + v.setVisibility(VISIBLE); + } + } + } + } +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java b/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java new file mode 100644 index 0000000..1b46efa --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java @@ -0,0 +1,1704 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl.keyguard; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.animation.Interpolator; +import android.widget.Scroller; + +import com.android.internal.R; + +import java.util.ArrayList; + +/** + * An abstraction of the original Workspace which supports browsing through a + * sequential list of "pages" + */ +public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener { + private static final String TAG = "WidgetPagedView"; + private static final boolean DEBUG = false; + protected static final int INVALID_PAGE = -1; + + // the min drag distance for a fling to register, to prevent random page shifts + private static final int MIN_LENGTH_FOR_FLING = 25; + + protected static final int PAGE_SNAP_ANIMATION_DURATION = 550; + protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950; + protected static final float NANOTIME_DIV = 1000000000.0f; + + private static final float OVERSCROLL_ACCELERATE_FACTOR = 2; + private static final float OVERSCROLL_DAMP_FACTOR = 0.14f; + + private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f; + // The page is moved more than halfway, automatically move to the next page on touch up. + private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f; + + // The following constants need to be scaled based on density. The scaled versions will be + // assigned to the corresponding member variables below. + private static final int FLING_THRESHOLD_VELOCITY = 500; + private static final int MIN_SNAP_VELOCITY = 1500; + private static final int MIN_FLING_VELOCITY = 250; + + static final int AUTOMATIC_PAGE_SPACING = -1; + + protected int mFlingThresholdVelocity; + protected int mMinFlingVelocity; + protected int mMinSnapVelocity; + + protected float mDensity; + protected float mSmoothingTime; + protected float mTouchX; + + protected boolean mFirstLayout = true; + + protected int mCurrentPage; + protected int mNextPage = INVALID_PAGE; + protected int mMaxScrollX; + protected Scroller mScroller; + private VelocityTracker mVelocityTracker; + + private float mDownMotionX; + protected float mLastMotionX; + protected float mLastMotionXRemainder; + protected float mLastMotionY; + protected float mTotalMotionX; + private int mLastScreenCenter = -1; + private int[] mChildOffsets; + private int[] mChildRelativeOffsets; + private int[] mChildOffsetsWithLayoutScale; + + protected final static int TOUCH_STATE_REST = 0; + protected final static int TOUCH_STATE_SCROLLING = 1; + protected final static int TOUCH_STATE_PREV_PAGE = 2; + protected final static int TOUCH_STATE_NEXT_PAGE = 3; + protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f; + + protected int mTouchState = TOUCH_STATE_REST; + protected boolean mForceScreenScrolled = false; + + protected OnLongClickListener mLongClickListener; + + protected boolean mAllowLongPress = true; + + protected int mTouchSlop; + private int mPagingTouchSlop; + private int mMaximumVelocity; + private int mMinimumWidth; + protected int mPageSpacing; + protected int mPageLayoutPaddingTop; + protected int mPageLayoutPaddingBottom; + protected int mPageLayoutPaddingLeft; + protected int mPageLayoutPaddingRight; + protected int mPageLayoutWidthGap; + protected int mPageLayoutHeightGap; + protected int mCellCountX = 0; + protected int mCellCountY = 0; + protected boolean mCenterPagesVertically; + protected boolean mAllowOverScroll = true; + protected int mUnboundedScrollX; + protected int[] mTempVisiblePagesRange = new int[2]; + protected boolean mForceDrawAllChildrenNextFrame; + + // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise + // it is equal to the scaled overscroll position. We use a separate value so as to prevent + // the screens from continuing to translate beyond the normal bounds. + protected int mOverScrollX; + + // parameter that adjusts the layout to be optimized for pages with that scale factor + protected float mLayoutScale = 1.0f; + + protected static final int INVALID_POINTER = -1; + + protected int mActivePointerId = INVALID_POINTER; + + private PageSwitchListener mPageSwitchListener; + + protected ArrayList<Boolean> mDirtyPageContent; + + // If true, syncPages and syncPageItems will be called to refresh pages + protected boolean mContentIsRefreshable = true; + + // If true, modify alpha of neighboring pages as user scrolls left/right + protected boolean mFadeInAdjacentScreens = true; + + // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding + // to switch to a new page + protected boolean mUsePagingTouchSlop = true; + + // If true, the subclass should directly update scrollX itself in its computeScroll method + // (SmoothPagedView does this) + protected boolean mDeferScrollUpdate = false; + + protected boolean mIsPageMoving = false; + + // All syncs and layout passes are deferred until data is ready. + protected boolean mIsDataReady = true; + + // Scrolling indicator + private ValueAnimator mScrollIndicatorAnimator; + private View mScrollIndicator; + private int mScrollIndicatorPaddingLeft; + private int mScrollIndicatorPaddingRight; + private boolean mShouldShowScrollIndicator = false; + private boolean mShouldShowScrollIndicatorImmediately = false; + protected static final int sScrollIndicatorFadeInDuration = 150; + protected static final int sScrollIndicatorFadeOutDuration = 650; + protected static final int sScrollIndicatorFlashDuration = 650; + + public interface PageSwitchListener { + void onPageSwitch(View newPage, int newPageIndex); + } + + public PagedView(Context context) { + this(context, null); + } + + public PagedView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PagedView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.PagedView, defStyle, 0); + setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0)); + mPageLayoutPaddingTop = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingTop, 0); + mPageLayoutPaddingBottom = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingBottom, 0); + mPageLayoutPaddingLeft = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingLeft, 0); + mPageLayoutPaddingRight = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingRight, 0); + mPageLayoutWidthGap = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutWidthGap, 0); + mPageLayoutHeightGap = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutHeightGap, 0); + mScrollIndicatorPaddingLeft = + a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingLeft, 0); + mScrollIndicatorPaddingRight = + a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingRight, 0); + a.recycle(); + + setHapticFeedbackEnabled(false); + init(); + } + + /** + * Initializes various states for this workspace. + */ + protected void init() { + mDirtyPageContent = new ArrayList<Boolean>(); + mDirtyPageContent.ensureCapacity(32); + mScroller = new Scroller(getContext(), new ScrollInterpolator()); + mCurrentPage = 0; + mCenterPagesVertically = true; + + final ViewConfiguration configuration = ViewConfiguration.get(getContext()); + mTouchSlop = configuration.getScaledTouchSlop(); + mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); + mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + mDensity = getResources().getDisplayMetrics().density; + + mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity); + mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity); + mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity); + setOnHierarchyChangeListener(this); + } + + public void setPageSwitchListener(PageSwitchListener pageSwitchListener) { + mPageSwitchListener = pageSwitchListener; + if (mPageSwitchListener != null) { + mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); + } + } + + /** + * Called by subclasses to mark that data is ready, and that we can begin loading and laying + * out pages. + */ + protected void setDataIsReady() { + mIsDataReady = true; + } + + protected boolean isDataReady() { + return mIsDataReady; + } + + /** + * Returns the index of the currently displayed page. + * + * @return The index of the currently displayed page. + */ + int getCurrentPage() { + return mCurrentPage; + } + + int getNextPage() { + return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; + } + + int getPageCount() { + return getChildCount(); + } + + View getPageAt(int index) { + return getChildAt(index); + } + + protected int indexToPage(int index) { + return index; + } + + /** + * Updates the scroll of the current page immediately to its final scroll position. We use this + * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of + * the previous tab page. + */ + protected void updateCurrentPageScroll() { + int offset = getChildOffset(mCurrentPage); + int relOffset = getRelativeChildOffset(mCurrentPage); + int newX = offset - relOffset; + scrollTo(newX, 0); + mScroller.setFinalX(newX); + mScroller.forceFinished(true); + } + + /** + * Sets the current page. + */ + void setCurrentPage(int currentPage) { + if (!mScroller.isFinished()) { + mScroller.abortAnimation(); + } + // don't introduce any checks like mCurrentPage == currentPage here-- if we change the + // the default + if (getChildCount() == 0) { + return; + } + + mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1)); + updateCurrentPageScroll(); + updateScrollingIndicator(); + notifyPageSwitchListener(); + invalidate(); + } + + protected void notifyPageSwitchListener() { + if (mPageSwitchListener != null) { + mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); + } + } + + protected void pageBeginMoving() { + if (!mIsPageMoving) { + mIsPageMoving = true; + onPageBeginMoving(); + } + } + + protected void pageEndMoving() { + if (mIsPageMoving) { + mIsPageMoving = false; + onPageEndMoving(); + } + } + + protected boolean isPageMoving() { + return mIsPageMoving; + } + + // a method that subclasses can override to add behavior + protected void onPageBeginMoving() { + } + + // a method that subclasses can override to add behavior + protected void onPageEndMoving() { + } + + /** + * Registers the specified listener on each page contained in this workspace. + * + * @param l The listener used to respond to long clicks. + */ + @Override + public void setOnLongClickListener(OnLongClickListener l) { + mLongClickListener = l; + final int count = getPageCount(); + for (int i = 0; i < count; i++) { + getPageAt(i).setOnLongClickListener(l); + } + } + + @Override + public void scrollBy(int x, int y) { + scrollTo(mUnboundedScrollX + x, getScrollY() + y); + } + + @Override + public void scrollTo(int x, int y) { + mUnboundedScrollX = x; + + if (x < 0) { + super.scrollTo(0, y); + if (mAllowOverScroll) { + overScroll(x); + } + } else if (x > mMaxScrollX) { + super.scrollTo(mMaxScrollX, y); + if (mAllowOverScroll) { + overScroll(x - mMaxScrollX); + } + } else { + mOverScrollX = x; + super.scrollTo(x, y); + } + + mTouchX = x; + mSmoothingTime = System.nanoTime() / NANOTIME_DIV; + } + + // we moved this functionality to a helper function so SmoothPagedView can reuse it + protected boolean computeScrollHelper() { + if (mScroller.computeScrollOffset()) { + // Don't bother scrolling if the page does not need to be moved + if (getScrollX() != mScroller.getCurrX() + || getScrollY() != mScroller.getCurrY() + || mOverScrollX != mScroller.getCurrX()) { + scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); + } + invalidate(); + return true; + } else if (mNextPage != INVALID_PAGE) { + mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1)); + mNextPage = INVALID_PAGE; + notifyPageSwitchListener(); + + // We don't want to trigger a page end moving unless the page has settled + // and the user has stopped scrolling + if (mTouchState == TOUCH_STATE_REST) { + pageEndMoving(); + } + + // Notify the user when the page changes + AccessibilityManager accessibilityManager = (AccessibilityManager) + getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + if (accessibilityManager.isEnabled()) { + AccessibilityEvent ev = + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); + ev.getText().add(getCurrentPageDescription()); + sendAccessibilityEventUnchecked(ev); + } + return true; + } + return false; + } + + public String getCurrentPageDescription() { + return ""; + } + + @Override + public void computeScroll() { + computeScrollHelper(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (!mIsDataReady || getChildCount() == 0) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + final int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + + // Return early if we aren't given a proper dimension + if (widthSize <= 0 || heightSize <= 0) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + + /* Allow the height to be set as WRAP_CONTENT. This allows the particular case + * of the All apps view on XLarge displays to not take up more space then it needs. Width + * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect + * each page to have the same width. + */ + final int verticalPadding = getPaddingTop() + getPaddingBottom(); + final int horizontalPadding = getPaddingLeft() + getPaddingRight(); + + // The children are given the same width and height as the workspace + // unless they were set to WRAP_CONTENT + if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize); + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + // disallowing padding in paged view (just pass 0) + final View child = getPageAt(i); + + int childWidthMode = MeasureSpec.EXACTLY; + int childHeightMode = MeasureSpec.EXACTLY; + + final int childWidthMeasureSpec = + MeasureSpec.makeMeasureSpec(widthSize - horizontalPadding, childWidthMode); + final int childHeightMeasureSpec = + MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + setMeasuredDimension(widthSize, heightSize); + + // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions. + // We also wait until we set the measured dimensions before flushing the cache as well, to + // ensure that the cache is filled with good values. + invalidateCachedOffsets(); + + if (childCount > 0) { + if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getMeasuredWidth() + ", " + + getChildWidth(0)); + + // Calculate the variable page spacing if necessary + if (mPageSpacing == AUTOMATIC_PAGE_SPACING) { + // The gap between pages in the PagedView should be equal to the gap from the page + // to the edge of the screen (so it is not visible in the current screen). To + // account for unequal padding on each side of the paged view, we take the maximum + // of the left/right gap and use that as the gap between each page. + int offset = getRelativeChildOffset(0); + int spacing = Math.max(offset, widthSize - offset - + getChildAt(0).getMeasuredWidth()); + setPageSpacing(spacing); + } + } + + updateScrollingIndicatorPosition(); + + if (childCount > 0) { + mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1); + } else { + mMaxScrollX = 0; + } + } + + public void setPageSpacing(int pageSpacing) { + mPageSpacing = pageSpacing; + invalidateCachedOffsets(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (!mIsDataReady || getChildCount() == 0) { + return; + } + + if (DEBUG) Log.d(TAG, "PagedView.onLayout()"); + final int verticalPadding = getPaddingTop() + getPaddingBottom(); + final int childCount = getChildCount(); + int childLeft = getRelativeChildOffset(0); + + for (int i = 0; i < childCount; i++) { + final View child = getPageAt(i); + if (child.getVisibility() != View.GONE) { + final int childWidth = getScaledMeasuredWidth(child); + final int childHeight = child.getMeasuredHeight(); + int childTop = getPaddingTop(); + if (mCenterPagesVertically) { + childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2; + } + + if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop); + child.layout(childLeft, childTop, + childLeft + child.getMeasuredWidth(), childTop + childHeight); + childLeft += childWidth + mPageSpacing; + } + } + + if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { + setHorizontalScrollBarEnabled(false); + updateCurrentPageScroll(); + setHorizontalScrollBarEnabled(true); + mFirstLayout = false; + } + } + + protected void screenScrolled(int screenCenter) { + if (isScrollingIndicatorEnabled()) { + updateScrollingIndicator(); + } + boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX; + + if (mFadeInAdjacentScreens && !isInOverscroll) { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child != null) { + float scrollProgress = getScrollProgress(screenCenter, child, i); + float alpha = 1 - Math.abs(scrollProgress); + child.setAlpha(alpha); + } + } + invalidate(); + } + } + + @Override + public void onChildViewAdded(View parent, View child) { + // This ensures that when children are added, they get the correct transforms / alphas + // in accordance with any scroll effects. + mForceScreenScrolled = true; + invalidate(); + invalidateCachedOffsets(); + } + + @Override + public void onChildViewRemoved(View parent, View child) { + } + + protected void invalidateCachedOffsets() { + int count = getChildCount(); + if (count == 0) { + mChildOffsets = null; + mChildRelativeOffsets = null; + mChildOffsetsWithLayoutScale = null; + return; + } + + mChildOffsets = new int[count]; + mChildRelativeOffsets = new int[count]; + mChildOffsetsWithLayoutScale = new int[count]; + for (int i = 0; i < count; i++) { + mChildOffsets[i] = -1; + mChildRelativeOffsets[i] = -1; + mChildOffsetsWithLayoutScale[i] = -1; + } + } + + protected int getChildOffset(int index) { + int[] childOffsets = Float.compare(mLayoutScale, 1f) == 0 ? + mChildOffsets : mChildOffsetsWithLayoutScale; + + if (childOffsets != null && childOffsets[index] != -1) { + return childOffsets[index]; + } else { + if (getChildCount() == 0) + return 0; + + int offset = getRelativeChildOffset(0); + for (int i = 0; i < index; ++i) { + offset += getScaledMeasuredWidth(getPageAt(i)) + mPageSpacing; + } + if (childOffsets != null) { + childOffsets[index] = offset; + } + return offset; + } + } + + protected int getRelativeChildOffset(int index) { + if (mChildRelativeOffsets != null && mChildRelativeOffsets[index] != -1) { + return mChildRelativeOffsets[index]; + } else { + final int padding = getPaddingLeft() + getPaddingRight(); + final int offset = getPaddingLeft() + + (getMeasuredWidth() - padding - getChildWidth(index)) / 2; + if (mChildRelativeOffsets != null) { + mChildRelativeOffsets[index] = offset; + } + return offset; + } + } + + protected int getScaledMeasuredWidth(View child) { + // This functions are called enough times that it actually makes a difference in the + // profiler -- so just inline the max() here + final int measuredWidth = child.getMeasuredWidth(); + final int minWidth = mMinimumWidth; + final int maxWidth = (minWidth > measuredWidth) ? minWidth : measuredWidth; + return (int) (maxWidth * mLayoutScale + 0.5f); + } + + protected void getVisiblePages(int[] range) { + final int pageCount = getChildCount(); + + if (pageCount > 0) { + final int screenWidth = getMeasuredWidth(); + int leftScreen = 0; + int rightScreen = 0; + View currPage = getPageAt(leftScreen); + while (leftScreen < pageCount - 1 && + currPage.getX() + currPage.getWidth() - + currPage.getPaddingRight() < getScrollX()) { + leftScreen++; + currPage = getPageAt(leftScreen); + } + rightScreen = leftScreen; + currPage = getPageAt(rightScreen + 1); + while (rightScreen < pageCount - 1 && + currPage.getX() - currPage.getPaddingLeft() < getScrollX() + screenWidth) { + rightScreen++; + currPage = getPageAt(rightScreen + 1); + } + range[0] = leftScreen; + range[1] = rightScreen; + } else { + range[0] = -1; + range[1] = -1; + } + } + + protected boolean shouldDrawChild(View child) { + return child.getAlpha() > 0; + } + + @Override + protected void dispatchDraw(Canvas canvas) { + int halfScreenSize = getMeasuredWidth() / 2; + // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. + // Otherwise it is equal to the scaled overscroll position. + int screenCenter = mOverScrollX + halfScreenSize; + + if (screenCenter != mLastScreenCenter || mForceScreenScrolled) { + // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can + // set it for the next frame + mForceScreenScrolled = false; + screenScrolled(screenCenter); + mLastScreenCenter = screenCenter; + } + + // Find out which screens are visible; as an optimization we only call draw on them + final int pageCount = getChildCount(); + if (pageCount > 0) { + getVisiblePages(mTempVisiblePagesRange); + final int leftScreen = mTempVisiblePagesRange[0]; + final int rightScreen = mTempVisiblePagesRange[1]; + if (leftScreen != -1 && rightScreen != -1) { + final long drawingTime = getDrawingTime(); + // Clip to the bounds + canvas.save(); + canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(), + getScrollY() + getBottom() - getTop()); + + for (int i = getChildCount() - 1; i >= 0; i--) { + final View v = getPageAt(i); + if (mForceDrawAllChildrenNextFrame || + (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) { + drawChild(canvas, v, drawingTime); + } + } + mForceDrawAllChildrenNextFrame = false; + canvas.restore(); + } + } + } + + @Override + public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { + int page = indexToPage(indexOfChild(child)); + if (page != mCurrentPage || !mScroller.isFinished()) { + snapToPage(page); + return true; + } + return false; + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + int focusablePage; + if (mNextPage != INVALID_PAGE) { + focusablePage = mNextPage; + } else { + focusablePage = mCurrentPage; + } + View v = getPageAt(focusablePage); + if (v != null) { + return v.requestFocus(direction, previouslyFocusedRect); + } + return false; + } + + @Override + public boolean dispatchUnhandledMove(View focused, int direction) { + if (direction == View.FOCUS_LEFT) { + if (getCurrentPage() > 0) { + snapToPage(getCurrentPage() - 1); + return true; + } + } else if (direction == View.FOCUS_RIGHT) { + if (getCurrentPage() < getPageCount() - 1) { + snapToPage(getCurrentPage() + 1); + return true; + } + } + return super.dispatchUnhandledMove(focused, direction); + } + + @Override + public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { + if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) { + getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode); + } + if (direction == View.FOCUS_LEFT) { + if (mCurrentPage > 0) { + getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode); + } + } else if (direction == View.FOCUS_RIGHT){ + if (mCurrentPage < getPageCount() - 1) { + getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode); + } + } + } + + /** + * If one of our descendant views decides that it could be focused now, only + * pass that along if it's on the current page. + * + * This happens when live folders requery, and if they're off page, they + * end up calling requestFocus, which pulls it on page. + */ + @Override + public void focusableViewAvailable(View focused) { + View current = getPageAt(mCurrentPage); + View v = focused; + while (true) { + if (v == current) { + super.focusableViewAvailable(focused); + return; + } + if (v == this) { + return; + } + ViewParent parent = v.getParent(); + if (parent instanceof View) { + v = (View)v.getParent(); + } else { + return; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + if (disallowIntercept) { + // We need to make sure to cancel our long press if + // a scrollable widget takes over touch events + final View currentPage = getPageAt(mCurrentPage); + currentPage.cancelLongPress(); + } + super.requestDisallowInterceptTouchEvent(disallowIntercept); + } + + /** + * Return true if a tap at (x, y) should trigger a flip to the previous page. + */ + protected boolean hitsPreviousPage(float x, float y) { + return (x < getRelativeChildOffset(mCurrentPage) - mPageSpacing); + } + + /** + * Return true if a tap at (x, y) should trigger a flip to the next page. + */ + protected boolean hitsNextPage(float x, float y) { + return (x > (getMeasuredWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing)); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + /* + * This method JUST determines whether we want to intercept the motion. + * If we return true, onTouchEvent will be called and we do the actual + * scrolling there. + */ + acquireVelocityTrackerAndAddMovement(ev); + + // Skip touch handling if there are no pages to swipe + if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev); + + /* + * Shortcut the most recurring case: the user is in the dragging + * state and he is moving his finger. We want to intercept this + * motion. + */ + final int action = ev.getAction(); + if ((action == MotionEvent.ACTION_MOVE) && + (mTouchState == TOUCH_STATE_SCROLLING)) { + return true; + } + + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_MOVE: { + /* + * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check + * whether the user has moved far enough from his original down touch. + */ + if (mActivePointerId != INVALID_POINTER) { + determineScrollingStart(ev); + break; + } + // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN + // event. in that case, treat the first occurence of a move event as a ACTION_DOWN + // i.e. fall through to the next case (don't break) + // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events + // while it's small- this was causing a crash before we checked for INVALID_POINTER) + } + + case MotionEvent.ACTION_DOWN: { + final float x = ev.getX(); + final float y = ev.getY(); + // Remember location of down touch + mDownMotionX = x; + mLastMotionX = x; + mLastMotionY = y; + mLastMotionXRemainder = 0; + mTotalMotionX = 0; + mActivePointerId = ev.getPointerId(0); + mAllowLongPress = true; + + /* + * If being flinged and user touches the screen, initiate drag; + * otherwise don't. mScroller.isFinished should be false when + * being flinged. + */ + final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX()); + final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop); + if (finishedScrolling) { + mTouchState = TOUCH_STATE_REST; + mScroller.abortAnimation(); + } else { + mTouchState = TOUCH_STATE_SCROLLING; + } + + // check if this can be the beginning of a tap on the side of the pages + // to scroll the current page + if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) { + if (getChildCount() > 0) { + if (hitsPreviousPage(x, y)) { + mTouchState = TOUCH_STATE_PREV_PAGE; + } else if (hitsNextPage(x, y)) { + mTouchState = TOUCH_STATE_NEXT_PAGE; + } + } + } + break; + } + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mTouchState = TOUCH_STATE_REST; + mAllowLongPress = false; + mActivePointerId = INVALID_POINTER; + releaseVelocityTracker(); + break; + + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + releaseVelocityTracker(); + break; + } + + /* + * The only time we want to intercept motion events is if we are in the + * drag mode. + */ + return mTouchState != TOUCH_STATE_REST; + } + + protected void determineScrollingStart(MotionEvent ev) { + determineScrollingStart(ev, 1.0f); + } + + /* + * Determines if we should change the touch state to start scrolling after the + * user moves their touch point too far. + */ + protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) { + /* + * Locally do absolute value. mLastMotionX is set to the y value + * of the down event. + */ + final int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == -1) { + return; + } + final float x = ev.getX(pointerIndex); + final float y = ev.getY(pointerIndex); + final int xDiff = (int) Math.abs(x - mLastMotionX); + final int yDiff = (int) Math.abs(y - mLastMotionY); + + final int touchSlop = Math.round(touchSlopScale * mTouchSlop); + boolean xPaged = xDiff > mPagingTouchSlop; + boolean xMoved = xDiff > touchSlop; + boolean yMoved = yDiff > touchSlop; + + if (xMoved || xPaged || yMoved) { + if (mUsePagingTouchSlop ? xPaged : xMoved) { + // Scroll if the user moved far enough along the X axis + mTouchState = TOUCH_STATE_SCROLLING; + mTotalMotionX += Math.abs(mLastMotionX - x); + mLastMotionX = x; + mLastMotionXRemainder = 0; + mTouchX = getScrollX(); + mSmoothingTime = System.nanoTime() / NANOTIME_DIV; + pageBeginMoving(); + } + // Either way, cancel any pending longpress + cancelCurrentPageLongPress(); + } + } + + protected void cancelCurrentPageLongPress() { + if (mAllowLongPress) { + mAllowLongPress = false; + // Try canceling the long press. It could also have been scheduled + // by a distant descendant, so use the mAllowLongPress flag to block + // everything + final View currentPage = getPageAt(mCurrentPage); + if (currentPage != null) { + currentPage.cancelLongPress(); + } + } + } + + protected float getScrollProgress(int screenCenter, View v, int page) { + final int halfScreenSize = getMeasuredWidth() / 2; + + int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing; + int delta = screenCenter - (getChildOffset(page) - + getRelativeChildOffset(page) + halfScreenSize); + + float scrollProgress = delta / (totalDistance * 1.0f); + scrollProgress = Math.min(scrollProgress, 1.0f); + scrollProgress = Math.max(scrollProgress, -1.0f); + return scrollProgress; + } + + // This curve determines how the effect of scrolling over the limits of the page dimishes + // as the user pulls further and further from the bounds + private float overScrollInfluenceCurve(float f) { + f -= 1.0f; + return f * f * f + 1.0f; + } + + protected void acceleratedOverScroll(float amount) { + int screenSize = getMeasuredWidth(); + + // We want to reach the max over scroll effect when the user has + // over scrolled half the size of the screen + float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize); + + if (f == 0) return; + + // Clamp this factor, f, to -1 < f < 1 + if (Math.abs(f) >= 1) { + f /= Math.abs(f); + } + + int overScrollAmount = (int) Math.round(f * screenSize); + if (amount < 0) { + mOverScrollX = overScrollAmount; + super.scrollTo(0, getScrollY()); + } else { + mOverScrollX = mMaxScrollX + overScrollAmount; + super.scrollTo(mMaxScrollX, getScrollY()); + } + invalidate(); + } + + protected void dampedOverScroll(float amount) { + int screenSize = getMeasuredWidth(); + + float f = (amount / screenSize); + + if (f == 0) return; + f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); + + // Clamp this factor, f, to -1 < f < 1 + if (Math.abs(f) >= 1) { + f /= Math.abs(f); + } + + int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize); + if (amount < 0) { + mOverScrollX = overScrollAmount; + super.scrollTo(0, getScrollY()); + } else { + mOverScrollX = mMaxScrollX + overScrollAmount; + super.scrollTo(mMaxScrollX, getScrollY()); + } + invalidate(); + } + + protected void overScroll(float amount) { + dampedOverScroll(amount); + } + + protected float maxOverScroll() { + // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not + // exceed). Used to find out how much extra wallpaper we need for the over scroll effect + float f = 1.0f; + f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); + return OVERSCROLL_DAMP_FACTOR * f; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + // Skip touch handling if there are no pages to swipe + if (getChildCount() <= 0) return super.onTouchEvent(ev); + + acquireVelocityTrackerAndAddMovement(ev); + + final int action = ev.getAction(); + + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + /* + * If being flinged and user touches, stop the fling. isFinished + * will be false if being flinged. + */ + if (!mScroller.isFinished()) { + mScroller.abortAnimation(); + } + + // Remember where the motion event started + mDownMotionX = mLastMotionX = ev.getX(); + mLastMotionXRemainder = 0; + mTotalMotionX = 0; + mActivePointerId = ev.getPointerId(0); + if (mTouchState == TOUCH_STATE_SCROLLING) { + pageBeginMoving(); + } + break; + + case MotionEvent.ACTION_MOVE: + if (mTouchState == TOUCH_STATE_SCROLLING) { + // Scroll to follow the motion event + final int pointerIndex = ev.findPointerIndex(mActivePointerId); + final float x = ev.getX(pointerIndex); + final float deltaX = mLastMotionX + mLastMotionXRemainder - x; + + mTotalMotionX += Math.abs(deltaX); + + // Only scroll and update mLastMotionX if we have moved some discrete amount. We + // keep the remainder because we are actually testing if we've moved from the last + // scrolled position (which is discrete). + if (Math.abs(deltaX) >= 1.0f) { + mTouchX += deltaX; + mSmoothingTime = System.nanoTime() / NANOTIME_DIV; + if (!mDeferScrollUpdate) { + scrollBy((int) deltaX, 0); + if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX); + } else { + invalidate(); + } + mLastMotionX = x; + mLastMotionXRemainder = deltaX - (int) deltaX; + } else { + awakenScrollBars(); + } + } else { + determineScrollingStart(ev); + } + break; + + case MotionEvent.ACTION_UP: + if (mTouchState == TOUCH_STATE_SCROLLING) { + final int activePointerId = mActivePointerId; + final int pointerIndex = ev.findPointerIndex(activePointerId); + final float x = ev.getX(pointerIndex); + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int velocityX = (int) velocityTracker.getXVelocity(activePointerId); + final int deltaX = (int) (x - mDownMotionX); + final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage)); + boolean isSignificantMove = Math.abs(deltaX) > pageWidth * + SIGNIFICANT_MOVE_THRESHOLD; + + mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x); + + boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING && + Math.abs(velocityX) > mFlingThresholdVelocity; + + // In the case that the page is moved far to one direction and then is flung + // in the opposite direction, we use a threshold to determine whether we should + // just return to the starting page, or if we should skip one further. + boolean returnToOriginalPage = false; + if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD && + Math.signum(velocityX) != Math.signum(deltaX) && isFling) { + returnToOriginalPage = true; + } + + int finalPage; + // We give flings precedence over large moves, which is why we short-circuit our + // test for a large move if a fling has been registered. That is, a large + // move to the left and fling to the right will register as a fling to the right. + if (((isSignificantMove && deltaX > 0 && !isFling) || + (isFling && velocityX > 0)) && mCurrentPage > 0) { + finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1; + snapToPageWithVelocity(finalPage, velocityX); + } else if (((isSignificantMove && deltaX < 0 && !isFling) || + (isFling && velocityX < 0)) && + mCurrentPage < getChildCount() - 1) { + finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1; + snapToPageWithVelocity(finalPage, velocityX); + } else { + snapToDestination(); + } + } else if (mTouchState == TOUCH_STATE_PREV_PAGE) { + // at this point we have not moved beyond the touch slop + // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so + // we can just page + int nextPage = Math.max(0, mCurrentPage - 1); + if (nextPage != mCurrentPage) { + snapToPage(nextPage); + } else { + snapToDestination(); + } + } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) { + // at this point we have not moved beyond the touch slop + // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so + // we can just page + int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1); + if (nextPage != mCurrentPage) { + snapToPage(nextPage); + } else { + snapToDestination(); + } + } else { + onUnhandledTap(ev); + } + mTouchState = TOUCH_STATE_REST; + mActivePointerId = INVALID_POINTER; + releaseVelocityTracker(); + break; + + case MotionEvent.ACTION_CANCEL: + if (mTouchState == TOUCH_STATE_SCROLLING) { + snapToDestination(); + } + mTouchState = TOUCH_STATE_REST; + mActivePointerId = INVALID_POINTER; + releaseVelocityTracker(); + break; + + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + break; + } + + return true; + } + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { + switch (event.getAction()) { + case MotionEvent.ACTION_SCROLL: { + // Handle mouse (or ext. device) by shifting the page depending on the scroll + final float vscroll; + final float hscroll; + if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) { + vscroll = 0; + hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); + } else { + vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); + hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); + } + if (hscroll != 0 || vscroll != 0) { + if (hscroll > 0 || vscroll > 0) { + scrollRight(); + } else { + scrollLeft(); + } + return true; + } + } + } + } + return super.onGenericMotionEvent(event); + } + + private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + } + + private void releaseVelocityTracker() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + private void onSecondaryPointerUp(MotionEvent ev) { + final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> + MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + // This was our active pointer going up. Choose a new + // active pointer and adjust accordingly. + // TODO: Make this decision more intelligent. + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mLastMotionX = mDownMotionX = ev.getX(newPointerIndex); + mLastMotionY = ev.getY(newPointerIndex); + mLastMotionXRemainder = 0; + mActivePointerId = ev.getPointerId(newPointerIndex); + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + } + } + } + + protected void onUnhandledTap(MotionEvent ev) {} + + @Override + public void requestChildFocus(View child, View focused) { + super.requestChildFocus(child, focused); + int page = indexToPage(indexOfChild(child)); + if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) { + snapToPage(page); + } + } + + protected int getChildIndexForRelativeOffset(int relativeOffset) { + final int childCount = getChildCount(); + int left; + int right; + for (int i = 0; i < childCount; ++i) { + left = getRelativeChildOffset(i); + right = (left + getScaledMeasuredWidth(getPageAt(i))); + if (left <= relativeOffset && relativeOffset <= right) { + return i; + } + } + return -1; + } + + protected int getChildWidth(int index) { + // This functions are called enough times that it actually makes a difference in the + // profiler -- so just inline the max() here + final int measuredWidth = getPageAt(index).getMeasuredWidth(); + final int minWidth = mMinimumWidth; + return (minWidth > measuredWidth) ? minWidth : measuredWidth; + } + + int getPageNearestToCenterOfScreen() { + int minDistanceFromScreenCenter = Integer.MAX_VALUE; + int minDistanceFromScreenCenterIndex = -1; + int screenCenter = getScrollX() + (getMeasuredWidth() / 2); + final int childCount = getChildCount(); + for (int i = 0; i < childCount; ++i) { + View layout = (View) getPageAt(i); + int childWidth = getScaledMeasuredWidth(layout); + int halfChildWidth = (childWidth / 2); + int childCenter = getChildOffset(i) + halfChildWidth; + int distanceFromScreenCenter = Math.abs(childCenter - screenCenter); + if (distanceFromScreenCenter < minDistanceFromScreenCenter) { + minDistanceFromScreenCenter = distanceFromScreenCenter; + minDistanceFromScreenCenterIndex = i; + } + } + return minDistanceFromScreenCenterIndex; + } + + protected void snapToDestination() { + snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION); + } + + private static class ScrollInterpolator implements Interpolator { + public ScrollInterpolator() { + } + + public float getInterpolation(float t) { + t -= 1.0f; + return t*t*t*t*t + 1; + } + } + + // We want the duration of the page snap animation to be influenced by the distance that + // the screen has to travel, however, we don't want this duration to be effected in a + // purely linear fashion. Instead, we use this method to moderate the effect that the distance + // of travel has on the overall snap duration. + float distanceInfluenceForSnapDuration(float f) { + f -= 0.5f; // center the values about 0. + f *= 0.3f * Math.PI / 2.0f; + return (float) Math.sin(f); + } + + protected void snapToPageWithVelocity(int whichPage, int velocity) { + whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1)); + int halfScreenSize = getMeasuredWidth() / 2; + + if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage)); + if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): " + + getMeasuredWidth() + ", " + getChildWidth(whichPage)); + final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); + int delta = newX - mUnboundedScrollX; + int duration = 0; + + if (Math.abs(velocity) < mMinFlingVelocity) { + // If the velocity is low enough, then treat this more as an automatic page advance + // as opposed to an apparent physical response to flinging + snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); + return; + } + + // Here we compute a "distance" that will be used in the computation of the overall + // snap duration. This is a function of the actual distance that needs to be traveled; + // we keep this value close to half screen size in order to reduce the variance in snap + // duration as a function of the distance the page needs to travel. + float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize)); + float distance = halfScreenSize + halfScreenSize * + distanceInfluenceForSnapDuration(distanceRatio); + + velocity = Math.abs(velocity); + velocity = Math.max(mMinSnapVelocity, velocity); + + // we want the page's snap velocity to approximately match the velocity at which the + // user flings, so we scale the duration by a value near to the derivative of the scroll + // interpolator at zero, ie. 5. We use 4 to make it a little slower. + duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); + + snapToPage(whichPage, delta, duration); + } + + protected void snapToPage(int whichPage) { + snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); + } + + protected void snapToPage(int whichPage, int duration) { + whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1)); + + if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage)); + if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getMeasuredWidth() + ", " + + getChildWidth(whichPage)); + int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); + final int sX = mUnboundedScrollX; + final int delta = newX - sX; + snapToPage(whichPage, delta, duration); + } + + protected void snapToPage(int whichPage, int delta, int duration) { + mNextPage = whichPage; + + View focusedChild = getFocusedChild(); + if (focusedChild != null && whichPage != mCurrentPage && + focusedChild == getPageAt(mCurrentPage)) { + focusedChild.clearFocus(); + } + + pageBeginMoving(); + awakenScrollBars(duration); + if (duration == 0) { + duration = Math.abs(delta); + } + + if (!mScroller.isFinished()) mScroller.abortAnimation(); + mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration); + + notifyPageSwitchListener(); + invalidate(); + } + + public void scrollLeft() { + if (mScroller.isFinished()) { + if (mCurrentPage > 0) snapToPage(mCurrentPage - 1); + } else { + if (mNextPage > 0) snapToPage(mNextPage - 1); + } + } + + public void scrollRight() { + if (mScroller.isFinished()) { + if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1); + } else { + if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1); + } + } + + public int getPageForView(View v) { + int result = -1; + if (v != null) { + ViewParent vp = v.getParent(); + int count = getChildCount(); + for (int i = 0; i < count; i++) { + if (vp == getPageAt(i)) { + return i; + } + } + } + return result; + } + + /** + * @return True is long presses are still allowed for the current touch + */ + public boolean allowLongPress() { + return mAllowLongPress; + } + + /** + * Set true to allow long-press events to be triggered, usually checked by + * {@link Launcher} to accept or block dpad-initiated long-presses. + */ + public void setAllowLongPress(boolean allowLongPress) { + mAllowLongPress = allowLongPress; + } + + public static class SavedState extends BaseSavedState { + int currentPage = -1; + + SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + currentPage = in.readInt(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeInt(currentPage); + } + + public static final Parcelable.Creator<SavedState> CREATOR = + new Parcelable.Creator<SavedState>() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + protected View getScrollingIndicator() { + return null; + } + + protected boolean isScrollingIndicatorEnabled() { + return false; + } + + Runnable hideScrollingIndicatorRunnable = new Runnable() { + @Override + public void run() { + hideScrollingIndicator(false); + } + }; + + protected void flashScrollingIndicator(boolean animated) { + removeCallbacks(hideScrollingIndicatorRunnable); + showScrollingIndicator(!animated); + postDelayed(hideScrollingIndicatorRunnable, sScrollIndicatorFlashDuration); + } + + protected void showScrollingIndicator(boolean immediately) { + mShouldShowScrollIndicator = true; + mShouldShowScrollIndicatorImmediately = true; + if (getChildCount() <= 1) return; + if (!isScrollingIndicatorEnabled()) return; + + mShouldShowScrollIndicator = false; + getScrollingIndicator(); + if (mScrollIndicator != null) { + // Fade the indicator in + updateScrollingIndicatorPosition(); + mScrollIndicator.setVisibility(View.VISIBLE); + cancelScrollingIndicatorAnimations(); + if (immediately) { + mScrollIndicator.setAlpha(1f); + } else { + mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 1f); + mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeInDuration); + mScrollIndicatorAnimator.start(); + } + } + } + + protected void cancelScrollingIndicatorAnimations() { + if (mScrollIndicatorAnimator != null) { + mScrollIndicatorAnimator.cancel(); + } + } + + protected void hideScrollingIndicator(boolean immediately) { + if (getChildCount() <= 1) return; + if (!isScrollingIndicatorEnabled()) return; + + getScrollingIndicator(); + if (mScrollIndicator != null) { + // Fade the indicator out + updateScrollingIndicatorPosition(); + cancelScrollingIndicatorAnimations(); + if (immediately) { + mScrollIndicator.setVisibility(View.INVISIBLE); + mScrollIndicator.setAlpha(0f); + } else { + mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 0f); + mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeOutDuration); + mScrollIndicatorAnimator.addListener(new AnimatorListenerAdapter() { + private boolean cancelled = false; + @Override + public void onAnimationCancel(android.animation.Animator animation) { + cancelled = true; + } + @Override + public void onAnimationEnd(Animator animation) { + if (!cancelled) { + mScrollIndicator.setVisibility(View.INVISIBLE); + } + } + }); + mScrollIndicatorAnimator.start(); + } + } + } + + /** + * To be overridden by subclasses to determine whether the scroll indicator should stretch to + * fill its space on the track or not. + */ + protected boolean hasElasticScrollIndicator() { + return true; + } + + private void updateScrollingIndicator() { + if (getChildCount() <= 1) return; + if (!isScrollingIndicatorEnabled()) return; + + getScrollingIndicator(); + if (mScrollIndicator != null) { + updateScrollingIndicatorPosition(); + } + if (mShouldShowScrollIndicator) { + showScrollingIndicator(mShouldShowScrollIndicatorImmediately); + } + } + + private void updateScrollingIndicatorPosition() { + if (!isScrollingIndicatorEnabled()) return; + if (mScrollIndicator == null) return; + int numPages = getChildCount(); + int pageWidth = getMeasuredWidth(); + int lastChildIndex = Math.max(0, getChildCount() - 1); + int maxScrollX = getChildOffset(lastChildIndex) - getRelativeChildOffset(lastChildIndex); + int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight; + int indicatorWidth = mScrollIndicator.getMeasuredWidth() - + mScrollIndicator.getPaddingLeft() - mScrollIndicator.getPaddingRight(); + + float offset = Math.max(0f, Math.min(1f, (float) getScrollX() / maxScrollX)); + int indicatorSpace = trackWidth / numPages; + int indicatorPos = (int) (offset * (trackWidth - indicatorSpace)) + mScrollIndicatorPaddingLeft; + if (hasElasticScrollIndicator()) { + if (mScrollIndicator.getMeasuredWidth() != indicatorSpace) { + mScrollIndicator.getLayoutParams().width = indicatorSpace; + mScrollIndicator.requestLayout(); + } + } else { + int indicatorCenterOffset = indicatorSpace / 2 - indicatorWidth / 2; + indicatorPos += indicatorCenterOffset; + } + mScrollIndicator.setTranslationX(indicatorPos); + } + + /* Accessibility */ + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setScrollable(getPageCount() > 1); + if (getCurrentPage() < getPageCount() - 1) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } + if (getCurrentPage() > 0) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setScrollable(true); + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { + event.setFromIndex(mCurrentPage); + event.setToIndex(mCurrentPage); + event.setItemCount(getChildCount()); + } + } + + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + if (super.performAccessibilityAction(action, arguments)) { + return true; + } + switch (action) { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + if (getCurrentPage() < getPageCount() - 1) { + scrollRight(); + return true; + } + } break; + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + if (getCurrentPage() > 0) { + scrollLeft(); + return true; + } + } break; + } + return false; + } + + @Override + public boolean onHoverEvent(android.view.MotionEvent event) { + return true; + } +} diff --git a/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/AccountUnlockScreen.java index a4baeed..d6a31b8 100644 --- a/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/AccountUnlockScreen.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import com.android.internal.R; import com.android.internal.widget.LockPatternUtils; diff --git a/policy/src/com/android/internal/policy/impl/keyguard_obsolete/BiometricSensorUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/BiometricSensorUnlock.java new file mode 100644 index 0000000..c38525e --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/BiometricSensorUnlock.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl.keyguard_obsolete; + +import android.view.View; + +interface BiometricSensorUnlock { + /** + * Initializes the view provided for the biometric unlock UI to work within. The provided area + * completely covers the backup unlock mechanism. + * @param biometricUnlockView View provided for the biometric unlock UI. + */ + public void initializeView(View biometricUnlockView); + + /** + * Indicates whether the biometric unlock is running. Before + * {@link BiometricSensorUnlock#start} is called, isRunning() returns false. After a successful + * call to {@link BiometricSensorUnlock#start}, isRunning() returns true until the biometric + * unlock completes, {@link BiometricSensorUnlock#stop} has been called, or an error has + * forced the biometric unlock to stop. + * @return whether the biometric unlock is currently running. + */ + public boolean isRunning(); + + /** + * Covers the backup unlock mechanism by showing the contents of the view initialized in + * {@link BiometricSensorUnlock#initializeView(View)}. The view should disappear after the + * specified timeout. If the timeout is 0, the interface shows until another event, such as + * calling {@link BiometricSensorUnlock#hide()}, causes it to disappear. Called on the UI + * thread. + * @param timeoutMilliseconds Amount of time in milliseconds to display the view before + * disappearing. A value of 0 means the view should remain visible. + */ + public void show(long timeoutMilliseconds); + + /** + * Uncovers the backup unlock mechanism by hiding the contents of the view initialized in + * {@link BiometricSensorUnlock#initializeView(View)}. + */ + public void hide(); + + /** + * Binds to the biometric unlock service and starts the unlock procedure. Called on the UI + * thread. + * @return false if it can't be started or the backup should be used. + */ + public boolean start(); + + /** + * Stops the biometric unlock procedure and unbinds from the service. Called on the UI thread. + * @return whether the biometric unlock was running when called. + */ + public boolean stop(); + + /** + * Cleans up any resources used by the biometric unlock. + */ + public void cleanUp(); + + /** + * Gets the Device Policy Manager quality of the biometric unlock sensor + * (e.g., PASSWORD_QUALITY_BIOMETRIC_WEAK). + * @return biometric unlock sensor quality, as defined by Device Policy Manager. + */ + public int getQuality(); +} diff --git a/policy/src/com/android/internal/policy/impl/FaceUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/FaceUnlock.java index fda3c9d..e4d9215 100644 --- a/policy/src/com/android/internal/policy/impl/FaceUnlock.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/FaceUnlock.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import com.android.internal.R; import com.android.internal.policy.IFaceLockCallback; diff --git a/policy/src/com/android/internal/policy/impl/KeyguardScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreen.java index bbb6875..ba5b7ff 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardScreen.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreen.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; /** * Common interface of each {@link android.view.View} that is a screen of @@ -27,7 +27,7 @@ public interface KeyguardScreen { * keyboard to be displayed. */ boolean needsInput(); - + /** * This screen is no longer in front of the user. */ diff --git a/policy/src/com/android/internal/policy/impl/KeyguardScreenCallback.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreenCallback.java index a843603..be505a1 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardScreenCallback.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreenCallback.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import android.content.res.Configuration; diff --git a/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardStatusViewManager.java index bb07a1d..409f87b 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardStatusViewManager.java @@ -14,14 +14,13 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import com.android.internal.R; import com.android.internal.telephony.IccCardConstants; import com.android.internal.widget.DigitalClock; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.TransportControlView; -import com.android.internal.policy.impl.KeyguardUpdateMonitor.BatteryStatus; import java.util.ArrayList; import java.util.Date; @@ -621,8 +620,7 @@ class KeyguardStatusViewManager implements OnClickListener { private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onRefreshBatteryInfo(BatteryStatus status) { + public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow(); mPluggedIn = status.isPluggedIn(); mBatteryLevel = status.level; diff --git a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitor.java index 5c0cd4f..d990f5f 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; diff --git a/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitorCallback.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitorCallback.java new file mode 100644 index 0000000..79233e8 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitorCallback.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.policy.impl.keyguard_obsolete; + +import android.app.admin.DevicePolicyManager; +import android.media.AudioManager; + +import com.android.internal.telephony.IccCardConstants; + +/** + * Callback for general information relevant to lock screen. + */ +class KeyguardUpdateMonitorCallback { + /** + * Called when the battery status changes, e.g. when plugged in or unplugged, charge + * level, etc. changes. + * + * @param status current battery status + */ + void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { } + + /** + * Called once per minute or when the time changes. + */ + void onTimeChanged() { } + + /** + * Called when the carrier PLMN or SPN changes. + * + * @param plmn The operator name of the registered network. May be null if it shouldn't + * be displayed. + * @param spn The service provider name. May be null if it shouldn't be displayed. + */ + void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) { } + + /** + * Called when the ringer mode changes. + * @param state the current ringer state, as defined in + * {@link AudioManager#RINGER_MODE_CHANGED_ACTION} + */ + void onRingerModeChanged(int state) { } + + /** + * Called when the phone state changes. String will be one of: + * {@link TelephonyManager#EXTRA_STATE_IDLE} + * {@link TelephonyManager@EXTRA_STATE_RINGING} + * {@link TelephonyManager#EXTRA_STATE_OFFHOOK + */ + void onPhoneStateChanged(int phoneState) { } + + /** + * Called when visibility of lockscreen clock changes, such as when + * obscured by a widget. + */ + void onClockVisibilityChanged() { } + + /** + * Called when the device becomes provisioned + */ + void onDeviceProvisioned() { } + + /** + * Called when the device policy changes. + * See {@link DevicePolicyManager#ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED} + */ + void onDevicePolicyManagerStateChanged() { } + + /** + * Called when the user changes. + */ + void onUserSwitched(int userId) { } + + /** + * Called when the SIM state changes. + * @param simState + */ + void onSimStateChanged(IccCardConstants.State simState) { } + + /** + * Called when a user is removed. + */ + void onUserRemoved(int userId) { } +} diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewBase.java index 29a5573..f9fe797 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewBase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import android.content.Context; import android.content.Intent; diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewCallback.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewCallback.java index b376d65..4cc0f30 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardViewCallback.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewCallback.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; /** - * The callback used by the keyguard view to tell the {@link KeyguardViewMediator} + * The callback used by the keyguard view to tell the {@link KeyguardViewMediator} * various things. */ public interface KeyguardViewCallback { diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewManager.java index d521c05..5dbef48 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import com.android.internal.R; @@ -231,7 +231,7 @@ public class KeyguardViewManager implements KeyguardWindowController { // Keyguard may be in the process of being shown, but not yet // updated with the window manager... give it a chance to do so. mKeyguardHost.post(new Runnable() { - @Override public void run() { + public void run() { if (mKeyguardHost.getVisibility() == View.VISIBLE) { showListener.onShown(mKeyguardHost.getWindowToken()); } else { diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewMediator.java index 236a4ea..641ee88 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewMediator.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; +import com.android.internal.policy.impl.PhoneWindowManager; import com.android.internal.telephony.IccCardConstants; import com.android.internal.widget.LockPatternUtils; diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewProperties.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewProperties.java index 51b7f1e..676574d 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardViewProperties.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import android.content.Context; diff --git a/policy/src/com/android/internal/policy/impl/KeyguardWindowController.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardWindowController.java index 4ad48fb..98e3209 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardWindowController.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardWindowController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; /** * Interface passed to the keyguard view, for it to call up to control diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardView.java index 32aec10..0ce87b1 100644 --- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardView.java @@ -14,10 +14,9 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import com.android.internal.R; -import com.android.internal.policy.impl.KeyguardUpdateMonitor.BatteryStatus; import com.android.internal.telephony.IccCardConstants; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockScreenWidgetCallback; @@ -689,7 +688,7 @@ public class LockPatternKeyguardView extends KeyguardViewBase { KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { @Override - public void onRefreshBatteryInfo(BatteryStatus status) { + public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { // When someone plugs in or unplugs the device, we hide the biometric sensor area and // suppress its startup for the next onScreenTurnedOn(). Since plugging/unplugging // causes the screen to turn on, the biometric unlock would start if it wasn't diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewProperties.java index 5066e3c..5d9cc8e 100644 --- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import com.android.internal.widget.LockPatternUtils; diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockScreen.java index 91b5ca1..4e9a1f7 100644 --- a/policy/src/com/android/internal/policy/impl/LockScreen.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockScreen.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import com.android.internal.R; import com.android.internal.telephony.IccCardConstants; diff --git a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PasswordUnlockScreen.java index 203f9db..87a7371 100644 --- a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PasswordUnlockScreen.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import java.util.List; diff --git a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PatternUnlockScreen.java index 9a6d2cc..6d5706b 100644 --- a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PatternUnlockScreen.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import android.content.Context; import android.content.res.Configuration; diff --git a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimPukUnlockScreen.java index 3b2a473..3c1703a 100644 --- a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimPukUnlockScreen.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import android.app.Dialog; import android.app.ProgressDialog; diff --git a/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimUnlockScreen.java index 80407f5..13c040c 100644 --- a/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimUnlockScreen.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import android.app.Dialog; import android.app.ProgressDialog; diff --git a/policy/tests/src/com/android/internal/policy/impl/LockPatternKeyguardViewTest.java b/policy/tests/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewTest.java index 862e683..97c5672 100644 --- a/policy/tests/src/com/android/internal/policy/impl/LockPatternKeyguardViewTest.java +++ b/policy/tests/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewTest.java @@ -14,10 +14,15 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import android.content.Context; -import com.android.internal.policy.impl.KeyguardViewCallback; + +import com.android.internal.policy.impl.keyguard_obsolete.KeyguardScreen; +import com.android.internal.policy.impl.keyguard_obsolete.KeyguardUpdateMonitor; +import com.android.internal.policy.impl.keyguard_obsolete.KeyguardViewCallback; +import com.android.internal.policy.impl.keyguard_obsolete.KeyguardWindowController; +import com.android.internal.policy.impl.keyguard_obsolete.LockPatternKeyguardView; import com.android.internal.telephony.IccCardConstants; import android.content.res.Configuration; import android.test.AndroidTestCase; diff --git a/services/input/SpriteController.cpp b/services/input/SpriteController.cpp index b15d4c8..1f3d2cf 100644 --- a/services/input/SpriteController.cpp +++ b/services/input/SpriteController.cpp @@ -298,7 +298,7 @@ void SpriteController::doUpdateSprites() { } if (becomingVisible) { - status = update.state.surfaceControl->show(surfaceLayer); + status = update.state.surfaceControl->show(); if (status) { ALOGE("Error %d showing sprite surface.", status); } else { @@ -369,7 +369,8 @@ sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height ensureSurfaceComposerClient(); sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface( - String8("Sprite"), 0, width, height, PIXEL_FORMAT_RGBA_8888); + String8("Sprite"), width, height, PIXEL_FORMAT_RGBA_8888, + ISurfaceComposerClient::eHidden); if (surfaceControl == NULL || !surfaceControl->isValid() || !surfaceControl->getSurface()->isValid()) { ALOGE("Error creating sprite surface."); diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java index 32ac8e1..9b7be02 100644 --- a/services/java/com/android/server/AlarmManagerService.java +++ b/services/java/com/android/server/AlarmManagerService.java @@ -34,6 +34,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; import android.os.WorkSource; import android.text.TextUtils; import android.text.format.Time; @@ -303,7 +304,7 @@ class AlarmManagerService extends IAlarmManager.Stub { } } } - + public void removeLocked(String packageName) { removeLocked(mRtcWakeupAlarms, packageName); removeLocked(mRtcAlarms, packageName); @@ -327,6 +328,29 @@ class AlarmManagerService extends IAlarmManager.Stub { } } } + + public void removeUserLocked(int userHandle) { + removeUserLocked(mRtcWakeupAlarms, userHandle); + removeUserLocked(mRtcAlarms, userHandle); + removeUserLocked(mElapsedRealtimeWakeupAlarms, userHandle); + removeUserLocked(mElapsedRealtimeAlarms, userHandle); + } + + private void removeUserLocked(ArrayList<Alarm> alarmList, int userHandle) { + if (alarmList.size() <= 0) { + return; + } + + // iterator over the list removing any it where the intent match + Iterator<Alarm> it = alarmList.iterator(); + + while (it.hasNext()) { + Alarm alarm = it.next(); + if (UserHandle.getUserId(alarm.operation.getTargetUid()) == userHandle) { + it.remove(); + } + } + } public boolean lookForPackageLocked(String packageName) { return lookForPackageLocked(mRtcWakeupAlarms, packageName) @@ -822,6 +846,7 @@ class AlarmManagerService extends IAlarmManager.Stub { // Register for events related to sdcard installation. IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + sdFilter.addAction(Intent.ACTION_USER_STOPPED); mContext.registerReceiver(this, sdFilter); } @@ -841,6 +866,11 @@ class AlarmManagerService extends IAlarmManager.Stub { return; } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + } else if (Intent.ACTION_USER_STOPPED.equals(action)) { + int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userHandle >= 0) { + removeUserLocked(userHandle); + } } else { if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 776a1a4..8a1ac10 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -42,6 +42,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; +import android.net.CaptivePortalTracker; import android.net.ConnectivityManager; import android.net.DummyDataStateTracker; import android.net.EthernetDataTracker; @@ -166,6 +167,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private NetworkStateTracker mNetTrackers[]; + /* Handles captive portal check on a network */ + private CaptivePortalTracker mCaptivePortalTracker; + /** * The link properties that define the current links */ @@ -1363,8 +1367,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { return false; } NetworkStateTracker tracker = mNetTrackers[networkType]; + DetailedState netState = tracker.getNetworkInfo().getDetailedState(); - if (tracker == null || !tracker.getNetworkInfo().isConnected() || + if (tracker == null || (netState != DetailedState.CONNECTED && + netState != DetailedState.CAPTIVE_PORTAL_CHECK) || tracker.isTeardownRequested()) { if (VDBG) { log("requestRouteToHostAddress on down network " + @@ -1966,32 +1972,29 @@ public class ConnectivityService extends IConnectivityManager.Stub { } }; + private boolean isNewNetTypePreferredOverCurrentNetType(int type) { + if ((type != mNetworkPreference && + mNetConfigs[mActiveDefaultNetwork].priority > + mNetConfigs[type].priority) || + mNetworkPreference == mActiveDefaultNetwork) return false; + return true; + } + private void handleConnect(NetworkInfo info) { - final int type = info.getType(); + final int newNetType = info.getType(); - setupDataActivityTracking(type); + setupDataActivityTracking(newNetType); // snapshot isFailover, because sendConnectedBroadcast() resets it boolean isFailover = info.isFailover(); - final NetworkStateTracker thisNet = mNetTrackers[type]; + final NetworkStateTracker thisNet = mNetTrackers[newNetType]; final String thisIface = thisNet.getLinkProperties().getInterfaceName(); // if this is a default net and other default is running // kill the one not preferred - if (mNetConfigs[type].isDefault()) { - if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) { - if ((type != mNetworkPreference && - mNetConfigs[mActiveDefaultNetwork].priority > - mNetConfigs[type].priority) || - mNetworkPreference == mActiveDefaultNetwork) { - // don't accept this one - if (VDBG) { - log("Not broadcasting CONNECT_ACTION " + - "to torn down network " + info.getTypeName()); - } - teardown(thisNet); - return; - } else { + if (mNetConfigs[newNetType].isDefault()) { + if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != newNetType) { + if (isNewNetTypePreferredOverCurrentNetType(newNetType)) { // tear down the other NetworkStateTracker otherNet = mNetTrackers[mActiveDefaultNetwork]; @@ -2004,6 +2007,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { teardown(thisNet); return; } + } else { + // don't accept this one + if (VDBG) { + log("Not broadcasting CONNECT_ACTION " + + "to torn down network " + info.getTypeName()); + } + teardown(thisNet); + return; } } synchronized (ConnectivityService.this) { @@ -2017,7 +2028,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { 1000); } } - mActiveDefaultNetwork = type; + mActiveDefaultNetwork = newNetType; // this will cause us to come up initially as unconnected and switching // to connected after our normal pause unless somebody reports us as reall // disconnected @@ -2029,19 +2040,47 @@ public class ConnectivityService extends IConnectivityManager.Stub { } thisNet.setTeardownRequested(false); updateNetworkSettings(thisNet); - handleConnectivityChange(type, false); + handleConnectivityChange(newNetType, false); sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay()); // notify battery stats service about this network if (thisIface != null) { try { - BatteryStatsService.getService().noteNetworkInterfaceType(thisIface, type); + BatteryStatsService.getService().noteNetworkInterfaceType(thisIface, newNetType); } catch (RemoteException e) { // ignored; service lives in system_server } } } + private void handleCaptivePortalTrackerCheck(NetworkInfo info) { + if (DBG) log("Captive portal check " + info); + int type = info.getType(); + final NetworkStateTracker thisNet = mNetTrackers[type]; + if (mNetConfigs[type].isDefault()) { + if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) { + if (isNewNetTypePreferredOverCurrentNetType(type)) { + if (DBG) log("Captive check on " + info.getTypeName()); + mCaptivePortalTracker = CaptivePortalTracker.detect(mContext, info, + ConnectivityService.this); + return; + } else { + if (DBG) log("Tear down low priority net " + info.getTypeName()); + teardown(thisNet); + return; + } + } + } + + thisNet.captivePortalCheckComplete(); + } + + /** @hide */ + public void captivePortalCheckComplete(NetworkInfo info) { + mNetTrackers[info.getType()].captivePortalCheckComplete(); + mCaptivePortalTracker = null; + } + /** * Setup data activity tracking for the given network interface. * @@ -2630,6 +2669,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (info.getDetailedState() == NetworkInfo.DetailedState.FAILED) { handleConnectionFailure(info); + } else if (info.getDetailedState() == + DetailedState.CAPTIVE_PORTAL_CHECK) { + handleCaptivePortalTrackerCheck(info); } else if (state == NetworkInfo.State.DISCONNECTED) { handleDisconnect(info); } else if (state == NetworkInfo.State.SUSPENDED) { diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index d6fed39..9b61ec4 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -544,6 +544,11 @@ public class NotificationManagerService extends INotificationManager.Stub mInCall = (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals( TelephonyManager.EXTRA_STATE_OFFHOOK)); updateNotificationPulse(); + } else if (action.equals(Intent.ACTION_USER_STOPPED)) { + int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userHandle >= 0) { + cancelAllNotificationsUser(userHandle); + } } else if (action.equals(Intent.ACTION_USER_PRESENT)) { // turn off LED when user passes through lock screen mNotificationLight.turnOff(); @@ -619,6 +624,7 @@ public class NotificationManagerService extends INotificationManager.Stub filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); filter.addAction(Intent.ACTION_USER_PRESENT); + filter.addAction(Intent.ACTION_USER_STOPPED); mContext.registerReceiver(mIntentReceiver, filter); IntentFilter pkgFilter = new IntentFilter(); pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); @@ -1249,6 +1255,29 @@ public class NotificationManagerService extends INotificationManager.Stub } } + /** + * Cancels all notifications from a given user. + */ + boolean cancelAllNotificationsUser(int userHandle) { + synchronized (mNotificationList) { + final int N = mNotificationList.size(); + boolean canceledSomething = false; + for (int i = N-1; i >= 0; --i) { + NotificationRecord r = mNotificationList.get(i); + if (UserHandle.getUserId(r.uid) != userHandle) { + continue; + } + canceledSomething = true; + mNotificationList.remove(i); + cancelNotificationLocked(r, false); + } + if (canceledSomething) { + updateLightsLocked(); + } + return canceledSomething; + } + } + @Deprecated public void cancelNotification(String pkg, int id) { cancelNotificationWithTag(pkg, null /* tag */, id); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 7097891..48b1215 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -28,6 +28,8 @@ import android.content.pm.IPackageManager; import android.content.res.Configuration; import android.media.AudioService; import android.net.wifi.p2p.WifiP2pService; +import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.RemoteException; import android.os.SchedulingPolicyService; @@ -147,7 +149,52 @@ class ServerThread extends Thread { CommonTimeManagementService commonTimeMgmtService = null; InputManagerService inputManager = null; + // Create a shared handler thread for UI within the system server. + // This thread is used by at least the following components: + // - WindowManagerPolicy + // - KeyguardViewManager + // - DisplayManagerService + HandlerThread uiHandlerThread = new HandlerThread("UI"); + uiHandlerThread.start(); + Handler uiHandler = new Handler(uiHandlerThread.getLooper()); + uiHandler.post(new Runnable() { + @Override + public void run() { + //Looper.myLooper().setMessageLogging(new LogPrinter( + // Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM)); + android.os.Process.setThreadPriority( + android.os.Process.THREAD_PRIORITY_FOREGROUND); + android.os.Process.setCanSelfBackground(false); + + // For debug builds, log event loop stalls to dropbox for analysis. + if (StrictMode.conditionallyEnableDebugLogging()) { + Slog.i(TAG, "Enabled StrictMode logging for UI Looper"); + } + } + }); + + // Create a handler thread just for the window manager to enjoy. + HandlerThread wmHandlerThread = new HandlerThread("WindowManager"); + wmHandlerThread.start(); + Handler wmHandler = new Handler(wmHandlerThread.getLooper()); + wmHandler.post(new Runnable() { + @Override + public void run() { + //Looper.myLooper().setMessageLogging(new LogPrinter( + // android.util.Log.DEBUG, TAG, android.util.Log.LOG_ID_SYSTEM)); + android.os.Process.setThreadPriority( + android.os.Process.THREAD_PRIORITY_DISPLAY); + android.os.Process.setCanSelfBackground(false); + + // For debug builds, log event loop stalls to dropbox for analysis. + if (StrictMode.conditionallyEnableDebugLogging()) { + Slog.i(TAG, "Enabled StrictMode logging for UI Looper"); + } + } + }); + // Critical services... + boolean onlyCore = false; try { Slog.i(TAG, "Entropy Mixer"); ServiceManager.addService("entropy", new EntropyMixer()); @@ -160,7 +207,7 @@ class ServerThread extends Thread { context = ActivityManagerService.main(factoryTest); Slog.i(TAG, "Display Manager"); - display = new DisplayManagerService(context); + display = new DisplayManagerService(context, uiHandler); ServiceManager.addService(Context.DISPLAY_SERVICE, display, true); Slog.i(TAG, "Telephony Registry"); @@ -172,10 +219,14 @@ class ServerThread extends Thread { AttributeCache.init(context); + if (!display.waitForDefaultDisplay()) { + reportWtf("Timeout waiting for default display to be initialized.", + new Throwable()); + } + Slog.i(TAG, "Package Manager"); // Only run "core" apps if we're encrypting the device. String cryptState = SystemProperties.get("vold.decrypt"); - boolean onlyCore = false; if (ENCRYPTING_STATE.equals(cryptState)) { Slog.w(TAG, "Detected encryption in progress - only parsing core apps"); onlyCore = true; @@ -244,6 +295,7 @@ class ServerThread extends Thread { Slog.i(TAG, "Window Manager"); wm = WindowManagerService.main(context, power, display, + uiHandler, wmHandler, factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL, !firstBoot, onlyCore); ServiceManager.addService(Context.WINDOW_SERVICE, wm); @@ -753,6 +805,12 @@ class ServerThread extends Thread { reportWtf("making Package Manager Service ready", e); } + try { + display.systemReady(safeMode, onlyCore); + } catch (Throwable e) { + reportWtf("making Display Manager Service ready", e); + } + // These are needed to propagate to the runnable below. final Context contextF = context; final BatteryService batteryF = battery; diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java index afd7d0e..7ed698b 100644 --- a/services/java/com/android/server/WallpaperManagerService.java +++ b/services/java/com/android/server/WallpaperManagerService.java @@ -19,6 +19,7 @@ package com.android.server; import static android.os.FileObserver.*; import static android.os.ParcelFileDescriptor.*; +import android.app.AppGlobals; import android.app.IWallpaperManager; import android.app.IWallpaperManagerCallback; import android.app.PendingIntent; @@ -31,6 +32,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; @@ -146,6 +148,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { final Context mContext; final IWindowManager mIWindowManager; + final IPackageManager mIPackageManager; final MyPackageMonitor mMonitor; WallpaperData mLastWallpaper; @@ -389,6 +392,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { mContext = context; mIWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService(Context.WINDOW_SERVICE)); + mIPackageManager = AppGlobals.getPackageManager(); mMonitor = new MyPackageMonitor(); mMonitor.register(context, null, true); WALLPAPER_BASE_DIR.mkdirs(); @@ -710,8 +714,9 @@ class WallpaperManagerService extends IWallpaperManager.Stub { if (DEBUG) Slog.v(TAG, "Using image wallpaper"); } } - ServiceInfo si = mContext.getPackageManager().getServiceInfo(componentName, - PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS); + int serviceUserId = wallpaper.userId; + ServiceInfo si = mIPackageManager.getServiceInfo(componentName, + PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId); if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) { String msg = "Selected service does not require " + android.Manifest.permission.BIND_WALLPAPER @@ -728,8 +733,10 @@ class WallpaperManagerService extends IWallpaperManager.Stub { Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); if (componentName != null && !componentName.equals(wallpaper.imageWallpaperComponent)) { // Make sure the selected service is actually a wallpaper service. - List<ResolveInfo> ris = mContext.getPackageManager() - .queryIntentServices(intent, PackageManager.GET_META_DATA); + List<ResolveInfo> ris = + mIPackageManager.queryIntentServices(intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + PackageManager.GET_META_DATA, serviceUserId); for (int i=0; i<ris.size(); i++) { ServiceInfo rsi = ris.get(i).serviceInfo; if (rsi.name.equals(si.name) && @@ -767,7 +774,6 @@ class WallpaperManagerService extends IWallpaperManager.Stub { if (DEBUG) Slog.v(TAG, "Binding to:" + componentName); WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper); intent.setComponent(componentName); - int serviceUserId = wallpaper.userId; intent.putExtra(Intent.EXTRA_CLIENT_LABEL, com.android.internal.R.string.wallpaper_binding_label); intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( @@ -800,8 +806,8 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } } catch (RemoteException e) { } - } catch (PackageManager.NameNotFoundException e) { - String msg = "Unknown component " + componentName; + } catch (RemoteException e) { + String msg = "Remote exception for " + componentName + "\n" + e; if (fromUser) { throw new IllegalArgumentException(msg); } diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java index 9edfad6..2c5038e 100644 --- a/services/java/com/android/server/Watchdog.java +++ b/services/java/com/android/server/Watchdog.java @@ -26,6 +26,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Build; import android.os.Debug; import android.os.Handler; import android.os.Message; @@ -39,6 +40,8 @@ import android.util.Log; import android.util.Slog; import java.io.File; +import java.io.FileWriter; +import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; @@ -429,11 +432,10 @@ public class Watchdog extends Thread { } // If we got here, that means that the system is most likely hung. - // First collect stack traces from all threads of the system process. - // Then kill this process so that the system will restart. final String name = (mCurrentMonitor != null) ? mCurrentMonitor.getClass().getName() : "null"; + Slog.w(TAG, "WATCHDOG PROBLEM IN SYSTEM SERVER: " + name); EventLog.writeEvent(EventLogTags.WATCHDOG, name); ArrayList<Integer> pids = new ArrayList<Integer>(); @@ -468,11 +470,15 @@ public class Watchdog extends Thread { dropboxThread.join(2000); // wait up to 2 seconds for it to return. } catch (InterruptedException ignored) {} - // Only kill the process if the debugger is not attached. + // Only kill/crash the process if the debugger is not attached. if (!Debug.isDebuggerConnected()) { Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + name); - Process.killProcess(Process.myPid()); - System.exit(10); + if (!Build.TYPE.equals("user")) { + forceCrashDump(); + } else { + Process.killProcess(Process.myPid()); + System.exit(10); + } } else { Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process"); } @@ -481,6 +487,50 @@ public class Watchdog extends Thread { } } + private void forceCrashDump() { + /* Sync file system to flash the data which is written just before the + * crash. + */ + java.lang.Process p = null; + try { + p = Runtime.getRuntime().exec("sync"); + if (p != null) { + // It is not necessary to check the exit code, here. + // 'sync' command always succeeds, and this function returns 0. + p.waitFor(); + } else { + Slog.e(TAG, "Failed to execute 'sync' command. (no process handle)"); + } + } catch (Exception e) { + // This code is an emergency path to crash MUT. The system already + // caused fatal error, and then calls this function to create a + // crash dump. This function must run the code below to force a + // crash, even if the sync command failed. + // Therefore, ignore all exceptions, here. + Slog.e(TAG, "Failed to execute 'sync' command prior to forcing crash: " + e); + } finally { + if (p != null) { + p.destroy(); + } + } + + FileWriter out = null; + try { + out = new FileWriter("/proc/sysrq-trigger"); + out.write("c"); + } catch (IOException e) { + Slog.e(TAG, "Failed to write to sysrq-trigger while triggering crash: " + e); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + Slog.e(TAG, "Failed to close sysrq-trigger while triggering crash: " + e); + } + } + } + } + private File dumpKernelStackTraces() { String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null); if (tracesPath == null || tracesPath.length() == 0) { diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index f483576..6bc5e10 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -240,7 +240,7 @@ public class WifiService extends IWifiManager.Stub { switch (msg.what) { case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: { if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { - Slog.d(TAG, "New client listening to asynchronous messages"); + if (DBG) Slog.d(TAG, "New client listening to asynchronous messages"); mClients.add((AsyncChannel) msg.obj); } else { Slog.e(TAG, "Client connection failure, error=" + msg.arg1); @@ -249,9 +249,9 @@ public class WifiService extends IWifiManager.Stub { } case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) { - Slog.d(TAG, "Send failed, client connection lost"); + if (DBG) Slog.d(TAG, "Send failed, client connection lost"); } else { - Slog.d(TAG, "Client connection lost with reason: " + msg.arg1); + if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1); } mClients.remove((AsyncChannel) msg.obj); break; @@ -412,6 +412,7 @@ public class WifiService extends IWifiManager.Stub { switch(mNetworkInfo.getDetailedState()) { case CONNECTED: case DISCONNECTED: + case CAPTIVE_PORTAL_CHECK: evaluateTrafficStatsPolling(); resetNotification(); break; @@ -606,6 +607,12 @@ public class WifiService extends IWifiManager.Stub { "WifiService"); } + private void enforceConnectivityInternalPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CONNECTIVITY_INTERNAL, + "ConnectivityService"); + } + /** * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)} * @param enable {@code true} to enable, {@code false} to disable. @@ -910,7 +917,7 @@ public class WifiService extends IWifiManager.Stub { * */ public void startWifi() { - enforceChangePermission(); + enforceConnectivityInternalPermission(); /* TODO: may be add permissions for access only to connectivity service * TODO: if a start issued, keep wifi alive until a stop issued irrespective * of WifiLock & device idle status unless wifi enabled status is toggled @@ -920,20 +927,24 @@ public class WifiService extends IWifiManager.Stub { mWifiStateMachine.reconnectCommand(); } + public void captivePortalCheckComplete() { + enforceConnectivityInternalPermission(); + mWifiStateMachine.captivePortalCheckComplete(); + } + /** * see {@link android.net.wifi.WifiManager#stopWifi} * */ public void stopWifi() { - enforceChangePermission(); - /* TODO: may be add permissions for access only to connectivity service + enforceConnectivityInternalPermission(); + /* * TODO: if a stop is issued, wifi is brought up only by startWifi * unless wifi enabled status is toggled */ mWifiStateMachine.setDriverStart(false, mEmergencyCallbackMode); } - /** * see {@link android.net.wifi.WifiManager#addToBlacklist} * diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java index ca7faa2..b0dfa80 100644 --- a/services/java/com/android/server/am/ActiveServices.java +++ b/services/java/com/android/server/am/ActiveServices.java @@ -989,6 +989,17 @@ public class ActiveServices { // restarting state. mRestartingServices.remove(r); + // Make sure that the user who owns this service is started. If not, + // we don't want to allow it to run. + if (mAm.mStartedUsers.get(r.userId) == null) { + Slog.w(TAG, "Unable to launch app " + + r.appInfo.packageName + "/" + + r.appInfo.uid + " for service " + + r.intent.getIntent() + ": user " + r.userId + " is stopped"); + bringDownServiceLocked(r, true); + return false; + } + // Service is now being launched, its package can't be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( @@ -1509,7 +1520,7 @@ public class ActiveServices { boolean didSomething = false; ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); for (ServiceRecord service : mServiceMap.getAllServices(userId)) { - if (service.packageName.equals(name) + if ((name == null || service.packageName.equals(name)) && (service.app == null || evenPersistent || !service.app.persistent)) { if (!doit) { return true; diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 2b4f8b1..a061d58 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -46,6 +46,7 @@ import android.app.IInstrumentationWatcher; import android.app.INotificationManager; import android.app.IProcessObserver; import android.app.IServiceConnection; +import android.app.IStopUserCallback; import android.app.IThumbnailReceiver; import android.app.Instrumentation; import android.app.Notification; @@ -428,6 +429,11 @@ public final class ActivityManagerService extends ActivityManagerNative long mPreviousProcessVisibleTime; /** + * Which uses have been started, so are allowed to run code. + */ + final SparseArray<UserStartedState> mStartedUsers = new SparseArray<UserStartedState>(); + + /** * Packages that the user has asked to have run in screen size * compatibility mode instead of filling the screen. */ @@ -791,7 +797,6 @@ public final class ActivityManagerService extends ActivityManagerNative static ActivityThread mSystemThread; private int mCurrentUserId; - private SparseIntArray mLoggedInUsers = new SparseIntArray(5); private UserManager mUserManager; private final class AppDeathRecipient implements IBinder.DeathRecipient { @@ -1506,6 +1511,9 @@ public final class ActivityManagerService extends ActivityManagerNative systemDir, "usagestats").toString()); mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0")); + // User 0 is the first and only user that runs at boot. + mStartedUsers.put(0, new UserStartedState(new UserHandle(0), true)); + GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", ConfigurationInfo.GL_ES_VERSION_UNDEFINED); @@ -1983,13 +1991,19 @@ public final class ActivityManagerService extends ActivityManagerNative try { final PackageManager pm = mContext.getPackageManager(); gids = pm.getPackageGids(app.info.packageName); + + if (Environment.isExternalStorageEmulated()) { + if (pm.checkPermission( + android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE, + app.info.packageName) == PERMISSION_GRANTED) { + mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL; + } else { + mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER; + } + } } catch (PackageManager.NameNotFoundException e) { Slog.w(TAG, "Unable to retrieve gids", e); } - - if (Environment.isExternalStorageEmulated()) { - mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER; - } } if (mFactoryTest != SystemServer.FACTORY_TEST_OFF) { if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL @@ -2095,7 +2109,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - boolean startHomeActivityLocked(int userId) { + boolean startHomeActivityLocked(int userId, UserStartedState startingUser) { if (mHeadless) { // Added because none of the other calls to ensureBootCompleted seem to fire // when running headless. @@ -2135,6 +2149,9 @@ public final class ActivityManagerService extends ActivityManagerNative null, null, 0, 0, 0, 0, null, false, null); } } + if (startingUser != null) { + mMainStack.addStartingUserLocked(startingUser); + } return true; } @@ -3454,7 +3471,7 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, "Invalid packageName: " + packageName); return; } - killPackageProcessesLocked(packageName, pkgUid, + killPackageProcessesLocked(packageName, pkgUid, -1, ProcessList.SERVICE_ADJ, false, true, true, false, "kill background"); } } finally { @@ -3650,7 +3667,8 @@ public final class ActivityManagerService extends ActivityManagerNative } private void forceStopPackageLocked(final String packageName, int uid) { - forceStopPackageLocked(packageName, uid, false, false, true, false, UserHandle.getUserId(uid)); + forceStopPackageLocked(packageName, uid, false, false, true, false, + UserHandle.getUserId(uid)); Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED, Uri.fromParts("package", packageName, null)); if (!mProcessesReady) { @@ -3662,16 +3680,27 @@ public final class ActivityManagerService extends ActivityManagerNative false, false, MY_PID, Process.SYSTEM_UID, UserHandle.getUserId(uid)); } - + + private void forceStopUserLocked(int userId) { + forceStopPackageLocked(null, -1, false, false, true, false, userId); + Intent intent = new Intent(Intent.ACTION_USER_STOPPED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + broadcastIntentLocked(null, null, intent, + null, null, 0, null, null, null, + false, false, + MY_PID, Process.SYSTEM_UID, userId); + } + private final boolean killPackageProcessesLocked(String packageName, int uid, - int minOomAdj, boolean callerWillRestart, boolean allowRestart, boolean doit, - boolean evenPersistent, String reason) { + int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart, + boolean doit, boolean evenPersistent, String reason) { ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>(); // Remove all processes this package may have touched: all with the // same UID (except for the system or root user), and all whose name // matches the package name. - final String procNamePrefix = packageName + ":"; + final String procNamePrefix = packageName != null ? (packageName + ":") : null; for (SparseArray<ProcessRecord> apps : mProcessNames.getMap().values()) { final int NA = apps.size(); for (int ia=0; ia<NA; ia++) { @@ -3684,6 +3713,18 @@ public final class ActivityManagerService extends ActivityManagerNative if (doit) { procs.add(app); } + // If no package is specified, we call all processes under the + // give user id. + } else if (packageName == null) { + if (app.userId == userId) { + if (app.setAdj >= minOomAdj) { + if (!doit) { + return true; + } + app.removed = true; + procs.add(app); + } + } // If uid is specified and the uid and process name match // Or, the uid is not specified and the process name matches } else if (((uid > 0 && uid != Process.SYSTEM_UID && app.info.uid == uid) @@ -3714,7 +3755,7 @@ public final class ActivityManagerService extends ActivityManagerNative int i; int N; - if (uid < 0) { + if (uid < 0 && name != null) { try { uid = AppGlobals.getPackageManager().getPackageUid(name, userId); } catch (RemoteException e) { @@ -3722,24 +3763,45 @@ public final class ActivityManagerService extends ActivityManagerNative } if (doit) { - Slog.i(TAG, "Force stopping package " + name + " uid=" + uid); + if (name != null) { + Slog.i(TAG, "Force stopping package " + name + " uid=" + uid); + } else { + Slog.i(TAG, "Force stopping user " + userId); + } Iterator<SparseArray<Long>> badApps = mProcessCrashTimes.getMap().values().iterator(); while (badApps.hasNext()) { SparseArray<Long> ba = badApps.next(); - if (ba.get(uid) != null) { + for (i=ba.size()-1; i>=0; i--) { + boolean remove = false; + final int entUid = ba.keyAt(i); + if (name != null) { + if (entUid == uid) { + remove = true; + } + } else if (UserHandle.getUserId(entUid) == userId) { + remove = true; + } + if (remove) { + ba.removeAt(i); + } + } + if (ba.size() == 0) { badApps.remove(); } } } - - boolean didSomething = killPackageProcessesLocked(name, uid, -100, - callerWillRestart, false, doit, evenPersistent, "force stop"); + + boolean didSomething = killPackageProcessesLocked(name, uid, + name == null ? userId : -1 , -100, callerWillRestart, false, + doit, evenPersistent, + name == null ? ("force stop user " + userId) : ("force stop " + name)); TaskRecord lastTask = null; for (i=0; i<mMainStack.mHistory.size(); i++) { ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); - final boolean samePackage = r.packageName.equals(name); + final boolean samePackage = r.packageName.equals(name) + || (name == null && r.userId == userId); if (r.userId == userId && (samePackage || r.task == lastTask) && (r.app == null || evenPersistent || !r.app.persistent)) { @@ -3776,7 +3838,7 @@ public final class ActivityManagerService extends ActivityManagerNative ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>(); for (ContentProviderRecord provider : mProviderMap.getProvidersByClass(userId).values()) { - if (provider.info.packageName.equals(name) + if ((name == null || provider.info.packageName.equals(name)) && (provider.proc == null || evenPersistent || !provider.proc.persistent)) { if (!doit) { return true; @@ -3792,7 +3854,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (doit) { - if (purgeCache) { + if (purgeCache && name != null) { AttributeCache ac = AttributeCache.instance(); if (ac != null) { ac.removePackage(name); @@ -4197,15 +4259,6 @@ public final class ActivityManagerService extends ActivityManagerNative } }, pkgFilter); - 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); - synchronized (this) { // Ensure that any processes we had put on hold are now started // up. @@ -4227,13 +4280,17 @@ public final class ActivityManagerService extends ActivityManagerNative // Tell anyone interested that we are done booting! SystemProperties.set("sys.boot_completed", "1"); SystemProperties.set("dev.bootcomplete", "1"); - List<UserInfo> users = getUserManager().getUsers(); - for (UserInfo user : users) { - broadcastIntentLocked(null, null, - new Intent(Intent.ACTION_BOOT_COMPLETED, null), - null, null, 0, null, null, - android.Manifest.permission.RECEIVE_BOOT_COMPLETED, - false, false, MY_PID, Process.SYSTEM_UID, user.id); + for (int i=0; i<mStartedUsers.size(); i++) { + UserStartedState uss = mStartedUsers.valueAt(i); + if (uss.mState == UserStartedState.STATE_BOOTING) { + uss.mState = UserStartedState.STATE_RUNNING; + broadcastIntentLocked(null, null, + new Intent(Intent.ACTION_BOOT_COMPLETED, null), + null, null, 0, null, null, + android.Manifest.permission.RECEIVE_BOOT_COMPLETED, + false, false, MY_PID, Process.SYSTEM_UID, + mStartedUsers.keyAt(i)); + } } } } @@ -4430,7 +4487,8 @@ public final class ActivityManagerService extends ActivityManagerNative PendingIntentRecord.Key key = new PendingIntentRecord.Key( type, packageName, activity, resultWho, - requestCode, intents, resolvedTypes, flags, options); + requestCode, intents, resolvedTypes, flags, options, + UserHandle.getUserId(callingUid)); WeakReference<PendingIntentRecord> ref; ref = mIntentSenderRecords.get(key); PendingIntentRecord rec = ref != null ? ref.get() : null; @@ -6296,6 +6354,16 @@ public final class ActivityManagerService extends ActivityManagerNative "Attempt to launch content provider before system ready"); } + // Make sure that the user who owns this provider is started. If not, + // we don't want to allow it to run. + if (mStartedUsers.get(userId) == null) { + Slog.w(TAG, "Unable to launch app " + + cpi.applicationInfo.packageName + "/" + + cpi.applicationInfo.uid + " for provider " + + name + ": user " + userId + " is stopped"); + return null; + } + ComponentName comp = new ComponentName(cpi.packageName, cpi.name); cpr = mProviderMap.getProviderByClass(comp, userId); final boolean firstClass = cpr == null; @@ -9047,6 +9115,13 @@ public final class ActivityManagerService extends ActivityManagerNative } pw.println(); + pw.println(" mStartedUsers:"); + for (int i=0; i<mStartedUsers.size(); i++) { + UserStartedState uss = mStartedUsers.valueAt(i); + pw.print(" User #"); pw.print(uss.mHandle.getIdentifier()); + pw.println(":"); + uss.dump(" ", pw); + } pw.println(" mHomeProcess: " + mHomeProcess); pw.println(" mPreviousProcess: " + mPreviousProcess); if (dumpAll) { @@ -9404,9 +9479,15 @@ public final class ActivityManagerService extends ActivityManagerNative boolean dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage) { boolean needSep = false; - + boolean onlyHistory = false; + + if ("history".equals(dumpPackage)) { + onlyHistory = true; + dumpPackage = null; + } + pw.println("ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)"); - if (dumpAll) { + if (!onlyHistory && dumpAll) { if (mRegisteredReceivers.size() > 0) { boolean printed = false; Iterator it = mRegisteredReceivers.values().iterator(); @@ -9439,7 +9520,7 @@ public final class ActivityManagerService extends ActivityManagerNative needSep = true; - if (mStickyBroadcasts != null && dumpPackage == null) { + if (!onlyHistory && mStickyBroadcasts != null && dumpPackage == null) { if (needSep) { pw.println(); } @@ -9471,7 +9552,7 @@ public final class ActivityManagerService extends ActivityManagerNative needSep = true; } - if (dumpAll) { + if (!onlyHistory && dumpAll) { pw.println(); for (BroadcastQueue queue : mBroadcastQueues) { pw.println(" mBroadcastsScheduled [" + queue.mQueueName + "]=" @@ -10667,7 +10748,25 @@ public final class ActivityManagerService extends ActivityManagerNative throw new IllegalArgumentException("File descriptors passed in Intent"); } - checkValidCaller(Binder.getCallingUid(), userId); + if (userId != UserHandle.getCallingUserId()) { + // Requesting a different user, make sure that they have permission + if (checkComponentPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + Binder.getCallingPid(), Binder.getCallingUid(), -1, true) + == PackageManager.PERMISSION_GRANTED) { + // Translate to the current user id, if caller wasn't aware + if (userId == UserHandle.USER_CURRENT) { + userId = mCurrentUserId; + } + } else { + String msg = "Permission Denial: Request to bindService as user " + userId + + " but is calling from user " + UserHandle.getCallingUserId() + + "; this requires " + + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + } synchronized(this) { return mServices.bindServiceLocked(caller, token, service, resolvedType, @@ -10982,7 +11081,7 @@ public final class ActivityManagerService extends ActivityManagerNative BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, null, null, -1, -1, null, receivers, null, 0, null, null, - false, true, true, false); + false, true, true, -1); queue.enqueueParallelBroadcastLocked(r); queue.scheduleBroadcastsLocked(); } @@ -11075,33 +11174,39 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!"); } - boolean onlySendToCaller = false; - // If the caller is trying to send this broadcast to a different // user, verify that is allowed. if (UserHandle.getUserId(callingUid) != userId) { if (checkComponentPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - callingPid, callingUid, -1, true) - != PackageManager.PERMISSION_GRANTED) { - if (checkComponentPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS, - callingPid, callingUid, -1, true) - == PackageManager.PERMISSION_GRANTED) { - onlySendToCaller = true; - } else { - String msg = "Permission Denial: " + intent.getAction() - + " broadcast from " + callerPackage - + " asks to send as user " + userId - + " but is calling from user " + UserHandle.getUserId(callingUid) - + "; this requires " - + android.Manifest.permission.INTERACT_ACROSS_USERS; - Slog.w(TAG, msg); - throw new SecurityException(msg); + android.Manifest.permission.INTERACT_ACROSS_USERS, + callingPid, callingUid, -1, true) != PackageManager.PERMISSION_GRANTED + && checkComponentPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + callingPid, callingUid, -1, true) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: " + intent.getAction() + + " broadcast from " + callerPackage + + " asks to send as user " + userId + + " but is calling from user " + UserHandle.getUserId(callingUid) + + "; this requires " + + android.Manifest.permission.INTERACT_ACROSS_USERS; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } else { + if (userId == UserHandle.USER_CURRENT) { + userId = mCurrentUserId; } } } + // Make sure that the user who is receiving this broadcast is started + // If not, we will just skip it. + if (mStartedUsers.get(userId) == null) { + Slog.w(TAG, "Skipping broadcast of " + intent + + ": user " + userId + " is stopped"); + return ActivityManager.BROADCAST_SUCCESS; + } + // Handle special intents: if this broadcast is from the package // manager about a package being removed, we need to remove all of // its activities from the history stack. @@ -11288,7 +11393,7 @@ public final class ActivityManagerService extends ActivityManagerNative BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, registeredReceivers, resultTo, resultCode, resultData, map, - ordered, sticky, false, onlySendToCaller); + ordered, sticky, false, userId); if (DEBUG_BROADCAST) Slog.v( TAG, "Enqueueing parallel broadcast " + r); final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r); @@ -11378,7 +11483,7 @@ public final class ActivityManagerService extends ActivityManagerNative BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, receivers, resultTo, resultCode, resultData, map, ordered, - sticky, false, onlySendToCaller); + sticky, false, userId); if (DEBUG_BROADCAST) Slog.v( TAG, "Enqueueing ordered broadcast " + r + ": prev had " + queue.mOrderedBroadcasts.size()); @@ -11644,7 +11749,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.instrumentationProfileFile = null; app.instrumentationArguments = null; - forceStopPackageLocked(app.processName, -1, false, false, true, true, app.userId); + forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, app.userId); } public void finishInstrumentation(IApplicationThread target, @@ -13486,75 +13591,174 @@ public final class ActivityManagerService extends ActivityManagerNative // Multi-user methods + @Override public boolean switchUser(int userId) { - final int callingUid = Binder.getCallingUid(); - if (callingUid != 0 && callingUid != Process.myUid()) { - Slog.e(TAG, "Trying to switch user from unauthorized app"); - return false; + if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: switchUser() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + throw new SecurityException(msg); } - if (mCurrentUserId == userId) - return true; - synchronized (this) { - // Check if user is already logged in, otherwise check if user exists first before - // adding to the list of logged in users. - if (mLoggedInUsers.indexOfKey(userId) < 0) { - if (!userExists(userId)) { - return false; - } - mLoggedInUsers.append(userId, userId); + if (mCurrentUserId == userId) { + return true; + } + + // If the user we are switching to is not currently started, then + // we need to start it now. + if (mStartedUsers.get(userId) == null) { + mStartedUsers.put(userId, new UserStartedState(new UserHandle(userId), false)); } mCurrentUserId = userId; boolean haveActivities = mMainStack.switchUser(userId); if (!haveActivities) { - startHomeActivityLocked(userId); + startHomeActivityLocked(userId, mStartedUsers.get(userId)); } - } - // Inform of user switch - Intent addedIntent = new Intent(Intent.ACTION_USER_SWITCHED); - addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); - mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_USERS); + long ident = Binder.clearCallingIdentity(); + try { + // Inform of user switch + Intent addedIntent = new Intent(Intent.ACTION_USER_SWITCHED); + addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_USERS); + } finally { + Binder.restoreCallingIdentity(ident); + } return true; } - @Override - public UserInfo getCurrentUser() throws RemoteException { - final int callingUid = Binder.getCallingUid(); - if (callingUid != 0 && callingUid != Process.myUid()) { - Slog.e(TAG, "Trying to get user from unauthorized app"); - return null; + void finishUserSwitch(UserStartedState uss) { + synchronized (this) { + if (uss.mState == UserStartedState.STATE_BOOTING + && mStartedUsers.get(uss.mHandle.getIdentifier()) == uss) { + uss.mState = UserStartedState.STATE_RUNNING; + broadcastIntentLocked(null, null, + new Intent(Intent.ACTION_BOOT_COMPLETED, null), + null, null, 0, null, null, + android.Manifest.permission.RECEIVE_BOOT_COMPLETED, + false, false, MY_PID, Process.SYSTEM_UID, uss.mHandle.getIdentifier()); + } } - return getUserManager().getUserInfo(mCurrentUserId); } - private void onUserRemoved(Intent intent) { - int extraUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - if (extraUserId < 1) return; - - // Kill all the processes for the user - ArrayList<Pair<String, Integer>> pkgAndUids = new ArrayList<Pair<String,Integer>>(); + @Override + public int stopUser(final int userId, final IStopUserCallback callback) { + if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: switchUser() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + if (userId <= 0) { + throw new IllegalArgumentException("Can't stop primary user " + userId); + } synchronized (this) { - HashMap<String,SparseArray<ProcessRecord>> map = mProcessNames.getMap(); - for (Entry<String, SparseArray<ProcessRecord>> uidMap : map.entrySet()) { - SparseArray<ProcessRecord> uids = uidMap.getValue(); - for (int i = 0; i < uids.size(); i++) { - if (UserHandle.getUserId(uids.keyAt(i)) == extraUserId) { - pkgAndUids.add(new Pair<String,Integer>(uidMap.getKey(), uids.keyAt(i))); - } + if (mCurrentUserId == userId) { + return ActivityManager.USER_OP_IS_CURRENT; + } + + final UserStartedState uss = mStartedUsers.get(userId); + if (uss == null) { + // User is not started, nothing to do... but we do need to + // callback if requested. + if (callback != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + try { + callback.userStopped(userId); + } catch (RemoteException e) { + } + } + }); } + return ActivityManager.USER_OP_SUCCESS; + } + + if (callback != null) { + uss.mStopCallbacks.add(callback); + } + + if (uss.mState != UserStartedState.STATE_STOPPING) { + uss.mState = UserStartedState.STATE_STOPPING; + + long ident = Binder.clearCallingIdentity(); + try { + // Inform of user switch + Intent intent = new Intent(Intent.ACTION_SHUTDOWN); + final IIntentReceiver resultReceiver = new IIntentReceiver.Stub() { + @Override + public void performReceive(Intent intent, int resultCode, String data, + Bundle extras, boolean ordered, boolean sticky) { + finishUserStop(uss); + } + }; + broadcastIntentLocked(null, null, intent, + null, resultReceiver, 0, null, null, null, + true, false, MY_PID, Process.SYSTEM_UID, userId); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + return ActivityManager.USER_OP_SUCCESS; + } + + void finishUserStop(UserStartedState uss) { + final int userId = uss.mHandle.getIdentifier(); + boolean stopped; + ArrayList<IStopUserCallback> callbacks; + synchronized (this) { + callbacks = new ArrayList<IStopUserCallback>(uss.mStopCallbacks); + if (uss.mState != UserStartedState.STATE_STOPPING + || mStartedUsers.get(userId) != uss) { + stopped = false; + } else { + stopped = true; + // User can no longer run. + mStartedUsers.remove(userId); + + // Clean up all state and processes associated with the user. + // Kill all the processes for the user. + forceStopUserLocked(userId); } + } - for (Pair<String,Integer> pkgAndUid : pkgAndUids) { - forceStopPackageLocked(pkgAndUid.first, pkgAndUid.second, - false, false, true, true, extraUserId); + for (int i=0; i<callbacks.size(); i++) { + try { + if (stopped) callbacks.get(i).userStopped(userId); + else callbacks.get(i).userStopAborted(userId); + } catch (RemoteException e) { } } } + @Override + public UserInfo getCurrentUser() { + if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: getCurrentUser() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + synchronized (this) { + return getUserManager().getUserInfo(mCurrentUserId); + } + } + private boolean userExists(int userId) { UserInfo user = getUserManager().getUserInfo(userId); return user != null; diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index ccea41a..a389edc 100755 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -211,7 +211,10 @@ final class ActivityStack { */ final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible = new ArrayList<IActivityManager.WaitResult>(); - + + final ArrayList<UserStartedState> mStartingUsers + = new ArrayList<UserStartedState>(); + /** * Set when the system is going to sleep, until we have * successfully paused the current activity and released our wake lock. @@ -1397,7 +1400,7 @@ final class ActivityStack { // Launcher... if (mMainStack) { ActivityOptions.abort(options); - return mService.startHomeActivityLocked(0); + return mService.startHomeActivityLocked(0, null); } } @@ -1427,7 +1430,16 @@ final class ActivityStack { ActivityOptions.abort(options); return false; } - + + // Make sure that the user who owns this activity is started. If not, + // we will just leave it as is because someone should be bringing + // another user's activities to the top of the stack. + if (mService.mStartedUsers.get(next.userId) == null) { + Slog.w(TAG, "Skipping resume of top activity " + next + + ": user " + next.userId + " is stopped"); + return false; + } + // The activity may be waiting for stop, but that is no longer // appropriate for it. mStoppingActivities.remove(next); @@ -1494,7 +1506,7 @@ final class ActivityStack { Slog.d(TAG, "no-history finish of " + last + " on new resume"); } requestFinishActivityLocked(last.appToken, Activity.RESULT_CANCELED, null, - "no-history"); + "no-history"); } } @@ -3414,6 +3426,7 @@ final class ActivityStack { ArrayList<ActivityRecord> stops = null; ArrayList<ActivityRecord> finishes = null; ArrayList<ActivityRecord> thumbnails = null; + ArrayList<UserStartedState> startingUsers = null; int NS = 0; int NF = 0; int NT = 0; @@ -3495,6 +3508,10 @@ final class ActivityStack { booting = mService.mBooting; mService.mBooting = false; } + if (mStartingUsers.size() > 0) { + startingUsers = new ArrayList<UserStartedState>(mStartingUsers); + mStartingUsers.clear(); + } } int i; @@ -3539,6 +3556,10 @@ final class ActivityStack { if (booting) { mService.finishBooting(); + } else if (startingUsers != null) { + for (i=0; i<startingUsers.size(); i++) { + mService.finishUserSwitch(startingUsers.get(i)); + } } mService.trimApplications(); @@ -3556,6 +3577,10 @@ final class ActivityStack { return res; } + final void addStartingUserLocked(UserStartedState uss) { + mStartingUsers.add(uss); + } + /** * @return Returns true if the activity is being finished, false if for * some reason it is being left as-is. diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java index 7873dd8..34dec3a 100644 --- a/services/java/com/android/server/am/BroadcastQueue.java +++ b/services/java/com/android/server/am/BroadcastQueue.java @@ -372,17 +372,7 @@ public class BroadcastQueue { private final void deliverToRegisteredReceiverLocked(BroadcastRecord r, BroadcastFilter filter, boolean ordered) { boolean skip = false; - if (r.onlySendToCaller) { - if (!UserHandle.isSameApp(r.callingUid, filter.owningUid)) { - Slog.w(TAG, "Permission Denial: broadcasting " - + r.intent.toString() - + " from " + r.callerPackage + " (pid=" - + r.callingPid + ", uid=" + r.callingUid + ")" - + " not allowed to go to different app " + filter.owningUid); - skip = true; - } - } - if (!skip && filter.requiredPermission != null) { + if (filter.requiredPermission != null) { int perm = mService.checkComponentPermission(filter.requiredPermission, r.callingPid, r.callingUid, -1, true); if (perm != PackageManager.PERMISSION_GRANTED) { @@ -667,18 +657,6 @@ public class BroadcastQueue { info.activityInfo.name); boolean skip = false; - if (r.onlySendToCaller) { - if (!UserHandle.isSameApp(r.callingUid, info.activityInfo.applicationInfo.uid)) { - Slog.w(TAG, "Permission Denial: broadcasting " - + r.intent.toString() - + " from " + r.callerPackage + " (pid=" - + r.callingPid + ", uid=" + r.callingUid + ")" - + " to " + component.flattenToShortString() - + " not allowed to go to different app " - + info.activityInfo.applicationInfo.uid); - skip = true; - } - } int perm = mService.checkComponentPermission(info.activityInfo.permission, r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid, info.activityInfo.exported); diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java index 799b609..ca6d5f7 100644 --- a/services/java/com/android/server/am/BroadcastRecord.java +++ b/services/java/com/android/server/am/BroadcastRecord.java @@ -44,7 +44,7 @@ class BroadcastRecord extends Binder { final boolean ordered; // serialize the send to receivers? final boolean sticky; // originated from existing sticky data? final boolean initialSticky; // initial broadcast from register to sticky? - final boolean onlySendToCaller; // only allow receipt by sender's components? + final int userId; // user id this broadcast was for final String requiredPermission; // a permission the caller has required final List receivers; // contains BroadcastFilter and ResolveInfo IIntentReceiver resultTo; // who receives final result if non-null @@ -80,7 +80,7 @@ class BroadcastRecord extends Binder { void dump(PrintWriter pw, String prefix) { final long now = SystemClock.uptimeMillis(); - pw.print(prefix); pw.println(this); + pw.print(prefix); pw.print(this); pw.print(" to user "); pw.println(userId); pw.print(prefix); pw.println(intent); if (sticky) { Bundle bundle = intent.getExtras(); @@ -141,14 +141,15 @@ class BroadcastRecord extends Binder { pw.println(curReceiver.applicationInfo.sourceDir); } } - String stateStr = " (?)"; - switch (state) { - case IDLE: stateStr=" (IDLE)"; break; - case APP_RECEIVE: stateStr=" (APP_RECEIVE)"; break; - case CALL_IN_RECEIVE: stateStr=" (CALL_IN_RECEIVE)"; break; - case CALL_DONE_RECEIVE: stateStr=" (CALL_DONE_RECEIVE)"; break; + if (state != IDLE) { + String stateStr = " (?)"; + switch (state) { + case APP_RECEIVE: stateStr=" (APP_RECEIVE)"; break; + case CALL_IN_RECEIVE: stateStr=" (CALL_IN_RECEIVE)"; break; + case CALL_DONE_RECEIVE: stateStr=" (CALL_DONE_RECEIVE)"; break; + } + pw.print(prefix); pw.print("state="); pw.print(state); pw.println(stateStr); } - pw.print(prefix); pw.print("state="); pw.print(state); pw.println(stateStr); final int N = receivers != null ? receivers.size() : 0; String p2 = prefix + " "; PrintWriterPrinter printer = new PrintWriterPrinter(pw); @@ -168,7 +169,8 @@ class BroadcastRecord extends Binder { int _callingPid, int _callingUid, String _requiredPermission, List _receivers, IIntentReceiver _resultTo, int _resultCode, String _resultData, Bundle _resultExtras, boolean _serialized, - boolean _sticky, boolean _initialSticky, boolean _onlySendToCaller) { + boolean _sticky, boolean _initialSticky, + int _userId) { queue = _queue; intent = _intent; callerApp = _callerApp; @@ -184,7 +186,7 @@ class BroadcastRecord extends Binder { ordered = _serialized; sticky = _sticky; initialSticky = _initialSticky; - onlySendToCaller = _onlySendToCaller; + userId = _userId; nextReceiver = 0; state = IDLE; } diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index d3b8510..8e70376 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -54,11 +54,12 @@ class PendingIntentRecord extends IIntentSender.Stub { String[] allResolvedTypes; final int flags; final int hashCode; + final int userId; private static final int ODD_PRIME_NUMBER = 37; Key(int _t, String _p, ActivityRecord _a, String _w, - int _r, Intent[] _i, String[] _it, int _f, Bundle _o) { + int _r, Intent[] _i, String[] _it, int _f, Bundle _o, int _userId) { type = _t; packageName = _p; activity = _a; @@ -70,10 +71,12 @@ class PendingIntentRecord extends IIntentSender.Stub { allResolvedTypes = _it; flags = _f; options = _o; - + userId = _userId; + int hash = 23; hash = (ODD_PRIME_NUMBER*hash) + _f; hash = (ODD_PRIME_NUMBER*hash) + _r; + hash = (ODD_PRIME_NUMBER*hash) + _userId; if (_w != null) { hash = (ODD_PRIME_NUMBER*hash) + _w.hashCode(); } @@ -102,6 +105,9 @@ class PendingIntentRecord extends IIntentSender.Stub { if (type != other.type) { return false; } + if (userId != other.userId){ + return false; + } if (!packageName.equals(other.packageName)) { return false; } @@ -156,7 +162,7 @@ class PendingIntentRecord extends IIntentSender.Stub { + " intent=" + (requestIntent != null ? requestIntent.toShortString(false, true, false, false) : "<null>") - + " flags=0x" + Integer.toHexString(flags) + "}"; + + " flags=0x" + Integer.toHexString(flags) + " u=" + userId + "}"; } String typeName() { diff --git a/services/java/com/android/server/am/UserStartedState.java b/services/java/com/android/server/am/UserStartedState.java new file mode 100644 index 0000000..3f3ed85 --- /dev/null +++ b/services/java/com/android/server/am/UserStartedState.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import java.io.PrintWriter; +import java.util.ArrayList; + +import android.app.IStopUserCallback; +import android.os.UserHandle; + +public class UserStartedState { + public final static int STATE_BOOTING = 0; + public final static int STATE_RUNNING = 1; + public final static int STATE_STOPPING = 2; + + public final UserHandle mHandle; + public final ArrayList<IStopUserCallback> mStopCallbacks + = new ArrayList<IStopUserCallback>(); + + public int mState = STATE_BOOTING; + + public UserStartedState(UserHandle handle, boolean initial) { + mHandle = handle; + } + + void dump(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("mState="); pw.println(mState); + } +} diff --git a/services/java/com/android/server/display/DisplayAdapter.java b/services/java/com/android/server/display/DisplayAdapter.java index f9fa7a8..d19fe01 100644 --- a/services/java/com/android/server/display/DisplayAdapter.java +++ b/services/java/com/android/server/display/DisplayAdapter.java @@ -16,33 +16,96 @@ package com.android.server.display; +import android.content.Context; +import android.os.Handler; + +import java.io.PrintWriter; + /** * A display adapter makes zero or more display devices available to the system * and provides facilities for discovering when displays are connected or disconnected. * <p> * For now, all display adapters are registered in the system server but * in principle it could be done from other processes. + * </p><p> + * Display devices are not thread-safe and must only be accessed + * on the display manager service's handler thread. * </p> */ -public abstract class DisplayAdapter { +public class DisplayAdapter { + private final Context mContext; + private final String mName; + private final Handler mHandler; + private Listener mListener; + + public static final int DISPLAY_DEVICE_EVENT_ADDED = 1; + public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2; + public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3; + + public DisplayAdapter(Context context, String name) { + mContext = context; + mName = name; + mHandler = new Handler(); + } + + public final Context getContext() { + return mContext; + } + + public final Handler getHandler() { + return mHandler; + } + /** * Gets the display adapter name for debugging purposes. * * @return The display adapter name. */ - public abstract String getName(); + public final String getName() { + return mName; + } /** * Registers the display adapter with the display manager. - * The display adapter should register any built-in display devices now. - * Other display devices can be registered dynamically later. * - * @param listener The listener for callbacks. + * @param listener The listener for callbacks. The listener will + * be invoked on the display manager service's handler thread. + */ + public final void register(Listener listener) { + mListener = listener; + onRegister(); + } + + /** + * Dumps the local state of the display adapter. + */ + public void dump(PrintWriter pw) { + } + + /** + * Called when the display adapter is registered. + * + * The display adapter should register any built-in display devices as soon as possible. + * The boot process will wait for the default display to be registered. + * + * Other display devices can be registered dynamically later. */ - public abstract void register(Listener listener); + protected void onRegister() { + } + + /** + * Sends a display device event to the display adapter listener asynchronously. + */ + protected void sendDisplayDeviceEvent(final DisplayDevice device, final int event) { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onDisplayDeviceEvent(device, event); + } + }); + } public interface Listener { - public void onDisplayDeviceAdded(DisplayDevice device); - public void onDisplayDeviceRemoved(DisplayDevice device); + public void onDisplayDeviceEvent(DisplayDevice device, int event); } } diff --git a/services/java/com/android/server/display/DisplayDevice.java b/services/java/com/android/server/display/DisplayDevice.java index 57002ff..c83ce96 100644 --- a/services/java/com/android/server/display/DisplayDevice.java +++ b/services/java/com/android/server/display/DisplayDevice.java @@ -16,17 +16,43 @@ package com.android.server.display; +import android.os.IBinder; + /** * Represents a physical display device such as the built-in display * an external monitor, or a WiFi display. + * <p> + * Display devices are not thread-safe and must only be accessed + * on the display manager service's handler thread. + * </p> */ public abstract class DisplayDevice { + private final DisplayAdapter mDisplayAdapter; + private final IBinder mDisplayToken; + + public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken) { + mDisplayAdapter = displayAdapter; + mDisplayToken = displayToken; + } + /** - * Gets the display adapter that makes the display device available. + * Gets the display adapter that owns the display device. * * @return The display adapter. */ - public abstract DisplayAdapter getAdapter(); + public final DisplayAdapter getAdapter() { + return mDisplayAdapter; + } + + /** + * Gets the Surface Flinger display token for this display. + * + * @return The display token, or null if the display is not being managed + * by Surface Flinger. + */ + public final IBinder getDisplayToken() { + return mDisplayToken; + } /** * Gets information about the display device. @@ -34,4 +60,12 @@ public abstract class DisplayDevice { * @param outInfo The object to populate with the information. */ public abstract void getInfo(DisplayDeviceInfo outInfo); + + // For debugging purposes. + @Override + public String toString() { + DisplayDeviceInfo info = new DisplayDeviceInfo(); + getInfo(info); + return info.toString() + ", owner=\"" + mDisplayAdapter.getName() + "\""; + } } diff --git a/services/java/com/android/server/display/DisplayDeviceInfo.java b/services/java/com/android/server/display/DisplayDeviceInfo.java index 9c0f964..c7b8c24 100644 --- a/services/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/java/com/android/server/display/DisplayDeviceInfo.java @@ -20,6 +20,9 @@ package com.android.server.display; * Describes the characteristics of a physical display device. */ public final class DisplayDeviceInfo { + public static final int FLAG_DEFAULT_DISPLAY = 1 << 0; + public static final int FLAG_SECURE = 1 << 1; + /** * Gets the name of the display device, which may be derived from * EDID or other sources. The name may be displayed to the user. @@ -43,6 +46,8 @@ public final class DisplayDeviceInfo { public float xDpi; public float yDpi; + public int flags; + public void copyFrom(DisplayDeviceInfo other) { name = other.name; width = other.width; @@ -51,12 +56,25 @@ public final class DisplayDeviceInfo { densityDpi = other.densityDpi; xDpi = other.xDpi; yDpi = other.yDpi; + flags = other.flags; } // For debugging purposes @Override public String toString() { return "\"" + name + "\": " + width + " x " + height + ", " + refreshRate + " fps, " - + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi"; + + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi" + + flagsToString(flags); + } + + private static String flagsToString(int flags) { + StringBuilder msg = new StringBuilder(); + if ((flags & FLAG_DEFAULT_DISPLAY) != 0) { + msg.append(", FLAG_DEFAULT_DISPLAY"); + } + if ((flags & FLAG_SECURE) != 0) { + msg.append(", FLAG_SECURE"); + } + return msg.toString(); } } diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java index 2ebad1d..cf835ef 100644 --- a/services/java/com/android/server/display/DisplayManagerService.java +++ b/services/java/com/android/server/display/DisplayManagerService.java @@ -16,57 +16,186 @@ package com.android.server.display; +import com.android.internal.util.IndentingPrintWriter; + import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; -import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.IDisplayManager; +import android.hardware.display.IDisplayManagerCallback; import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.SystemClock; import android.os.SystemProperties; +import android.util.Slog; +import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; import android.view.Surface; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayList; /** - * Manages the properties, media routing and power state of attached displays. + * Manages attached displays. * <p> - * The display manager service does not own or directly control the displays. - * Instead, other components in the system register their display adapters with the - * display manager service which acts as a central controller. + * The {@link DisplayManagerService} manages the global lifecycle of displays, + * decides how to configure logical displays based on the physical display devices currently + * attached, sends notifications to the system and to applications when the state + * changes, and so on. + * </p><p> + * The display manager service relies on a collection of {@link DisplayAdapter} components, + * for discovering and configuring physical display devices attached to the system. + * There are separate display adapters for each manner that devices are attached: + * one display adapter for built-in local displays, one for simulated non-functional + * displays when the system is headless, one for simulated overlay displays used for + * development, one for wifi displays, etc. + * </p><p> + * Display adapters are only weakly coupled to the display manager service. + * Display adapters communicate changes in display device state to the display manager + * service asynchronously via a {@link DisplayAdapter.DisplayAdapterListener} registered + * by the display manager service. This separation of concerns is important for + * two main reasons. First, it neatly encapsulates the responsibilities of these + * two classes: display adapters handle individual display devices whereas + * the display manager service handles the global state. Second, it eliminates + * the potential for deadlocks resulting from asynchronous display device discovery. + * </p><p> + * To keep things simple, display adapters and display devices are single-threaded + * and are only accessed on the display manager's handler thread. Of course, the + * display manager must be accessible by multiple thread (especially for + * incoming binder calls) so all of the display manager's state is synchronized + * and guarded by a lock. * </p> */ public final class DisplayManagerService extends IDisplayManager.Stub { private static final String TAG = "DisplayManagerService"; + private static final boolean DEBUG = false; private static final String SYSTEM_HEADLESS = "ro.config.headless"; + private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000; + + private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER = 1; + private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2; + private static final int MSG_DELIVER_DISPLAY_EVENT = 3; private final Object mLock = new Object(); private final Context mContext; private final boolean mHeadless; + private final DisplayManagerHandler mHandler; + private final DisplayAdapterListener mDisplayAdapterListener = new DisplayAdapterListener(); + private final SparseArray<CallbackRecord> mCallbacks = + new SparseArray<CallbackRecord>(); + + // List of all currently registered display adapters. private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>(); - private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo(); - public DisplayManagerService(Context context) { + // List of all currently connected display devices. + private final ArrayList<DisplayDevice> mDisplayDevices = new ArrayList<DisplayDevice>(); + + // List of all logical displays, indexed by logical display id. + private final SparseArray<LogicalDisplay> mLogicalDisplays = new SparseArray<LogicalDisplay>(); + private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1; + + // True if in safe mode. + // This option may disable certain display adapters. + private boolean mSafeMode; + + // True if we are in a special boot mode where only core applications and + // services should be started. This option may disable certain display adapters. + private boolean mOnlyCore; + + // Temporary callback list, used when sending display events to applications. + private ArrayList<CallbackRecord> mTempCallbacks = new ArrayList<CallbackRecord>(); + + public DisplayManagerService(Context context, Handler uiHandler) { mContext = context; mHeadless = SystemProperties.get(SYSTEM_HEADLESS).equals("1"); - registerDefaultDisplayAdapter(); + mHandler = new DisplayManagerHandler(uiHandler.getLooper()); + mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER); + } + + /** + * Pauses the boot process to wait for the first display to be initialized. + */ + public boolean waitForDefaultDisplay() { + synchronized (mLock) { + long timeout = SystemClock.uptimeMillis() + WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT; + while (mLogicalDisplays.get(Display.DEFAULT_DISPLAY) == null) { + long delay = timeout - SystemClock.uptimeMillis(); + if (delay <= 0) { + return false; + } + if (DEBUG) { + Slog.d(TAG, "waitForDefaultDisplay: waiting, timeout=" + delay); + } + try { + mLock.wait(delay); + } catch (InterruptedException ex) { + } + } + } + return true; + } + + /** + * Called when the system is ready to go. + */ + public void systemReady(boolean safeMode, boolean onlyCore) { + synchronized (mLock) { + mSafeMode = safeMode; + mOnlyCore = onlyCore; + } + mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS); } + // Runs on handler. private void registerDefaultDisplayAdapter() { + // Register default display adapter. if (mHeadless) { registerDisplayAdapter(new HeadlessDisplayAdapter(mContext)); } else { - registerDisplayAdapter(new SurfaceFlingerDisplayAdapter(mContext)); + registerDisplayAdapter(new LocalDisplayAdapter(mContext)); + } + } + + // Runs on handler. + private void registerAdditionalDisplayAdapters() { + if (shouldRegisterNonEssentialDisplayAdapters()) { + registerDisplayAdapter(new OverlayDisplayAdapter(mContext)); + } + } + + private boolean shouldRegisterNonEssentialDisplayAdapters() { + // In safe mode, we disable non-essential display adapters to give the user + // an opportunity to fix broken settings or other problems that might affect + // system stability. + // In only-core mode, we disable non-essential display adapters to minimize + // the number of dependencies that are started while in this mode and to + // prevent problems that might occur due to the device being encrypted. + synchronized (mLock) { + return !mSafeMode && !mOnlyCore; } } + // Runs on handler. + private void registerDisplayAdapter(DisplayAdapter adapter) { + synchronized (mLock) { + mDisplayAdapters.add(adapter); + } + + adapter.register(mDisplayAdapterListener); + } + // FIXME: this isn't the right API for the long term public void getDefaultExternalDisplayDeviceInfo(DisplayDeviceInfo info) { // hardcoded assuming 720p touch screen plugged into HDMI and USB @@ -85,71 +214,224 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } /** - * Save away new DisplayInfo data. - * @param displayId The local DisplayInfo to store the new data in. + * Sets the new logical display orientation. + * + * @param displayId The logical display id. + * @param orientation One of the Surface.ROTATION_* constants. + */ + public void setDisplayOrientation(int displayId, int orientation) { + synchronized (mLock) { + // TODO: update mirror transforms + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display != null && display.mPrimaryDisplayDevice != null) { + IBinder displayToken = display.mPrimaryDisplayDevice.getDisplayToken(); + if (displayToken != null) { + Surface.openTransaction(); + try { + Surface.setDisplayOrientation(displayToken, orientation); + } finally { + Surface.closeTransaction(); + } + } + + display.mBaseDisplayInfo.rotation = orientation; + sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + } + } + } + + /** + * Overrides the display information of a particular logical display. + * This is used by the window manager to control the size and characteristics + * of the default display. + * + * @param displayId The logical display id. * @param info The new data to be stored. */ - public void setDisplayInfo(int displayId, DisplayInfo info) { + public void setDisplayInfoOverrideFromWindowManager(int displayId, DisplayInfo info) { synchronized (mLock) { - if (displayId != Display.DEFAULT_DISPLAY) { - throw new UnsupportedOperationException(); + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display != null) { + if (info != null) { + if (display.mOverrideDisplayInfo == null) { + display.mOverrideDisplayInfo = new DisplayInfo(); + } + display.mOverrideDisplayInfo.copyFrom(info); + } else { + display.mOverrideDisplayInfo = null; + } + + sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); } - mDefaultDisplayInfo.copyFrom(info); } } /** - * Return requested DisplayInfo. - * @param displayId The data to retrieve. - * @param outInfo The structure to receive the data. + * Returns information about the specified logical display. + * + * @param displayId The logical display id. + * @param The logical display info, or null if the display does not exist. */ @Override // Binder call - public boolean getDisplayInfo(int displayId, DisplayInfo outInfo) { + public DisplayInfo getDisplayInfo(int displayId) { synchronized (mLock) { - if (displayId != Display.DEFAULT_DISPLAY) { - return false; + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display != null) { + if (display.mOverrideDisplayInfo != null) { + return new DisplayInfo(display.mOverrideDisplayInfo); + } + return new DisplayInfo(display.mBaseDisplayInfo); } - outInfo.copyFrom(mDefaultDisplayInfo); - return true; + return null; } } - private void registerDisplayAdapter(DisplayAdapter adapter) { - mDisplayAdapters.add(adapter); - adapter.register(new DisplayAdapter.Listener() { - @Override - public void onDisplayDeviceAdded(DisplayDevice device) { - DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo(); - device.getInfo(deviceInfo); - copyDisplayInfoFromDeviceInfo(mDefaultDisplayInfo, deviceInfo); + @Override // Binder call + public int[] getDisplayIds() { + synchronized (mLock) { + final int count = mLogicalDisplays.size(); + int[] displayIds = new int[count]; + for (int i = 0; i > count; i++) { + displayIds[i] = mLogicalDisplays.keyAt(i); } + return displayIds; + } + } - @Override - public void onDisplayDeviceRemoved(DisplayDevice device) { + @Override // Binder call + public void registerCallback(IDisplayManagerCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + synchronized (mLock) { + int callingPid = Binder.getCallingPid(); + if (mCallbacks.get(callingPid) != null) { + throw new SecurityException("The calling process has already " + + "registered an IDisplayManagerCallback."); } - }); + + CallbackRecord record = new CallbackRecord(callingPid, callback); + try { + IBinder binder = callback.asBinder(); + binder.linkToDeath(record, 0); + } catch (RemoteException ex) { + // give up + throw new RuntimeException(ex); + } + + mCallbacks.put(callingPid, record); + } + } + + private void onCallbackDied(int pid) { + synchronized (mLock) { + mCallbacks.remove(pid); + } + } + + // Runs on handler. + private void handleDisplayDeviceAdded(DisplayDevice device) { + synchronized (mLock) { + if (mDisplayDevices.contains(device)) { + Slog.w(TAG, "Attempted to add already added display device: " + device); + return; + } + + mDisplayDevices.add(device); + + LogicalDisplay display = new LogicalDisplay(device); + display.updateFromPrimaryDisplayDevice(); + + boolean isDefault = (display.mPrimaryDisplayDeviceInfo.flags + & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0; + if (isDefault && mLogicalDisplays.get(Display.DEFAULT_DISPLAY) != null) { + Slog.w(TAG, "Attempted to add a second default device: " + device); + isDefault = false; + } + + int displayId = isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++; + mLogicalDisplays.put(displayId, display); + + sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); + + // Wake up waitForDefaultDisplay. + if (isDefault) { + mLock.notifyAll(); + } + } + } + + // Runs on handler. + private void handleDisplayDeviceChanged(DisplayDevice device) { + synchronized (mLock) { + if (!mDisplayDevices.contains(device)) { + Slog.w(TAG, "Attempted to change non-existent display device: " + device); + return; + } + + for (int i = mLogicalDisplays.size(); i-- > 0; ) { + LogicalDisplay display = mLogicalDisplays.valueAt(i); + if (display.mPrimaryDisplayDevice == device) { + final int displayId = mLogicalDisplays.keyAt(i); + display.updateFromPrimaryDisplayDevice(); + sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + } + } + } + } + + // Runs on handler. + private void handleDisplayDeviceRemoved(DisplayDevice device) { + synchronized (mLock) { + if (!mDisplayDevices.remove(device)) { + Slog.w(TAG, "Attempted to remove non-existent display device: " + device); + return; + } + + for (int i = mLogicalDisplays.size(); i-- > 0; ) { + LogicalDisplay display = mLogicalDisplays.valueAt(i); + if (display.mPrimaryDisplayDevice == device) { + final int displayId = mLogicalDisplays.keyAt(i); + mLogicalDisplays.removeAt(i); + sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); + } + } + } + } + + // Posts a message to send a display event at the next opportunity. + private void sendDisplayEventLocked(int displayId, int event) { + Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event); + mHandler.sendMessage(msg); } - private void copyDisplayInfoFromDeviceInfo( - DisplayInfo displayInfo, DisplayDeviceInfo deviceInfo) { - // Bootstrap the logical display using the physical display. - displayInfo.appWidth = deviceInfo.width; - displayInfo.appHeight = deviceInfo.height; - displayInfo.logicalWidth = deviceInfo.width; - displayInfo.logicalHeight = deviceInfo.height; - displayInfo.rotation = Surface.ROTATION_0; - displayInfo.refreshRate = deviceInfo.refreshRate; - displayInfo.logicalDensityDpi = deviceInfo.densityDpi; - displayInfo.physicalXDpi = deviceInfo.xDpi; - displayInfo.physicalYDpi = deviceInfo.yDpi; - displayInfo.smallestNominalAppWidth = deviceInfo.width; - displayInfo.smallestNominalAppHeight = deviceInfo.height; - displayInfo.largestNominalAppWidth = deviceInfo.width; - displayInfo.largestNominalAppHeight = deviceInfo.height; + // Runs on handler. + // This method actually sends display event notifications. + // Note that it must be very careful not to be holding the lock while sending + // is in progress. + private void deliverDisplayEvent(int displayId, int event) { + if (DEBUG) { + Slog.d(TAG, "Delivering display event: displayId=" + displayId + ", event=" + event); + } + + final int count; + synchronized (mLock) { + count = mCallbacks.size(); + mTempCallbacks.clear(); + for (int i = 0; i < count; i++) { + mTempCallbacks.add(mCallbacks.valueAt(i)); + } + } + + for (int i = 0; i < count; i++) { + mTempCallbacks.get(i).notifyDisplayEventAsync(displayId, event); + } + mTempCallbacks.clear(); } @Override // Binder call - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (mContext == null || mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -158,19 +440,159 @@ public final class DisplayManagerService extends IDisplayManager.Stub { return; } - pw.println("DISPLAY MANAGER (dumpsys display)\n"); + pw.println("DISPLAY MANAGER (dumpsys display)"); + pw.println(" mHeadless=" + mHeadless); - pw.println("Headless: " + mHeadless); + mHandler.runWithScissors(new Runnable() { + @Override + public void run() { + dumpLocal(pw); + } + }); + } + // Runs on handler. + private void dumpLocal(PrintWriter pw) { synchronized (mLock) { + IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + + pw.println(); + pw.println("Display Adapters: size=" + mDisplayAdapters.size()); for (DisplayAdapter adapter : mDisplayAdapters) { - pw.println("Adapter: " + adapter.getName()); + pw.println(" " + adapter.getName()); + adapter.dump(ipw); + } + + pw.println(); + pw.println("Display Devices: size=" + mDisplayDevices.size()); + for (DisplayDevice device : mDisplayDevices) { + pw.println(" " + device); + } + + final int logicalDisplayCount = mLogicalDisplays.size(); + pw.println(); + pw.println("Logical Displays: size=" + logicalDisplayCount); + for (int i = 0; i < logicalDisplayCount; i++) { + int displayId = mLogicalDisplays.keyAt(i); + LogicalDisplay display = mLogicalDisplays.valueAt(i); + pw.println(" Display " + displayId + ":"); + pw.println(" mPrimaryDisplayDevice=" + display.mPrimaryDisplayDevice); + pw.println(" mBaseDisplayInfo=" + display.mBaseDisplayInfo); + pw.println(" mOverrideDisplayInfo=" + + display.mOverrideDisplayInfo); + } + } + } + + private final class DisplayManagerHandler extends Handler { + public DisplayManagerHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER: + registerDefaultDisplayAdapter(); + break; + + case MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS: + registerAdditionalDisplayAdapters(); + break; + + case MSG_DELIVER_DISPLAY_EVENT: + deliverDisplayEvent(msg.arg1, msg.arg2); + break; + } + } + } + + private final class DisplayAdapterListener implements DisplayAdapter.Listener { + @Override + public void onDisplayDeviceEvent(DisplayDevice device, int event) { + switch (event) { + case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED: + handleDisplayDeviceAdded(device); + break; + + case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED: + handleDisplayDeviceChanged(device); + break; + + case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED: + handleDisplayDeviceRemoved(device); + break; + } + } + } + + private final class CallbackRecord implements DeathRecipient { + private final int mPid; + private final IDisplayManagerCallback mCallback; + + public CallbackRecord(int pid, IDisplayManagerCallback callback) { + mPid = pid; + mCallback = callback; + } + + @Override + public void binderDied() { + if (DEBUG) { + Slog.d(TAG, "Display listener for pid " + mPid + " died."); + } + onCallbackDied(mPid); + } + + public void notifyDisplayEventAsync(int displayId, int event) { + try { + mCallback.onDisplayEvent(displayId, event); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + + mPid + " that displays changed, assuming it died.", ex); + binderDied(); } + } + } - pw.println("Default display info: " + mDefaultDisplayInfo); + /** + * Each logical display is primarily associated with one display device. + * The primary display device is nominally responsible for the basic properties + * of the logical display such as its size, refresh rate, and dpi. + * + * A logical display may be mirrored onto other display devices besides its + * primary display device, but it always remains bound to its primary. + * Note that the contents of a logical display may not always be visible, even + * on its primary display device, such as in the case where the logical display's + * primary display device is currently mirroring content from a different logical display. + */ + private final static class LogicalDisplay { + public final DisplayInfo mBaseDisplayInfo = new DisplayInfo(); + public DisplayInfo mOverrideDisplayInfo; // set by the window manager + + public final DisplayDevice mPrimaryDisplayDevice; + public final DisplayDeviceInfo mPrimaryDisplayDeviceInfo = new DisplayDeviceInfo(); + + public LogicalDisplay(DisplayDevice primaryDisplayDevice) { + mPrimaryDisplayDevice = primaryDisplayDevice; } - pw.println("Default display: " - + DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY)); + public void updateFromPrimaryDisplayDevice() { + // Bootstrap the logical display using its associated primary physical display. + mPrimaryDisplayDevice.getInfo(mPrimaryDisplayDeviceInfo); + + mBaseDisplayInfo.appWidth = mPrimaryDisplayDeviceInfo.width; + mBaseDisplayInfo.appHeight = mPrimaryDisplayDeviceInfo.height; + mBaseDisplayInfo.logicalWidth = mPrimaryDisplayDeviceInfo.width; + mBaseDisplayInfo.logicalHeight = mPrimaryDisplayDeviceInfo.height; + mBaseDisplayInfo.rotation = Surface.ROTATION_0; + mBaseDisplayInfo.refreshRate = mPrimaryDisplayDeviceInfo.refreshRate; + mBaseDisplayInfo.logicalDensityDpi = mPrimaryDisplayDeviceInfo.densityDpi; + mBaseDisplayInfo.physicalXDpi = mPrimaryDisplayDeviceInfo.xDpi; + mBaseDisplayInfo.physicalYDpi = mPrimaryDisplayDeviceInfo.yDpi; + mBaseDisplayInfo.smallestNominalAppWidth = mPrimaryDisplayDeviceInfo.width; + mBaseDisplayInfo.smallestNominalAppHeight = mPrimaryDisplayDeviceInfo.height; + mBaseDisplayInfo.largestNominalAppWidth = mPrimaryDisplayDeviceInfo.width; + mBaseDisplayInfo.largestNominalAppHeight = mPrimaryDisplayDeviceInfo.height; + } } } diff --git a/services/java/com/android/server/display/HeadlessDisplayAdapter.java b/services/java/com/android/server/display/HeadlessDisplayAdapter.java index 17c2360..f5c78b9 100644 --- a/services/java/com/android/server/display/HeadlessDisplayAdapter.java +++ b/services/java/com/android/server/display/HeadlessDisplayAdapter.java @@ -21,42 +21,40 @@ import android.util.DisplayMetrics; /** * Provides a fake default display for headless systems. + * <p> + * Display adapters are not thread-safe and must only be accessed + * on the display manager service's handler thread. + * </p> */ public final class HeadlessDisplayAdapter extends DisplayAdapter { - private final Context mContext; - private final HeadlessDisplayDevice mDefaultDisplayDevice; + private static final String TAG = "HeadlessDisplayAdapter"; public HeadlessDisplayAdapter(Context context) { - mContext = context; - mDefaultDisplayDevice = new HeadlessDisplayDevice(); + super(context, TAG); } @Override - public String getName() { - return "HeadlessDisplayAdapter"; - } - - @Override - public void register(Listener listener) { - listener.onDisplayDeviceAdded(mDefaultDisplayDevice); + protected void onRegister() { + sendDisplayDeviceEvent(new HeadlessDisplayDevice(), DISPLAY_DEVICE_EVENT_ADDED); } private final class HeadlessDisplayDevice extends DisplayDevice { - @Override - public DisplayAdapter getAdapter() { - return HeadlessDisplayAdapter.this; + public HeadlessDisplayDevice() { + super(HeadlessDisplayAdapter.this, null); } @Override public void getInfo(DisplayDeviceInfo outInfo) { - outInfo.name = mContext.getResources().getString( - com.android.internal.R.string.display_manager_built_in_display); + outInfo.name = getContext().getResources().getString( + com.android.internal.R.string.display_manager_built_in_display_name); outInfo.width = 640; outInfo.height = 480; outInfo.refreshRate = 60; outInfo.densityDpi = DisplayMetrics.DENSITY_DEFAULT; outInfo.xDpi = 160; outInfo.yDpi = 160; + outInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY + | DisplayDeviceInfo.FLAG_SECURE; } } } diff --git a/services/java/com/android/server/display/LocalDisplayAdapter.java b/services/java/com/android/server/display/LocalDisplayAdapter.java new file mode 100644 index 0000000..73544fc --- /dev/null +++ b/services/java/com/android/server/display/LocalDisplayAdapter.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.content.Context; +import android.os.IBinder; +import android.view.Surface; +import android.view.Surface.PhysicalDisplayInfo; + +/** + * A display adapter for the local displays managed by Surface Flinger. + * <p> + * Display adapters are not thread-safe and must only be accessed + * on the display manager service's handler thread. + * </p> + */ +public final class LocalDisplayAdapter extends DisplayAdapter { + private static final String TAG = "LocalDisplayAdapter"; + + public LocalDisplayAdapter(Context context) { + super(context, TAG); + } + + @Override + protected void onRegister() { + // TODO: listen for notifications from Surface Flinger about + // built-in displays being added or removed and rescan as needed. + IBinder displayToken = Surface.getBuiltInDisplay(Surface.BUILT_IN_DISPLAY_ID_MAIN); + sendDisplayDeviceEvent(new LocalDisplayDevice(displayToken, true), + DISPLAY_DEVICE_EVENT_ADDED); + } + + private final class LocalDisplayDevice extends DisplayDevice { + private final boolean mIsDefault; + + public LocalDisplayDevice(IBinder displayToken, boolean isDefault) { + super(LocalDisplayAdapter.this, displayToken); + mIsDefault = isDefault; + } + + @Override + public void getInfo(DisplayDeviceInfo outInfo) { + PhysicalDisplayInfo phys = new PhysicalDisplayInfo(); + Surface.getDisplayInfo(getDisplayToken(), phys); + + outInfo.name = getContext().getResources().getString( + com.android.internal.R.string.display_manager_built_in_display_name); + outInfo.width = phys.width; + outInfo.height = phys.height; + outInfo.refreshRate = phys.refreshRate; + outInfo.densityDpi = (int)(phys.density * 160 + 0.5f); + outInfo.xDpi = phys.xDpi; + outInfo.yDpi = phys.yDpi; + if (mIsDefault) { + outInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY + | DisplayDeviceInfo.FLAG_SECURE; + } + } + } +} diff --git a/services/java/com/android/server/display/OverlayDisplayAdapter.java b/services/java/com/android/server/display/OverlayDisplayAdapter.java new file mode 100644 index 0000000..476d21a --- /dev/null +++ b/services/java/com/android/server/display/OverlayDisplayAdapter.java @@ -0,0 +1,530 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.content.Context; +import android.database.ContentObserver; +import android.graphics.SurfaceTexture; +import android.hardware.display.DisplayManager; +import android.os.IBinder; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.Surface; +import android.view.TextureView; +import android.view.TextureView.SurfaceTextureListener; +import android.view.View; +import android.view.WindowManager; +import android.widget.TextView; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A display adapter that uses overlay windows to simulate secondary displays + * for development purposes. Use Development Settings to enable one or more + * overlay displays. + * <p> + * Display adapters are not thread-safe and must only be accessed + * on the display manager service's handler thread. + * </p> + */ +public final class OverlayDisplayAdapter extends DisplayAdapter { + private static final String TAG = "OverlayDisplayAdapter"; + + private static final int MIN_WIDTH = 100; + private static final int MIN_HEIGHT = 100; + private static final int MAX_WIDTH = 4096; + private static final int MAX_HEIGHT = 4096; + + private static final Pattern SETTING_PATTERN = + Pattern.compile("(\\d+)x(\\d+)/(\\d+)"); + + private final ArrayList<Overlay> mOverlays = new ArrayList<Overlay>(); + private String mCurrentOverlaySetting = ""; + + public OverlayDisplayAdapter(Context context) { + super(context, TAG); + } + + @Override + public void dump(PrintWriter pw) { + pw.println("mCurrentOverlaySetting=" + mCurrentOverlaySetting); + pw.println("mOverlays: size=" + mOverlays.size()); + for (Overlay overlay : mOverlays) { + overlay.dump(pw); + } + } + + @Override + protected void onRegister() { + getContext().getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.Secure.OVERLAY_DISPLAY_DEVICES), true, + new ContentObserver(getHandler()) { + @Override + public void onChange(boolean selfChange) { + updateOverlayDisplayDevices(); + } + }); + updateOverlayDisplayDevices(); + } + + private void updateOverlayDisplayDevices() { + String value = Settings.System.getString(getContext().getContentResolver(), + Settings.Secure.OVERLAY_DISPLAY_DEVICES); + if (value == null) { + value = ""; + } + + if (value.equals(mCurrentOverlaySetting)) { + return; + } + mCurrentOverlaySetting = value; + + if (!mOverlays.isEmpty()) { + Slog.i(TAG, "Dismissing all overlay display devices."); + for (Overlay overlay : mOverlays) { + overlay.dismiss(); + } + mOverlays.clear(); + } + + int number = 1; + for (String part : value.split(";")) { + if (number > 4) { + Slog.w(TAG, "Too many overlay display devices."); + } + Matcher matcher = SETTING_PATTERN.matcher(part); + if (matcher.matches()) { + try { + int width = Integer.parseInt(matcher.group(1), 10); + int height = Integer.parseInt(matcher.group(2), 10); + int densityDpi = Integer.parseInt(matcher.group(3), 10); + if (width >= MIN_WIDTH && width <= MAX_WIDTH + && height >= MIN_HEIGHT && height <= MAX_HEIGHT + && densityDpi >= DisplayMetrics.DENSITY_LOW + && densityDpi <= DisplayMetrics.DENSITY_XXHIGH) { + Slog.i(TAG, "Showing overlay display device #" + number + + ": width=" + width + ", height=" + height + + ", densityDpi=" + densityDpi); + mOverlays.add(new Overlay(number++, width, height, densityDpi)); + continue; + } + } catch (NumberFormatException ex) { + } + } else if (part.isEmpty()) { + continue; + } + Slog.w(TAG, "Malformed overlay display devices setting: \"" + value + "\""); + } + + for (Overlay overlay : mOverlays) { + overlay.show(); + } + } + + // Manages an overlay window. + private final class Overlay { + private final float INITIAL_SCALE = 0.5f; + private final float MIN_SCALE = 0.3f; + private final float MAX_SCALE = 1.0f; + private final float WINDOW_ALPHA = 0.8f; + + // When true, disables support for moving and resizing the overlay. + // The window is made non-touchable, which makes it possible to + // directly interact with the content underneath. + private final boolean DISABLE_MOVE_AND_RESIZE = false; + + private final DisplayManager mDisplayManager; + private final WindowManager mWindowManager; + + private final int mNumber; + private final int mWidth; + private final int mHeight; + private final int mDensityDpi; + + private final String mName; + private final String mTitle; + + private final Display mDefaultDisplay; + private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo(); + private final IBinder mDisplayToken; + private final OverlayDisplayDevice mDisplayDevice; + + private View mWindowContent; + private WindowManager.LayoutParams mWindowParams; + private TextureView mTextureView; + private TextView mTitleTextView; + private ScaleGestureDetector mScaleGestureDetector; + + private boolean mWindowVisible; + private int mWindowX; + private int mWindowY; + private float mWindowScale; + + private int mLiveTranslationX; + private int mLiveTranslationY; + private float mLiveScale = 1.0f; + + private int mDragPointerId; + private float mDragTouchX; + private float mDragTouchY; + + public Overlay(int number, int width, int height, int densityDpi) { + Context context = getContext(); + mDisplayManager = (DisplayManager)context.getSystemService( + Context.DISPLAY_SERVICE); + mWindowManager = (WindowManager)context.getSystemService( + Context.WINDOW_SERVICE); + + mNumber = number; + mWidth = width; + mHeight = height; + mDensityDpi = densityDpi; + + mName = context.getResources().getString( + com.android.internal.R.string.display_manager_overlay_display_name, number); + mTitle = context.getResources().getString( + com.android.internal.R.string.display_manager_overlay_display_title, + mNumber, mWidth, mHeight, mDensityDpi); + + mDefaultDisplay = mWindowManager.getDefaultDisplay(); + updateDefaultDisplayInfo(); + + mDisplayToken = Surface.createDisplay(mName); + mDisplayDevice = new OverlayDisplayDevice(mDisplayToken, mName, + mDefaultDisplayInfo.refreshRate, mDensityDpi); + + createWindow(); + } + + public void show() { + if (!mWindowVisible) { + mDisplayManager.registerDisplayListener(mDisplayListener, null); + if (!updateDefaultDisplayInfo()) { + mDisplayManager.unregisterDisplayListener(mDisplayListener); + return; + } + + clearLiveState(); + updateWindowParams(); + mWindowManager.addView(mWindowContent, mWindowParams); + mWindowVisible = true; + } + } + + public void dismiss() { + if (mWindowVisible) { + mDisplayManager.unregisterDisplayListener(mDisplayListener); + mWindowManager.removeView(mWindowContent); + mWindowVisible = false; + } + } + + public void relayout() { + if (mWindowVisible) { + updateWindowParams(); + mWindowManager.updateViewLayout(mWindowContent, mWindowParams); + } + } + + public void dump(PrintWriter pw) { + pw.println(" #" + mNumber + ": " + + mWidth + "x" + mHeight + ", " + mDensityDpi + " dpi"); + pw.println(" mName=" + mName); + pw.println(" mWindowVisible=" + mWindowVisible); + pw.println(" mWindowX=" + mWindowX); + pw.println(" mWindowY=" + mWindowY); + pw.println(" mWindowScale=" + mWindowScale); + pw.println(" mWindowParams=" + mWindowParams); + pw.println(" mLiveTranslationX=" + mLiveTranslationX); + pw.println(" mLiveTranslationY=" + mLiveTranslationY); + pw.println(" mLiveScale=" + mLiveScale); + } + + private boolean updateDefaultDisplayInfo() { + if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) { + Slog.w(TAG, "Cannot show overlay display because there is no " + + "default display upon which to show it."); + return false; + } + return true; + } + + private void createWindow() { + Context context = getContext(); + LayoutInflater inflater = LayoutInflater.from(context); + + mWindowContent = inflater.inflate( + com.android.internal.R.layout.overlay_display_window, null); + mWindowContent.setOnTouchListener(mOnTouchListener); + + mTextureView = (TextureView)mWindowContent.findViewById( + com.android.internal.R.id.overlay_display_window_texture); + mTextureView.setPivotX(0); + mTextureView.setPivotY(0); + mTextureView.getLayoutParams().width = mWidth; + mTextureView.getLayoutParams().height = mHeight; + mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); + + mTitleTextView = (TextView)mWindowContent.findViewById( + com.android.internal.R.id.overlay_display_window_title); + mTitleTextView.setText(mTitle); + + mWindowParams = new WindowManager.LayoutParams( + WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY); + mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + if (DISABLE_MOVE_AND_RESIZE) { + mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + } + mWindowParams.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED; + mWindowParams.alpha = WINDOW_ALPHA; + mWindowParams.gravity = Gravity.TOP | Gravity.LEFT; + mWindowParams.setTitle(mTitle); + + mScaleGestureDetector = new ScaleGestureDetector(context, mOnScaleGestureListener); + + // By default, arrange the displays in the four corners. + mWindowVisible = false; + mWindowScale = INITIAL_SCALE; + if (mNumber == 2 || mNumber == 3) { + mWindowX = mDefaultDisplayInfo.logicalWidth; + } else { + mWindowX = 0; + } + if (mNumber == 2 || mNumber == 4) { + mWindowY = mDefaultDisplayInfo.logicalHeight; + } else { + mWindowY = 0; + } + } + + private void updateWindowParams() { + float scale = mWindowScale * mLiveScale; + if (mWidth * scale > mDefaultDisplayInfo.logicalWidth) { + scale = mDefaultDisplayInfo.logicalWidth / mWidth; + } + if (mHeight * scale > mDefaultDisplayInfo.logicalHeight) { + scale = mDefaultDisplayInfo.logicalHeight / mHeight; + } + scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale)); + + float offsetScale = (scale / mWindowScale - 1.0f) * 0.5f; + int width = (int)(mWidth * scale); + int height = (int)(mHeight * scale); + int x = mWindowX + mLiveTranslationX - (int)(width * offsetScale); + int y = mWindowY + mLiveTranslationY - (int)(height * offsetScale); + x = Math.max(0, Math.min(x, mDefaultDisplayInfo.logicalWidth - width)); + y = Math.max(0, Math.min(y, mDefaultDisplayInfo.logicalHeight - height)); + + mTextureView.setScaleX(scale); + mTextureView.setScaleY(scale); + + mWindowParams.x = x; + mWindowParams.y = y; + mWindowParams.width = width; + mWindowParams.height = height; + } + + private void saveWindowParams() { + mWindowX = mWindowParams.x; + mWindowY = mWindowParams.y; + mWindowScale = mTextureView.getScaleX(); + clearLiveState(); + } + + private void clearLiveState() { + mLiveTranslationX = 0; + mLiveTranslationY = 0; + mLiveScale = 1.0f; + } + + private final DisplayManager.DisplayListener mDisplayListener = + new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + } + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == mDefaultDisplay.getDisplayId()) { + if (updateDefaultDisplayInfo()) { + relayout(); + } else { + dismiss(); + } + } + } + + @Override + public void onDisplayRemoved(int displayId) { + if (displayId == mDefaultDisplay.getDisplayId()) { + dismiss(); + } + } + }; + + private final SurfaceTextureListener mSurfaceTextureListener = + new SurfaceTextureListener() { + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { + Surface.openTransaction(); + try { + Surface.setDisplaySurface(mDisplayToken, surface); + } finally { + Surface.closeTransaction(); + } + + mDisplayDevice.setSize(width, height); + sendDisplayDeviceEvent(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED); + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { + sendDisplayDeviceEvent(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED); + + Surface.openTransaction(); + try { + Surface.setDisplaySurface(mDisplayToken, null); + } finally { + Surface.closeTransaction(); + } + return true; + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { + mDisplayDevice.setSize(width, height); + sendDisplayDeviceEvent(mDisplayDevice, DISPLAY_DEVICE_EVENT_CHANGED); + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + } + }; + + private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent event) { + // Work in screen coordinates. + final float oldX = event.getX(); + final float oldY = event.getY(); + event.setLocation(event.getRawX(), event.getRawY()); + + mScaleGestureDetector.onTouchEvent(event); + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + resetDrag(event); + break; + + case MotionEvent.ACTION_MOVE: + if (event.getPointerCount() == 1) { + int index = event.findPointerIndex(mDragPointerId); + if (index < 0) { + resetDrag(event); + } else { + mLiveTranslationX = (int)(event.getX(index) - mDragTouchX); + mLiveTranslationY = (int)(event.getY(index) - mDragTouchY); + relayout(); + } + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + saveWindowParams(); + break; + } + + // Revert to window coordinates. + event.setLocation(oldX, oldY); + return true; + } + + private void resetDrag(MotionEvent event) { + saveWindowParams(); + mDragPointerId = event.getPointerId(0); + mDragTouchX = event.getX(); + mDragTouchY = event.getY(); + } + }; + + private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener = + new ScaleGestureDetector.SimpleOnScaleGestureListener() { + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + saveWindowParams(); + mDragPointerId = -1; // cause drag to be reset + return true; + } + + @Override + public boolean onScale(ScaleGestureDetector detector) { + mLiveScale = detector.getScaleFactor(); + relayout(); + return false; + } + }; + } + + private final class OverlayDisplayDevice extends DisplayDevice { + private final String mName; + private final float mRefreshRate; + private final int mDensityDpi; + private int mWidth; + private int mHeight; + + public OverlayDisplayDevice(IBinder displayToken, String name, + float refreshRate, int densityDpi) { + super(OverlayDisplayAdapter.this, displayToken); + mName = name; + mRefreshRate = refreshRate; + mDensityDpi = densityDpi; + } + + public void setSize(int width, int height) { + mWidth = width; + mHeight = height; + } + + @Override + public void getInfo(DisplayDeviceInfo outInfo) { + outInfo.name = mName; + outInfo.width = mWidth; + outInfo.height = mHeight; + outInfo.refreshRate = mRefreshRate; + outInfo.densityDpi = mDensityDpi; + outInfo.xDpi = mDensityDpi; + outInfo.yDpi = mDensityDpi; + outInfo.flags = DisplayDeviceInfo.FLAG_SECURE; + } + } +} diff --git a/services/java/com/android/server/display/SurfaceFlingerDisplayAdapter.java b/services/java/com/android/server/display/SurfaceFlingerDisplayAdapter.java deleted file mode 100644 index 9531acb..0000000 --- a/services/java/com/android/server/display/SurfaceFlingerDisplayAdapter.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.display; - -import android.content.Context; - -/** - * A display adapter for the displays managed by Surface Flinger. - */ -public final class SurfaceFlingerDisplayAdapter extends DisplayAdapter { - private final Context mContext; - private final SurfaceFlingerDisplayDevice mDefaultDisplayDevice; - - private static native void nativeGetDefaultDisplayDeviceInfo(DisplayDeviceInfo outInfo); - - public SurfaceFlingerDisplayAdapter(Context context) { - mContext = context; - mDefaultDisplayDevice = new SurfaceFlingerDisplayDevice(); - } - - @Override - public String getName() { - return "SurfaceFlingerDisplayAdapter"; - } - - @Override - public void register(Listener listener) { - listener.onDisplayDeviceAdded(mDefaultDisplayDevice); - } - - private final class SurfaceFlingerDisplayDevice extends DisplayDevice { - @Override - public DisplayAdapter getAdapter() { - return SurfaceFlingerDisplayAdapter.this; - } - - @Override - public void getInfo(DisplayDeviceInfo outInfo) { - outInfo.name = mContext.getResources().getString( - com.android.internal.R.string.display_manager_built_in_display); - nativeGetDefaultDisplayDeviceInfo(outInfo); - } - } -} diff --git a/services/java/com/android/server/net/LockdownVpnTracker.java b/services/java/com/android/server/net/LockdownVpnTracker.java index 541650e..dabcf2f 100644 --- a/services/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/java/com/android/server/net/LockdownVpnTracker.java @@ -55,6 +55,7 @@ public class LockdownVpnTracker { private static final int MAX_ERROR_COUNT = 4; private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET"; + private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS"; private final Context mContext; private final INetworkManagementService mNetService; @@ -84,9 +85,9 @@ public class LockdownVpnTracker { mVpn = Preconditions.checkNotNull(vpn); mProfile = Preconditions.checkNotNull(profile); - final Intent intent = new Intent(ACTION_LOCKDOWN_RESET); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - mResetIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); + final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET); + resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0); } private BroadcastReceiver mResetReceiver = new BroadcastReceiver() { @@ -115,7 +116,7 @@ public class LockdownVpnTracker { final boolean egressChanged = egressProp == null || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName()); if (egressDisconnected || egressChanged) { - clearSourceRules(); + clearSourceRulesLocked(); mAcceptedEgressIface = null; mVpn.stopLegacyVpn(); } @@ -150,7 +151,7 @@ public class LockdownVpnTracker { showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected); try { - clearSourceRules(); + clearSourceRulesLocked(); mNetService.setFirewallInterfaceRule(iface, true); mNetService.setFirewallEgressSourceRule(sourceAddr, true); @@ -167,7 +168,13 @@ public class LockdownVpnTracker { } public void init() { - Slog.d(TAG, "init()"); + synchronized (mStateLock) { + initLocked(); + } + } + + private void initLocked() { + Slog.d(TAG, "initLocked()"); mVpn.setEnableNotifications(false); @@ -188,7 +195,13 @@ public class LockdownVpnTracker { } public void shutdown() { - Slog.d(TAG, "shutdown()"); + synchronized (mStateLock) { + shutdownLocked(); + } + } + + private void shutdownLocked() { + Slog.d(TAG, "shutdownLocked()"); mAcceptedEgressIface = null; mErrorCount = 0; @@ -200,7 +213,7 @@ public class LockdownVpnTracker { } catch (RemoteException e) { throw new RuntimeException("Problem setting firewall rules", e); } - clearSourceRules(); + clearSourceRulesLocked(); hideNotification(); mContext.unregisterReceiver(mResetReceiver); @@ -208,15 +221,15 @@ public class LockdownVpnTracker { } public void reset() { - // cycle tracker, reset error count, and trigger retry - shutdown(); - init(); synchronized (mStateLock) { + // cycle tracker, reset error count, and trigger retry + shutdownLocked(); + initLocked(); handleStateChangedLocked(); } } - private void clearSourceRules() { + private void clearSourceRulesLocked() { try { if (mAcceptedIface != null) { mNetService.setFirewallInterfaceRule(mAcceptedIface, false); diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index b84e25a..48a4b74 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -710,20 +710,56 @@ public class PackageManagerService extends IPackageManager.Stub { res.removedInfo.sendBroadcast(false, true); Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, res.uid); + // Determine the set of users who are adding this + // package for the first time vs. those who are seeing + // an update. + int[] firstUsers; + int[] updateUsers = new int[0]; + if (res.origUsers == null || res.origUsers.length == 0) { + firstUsers = res.newUsers; + } else { + firstUsers = new int[0]; + for (int i=0; i<res.newUsers.length; i++) { + int user = res.newUsers[i]; + boolean isNew = true; + for (int j=0; j<res.origUsers.length; j++) { + if (res.origUsers[j] == user) { + isNew = false; + break; + } + } + if (isNew) { + int[] newFirst = new int[firstUsers.length+1]; + System.arraycopy(firstUsers, 0, newFirst, 0, + firstUsers.length); + newFirst[firstUsers.length] = user; + firstUsers = newFirst; + } else { + int[] newUpdate = new int[updateUsers.length+1]; + System.arraycopy(updateUsers, 0, newUpdate, 0, + updateUsers.length); + newUpdate[updateUsers.length] = user; + updateUsers = newUpdate; + } + } + } + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, + res.pkg.applicationInfo.packageName, + extras, null, null, firstUsers); final boolean update = res.removedInfo.removedPackage != null; if (update) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, res.pkg.applicationInfo.packageName, - extras, null, null, res.users); + extras, null, null, updateUsers); if (update) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, res.pkg.applicationInfo.packageName, - extras, null, null, res.users); + extras, null, null, updateUsers); sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, - res.pkg.applicationInfo.packageName, null, res.users); + res.pkg.applicationInfo.packageName, null, updateUsers); } if (res.removedInfo.args != null) { // Remove the replaced package's older resources safely now @@ -1782,7 +1818,7 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.publicSourceDir = ps.resourcePathString; pkg.applicationInfo.sourceDir = ps.codePathString; pkg.applicationInfo.dataDir = - getDataPathForPackage(ps.pkg.packageName, 0).getPath(); + getDataPathForPackage(packageName, 0).getPath(); pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString; } // pkg.mSetEnabled = ps.getEnabled(userId); @@ -5278,6 +5314,7 @@ public class PackageManagerService extends IPackageManager.Stub { return pkgs.get(0); } } + mSettings.mPackagesToBeCleaned.remove(userId); } // Move on to the next user to clean. long ident = Binder.clearCallingIdentity(); @@ -5343,8 +5380,10 @@ public class PackageManagerService extends IPackageManager.Stub { public void onEvent(int event, String path) { String removedPackage = null; int removedUid = -1; + int[] removedUsers = null; String addedPackage = null; int addedUid = -1; + int[] addedUsers = null; // TODO post a message to the handler to obtain serial ordering synchronized (mInstallLock) { @@ -5373,6 +5412,15 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { p = mAppDirs.get(fullPathStr); + if (p != null) { + PackageSetting ps = mSettings.mPackages.get(p.applicationInfo.packageName); + if (ps != null) { + removedUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true); + } else { + removedUsers = sUserManager.getUserIds(); + } + } + addedUsers = sUserManager.getUserIds(); } if ((event&REMOVE_EVENTS) != 0) { if (p != null) { @@ -5390,7 +5438,7 @@ public class PackageManagerService extends IPackageManager.Stub { PackageParser.PARSE_CHATTY | PackageParser.PARSE_MUST_BE_APK, SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME, - System.currentTimeMillis(), null); + System.currentTimeMillis(), UserHandle.ALL); if (p != null) { /* * TODO this seems dangerous as the package may have @@ -5419,13 +5467,13 @@ public class PackageManagerService extends IPackageManager.Stub { extras.putInt(Intent.EXTRA_UID, removedUid); extras.putBoolean(Intent.EXTRA_DATA_REMOVED, false); sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, - extras, null, null, null); + extras, null, null, removedUsers); } if (addedPackage != null) { Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, addedUid); sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage, - extras, null, null, null); + extras, null, null, addedUsers); } } @@ -5468,7 +5516,7 @@ public class PackageManagerService extends IPackageManager.Stub { if ((flags&PackageManager.INSTALL_ALL_USERS) != 0) { user = UserHandle.ALL; } else { - user = Process.myUserHandle(); + user = new UserHandle(UserHandle.getUserId(uid)); } final int filteredFlags; @@ -5531,6 +5579,10 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public void verifyPendingInstall(int id, int verificationCode) throws RemoteException { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.PACKAGE_VERIFICATION_AGENT, + "Only package verification agents can verify applications"); + final Message msg = mHandler.obtainMessage(PACKAGE_VERIFIED); final PackageVerificationResponse response = new PackageVerificationResponse( verificationCode, Binder.getCallingUid()); @@ -5542,6 +5594,10 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.PACKAGE_VERIFICATION_AGENT, + "Only package verification agents can extend verification timeouts"); + final PackageVerificationState state = mPendingVerification.get(id); final PackageVerificationResponse response = new PackageVerificationResponse( verificationCodeAtTimeout, Binder.getCallingUid()); @@ -7212,7 +7268,10 @@ public class PackageManagerService extends IPackageManager.Stub { class PackageInstalledInfo { String name; int uid; - int[] users; + // The set of users that originally had this package installed. + int[] origUsers; + // The set of users that now have this package installed. + int[] newUsers; PackageParser.Package pkg; int returnCode; PackageRemovedInfo removedInfo; @@ -7362,7 +7421,7 @@ public class PackageManagerService extends IPackageManager.Stub { int oldScanMode = (oldOnSd ? 0 : SCAN_MONITOR) | SCAN_UPDATE_SIGNATURE | SCAN_UPDATE_TIME; if (scanPackageLI(restoreFile, oldParseFlags, oldScanMode, - origUpdateTime, user) == null) { + origUpdateTime, null) == null) { Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade"); return; } @@ -7511,10 +7570,6 @@ public class PackageManagerService extends IPackageManager.Stub { UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0 ? UPDATE_PERMISSIONS_ALL : 0)); res.name = pkgName; - PackageSetting ps = mSettings.mPackages.get(pkgName); - if (ps != null) { - res.users = ps.getInstalledUsers(sUserManager.getUserIds()); - } res.uid = newPackage.applicationInfo.uid; res.pkg = newPackage; mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_COMPLETE); @@ -7612,6 +7667,7 @@ public class PackageManagerService extends IPackageManager.Stub { systemApp = (ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; } + res.origUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true); } } @@ -7634,12 +7690,12 @@ public class PackageManagerService extends IPackageManager.Stub { installerPackageName, res); } else { installNewPackageLI(pkg, parseFlags, scanMode, args.user, - installerPackageName,res); + installerPackageName, res); } synchronized (mPackages) { - PackageSetting ps = mSettings.mPackages.get(pkgName); + final PackageSetting ps = mSettings.mPackages.get(pkgName); if (ps != null) { - res.users = ps.getInstalledUsers(sUserManager.getUserIds()); + res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true); } } } @@ -7865,7 +7921,8 @@ public class PackageManagerService extends IPackageManager.Stub { if (outInfo != null) { outInfo.removedPackage = packageName; outInfo.removedUsers = deletedPs != null - ? deletedPs.getInstalledUsers(sUserManager.getUserIds()) : null; + ? deletedPs.queryInstalledUsers(sUserManager.getUserIds(), true) + : null; } } if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) { diff --git a/services/java/com/android/server/pm/PackageSettingBase.java b/services/java/com/android/server/pm/PackageSettingBase.java index 6d31f0e..d8f7345 100644 --- a/services/java/com/android/server/pm/PackageSettingBase.java +++ b/services/java/com/android/server/pm/PackageSettingBase.java @@ -210,17 +210,17 @@ class PackageSettingBase extends GrantedPermissions { return false; } - int[] getInstalledUsers(int[] users) { + int[] queryInstalledUsers(int[] users, boolean installed) { int num = 0; for (int user : users) { - if (getInstalled(user)) { + if (getInstalled(user) == installed) { num++; } } int[] res = new int[num]; num = 0; for (int user : users) { - if (getInstalled(user)) { + if (getInstalled(user) == installed) { res[num] = user; num++; } diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java index 750aa72..fb04d0f 100644 --- a/services/java/com/android/server/pm/UserManagerService.java +++ b/services/java/com/android/server/pm/UserManagerService.java @@ -23,6 +23,8 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import android.app.ActivityManager; +import android.app.ActivityManagerNative; +import android.app.IStopUserCallback; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -33,6 +35,7 @@ import android.os.FileUtils; import android.os.IUserManager; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.RemoteException; import android.os.UserHandle; import android.util.AtomicFile; import android.util.Slog; @@ -549,13 +552,36 @@ public class UserManagerService extends IUserManager.Stub { */ public boolean removeUser(int userHandle) { checkManageUsersPermission("Only the system can remove users"); + final UserInfo user; + synchronized (mPackagesLock) { + user = mUsers.get(userHandle); + if (userHandle == 0 || user == null) { + return false; + } + } + + int res; + try { + res = ActivityManagerNative.getDefault().stopUser(userHandle, + new IStopUserCallback.Stub() { + @Override + public void userStopped(int userId) { + finishRemoveUser(userId); + } + @Override + public void userStopAborted(int userId) { + } + }); + } catch (RemoteException e) { + return false; + } + + return res == ActivityManager.USER_OP_SUCCESS; + } + + void finishRemoveUser(int userHandle) { synchronized (mInstallLock) { synchronized (mPackagesLock) { - final UserInfo user = mUsers.get(userHandle); - if (userHandle == 0 || user == null) { - return false; - } - // Cleanup package manager settings mPm.cleanUpUserLILPw(userHandle); @@ -574,7 +600,6 @@ public class UserManagerService extends IUserManager.Stub { Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED); addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle); mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_USERS); - return true; } @Override diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java index cd211da..6b6d899 100644 --- a/services/java/com/android/server/power/DisplayPowerController.java +++ b/services/java/com/android/server/power/DisplayPowerController.java @@ -45,7 +45,6 @@ import android.view.Display; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; /** @@ -169,6 +168,9 @@ final class DisplayPowerController { // The twilight service. private final TwilightService mTwilight; + // The display manager. + private final DisplayManager mDisplayManager; + // The sensor manager. private final SensorManager mSensorManager; @@ -330,6 +332,7 @@ final class DisplayPowerController { mLights = lights; mTwilight = twilight; mSensorManager = new SystemSensorManager(mHandler.getLooper()); + mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); final Resources resources = context.getResources(); mScreenBrightnessDimConfig = resources.getInteger( @@ -475,7 +478,7 @@ final class DisplayPowerController { private void initialize() { final Executor executor = AsyncTask.THREAD_POOL_EXECUTOR; - Display display = DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); + Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); mPowerState = new DisplayPowerState(new ElectronBeam(display), new PhotonicModulator(executor, mLights.getLight(LightsService.LIGHT_ID_BACKLIGHT), @@ -980,7 +983,7 @@ final class DisplayPowerController { } }; - public void dump(PrintWriter pw) { + public void dump(final PrintWriter pw) { synchronized (mLock) { pw.println(); pw.println("Display Controller Locked State:"); @@ -1000,33 +1003,12 @@ final class DisplayPowerController { pw.println(" mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline); pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); - if (Looper.myLooper() == mHandler.getLooper()) { - dumpLocal(pw); - } else { - final StringWriter out = new StringWriter(); - final CountDownLatch latch = new CountDownLatch(1); - Message msg = Message.obtain(mHandler, new Runnable() { - @Override - public void run() { - PrintWriter localpw = new PrintWriter(out); - try { - dumpLocal(localpw); - } finally { - localpw.flush(); - latch.countDown(); - } - } - }); - msg.setAsynchronous(true); - mHandler.sendMessage(msg); - try { - latch.await(); - pw.print(out.toString()); - } catch (InterruptedException ex) { - pw.println(); - pw.println("Failed to dump thread state due to interrupted exception!"); + mHandler.runWithScissors(new Runnable() { + @Override + public void run() { + dumpLocal(pw); } - } + }); } private void dumpLocal(PrintWriter pw) { diff --git a/services/java/com/android/server/power/ElectronBeam.java b/services/java/com/android/server/power/ElectronBeam.java index aad5a9a..0c68997 100644 --- a/services/java/com/android/server/power/ElectronBeam.java +++ b/services/java/com/android/server/power/ElectronBeam.java @@ -481,8 +481,8 @@ final class ElectronBeam { try { if (mSurface == null) { try { - mSurface = new Surface(mSurfaceSession, Process.myPid(), - "ElectronBeam", mDisplayLayerStack, mDisplayWidth, mDisplayHeight, + mSurface = new Surface(mSurfaceSession, + "ElectronBeam", mDisplayWidth, mDisplayHeight, PixelFormat.OPAQUE, Surface.OPAQUE | Surface.HIDDEN); } catch (Surface.OutOfResourcesException ex) { Slog.e(TAG, "Unable to create surface.", ex); @@ -490,6 +490,7 @@ final class ElectronBeam { } } + mSurface.setLayerStack(mDisplayLayerStack); mSurface.setSize(mDisplayWidth, mDisplayHeight); switch (mDisplayRotation) { diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java index 6d68104..59d0954 100644 --- a/services/java/com/android/server/power/PowerManagerService.java +++ b/services/java/com/android/server/power/PowerManagerService.java @@ -1136,7 +1136,8 @@ public final class PowerManagerService extends IPowerManager.Stub private boolean isItBedTimeYetLocked() { return mBootCompleted && !mStayOn && (mWakeLockSummary - & (WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM)) == 0 + & (WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM + | WAKE_LOCK_PROXIMITY_SCREEN_OFF)) == 0 && (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM)) == 0; } diff --git a/services/java/com/android/server/usb/UsbDebuggingManager.java b/services/java/com/android/server/usb/UsbDebuggingManager.java index a3b45c7..1bb3a2c 100644 --- a/services/java/com/android/server/usb/UsbDebuggingManager.java +++ b/services/java/com/android/server/usb/UsbDebuggingManager.java @@ -53,16 +53,15 @@ public class UsbDebuggingManager implements Runnable { private final int BUFFER_SIZE = 4096; private final Context mContext; - private final Thread mThread; private final Handler mHandler; private final HandlerThread mHandlerThread; + private Thread mThread; private boolean mAdbEnabled = false; private String mFingerprints; private LocalSocket mSocket = null; private OutputStream mOutputStream = null; public UsbDebuggingManager(Context context) { - mThread = new Thread(this); mHandlerThread = new HandlerThread("UsbDebuggingHandler"); mHandlerThread.start(); mHandler = new UsbDebuggingHandler(mHandlerThread.getLooper()); @@ -165,6 +164,7 @@ public class UsbDebuggingManager implements Runnable { mAdbEnabled = true; + mThread = new Thread(UsbDebuggingManager.this); mThread.start(); break; @@ -181,8 +181,10 @@ public class UsbDebuggingManager implements Runnable { } catch (Exception ex) { } + mThread = null; mOutputStream = null; mSocket = null; + break; case MESSAGE_ADB_ALLOW: { String key = (String)msg.obj; diff --git a/services/java/com/android/server/wm/BlackFrame.java b/services/java/com/android/server/wm/BlackFrame.java index 64d2602..5b77b20 100644 --- a/services/java/com/android/server/wm/BlackFrame.java +++ b/services/java/com/android/server/wm/BlackFrame.java @@ -43,14 +43,15 @@ public class BlackFrame { int w = r-l; int h = b-t; if (WindowManagerService.DEBUG_SURFACE_TRACE) { - surface = new WindowStateAnimator.SurfaceTrace(session, 0, "BlackSurface(" - + l + ", " + t + ")", layerStack, - w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM); + surface = new WindowStateAnimator.SurfaceTrace(session, "BlackSurface(" + + l + ", " + t + ")", + w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM | Surface.HIDDEN); } else { - surface = new Surface(session, 0, "BlackSurface", layerStack, - w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM); + surface = new Surface(session, "BlackSurface", + w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM | Surface.HIDDEN); } surface.setAlpha(1); + surface.setLayerStack(layerStack); surface.setLayer(layer); surface.show(); if (WindowManagerService.SHOW_TRANSACTIONS || diff --git a/services/java/com/android/server/wm/DimAnimator.java b/services/java/com/android/server/wm/DimAnimator.java index 81daac6..afcf339 100644 --- a/services/java/com/android/server/wm/DimAnimator.java +++ b/services/java/com/android/server/wm/DimAnimator.java @@ -43,20 +43,21 @@ class DimAnimator { if (mDimSurface == null) { try { if (WindowManagerService.DEBUG_SURFACE_TRACE) { - mDimSurface = new WindowStateAnimator.SurfaceTrace(session, 0, + mDimSurface = new WindowStateAnimator.SurfaceTrace(session, "DimAnimator", - layerStack, 16, 16, PixelFormat.OPAQUE, - Surface.FX_SURFACE_DIM); + 16, 16, PixelFormat.OPAQUE, + Surface.FX_SURFACE_DIM | Surface.HIDDEN); } else { - mDimSurface = new Surface(session, 0, - "DimAnimator", - layerStack, 16, 16, PixelFormat.OPAQUE, - Surface.FX_SURFACE_DIM); + mDimSurface = new Surface(session, "DimAnimator", + 16, 16, PixelFormat.OPAQUE, + Surface.FX_SURFACE_DIM | Surface.HIDDEN); } if (WindowManagerService.SHOW_TRANSACTIONS || WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface + ": CREATE"); + mDimSurface.setLayerStack(layerStack); mDimSurface.setAlpha(0.0f); + mDimSurface.show(); } catch (Exception e) { Slog.e(WindowManagerService.TAG, "Exception creating Dim surface", e); } @@ -212,4 +213,4 @@ class DimAnimator { mDimTarget = dimTarget; } } -}
\ No newline at end of file +} diff --git a/services/java/com/android/server/wm/DimSurface.java b/services/java/com/android/server/wm/DimSurface.java index 4ab8ce1..ddbd70d 100644 --- a/services/java/com/android/server/wm/DimSurface.java +++ b/services/java/com/android/server/wm/DimSurface.java @@ -34,20 +34,21 @@ class DimSurface { if (mDimSurface == null) { try { if (WindowManagerService.DEBUG_SURFACE_TRACE) { - mDimSurface = new WindowStateAnimator.SurfaceTrace(session, 0, + mDimSurface = new WindowStateAnimator.SurfaceTrace(session, "DimSurface", - layerStack, 16, 16, PixelFormat.OPAQUE, - Surface.FX_SURFACE_DIM); + 16, 16, PixelFormat.OPAQUE, + Surface.FX_SURFACE_DIM | Surface.HIDDEN); } else { - mDimSurface = new Surface(session, 0, - "DimSurface", - layerStack, 16, 16, PixelFormat.OPAQUE, - Surface.FX_SURFACE_DIM); + mDimSurface = new Surface(session, "DimSurface", + 16, 16, PixelFormat.OPAQUE, + Surface.FX_SURFACE_DIM | Surface.HIDDEN); } if (WindowManagerService.SHOW_TRANSACTIONS || WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface + ": CREATE"); + mDimSurface.setLayerStack(layerStack); mDimSurface.setAlpha(0.0f); + mDimSurface.show(); } catch (Exception e) { Slog.e(WindowManagerService.TAG, "Exception creating Dim surface", e); } diff --git a/services/java/com/android/server/wm/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java index 7679413..acf3249 100644 --- a/services/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java @@ -215,12 +215,12 @@ class ScreenRotationAnimation { try { try { if (WindowManagerService.DEBUG_SURFACE_TRACE) { - mSurface = new SurfaceTrace(session, 0, "FreezeSurface", - mDisplay.getLayerStack(), mWidth, mHeight, + mSurface = new SurfaceTrace(session, "FreezeSurface", + mWidth, mHeight, PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN); } else { - mSurface = new Surface(session, 0, "FreezeSurface", - mDisplay.getLayerStack(), mWidth, mHeight, + mSurface = new Surface(session, "FreezeSurface", + mWidth, mHeight, PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN); } if (!mSurface.isValid()) { @@ -228,6 +228,7 @@ class ScreenRotationAnimation { mSurface = null; return; } + mSurface.setLayerStack(mDisplay.getLayerStack()); mSurface.setLayer(FREEZE_LAYER + 1); mSurface.setAlpha(0); mSurface.show(); diff --git a/services/java/com/android/server/wm/StrictModeFlash.java b/services/java/com/android/server/wm/StrictModeFlash.java index 775aa0f..90bbd08 100644 --- a/services/java/com/android/server/wm/StrictModeFlash.java +++ b/services/java/com/android/server/wm/StrictModeFlash.java @@ -37,14 +37,16 @@ class StrictModeFlash { public StrictModeFlash(Display display, SurfaceSession session) { try { - mSurface = new Surface(session, 0, "StrictModeFlash", display.getLayerStack(), - 1, 1, PixelFormat.TRANSLUCENT, 0); + mSurface = new Surface(session, "StrictModeFlash", + 1, 1, PixelFormat.TRANSLUCENT, Surface.HIDDEN); } catch (Surface.OutOfResourcesException e) { return; } + mSurface.setLayerStack(display.getLayerStack()); mSurface.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); // one more than Watermark? arbitrary. mSurface.setPosition(0, 0); + mSurface.show(); mDrawNeeded = true; } diff --git a/services/java/com/android/server/wm/Watermark.java b/services/java/com/android/server/wm/Watermark.java index 5901cc8..ac152c9 100644 --- a/services/java/com/android/server/wm/Watermark.java +++ b/services/java/com/android/server/wm/Watermark.java @@ -113,9 +113,9 @@ class Watermark { mTextPaint.setShadowLayer(shadowRadius, shadowDx, shadowDy, shadowColor); try { - mSurface = new Surface(session, 0, - "WatermarkSurface", mDisplay.getLayerStack(), - 1, 1, PixelFormat.TRANSLUCENT, 0); + mSurface = new Surface(session, "WatermarkSurface", + 1, 1, PixelFormat.TRANSLUCENT, Surface.HIDDEN); + mSurface.setLayerStack(mDisplay.getLayerStack()); mSurface.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER*100); mSurface.setPosition(0, 0); mSurface.show(); @@ -174,4 +174,4 @@ class Watermark { } } } -}
\ No newline at end of file +} diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 42bc7ce..0d9db90 100755 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -739,116 +739,38 @@ public class WindowManagerService extends IWindowManager.Stub // For example, when this flag is true, there will be no wallpaper service. final boolean mOnlyCore; - public static WindowManagerService main(Context context, - PowerManagerService pm, DisplayManagerService dm, - boolean haveInputMethods, boolean allowBootMsgs, - boolean onlyCore) { - WMThread thr = new WMThread(context, pm, dm, haveInputMethods, allowBootMsgs, onlyCore); - thr.start(); - - synchronized (thr) { - while (thr.mService == null) { - try { - thr.wait(); - } catch (InterruptedException e) { - } + public static WindowManagerService main(final Context context, + final PowerManagerService pm, final DisplayManagerService dm, + final Handler uiHandler, final Handler wmHandler, + final boolean haveInputMethods, final boolean showBootMsgs, + final boolean onlyCore) { + final WindowManagerService[] holder = new WindowManagerService[1]; + wmHandler.runWithScissors(new Runnable() { + @Override + public void run() { + holder[0] = new WindowManagerService(context, pm, dm, + uiHandler, haveInputMethods, showBootMsgs, onlyCore); } - return thr.mService; - } - } - - static class WMThread extends Thread { - WindowManagerService mService; - - private final Context mContext; - private final PowerManagerService mPM; - private final DisplayManagerService mDisplayManager; - private final boolean mHaveInputMethods; - private final boolean mAllowBootMessages; - private final boolean mOnlyCore; - - public WMThread(Context context, PowerManagerService pm, - DisplayManagerService dm, - boolean haveInputMethods, boolean allowBootMsgs, boolean onlyCore) { - super("WindowManager"); - mContext = context; - mPM = pm; - mDisplayManager = dm; - mHaveInputMethods = haveInputMethods; - mAllowBootMessages = allowBootMsgs; - mOnlyCore = onlyCore; - } - - @Override - public void run() { - Looper.prepare(); - //Looper.myLooper().setMessageLogging(new LogPrinter( - // android.util.Log.DEBUG, TAG, android.util.Log.LOG_ID_SYSTEM)); - WindowManagerService s = new WindowManagerService(mContext, mPM, mDisplayManager, - mHaveInputMethods, mAllowBootMessages, mOnlyCore); - android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_DISPLAY); - android.os.Process.setCanSelfBackground(false); - - synchronized (this) { - mService = s; - notifyAll(); - } - - // For debug builds, log event loop stalls to dropbox for analysis. - if (StrictMode.conditionallyEnableDebugLogging()) { - Slog.i(TAG, "Enabled StrictMode logging for WMThread's Looper"); - } - - Looper.loop(); - } + }); + return holder[0]; } - static class PolicyThread extends Thread { - private final WindowManagerPolicy mPolicy; - private final WindowManagerService mService; - private final Context mContext; - boolean mRunning = false; + private void initPolicy(Handler uiHandler) { + uiHandler.runWithScissors(new Runnable() { + @Override + public void run() { + WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper()); - public PolicyThread(WindowManagerPolicy policy, - WindowManagerService service, Context context) { - super("WindowManagerPolicy"); - mPolicy = policy; - mService = service; - mContext = context; - } - - @Override - public void run() { - Looper.prepare(); - WindowManagerPolicyThread.set(this, Looper.myLooper()); - - //Looper.myLooper().setMessageLogging(new LogPrinter( - // Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM)); - android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_FOREGROUND); - android.os.Process.setCanSelfBackground(false); - mPolicy.init(mContext, mService, mService); - mService.mAnimator.mAboveUniverseLayer = mPolicy.getAboveUniverseLayer() - * TYPE_LAYER_MULTIPLIER - + TYPE_LAYER_OFFSET; - - synchronized (this) { - mRunning = true; - notifyAll(); - } - - // For debug builds, log event loop stalls to dropbox for analysis. - if (StrictMode.conditionallyEnableDebugLogging()) { - Slog.i(TAG, "Enabled StrictMode for PolicyThread's Looper"); + mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this); + mAnimator.mAboveUniverseLayer = mPolicy.getAboveUniverseLayer() + * TYPE_LAYER_MULTIPLIER + + TYPE_LAYER_OFFSET; } - - Looper.loop(); - } + }); } private WindowManagerService(Context context, PowerManagerService pm, - DisplayManagerService displayManager, + DisplayManagerService displayManager, Handler uiHandler, boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) { mContext = context; mHaveInputMethods = haveInputMethods; @@ -857,7 +779,7 @@ public class WindowManagerService extends IWindowManager.Stub mLimitedAlphaCompositing = context.getResources().getBoolean( com.android.internal.R.bool.config_sf_limitedAlpha); mDisplayManagerService = displayManager; - mDisplayManager = DisplayManager.getInstance(); + mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); mHeadless = displayManager.isHeadless(); mKeyguardDisableHandler = new KeyguardDisableHandler(mContext, mPolicy); @@ -895,17 +817,7 @@ public class WindowManagerService extends IWindowManager.Stub mFxSession = new SurfaceSession(); mAnimator = new WindowAnimator(this); - PolicyThread thr = new PolicyThread(mPolicy, this, context); - thr.start(); - - synchronized (thr) { - while (!thr.mRunning) { - try { - thr.wait(); - } catch (InterruptedException e) { - } - } - } + initPolicy(uiHandler); mInputManager.start(); @@ -5847,7 +5759,8 @@ public class WindowManagerService extends IWindowManager.Stub updateLayoutToAnimationLocked(); } } - Surface.setOrientation(0, rotation); + mDisplayManagerService.setDisplayOrientation( + displayContent.getDisplayId(), rotation); } finally { if (!inTransaction) { Surface.closeTransaction(); @@ -6561,7 +6474,8 @@ public class WindowManagerService extends IWindowManager.Stub displayInfo.appHeight = appHeight; displayInfo.getLogicalMetrics(mRealDisplayMetrics, null); displayInfo.getAppMetrics(mDisplayMetrics, null); - mDisplayManagerService.setDisplayInfo(displayContent.getDisplayId(), displayInfo); + mDisplayManagerService.setDisplayInfoOverrideFromWindowManager( + displayContent.getDisplayId(), displayInfo); mAnimator.setDisplayDimensions(dw, dh, appWidth, appHeight); } @@ -6711,9 +6625,9 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { try { if (mDragState == null) { - Surface surface = new Surface(session, callerPid, "drag surface", - mDefaultDisplay.getLayerStack(), + Surface surface = new Surface(session, "drag surface", width, height, PixelFormat.TRANSLUCENT, Surface.HIDDEN); + surface.setLayerStack(mDefaultDisplay.getLayerStack()); if (SHOW_TRANSACTIONS) Slog.i(TAG, " DRAG " + surface + ": CREATE"); outSurface.copyFrom(surface); @@ -6913,7 +6827,10 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(displayContent.mDisplaySizeLock) { // Bootstrap the default logical display from the display manager. displayInfo = displayContent.getDisplayInfo(); - mDisplayManagerService.getDisplayInfo(displayId, displayInfo); + DisplayInfo newDisplayInfo = mDisplayManagerService.getDisplayInfo(displayId); + if (newDisplayInfo != null) { + displayInfo.copyFrom(newDisplayInfo); + } displayContent.mInitialDisplayWidth = displayInfo.logicalWidth; displayContent.mInitialDisplayHeight = displayInfo.logicalHeight; displayContent.mInitialDisplayDensity = displayInfo.logicalDensityDpi; @@ -8342,10 +8259,11 @@ public class WindowManagerService extends IWindowManager.Stub Rect dirty = new Rect(0, 0, mNextAppTransitionThumbnail.getWidth(), mNextAppTransitionThumbnail.getHeight()); try { - Surface surface = new Surface(mFxSession, Process.myPid(), - "thumbnail anim", mDefaultDisplay.getLayerStack(), + Surface surface = new Surface(mFxSession, + "thumbnail anim", dirty.width(), dirty.height(), PixelFormat.TRANSLUCENT, Surface.HIDDEN); + surface.setLayerStack(mDefaultDisplay.getLayerStack()); topOpeningApp.mAppAnimator.thumbnail = surface; if (SHOW_TRANSACTIONS) Slog.i(TAG, " THUMBNAIL " + surface + ": CREATE"); @@ -10363,7 +10281,7 @@ public class WindowManagerService extends IWindowManager.Stub public DisplayContent getDisplayContent(final int displayId) { DisplayContent displayContent = mDisplayContents.get(displayId); if (displayContent == null) { - displayContent = new DisplayContent(mDisplayManager.getRealDisplay(displayId)); + displayContent = new DisplayContent(mDisplayManager.getDisplay(displayId)); mDisplayContents.put(displayId, displayContent); } return displayContent; diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java index 982f60d..1bda22a 100644 --- a/services/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/java/com/android/server/wm/WindowStateAnimator.java @@ -481,9 +481,9 @@ class WindowStateAnimator { private String mName; public SurfaceTrace(SurfaceSession s, - int pid, String name, int layerStack, int w, int h, int format, int flags) + String name, int w, int h, int format, int flags) throws OutOfResourcesException { - super(s, pid, name, layerStack, w, h, format, flags); + super(s, name, w, h, format, flags); mName = name != null ? name : "Not named"; mSize.set(w, h); Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by " @@ -608,7 +608,7 @@ class WindowStateAnimator { mService.makeWindowFreezingScreenIfNeededLocked(mWin); - int flags = 0; + int flags = Surface.HIDDEN; final WindowManager.LayoutParams attrs = mWin.mAttrs; if ((attrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) { @@ -652,14 +652,14 @@ class WindowStateAnimator { } if (DEBUG_SURFACE_TRACE) { mSurface = new SurfaceTrace( - mSession.mSurfaceSession, mSession.mPid, + mSession.mSurfaceSession, attrs.getTitle().toString(), - mLayerStack, w, h, format, flags); + w, h, format, flags); } else { mSurface = new Surface( - mSession.mSurfaceSession, mSession.mPid, + mSession.mSurfaceSession, attrs.getTitle().toString(), - mLayerStack, w, h, format, flags); + w, h, format, flags); } mWin.mHasSurface = true; if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG, @@ -701,10 +701,10 @@ class WindowStateAnimator { mSurfaceY = mWin.mFrame.top + mWin.mYOffset; mSurface.setPosition(mSurfaceX, mSurfaceY); mSurfaceLayer = mAnimLayer; + mSurface.setLayerStack(mLayerStack); mSurface.setLayer(mAnimLayer); mSurface.setAlpha(0); mSurfaceShown = false; - mSurface.hide(); } catch (RuntimeException e) { Slog.w(TAG, "Error creating surface in " + w, e); mService.reclaimSomeSurfaceMemoryLocked(this, "create-init", true); diff --git a/services/jni/Android.mk b/services/jni/Android.mk index 43e59b2..d097a93 100644 --- a/services/jni/Android.mk +++ b/services/jni/Android.mk @@ -4,7 +4,6 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ com_android_server_AlarmManagerService.cpp \ com_android_server_BatteryService.cpp \ - com_android_server_display_SurfaceFlingerDisplayAdapter.cpp \ com_android_server_input_InputApplicationHandle.cpp \ com_android_server_input_InputManagerService.cpp \ com_android_server_input_InputWindowHandle.cpp \ diff --git a/services/jni/com_android_server_display_SurfaceFlingerDisplayAdapter.cpp b/services/jni/com_android_server_display_SurfaceFlingerDisplayAdapter.cpp deleted file mode 100644 index 05a74d3..0000000 --- a/services/jni/com_android_server_display_SurfaceFlingerDisplayAdapter.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "SurfaceFlingerDisplayAdapter" - -#include "JNIHelp.h" -#include "jni.h" -#include <android_runtime/AndroidRuntime.h> - -#include <gui/SurfaceComposerClient.h> -#include <ui/DisplayInfo.h> - -#include <utils/Log.h> - -namespace android { - -static struct { - jfieldID width; - jfieldID height; - jfieldID refreshRate; - jfieldID densityDpi; - jfieldID xDpi; - jfieldID yDpi; -} gDisplayDeviceInfoClassInfo; - - -static void nativeGetDefaultDisplayDeviceInfo(JNIEnv* env, jclass clazz, jobject infoObj) { - DisplayInfo info; - status_t err = SurfaceComposerClient::getDisplayInfo(0, &info); - if (err < 0) { - jniThrowExceptionFmt(env, "java/lang/RuntimeException", - "Could not get display info. err=%d", err); - return; - } - - env->SetIntField(infoObj, gDisplayDeviceInfoClassInfo.width, info.w); - env->SetIntField(infoObj, gDisplayDeviceInfoClassInfo.height, info.h); - env->SetFloatField(infoObj, gDisplayDeviceInfoClassInfo.refreshRate, info.fps); - env->SetIntField(infoObj, gDisplayDeviceInfoClassInfo.densityDpi, - (jint)((info.density*160) + .5f)); - env->SetFloatField(infoObj, gDisplayDeviceInfoClassInfo.xDpi, info.xdpi); - env->SetFloatField(infoObj, gDisplayDeviceInfoClassInfo.yDpi, info.ydpi); -} - - -static JNINativeMethod gSurfaceFlingerDisplayAdapterMethods[] = { - /* name, signature, funcPtr */ - { "nativeGetDefaultDisplayDeviceInfo", - "(Lcom/android/server/display/DisplayDeviceInfo;)V", - (void*) nativeGetDefaultDisplayDeviceInfo }, -}; - -#define FIND_CLASS(var, className) \ - var = env->FindClass(className); \ - LOG_FATAL_IF(! var, "Unable to find class " className); - -#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ - var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ - LOG_FATAL_IF(! var, "Unable to find field " fieldName); - -int register_android_server_display_SurfaceFlingerDisplayAdapter(JNIEnv* env) { - int res = jniRegisterNativeMethods(env, - "com/android/server/display/SurfaceFlingerDisplayAdapter", - gSurfaceFlingerDisplayAdapterMethods, NELEM(gSurfaceFlingerDisplayAdapterMethods)); - LOG_FATAL_IF(res < 0, "Unable to register native methods."); - - jclass clazz; - FIND_CLASS(clazz, "com/android/server/display/DisplayDeviceInfo"); - GET_FIELD_ID(gDisplayDeviceInfoClassInfo.width, clazz, "width", "I"); - GET_FIELD_ID(gDisplayDeviceInfoClassInfo.height, clazz, "height", "I"); - GET_FIELD_ID(gDisplayDeviceInfoClassInfo.refreshRate, clazz, "refreshRate", "F"); - GET_FIELD_ID(gDisplayDeviceInfoClassInfo.densityDpi, clazz, "densityDpi", "I"); - GET_FIELD_ID(gDisplayDeviceInfoClassInfo.xDpi, clazz, "xDpi", "F"); - GET_FIELD_ID(gDisplayDeviceInfoClassInfo.yDpi, clazz, "yDpi", "F"); - return 0; -} - -} /* namespace android */ diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp index 50873fc..423ebd1 100644 --- a/services/jni/onload.cpp +++ b/services/jni/onload.cpp @@ -22,7 +22,6 @@ namespace android { int register_android_server_AlarmManagerService(JNIEnv* env); int register_android_server_BatteryService(JNIEnv* env); -int register_android_server_display_SurfaceFlingerDisplayAdapter(JNIEnv* env); int register_android_server_InputApplicationHandle(JNIEnv* env); int register_android_server_InputWindowHandle(JNIEnv* env); int register_android_server_InputManager(JNIEnv* env); @@ -52,7 +51,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) register_android_server_PowerManagerService(env); register_android_server_SerialService(env); - register_android_server_display_SurfaceFlingerDisplayAdapter(env); register_android_server_InputApplicationHandle(env); register_android_server_InputWindowHandle(env); register_android_server_InputManager(env); diff --git a/telephony/java/android/telephony/CellSignalStrength.java b/telephony/java/android/telephony/CellSignalStrength.java index a80207e..581efc2 100644 --- a/telephony/java/android/telephony/CellSignalStrength.java +++ b/telephony/java/android/telephony/CellSignalStrength.java @@ -59,22 +59,16 @@ public abstract class CellSignalStrength implements Parcelable { /** * Get signal level as an int from 0..4 - * - * @hide */ public abstract int getLevel(); /** * Get the signal level as an asu value between 0..31, 99 is unknown - * - * @hide */ public abstract int getAsuLevel(); /** * Get the signal strength as dBm - * - * @hide */ public abstract int getDbm(); diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java index 20f713b..3912629 100644 --- a/telephony/java/android/telephony/CellSignalStrengthCdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java @@ -113,9 +113,7 @@ public class CellSignalStrengthCdma extends CellSignalStrength implements Parcel } /** - * Get LTE as level 0..4 - * - * @hide + * Get signal level as an int from 0..4 */ @Override public int getLevel() { @@ -140,8 +138,6 @@ public class CellSignalStrengthCdma extends CellSignalStrength implements Parcel /** * Get the LTE signal level as an asu value between 0..97, 99 is unknown * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69 - * - * @hide */ @Override public int getAsuLevel() { @@ -172,8 +168,6 @@ public class CellSignalStrengthCdma extends CellSignalStrength implements Parcel /** * Get cdma as level 0..4 - * - * @hide */ public int getCdmaLevel() { final int cdmaDbm = getCdmaDbm(); @@ -201,8 +195,6 @@ public class CellSignalStrengthCdma extends CellSignalStrength implements Parcel /** * Get Evdo as level 0..4 - * - * @hide */ public int getEvdoLevel() { int evdoDbm = getEvdoDbm(); @@ -228,9 +220,7 @@ public class CellSignalStrengthCdma extends CellSignalStrength implements Parcel } /** - * Get as dBm - * - * @hide + * Get the signal strength as dBm */ @Override public int getDbm() { diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java index a311c3c..30b444b 100644 --- a/telephony/java/android/telephony/CellSignalStrengthGsm.java +++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java @@ -100,9 +100,7 @@ public class CellSignalStrengthGsm extends CellSignalStrength implements Parcela } /** - * Get LTE as level 0..4 - * - * @hide + * Get signal level as an int from 0..4 */ @Override public int getLevel() { @@ -123,9 +121,7 @@ public class CellSignalStrengthGsm extends CellSignalStrength implements Parcela } /** - * Get LTE as dBm - * - * @hide + * Get the signal strength as dBm */ @Override public int getDbm() { @@ -145,8 +141,6 @@ public class CellSignalStrengthGsm extends CellSignalStrength implements Parcela /** * Get the LTE signal level as an asu value between 0..97, 99 is unknown * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69 - * - * @hide */ @Override public int getAsuLevel() { diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index 078699f..7a4d626 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -134,9 +134,7 @@ public class CellSignalStrengthLte extends CellSignalStrength implements Parcela } /** - * Get LTE as level 0..4 - * - * @hide + * Get signal level as an int from 0..4 */ @Override public int getLevel() { @@ -170,9 +168,7 @@ public class CellSignalStrengthLte extends CellSignalStrength implements Parcela } /** - * Get LTE as dBm - * - * @hide + * Get signal strength as dBm */ @Override public int getDbm() { @@ -182,8 +178,6 @@ public class CellSignalStrengthLte extends CellSignalStrength implements Parcela /** * Get the LTE signal level as an asu value between 0..97, 99 is unknown * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69 - * - * @hide */ @Override public int getAsuLevel() { diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java index f26edc6..e39d53c 100644 --- a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java +++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java @@ -54,9 +54,9 @@ public class MemoryUsageTest extends InstrumentationTestCase { private static final String TAG = "MemoryUsageInstrumentation"; private static final String KEY_APPS = "apps"; - private Map<String, Intent> nameToIntent; - private Map<String, String> nameToProcess; - private Map<String, String> nameToResultKey; + private Map<String, Intent> mNameToIntent; + private Map<String, String> mNameToProcess; + private Map<String, String> mNameToResultKey; public void testMemory() { MemoryUsageInstrumentation instrumentation = @@ -67,7 +67,7 @@ public class MemoryUsageTest extends InstrumentationTestCase { parseArgs(args); Bundle results = new Bundle(); - for (String app : nameToResultKey.keySet()) { + for (String app : mNameToResultKey.keySet()) { String processName; try { processName = startApp(app); @@ -81,7 +81,7 @@ public class MemoryUsageTest extends InstrumentationTestCase { } private void parseArgs(Bundle args) { - nameToResultKey = new HashMap<String, String>(); + mNameToResultKey = new HashMap<String, String>(); String appList = args.getString(KEY_APPS); if (appList == null) @@ -95,13 +95,13 @@ public class MemoryUsageTest extends InstrumentationTestCase { fail(); } - nameToResultKey.put(parts[0], parts[1]); + mNameToResultKey.put(parts[0], parts[1]); } } private void createMappings() { - nameToIntent = new HashMap<String, Intent>(); - nameToProcess = new HashMap<String, String>(); + mNameToIntent = new HashMap<String, Intent>(); + mNameToProcess = new HashMap<String, String>(); PackageManager pm = getInstrumentation().getContext() .getPackageManager(); @@ -120,8 +120,8 @@ public class MemoryUsageTest extends InstrumentationTestCase { | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); startIntent.setClassName(ri.activityInfo.packageName, ri.activityInfo.name); - nameToIntent.put(ri.loadLabel(pm).toString(), startIntent); - nameToProcess.put(ri.loadLabel(pm).toString(), + mNameToIntent.put(ri.loadLabel(pm).toString(), startIntent); + mNameToProcess.put(ri.loadLabel(pm).toString(), ri.activityInfo.processName); } } @@ -130,11 +130,11 @@ public class MemoryUsageTest extends InstrumentationTestCase { private String startApp(String appName) throws NameNotFoundException { Log.i(TAG, "Starting " + appName); - if (!nameToProcess.containsKey(appName)) + if (!mNameToProcess.containsKey(appName)) throw new NameNotFoundException("Could not find: " + appName); - String process = nameToProcess.get(appName); - Intent startIntent = nameToIntent.get(appName); + String process = mNameToProcess.get(appName); + Intent startIntent = mNameToIntent.get(appName); getInstrumentation().getContext().startActivity(startIntent); return process; } @@ -154,14 +154,14 @@ public class MemoryUsageTest extends InstrumentationTestCase { } pssData.add(pss); if (iteration >= MIN_ITERATIONS && stabilized(pssData)) { - results.putInt(nameToResultKey.get(appName), pss); + results.putInt(mNameToResultKey.get(appName), pss); return; } iteration++; } Log.w(TAG, appName + " memory usage did not stabilize"); - results.putInt(appName, average(pssData)); + results.putInt(mNameToResultKey.get(appName), average(pssData)); } private int average(List<Integer> pssData) { @@ -202,12 +202,12 @@ public class MemoryUsageTest extends InstrumentationTestCase { continue; Log.w(TAG, appName + " crashed: " + crash.shortMsg); - results.putString(nameToResultKey.get(appName), crash.shortMsg); + results.putString(mNameToResultKey.get(appName), crash.shortMsg); return; } } - results.putString(nameToResultKey.get(appName), + results.putString(mNameToResultKey.get(appName), "Crashed for unknown reason"); Log.w(TAG, appName + " not found in process list, most likely it is crashed"); diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 6b08074..55de065 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -106,5 +106,7 @@ interface IWifiManager Messenger getWifiStateMachineMessenger(); String getConfigFile(); + + void captivePortalCheckComplete(); } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 284bee8..aa59158 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1971,4 +1971,11 @@ public class WifiManager { return false; } } + + /** @hide */ + public void captivePortalCheckComplete() { + try { + mService.captivePortalCheckComplete(); + } catch (RemoteException e) {} + } } diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java index a447c86..17c930b 100644 --- a/wifi/java/android/net/wifi/WifiMonitor.java +++ b/wifi/java/android/net/wifi/WifiMonitor.java @@ -20,6 +20,8 @@ import android.net.NetworkInfo; import android.net.wifi.p2p.WifiP2pConfig; import android.net.wifi.p2p.WifiP2pDevice; import android.net.wifi.p2p.WifiP2pGroup; +import android.net.wifi.p2p.WifiP2pService; +import android.net.wifi.p2p.WifiP2pService.P2pStatus; import android.net.wifi.p2p.WifiP2pProvDiscEvent; import android.net.wifi.p2p.nsd.WifiP2pServiceResponse; import android.net.wifi.StateChangeResult; @@ -186,7 +188,7 @@ public class WifiMonitor { /* P2P-GROUP-STARTED p2p-wlan0-0 [client|GO] ssid="DIRECT-W8" freq=2437 [psk=2182b2e50e53f260d04f3c7b25ef33c965a3291b9b36b455a82d77fd82ca15bc|passphrase="fKG4jMe3"] - go_dev_addr=fa:7b:7a:42:02:13 */ + go_dev_addr=fa:7b:7a:42:02:13 [PERSISTENT] */ private static final String P2P_GROUP_STARTED_STR = "P2P-GROUP-STARTED"; /* P2P-GROUP-REMOVED p2p-wlan0-0 [client|GO] reason=REQUESTED */ @@ -594,7 +596,13 @@ public class WifiMonitor { if (tokens.length != 2) return; String[] nameValue = tokens[1].split("="); if (nameValue.length != 2) return; - mStateMachine.sendMessage(P2P_INVITATION_RESULT_EVENT, nameValue[1]); + P2pStatus err = P2pStatus.UNKNOWN; + try { + err = P2pStatus.valueOf(Integer.parseInt(nameValue[1])); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + mStateMachine.sendMessage(P2P_INVITATION_RESULT_EVENT, err); } else if (dataString.startsWith(P2P_PROV_DISC_PBC_REQ_STR)) { mStateMachine.sendMessage(P2P_PROV_DISC_PBC_REQ_EVENT, new WifiP2pProvDiscEvent(dataString)); diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java index 1b7e378..b9feb34 100644 --- a/wifi/java/android/net/wifi/WifiNative.java +++ b/wifi/java/android/net/wifi/WifiNative.java @@ -569,10 +569,9 @@ public class WifiNative { break; } - //TODO: Add persist behavior once the supplicant interaction is fixed for both - // group and client scenarios - /* Persist unless there is an explicit request to not do so*/ - //if (config.persist != WifiP2pConfig.Persist.NO) args.add("persistent"); + if (config.netId == WifiP2pGroup.PERSISTENT_NET_ID) { + args.add("persistent"); + } if (joinExistingGroup) { args.add("join"); @@ -614,10 +613,17 @@ public class WifiNative { return false; } - public boolean p2pGroupAdd() { + public boolean p2pGroupAdd(boolean persistent) { + if (persistent) { + return doBooleanCommand("P2P_GROUP_ADD persistent"); + } return doBooleanCommand("P2P_GROUP_ADD"); } + public boolean p2pGroupAdd(int netId) { + return doBooleanCommand("P2P_GROUP_ADD persistent=" + netId); + } + public boolean p2pGroupRemove(String iface) { if (TextUtils.isEmpty(iface)) return false; return doBooleanCommand("P2P_GROUP_REMOVE " + iface); @@ -646,6 +652,9 @@ public class WifiNative { return doBooleanCommand("P2P_INVITE persistent=" + netId + " peer=" + deviceAddress); } + public String p2pGetSsid(String deviceAddress) { + return p2pGetParam(deviceAddress, "oper_ssid"); + } public String p2pGetDeviceAddress() { String status = status(); @@ -687,6 +696,24 @@ public class WifiNative { return doStringCommand("P2P_PEER " + deviceAddress); } + private String p2pGetParam(String deviceAddress, String key) { + if (deviceAddress == null) return null; + + String peerInfo = p2pPeer(deviceAddress); + if (peerInfo == null) return null; + String[] tokens= peerInfo.split("\n"); + + key += "="; + for (String token : tokens) { + if (token.startsWith(key)) { + String[] nameValue = token.split("="); + if (nameValue.length != 2) break; + return nameValue[1]; + } + } + return null; + } + public boolean p2pServiceAdd(WifiP2pServiceInfo servInfo) { /* * P2P_SERVICE_ADD bonjour <query hexdump> <RDATA hexdump> diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index a5322fa..b52250d 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -256,6 +256,8 @@ public class WifiStateMachine extends StateMachine { static final int CMD_DELAYED_STOP_DRIVER = BASE + 18; /* A delayed message sent to start driver when it fail to come up */ static final int CMD_DRIVER_START_TIMED_OUT = BASE + 19; + /* Ready to switch to network as default */ + static final int CMD_CAPTIVE_CHECK_COMPLETE = BASE + 20; /* Start the soft access point */ static final int CMD_START_AP = BASE + 21; @@ -459,6 +461,8 @@ public class WifiStateMachine extends StateMachine { private State mObtainingIpState = new ObtainingIpState(); /* Waiting for link quality verification to be complete */ private State mVerifyingLinkState = new VerifyingLinkState(); + /* Waiting for captive portal check to be complete */ + private State mCaptivePortalCheckState = new CaptivePortalCheckState(); /* Connected with IP addr */ private State mConnectedState = new ConnectedState(); /* disconnect issued, waiting for network disconnect confirmation */ @@ -695,6 +699,7 @@ public class WifiStateMachine extends StateMachine { addState(mL2ConnectedState, mConnectModeState); addState(mObtainingIpState, mL2ConnectedState); addState(mVerifyingLinkState, mL2ConnectedState); + addState(mCaptivePortalCheckState, mL2ConnectedState); addState(mConnectedState, mL2ConnectedState); addState(mDisconnectingState, mConnectModeState); addState(mDisconnectedState, mConnectModeState); @@ -865,6 +870,10 @@ public class WifiStateMachine extends StateMachine { } } + public void captivePortalCheckComplete() { + sendMessage(obtainMessage(CMD_CAPTIVE_CHECK_COMPLETE)); + } + /** * TODO: doc */ @@ -1616,7 +1625,7 @@ public class WifiStateMachine extends StateMachine { } if (state != mNetworkInfo.getDetailedState()) { - mNetworkInfo.setDetailedState(state, null, null); + mNetworkInfo.setDetailedState(state, null, mWifiInfo.getSSID()); } } @@ -1663,10 +1672,7 @@ public class WifiStateMachine extends StateMachine { /* In case we were in middle of DHCP operation restore back powermode */ handlePostDhcpSetup(); - mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_STOP_DHCP); - mDhcpStateMachine.doQuit(); - mDhcpStateMachine = null; } try { @@ -1919,6 +1925,9 @@ public class WifiStateMachine extends StateMachine { case CMD_CLEAR_SUSPEND_OPTIMIZATIONS: case CMD_NO_NETWORKS_PERIODIC_SCAN: break; + case DhcpStateMachine.CMD_ON_QUIT: + mDhcpStateMachine = null; + break; case CMD_SET_SUSPEND_OPTIMIZATIONS: mSuspendWakeLock.release(); break; @@ -2489,6 +2498,9 @@ public class WifiStateMachine extends StateMachine { /* Send any reset commands to supplicant before shutting it down */ handleNetworkDisconnect(); + if (mDhcpStateMachine != null) { + mDhcpStateMachine.doQuit(); + } if (DBG) log("stopping supplicant"); if (!mWifiNative.stopSupplicant()) { @@ -3188,8 +3200,11 @@ public class WifiStateMachine extends StateMachine { if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) { //start DHCP - mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine( - mContext, WifiStateMachine.this, mInterfaceName); + if (mDhcpStateMachine == null) { + mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine( + mContext, WifiStateMachine.this, mInterfaceName); + + } mDhcpStateMachine.registerForPreDhcpNotification(); mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP); } else { @@ -3253,6 +3268,26 @@ public class WifiStateMachine extends StateMachine { //stay here break; case WifiWatchdogStateMachine.GOOD_LINK_DETECTED: + transitionTo(mCaptivePortalCheckState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class CaptivePortalCheckState extends State { + @Override + public void enter() { + setNetworkDetailedState(DetailedState.CAPTIVE_PORTAL_CHECK); + mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CAPTIVE_PORTAL_CHECK); + sendNetworkStateChangeBroadcast(mLastBssid); + } + @Override + public boolean processMessage(Message message) { + switch (message.what) { + case CMD_CAPTIVE_CHECK_COMPLETE: try { mNwService.enableIpv6(mInterfaceName); } catch (RemoteException re) { @@ -3260,7 +3295,6 @@ public class WifiStateMachine extends StateMachine { } catch (IllegalStateException e) { loge("Failed to enable IPv6: " + e); } - setNetworkDetailedState(DetailedState.CONNECTED); mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED); sendNetworkStateChangeBroadcast(mLastBssid); diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java index bfb91e2..a5a2469 100644 --- a/wifi/java/android/net/wifi/WifiStateTracker.java +++ b/wifi/java/android/net/wifi/WifiStateTracker.java @@ -23,6 +23,7 @@ import android.content.IntentFilter; import android.net.LinkCapabilities; import android.net.LinkProperties; import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; import android.net.NetworkStateTracker; import android.net.wifi.p2p.WifiP2pManager; import android.os.Handler; @@ -113,6 +114,14 @@ public class WifiStateTracker implements NetworkStateTracker { } /** + * Captive check is complete, switch to network + */ + @Override + public void captivePortalCheckComplete() { + mWifiManager.captivePortalCheckComplete(); + } + + /** * Turn the wireless radio off for a network. * @param turnOn {@code true} to turn the radio on, {@code false} */ @@ -235,9 +244,10 @@ public class WifiStateTracker implements NetworkStateTracker { mLinkCapabilities = new LinkCapabilities(); } // don't want to send redundent state messages - // TODO can this be fixed in WifiStateMachine? + // but send portal check detailed state notice NetworkInfo.State state = mNetworkInfo.getState(); - if (mLastState == state) { + if (mLastState == state && + mNetworkInfo.getDetailedState() != DetailedState.CAPTIVE_PORTAL_CHECK) { return; } else { mLastState = state; diff --git a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java index 29a53b6..7fa6aac 100644 --- a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java +++ b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java @@ -16,20 +16,15 @@ package android.net.wifi; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.res.Resources; import android.database.ContentObserver; import android.net.ConnectivityManager; import android.net.LinkProperties; import android.net.NetworkInfo; -import android.net.Uri; import android.net.wifi.RssiPacketCountInfo; import android.os.Message; import android.os.SystemClock; @@ -44,10 +39,7 @@ import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; -import java.io.IOException; import java.io.PrintWriter; -import java.net.HttpURLConnection; -import java.net.URL; import java.text.DecimalFormat; /** @@ -100,8 +92,7 @@ public class WifiWatchdogStateMachine extends StateMachine { private static final int EVENT_SCREEN_OFF = BASE + 9; /* Internal messages */ - private static final int CMD_DELAYED_WALLED_GARDEN_CHECK = BASE + 11; - private static final int CMD_RSSI_FETCH = BASE + 12; + private static final int CMD_RSSI_FETCH = BASE + 11; /* Notifications from/to WifiStateMachine */ static final int POOR_LINK_DETECTED = BASE + 21; @@ -266,27 +257,6 @@ public class WifiWatchdogStateMachine extends StateMachine { new MaxAvoidTime( 0 * 60000, -55 ), }; - - private static final String WALLED_GARDEN_NOTIFICATION_ID = "WifiWatchdog.walledgarden"; - - private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000; - - /** - * See http://go/clientsdns for usage approval - */ - private static final String DEFAULT_WALLED_GARDEN_URL = - "http://clients3.google.com/generate_204"; - private static final int WALLED_GARDEN_SOCKET_TIMEOUT_MS = 10000; - - /** - * Some carrier apps might have support captive portal handling. Add some - * delay to allow app authentication to be done before our test. TODO: This - * should go away once we provide an API to apps to disable walled garden - * test for certain SSIDs - */ - private static final int WALLED_GARDEN_START_DELAY_MS = 3000; - - /* Framework related */ private Context mContext; private ContentResolver mContentResolver; @@ -300,13 +270,6 @@ public class WifiWatchdogStateMachine extends StateMachine { /* System settingss related */ private static boolean sWifiOnly = false; private boolean mPoorNetworkDetectionEnabled; - private long mWalledGardenIntervalMs; - private boolean mWalledGardenTestEnabled; - private String mWalledGardenUrl; - - /* Wall garden detection related */ - private long mLastWalledGardenCheckTime = 0; - private boolean mWalledGardenNotificationShown; /* Poor link detection related */ private LruCache<String, BssidStatistics> mBssidCache = @@ -325,7 +288,6 @@ public class WifiWatchdogStateMachine extends StateMachine { private NotConnectedState mNotConnectedState = new NotConnectedState(); private VerifyingLinkState mVerifyingLinkState = new VerifyingLinkState(); private ConnectedState mConnectedState = new ConnectedState(); - private WalledGardenCheckState mWalledGardenCheckState = new WalledGardenCheckState(); private OnlineWatchState mOnlineWatchState = new OnlineWatchState(); private LinkMonitoringState mLinkMonitoringState = new LinkMonitoringState(); private OnlineState mOnlineState = new OnlineState(); @@ -359,7 +321,6 @@ public class WifiWatchdogStateMachine extends StateMachine { addState(mNotConnectedState, mWatchdogEnabledState); addState(mVerifyingLinkState, mWatchdogEnabledState); addState(mConnectedState, mWatchdogEnabledState); - addState(mWalledGardenCheckState, mConnectedState); addState(mOnlineWatchState, mConnectedState); addState(mLinkMonitoringState, mConnectedState); addState(mOnlineState, mConnectedState); @@ -379,13 +340,12 @@ public class WifiWatchdogStateMachine extends StateMachine { Context.CONNECTIVITY_SERVICE); sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false); - // Watchdog is always enabled. Poor network detection & walled garden detection - // can individually be turned on/off + // Watchdog is always enabled. Poor network detection can be seperately turned on/off // TODO: Remove this setting & clean up state machine since we always have // watchdog in an enabled state putSettingsBoolean(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON, true); - // disable poor network avoidance, but keep watchdog active for walled garden detection + // disable poor network avoidance if (sWifiOnly) { logd("Disabling poor network avoidance for wi-fi only device"); putSettingsBoolean(contentResolver, @@ -458,44 +418,8 @@ public class WifiWatchdogStateMachine extends StateMachine { }; mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS), - false, contentObserver); - mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED), false, contentObserver); - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED), - false, contentObserver); - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL), - false, contentObserver); - } - - /** - * DNS based detection techniques do not work at all hotspots. The one sure - * way to check a walled garden is to see if a URL fetch on a known address - * fetches the data we expect - */ - private boolean isWalledGardenConnection() { - HttpURLConnection urlConnection = null; - try { - URL url = new URL(mWalledGardenUrl); - urlConnection = (HttpURLConnection) url.openConnection(); - urlConnection.setInstanceFollowRedirects(false); - urlConnection.setConnectTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS); - urlConnection.setReadTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS); - urlConnection.setUseCaches(false); - urlConnection.getInputStream(); - // we got a valid response, but not from the real google - return urlConnection.getResponseCode() != 204; - } catch (IOException e) { - if (DBG) logd("Walled garden check - probably not a portal: exception " + e); - return false; - } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } - } } public void dump(PrintWriter pw) { @@ -504,10 +428,7 @@ public class WifiWatchdogStateMachine extends StateMachine { pw.println("mWifiInfo: [" + mWifiInfo + "]"); pw.println("mLinkProperties: [" + mLinkProperties + "]"); pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]"); - pw.println("mWalledGardenIntervalMs: [" + mWalledGardenIntervalMs + "]"); pw.println("mPoorNetworkDetectionEnabled: [" + mPoorNetworkDetectionEnabled + "]"); - pw.println("mWalledGardenTestEnabled: [" + mWalledGardenTestEnabled + "]"); - pw.println("mWalledGardenUrl: [" + mWalledGardenUrl + "]"); } private boolean isWatchdogEnabled() { @@ -521,47 +442,6 @@ public class WifiWatchdogStateMachine extends StateMachine { mPoorNetworkDetectionEnabled = getSettingsBoolean(mContentResolver, Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, true); - mWalledGardenTestEnabled = getSettingsBoolean(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, true); - mWalledGardenUrl = getSettingsStr(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL, - DEFAULT_WALLED_GARDEN_URL); - mWalledGardenIntervalMs = Secure.getLong(mContentResolver, - Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS, - DEFAULT_WALLED_GARDEN_INTERVAL_MS); - } - - private void setWalledGardenNotificationVisible(boolean visible) { - // if it should be hidden and it is already hidden, then noop - if (!visible && !mWalledGardenNotificationShown) { - return; - } - - Resources r = Resources.getSystem(); - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - - if (visible) { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mWalledGardenUrl)); - intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); - - CharSequence title = r.getString(R.string.wifi_available_sign_in, 0); - CharSequence details = r.getString(R.string.wifi_available_sign_in_detailed, - mWifiInfo.getSSID()); - - Notification notification = new Notification(); - notification.when = 0; - notification.icon = com.android.internal.R.drawable.stat_notify_wifi_in_range; - notification.flags = Notification.FLAG_AUTO_CANCEL; - notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0); - notification.tickerText = title; - notification.setLatestEventInfo(mContext, title, details, notification.contentIntent); - - notificationManager.notify(WALLED_GARDEN_NOTIFICATION_ID, 1, notification); - } else { - notificationManager.cancel(WALLED_GARDEN_NOTIFICATION_ID, 1); - } - mWalledGardenNotificationShown = visible; } /** @@ -587,7 +467,6 @@ public class WifiWatchdogStateMachine extends StateMachine { case EVENT_NETWORK_STATE_CHANGE: case EVENT_SUPPLICANT_STATE_CHANGE: case EVENT_BSSID_CHANGE: - case CMD_DELAYED_WALLED_GARDEN_CHECK: case CMD_RSSI_FETCH: case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED: case WifiManager.RSSI_PKTCNT_FETCH_FAILED: @@ -685,11 +564,7 @@ public class WifiWatchdogStateMachine extends StateMachine { } break; case CONNECTED: - if (shouldCheckWalledGarden()) { - transitionTo(mWalledGardenCheckState); - } else { - transitionTo(mOnlineWatchState); - } + transitionTo(mOnlineWatchState); break; default: transitionTo(mNotConnectedState); @@ -716,7 +591,6 @@ public class WifiWatchdogStateMachine extends StateMachine { return NOT_HANDLED; } - setWalledGardenNotificationVisible(false); return HANDLED; } } @@ -834,38 +708,6 @@ public class WifiWatchdogStateMachine extends StateMachine { } /** - * Checking for wall garden. - */ - class WalledGardenCheckState extends State { - private int mWalledGardenToken = 0; - @Override - public void enter() { - if (DBG) logd(getName()); - sendMessageDelayed(obtainMessage(CMD_DELAYED_WALLED_GARDEN_CHECK, - ++mWalledGardenToken, 0), WALLED_GARDEN_START_DELAY_MS); - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_DELAYED_WALLED_GARDEN_CHECK: - if (msg.arg1 == mWalledGardenToken) { - mLastWalledGardenCheckTime = SystemClock.elapsedRealtime(); - if (isWalledGardenConnection()) { - if (DBG) logd("Walled garden detected"); - setWalledGardenNotificationVisible(true); - } - transitionTo(mOnlineWatchState); - } - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - /** * RSSI is high enough and don't need link monitoring. */ class OnlineWatchState extends State { @@ -1037,22 +879,6 @@ public class WifiWatchdogStateMachine extends StateMachine { } } - private boolean shouldCheckWalledGarden() { - if (!mWalledGardenTestEnabled) { - if (DBG) logd("Skipping walled garden check - disabled"); - return false; - } - - long waitTime = (mWalledGardenIntervalMs + mLastWalledGardenCheckTime) - - SystemClock.elapsedRealtime(); - - if (mLastWalledGardenCheckTime != 0 && waitTime > 0) { - if (DBG) logd("Skipping walled garden check - wait " + waitTime + " ms."); - return false; - } - return true; - } - private void updateCurrentBssid(String bssid) { if (DBG) logd("Update current BSSID to " + (bssid != null ? bssid : "null")); diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java index 6aea090..100e062 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java @@ -46,18 +46,8 @@ public class WifiP2pConfig implements Parcelable { */ public int groupOwnerIntent = -1; - /** - * Indicates whether the configuration is saved - * @hide - */ - public enum Persist { - SYSTEM_DEFAULT, - YES, - NO - } - /** @hide */ - public Persist persist = Persist.SYSTEM_DEFAULT; + public int netId = WifiP2pGroup.PERSISTENT_NET_ID; public WifiP2pConfig() { //set defaults @@ -110,7 +100,7 @@ public class WifiP2pConfig implements Parcelable { sbuf.append("\n address: ").append(deviceAddress); sbuf.append("\n wps: ").append(wps); sbuf.append("\n groupOwnerIntent: ").append(groupOwnerIntent); - sbuf.append("\n persist: ").append(persist.toString()); + sbuf.append("\n persist: ").append(netId); return sbuf.toString(); } @@ -125,7 +115,7 @@ public class WifiP2pConfig implements Parcelable { deviceAddress = source.deviceAddress; wps = new WpsInfo(source.wps); groupOwnerIntent = source.groupOwnerIntent; - persist = source.persist; + netId = source.netId; } } @@ -134,7 +124,7 @@ public class WifiP2pConfig implements Parcelable { dest.writeString(deviceAddress); dest.writeParcelable(wps, flags); dest.writeInt(groupOwnerIntent); - dest.writeString(persist.name()); + dest.writeInt(netId); } /** Implement the Parcelable interface */ @@ -145,7 +135,7 @@ public class WifiP2pConfig implements Parcelable { config.deviceAddress = in.readString(); config.wps = (WpsInfo) in.readParcelable(null); config.groupOwnerIntent = in.readInt(); - config.persist = Persist.valueOf(in.readString()); + config.netId = in.readInt(); return config; } diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java index afdc9be..c86ec8b 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java @@ -231,11 +231,26 @@ public class WifiP2pDevice implements Parcelable { return (deviceCapability & DEVICE_CAPAB_SERVICE_DISCOVERY) != 0; } + /** Returns true if the device is capable of invitation {@hide}*/ + public boolean isInvitationCapable() { + return (deviceCapability & DEVICE_CAPAB_INVITATION_PROCEDURE) != 0; + } + + /** Returns true if the device reaches the limit. {@hide}*/ + public boolean isDeviceLimit() { + return (deviceCapability & DEVICE_CAPAB_DEVICE_LIMIT) != 0; + } + /** Returns true if the device is a group owner */ public boolean isGroupOwner() { return (groupCapability & GROUP_CAPAB_GROUP_OWNER) != 0; } + /** Returns true if the group reaches the limit. {@hide}*/ + public boolean isGroupLimit() { + return (groupCapability & GROUP_CAPAB_GROUP_LIMIT) != 0; + } + @Override public boolean equals(Object obj) { if (this == obj) return true; diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java index c30cc73..bc492b3 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java @@ -33,6 +33,16 @@ import java.util.regex.Matcher; */ public class WifiP2pGroup implements Parcelable { + /** The temporary network id. + * {@hide} */ + public static final int TEMPORARY_NET_ID = -1; + + /** The persistent network id. + * If a matching persistent profile is found, use it. + * Otherwise, create a new persistent profile. + * {@hide} */ + public static final int PERSISTENT_NET_ID = -2; + /** The network name */ private String mNetworkName; @@ -50,13 +60,17 @@ public class WifiP2pGroup implements Parcelable { private String mInterface; + /** The network id in the wpa_supplicant */ + private int mNetId; + /** P2P group started string pattern */ private static final Pattern groupStartedPattern = Pattern.compile( "ssid=\"(.+)\" " + "freq=(\\d+) " + "(?:psk=)?([0-9a-fA-F]{64})?" + "(?:passphrase=)?(?:\"(.{8,63})\")? " + - "go_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2})" + "go_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2})" + + " ?(\\[PERSISTENT\\])?" ); public WifiP2pGroup() { @@ -67,13 +81,15 @@ public class WifiP2pGroup implements Parcelable { * * P2P-GROUP-STARTED p2p-wlan0-0 [client|GO] ssid="DIRECT-W8" freq=2437 * [psk=2182b2e50e53f260d04f3c7b25ef33c965a3291b9b36b455a82d77fd82ca15bc| - * passphrase="fKG4jMe3"] go_dev_addr=fa:7b:7a:42:02:13 + * passphrase="fKG4jMe3"] go_dev_addr=fa:7b:7a:42:02:13 [PERSISTENT] * * P2P-GROUP-REMOVED p2p-wlan0-0 [client|GO] reason=REQUESTED * * P2P-INVITATION-RECEIVED sa=fa:7b:7a:42:02:13 go_dev_addr=f8:7b:7a:42:02:13 * bssid=fa:7b:7a:42:82:13 unknown-network * + * P2P-INVITATION-RECEIVED sa=b8:f9:34:2a:c7:9d persistent=0 + * * Note: The events formats can be looked up in the wpa_supplicant code * @hide */ @@ -100,16 +116,38 @@ public class WifiP2pGroup implements Parcelable { //String psk = match.group(3); mPassphrase = match.group(4); mOwner = new WifiP2pDevice(match.group(5)); - + if (match.group(6) != null) { + mNetId = PERSISTENT_NET_ID; + } else { + mNetId = TEMPORARY_NET_ID; + } } else if (tokens[0].equals("P2P-INVITATION-RECEIVED")) { + String sa = null; + mNetId = PERSISTENT_NET_ID; for (String token : tokens) { String[] nameValue = token.split("="); if (nameValue.length != 2) continue; + if (nameValue[0].equals("sa")) { + sa = nameValue[1]; + + // set source address into the client list. + WifiP2pDevice dev = new WifiP2pDevice(); + dev.deviceAddress = nameValue[1]; + mClients.add(dev); + continue; + } + if (nameValue[0].equals("go_dev_addr")) { mOwner = new WifiP2pDevice(nameValue[1]); continue; } + + if (nameValue[0].equals("persistent")) { + mOwner = new WifiP2pDevice(sa); + mNetId = Integer.parseInt(nameValue[1]); + continue; + } } } else { throw new IllegalArgumentException("Malformed supplicant event"); @@ -212,6 +250,16 @@ public class WifiP2pGroup implements Parcelable { return mInterface; } + /** @hide */ + public int getNetworkId() { + return mNetId; + } + + /** @hide */ + public void setNetworkId(int netId) { + this.mNetId = netId; + } + public String toString() { StringBuffer sbuf = new StringBuffer(); sbuf.append("network: ").append(mNetworkName); @@ -221,6 +269,7 @@ public class WifiP2pGroup implements Parcelable { sbuf.append("\n Client: ").append(client); } sbuf.append("\n interface: ").append(mInterface); + sbuf.append("\n networkId: ").append(mNetId); return sbuf.toString(); } @@ -238,6 +287,7 @@ public class WifiP2pGroup implements Parcelable { for (WifiP2pDevice d : source.getClientList()) mClients.add(d); mPassphrase = source.getPassphrase(); mInterface = source.getInterface(); + mNetId = source.getNetworkId(); } } @@ -252,6 +302,7 @@ public class WifiP2pGroup implements Parcelable { } dest.writeString(mPassphrase); dest.writeString(mInterface); + dest.writeInt(mNetId); } /** Implement the Parcelable interface */ @@ -268,6 +319,7 @@ public class WifiP2pGroup implements Parcelable { } group.setPassphrase(in.readString()); group.setInterface(in.readString()); + group.setNetworkId(in.readInt()); return group; } diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.aidl b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.aidl new file mode 100644 index 0000000..3d8a476 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2012, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p; + +parcelable WifiP2pGroupList;
\ No newline at end of file diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java new file mode 100644 index 0000000..3459a5a --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.wifi.p2p; + +import java.util.Collection; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.LruCache; + + +/** + * A class representing a Wi-Fi P2p group list + * + * {@see WifiP2pManager} + * @hide + */ +public class WifiP2pGroupList implements Parcelable { + + private static final int CREDENTIAL_MAX_NUM = 32; + + private LruCache<Integer, WifiP2pGroup> mGroups; + private GroupDeleteListener mListener; + private boolean isClearCalled = false; + + public interface GroupDeleteListener { + public void onDeleteGroup(int netId); + } + + WifiP2pGroupList() { + this(null); + } + + WifiP2pGroupList(GroupDeleteListener listener) { + mListener = listener; + mGroups = new LruCache<Integer, WifiP2pGroup>(CREDENTIAL_MAX_NUM) { + @Override + protected void entryRemoved(boolean evicted, Integer netId, + WifiP2pGroup oldValue, WifiP2pGroup newValue) { + if (mListener != null && !isClearCalled) { + mListener.onDeleteGroup(oldValue.getNetworkId()); + } + } + }; + } + + /** + * Return the list of p2p group. + * + * @return the list of p2p group. + */ + public Collection<WifiP2pGroup> getGroupList() { + return mGroups.snapshot().values(); + } + + /** + * Add the specified group to this group list. + * + * @param group + */ + void add(WifiP2pGroup group) { + mGroups.put(group.getNetworkId(), group); + } + + /** + * Remove the group with the specified network id from this group list. + * + * @param netId + */ + void remove(int netId) { + mGroups.remove(netId); + } + + /** + * Remove the group with the specified device address from this group list. + * + * @param deviceAddress + */ + void remove(String deviceAddress) { + remove(getNetworkId(deviceAddress)); + } + + /** + * Clear the group. + */ + boolean clear() { + if (mGroups.size() == 0) return false; + isClearCalled = true; + mGroups.evictAll(); + isClearCalled = false; + return true; + } + + /** + * Return the network id of the group owner profile with the specified p2p device + * address. + * If more than one persistent group of the same address is present in the list, + * return the first one. + * + * @param deviceAddress p2p device address. + * @return the network id. if not found, return -1. + */ + int getNetworkId(String deviceAddress) { + if (deviceAddress == null) return -1; + + final Collection<WifiP2pGroup> groups = mGroups.snapshot().values(); + for (WifiP2pGroup grp: groups) { + if (deviceAddress.equalsIgnoreCase(grp.getOwner().deviceAddress)) { + // update cache ordered. + mGroups.get(grp.getNetworkId()); + return grp.getNetworkId(); + } + } + return -1; + } + + /** + * Return the network id of the group with the specified p2p device address + * and the ssid. + * + * @param deviceAddress p2p device address. + * @param ssid ssid. + * @return the network id. if not found, return -1. + */ + int getNetworkId(String deviceAddress, String ssid) { + if (deviceAddress == null || ssid == null) { + return -1; + } + + final Collection<WifiP2pGroup> groups = mGroups.snapshot().values(); + for (WifiP2pGroup grp: groups) { + if (deviceAddress.equalsIgnoreCase(grp.getOwner().deviceAddress) && + ssid.equals(grp.getNetworkName())) { + // update cache ordered. + mGroups.get(grp.getNetworkId()); + return grp.getNetworkId(); + } + } + + return -1; + } + + /** + * Return the group owner address of the group with the specified network id + * + * @param netId network id. + * @return the address. if not found, return null. + */ + String getOwnerAddr(int netId) { + WifiP2pGroup grp = mGroups.get(netId); + if (grp != null) { + return grp.getOwner().deviceAddress; + } + return null; + } + + /** + * Return true if this group list contains the specified network id. + * This function does NOT update LRU information. + * It means the internal queue is NOT reordered. + * + * @param netId network id. + * @return true if the specified network id is present in this group list. + */ + boolean contains(int netId) { + final Collection<WifiP2pGroup> groups = mGroups.snapshot().values(); + for (WifiP2pGroup grp: groups) { + if (netId == grp.getNetworkId()) { + return true; + } + } + return false; + } + + public String toString() { + StringBuffer sbuf = new StringBuffer(); + + final Collection<WifiP2pGroup> groups = mGroups.snapshot().values(); + for (WifiP2pGroup grp: groups) { + sbuf.append(grp).append("\n"); + } + return sbuf.toString(); + } + + /** Implement the Parcelable interface */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public void writeToParcel(Parcel dest, int flags) { + final Collection<WifiP2pGroup> groups = mGroups.snapshot().values(); + dest.writeInt(groups.size()); + for(WifiP2pGroup group : groups) { + dest.writeParcelable(group, flags); + } + } + + /** Implement the Parcelable interface */ + public static final Creator<WifiP2pGroupList> CREATOR = + new Creator<WifiP2pGroupList>() { + public WifiP2pGroupList createFromParcel(Parcel in) { + WifiP2pGroupList grpList = new WifiP2pGroupList(); + + int deviceCount = in.readInt(); + for (int i = 0; i < deviceCount; i++) { + grpList.add((WifiP2pGroup)in.readParcelable(null)); + } + return grpList; + } + + public WifiP2pGroupList[] newArray(int size) { + return new WifiP2pGroupList[size]; + } + }; +} diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java index 2c25e9d..96d3a7f 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java @@ -34,10 +34,10 @@ import android.os.IBinder; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.Messenger; import android.os.RemoteException; import android.os.ServiceManager; import android.os.WorkSource; -import android.os.Messenger; import android.util.Log; import com.android.internal.util.AsyncChannel; @@ -267,6 +267,13 @@ public class WifiP2pManager { public static final String EXTRA_WIFI_P2P_DEVICE = "wifiP2pDevice"; /** + * Broadcast intent action indicating that remembered persistent groups have changed. + * @hide + */ + public static final String WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION = + "android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED"; + + /** * The lookup key for a {@link #String} object. * Retrieve with {@link android.os.Bundle#getString(String)}. * @hide @@ -436,6 +443,18 @@ public class WifiP2pManager { /** @hide */ public static final int SHOW_PIN_REQUESTED = BASE + 58; + /** @hide */ + public static final int DELETE_PERSISTENT_GROUP = BASE + 59; + /** @hide */ + public static final int DELETE_PERSISTENT_GROUP_FAILED = BASE + 60; + /** @hide */ + public static final int DELETE_PERSISTENT_GROUP_SUCCEEDED = BASE + 61; + + /** @hide */ + public static final int REQUEST_PERSISTENT_GROUP_INFO = BASE + 62; + /** @hide */ + public static final int RESPONSE_PERSISTENT_GROUP_INFO = BASE + 63; + /** * Create a new WifiP2pManager instance. Applications use * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve @@ -657,6 +676,15 @@ public class WifiP2pManager { public void onDetached(int reason); } + /** Interface for callback invocation when stored group info list is available {@hide}*/ + public interface PersistentGroupInfoListener { + /** + * The requested stored p2p group info list is available + * @param groups Wi-Fi p2p group info list + */ + public void onPersistentGroupInfoAvailable(WifiP2pGroupList groups); + } + /** * A channel that connects the application to the Wifi p2p framework. * Most p2p operations require a Channel as an argument. An instance of Channel is obtained @@ -713,6 +741,7 @@ public class WifiP2pManager { case WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED: case WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED: case WifiP2pManager.SET_DEVICE_NAME_FAILED: + case WifiP2pManager.DELETE_PERSISTENT_GROUP_FAILED: if (listener != null) { ((ActionListener) listener).onFailure(message.arg1); } @@ -732,6 +761,7 @@ public class WifiP2pManager { case WifiP2pManager.REMOVE_SERVICE_REQUEST_SUCCEEDED: case WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED: case WifiP2pManager.SET_DEVICE_NAME_SUCCEEDED: + case WifiP2pManager.DELETE_PERSISTENT_GROUP_SUCCEEDED: if (listener != null) { ((ActionListener) listener).onSuccess(); } @@ -786,6 +816,13 @@ public class WifiP2pManager { mDialogListener = null; } break; + case WifiP2pManager.RESPONSE_PERSISTENT_GROUP_INFO: + WifiP2pGroupList groups = (WifiP2pGroupList) message.obj; + if (listener != null) { + ((PersistentGroupInfoListener) listener). + onPersistentGroupInfoAvailable(groups); + } + break; default: Log.d(TAG, "Ignored " + message); break; @@ -995,7 +1032,8 @@ public class WifiP2pManager { */ public void createGroup(Channel c, ActionListener listener) { checkChannel(c); - c.mAsyncChannel.sendMessage(CREATE_GROUP, 0, c.putListener(listener)); + c.mAsyncChannel.sendMessage(CREATE_GROUP, WifiP2pGroup.PERSISTENT_NET_ID, + c.putListener(listener)); } /** @@ -1297,6 +1335,40 @@ public class WifiP2pManager { } /** + * Delete a stored persistent group from the system settings. + * + * <p> The function call immediately returns after sending a persistent group removal request + * to the framework. The application is notified of a success or failure to initiate + * group removal through listener callbacks {@link ActionListener#onSuccess} or + * {@link ActionListener#onFailure}. + * + * <p>The persistent p2p group list stored in the system can be obtained by + * {@link #requestPersistentGroupInfo(Channel, PersistentGroupInfoListener)} and + * a network id can be obtained by {@link WifiP2pGroup#getNetworkId()}. + * + * @param c is the channel created at {@link #initialize} + * @param netId he network id of the p2p group. + * @param listener for callbacks on success or failure. Can be null. + * @hide + */ + public void deletePersistentGroup(Channel c, int netId, ActionListener listener) { + checkChannel(c); + c.mAsyncChannel.sendMessage(DELETE_PERSISTENT_GROUP, netId, c.putListener(listener)); + } + + /** + * Request a list of all the persistent p2p groups stored in system. + * + * @param c is the channel created at {@link #initialize} + * @param listener for callback when persistent group info list is available. Can be null. + * @hide + */ + public void requestPersistentGroupInfo(Channel c, PersistentGroupInfoListener listener) { + checkChannel(c); + c.mAsyncChannel.sendMessage(REQUEST_PERSISTENT_GROUP_INFO, 0, c.putListener(listener)); + } + + /** * Get a reference to WifiP2pService handler. This is used to establish * an AsyncChannel communication with WifiService * diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java index 6978084..8b8077ec 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java @@ -45,6 +45,7 @@ import android.net.wifi.WifiMonitor; import android.net.wifi.WifiNative; import android.net.wifi.WifiStateMachine; import android.net.wifi.WpsInfo; +import android.net.wifi.p2p.WifiP2pGroupList.GroupDeleteListener; import android.net.wifi.p2p.nsd.WifiP2pServiceInfo; import android.net.wifi.p2p.nsd.WifiP2pServiceRequest; import android.net.wifi.p2p.nsd.WifiP2pServiceResponse; @@ -118,6 +119,13 @@ public class WifiP2pService extends IWifiP2pManager.Stub { private static final Boolean JOIN_GROUP = true; private static final Boolean FORM_GROUP = false; + private static final Boolean TRY_REINVOCATION = true;; + private static final Boolean NO_REINVOCATION = false; + + private static final int CONNECT_FAILURE = -1; + private static final int CONNECT_SUCCESS = 0; + private static final int NEEDS_PROVISION_REQ = 1; + /* Two minutes comes from the wpa_supplicant setting */ private static final int GROUP_CREATING_WAIT_TIME_MS = 120 * 1000; private static int mGroupCreatingTimeoutIndex = 0; @@ -191,6 +199,84 @@ public class WifiP2pService extends IWifiP2pManager.Stub { private static final String[] DHCP_RANGE = {"192.168.49.2", "192.168.49.254"}; private static final String SERVER_ADDRESS = "192.168.49.1"; + /** + * Error code definition. + * see the Table.8 in the WiFi Direct specification for the detail. + */ + public static enum P2pStatus { + /* Success. */ + SUCCESS, + + /* The target device is currently unavailable. */ + INFORMATION_IS_CURRENTLY_UNAVAILABLE, + + /* Protocol error. */ + INCOMPATIBLE_PARAMETERS, + + /* The target device reached the limit of the number of the connectable device. + * For example, device limit or group limit is set. */ + LIMIT_REACHED, + + /* Protocol error. */ + INVALID_PARAMETER, + + /* Unable to accommodate request. */ + UNABLE_TO_ACCOMMODATE_REQUEST, + + /* Previous protocol error, or disruptive behavior. */ + PREVIOUS_PROTOCOL_ERROR, + + /* There is no common channels the both devices can use. */ + NO_COMMON_CHANNE, + + /* Unknown p2p group. For example, Device A tries to invoke the previous persistent group, + * but device B has removed the specified credential already. */ + UNKNOWN_P2P_GROUP, + + /* Both p2p devices indicated an intent of 15 in group owner negotiation. */ + BOTH_GO_INTENT_15, + + /* Incompatible provisioning method. */ + INCOMPATIBLE_PROVISIONING_METHOD, + + /* Rejected by user */ + REJECTED_BY_USER, + + /* Unknown error */ + UNKNOWN; + + public static P2pStatus valueOf(int error) { + switch(error) { + case 0 : + return SUCCESS; + case 1: + return INFORMATION_IS_CURRENTLY_UNAVAILABLE; + case 2: + return INCOMPATIBLE_PARAMETERS; + case 3: + return LIMIT_REACHED; + case 4: + return INVALID_PARAMETER; + case 5: + return UNABLE_TO_ACCOMMODATE_REQUEST; + case 6: + return PREVIOUS_PROTOCOL_ERROR; + case 7: + return NO_COMMON_CHANNE; + case 8: + return UNKNOWN_P2P_GROUP; + case 9: + return BOTH_GO_INTENT_15; + case 10: + return INCOMPATIBLE_PROVISIONING_METHOD; + case 11: + return REJECTED_BY_USER; + default: + return UNKNOWN; + } + } + } + public WifiP2pService(Context context) { mContext = context; @@ -273,6 +359,16 @@ public class WifiP2pService extends IWifiP2pManager.Stub { private WifiMonitor mWifiMonitor = new WifiMonitor(this, mWifiNative); private WifiP2pDeviceList mPeers = new WifiP2pDeviceList(); + private WifiP2pGroupList mGroups = new WifiP2pGroupList( + new GroupDeleteListener() { + @Override + public void onDeleteGroup(int netId) { + if (DBG) logd("called onDeleteGroup() netId=" + netId); + mWifiNative.removeNetwork(netId); + mWifiNative.saveConfig(); + sendP2pPersistentGroupsChangedBroadcast(); + } + }); private WifiP2pInfo mWifiP2pInfo = new WifiP2pInfo(); private WifiP2pGroup mGroup; @@ -395,6 +491,10 @@ public class WifiP2pService extends IWifiP2pManager.Stub { replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED, WifiP2pManager.BUSY); break; + case WifiP2pManager.DELETE_PERSISTENT_GROUP: + replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP, + WifiP2pManager.BUSY); + break; case WifiP2pManager.REQUEST_PEERS: replyToMessage(message, WifiP2pManager.RESPONSE_PEERS, mPeers); break; @@ -404,6 +504,10 @@ public class WifiP2pService extends IWifiP2pManager.Stub { case WifiP2pManager.REQUEST_GROUP_INFO: replyToMessage(message, WifiP2pManager.RESPONSE_GROUP_INFO, mGroup); break; + case WifiP2pManager.REQUEST_PERSISTENT_GROUP_INFO: + replyToMessage(message, WifiP2pManager.RESPONSE_PERSISTENT_GROUP_INFO, + mGroups); + break; case WifiP2pManager.SET_DIALOG_LISTENER: String appPkgName = (String)message.getData().getString( WifiP2pManager.APP_PKG_BUNDLE_KEY); @@ -520,6 +624,10 @@ public class WifiP2pService extends IWifiP2pManager.Stub { replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED, WifiP2pManager.P2P_UNSUPPORTED); break; + case WifiP2pManager.DELETE_PERSISTENT_GROUP: + replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP, + WifiP2pManager.P2P_UNSUPPORTED); + break; default: return NOT_HANDLED; } @@ -626,6 +734,8 @@ public class WifiP2pService extends IWifiP2pManager.Stub { break; case WifiStateMachine.CMD_DISABLE_P2P: if (mPeers.clear()) sendP2pPeersChangedBroadcast(); + if (mGroups.clear()) sendP2pPersistentGroupsChangedBroadcast(); + mWifiNative.closeSupplicantConnection(); transitionTo(mP2pDisablingState); break; @@ -734,6 +844,11 @@ public class WifiP2pService extends IWifiP2pManager.Stub { sendServiceResponse(resp); } break; + case WifiP2pManager.DELETE_PERSISTENT_GROUP: + if (DBG) logd(getName() + " delete persistent group"); + mGroups.remove(message.arg1); + replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP_SUCCEEDED); + break; default: return NOT_HANDLED; } @@ -768,47 +883,35 @@ public class WifiP2pService extends IWifiP2pManager.Stub { /* Update group capability before connect */ int gc = mWifiNative.getGroupCapability(config.deviceAddress); mPeers.updateGroupCapability(config.deviceAddress, gc); - - if (mSavedPeerConfig != null && config.deviceAddress.equals( - mSavedPeerConfig.deviceAddress)) { - mSavedPeerConfig = config; - - //Stop discovery before issuing connect - mWifiNative.p2pStopFind(); - if (mPeers.isGroupOwner(mSavedPeerConfig.deviceAddress)) { - p2pConnectWithPinDisplay(mSavedPeerConfig, JOIN_GROUP); - } else { - p2pConnectWithPinDisplay(mSavedPeerConfig, FORM_GROUP); - } - transitionTo(mGroupNegotiationState); - } else { - mSavedPeerConfig = config; - int netId = configuredNetworkId(mSavedPeerConfig.deviceAddress); - if (netId >= 0) { - //TODO: if failure, remove config and do a regular p2pConnect() - mWifiNative.p2pReinvoke(netId, mSavedPeerConfig.deviceAddress); - } else { - //Stop discovery before issuing connect - mWifiNative.p2pStopFind(); - //If peer is a GO, we do not need to send provisional discovery, - //the supplicant takes care of it. - if (mPeers.isGroupOwner(mSavedPeerConfig.deviceAddress)) { - if (DBG) logd("Sending join to GO"); - p2pConnectWithPinDisplay(mSavedPeerConfig, JOIN_GROUP); - transitionTo(mGroupNegotiationState); - } else { - if (DBG) logd("Sending prov disc"); - transitionTo(mProvisionDiscoveryState); - } - } + int connectRet = connect(config, TRY_REINVOCATION); + if (connectRet == CONNECT_FAILURE) { + replyToMessage(message, WifiP2pManager.CONNECT_FAILED); + break; } mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED); sendP2pPeersChangedBroadcast(); replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED); + if (connectRet == NEEDS_PROVISION_REQ) { + if (DBG) logd("Sending prov disc"); + transitionTo(mProvisionDiscoveryState); + break; + } + transitionTo(mGroupNegotiationState); + break; + case WifiP2pManager.STOP_DISCOVERY: + if (mWifiNative.p2pStopFind()) { + // When discovery stops in inactive state, flush to clear + // state peer data + mWifiNative.p2pFlush(); + mServiceDiscReqId = null; + replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_SUCCEEDED); + } else { + replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED, + WifiP2pManager.ERROR); + } break; case WifiMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT: mSavedPeerConfig = (WifiP2pConfig) message.obj; - mAutonomousGroup = false; mJoinExistingGroup = false; if (!sendConnectNoticeToApp(mPeers.get(mSavedPeerConfig.deviceAddress), @@ -848,13 +951,6 @@ public class WifiP2pService extends IWifiP2pManager.Stub { transitionTo(mUserAuthorizingInvitationState); } break; - case WifiMonitor.P2P_FIND_STOPPED_EVENT: - // When discovery stops in inactive state, flush to clear - // state peer data - mWifiNative.p2pFlush(); - mServiceDiscReqId = null; - sendP2pDiscoveryChangedBroadcast(false); - break; case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT: case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT: case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT: @@ -865,16 +961,44 @@ public class WifiP2pService extends IWifiP2pManager.Stub { break; case WifiP2pManager.CREATE_GROUP: mAutonomousGroup = true; - if (mWifiNative.p2pGroupAdd()) { - replyToMessage(message, WifiP2pManager.CREATE_GROUP_SUCCEEDED); + int netId = message.arg1; + boolean ret = false; + if (netId == WifiP2pGroup.PERSISTENT_NET_ID) { + // check if the go persistent group is present. + netId = mGroups.getNetworkId(mThisDevice.deviceAddress); + if (netId != -1) { + ret = mWifiNative.p2pGroupAdd(netId); + } else { + ret = mWifiNative.p2pGroupAdd(true); + } + } else { + ret = mWifiNative.p2pGroupAdd(false); + } + + if (ret) { + replyToMessage(message, WifiP2pManager.CREATE_GROUP_SUCCEEDED); + transitionTo(mGroupNegotiationState); + } else { + replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED, + WifiP2pManager.ERROR); + // remain at this state. + } + break; + case WifiMonitor.P2P_GROUP_STARTED_EVENT: + mGroup = (WifiP2pGroup) message.obj; + if (DBG) logd(getName() + " group started"); + + if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) { + // This is an invocation case. + mAutonomousGroup = false; + deferMessage(message); + transitionTo(mGroupNegotiationState); } else { - replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED, - WifiP2pManager.ERROR); + return NOT_HANDLED; } - transitionTo(mGroupNegotiationState); break; default: - return NOT_HANDLED; + return NOT_HANDLED; } return HANDLED; } @@ -941,10 +1065,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub { @Override public void enter() { if (DBG) logd(getName()); - if (!sendConnectNoticeToApp(mPeers.get(mSavedPeerConfig.deviceAddress), - mSavedPeerConfig)) { - notifyInvitationReceived(); - } + notifyInvitationReceived(); } @Override @@ -953,11 +1074,10 @@ public class WifiP2pService extends IWifiP2pManager.Stub { boolean ret = HANDLED; switch (message.what) { case PEER_CONNECTION_USER_ACCEPT: - //TODO: handle persistence - if (mJoinExistingGroup) { - p2pConnectWithPinDisplay(mSavedPeerConfig, JOIN_GROUP); - } else { - p2pConnectWithPinDisplay(mSavedPeerConfig, FORM_GROUP); + if (connect(mSavedPeerConfig, TRY_REINVOCATION) == CONNECT_FAILURE) { + handleGroupCreationFailure(); + transitionTo(mInactiveState); + break; } mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED); sendP2pPeersChangedBroadcast(); @@ -1000,7 +1120,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub { if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) { if (DBG) logd("Found a match " + mSavedPeerConfig); - mWifiNative.p2pConnect(mSavedPeerConfig, FORM_GROUP); + p2pConnectWithPinDisplay(mSavedPeerConfig); transitionTo(mGroupNegotiationState); } break; @@ -1013,11 +1133,14 @@ public class WifiP2pService extends IWifiP2pManager.Stub { if (DBG) logd("Found a match " + mSavedPeerConfig); /* we already have the pin */ if (!TextUtils.isEmpty(mSavedPeerConfig.wps.pin)) { - mWifiNative.p2pConnect(mSavedPeerConfig, FORM_GROUP); + p2pConnectWithPinDisplay(mSavedPeerConfig); transitionTo(mGroupNegotiationState); } else { mJoinExistingGroup = false; - transitionTo(mUserAuthorizingInvitationState); + if (!sendConnectNoticeToApp(mPeers.get(mSavedPeerConfig.deviceAddress), + mSavedPeerConfig)) { + transitionTo(mUserAuthorizingInvitationState); + } } } break; @@ -1029,7 +1152,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub { if (mSavedPeerConfig.wps.setup == WpsInfo.DISPLAY) { if (DBG) logd("Found a match " + mSavedPeerConfig); mSavedPeerConfig.wps.pin = provDisc.pin; - mWifiNative.p2pConnect(mSavedPeerConfig, FORM_GROUP); + p2pConnectWithPinDisplay(mSavedPeerConfig); if (!sendShowPinReqToFrontApp(provDisc.pin)) { notifyInvitationSent(provDisc.pin, device.deviceAddress); } @@ -1062,6 +1185,17 @@ public class WifiP2pService extends IWifiP2pManager.Stub { case WifiMonitor.P2P_GROUP_STARTED_EVENT: mGroup = (WifiP2pGroup) message.obj; if (DBG) logd(getName() + " group started"); + + if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) { + /* + * update cache information and set network id to mGroup. + */ + updatePersistentNetworks(); + String devAddr = mGroup.getOwner().deviceAddress; + mGroup.setNetworkId(mGroups.getNetworkId(devAddr, + mGroup.getNetworkName())); + } + if (mGroup.isGroupOwner()) { startDhcpServer(mGroup.getInterface()); } else { @@ -1090,6 +1224,29 @@ public class WifiP2pService extends IWifiP2pManager.Stub { // failure causes supplicant issues. Ignore right now. case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT: break; + case WifiMonitor.P2P_INVITATION_RESULT_EVENT: + P2pStatus status = (P2pStatus)message.obj; + if (status == P2pStatus.SUCCESS) { + // invocation was succeeded. + // wait P2P_GROUP_STARTED_EVENT. + break; + } else if (status == P2pStatus.UNKNOWN_P2P_GROUP) { + // target device has already removed the credential. + // So, remove this credential accordingly. + int netId = mSavedPeerConfig.netId; + if (netId >= 0) { + if (DBG) logd("Remove unknown client from the list"); + removeClientFromList(netId, mSavedPeerConfig.deviceAddress, true); + } + } + + // invocation is failed or deferred. Try another way to connect. + mSavedPeerConfig.netId = WifiP2pGroup.PERSISTENT_NET_ID; + if (connect(mSavedPeerConfig, NO_REINVOCATION) == CONNECT_FAILURE) { + handleGroupCreationFailure(); + transitionTo(mInactiveState); + } + break; default: return NOT_HANDLED; } @@ -1152,7 +1309,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub { } } sendP2pPeersChangedBroadcast(); - if (DBG) loge(getName() + " ap sta disconnected"); + if (DBG) logd(getName() + " ap sta disconnected"); } else { loge("Disconnect on unknown device: " + device); } @@ -1172,7 +1329,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub { } break; case WifiP2pManager.REMOVE_GROUP: - if (DBG) loge(getName() + " remove group"); + if (DBG) logd(getName() + " remove group"); if (mWifiNative.p2pGroupRemove(mGroup.getInterface())) { replyToMessage(message, WifiP2pManager.REMOVE_GROUP_SUCCEEDED); } else { @@ -1181,7 +1338,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub { } break; case WifiMonitor.P2P_GROUP_REMOVED_EVENT: - if (DBG) loge(getName() + " group removed"); + if (DBG) logd(getName() + " group removed"); Collection <WifiP2pDevice> devices = mGroup.getClientList(); boolean changed = false; for (WifiP2pDevice d : mPeers.getDeviceList()) { @@ -1252,6 +1409,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub { replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED); } else { logd("Inviting device : " + config.deviceAddress); + mSavedPeerConfig = config; if (mWifiNative.p2pInvite(mGroup, config.deviceAddress)) { mPeers.updateStatus(config.deviceAddress, WifiP2pDevice.INVITED); sendP2pPeersChangedBroadcast(); @@ -1263,6 +1421,29 @@ public class WifiP2pService extends IWifiP2pManager.Stub { } // TODO: figure out updating the status to declined when invitation is rejected break; + case WifiMonitor.P2P_INVITATION_RESULT_EVENT: + P2pStatus status = (P2pStatus)message.obj; + logd("===> INVITATION RESULT EVENT : " + status); + if (status == P2pStatus.SUCCESS) { + // invocation was succeeded. + break; + } else if (status == P2pStatus.UNKNOWN_P2P_GROUP) { + // target device has already removed the credential. + // So, remove this credential accordingly. + int netId = mGroup.getNetworkId(); + if (netId >= 0) { + if (DBG) logd("Remove unknown client from the list"); + if (!removeClientFromList(netId, + mSavedPeerConfig.deviceAddress, false)) { + // not found the client on the list + Slog.e(TAG, "Already removed the client, ignore"); + break; + } + // try invitation. + sendMessage(WifiP2pManager.CONNECT, mSavedPeerConfig); + } + } + break; case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT: case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT: case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT: @@ -1304,7 +1485,6 @@ public class WifiP2pService extends IWifiP2pManager.Stub { @Override public void enter() { if (DBG) logd(getName()); - notifyInvitationReceived(); } @@ -1394,6 +1574,13 @@ public class WifiP2pService extends IWifiP2pManager.Stub { mContext.sendStickyBroadcast(intent); } + private void sendP2pPersistentGroupsChangedBroadcast() { + if (DBG) logd("sending p2p persistent groups changed broadcast"); + Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mContext.sendStickyBroadcast(intent); + } + private void startDhcpServer(String intf) { InterfaceConfiguration ifcg = null; try { @@ -1521,11 +1708,246 @@ public class WifiP2pService extends IWifiP2pManager.Stub { dialog.show(); } - //TODO: implement when wpa_supplicant is fixed - private int configuredNetworkId(String deviceAddress) { + /** + * Synchronize the persistent group list between + * wpa_supplicant and mGroups. + */ + private void updatePersistentNetworks() { + String listStr = mWifiNative.listNetworks(); + + boolean isSaveRequired = false; + String[] lines = listStr.split("\n"); + // Skip the first line, which is a header + for (int i = 1; i < lines.length; i++) { + String[] result = lines[i].split("\t"); + if (result == null || result.length < 4) { + continue; + } + // network-id | ssid | bssid | flags + int netId = -1; + String ssid = result[1]; + String bssid = result[2]; + String flags = result[3]; + try { + netId = Integer.parseInt(result[0]); + } catch(NumberFormatException e) { + e.printStackTrace(); + continue; + } + + if (flags.indexOf("[CURRENT]") != -1) { + continue; + } + if (flags.indexOf("[P2P-PERSISTENT]") == -1) { + /* + * The unused profile is sometimes remained when the p2p group formation is failed. + * So, we clean up the p2p group here. + */ + if (DBG) logd("clean up the unused persistent group. netId=" + netId); + mWifiNative.removeNetwork(netId); + isSaveRequired = true; + continue; + } + + if (mGroups.contains(netId)) { + continue; + } + + WifiP2pGroup group = new WifiP2pGroup(); + group.setNetworkId(netId); + group.setNetworkName(ssid); + String mode = mWifiNative.getNetworkVariable(netId, "mode"); + if (mode != null && mode.equals("3")) { + group.setIsGroupOwner(true); + } + if (bssid.equalsIgnoreCase(mThisDevice.deviceAddress)) { + group.setOwner(mThisDevice); + } else { + WifiP2pDevice device = new WifiP2pDevice(); + device.deviceAddress = bssid; + group.setOwner(device); + } + mGroups.add(group); + isSaveRequired = true; + } + + if (isSaveRequired) { + sendP2pPersistentGroupsChangedBroadcast(); + mWifiNative.saveConfig(); + } + } + + /** + * Try to connect to the target device. + * + * Use the persistent credential if it has been stored. + * + * @param config + * @param tryInvocation if true, try to invoke. Otherwise, never try to invoke. + * @return + */ + private int connect(WifiP2pConfig config, boolean tryInvocation) { + + if (config == null) { + loge("invalid argument."); + return CONNECT_FAILURE; + } + + boolean isResp = (mSavedPeerConfig != null && + config.deviceAddress.equals(mSavedPeerConfig.deviceAddress)); + mSavedPeerConfig = config; + + WifiP2pDevice dev = mPeers.get(config.deviceAddress); + if (dev == null) { + loge("target device is not found."); + return CONNECT_FAILURE; + } + + boolean join = dev.isGroupOwner(); + String ssid = mWifiNative.p2pGetSsid(dev.deviceAddress); + if (DBG) logd("target ssid is " + ssid + " join:" + join); + + if (join && dev.isGroupLimit()) { + if (DBG) logd("target device reaches group limit."); + + // if the target group has reached the limit, + // try group formation. + join = false; + } else if (join) { + int netId = mGroups.getNetworkId(dev.deviceAddress, ssid); + if (netId >= 0) { + // Skip WPS and start 4way handshake immediately. + if (!mWifiNative.p2pGroupAdd(netId)) { + return CONNECT_FAILURE; + } + return CONNECT_SUCCESS; + } + } + + if (!join && dev.isDeviceLimit()) { + loge("target device reaches the device limit."); + return CONNECT_FAILURE; + } + + if (!join && tryInvocation && dev.isInvitationCapable()) { + int netId = WifiP2pGroup.PERSISTENT_NET_ID; + if (config.netId >= 0) { + if (config.deviceAddress.equals(mGroups.getOwnerAddr(config.netId))) { + netId = config.netId; + } + } else { + netId = mGroups.getNetworkId(dev.deviceAddress); + } + if (netId < 0) { + netId = getNetworkIdFromClientList(dev.deviceAddress); + } + if (DBG) logd("netId related with " + dev.deviceAddress + " = " + netId); + if (netId >= 0) { + + // Invoke the persistent group. + if (!mWifiNative.p2pReinvoke(netId, dev.deviceAddress)) { + loge("p2pReinvoke() failed"); + return CONNECT_FAILURE; + } + // Save network id. It'll be used when an invitation result event is received. + mSavedPeerConfig.netId = netId; + return CONNECT_SUCCESS; + } + } + + //Stop discovery before issuing connect + mWifiNative.p2pStopFind(); + + if (!isResp) { + return NEEDS_PROVISION_REQ; + } + + p2pConnectWithPinDisplay(config); + return CONNECT_SUCCESS; + } + + /** + * Return the network id of the group owner profile which has the p2p client with + * the specified device address in it's client list. + * If more than one persistent group of the same address is present in its client + * lists, return the first one. + * + * @param deviceAddress p2p device address. + * @return the network id. if not found, return -1. + */ + private int getNetworkIdFromClientList(String deviceAddress) { + if (deviceAddress == null) return -1; + + Collection<WifiP2pGroup> groups = mGroups.getGroupList(); + for (WifiP2pGroup group : groups) { + int netId = group.getNetworkId(); + String[] p2pClientList = getClientList(netId); + if (p2pClientList == null) continue; + for (String client : p2pClientList) { + if (deviceAddress.equalsIgnoreCase(client)) { + return netId; + } + } + } return -1; } + /** + * Return p2p client list associated with the specified network id. + * @param netId network id. + * @return p2p client list. if not found, return null. + */ + private String[] getClientList(int netId) { + String p2pClients = mWifiNative.getNetworkVariable(netId, "p2p_client_list"); + if (p2pClients == null) { + return null; + } + return p2pClients.split(" "); + } + + /** + * Remove the specified p2p client from the specified profile. + * @param netId network id of the profile. + * @param addr p2p client address to be removed. + * @param isRemovable if true, remove the specified profile if its client list becomes empty. + * @return whether removing the specified p2p client is successful or not. + */ + private boolean removeClientFromList(int netId, String addr, boolean isRemovable) { + StringBuilder modifiedClientList = new StringBuilder(); + String[] currentClientList = getClientList(netId); + boolean isClientRemoved = false; + if (currentClientList != null) { + for (String client : currentClientList) { + if (!client.equalsIgnoreCase(addr)) { + modifiedClientList.append(" "); + modifiedClientList.append(client); + } else { + isClientRemoved = true; + } + } + } + if (modifiedClientList.length() == 0 && isRemovable) { + // the client list is empty. so remove it. + if (DBG) logd("Remove unknown network"); + mGroups.remove(netId); + return true; + } + + if (!isClientRemoved) { + // specified p2p client is not found. already removed. + return false; + } + + if (DBG) logd("Modified client list: " + modifiedClientList); + if (modifiedClientList.length() == 0) { + modifiedClientList.append("\"\""); + } + mWifiNative.setNetworkVariable(netId, + "p2p_client_list", modifiedClientList.toString()); + mWifiNative.saveConfig(); + return true; + } + private void setWifiP2pInfoOnGroupFormation(String serverAddress) { mWifiP2pInfo.groupFormed = true; mWifiP2pInfo.isGroupOwner = mGroup.isGroupOwner(); @@ -1547,8 +1969,14 @@ public class WifiP2pService extends IWifiP2pManager.Stub { return deviceAddress; } - private void p2pConnectWithPinDisplay(WifiP2pConfig config, boolean join) { - String pin = mWifiNative.p2pConnect(config, join); + private void p2pConnectWithPinDisplay(WifiP2pConfig config) { + WifiP2pDevice dev = mPeers.get(config.deviceAddress); + if (dev == null) { + loge("target device is not found " + config.deviceAddress); + return; + } + + String pin = mWifiNative.p2pConnect(config, dev.isGroupOwner()); try { Integer.parseInt(pin); if (!sendShowPinReqToFrontApp(pin)) { @@ -1611,6 +2039,8 @@ public class WifiP2pService extends IWifiP2pManager.Stub { mWifiNative.p2pServiceFlush(); mServiceTransactionId = 0; mServiceDiscReqId = null; + + updatePersistentNetworks(); } private void updateThisDevice(int status) { @@ -1739,7 +2169,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub { //Application does not have transaction id information //go through stored requests to remove boolean removed = false; - for (int i=0; i < clientInfo.mReqList.size(); i++) { + for (int i=0; i<clientInfo.mReqList.size(); i++) { if (req.equals(clientInfo.mReqList.valueAt(i))) { removed = true; clientInfo.mReqList.removeAt(i); @@ -2078,5 +2508,4 @@ public class WifiP2pService extends IWifiP2pManager.Stub { mServList = new ArrayList<WifiP2pServiceInfo>(); } } - } |
