am a599469f: am c2461be6: Merge "Fix 2579461 Move install location values to secure settings. Diable attribute for UI. Set default value to auto. Add command line interface to set install location via pm." into froyo
Merge commit 'a599469f9095532cac95a8e7600412f156b88f1c' into kraken
* commit 'a599469f9095532cac95a8e7600412f156b88f1c':
Fix 2579461
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 707404b..6455103 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -51,6 +51,8 @@
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/app)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/content)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/FrameworkTest_intermediates/)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android.policy*)
+$(call add-clean-step, rm -rf $(TARGET_OUT_JAVA_LIBRARIES)/android.policy.jar)
# ************************************************
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index 41f070c..b8ba3f6 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -16,7 +16,7 @@
#include "installd.h"
-int install(const char *pkgname, uid_t uid, gid_t gid)
+int install(const char *pkgname, int encrypted_fs_flag, uid_t uid, gid_t gid)
{
char pkgdir[PKG_PATH_MAX];
char libdir[PKG_PATH_MAX];
@@ -27,11 +27,17 @@
}
- if (create_pkg_path(pkgdir, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX))
- return -1;
- if (create_pkg_path(libdir, PKG_LIB_PREFIX, pkgname, PKG_LIB_POSTFIX))
- return -1;
-
+ if (encrypted_fs_flag == USE_UNENCRYPTED_FS) {
+ if (create_pkg_path(pkgdir, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX))
+ return -1;
+ if (create_pkg_path(libdir, PKG_LIB_PREFIX, pkgname, PKG_LIB_POSTFIX))
+ return -1;
+ } else {
+ if (create_pkg_path(pkgdir, PKG_SEC_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX))
+ return -1;
+ if (create_pkg_path(libdir, PKG_SEC_LIB_PREFIX, pkgname, PKG_LIB_POSTFIX))
+ return -1;
+ }
if (mkdir(pkgdir, 0751) < 0) {
LOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno));
@@ -56,27 +62,38 @@
return 0;
}
-int uninstall(const char *pkgname)
+int uninstall(const char *pkgname, int encrypted_fs_flag)
{
char pkgdir[PKG_PATH_MAX];
- if (create_pkg_path(pkgdir, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX))
- return -1;
+ if (encrypted_fs_flag == USE_UNENCRYPTED_FS) {
+ if (create_pkg_path(pkgdir, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX))
+ return -1;
+ } else {
+ if (create_pkg_path(pkgdir, PKG_SEC_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX))
+ return -1;
+ }
/* delete contents AND directory, no exceptions */
return delete_dir_contents(pkgdir, 1, 0);
}
-int renamepkg(const char *oldpkgname, const char *newpkgname)
+int renamepkg(const char *oldpkgname, const char *newpkgname, int encrypted_fs_flag)
{
char oldpkgdir[PKG_PATH_MAX];
char newpkgdir[PKG_PATH_MAX];
- if (create_pkg_path(oldpkgdir, PKG_DIR_PREFIX, oldpkgname, PKG_DIR_POSTFIX))
- return -1;
- if (create_pkg_path(newpkgdir, PKG_DIR_PREFIX, newpkgname, PKG_DIR_POSTFIX))
- return -1;
-
+ if (encrypted_fs_flag == USE_UNENCRYPTED_FS) {
+ if (create_pkg_path(oldpkgdir, PKG_DIR_PREFIX, oldpkgname, PKG_DIR_POSTFIX))
+ return -1;
+ if (create_pkg_path(newpkgdir, PKG_DIR_PREFIX, newpkgname, PKG_DIR_POSTFIX))
+ return -1;
+ } else {
+ if (create_pkg_path(oldpkgdir, PKG_SEC_DIR_PREFIX, oldpkgname, PKG_DIR_POSTFIX))
+ return -1;
+ if (create_pkg_path(newpkgdir, PKG_SEC_DIR_PREFIX, newpkgname, PKG_DIR_POSTFIX))
+ return -1;
+ }
if (rename(oldpkgdir, newpkgdir) < 0) {
LOGE("cannot rename dir '%s' to '%s': %s\n", oldpkgdir, newpkgdir, strerror(errno));
@@ -85,29 +102,41 @@
return 0;
}
-int delete_user_data(const char *pkgname)
+int delete_user_data(const char *pkgname, int encrypted_fs_flag)
{
char pkgdir[PKG_PATH_MAX];
- if (create_pkg_path(pkgdir, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX))
- return -1;
+ if (encrypted_fs_flag == USE_UNENCRYPTED_FS) {
+ if (create_pkg_path(pkgdir, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX))
+ return -1;
+ } else {
+ if (create_pkg_path(pkgdir, PKG_SEC_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX))
+ return -1;
+ }
/* delete contents, excluding "lib", but not the directory itself */
return delete_dir_contents(pkgdir, 0, "lib");
}
-int delete_cache(const char *pkgname)
+int delete_cache(const char *pkgname, int encrypted_fs_flag)
{
char cachedir[PKG_PATH_MAX];
- if (create_pkg_path(cachedir, CACHE_DIR_PREFIX, pkgname, CACHE_DIR_POSTFIX))
- return -1;
-
+ if (encrypted_fs_flag == USE_UNENCRYPTED_FS) {
+ if (create_pkg_path(cachedir, CACHE_DIR_PREFIX, pkgname, CACHE_DIR_POSTFIX))
+ return -1;
+ } else {
+ if (create_pkg_path(cachedir, CACHE_SEC_DIR_PREFIX, pkgname, CACHE_DIR_POSTFIX))
+ return -1;
+ }
/* delete contents, not the directory, no exceptions */
return delete_dir_contents(cachedir, 0, 0);
}
+/* TODO(oam): depending on use case (ecryptfs or dmcrypt)
+ * change implementation
+ */
static int disk_free()
{
struct statfs sfs;
@@ -139,6 +168,39 @@
LOGI("free_cache(%d) avail %d\n", free_size, avail);
if (avail >= free_size) return 0;
+ /* First try encrypted dir */
+ d = opendir(PKG_SEC_DIR_PREFIX);
+ if (d == NULL) {
+ LOGE("cannot open %s\n", PKG_SEC_DIR_PREFIX);
+ } else {
+ dfd = dirfd(d);
+
+ while ((de = readdir(d))) {
+ if (de->d_type != DT_DIR) continue;
+ name = de->d_name;
+
+ /* always skip "." and ".." */
+ if (name[0] == '.') {
+ if (name[1] == 0) continue;
+ if ((name[1] == '.') && (name[2] == 0)) continue;
+ }
+
+ subfd = openat(dfd, name, O_RDONLY | O_DIRECTORY);
+ if (subfd < 0) continue;
+
+ delete_dir_contents_fd(subfd, "cache");
+ close(subfd);
+
+ avail = disk_free();
+ if (avail >= free_size) {
+ closedir(d);
+ return 0;
+ }
+ }
+ closedir(d);
+ }
+
+ /* Next try unencrypted dir... */
d = opendir(PKG_DIR_PREFIX);
if (d == NULL) {
LOGE("cannot open %s\n", PKG_DIR_PREFIX);
@@ -314,7 +376,7 @@
int get_size(const char *pkgname, const char *apkpath,
const char *fwdlock_apkpath,
- int *_codesize, int *_datasize, int *_cachesize)
+ int *_codesize, int *_datasize, int *_cachesize, int encrypted_fs_flag)
{
DIR *d;
int dfd;
@@ -349,8 +411,14 @@
}
}
- if (create_pkg_path(path, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX)) {
- goto done;
+ if (encrypted_fs_flag == 0) {
+ if (create_pkg_path(path, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX)) {
+ goto done;
+ }
+ } else {
+ if (create_pkg_path(path, PKG_SEC_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX)) {
+ goto done;
+ }
}
d = opendir(path);
diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c
index f6ca998..882c493 100644
--- a/cmds/installd/installd.c
+++ b/cmds/installd/installd.c
@@ -29,7 +29,7 @@
static int do_install(char **arg, char reply[REPLY_MAX])
{
- return install(arg[0], atoi(arg[1]), atoi(arg[2])); /* pkgname, uid, gid */
+ return install(arg[0], atoi(arg[1]), atoi(arg[2]), atoi(arg[3])); /* pkgname, uid, gid */
}
static int do_dexopt(char **arg, char reply[REPLY_MAX])
@@ -50,12 +50,12 @@
static int do_remove(char **arg, char reply[REPLY_MAX])
{
- return uninstall(arg[0]); /* pkgname */
+ return uninstall(arg[0], atoi(arg[1])); /* pkgname */
}
static int do_rename(char **arg, char reply[REPLY_MAX])
{
- return renamepkg(arg[0], arg[1]); /* oldpkgname, newpkgname */
+ return renamepkg(arg[0], arg[1], atoi(arg[2])); /* oldpkgname, newpkgname */
}
static int do_free_cache(char **arg, char reply[REPLY_MAX]) /* TODO int:free_size */
@@ -65,7 +65,7 @@
static int do_rm_cache(char **arg, char reply[REPLY_MAX])
{
- return delete_cache(arg[0]); /* pkgname */
+ return delete_cache(arg[0], atoi(arg[1])); /* pkgname */
}
static int do_protect(char **arg, char reply[REPLY_MAX])
@@ -81,7 +81,7 @@
int res = 0;
/* pkgdir, apkpath */
- res = get_size(arg[0], arg[1], arg[2], &codesize, &datasize, &cachesize);
+ res = get_size(arg[0], arg[1], arg[2], &codesize, &datasize, &cachesize, atoi(arg[3]));
sprintf(reply,"%d %d %d", codesize, datasize, cachesize);
return res;
@@ -89,7 +89,7 @@
static int do_rm_user_data(char **arg, char reply[REPLY_MAX])
{
- return delete_user_data(arg[0]); /* pkgname */
+ return delete_user_data(arg[0], atoi(arg[1])); /* pkgname */
}
static int do_movefiles(char **arg, char reply[REPLY_MAX])
@@ -105,17 +105,17 @@
struct cmdinfo cmds[] = {
{ "ping", 0, do_ping },
- { "install", 3, do_install },
+ { "install", 4, do_install },
{ "dexopt", 3, do_dexopt },
{ "movedex", 2, do_move_dex },
{ "rmdex", 1, do_rm_dex },
- { "remove", 1, do_remove },
- { "rename", 2, do_rename },
+ { "remove", 2, do_remove },
+ { "rename", 3, do_rename },
{ "freecache", 1, do_free_cache },
- { "rmcache", 1, do_rm_cache },
+ { "rmcache", 2, do_rm_cache },
{ "protect", 2, do_protect },
- { "getsize", 3, do_get_size },
- { "rmuserdata", 1, do_rm_user_data },
+ { "getsize", 4, do_get_size },
+ { "rmuserdata", 2, do_rm_user_data },
{ "movefiles", 0, do_movefiles },
};
diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h
index cfcdb98..8e4adb1 100644
--- a/cmds/installd/installd.h
+++ b/cmds/installd/installd.h
@@ -48,16 +48,23 @@
/* elements combined with a valid package name to form paths */
#define PKG_DIR_PREFIX "/data/data/"
+#define PKG_SEC_DIR_PREFIX "/data/secure/data/"
#define PKG_DIR_POSTFIX ""
#define PKG_LIB_PREFIX "/data/data/"
+#define PKG_SEC_LIB_PREFIX "/data/secure/data/"
#define PKG_LIB_POSTFIX "/lib"
#define CACHE_DIR_PREFIX "/data/data/"
+#define CACHE_SEC_DIR_PREFIX "/data/secure/data/"
#define CACHE_DIR_POSTFIX "/cache"
#define APK_DIR_PREFIX "/data/app/"
+/* Encrypted File SYstems constants */
+#define USE_ENCRYPTED_FS 1
+#define USE_UNENCRYPTED_FS 0
+
/* other handy constants */
#define PROTECTED_DIR_PREFIX "/data/app-private/"
@@ -89,16 +96,16 @@
/* commands.c */
-int install(const char *pkgname, uid_t uid, gid_t gid);
-int uninstall(const char *pkgname);
-int renamepkg(const char *oldpkgname, const char *newpkgname);
-int delete_user_data(const char *pkgname);
-int delete_cache(const char *pkgname);
+int install(const char *pkgname, int encrypted_fs_flag, uid_t uid, gid_t gid);
+int uninstall(const char *pkgname, int encrypted_fs_flag);
+int renamepkg(const char *oldpkgname, const char *newpkgname, int encrypted_fs_flag);
+int delete_user_data(const char *pkgname, int encrypted_fs_flag);
+int delete_cache(const char *pkgname, int encrypted_fs_flag);
int move_dex(const char *src, const char *dst);
int rm_dex(const char *path);
int protect(char *pkgname, gid_t gid);
int get_size(const char *pkgname, const char *apkpath, const char *fwdlock_apkpath,
- int *codesize, int *datasize, int *cachesize);
+ int *codesize, int *datasize, int *cachesize, int encrypted_fs_flag);
int free_cache(int free_size);
int dexopt(const char *apk_path, uid_t uid, int is_public);
int movefiles();
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 1cd7aa7..1d9e0f1 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -1483,7 +1483,13 @@
}
private static String getDatabaseName() {
- return DATABASE_NAME;
+ if(Environment.isEncryptedFilesystemEnabled()) {
+ // Hard-coded path in case of encrypted file system
+ return Environment.getSystemSecureDirectory().getPath() + File.separator + DATABASE_NAME;
+ } else {
+ // Regular path in case of non-encrypted file system
+ return DATABASE_NAME;
+ }
}
private class DatabaseHelper extends SQLiteOpenHelper {
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 98a4993..6413cec 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -317,7 +317,9 @@
if (sSyncStorageEngine != null) {
return;
}
- File dataDir = Environment.getDataDirectory();
+ // This call will return the correct directory whether Encrypted File Systems is
+ // enabled or not.
+ File dataDir = Environment.getSecureDataDirectory();
sSyncStorageEngine = new SyncStorageEngine(context, dataDir);
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 0a04e5b..023f9c4 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -252,6 +252,16 @@
public static final int FLAG_RESTORE_ANY_VERSION = 1<<17;
/**
+ * Value for {@link #flags}: this is true if the application has set
+ * its android:neverEncrypt to true, false otherwise. It is used to specify
+ * that this package specifically "opts-out" of a secured file system solution,
+ * and will always store its data in-the-clear.
+ *
+ * {@hide}
+ */
+ public static final int FLAG_NEVER_ENCRYPT = 1<<18;
+
+ /**
* Value for {@link #flags}: Set to true if the application has been
* installed using the forward lock option.
*
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 2a20a2d..f9e12ce 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1542,6 +1542,12 @@
ai.flags |= ApplicationInfo.FLAG_TEST_ONLY;
}
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_neverEncrypt,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_NEVER_ENCRYPT;
+ }
+
String str;
str = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifestApplication_permission, 0);
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 812391c..c7cbed6 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -28,6 +28,8 @@
private static final File ROOT_DIRECTORY
= getDirectory("ANDROID_ROOT", "/system");
+ private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";
+
private static IMountService mMntSvc = null;
/**
@@ -37,9 +39,55 @@
return ROOT_DIRECTORY;
}
+ /**
+ * Gets the system directory available for secure storage.
+ * If Encrypted File system is enabled, it returns an encrypted directory (/data/secure/system).
+ * Otherwise, it returns the unencrypted /data/system directory.
+ * @return File object representing the secure storage system directory.
+ * @hide
+ */
+ public static File getSystemSecureDirectory() {
+ if (isEncryptedFilesystemEnabled()) {
+ return new File(SECURE_DATA_DIRECTORY, "system");
+ } else {
+ return new File(DATA_DIRECTORY, "system");
+ }
+ }
+
+ /**
+ * Gets the data directory for secure storage.
+ * If Encrypted File system is enabled, it returns an encrypted directory (/data/secure).
+ * Otherwise, it returns the unencrypted /data directory.
+ * @return File object representing the data directory for secure storage.
+ * @hide
+ */
+ public static File getSecureDataDirectory() {
+ if (isEncryptedFilesystemEnabled()) {
+ return SECURE_DATA_DIRECTORY;
+ } else {
+ return DATA_DIRECTORY;
+ }
+ }
+
+ /**
+ * Returns whether the Encrypted File System feature is enabled on the device or not.
+ * @return <code>true</code> if Encrypted File System feature is enabled, <code>false</code>
+ * if disabled.
+ * @hide
+ */
+ public static boolean isEncryptedFilesystemEnabled() {
+ return SystemProperties.getBoolean(SYSTEM_PROPERTY_EFS_ENABLED, false);
+ }
+
private static final File DATA_DIRECTORY
= getDirectory("ANDROID_DATA", "/data");
+ /**
+ * @hide
+ */
+ private static final File SECURE_DATA_DIRECTORY
+ = getDirectory("ANDROID_SECURE_DATA", "/data/secure");
+
private static final File EXTERNAL_STORAGE_DIRECTORY
= getDirectory("EXTERNAL_STORAGE", "/sdcard");
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index b3ec114..1b103aa 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -352,6 +352,23 @@
}
/**
+ * Reboot into the recovery system to wipe the /data partition and toggle
+ * Encrypted File Systems on/off.
+ * @param extras to add to the RECOVERY_COMPLETED intent after rebooting.
+ * @throws IOException if something goes wrong.
+ *
+ * @hide
+ */
+ public static void rebootToggleEFS(Context context, boolean efsEnabled)
+ throws IOException {
+ if (efsEnabled) {
+ bootCommand(context, "--set_encrypted_filesystem=on");
+ } else {
+ bootCommand(context, "--set_encrypted_filesystem=off");
+ }
+ }
+
+ /**
* Reboot into the recovery system with the supplied argument.
* @param arg to pass to the recovery utility.
* @throws IOException if something goes wrong.
diff --git a/core/java/com/android/internal/app/NetInitiatedActivity.java b/core/java/com/android/internal/app/NetInitiatedActivity.java
index 24818a8..03b0957 100755
--- a/core/java/com/android/internal/app/NetInitiatedActivity.java
+++ b/core/java/com/android/internal/app/NetInitiatedActivity.java
@@ -30,7 +30,6 @@
import android.widget.Toast;
import android.util.Log;
import android.location.LocationManager;
-import com.android.internal.location.GpsLocationProvider;
import com.android.internal.location.GpsNetInitiatedHandler;
/**
diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java
index 23e2277..fa47ff6 100644
--- a/core/java/com/android/internal/widget/DigitalClock.java
+++ b/core/java/com/android/internal/widget/DigitalClock.java
@@ -30,7 +30,7 @@
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.RelativeLayout;
+import android.widget.LinearLayout;
import android.widget.TextView;
import java.text.DateFormatSymbols;
@@ -39,7 +39,7 @@
/**
* Displays the time
*/
-public class DigitalClock extends RelativeLayout {
+public class DigitalClock extends LinearLayout {
private final static String M12 = "h:mm";
private final static String M24 = "kk:mm";
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 85d1a6f..6d1a414 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -121,7 +121,6 @@
android_server_BluetoothA2dpService.cpp \
android_message_digest_sha1.cpp \
android_ddm_DdmHandleNativeHeap.cpp \
- android_location_GpsLocationProvider.cpp \
com_android_internal_os_ZygoteInit.cpp \
com_android_internal_graphics_NativeUtils.cpp \
android_backup_BackupDataInput.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 7f8e854..c9e5bdc 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -154,7 +154,6 @@
extern int register_android_server_BluetoothA2dpService(JNIEnv* env);
extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
extern int register_com_android_internal_os_ZygoteInit(JNIEnv* env);
-extern int register_android_location_GpsLocationProvider(JNIEnv* env);
extern int register_android_backup_BackupDataInput(JNIEnv *env);
extern int register_android_backup_BackupDataOutput(JNIEnv *env);
extern int register_android_backup_FileBackupHelperBase(JNIEnv *env);
@@ -1267,7 +1266,6 @@
REG_JNI(register_android_server_BluetoothA2dpService),
REG_JNI(register_android_message_digest_sha1),
REG_JNI(register_android_ddm_DdmHandleNativeHeap),
- REG_JNI(register_android_location_GpsLocationProvider),
REG_JNI(register_android_backup_BackupDataInput),
REG_JNI(register_android_backup_BackupDataOutput),
REG_JNI(register_android_backup_FileBackupHelperBase),
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml
index a4b2357..b404955 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml
@@ -41,21 +41,20 @@
android:ellipsize="marquee"
android:gravity="right|bottom"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="22sp"
/>
- <!-- "emergency calls only" shown when sim is missing or PUKd -->
- <TextView
- android:id="@+id/emergencyCallText"
+ <!-- emergency call button shown when sim is missing or PUKd -->
+ <Button
+ android:id="@+id/emergencyCallButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_below="@id/carrier"
+ android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
- android:layout_marginTop="0dip"
+ android:layout_marginTop="10dip"
android:layout_marginRight="8dip"
- android:text="@string/emergency_calls_only"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="@color/white"
+ android:drawableLeft="@drawable/ic_emergency"
+ style="@style/Widget.Button.Transparent"
+ android:drawablePadding="8dip"
/>
<!-- time and date -->
@@ -65,7 +64,6 @@
android:layout_below="@id/carrier"
android:layout_marginTop="52dip"
android:layout_marginLeft="20dip"
- android:layout_marginBottom="8dip"
>
<TextView android:id="@+id/timeDisplay"
@@ -73,6 +71,7 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="none"
+ android:gravity="bottom"
android:textSize="72sp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:shadowColor="#C0000000"
@@ -85,9 +84,8 @@
<TextView android:id="@+id/am_pm"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/timeDisplay"
- android:layout_alignBaseline="@id/timeDisplay"
+ android:layout_height="match_parent"
+ android:gravity="bottom"
android:singleLine="true"
android:ellipsize="none"
android:textSize="22sp"
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
index e1c9772..6ee659c 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
@@ -46,19 +46,18 @@
android:ellipsize="marquee"
android:gravity="right|bottom"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="22sp"
/>
- <!-- "emergency calls only" shown when sim is missing or PUKd -->
- <TextView
- android:id="@+id/emergencyCallText"
+ <!-- emergency call button shown when sim is missing or PUKd -->
+ <Button
+ android:id="@+id/emergencyCallButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginTop="20dip"
- android:text="@string/emergency_calls_only"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="@color/white"
+ android:drawableLeft="@drawable/ic_emergency"
+ style="@style/Widget.Button.Transparent"
+ android:drawablePadding="8dip"
/>
<com.android.internal.widget.DigitalClock android:id="@+id/time"
@@ -66,12 +65,12 @@
android:layout_height="wrap_content"
android:layout_below="@id/carrier"
android:layout_marginTop="56dip"
- android:layout_marginBottom="8dip"
>
<TextView android:id="@+id/timeDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:gravity="bottom"
android:singleLine="true"
android:ellipsize="none"
android:textSize="72sp"
@@ -86,9 +85,8 @@
<TextView android:id="@+id/am_pm"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/timeDisplay"
- android:layout_alignBaseline="@id/timeDisplay"
+ android:layout_height="match_parent"
+ android:gravity="bottom"
android:singleLine="true"
android:ellipsize="none"
android:textSize="22sp"
diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
index 83381a1..c1b406f 100644
--- a/core/res/res/layout/keyguard_screen_unlock_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
@@ -58,19 +58,18 @@
android:ellipsize="marquee"
android:gravity="right|bottom"
/>
-
<com.android.internal.widget.DigitalClock android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_marginTop="8dip"
- android:layout_marginBottom="8dip"
>
<TextView android:id="@+id/timeDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:gravity="bottom"
android:singleLine="true"
android:ellipsize="none"
android:textSize="72sp"
@@ -85,9 +84,8 @@
<TextView android:id="@+id/am_pm"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/timeDisplay"
- android:layout_alignBaseline="@id/timeDisplay"
+ android:layout_height="match_parent"
+ android:gravity="bottom"
android:singleLine="true"
android:ellipsize="none"
android:textSize="22sp"
diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
index 8dacfaf..74a0eee 100644
--- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
@@ -55,12 +55,12 @@
android:layout_alignParentTop="true"
android:layout_marginTop="15dip"
android:layout_marginLeft="20dip"
- android:layout_marginBottom="8dip"
>
<TextView android:id="@+id/timeDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:gravity="bottom"
android:singleLine="true"
android:ellipsize="none"
android:textSize="56sp"
@@ -74,9 +74,8 @@
<TextView android:id="@+id/am_pm"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/timeDisplay"
- android:layout_alignBaseline="@id/timeDisplay"
+ android:layout_height="match_parent"
+ android:gravity="bottom"
android:singleLine="true"
android:ellipsize="none"
android:textSize="18sp"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 3585bf1..65ec2f7 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -79,6 +79,13 @@
by applications. -->
<attr name="allowClearUserData" format="boolean" />
+ <!-- Option to let applications specify that user data should
+ never be encrypted if an Encrypted File System solution
+ is enabled. Specifically, this is an "opt-out" feature, meaning
+ that, by default, user data will be encrypted if the EFS feature
+ is enabled. -->
+ <attr name="neverEncrypt" format="boolean" />
+
<!-- Option to indicate this application is only for testing purposes.
For example, it may expose functionality or data outside of itself
that would cause a security hole, but is useful for testing. This
@@ -715,6 +722,7 @@
<attr name="killAfterRestore" />
<attr name="restoreNeedsApplication" />
<attr name="restoreAnyVersion" />
+ <attr name="neverEncrypt" />
</declare-styleable>
<!-- The <code>permission</code> tag declares a security permission that can be
diff --git a/libs/surfaceflinger/SurfaceFlinger.cpp b/libs/surfaceflinger/SurfaceFlinger.cpp
index 0722fda..8a197e2 100644
--- a/libs/surfaceflinger/SurfaceFlinger.cpp
+++ b/libs/surfaceflinger/SurfaceFlinger.cpp
@@ -1298,7 +1298,7 @@
format = PIXEL_FORMAT_RGBA_8888;
break;
case PIXEL_FORMAT_OPAQUE:
- format = PIXEL_FORMAT_RGB_565;
+ format = PIXEL_FORMAT_RGBX_8888;
break;
}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 9e4a16b..28bc599 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -117,6 +117,37 @@
*/
public static final String KEY_LOCATION_CHANGED = "location";
+ /**
+ * Broadcast intent action indicating that the GPS has either been
+ * enabled or disabled. An intent extra provides this state as a boolean,
+ * where {@code true} means enabled.
+ * @see #EXTRA_GPS_ENABLED
+ *
+ * {@hide}
+ */
+ public static final String GPS_ENABLED_CHANGE_ACTION =
+ "android.location.GPS_ENABLED_CHANGE";
+
+ /**
+ * Broadcast intent action indicating that the GPS has either started or
+ * stopped receiving GPS fixes. An intent extra provides this state as a
+ * boolean, where {@code true} means that the GPS is actively receiving fixes.
+ * @see #EXTRA_GPS_ENABLED
+ *
+ * {@hide}
+ */
+ public static final String GPS_FIX_CHANGE_ACTION =
+ "android.location.GPS_FIX_CHANGE";
+
+ /**
+ * The lookup key for a boolean that indicates whether GPS is enabled or
+ * disabled. {@code true} means GPS is enabled. Retrieve it with
+ * {@link android.content.Intent#getBooleanExtra(String,boolean)}.
+ *
+ * {@hide}
+ */
+ public static final String EXTRA_GPS_ENABLED = "enabled";
+
// Map from LocationListeners to their associated ListenerTransport objects
private HashMap<LocationListener,ListenerTransport> mListeners =
new HashMap<LocationListener,ListenerTransport>();
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index a5466d1..d3a71b3 100755
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -23,6 +23,7 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.location.LocationManager;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
@@ -81,7 +82,7 @@
private final Context mContext;
// parent gps location provider
- private final GpsLocationProvider mGpsLocationProvider;
+ private final LocationManager mLocationManager;
// configuration of notificaiton behavior
private boolean mPlaySounds = false;
@@ -93,25 +94,25 @@
public static class GpsNiNotification
{
- int notificationId;
- int niType;
- boolean needNotify;
- boolean needVerify;
- boolean privacyOverride;
- int timeout;
- int defaultResponse;
- String requestorId;
- String text;
- int requestorIdEncoding;
- int textEncoding;
- Bundle extras;
+ public int notificationId;
+ public int niType;
+ public boolean needNotify;
+ public boolean needVerify;
+ public boolean privacyOverride;
+ public int timeout;
+ public int defaultResponse;
+ public String requestorId;
+ public String text;
+ public int requestorIdEncoding;
+ public int textEncoding;
+ public Bundle extras;
};
public static class GpsNiResponse {
- /* User reponse, one of the values in GpsUserResponseType */
- int userResponse;
- /* Optional extra data to pass with the user response */
- Bundle extras;
+ /* User reponse, one of the values in GpsUserResponseType */
+ int userResponse;
+ /* Optional extra data to pass with the user response */
+ Bundle extras;
};
/**
@@ -122,63 +123,57 @@
*/
private Notification mNiNotification;
- public GpsNetInitiatedHandler(Context context, GpsLocationProvider gpsLocationProvider) {
- mContext = context;
- mGpsLocationProvider = gpsLocationProvider;
+ public GpsNetInitiatedHandler(Context context) {
+ mContext = context;
+ mLocationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
}
// Handles NI events from HAL
public void handleNiNotification(GpsNiNotification notif)
{
- if (DEBUG) Log.d(TAG, "handleNiNotification" + " notificationId: " + notif.notificationId
- + " requestorId: " + notif.requestorId + " text: " + notif.text);
-
- // Notify and verify with immediate pop-up
- if (notif.needNotify && notif.needVerify && mPopupImmediately)
- {
- // Popup the dialog box now
- openNiDialog(notif);
- }
-
- // Notify only, or delayed pop-up (change mPopupImmediately to FALSE)
- if (notif.needNotify && !notif.needVerify ||
- notif.needNotify && notif.needVerify && !mPopupImmediately)
- {
- // Show the notification
+ if (DEBUG) Log.d(TAG, "handleNiNotification" + " notificationId: " + notif.notificationId
+ + " requestorId: " + notif.requestorId + " text: " + notif.text);
- // if mPopupImmediately == FALSE and needVerify == TRUE, a dialog will be opened
- // when the user opens the notification message
-
- setNiNotification(notif);
- }
-
- // ACCEPT cases: 1. Notify, no verify; 2. no notify, no verify; 3. privacy override.
- if ( notif.needNotify && !notif.needVerify ||
- !notif.needNotify && !notif.needVerify ||
- notif.privacyOverride)
- {
- try {
- mGpsLocationProvider.getNetInitiatedListener().sendNiResponse(notif.notificationId, GPS_NI_RESPONSE_ACCEPT);
- }
- catch (RemoteException e)
- {
- Log.e(TAG, e.getMessage());
- }
- }
-
- //////////////////////////////////////////////////////////////////////////
- // A note about timeout
- // According to the protocol, in the need_notify and need_verify case,
- // a default response should be sent when time out.
- //
- // In some GPS hardware, the GPS driver (under HAL) can handle the timeout case
- // and this class GpsNetInitiatedHandler does not need to do anything.
- //
- // However, the UI should at least close the dialog when timeout. Further,
- // for more general handling, timeout response should be added to the Handler here.
- //
+ // Notify and verify with immediate pop-up
+ if (notif.needNotify && notif.needVerify && mPopupImmediately)
+ {
+ // Popup the dialog box now
+ openNiDialog(notif);
+ }
+
+ // Notify only, or delayed pop-up (change mPopupImmediately to FALSE)
+ if (notif.needNotify && !notif.needVerify ||
+ notif.needNotify && notif.needVerify && !mPopupImmediately)
+ {
+ // Show the notification
+
+ // if mPopupImmediately == FALSE and needVerify == TRUE, a dialog will be opened
+ // when the user opens the notification message
+
+ setNiNotification(notif);
+ }
+
+ // ACCEPT cases: 1. Notify, no verify; 2. no notify, no verify; 3. privacy override.
+ if ( notif.needNotify && !notif.needVerify ||
+ !notif.needNotify && !notif.needVerify ||
+ notif.privacyOverride)
+ {
+ mLocationManager.sendNiResponse(notif.notificationId, GPS_NI_RESPONSE_ACCEPT);
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // A note about timeout
+ // According to the protocol, in the need_notify and need_verify case,
+ // a default response should be sent when time out.
+ //
+ // In some GPS hardware, the GPS driver (under HAL) can handle the timeout case
+ // and this class GpsNetInitiatedHandler does not need to do anything.
+ //
+ // However, the UI should at least close the dialog when timeout. Further,
+ // for more general handling, timeout response should be added to the Handler here.
+ //
}
-
+
// Sets the NI notification.
private synchronized void setNiNotification(GpsNiNotification notif) {
NotificationManager notificationManager = (NotificationManager) mContext
@@ -186,272 +181,272 @@
if (notificationManager == null) {
return;
}
-
- String title = getNotifTitle(notif);
- String message = getNotifMessage(notif);
-
+
+ String title = getNotifTitle(notif);
+ String message = getNotifMessage(notif);
+
if (DEBUG) Log.d(TAG, "setNiNotification, notifyId: " + notif.notificationId +
- ", title: " + title +
- ", message: " + message);
-
- // Construct Notification
- if (mNiNotification == null) {
- mNiNotification = new Notification();
- mNiNotification.icon = com.android.internal.R.drawable.stat_sys_gps_on; /* Change notification icon here */
- mNiNotification.when = 0;
+ ", title: " + title +
+ ", message: " + message);
+
+ // Construct Notification
+ if (mNiNotification == null) {
+ mNiNotification = new Notification();
+ mNiNotification.icon = com.android.internal.R.drawable.stat_sys_gps_on; /* Change notification icon here */
+ mNiNotification.when = 0;
}
-
+
if (mPlaySounds) {
- mNiNotification.defaults |= Notification.DEFAULT_SOUND;
+ mNiNotification.defaults |= Notification.DEFAULT_SOUND;
} else {
- mNiNotification.defaults &= ~Notification.DEFAULT_SOUND;
+ mNiNotification.defaults &= ~Notification.DEFAULT_SOUND;
}
-
+
mNiNotification.flags = Notification.FLAG_ONGOING_EVENT;
mNiNotification.tickerText = getNotifTicker(notif);
-
+
// if not to popup dialog immediately, pending intent will open the dialog
- Intent intent = !mPopupImmediately ? getDlgIntent(notif) : new Intent();
+ Intent intent = !mPopupImmediately ? getDlgIntent(notif) : new Intent();
PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
mNiNotification.setLatestEventInfo(mContext, title, message, pi);
-
+
if (visible) {
notificationManager.notify(notif.notificationId, mNiNotification);
} else {
notificationManager.cancel(notif.notificationId);
}
}
-
- // Opens the notification dialog and waits for user input
- private void openNiDialog(GpsNiNotification notif)
- {
- Intent intent = getDlgIntent(notif);
-
- if (DEBUG) Log.d(TAG, "openNiDialog, notifyId: " + notif.notificationId +
- ", requestorId: " + notif.requestorId +
- ", text: " + notif.text);
- mContext.startActivity(intent);
+ // Opens the notification dialog and waits for user input
+ private void openNiDialog(GpsNiNotification notif)
+ {
+ Intent intent = getDlgIntent(notif);
+
+ if (DEBUG) Log.d(TAG, "openNiDialog, notifyId: " + notif.notificationId +
+ ", requestorId: " + notif.requestorId +
+ ", text: " + notif.text);
+
+ mContext.startActivity(intent);
}
-
+
// Construct the intent for bringing up the dialog activity, which shows the
// notification and takes user input
private Intent getDlgIntent(GpsNiNotification notif)
{
- Intent intent = new Intent();
- String title = getDialogTitle(notif);
- String message = getDialogMessage(notif);
-
- // directly bring up the NI activity
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setClass(mContext, com.android.internal.app.NetInitiatedActivity.class);
+ Intent intent = new Intent();
+ String title = getDialogTitle(notif);
+ String message = getDialogMessage(notif);
- // put data in the intent
- intent.putExtra(NI_INTENT_KEY_NOTIF_ID, notif.notificationId);
- intent.putExtra(NI_INTENT_KEY_TITLE, title);
- intent.putExtra(NI_INTENT_KEY_MESSAGE, message);
- intent.putExtra(NI_INTENT_KEY_TIMEOUT, notif.timeout);
- intent.putExtra(NI_INTENT_KEY_DEFAULT_RESPONSE, notif.defaultResponse);
-
- if (DEBUG) Log.d(TAG, "generateIntent, title: " + title + ", message: " + message +
- ", timeout: " + notif.timeout);
-
- return intent;
+ // directly bring up the NI activity
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClass(mContext, com.android.internal.app.NetInitiatedActivity.class);
+
+ // put data in the intent
+ intent.putExtra(NI_INTENT_KEY_NOTIF_ID, notif.notificationId);
+ intent.putExtra(NI_INTENT_KEY_TITLE, title);
+ intent.putExtra(NI_INTENT_KEY_MESSAGE, message);
+ intent.putExtra(NI_INTENT_KEY_TIMEOUT, notif.timeout);
+ intent.putExtra(NI_INTENT_KEY_DEFAULT_RESPONSE, notif.defaultResponse);
+
+ if (DEBUG) Log.d(TAG, "generateIntent, title: " + title + ", message: " + message +
+ ", timeout: " + notif.timeout);
+
+ return intent;
}
-
+
// Converts a string (or Hex string) to a char array
static byte[] stringToByteArray(String original, boolean isHex)
{
- int length = isHex ? original.length() / 2 : original.length();
- byte[] output = new byte[length];
- int i;
-
- if (isHex)
- {
- for (i = 0; i < length; i++)
- {
- output[i] = (byte) Integer.parseInt(original.substring(i*2, i*2+2), 16);
- }
- }
- else {
- for (i = 0; i < length; i++)
- {
- output[i] = (byte) original.charAt(i);
- }
- }
-
- return output;
+ int length = isHex ? original.length() / 2 : original.length();
+ byte[] output = new byte[length];
+ int i;
+
+ if (isHex)
+ {
+ for (i = 0; i < length; i++)
+ {
+ output[i] = (byte) Integer.parseInt(original.substring(i*2, i*2+2), 16);
+ }
+ }
+ else {
+ for (i = 0; i < length; i++)
+ {
+ output[i] = (byte) original.charAt(i);
+ }
+ }
+
+ return output;
}
-
+
/**
* Unpacks an byte array containing 7-bit packed characters into a String.
- *
+ *
* @param input a 7-bit packed char array
* @return the unpacked String
*/
static String decodeGSMPackedString(byte[] input)
{
- final char CHAR_CR = 0x0D;
- int nStridx = 0;
- int nPckidx = 0;
- int num_bytes = input.length;
- int cPrev = 0;
- int cCurr = 0;
- byte nShift;
- byte nextChar;
- byte[] stringBuf = new byte[input.length * 2];
- String result = "";
-
- while(nPckidx < num_bytes)
- {
- nShift = (byte) (nStridx & 0x07);
- cCurr = input[nPckidx++];
- if (cCurr < 0) cCurr += 256;
+ final char CHAR_CR = 0x0D;
+ int nStridx = 0;
+ int nPckidx = 0;
+ int num_bytes = input.length;
+ int cPrev = 0;
+ int cCurr = 0;
+ byte nShift;
+ byte nextChar;
+ byte[] stringBuf = new byte[input.length * 2];
+ String result = "";
- /* A 7-bit character can be split at the most between two bytes of packed
- ** data.
- */
- nextChar = (byte) (( (cCurr << nShift) | (cPrev >> (8-nShift)) ) & 0x7F);
- stringBuf[nStridx++] = nextChar;
+ while(nPckidx < num_bytes)
+ {
+ nShift = (byte) (nStridx & 0x07);
+ cCurr = input[nPckidx++];
+ if (cCurr < 0) cCurr += 256;
- /* Special case where the whole of the next 7-bit character fits inside
- ** the current byte of packed data.
- */
- if(nShift == 6)
- {
- /* If the next 7-bit character is a CR (0x0D) and it is the last
- ** character, then it indicates a padding character. Drop it.
- */
- if (nPckidx == num_bytes || (cCurr >> 1) == CHAR_CR)
- {
- break;
- }
-
- nextChar = (byte) (cCurr >> 1);
- stringBuf[nStridx++] = nextChar;
- }
+ /* A 7-bit character can be split at the most between two bytes of packed
+ ** data.
+ */
+ nextChar = (byte) (( (cCurr << nShift) | (cPrev >> (8-nShift)) ) & 0x7F);
+ stringBuf[nStridx++] = nextChar;
- cPrev = cCurr;
- }
-
- try{
- result = new String(stringBuf, 0, nStridx, "US-ASCII");
- }
- catch (UnsupportedEncodingException e)
- {
- Log.e(TAG, e.getMessage());
- }
-
- return result;
+ /* Special case where the whole of the next 7-bit character fits inside
+ ** the current byte of packed data.
+ */
+ if(nShift == 6)
+ {
+ /* If the next 7-bit character is a CR (0x0D) and it is the last
+ ** character, then it indicates a padding character. Drop it.
+ */
+ if (nPckidx == num_bytes || (cCurr >> 1) == CHAR_CR)
+ {
+ break;
+ }
+
+ nextChar = (byte) (cCurr >> 1);
+ stringBuf[nStridx++] = nextChar;
+ }
+
+ cPrev = cCurr;
+ }
+
+ try {
+ result = new String(stringBuf, 0, nStridx, "US-ASCII");
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ Log.e(TAG, e.getMessage());
+ }
+
+ return result;
}
-
+
static String decodeUTF8String(byte[] input)
{
- String decoded = "";
- try {
- decoded = new String(input, "UTF-8");
- }
- catch (UnsupportedEncodingException e)
- {
- Log.e(TAG, e.getMessage());
- }
- return decoded;
+ String decoded = "";
+ try {
+ decoded = new String(input, "UTF-8");
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ Log.e(TAG, e.getMessage());
+ }
+ return decoded;
}
-
+
static String decodeUCS2String(byte[] input)
{
- String decoded = "";
- try {
- decoded = new String(input, "UTF-16");
- }
- catch (UnsupportedEncodingException e)
- {
- Log.e(TAG, e.getMessage());
- }
- return decoded;
+ String decoded = "";
+ try {
+ decoded = new String(input, "UTF-16");
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ Log.e(TAG, e.getMessage());
+ }
+ return decoded;
}
-
+
/** Decode NI string
- *
+ *
* @param original The text string to be decoded
* @param isHex Specifies whether the content of the string has been encoded as a Hex string. Encoding
- * a string as Hex can allow zeros inside the coded text.
+ * a string as Hex can allow zeros inside the coded text.
* @param coding Specifies the coding scheme of the string, such as GSM, UTF8, UCS2, etc. This coding scheme
- * needs to match those used passed to HAL from the native GPS driver. Decoding is done according
+ * needs to match those used passed to HAL from the native GPS driver. Decoding is done according
* to the <code> coding </code>, after a Hex string is decoded. Generally, if the
- * notification strings don't need further decoding, <code> coding </code> encoding can be
+ * notification strings don't need further decoding, <code> coding </code> encoding can be
* set to -1, and <code> isHex </code> can be false.
* @return the decoded string
*/
static private String decodeString(String original, boolean isHex, int coding)
{
- String decoded = original;
- byte[] input = stringToByteArray(original, isHex);
+ String decoded = original;
+ byte[] input = stringToByteArray(original, isHex);
- switch (coding) {
- case GPS_ENC_NONE:
- decoded = original;
- break;
-
- case GPS_ENC_SUPL_GSM_DEFAULT:
- decoded = decodeGSMPackedString(input);
- break;
-
- case GPS_ENC_SUPL_UTF8:
- decoded = decodeUTF8String(input);
- break;
-
- case GPS_ENC_SUPL_UCS2:
- decoded = decodeUCS2String(input);
- break;
-
- case GPS_ENC_UNKNOWN:
- decoded = original;
- break;
-
- default:
- Log.e(TAG, "Unknown encoding " + coding + " for NI text " + original);
- break;
- }
- return decoded;
+ switch (coding) {
+ case GPS_ENC_NONE:
+ decoded = original;
+ break;
+
+ case GPS_ENC_SUPL_GSM_DEFAULT:
+ decoded = decodeGSMPackedString(input);
+ break;
+
+ case GPS_ENC_SUPL_UTF8:
+ decoded = decodeUTF8String(input);
+ break;
+
+ case GPS_ENC_SUPL_UCS2:
+ decoded = decodeUCS2String(input);
+ break;
+
+ case GPS_ENC_UNKNOWN:
+ decoded = original;
+ break;
+
+ default:
+ Log.e(TAG, "Unknown encoding " + coding + " for NI text " + original);
+ break;
+ }
+ return decoded;
}
-
+
// change this to configure notification display
static private String getNotifTicker(GpsNiNotification notif)
{
- String ticker = String.format("Position request! ReqId: [%s] ClientName: [%s]",
- decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
- decodeString(notif.text, mIsHexInput, notif.textEncoding));
- return ticker;
+ String ticker = String.format("Position request! ReqId: [%s] ClientName: [%s]",
+ decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
+ decodeString(notif.text, mIsHexInput, notif.textEncoding));
+ return ticker;
}
-
+
// change this to configure notification display
static private String getNotifTitle(GpsNiNotification notif)
{
- String title = String.format("Position Request");
- return title;
+ String title = String.format("Position Request");
+ return title;
}
-
+
// change this to configure notification display
static private String getNotifMessage(GpsNiNotification notif)
{
- String message = String.format(
- "NI Request received from [%s] for client [%s]!",
- decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
- decodeString(notif.text, mIsHexInput, notif.textEncoding));
- return message;
+ String message = String.format(
+ "NI Request received from [%s] for client [%s]!",
+ decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
+ decodeString(notif.text, mIsHexInput, notif.textEncoding));
+ return message;
}
-
+
// change this to configure dialog display (for verification)
static public String getDialogTitle(GpsNiNotification notif)
{
- return getNotifTitle(notif);
+ return getNotifTitle(notif);
}
-
+
// change this to configure dialog display (for verification)
static private String getDialogMessage(GpsNiNotification notif)
{
- return getNotifMessage(notif);
+ return getNotifMessage(notif);
}
-
+
}
diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml
index d057ab7..dd0d064 100644
--- a/packages/SettingsProvider/AndroidManifest.xml
+++ b/packages/SettingsProvider/AndroidManifest.xml
@@ -8,6 +8,8 @@
android:backupAgent="SettingsBackupAgent"
android:killAfterRestore="false"
android:icon="@drawable/ic_launcher_settings">
+
+ <!-- todo add: android:neverEncrypt="true" -->
<provider android:name="SettingsProvider" android:authorities="settings"
android:multiprocess="false"
diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java b/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java
index 5672a01..eeafd5a 100644
--- a/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java
+++ b/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java
@@ -52,8 +52,11 @@
// The actual implementation is delegated to the VpnService class.
private VpnService<? extends VpnProfile> mService;
+ // TODO(oam): Test VPN when EFS is enabled (will do later)...
private static String getStateFilePath() {
- return Environment.getDataDirectory().getPath() + STATES_FILE_RELATIVE_PATH;
+ // This call will return the correcu directory whether Encrypted FS is enabled or not
+ // Disabled: /data/misc/vpn/.states Enabled: /data/secure/misc/vpn/.states
+ return Environment.getSecureDataDirectory().getPath() + STATES_FILE_RELATIVE_PATH;
}
private final IBinder mBinder = new IVpnService.Stub() {
diff --git a/policy/Android.mk b/policy/Android.mk
new file mode 100644
index 0000000..a887142
--- /dev/null
+++ b/policy/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+
+# the library
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ $(call all-subdir-java-files)
+
+LOCAL_MODULE := android.policy
+
+include $(BUILD_JAVA_LIBRARY)
diff --git a/policy/com/android/internal/policy/impl/AccountUnlockScreen.java b/policy/com/android/internal/policy/impl/AccountUnlockScreen.java
new file mode 100644
index 0000000..9921069
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/AccountUnlockScreen.java
@@ -0,0 +1,341 @@
+/*
+ * 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;
+
+import com.android.internal.R;
+import com.android.internal.widget.LockPatternUtils;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.OperationCanceledException;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.AccountManagerCallback;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.telephony.TelephonyManager;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.LoginFilter;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.os.Bundle;
+
+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 AccountUnlockScreen extends RelativeLayout implements KeyguardScreen,
+ KeyguardUpdateMonitor.InfoCallback,View.OnClickListener, TextWatcher {
+ private static final String LOCK_PATTERN_PACKAGE = "com.android.settings";
+ private static final String LOCK_PATTERN_CLASS =
+ "com.android.settings.ChooseLockPattern";
+
+ /**
+ * The amount of millis to stay awake once this screen detects activity
+ */
+ private static final int AWAKE_POKE_MILLIS = 30000;
+
+ private final KeyguardScreenCallback mCallback;
+ private final LockPatternUtils mLockPatternUtils;
+
+ private TextView mTopHeader;
+ private TextView mInstructions;
+ private EditText mLogin;
+ private EditText mPassword;
+ private Button mOk;
+ private Button mEmergencyCall;
+
+ /**
+ * Shown while making asynchronous check of password.
+ */
+ private ProgressDialog mCheckingDialog;
+
+ /**
+ * AccountUnlockScreen constructor.
+ * @param configuration
+ */
+ public AccountUnlockScreen(Context context,Configuration configuration,
+ KeyguardScreenCallback callback, LockPatternUtils lockPatternUtils) {
+ super(context);
+ mCallback = callback;
+ mLockPatternUtils = lockPatternUtils;
+
+ LayoutInflater.from(context).inflate(
+ R.layout.keyguard_screen_glogin_unlock, this, true);
+
+ mTopHeader = (TextView) findViewById(R.id.topHeader);
+ mTopHeader.setText(mLockPatternUtils.isPermanentlyLocked() ?
+ R.string.lockscreen_glogin_too_many_attempts :
+ R.string.lockscreen_glogin_forgot_pattern);
+
+ mInstructions = (TextView) findViewById(R.id.instructions);
+
+ 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);
+
+ mEmergencyCall = (Button) findViewById(R.id.emergencyCall);
+ mEmergencyCall.setOnClickListener(this);
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCall);
+ }
+
+ 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) {
+ mCallback.pokeWakelock(AWAKE_POKE_MILLIS);
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction,
+ Rect previouslyFocusedRect) {
+ // send focus to the login field
+ return mLogin.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ /** {@inheritDoc} */
+ public boolean needsInput() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public void onPause() {
+
+ }
+
+ /** {@inheritDoc} */
+ public void onResume() {
+ // start fresh
+ mLogin.setText("");
+ mPassword.setText("");
+ mLogin.requestFocus();
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCall);
+ }
+
+ /** {@inheritDoc} */
+ public void cleanUp() {
+ if (mCheckingDialog != null) {
+ mCheckingDialog.hide();
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onClick(View v) {
+ mCallback.pokeWakelock();
+ if (v == mOk) {
+ asyncCheckPassword();
+ }
+
+ if (v == mEmergencyCall) {
+ mCallback.takeEmergencyCallAction();
+ }
+ }
+
+ private void onCheckPasswordResult(boolean success) {
+ 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();
+
+ // close the keyguard
+ mCallback.keyguardDone(true);
+ } else {
+ mInstructions.setText(R.string.lockscreen_glogin_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.goToLockScreen();
+ } else {
+ 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.pokeWakelock(AWAKE_POKE_MILLIS);
+ final String login = mLogin.getText().toString();
+ final String password = mPassword.getText().toString();
+ Account account = findIntendedAccount(login);
+ if (account == null) {
+ onCheckPasswordResult(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.pokeWakelock(AWAKE_POKE_MILLIS);
+ final Bundle result = future.getResult();
+ final boolean verified = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+ // ensure on UI thread
+ mLogin.post(new Runnable() {
+ public void run() {
+ onCheckPasswordResult(verified);
+ }
+ });
+ } catch (OperationCanceledException e) {
+ onCheckPasswordResult(false);
+ } catch (IOException e) {
+ onCheckPasswordResult(false);
+ } catch (AuthenticatorException e) {
+ onCheckPasswordResult(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.lockscreen_glogin_checking_password));
+ mCheckingDialog.setIndeterminate(true);
+ mCheckingDialog.setCancelable(false);
+ mCheckingDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ if (!mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_sf_slowBlur)) {
+ mCheckingDialog.getWindow().setFlags(
+ WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
+ WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+ }
+ }
+ return mCheckingDialog;
+ }
+
+ public void onPhoneStateChanged(String newState) {
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCall);
+ }
+
+ public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) {
+
+ }
+
+ public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
+
+ }
+
+ public void onRingerModeChanged(int state) {
+
+ }
+
+ public void onTimeChanged() {
+
+ }
+}
diff --git a/policy/com/android/internal/policy/impl/GlobalActions.java b/policy/com/android/internal/policy/impl/GlobalActions.java
new file mode 100644
index 0000000..1f06dcc
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/GlobalActions.java
@@ -0,0 +1,578 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.internal.R;
+import com.android.internal.app.ShutdownThread;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+
+/**
+ * Helper to show the global actions dialog. Each item is an {@link Action} that
+ * may show depending on whether the keyguard is showing, and whether the device
+ * is provisioned.
+ */
+class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
+
+ private static final String TAG = "GlobalActions";
+
+ private StatusBarManager mStatusBar;
+
+ private final Context mContext;
+ private final AudioManager mAudioManager;
+
+ private ArrayList<Action> mItems;
+ private AlertDialog mDialog;
+
+ private ToggleAction mSilentModeToggle;
+ private ToggleAction mAirplaneModeOn;
+
+ private MyAdapter mAdapter;
+
+ private boolean mKeyguardShowing = false;
+ private boolean mDeviceProvisioned = false;
+ private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
+ private boolean mIsWaitingForEcmExit = false;
+
+ /**
+ * @param context everything needs a context :(
+ */
+ public GlobalActions(Context context) {
+ mContext = context;
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+
+ // receive broadcasts
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+ context.registerReceiver(mBroadcastReceiver, filter);
+
+ // get notified of phone state changes
+ TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+ }
+
+ /**
+ * Show the global actions dialog (creating if necessary)
+ * @param keyguardShowing True if keyguard is showing
+ */
+ public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
+ mKeyguardShowing = keyguardShowing;
+ mDeviceProvisioned = isDeviceProvisioned;
+ if (mDialog == null) {
+ mStatusBar = (StatusBarManager)mContext.getSystemService(Context.STATUS_BAR_SERVICE);
+ mDialog = createDialog();
+ }
+ prepareDialog();
+
+ mStatusBar.disable(StatusBarManager.DISABLE_EXPAND);
+ mDialog.show();
+ }
+
+ /**
+ * Create the global actions dialog.
+ * @return A new dialog.
+ */
+ private AlertDialog createDialog() {
+ mSilentModeToggle = new ToggleAction(
+ R.drawable.ic_lock_silent_mode,
+ R.drawable.ic_lock_silent_mode_off,
+ R.string.global_action_toggle_silent_mode,
+ R.string.global_action_silent_mode_on_status,
+ R.string.global_action_silent_mode_off_status) {
+
+ void willCreate() {
+ // XXX: FIXME: switch to ic_lock_vibrate_mode when available
+ mEnabledIconResId = (Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.VIBRATE_IN_SILENT, 1) == 1)
+ ? R.drawable.ic_lock_silent_mode_vibrate
+ : R.drawable.ic_lock_silent_mode;
+ }
+
+ void onToggle(boolean on) {
+ if (on) {
+ mAudioManager.setRingerMode((Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.VIBRATE_IN_SILENT, 1) == 1)
+ ? AudioManager.RINGER_MODE_VIBRATE
+ : AudioManager.RINGER_MODE_SILENT);
+ } else {
+ mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ }
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ };
+
+ mAirplaneModeOn = new ToggleAction(
+ R.drawable.ic_lock_airplane_mode,
+ R.drawable.ic_lock_airplane_mode_off,
+ R.string.global_actions_toggle_airplane_mode,
+ R.string.global_actions_airplane_mode_on_status,
+ R.string.global_actions_airplane_mode_off_status) {
+
+ void onToggle(boolean on) {
+ if (Boolean.parseBoolean(
+ SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
+ mIsWaitingForEcmExit = true;
+ // Launch ECM exit dialog
+ Intent ecmDialogIntent =
+ new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
+ ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(ecmDialogIntent);
+ } else {
+ changeAirplaneModeSystemSetting(on);
+ }
+ }
+
+ @Override
+ protected void changeStateFromPress(boolean buttonOn) {
+ // In ECM mode airplane state cannot be changed
+ if (!(Boolean.parseBoolean(
+ SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {
+ mState = buttonOn ? State.TurningOn : State.TurningOff;
+ mAirplaneState = mState;
+ }
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ };
+
+ mItems = Lists.newArrayList(
+ // silent mode
+ mSilentModeToggle,
+ // next: airplane mode
+ mAirplaneModeOn,
+ // last: power off
+ new SinglePressAction(
+ com.android.internal.R.drawable.ic_lock_power_off,
+ R.string.global_action_power_off) {
+
+ public void onPress() {
+ // shutdown by making sure radio and power are handled accordingly.
+ ShutdownThread.shutdown(mContext, true);
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ });
+
+ mAdapter = new MyAdapter();
+
+ final AlertDialog.Builder ab = new AlertDialog.Builder(mContext);
+
+ ab.setAdapter(mAdapter, this)
+ .setInverseBackgroundForced(true)
+ .setTitle(R.string.global_actions);
+
+ final AlertDialog dialog = ab.create();
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+ if (!mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_sf_slowBlur)) {
+ dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
+ WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+ }
+
+ dialog.setOnDismissListener(this);
+
+ return dialog;
+ }
+
+ private void prepareDialog() {
+ final boolean silentModeOn =
+ mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
+ mSilentModeToggle.updateState(
+ silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
+ mAirplaneModeOn.updateState(mAirplaneState);
+ mAdapter.notifyDataSetChanged();
+ if (mKeyguardShowing) {
+ mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ } else {
+ mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public void onDismiss(DialogInterface dialog) {
+ mStatusBar.disable(StatusBarManager.DISABLE_NONE);
+ }
+
+ /** {@inheritDoc} */
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ mAdapter.getItem(which).onPress();
+ }
+
+
+ /**
+ * The adapter used for the list within the global actions dialog, taking
+ * into account whether the keyguard is showing via
+ * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned
+ * via {@link GlobalActions#mDeviceProvisioned}.
+ */
+ private class MyAdapter extends BaseAdapter {
+
+ public int getCount() {
+ int count = 0;
+
+ for (int i = 0; i < mItems.size(); i++) {
+ final Action action = mItems.get(i);
+
+ if (mKeyguardShowing && !action.showDuringKeyguard()) {
+ continue;
+ }
+ if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
+ continue;
+ }
+ count++;
+ }
+ return count;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return getItem(position).isEnabled();
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ public Action getItem(int position) {
+
+ int filteredPos = 0;
+ for (int i = 0; i < mItems.size(); i++) {
+ final Action action = mItems.get(i);
+ if (mKeyguardShowing && !action.showDuringKeyguard()) {
+ continue;
+ }
+ if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
+ continue;
+ }
+ if (filteredPos == position) {
+ return action;
+ }
+ filteredPos++;
+ }
+
+ throw new IllegalArgumentException("position " + position + " out of "
+ + "range of showable actions, filtered count = "
+ + "= " + getCount() + ", keyguardshowing=" + mKeyguardShowing
+ + ", provisioned=" + mDeviceProvisioned);
+ }
+
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Action action = getItem(position);
+ return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
+ }
+ }
+
+ // note: the scheme below made more sense when we were planning on having
+ // 8 different things in the global actions dialog. seems overkill with
+ // only 3 items now, but may as well keep this flexible approach so it will
+ // be easy should someone decide at the last minute to include something
+ // else, such as 'enable wifi', or 'enable bluetooth'
+
+ /**
+ * What each item in the global actions dialog must be able to support.
+ */
+ private interface Action {
+ View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
+
+ void onPress();
+
+ /**
+ * @return whether this action should appear in the dialog when the keygaurd
+ * is showing.
+ */
+ boolean showDuringKeyguard();
+
+ /**
+ * @return whether this action should appear in the dialog before the
+ * device is provisioned.
+ */
+ boolean showBeforeProvisioning();
+
+ boolean isEnabled();
+ }
+
+ /**
+ * A single press action maintains no state, just responds to a press
+ * and takes an action.
+ */
+ private static abstract class SinglePressAction implements Action {
+ private final int mIconResId;
+ private final int mMessageResId;
+
+ protected SinglePressAction(int iconResId, int messageResId) {
+ mIconResId = iconResId;
+ mMessageResId = messageResId;
+ }
+
+ public boolean isEnabled() {
+ return true;
+ }
+
+ abstract public void onPress();
+
+ public View create(
+ Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
+ View v = (convertView != null) ?
+ convertView :
+ inflater.inflate(R.layout.global_actions_item, parent, false);
+
+ ImageView icon = (ImageView) v.findViewById(R.id.icon);
+ TextView messageView = (TextView) v.findViewById(R.id.message);
+
+ v.findViewById(R.id.status).setVisibility(View.GONE);
+
+ icon.setImageDrawable(context.getResources().getDrawable(mIconResId));
+ messageView.setText(mMessageResId);
+
+ return v;
+ }
+ }
+
+ /**
+ * A toggle action knows whether it is on or off, and displays an icon
+ * and status message accordingly.
+ */
+ private static abstract class ToggleAction implements Action {
+
+ enum State {
+ Off(false),
+ TurningOn(true),
+ TurningOff(true),
+ On(false);
+
+ private final boolean inTransition;
+
+ State(boolean intermediate) {
+ inTransition = intermediate;
+ }
+
+ public boolean inTransition() {
+ return inTransition;
+ }
+ }
+
+ protected State mState = State.Off;
+
+ // prefs
+ protected int mEnabledIconResId;
+ protected int mDisabledIconResid;
+ protected int mMessageResId;
+ protected int mEnabledStatusMessageResId;
+ protected int mDisabledStatusMessageResId;
+
+ /**
+ * @param enabledIconResId The icon for when this action is on.
+ * @param disabledIconResid The icon for when this action is off.
+ * @param essage The general information message, e.g 'Silent Mode'
+ * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
+ * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
+ */
+ public ToggleAction(int enabledIconResId,
+ int disabledIconResid,
+ int essage,
+ int enabledStatusMessageResId,
+ int disabledStatusMessageResId) {
+ mEnabledIconResId = enabledIconResId;
+ mDisabledIconResid = disabledIconResid;
+ mMessageResId = essage;
+ mEnabledStatusMessageResId = enabledStatusMessageResId;
+ mDisabledStatusMessageResId = disabledStatusMessageResId;
+ }
+
+ /**
+ * Override to make changes to resource IDs just before creating the
+ * View.
+ */
+ void willCreate() {
+
+ }
+
+ public View create(Context context, View convertView, ViewGroup parent,
+ LayoutInflater inflater) {
+ willCreate();
+
+ View v = (convertView != null) ?
+ convertView :
+ inflater.inflate(R
+ .layout.global_actions_item, parent, false);
+
+ ImageView icon = (ImageView) v.findViewById(R.id.icon);
+ TextView messageView = (TextView) v.findViewById(R.id.message);
+ TextView statusView = (TextView) v.findViewById(R.id.status);
+
+ messageView.setText(mMessageResId);
+
+ boolean on = ((mState == State.On) || (mState == State.TurningOn));
+ icon.setImageDrawable(context.getResources().getDrawable(
+ (on ? mEnabledIconResId : mDisabledIconResid)));
+ statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
+ statusView.setVisibility(View.VISIBLE);
+
+ final boolean enabled = isEnabled();
+ messageView.setEnabled(enabled);
+ statusView.setEnabled(enabled);
+ icon.setEnabled(enabled);
+ v.setEnabled(enabled);
+
+ return v;
+ }
+
+ public final void onPress() {
+ if (mState.inTransition()) {
+ Log.w(TAG, "shouldn't be able to toggle when in transition");
+ return;
+ }
+
+ final boolean nowOn = !(mState == State.On);
+ onToggle(nowOn);
+ changeStateFromPress(nowOn);
+ }
+
+ public boolean isEnabled() {
+ return !mState.inTransition();
+ }
+
+ /**
+ * Implementations may override this if their state can be in on of the intermediate
+ * states until some notification is received (e.g airplane mode is 'turning off' until
+ * we know the wireless connections are back online
+ * @param buttonOn Whether the button was turned on or off
+ */
+ protected void changeStateFromPress(boolean buttonOn) {
+ mState = buttonOn ? State.On : State.Off;
+ }
+
+ abstract void onToggle(boolean on);
+
+ public void updateState(State state) {
+ mState = state;
+ }
+ }
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+ || Intent.ACTION_SCREEN_OFF.equals(action)) {
+ String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY);
+ if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
+ mHandler.sendEmptyMessage(MESSAGE_DISMISS);
+ }
+ } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
+ // Airplane mode can be changed after ECM exits if airplane toggle button
+ // is pressed during ECM mode
+ if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
+ mIsWaitingForEcmExit) {
+ mIsWaitingForEcmExit = false;
+ changeAirplaneModeSystemSetting(true);
+ }
+ }
+ }
+ };
+
+ PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onServiceStateChanged(ServiceState serviceState) {
+ final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
+ mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off;
+ mAirplaneModeOn.updateState(mAirplaneState);
+ mAdapter.notifyDataSetChanged();
+ }
+ };
+
+ private static final int MESSAGE_DISMISS = 0;
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ if (msg.what == MESSAGE_DISMISS) {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
+ }
+ }
+ };
+
+ /**
+ * Change the airplane mode system setting
+ */
+ private void changeAirplaneModeSystemSetting(boolean on) {
+ Settings.System.putInt(
+ mContext.getContentResolver(),
+ Settings.System.AIRPLANE_MODE_ON,
+ on ? 1 : 0);
+ Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra("state", on);
+ mContext.sendBroadcast(intent);
+ }
+}
diff --git a/policy/com/android/internal/policy/impl/IconUtilities.java b/policy/com/android/internal/policy/impl/IconUtilities.java
new file mode 100644
index 0000000..99055cf
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/IconUtilities.java
@@ -0,0 +1,192 @@
+/*
+ * 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;
+
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.PaintDrawable;
+import android.graphics.drawable.StateListDrawable;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.TableMaskFilter;
+import android.graphics.Typeface;
+import android.text.Layout.Alignment;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.content.res.Resources;
+import android.content.Context;
+
+/**
+ * Various utilities shared amongst the Launcher's classes.
+ */
+final class IconUtilities {
+ private static final String TAG = "IconUtilities";
+
+ private static final int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff };
+
+ private int mIconWidth = -1;
+ private int mIconHeight = -1;
+ private int mIconTextureWidth = -1;
+ private int mIconTextureHeight = -1;
+
+ private final Paint mPaint = new Paint();
+ private final Paint mBlurPaint = new Paint();
+ private final Paint mGlowColorPressedPaint = new Paint();
+ private final Paint mGlowColorFocusedPaint = new Paint();
+ private final Rect mOldBounds = new Rect();
+ private final Canvas mCanvas = new Canvas();
+ private final DisplayMetrics mDisplayMetrics;
+
+ private int mColorIndex = 0;
+
+ public IconUtilities(Context context) {
+ final Resources resources = context.getResources();
+ DisplayMetrics metrics = mDisplayMetrics = resources.getDisplayMetrics();
+ final float density = metrics.density;
+ final float blurPx = 5 * density;
+
+ mIconWidth = mIconHeight = (int) resources.getDimension(android.R.dimen.app_icon_size);
+ mIconTextureWidth = mIconTextureHeight = mIconWidth + (int)(blurPx*2);
+
+ mBlurPaint.setMaskFilter(new BlurMaskFilter(blurPx, BlurMaskFilter.Blur.NORMAL));
+ mGlowColorPressedPaint.setColor(0xffffc300);
+ mGlowColorPressedPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
+ mGlowColorFocusedPaint.setColor(0xffff8e00);
+ mGlowColorFocusedPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
+
+ ColorMatrix cm = new ColorMatrix();
+ cm.setSaturation(0.2f);
+
+ mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
+ Paint.FILTER_BITMAP_FLAG));
+ }
+
+ public Drawable createIconDrawable(Drawable src) {
+ Bitmap scaled = createIconBitmap(src);
+
+ StateListDrawable result = new StateListDrawable();
+
+ result.addState(new int[] { android.R.attr.state_focused },
+ new BitmapDrawable(createSelectedBitmap(scaled, false)));
+ result.addState(new int[] { android.R.attr.state_pressed },
+ new BitmapDrawable(createSelectedBitmap(scaled, true)));
+ result.addState(new int[0], new BitmapDrawable(scaled));
+
+ result.setBounds(0, 0, mIconTextureWidth, mIconTextureHeight);
+ return result;
+ }
+
+ /**
+ * Returns a bitmap suitable for the all apps view. The bitmap will be a power
+ * of two sized ARGB_8888 bitmap that can be used as a gl texture.
+ */
+ private Bitmap createIconBitmap(Drawable icon) {
+ int width = mIconWidth;
+ int height = mIconHeight;
+
+ if (icon instanceof PaintDrawable) {
+ PaintDrawable painter = (PaintDrawable) icon;
+ painter.setIntrinsicWidth(width);
+ painter.setIntrinsicHeight(height);
+ } else if (icon instanceof BitmapDrawable) {
+ // Ensure the bitmap has a density.
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+ Bitmap bitmap = bitmapDrawable.getBitmap();
+ if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
+ bitmapDrawable.setTargetDensity(mDisplayMetrics);
+ }
+ }
+ int sourceWidth = icon.getIntrinsicWidth();
+ int sourceHeight = icon.getIntrinsicHeight();
+
+ if (sourceWidth > 0 && sourceWidth > 0) {
+ // There are intrinsic sizes.
+ if (width < sourceWidth || height < sourceHeight) {
+ // It's too big, scale it down.
+ final float ratio = (float) sourceWidth / sourceHeight;
+ if (sourceWidth > sourceHeight) {
+ height = (int) (width / ratio);
+ } else if (sourceHeight > sourceWidth) {
+ width = (int) (height * ratio);
+ }
+ } else if (sourceWidth < width && sourceHeight < height) {
+ // It's small, use the size they gave us.
+ width = sourceWidth;
+ height = sourceHeight;
+ }
+ }
+
+ // no intrinsic size --> use default size
+ int textureWidth = mIconTextureWidth;
+ int textureHeight = mIconTextureHeight;
+
+ final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
+ Bitmap.Config.ARGB_8888);
+ final Canvas canvas = mCanvas;
+ canvas.setBitmap(bitmap);
+
+ final int left = (textureWidth-width) / 2;
+ final int top = (textureHeight-height) / 2;
+
+ if (false) {
+ // draw a big box for the icon for debugging
+ canvas.drawColor(sColors[mColorIndex]);
+ if (++mColorIndex >= sColors.length) mColorIndex = 0;
+ Paint debugPaint = new Paint();
+ debugPaint.setColor(0xffcccc00);
+ canvas.drawRect(left, top, left+width, top+height, debugPaint);
+ }
+
+ mOldBounds.set(icon.getBounds());
+ icon.setBounds(left, top, left+width, top+height);
+ icon.draw(canvas);
+ icon.setBounds(mOldBounds);
+
+ return bitmap;
+ }
+
+ private Bitmap createSelectedBitmap(Bitmap src, boolean pressed) {
+ final Bitmap result = Bitmap.createBitmap(mIconTextureWidth, mIconTextureHeight,
+ Bitmap.Config.ARGB_8888);
+ final Canvas dest = new Canvas(result);
+
+ dest.drawColor(0, PorterDuff.Mode.CLEAR);
+
+ int[] xy = new int[2];
+ Bitmap mask = src.extractAlpha(mBlurPaint, xy);
+
+ dest.drawBitmap(mask, xy[0], xy[1],
+ pressed ? mGlowColorPressedPaint : mGlowColorFocusedPaint);
+
+ mask.recycle();
+
+ dest.drawBitmap(src, 0, 0, mPaint);
+
+ return result;
+ }
+}
diff --git a/policy/com/android/internal/policy/impl/KeyguardScreen.java b/policy/com/android/internal/policy/impl/KeyguardScreen.java
new file mode 100644
index 0000000..bbb6875
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/KeyguardScreen.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+/**
+ * Common interface of each {@link android.view.View} that is a screen of
+ * {@link LockPatternKeyguardView}.
+ */
+public interface KeyguardScreen {
+
+ /**
+ * Return true if your view needs input, so should allow the soft
+ * keyboard to be displayed.
+ */
+ boolean needsInput();
+
+ /**
+ * This screen is no longer in front of the user.
+ */
+ void onPause();
+
+ /**
+ * This screen is going to be in front of the user.
+ */
+ void onResume();
+
+ /**
+ * This view is going away; a hook to do cleanup.
+ */
+ void cleanUp();
+}
diff --git a/policy/com/android/internal/policy/impl/KeyguardScreenCallback.java b/policy/com/android/internal/policy/impl/KeyguardScreenCallback.java
new file mode 100644
index 0000000..a843603
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/KeyguardScreenCallback.java
@@ -0,0 +1,82 @@
+/*
+ * 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;
+
+import android.content.res.Configuration;
+
+/**
+ * Within a keyguard, there may be several screens that need a callback
+ * to the host keyguard view.
+ */
+public interface KeyguardScreenCallback extends KeyguardViewCallback {
+
+ /**
+ * Transition to the lock screen.
+ */
+ void goToLockScreen();
+
+ /**
+ * Transition to the unlock screen.
+ */
+ void goToUnlockScreen();
+
+ /**
+ * The user reported that they forgot their pattern (or not, when they want to back out of the
+ * forgot pattern screen).
+ *
+ * @param isForgotten True if the user hit the forgot pattern, false if they want to back out
+ * of the account screen.
+ */
+ void forgotPattern(boolean isForgotten);
+
+ /**
+ * @return Whether the keyguard requires some sort of PIN.
+ */
+ boolean isSecure();
+
+ /**
+ * @return Whether we are in a mode where we only want to verify the
+ * user can get past the keyguard.
+ */
+ boolean isVerifyUnlockOnly();
+
+ /**
+ * Stay on me, but recreate me (so I can use a different layout).
+ */
+ void recreateMe(Configuration config);
+
+ /**
+ * Take action to send an emergency call.
+ */
+ void takeEmergencyCallAction();
+
+ /**
+ * Report that the user had a failed attempt to unlock with password or pattern.
+ */
+ void reportFailedUnlockAttempt();
+
+ /**
+ * Report that the user successfully entered their password or pattern.
+ */
+ void reportSuccessfulUnlockAttempt();
+
+ /**
+ * Report whether we there's another way to unlock the device.
+ * @return true
+ */
+ boolean doesFallbackUnlockScreenExist();
+}
diff --git a/policy/com/android/internal/policy/impl/KeyguardUpdateMonitor.java b/policy/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
new file mode 100644
index 0000000..b225e56
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
@@ -0,0 +1,526 @@
+/*
+ * 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;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.database.ContentObserver;
+import static android.os.BatteryManager.BATTERY_STATUS_CHARGING;
+import static android.os.BatteryManager.BATTERY_STATUS_FULL;
+import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.provider.Telephony;
+import static android.provider.Telephony.Intents.EXTRA_PLMN;
+import static android.provider.Telephony.Intents.EXTRA_SHOW_PLMN;
+import static android.provider.Telephony.Intents.EXTRA_SHOW_SPN;
+import static android.provider.Telephony.Intents.EXTRA_SPN;
+import static android.provider.Telephony.Intents.SPN_STRINGS_UPDATED_ACTION;
+
+import com.android.internal.telephony.IccCard;
+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.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 {
+
+ static private final String TAG = "KeyguardUpdateMonitor";
+ static private final boolean DEBUG = false;
+
+ private static final int LOW_BATTERY_THRESHOLD = 20;
+
+ private final Context mContext;
+
+ private IccCard.State mSimState = IccCard.State.READY;
+
+ private boolean mKeyguardBypassEnabled;
+
+ private boolean mDevicePluggedIn;
+
+ private boolean mDeviceProvisioned;
+
+ private int mBatteryLevel;
+
+ private CharSequence mTelephonyPlmn;
+ private CharSequence mTelephonySpn;
+
+ private int mFailedAttempts = 0;
+
+ private Handler mHandler;
+
+ private ArrayList<InfoCallback> mInfoCallbacks = Lists.newArrayList();
+ private ArrayList<SimStateCallback> mSimStateCallbacks = Lists.newArrayList();
+ private ContentObserver mContentObserver;
+
+ // messages for the handler
+ 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;
+
+
+ /**
+ * 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 IccCard.State simState;
+
+ private SimArgs(Intent intent) {
+ if (!TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
+ throw new IllegalArgumentException("only handles intent ACTION_SIM_STATE_CHANGED");
+ }
+ String stateExtra = intent.getStringExtra(IccCard.INTENT_KEY_ICC_STATE);
+ if (IccCard.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
+ this.simState = IccCard.State.ABSENT;
+ } else if (IccCard.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
+ this.simState = IccCard.State.READY;
+ } else if (IccCard.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
+ final String lockedReason = intent
+ .getStringExtra(IccCard.INTENT_KEY_LOCKED_REASON);
+ if (IccCard.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
+ this.simState = IccCard.State.PIN_REQUIRED;
+ } else if (IccCard.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
+ this.simState = IccCard.State.PUK_REQUIRED;
+ } else {
+ this.simState = IccCard.State.UNKNOWN;
+ }
+ } else if (IccCard.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) {
+ this.simState = IccCard.State.NETWORK_LOCKED;
+ } else {
+ this.simState = IccCard.State.UNKNOWN;
+ }
+ }
+
+ public String toString() {
+ return simState.toString();
+ }
+ }
+
+ public KeyguardUpdateMonitor(Context context) {
+ mContext = context;
+
+ mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_TIME_UPDATE:
+ handleTimeUpdate();
+ break;
+ case MSG_BATTERY_UPDATE:
+ handleBatteryUpdate(msg.arg1, msg.arg2);
+ 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;
+ }
+ }
+ };
+
+ mKeyguardBypassEnabled = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_bypass_keyguard_if_slider_open);
+
+ 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) {
+ 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 && mContentObserver != null) {
+ // We don't need the observer anymore...
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ mContentObserver = null;
+ }
+ 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...
+ mDeviceProvisioned = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
+ }
+
+ // take a guess to start
+ mSimState = IccCard.State.READY;
+ mDevicePluggedIn = true;
+ mBatteryLevel = 100;
+
+ mTelephonyPlmn = getDefaultPlmn();
+
+ // setup receiver
+ 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(SPN_STRINGS_UPDATED_ACTION);
+ filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+ context.registerReceiver(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 (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 pluggedInStatus = intent
+ .getIntExtra("status", BATTERY_STATUS_UNKNOWN);
+ int batteryLevel = intent.getIntExtra("level", 0);
+ final Message msg = mHandler.obtainMessage(
+ MSG_BATTERY_UPDATE,
+ pluggedInStatus,
+ batteryLevel);
+ mHandler.sendMessage(msg);
+ } else if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) {
+ mHandler.sendMessage(mHandler.obtainMessage(
+ MSG_SIM_STATE_CHANGE,
+ new SimArgs(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));
+ }
+ }
+ }, filter);
+ }
+
+ protected void handlePhoneStateChanged(String newState) {
+ if (DEBUG) Log.d(TAG, "handlePhoneStateChanged(" + newState + ")");
+ for (int i = 0; i < mInfoCallbacks.size(); i++) {
+ mInfoCallbacks.get(i).onPhoneStateChanged(newState);
+ }
+ }
+
+ protected void handleRingerModeChange(int mode) {
+ if (DEBUG) Log.d(TAG, "handleRingerModeChange(" + mode + ")");
+ for (int i = 0; i < mInfoCallbacks.size(); i++) {
+ mInfoCallbacks.get(i).onRingerModeChanged(mode);
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_TIME_UPDATE}
+ */
+ private void handleTimeUpdate() {
+ if (DEBUG) Log.d(TAG, "handleTimeUpdate");
+ for (int i = 0; i < mInfoCallbacks.size(); i++) {
+ mInfoCallbacks.get(i).onTimeChanged();
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_BATTERY_UPDATE}
+ */
+ private void handleBatteryUpdate(int pluggedInStatus, int batteryLevel) {
+ if (DEBUG) Log.d(TAG, "handleBatteryUpdate");
+ final boolean pluggedIn = isPluggedIn(pluggedInStatus);
+
+ if (isBatteryUpdateInteresting(pluggedIn, batteryLevel)) {
+ mBatteryLevel = batteryLevel;
+ mDevicePluggedIn = pluggedIn;
+ for (int i = 0; i < mInfoCallbacks.size(); i++) {
+ mInfoCallbacks.get(i).onRefreshBatteryInfo(
+ shouldShowBatteryInfo(), pluggedIn, batteryLevel);
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_CARRIER_INFO_UPDATE}
+ */
+ private void handleCarrierInfoUpdate() {
+ if (DEBUG) Log.d(TAG, "handleCarrierInfoUpdate: plmn = " + mTelephonyPlmn
+ + ", spn = " + mTelephonySpn);
+
+ for (int i = 0; i < mInfoCallbacks.size(); i++) {
+ mInfoCallbacks.get(i).onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_SIM_STATE_CHANGE}
+ */
+ private void handleSimStateChange(SimArgs simArgs) {
+ final IccCard.State state = simArgs.simState;
+
+ if (DEBUG) {
+ Log.d(TAG, "handleSimStateChange: intentValue = " + simArgs + " "
+ + "state resolved to " + state.toString());
+ }
+
+ if (state != IccCard.State.UNKNOWN && state != mSimState) {
+ mSimState = state;
+ for (int i = 0; i < mSimStateCallbacks.size(); i++) {
+ mSimStateCallbacks.get(i).onSimStateChanged(state);
+ }
+ }
+ }
+
+ /**
+ * @param status One of the statuses of {@link android.os.BatteryManager}
+ * @return Whether the status maps to a status for being plugged in.
+ */
+ private boolean isPluggedIn(int status) {
+ return status == BATTERY_STATUS_CHARGING || status == BATTERY_STATUS_FULL;
+ }
+
+ private boolean isBatteryUpdateInteresting(boolean pluggedIn, int batteryLevel) {
+ // change in plug is always interesting
+ if (mDevicePluggedIn != pluggedIn) {
+ return true;
+ }
+
+ // change in battery level while plugged in
+ if (pluggedIn && mBatteryLevel != batteryLevel) {
+ return true;
+ }
+
+ if (!pluggedIn) {
+ // not plugged in and below threshold
+ if (batteryLevel < LOW_BATTERY_THRESHOLD && batteryLevel != mBatteryLevel) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @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 getTelephonyPlmnFrom(Intent intent) {
+ if (intent.getBooleanExtra(EXTRA_SHOW_PLMN, false)) {
+ final String plmn = intent.getStringExtra(EXTRA_PLMN);
+ if (plmn != null) {
+ return plmn;
+ } else {
+ return 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(EXTRA_SHOW_SPN, false)) {
+ final String spn = intent.getStringExtra(EXTRA_SPN);
+ if (spn != null) {
+ return spn;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Remove the given observer from being registered from any of the kinds
+ * of callbacks.
+ * @param observer The observer to remove (an instance of {@link ConfigurationChangeCallback},
+ * {@link InfoCallback} or {@link SimStateCallback}
+ */
+ public void removeCallback(Object observer) {
+ mInfoCallbacks.remove(observer);
+ mSimStateCallbacks.remove(observer);
+ }
+
+ /**
+ * Callback for general information relevant to lock screen.
+ */
+ interface InfoCallback {
+ void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel);
+ void onTimeChanged();
+
+ /**
+ * @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(String newState);
+ }
+
+ /**
+ * Callback to notify of sim state change.
+ */
+ interface SimStateCallback {
+ void onSimStateChanged(IccCard.State simState);
+ }
+
+ /**
+ * Register to receive notifications about general keyguard information
+ * (see {@link InfoCallback}.
+ * @param callback The callback.
+ */
+ public void registerInfoCallback(InfoCallback callback) {
+ if (!mInfoCallbacks.contains(callback)) {
+ mInfoCallbacks.add(callback);
+ } else {
+ Log.e(TAG, "Object tried to add another INFO callback", new Exception("Whoops"));
+ }
+ }
+
+ /**
+ * Register to be notified of sim state changes.
+ * @param callback The callback.
+ */
+ public void registerSimStateCallback(SimStateCallback callback) {
+ if (!mSimStateCallbacks.contains(callback)) {
+ mSimStateCallbacks.add(callback);
+ } else {
+ Log.e(TAG, "Object tried to add another SIM callback", new Exception("Whoops"));
+ }
+ }
+
+ public IccCard.State getSimState() {
+ return mSimState;
+ }
+
+ /**
+ * Report that the user succesfully entered the sim pin so we
+ * have the information earlier than waiting for the intent
+ * broadcast from the telephony code.
+ */
+ public void reportSimPinUnlocked() {
+ mSimState = IccCard.State.READY;
+ }
+
+ public boolean isKeyguardBypassEnabled() {
+ return mKeyguardBypassEnabled;
+ }
+
+ public boolean isDevicePluggedIn() {
+ return mDevicePluggedIn;
+ }
+
+ public int getBatteryLevel() {
+ return mBatteryLevel;
+ }
+
+ public boolean shouldShowBatteryInfo() {
+ return mDevicePluggedIn || mBatteryLevel < LOW_BATTERY_THRESHOLD;
+ }
+
+ 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;
+ }
+
+ public void reportFailedAttempt() {
+ mFailedAttempts++;
+ }
+}
diff --git a/policy/com/android/internal/policy/impl/KeyguardViewBase.java b/policy/com/android/internal/policy/impl/KeyguardViewBase.java
new file mode 100644
index 0000000..9dcbcb6
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/KeyguardViewBase.java
@@ -0,0 +1,209 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.telephony.TelephonyManager;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Gravity;
+import android.widget.FrameLayout;
+import android.util.AttributeSet;
+
+/**
+ * Base class for keyguard views. {@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 FrameLayout {
+
+ private KeyguardViewCallback mCallback;
+ private AudioManager mAudioManager;
+ private TelephonyManager mTelephonyManager = null;
+
+ public KeyguardViewBase(Context context) {
+ super(context);
+
+ // drop shadow below status bar in keyguard too
+ mForegroundInPadding = false;
+ setForegroundGravity(Gravity.FILL_HORIZONTAL | Gravity.TOP);
+ setForeground(
+ context.getResources().getDrawable(
+ com.android.internal.R.drawable.title_bar_shadow));
+ }
+
+ // used to inject callback
+ void setCallback(KeyguardViewCallback callback) {
+ mCallback = callback;
+ }
+
+ public KeyguardViewCallback getCallback() {
+ return mCallback;
+ }
+
+ /**
+ * 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 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.
+ */
+ 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 (shouldEventKeepScreenOnWhileKeyguardShowing(event)) {
+ mCallback.pokeWakelock();
+ }
+
+ if (interceptMediaKey(event)) {
+ return true;
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ private boolean shouldEventKeepScreenOnWhileKeyguardShowing(KeyEvent event) {
+ if (event.getAction() != KeyEvent.ACTION_DOWN) {
+ return false;
+ }
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * 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_PAUSE:
+ /* Suppress PLAYPAUSE 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_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_FAST_FORWARD: {
+ Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
+ intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
+ getContext().sendOrderedBroadcast(intent, null);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN: {
+ synchronized (this) {
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager) getContext().getSystemService(
+ Context.AUDIO_SERVICE);
+ }
+ }
+ // Volume buttons should only function for music.
+ if (mAudioManager.isMusicActive()) {
+ mAudioManager.adjustStreamVolume(
+ AudioManager.STREAM_MUSIC,
+ keyCode == KeyEvent.KEYCODE_VOLUME_UP
+ ? AudioManager.ADJUST_RAISE
+ : AudioManager.ADJUST_LOWER,
+ 0);
+ }
+ // Don't execute default volume behavior
+ return true;
+ }
+ }
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ 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_FAST_FORWARD: {
+ Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
+ intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
+ getContext().sendOrderedBroadcast(intent, null);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/policy/com/android/internal/policy/impl/KeyguardViewCallback.java b/policy/com/android/internal/policy/impl/KeyguardViewCallback.java
new file mode 100644
index 0000000..b376d65
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/KeyguardViewCallback.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+/**
+ * The callback used by the keyguard view to tell the {@link KeyguardViewMediator}
+ * various things.
+ */
+public interface KeyguardViewCallback {
+
+ /**
+ * 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(int 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();
+}
diff --git a/policy/com/android/internal/policy/impl/KeyguardViewManager.java b/policy/com/android/internal/policy/impl/KeyguardViewManager.java
new file mode 100644
index 0000000..ba1d7f5
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/KeyguardViewManager.java
@@ -0,0 +1,241 @@
+/*
+ * 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;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.PixelFormat;
+import android.graphics.Canvas;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewManager;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+/**
+ * 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 implements KeyguardWindowController {
+ private final static boolean DEBUG = false;
+ private static String TAG = "KeyguardViewManager";
+
+ private final Context mContext;
+ private final ViewManager mViewManager;
+ private final KeyguardViewCallback mCallback;
+ private final KeyguardViewProperties mKeyguardViewProperties;
+
+ private final KeyguardUpdateMonitor mUpdateMonitor;
+
+ private WindowManager.LayoutParams mWindowLayoutParams;
+ private boolean mNeedsInput = false;
+
+ private FrameLayout mKeyguardHost;
+ private KeyguardViewBase mKeyguardView;
+
+ private boolean mScreenOn = false;
+
+ /**
+ * @param context Used to create views.
+ * @param viewManager Keyguard will be attached to this.
+ * @param callback Used to notify of changes.
+ */
+ public KeyguardViewManager(Context context, ViewManager viewManager,
+ KeyguardViewCallback callback, KeyguardViewProperties keyguardViewProperties, KeyguardUpdateMonitor updateMonitor) {
+ mContext = context;
+ mViewManager = viewManager;
+ mCallback = callback;
+ mKeyguardViewProperties = keyguardViewProperties;
+
+ mUpdateMonitor = updateMonitor;
+ }
+
+ /**
+ * Helper class to host the keyguard view.
+ */
+ private static class KeyguardViewHost extends FrameLayout {
+ private final KeyguardViewCallback mCallback;
+
+ private KeyguardViewHost(Context context, KeyguardViewCallback callback) {
+ super(context);
+ mCallback = callback;
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ mCallback.keyguardDoneDrawing();
+ }
+ }
+
+ /**
+ * 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);
+
+ if (mKeyguardHost == null) {
+ if (DEBUG) Log.d(TAG, "keyguard host is null, creating it...");
+
+ mKeyguardHost = new KeyguardViewHost(mContext, mCallback);
+
+ final int stretch = ViewGroup.LayoutParams.MATCH_PARENT;
+ int flags = WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN
+ | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER
+ | WindowManager.LayoutParams.FLAG_KEEP_SURFACE_WHILE_ANIMATING
+ /*| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR*/ ;
+ if (!mNeedsInput) {
+ flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ }
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ stretch, stretch, WindowManager.LayoutParams.TYPE_KEYGUARD,
+ flags, PixelFormat.TRANSLUCENT);
+ lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+ lp.windowAnimations = com.android.internal.R.style.Animation_LockScreen;
+ lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+ lp.setTitle("Keyguard");
+ mWindowLayoutParams = lp;
+
+ mViewManager.addView(mKeyguardHost, lp);
+ }
+
+ if (mKeyguardView == null) {
+ if (DEBUG) Log.d(TAG, "keyguard view is null, creating it...");
+ mKeyguardView = mKeyguardViewProperties.createKeyguardView(mContext, mUpdateMonitor, this);
+ mKeyguardView.setId(R.id.lock_screen);
+ mKeyguardView.setCallback(mCallback);
+
+ final ViewGroup.LayoutParams lp = new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+
+ mKeyguardHost.addView(mKeyguardView, lp);
+
+ if (mScreenOn) {
+ mKeyguardView.onScreenTurnedOn();
+ }
+ }
+
+ mKeyguardHost.setVisibility(View.VISIBLE);
+ mKeyguardView.requestFocus();
+ }
+
+ 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;
+ }
+ mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
+ }
+ }
+
+ /**
+ * 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() {
+ if (DEBUG) Log.d(TAG, "onScreenTurnedOn()");
+ mScreenOn = true;
+ if (mKeyguardView != null) {
+ mKeyguardView.onScreenTurnedOn();
+ }
+ }
+
+ 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.
+ */
+ 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) {
+ mKeyguardHost.removeView(lastView);
+ lastView.cleanUp();
+ }
+ }
+ }, 500);
+ }
+ }
+ }
+
+ /**
+ * @return Whether the keyguard is showing
+ */
+ public synchronized boolean isShowing() {
+ return (mKeyguardHost != null && mKeyguardHost.getVisibility() == View.VISIBLE);
+ }
+}
diff --git a/policy/com/android/internal/policy/impl/KeyguardViewMediator.java b/policy/com/android/internal/policy/impl/KeyguardViewMediator.java
new file mode 100644
index 0000000..c255041
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/KeyguardViewMediator.java
@@ -0,0 +1,1135 @@
+/*
+ * 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;
+
+import com.android.internal.telephony.IccCard;
+import com.android.internal.widget.LockPatternUtils;
+
+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.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.LocalPowerManager;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.util.Config;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.WindowManagerImpl;
+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.KeyInputQueue}'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 implements KeyguardViewCallback,
+ KeyguardUpdateMonitor.SimStateCallback {
+ private final static boolean DEBUG = false && Config.LOGD;
+ private final static boolean DBG_WAKE = DEBUG || true;
+
+ 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 = 5000;
+
+
+ /**
+ * The default amount of time we stay awake (used for all key input) when
+ * the keyboard is open
+ */
+ protected static final int AWAKE_INTERVAL_DEFAULT_KEYBOARD_OPEN_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_DELAY_MS = 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;
+
+ private Context mContext;
+ private AlarmManager mAlarmManager;
+ private StatusBarManager mStatusBarManager;
+ private boolean mShowLockIcon = false;
+ private IBinder mSecureLockIcon = null;
+
+ 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;
+
+
+ /** Low level access to the power manager for enableUserActivity. Having this
+ * requires that we run in the system process. */
+ LocalPowerManager mRealPowerManager;
+
+ /** 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;
+
+ private PhoneWindowManager mCallback;
+
+ /**
+ * 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 KeyguardViewProperties mKeyguardViewProperties;
+
+ private KeyguardUpdateMonitor mUpdateMonitor;
+
+ private boolean mKeyboardOpen = false;
+
+ private boolean mScreenOn = false;
+
+ // 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;
+
+ public KeyguardViewMediator(Context context, PhoneWindowManager callback,
+ LocalPowerManager powerManager) {
+ mContext = context;
+
+ mRealPowerManager = powerManager;
+ 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);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(DELAYED_KEYGUARD_ACTION);
+ filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
+ context.registerReceiver(mBroadCastReceiver, filter);
+ mAlarmManager = (AlarmManager) context
+ .getSystemService(Context.ALARM_SERVICE);
+ mCallback = callback;
+
+ mUpdateMonitor = new KeyguardUpdateMonitor(context);
+
+ mUpdateMonitor.registerSimStateCallback(this);
+
+ mKeyguardViewProperties = new LockPatternKeyguardViewProperties(
+ new LockPatternUtils(mContext), mUpdateMonitor);
+
+ mKeyguardViewManager = new KeyguardViewManager(
+ context, WindowManagerImpl.getDefault(), this,
+ mKeyguardViewProperties, mUpdateMonitor);
+
+ mUserPresentIntent = new Intent(Intent.ACTION_USER_PRESENT);
+ mUserPresentIntent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+
+ final ContentResolver cr = mContext.getContentResolver();
+ mShowLockIcon = (Settings.System.getInt(cr, "show_status_bar_lock", 0) == 1);
+ }
+
+ /**
+ * Let us know that the system is ready after startup.
+ */
+ public void onSystemReady() {
+ synchronized (this) {
+ if (DEBUG) Log.d(TAG, "onSystemReady");
+ mSystemReady = true;
+ doKeyguard();
+ }
+ }
+
+ /**
+ * 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 + ")");
+
+ 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) {
+ // if the screen turned off because of timeout, 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)
+ long when = SystemClock.elapsedRealtime() + KEYGUARD_DELAY_MS;
+ 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 {
+ doKeyguard();
+ }
+ }
+ }
+
+ /**
+ * Let's us know the screen was turned on.
+ */
+ public void onScreenTurnedOn() {
+ synchronized (this) {
+ mScreenOn = true;
+ mDelayedShowingSequence++;
+ if (DEBUG) Log.d(TAG, "onScreenTurnedOn, seq = " + mDelayedShowingSequence);
+ notifyScreenOnLocked();
+ }
+ }
+
+ /**
+ * 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;
+ 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();
+ }
+
+ /**
+ * Returns true if the change is resulting in the keyguard beign dismissed,
+ * meaning the screen can turn on immediately. Otherwise returns false.
+ */
+ public boolean doLidChangeTq(boolean isLidOpen) {
+ mKeyboardOpen = isLidOpen;
+
+ if (mUpdateMonitor.isKeyguardBypassEnabled() && mKeyboardOpen
+ && !mKeyguardViewProperties.isSecure() && mKeyguardViewManager.isShowing()) {
+ if (DEBUG) Log.d(TAG, "bypassing keyguard on sliding open of keyboard with non-secure keyguard");
+ mHandler.sendEmptyMessage(KEYGUARD_DONE_AUTHENTICATING);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Enable the keyguard if the settings are appropriate.
+ */
+ private void doKeyguard() {
+ synchronized (this) {
+ // 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 IccCard.State state = mUpdateMonitor.getSimState();
+ final boolean lockedOrMissing = state.isPinLocked()
+ || ((state == IccCard.State.ABSENT) && 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 (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() {
+ if (DEBUG) Log.d(TAG, "notifyScreenOnLocked");
+ mHandler.sendEmptyMessage(NOTIFY_SCREEN_ON);
+ }
+
+ /**
+ * 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);
+ }
+
+ /** {@inheritDoc} */
+ public void onSimStateChanged(IccCard.State simState) {
+ if (DEBUG) Log.d(TAG, "onSimStateChanged: " + simState);
+
+ switch (simState) {
+ case ABSENT:
+ // only force lock screen in case of missing sim if user hasn't
+ // gone through setup wizard
+ if (!mUpdateMonitor.isDeviceProvisioned()) {
+ if (!isShowing()) {
+ if (DEBUG) Log.d(TAG, "INTENT_VALUE_ICC_ABSENT and keygaurd isn't showing, we need "
+ + "to show the keyguard since the device isn't provisioned yet.");
+ doKeyguard();
+ } else {
+ resetStateLocked();
+ }
+ }
+ break;
+ case PIN_REQUIRED:
+ case PUK_REQUIRED:
+ if (!isShowing()) {
+ if (DEBUG) Log.d(TAG, "INTENT_VALUE_ICC_LOCKED and keygaurd isn't showing, we need "
+ + "to show the keyguard so the user can enter their sim pin");
+ doKeyguard();
+ } else {
+ resetStateLocked();
+ }
+
+ break;
+ case READY:
+ if (isShowing()) {
+ resetStateLocked();
+ }
+ break;
+ }
+ }
+
+ public boolean isSecure() {
+ return mKeyguardViewProperties.isSecure();
+ }
+
+ private BroadcastReceiver mBroadCastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(DELAYED_KEYGUARD_ACTION)) {
+
+ int sequence = intent.getIntExtra("seq", 0);
+
+ if (false) Log.d(TAG, "received DELAYED_KEYGUARD_ACTION with seq = "
+ + sequence + ", mDelayedShowingSequence = " + mDelayedShowingSequence);
+
+ if (mDelayedShowingSequence == sequence) {
+ // Don't play lockscreen SFX if the screen went off due to
+ // timeout.
+ mSuppressNextLockSound = true;
+
+ doKeyguard();
+ }
+ } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
+ mPhoneState = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
+
+ if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState) // 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");
+ doKeyguard();
+ }
+ }
+ }
+ };
+
+
+ /**
+ * 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
+ * @return Whether we poked the wake lock (and turned the screen on)
+ */
+ public boolean onWakeKeyWhenKeyguardShowingTq(int keyCode) {
+ if (DEBUG) Log.d(TAG, "onWakeKeyWhenKeyguardShowing(" + keyCode + ")");
+
+ if (isWakeKeyWhenKeyguardShowing(keyCode)) {
+ // 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;
+ }
+ }
+
+ private boolean isWakeKeyWhenKeyguardShowing(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ 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_FAST_FORWARD:
+ case KeyEvent.KEYCODE_CAMERA:
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Callbacks from {@link KeyguardViewManager}.
+ */
+
+ /** {@inheritDoc} */
+ public void pokeWakelock() {
+ pokeWakelock(mKeyboardOpen ?
+ AWAKE_INTERVAL_DEFAULT_KEYBOARD_OPEN_MS : AWAKE_INTERVAL_DEFAULT_MS);
+ }
+
+ /** {@inheritDoc} */
+ public void pokeWakelock(int 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);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see #handleKeyguardDone
+ */
+ public void keyguardDone(boolean authenticated) {
+ keyguardDone(authenticated, 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;
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see #handleKeyguardDoneDrawing
+ */
+ public void keyguardDoneDrawing() {
+ mHandler.sendEmptyMessage(KEYGUARD_DONE_DRAWING);
+ }
+
+ /**
+ * 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() {
+ @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();
+ 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);
+ return;
+ case SET_HIDDEN:
+ handleSetHidden(msg.arg1 != 0);
+ break;
+ case KEYGUARD_TIMEOUT:
+ doKeyguard();
+ break;
+ }
+ }
+ };
+
+ /**
+ * @see #keyguardDone
+ * @see #KEYGUARD_DONE
+ */
+ private void handleKeyguardDone(boolean wakeup) {
+ if (DEBUG) Log.d(TAG, "handleKeyguardDone");
+ handleHide();
+ if (wakeup) {
+ mPM.userActivity(SystemClock.uptimeMillis(), true);
+ }
+ mWakeLock.release();
+ mContext.sendBroadcast(mUserPresentIntent);
+ }
+
+ /**
+ * @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 String whichSound = locked
+ ? Settings.System.LOCK_SOUND
+ : Settings.System.UNLOCK_SOUND;
+ final String soundPath = Settings.System.getString(cr, whichSound);
+ if (soundPath != null) {
+ final Uri soundUri = Uri.parse("file://" + soundPath);
+ if (soundUri != null) {
+ final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
+ if (sfx != null) {
+ sfx.setStreamType(AudioManager.STREAM_SYSTEM);
+ sfx.play();
+ } else {
+ Log.d(TAG, "playSounds: failed to load ringtone from uri: " + soundUri);
+ }
+ } else {
+ Log.d(TAG, "playSounds: could not parse Uri: " + soundPath);
+ }
+ } else {
+ Log.d(TAG, "playSounds: whichSound = " + whichSound + "; soundPath was null");
+ }
+ }
+ }
+
+ /**
+ * Handle message sent by {@link #showLocked}.
+ * @see #SHOW
+ */
+ private void handleShow() {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleShow");
+ if (!mSystemReady) return;
+
+ playSounds(true);
+
+ mKeyguardViewManager.show();
+ mShowing = true;
+ adjustUserActivityLocked();
+ adjustStatusBarLocked();
+ try {
+ ActivityManagerNative.getDefault().closeSystemDialogs("lock");
+ } catch (RemoteException e) {
+ }
+ 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;
+ 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;
+ 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 (mSecureLockIcon == null) {
+ mSecureLockIcon = mStatusBarManager.addIcon("secure",
+ com.android.internal.R.drawable.stat_sys_secure, 0);
+ }
+ } else {
+ if (mSecureLockIcon != null) {
+ mStatusBarManager.removeIcon(mSecureLockIcon);
+ mSecureLockIcon = null;
+ }
+ }
+ }
+
+ // if the keyguard is shown, allow the status bar to open
+ // only if the keyguard is insecure and is covered by another window
+ boolean enable = !mShowing || (mHidden && !isSecure());
+ mStatusBarManager.disable(enable ?
+ StatusBarManager.DISABLE_NONE :
+ StatusBarManager.DISABLE_EXPAND);
+ }
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * 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() {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleNotifyScreenOn");
+ mKeyguardViewManager.onScreenTurnedOn();
+ }
+ }
+}
+
+
diff --git a/policy/com/android/internal/policy/impl/KeyguardViewProperties.java b/policy/com/android/internal/policy/impl/KeyguardViewProperties.java
new file mode 100644
index 0000000..bda08eb
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/KeyguardViewProperties.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+import android.content.Context;
+
+/**
+ * Defines operations necessary for showing a keyguard, including how to create
+ * it, and various properties that are useful to be able to query independant
+ * of whether the keyguard instance is around or not.
+ */
+public interface KeyguardViewProperties {
+
+ /**
+ * Create a keyguard view.
+ * @param context the context to use when creating the view.
+ * @param updateMonitor configuration may be based on this.
+ * @param controller for talking back with the containing window.
+ * @return the view.
+ */
+ KeyguardViewBase createKeyguardView(Context context,
+ KeyguardUpdateMonitor updateMonitor,
+ KeyguardWindowController controller);
+
+ /**
+ * Would the keyguard be secure right now?
+ * @return Whether the keyguard is currently secure, meaning it will block
+ * the user from getting past it until the user enters some sort of PIN.
+ */
+ boolean isSecure();
+
+}
diff --git a/policy/com/android/internal/policy/impl/KeyguardWindowController.java b/policy/com/android/internal/policy/impl/KeyguardWindowController.java
new file mode 100644
index 0000000..4ad48fb
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/KeyguardWindowController.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl;
+
+/**
+ * Interface passed to the keyguard view, for it to call up to control
+ * its containing window.
+ */
+public interface KeyguardWindowController {
+ /**
+ * Control whether the window needs input -- that is if it has
+ * text fields and thus should allow input method interaction.
+ */
+ void setNeedsInput(boolean needsInput);
+}
diff --git a/policy/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/com/android/internal/policy/impl/LockPatternKeyguardView.java
new file mode 100644
index 0000000..c1b14c4
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/LockPatternKeyguardView.java
@@ -0,0 +1,789 @@
+/*
+ * 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;
+
+import com.android.internal.R;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.widget.LockPatternUtils;
+
+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.AlertDialog;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager;
+
+import java.io.IOException;
+
+/**
+ * The host view for all of the screens of the pattern unlock screen. There are
+ * two {@link Mode}s of operation, lock and unlock. This will show the appropriate
+ * screen, and listen for callbacks via
+ * {@link com.android.internal.policy.impl.KeyguardScreenCallback}
+ * from the current screen.
+ *
+ * This view, in turn, communicates back to
+ * {@link com.android.internal.policy.impl.KeyguardViewManager}
+ * via its {@link com.android.internal.policy.impl.KeyguardViewCallback}, as appropriate.
+ */
+public class LockPatternKeyguardView extends KeyguardViewBase {
+
+ static final boolean DEBUG_CONFIGURATION = false;
+
+ // 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 boolean DEBUG = false;
+ private static final String TAG = "LockPatternKeyguardView";
+
+ private final KeyguardUpdateMonitor mUpdateMonitor;
+ private final KeyguardWindowController mWindowController;
+
+ private View mLockScreen;
+ private View mUnlockScreen;
+
+ private boolean mScreenOn = false;
+ private boolean mEnableFallback = false; // assume no fallback UI until we know better
+
+ /**
+ * The current {@link KeyguardScreen} will use this to communicate back to us.
+ */
+ KeyguardScreenCallback mKeyguardScreenCallback;
+
+
+ private boolean mRequiresSim;
+
+
+ /**
+ * Either a lock screen (an informational keyguard screen), or an unlock
+ * screen (a means for unlocking the device) is shown at any given time.
+ */
+ enum Mode {
+ LockScreen,
+ UnlockScreen
+ }
+
+ /**
+ * The different types screens available for {@link Mode#UnlockScreen}.
+ * @see com.android.internal.policy.impl.LockPatternKeyguardView#getUnlockMode()
+ */
+ enum UnlockMode {
+
+ /**
+ * Unlock by drawing a pattern.
+ */
+ Pattern,
+
+ /**
+ * Unlock by entering a sim pin.
+ */
+ SimPin,
+
+ /**
+ * Unlock by entering an account's login and password.
+ */
+ Account,
+
+ /**
+ * Unlock by entering a password or PIN
+ */
+ Password,
+
+ /**
+ * Unknown (uninitialized) value
+ */
+ Unknown
+ }
+
+ /**
+ * The current mode.
+ */
+ private Mode mMode = Mode.LockScreen;
+
+ /**
+ * Keeps track of what mode the current unlock screen is (cached from most recent computation in
+ * {@link #getUnlockMode}).
+ */
+ private UnlockMode mUnlockScreenMode;
+
+ private boolean mForgotPattern;
+
+ /**
+ * If true, it means we are in the process of verifying that the user
+ * can get past the lock screen per {@link #verifyUnlock()}
+ */
+ private boolean mIsVerifyUnlockOnly = false;
+
+
+ /**
+ * Used to lookup the state of the lock pattern
+ */
+ private final LockPatternUtils mLockPatternUtils;
+
+ private UnlockMode mCurrentUnlockMode = UnlockMode.Unknown;
+
+ /**
+ * The current configuration.
+ */
+ private Configuration mConfiguration;
+
+ /**
+ * @return Whether we are stuck on the lock screen because the sim is
+ * missing.
+ */
+ private boolean stuckOnLockScreenBecauseSimMissing() {
+ return mRequiresSim
+ && (!mUpdateMonitor.isDeviceProvisioned())
+ && (mUpdateMonitor.getSimState() == IccCard.State.ABSENT);
+ }
+
+ /**
+ * @param context Used to inflate, and create views.
+ * @param updateMonitor Knows the state of the world, and passed along to each
+ * screen so they can use the knowledge, and also register for callbacks
+ * on dynamic information.
+ * @param lockPatternUtils Used to look up state of lock pattern.
+ */
+ public LockPatternKeyguardView(
+ Context context,
+ KeyguardUpdateMonitor updateMonitor,
+ LockPatternUtils lockPatternUtils,
+ KeyguardWindowController controller) {
+ super(context);
+
+ mConfiguration = context.getResources().getConfiguration();
+ mEnableFallback = false;
+
+ mRequiresSim =
+ TextUtils.isEmpty(SystemProperties.get("keyguard.no_require_sim"));
+
+ mUpdateMonitor = updateMonitor;
+ mLockPatternUtils = lockPatternUtils;
+ mWindowController = controller;
+
+ mMode = getInitialMode();
+
+ mKeyguardScreenCallback = new KeyguardScreenCallback() {
+
+ public void goToLockScreen() {
+ mForgotPattern = false;
+ if (mIsVerifyUnlockOnly) {
+ // navigating away from unlock screen during verify mode means
+ // we are done and the user failed to authenticate.
+ mIsVerifyUnlockOnly = false;
+ getCallback().keyguardDone(false);
+ } else {
+ updateScreen(Mode.LockScreen);
+ }
+ }
+
+ public void goToUnlockScreen() {
+ final IccCard.State simState = mUpdateMonitor.getSimState();
+ if (stuckOnLockScreenBecauseSimMissing()
+ || (simState == IccCard.State.PUK_REQUIRED)){
+ // stuck on lock screen when sim missing or puk'd
+ return;
+ }
+ if (!isSecure()) {
+ getCallback().keyguardDone(true);
+ } else {
+ updateScreen(Mode.UnlockScreen);
+ }
+ }
+
+ public void forgotPattern(boolean isForgotten) {
+ if (mEnableFallback) {
+ mForgotPattern = isForgotten;
+ updateScreen(Mode.UnlockScreen);
+ }
+ }
+
+ public boolean isSecure() {
+ return LockPatternKeyguardView.this.isSecure();
+ }
+
+ public boolean isVerifyUnlockOnly() {
+ return mIsVerifyUnlockOnly;
+ }
+
+ public void recreateMe(Configuration config) {
+ mConfiguration = config;
+ recreateScreens();
+ }
+
+ public void takeEmergencyCallAction() {
+ pokeWakelock(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);
+ }
+ }
+
+ public void pokeWakelock() {
+ getCallback().pokeWakelock();
+ }
+
+ public void pokeWakelock(int millis) {
+ getCallback().pokeWakelock(millis);
+ }
+
+ public void keyguardDone(boolean authenticated) {
+ getCallback().keyguardDone(authenticated);
+ }
+
+ public void keyguardDoneDrawing() {
+ // irrelevant to keyguard screen, they shouldn't be calling this
+ }
+
+ public void reportFailedUnlockAttempt() {
+ mUpdateMonitor.reportFailedAttempt();
+ final int failedAttempts = mUpdateMonitor.getFailedAttempts();
+ if (DEBUG) Log.d(TAG,
+ "reportFailedPatternAttempt: #" + failedAttempts +
+ " (enableFallback=" + mEnableFallback + ")");
+ final boolean usingLockPattern = mLockPatternUtils.getKeyguardStoredPasswordQuality()
+ == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+ if (usingLockPattern && mEnableFallback && failedAttempts ==
+ (LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET
+ - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
+ showAlmostAtAccountLoginDialog();
+ } else if (usingLockPattern && mEnableFallback
+ && failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) {
+ mLockPatternUtils.setPermanentlyLocked(true);
+ updateScreen(mMode);
+ } else if ((failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)
+ == 0) {
+ showTimeoutDialog();
+ }
+ mLockPatternUtils.reportFailedPasswordAttempt();
+ }
+
+ public boolean doesFallbackUnlockScreenExist() {
+ return mEnableFallback;
+ }
+
+ public void reportSuccessfulUnlockAttempt() {
+ mLockPatternUtils.reportSuccessfulPasswordAttempt();
+ }
+ };
+
+ /**
+ * We'll get key events the current screen doesn't use. see
+ * {@link KeyguardViewBase#onKeyDown(int, android.view.KeyEvent)}
+ */
+ setFocusableInTouchMode(true);
+ setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+
+ // create both the lock and unlock screen so they are quickly available
+ // when the screen turns on
+ mLockScreen = createLockScreen();
+ addView(mLockScreen);
+ final UnlockMode unlockMode = getUnlockMode();
+ if (DEBUG) Log.d(TAG,
+ "LockPatternKeyguardView ctor: about to createUnlockScreenFor; mEnableFallback="
+ + mEnableFallback);
+ mUnlockScreen = createUnlockScreenFor(unlockMode);
+ mUnlockScreenMode = unlockMode;
+
+ maybeEnableFallback(context);
+
+ addView(mUnlockScreen);
+ updateScreen(mMode);
+ }
+
+ 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 (mEnableFallback || mAccountIndex >= mAccounts.length) {
+ if (mUnlockScreen == null) {
+ Log.w(TAG, "no unlock screen when trying to enable fallback");
+ } else if (mUnlockScreen instanceof PatternUnlockScreen) {
+ ((PatternUnlockScreen)mUnlockScreen).setEnableFallback(mEnableFallback);
+ }
+ 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 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();
+ }
+
+
+ // TODO:
+ // This overloaded method was added to workaround a race condition in the framework between
+ // notification for orientation changed, layout() and switching resources. This code attempts
+ // to avoid drawing the incorrect layout while things are in transition. The method can just
+ // be removed once the race condition is fixed. See bugs 2262578 and 2292713.
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ if (DEBUG) Log.v(TAG, "*** dispatchDraw() time: " + SystemClock.elapsedRealtime());
+ super.dispatchDraw(canvas);
+ }
+
+ @Override
+ public void reset() {
+ mIsVerifyUnlockOnly = false;
+ mForgotPattern = false;
+ updateScreen(getInitialMode());
+ }
+
+ @Override
+ public void onScreenTurnedOff() {
+ mScreenOn = false;
+ mForgotPattern = false;
+ if (mMode == Mode.LockScreen) {
+ ((KeyguardScreen) mLockScreen).onPause();
+ } else {
+ ((KeyguardScreen) mUnlockScreen).onPause();
+ }
+ }
+
+ @Override
+ public void onScreenTurnedOn() {
+ mScreenOn = true;
+ if (mMode == Mode.LockScreen) {
+ ((KeyguardScreen) mLockScreen).onResume();
+ } else {
+ ((KeyguardScreen) mUnlockScreen).onResume();
+ }
+ }
+
+ private void recreateLockScreen() {
+ if (mLockScreen.getVisibility() == View.VISIBLE) {
+ ((KeyguardScreen) mLockScreen).onPause();
+ }
+ ((KeyguardScreen) mLockScreen).cleanUp();
+ removeView(mLockScreen);
+
+ mLockScreen = createLockScreen();
+ mLockScreen.setVisibility(View.INVISIBLE);
+ addView(mLockScreen);
+ }
+
+ private void recreateUnlockScreen() {
+ if (mUnlockScreen.getVisibility() == View.VISIBLE) {
+ ((KeyguardScreen) mUnlockScreen).onPause();
+ }
+ ((KeyguardScreen) mUnlockScreen).cleanUp();
+ removeView(mUnlockScreen);
+
+ final UnlockMode unlockMode = getUnlockMode();
+ mUnlockScreen = createUnlockScreenFor(unlockMode);
+ mUnlockScreen.setVisibility(View.INVISIBLE);
+ mUnlockScreenMode = unlockMode;
+ addView(mUnlockScreen);
+ }
+
+ private void recreateScreens() {
+ recreateLockScreen();
+ recreateUnlockScreen();
+ updateScreen(mMode);
+ }
+
+ @Override
+ public void wakeWhenReadyTq(int keyCode) {
+ if (DEBUG) Log.d(TAG, "onWakeKey");
+ if (keyCode == KeyEvent.KEYCODE_MENU && isSecure() && (mMode == Mode.LockScreen)
+ && (mUpdateMonitor.getSimState() != IccCard.State.PUK_REQUIRED)) {
+ if (DEBUG) Log.d(TAG, "switching screens to unlock screen because wake key was MENU");
+ updateScreen(Mode.UnlockScreen);
+ getCallback().pokeWakelock();
+ } else {
+ if (DEBUG) Log.d(TAG, "poking wake lock immediately");
+ getCallback().pokeWakelock();
+ }
+ }
+
+ @Override
+ public void verifyUnlock() {
+ if (!isSecure()) {
+ // non-secure keyguard screens are successfull by default
+ getCallback().keyguardDone(true);
+ } else if (mUnlockScreenMode != UnlockMode.Pattern) {
+ // can only verify unlock when in pattern mode
+ getCallback().keyguardDone(false);
+ } else {
+ // otherwise, go to the unlock screen, see if they can verify it
+ mIsVerifyUnlockOnly = true;
+ updateScreen(Mode.UnlockScreen);
+ }
+ }
+
+ @Override
+ public void cleanUp() {
+ ((KeyguardScreen) mLockScreen).onPause();
+ ((KeyguardScreen) mLockScreen).cleanUp();
+ ((KeyguardScreen) mUnlockScreen).onPause();
+ ((KeyguardScreen) mUnlockScreen).cleanUp();
+ }
+
+ private boolean isSecure() {
+ UnlockMode unlockMode = getUnlockMode();
+ boolean secure = false;
+ switch (unlockMode) {
+ case Pattern:
+ secure = mLockPatternUtils.isLockPatternEnabled();
+ break;
+ case SimPin:
+ secure = mUpdateMonitor.getSimState() == IccCard.State.PIN_REQUIRED
+ || mUpdateMonitor.getSimState() == IccCard.State.PUK_REQUIRED;
+ break;
+ case Account:
+ secure = true;
+ break;
+ case Password:
+ secure = mLockPatternUtils.isLockPasswordEnabled();
+ break;
+ default:
+ throw new IllegalStateException("unknown unlock mode " + unlockMode);
+ }
+ return secure;
+ }
+
+ private void updateScreen(final Mode mode) {
+
+ if (DEBUG_CONFIGURATION) Log.v(TAG, "**** UPDATE SCREEN: mode=" + mode
+ + " last mode=" + mMode, new RuntimeException());
+
+ mMode = mode;
+
+ // Re-create the unlock screen if necessary. This is primarily required to properly handle
+ // SIM state changes. This typically happens when this method is called by reset()
+ if (mode == Mode.UnlockScreen && mCurrentUnlockMode != getUnlockMode()) {
+ recreateUnlockScreen();
+ }
+
+ final View goneScreen = (mode == Mode.LockScreen) ? mUnlockScreen : mLockScreen;
+ final View visibleScreen = (mode == Mode.LockScreen) ? mLockScreen : mUnlockScreen;
+
+ // do this before changing visibility so focus isn't requested before the input
+ // flag is set
+ mWindowController.setNeedsInput(((KeyguardScreen)visibleScreen).needsInput());
+
+ if (DEBUG_CONFIGURATION) {
+ Log.v(TAG, "Gone=" + goneScreen);
+ Log.v(TAG, "Visible=" + visibleScreen);
+ }
+
+ if (mScreenOn) {
+ if (goneScreen.getVisibility() == View.VISIBLE) {
+ ((KeyguardScreen) goneScreen).onPause();
+ }
+ if (visibleScreen.getVisibility() != View.VISIBLE) {
+ ((KeyguardScreen) visibleScreen).onResume();
+ }
+ }
+
+ goneScreen.setVisibility(View.GONE);
+ visibleScreen.setVisibility(View.VISIBLE);
+ requestLayout();
+
+ if (!visibleScreen.requestFocus()) {
+ throw new IllegalStateException("keyguard screen must be able to take "
+ + "focus when shown " + visibleScreen.getClass().getCanonicalName());
+ }
+ }
+
+ View createLockScreen() {
+ return new LockScreen(
+ mContext,
+ mConfiguration,
+ mLockPatternUtils,
+ mUpdateMonitor,
+ mKeyguardScreenCallback);
+ }
+
+ View createUnlockScreenFor(UnlockMode unlockMode) {
+ View unlockView = null;
+ if (unlockMode == UnlockMode.Pattern) {
+ PatternUnlockScreen view = new PatternUnlockScreen(
+ mContext,
+ mConfiguration,
+ mLockPatternUtils,
+ mUpdateMonitor,
+ mKeyguardScreenCallback,
+ mUpdateMonitor.getFailedAttempts());
+ if (DEBUG) Log.d(TAG,
+ "createUnlockScreenFor(" + unlockMode + "): mEnableFallback=" + mEnableFallback);
+ view.setEnableFallback(mEnableFallback);
+ unlockView = view;
+ } else if (unlockMode == UnlockMode.SimPin) {
+ unlockView = new SimUnlockScreen(
+ mContext,
+ mConfiguration,
+ mUpdateMonitor,
+ mKeyguardScreenCallback,
+ mLockPatternUtils);
+ } else if (unlockMode == UnlockMode.Account) {
+ try {
+ unlockView = new AccountUnlockScreen(
+ mContext,
+ mConfiguration,
+ mKeyguardScreenCallback,
+ mLockPatternUtils);
+ } catch (IllegalStateException e) {
+ Log.i(TAG, "Couldn't instantiate AccountUnlockScreen"
+ + " (IAccountsService isn't available)");
+ // TODO: Need a more general way to provide a
+ // platform-specific fallback UI here.
+ // For now, if we can't display the account login
+ // unlock UI, just bring back the regular "Pattern" unlock mode.
+
+ // (We do this by simply returning a regular UnlockScreen
+ // here. This means that the user will still see the
+ // regular pattern unlock UI, regardless of the value of
+ // mUnlockScreenMode or whether or not we're in the
+ // "permanently locked" state.)
+ unlockView = createUnlockScreenFor(UnlockMode.Pattern);
+ }
+ } else if (unlockMode == UnlockMode.Password) {
+ unlockView = new PasswordUnlockScreen(
+ mContext,
+ mConfiguration,
+ mLockPatternUtils,
+ mUpdateMonitor,
+ mKeyguardScreenCallback);
+ } else {
+ throw new IllegalArgumentException("unknown unlock mode " + unlockMode);
+ }
+ mCurrentUnlockMode = unlockMode;
+ return unlockView;
+ }
+
+ /**
+ * Given the current state of things, what should be the initial mode of
+ * the lock screen (lock or unlock).
+ */
+ private Mode getInitialMode() {
+ final IccCard.State simState = mUpdateMonitor.getSimState();
+ if (stuckOnLockScreenBecauseSimMissing() || (simState == IccCard.State.PUK_REQUIRED)) {
+ return Mode.LockScreen;
+ } else {
+ // Show LockScreen first for any screen other than Pattern unlock.
+ final boolean usingLockPattern = mLockPatternUtils.getKeyguardStoredPasswordQuality()
+ == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+ if (isSecure() && usingLockPattern) {
+ return Mode.UnlockScreen;
+ } else {
+ return Mode.LockScreen;
+ }
+ }
+ }
+
+ /**
+ * Given the current state of things, what should the unlock screen be?
+ */
+ private UnlockMode getUnlockMode() {
+ final IccCard.State simState = mUpdateMonitor.getSimState();
+ UnlockMode currentMode;
+ if (simState == IccCard.State.PIN_REQUIRED || simState == IccCard.State.PUK_REQUIRED) {
+ currentMode = UnlockMode.SimPin;
+ } else {
+ final int mode = mLockPatternUtils.getKeyguardStoredPasswordQuality();
+ switch (mode) {
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+ currentMode = UnlockMode.Password;
+ break;
+ case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
+ case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
+ // "forgot pattern" button is only available in the pattern mode...
+ if (mForgotPattern || mLockPatternUtils.isPermanentlyLocked()) {
+ currentMode = UnlockMode.Account;
+ } else {
+ currentMode = UnlockMode.Pattern;
+ }
+ break;
+ default:
+ throw new IllegalStateException("Unknown unlock mode:" + mode);
+ }
+ }
+ return currentMode;
+ }
+
+ private void showTimeoutDialog() {
+ int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
+ String message = mContext.getString(
+ R.string.lockscreen_too_many_failed_attempts_dialog_message,
+ mUpdateMonitor.getFailedAttempts(),
+ timeoutInSeconds);
+ final AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setTitle(null)
+ .setMessage(message)
+ .setNeutralButton(R.string.ok, null)
+ .create();
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ if (!mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_sf_slowBlur)) {
+ dialog.getWindow().setFlags(
+ WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
+ WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+ }
+ dialog.show();
+ }
+
+ private void showAlmostAtAccountLoginDialog() {
+ int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
+ String message = mContext.getString(
+ R.string.lockscreen_failed_attempts_almost_glogin,
+ LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET
+ - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT,
+ LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT,
+ timeoutInSeconds);
+ final AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setTitle(null)
+ .setMessage(message)
+ .setNeutralButton(R.string.ok, null)
+ .create();
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ if (!mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_sf_slowBlur)) {
+ dialog.getWindow().setFlags(
+ WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
+ WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+ }
+ dialog.show();
+ }
+
+ /**
+ * Used to put wallpaper on the background of the lock screen. Centers it
+ * Horizontally and pins the bottom (assuming that the lock screen is aligned
+ * with the bottom, so the wallpaper should extend above the top into the
+ * status bar).
+ */
+ static private class FastBitmapDrawable extends Drawable {
+ private Bitmap mBitmap;
+ private int mOpacity;
+
+ private FastBitmapDrawable(Bitmap bitmap) {
+ mBitmap = bitmap;
+ mOpacity = mBitmap.hasAlpha() ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawBitmap(
+ mBitmap,
+ (getBounds().width() - mBitmap.getWidth()) / 2,
+ (getBounds().height() - mBitmap.getHeight()),
+ null);
+ }
+
+ @Override
+ public int getOpacity() {
+ return mOpacity;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mBitmap.getWidth();
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mBitmap.getHeight();
+ }
+
+ @Override
+ public int getMinimumWidth() {
+ return mBitmap.getWidth();
+ }
+
+ @Override
+ public int getMinimumHeight() {
+ return mBitmap.getHeight();
+ }
+ }
+}
+
diff --git a/policy/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java b/policy/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java
new file mode 100644
index 0000000..ed5a058
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import android.content.Context;
+import com.android.internal.telephony.IccCard;
+
+/**
+ * Knows how to create a lock pattern keyguard view, and answer questions about
+ * it (even if it hasn't been created, per the interface specs).
+ */
+public class LockPatternKeyguardViewProperties implements KeyguardViewProperties {
+
+ private final LockPatternUtils mLockPatternUtils;
+ private final KeyguardUpdateMonitor mUpdateMonitor;
+
+ /**
+ * @param lockPatternUtils Used to know whether the pattern enabled, and passed
+ * onto the keygaurd view when it is created.
+ * @param updateMonitor Used to know whether the sim pin is enabled, and passed
+ * onto the keyguard view when it is created.
+ */
+ public LockPatternKeyguardViewProperties(LockPatternUtils lockPatternUtils,
+ KeyguardUpdateMonitor updateMonitor) {
+ mLockPatternUtils = lockPatternUtils;
+ mUpdateMonitor = updateMonitor;
+ }
+
+ public KeyguardViewBase createKeyguardView(Context context,
+ KeyguardUpdateMonitor updateMonitor,
+ KeyguardWindowController controller) {
+ return new LockPatternKeyguardView(context, updateMonitor,
+ mLockPatternUtils, controller);
+ }
+
+ public boolean isSecure() {
+ return mLockPatternUtils.isSecure() || isSimPinSecure();
+ }
+
+ private boolean isSimPinSecure() {
+ final IccCard.State simState = mUpdateMonitor.getSimState();
+ return (simState == IccCard.State.PIN_REQUIRED || simState == IccCard.State.PUK_REQUIRED
+ || simState == IccCard.State.ABSENT);
+ }
+
+}
diff --git a/policy/com/android/internal/policy/impl/LockScreen.java b/policy/com/android/internal/policy/impl/LockScreen.java
new file mode 100644
index 0000000..baed9eb
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/LockScreen.java
@@ -0,0 +1,678 @@
+/*
+ * 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;
+
+import com.android.internal.R;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.SlidingTab;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.ColorStateList;
+import android.text.format.DateFormat;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.*;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.media.AudioManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.provider.Settings;
+
+import java.util.Date;
+import java.io.File;
+
+/**
+ * The screen within {@link LockPatternKeyguardView} that shows general
+ * information about the device depending on its state, and how to get
+ * past it, as applicable.
+ */
+class LockScreen extends LinearLayout implements KeyguardScreen, KeyguardUpdateMonitor.InfoCallback,
+ KeyguardUpdateMonitor.SimStateCallback, SlidingTab.OnTriggerListener {
+
+ private static final boolean DBG = false;
+ private static final String TAG = "LockScreen";
+ private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
+
+ private Status mStatus = Status.Normal;
+
+ private final LockPatternUtils mLockPatternUtils;
+ private final KeyguardUpdateMonitor mUpdateMonitor;
+ private final KeyguardScreenCallback mCallback;
+
+ private TextView mCarrier;
+ private SlidingTab mSelector;
+ private TextView mTime;
+ private TextView mDate;
+ private TextView mStatus1;
+ private TextView mStatus2;
+ private TextView mScreenLocked;
+ private Button mEmergencyCallButton;
+
+ // current configuration state of keyboard and display
+ private int mKeyboardHidden;
+ private int mCreationOrientation;
+
+ // 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;
+
+ private String mNextAlarm = null;
+ private Drawable mAlarmIcon = null;
+ private String mCharging = null;
+ private Drawable mChargingIcon = null;
+
+ private boolean mSilentMode;
+ private AudioManager mAudioManager;
+ private String mDateFormatString;
+ private java.text.DateFormat mTimeFormat;
+ private boolean mEnableMenuKeyInLockScreen;
+
+ /**
+ * The status of this lock screen.
+ */
+ enum Status {
+ /**
+ * 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);
+
+ private final boolean mShowStatusLines;
+
+ Status(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 showStatusLines() {
+ return mShowStatusLines;
+ }
+ }
+
+ /**
+ * In general, we enable unlocking the insecure key guard 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 boolean shouldEnableMenuKey() {
+ final Resources res = getResources();
+ final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
+ final boolean isMonkey = SystemProperties.getBoolean("ro.monkey", false);
+ final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
+ return !configDisabled || isMonkey || fileOverride;
+ }
+
+ /**
+ * @param context Used to setup the view.
+ * @param configuration The current configuration. Used to use when selecting layout, etc.
+ * @param lockPatternUtils Used to know the state of the lock pattern settings.
+ * @param updateMonitor Used to register for updates on various keyguard related
+ * state, and query the initial state at setup.
+ * @param callback Used to communicate back to the host keyguard view.
+ */
+ LockScreen(Context context, Configuration configuration, LockPatternUtils lockPatternUtils,
+ KeyguardUpdateMonitor updateMonitor,
+ KeyguardScreenCallback callback) {
+ super(context);
+ mLockPatternUtils = lockPatternUtils;
+ mUpdateMonitor = updateMonitor;
+ mCallback = callback;
+
+ mEnableMenuKeyInLockScreen = shouldEnableMenuKey();
+
+ mCreationOrientation = configuration.orientation;
+
+ mKeyboardHidden = configuration.hardKeyboardHidden;
+
+ if (LockPatternKeyguardView.DEBUG_CONFIGURATION) {
+ Log.v(TAG, "***** CREATING LOCK SCREEN", new RuntimeException());
+ Log.v(TAG, "Cur orient=" + mCreationOrientation
+ + " res orient=" + context.getResources().getConfiguration().orientation);
+ }
+
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ if (DBG) Log.v(TAG, "Creation orientation = " + mCreationOrientation);
+ if (mCreationOrientation != Configuration.ORIENTATION_LANDSCAPE) {
+ inflater.inflate(R.layout.keyguard_screen_tab_unlock, this, true);
+ } else {
+ inflater.inflate(R.layout.keyguard_screen_tab_unlock_land, this, true);
+ }
+
+ mCarrier = (TextView) findViewById(R.id.carrier);
+ // Required for Marquee to work
+ mCarrier.setSelected(true);
+ mCarrier.setTextColor(0xffffffff);
+
+ mDate = (TextView) findViewById(R.id.date);
+ mStatus1 = (TextView) findViewById(R.id.status1);
+ mStatus2 = (TextView) findViewById(R.id.status2);
+
+ mEmergencyCallButton = (Button) findViewById(R.id.emergencyCallButton);
+ mEmergencyCallButton.setText(R.string.lockscreen_emergency_call);
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
+ mScreenLocked = (TextView) findViewById(R.id.screenLocked);
+ mSelector = (SlidingTab) findViewById(R.id.tab_selector);
+ mSelector.setHoldAfterTrigger(true, false);
+ mSelector.setLeftHintText(R.string.lockscreen_unlock_label);
+ mEmergencyCallButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ mCallback.takeEmergencyCallAction();
+ }
+ });
+
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+ setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+
+ updateMonitor.registerInfoCallback(this);
+ updateMonitor.registerSimStateCallback(this);
+
+ mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+ mSilentMode = isSilentMode();
+
+ mSelector.setLeftTabResources(
+ R.drawable.ic_jog_dial_unlock,
+ R.drawable.jog_tab_target_green,
+ R.drawable.jog_tab_bar_left_unlock,
+ R.drawable.jog_tab_left_unlock);
+
+ updateRightTabResources();
+
+ mSelector.setOnTriggerListener(this);
+
+ resetStatusInfo(updateMonitor);
+ }
+
+ private boolean isSilentMode() {
+ return mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
+ }
+
+ private void updateRightTabResources() {
+ boolean vibe = mSilentMode
+ && (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE);
+
+ mSelector.setRightTabResources(
+ mSilentMode ? ( vibe ? R.drawable.ic_jog_dial_vibrate_on
+ : R.drawable.ic_jog_dial_sound_off )
+ : R.drawable.ic_jog_dial_sound_on,
+ mSilentMode ? R.drawable.jog_tab_target_yellow
+ : R.drawable.jog_tab_target_gray,
+ mSilentMode ? R.drawable.jog_tab_bar_right_sound_on
+ : R.drawable.jog_tab_bar_right_sound_off,
+ mSilentMode ? R.drawable.jog_tab_right_sound_on
+ : R.drawable.jog_tab_right_sound_off);
+ }
+
+ private void resetStatusInfo(KeyguardUpdateMonitor updateMonitor) {
+ mShowingBatteryInfo = updateMonitor.shouldShowBatteryInfo();
+ mPluggedIn = updateMonitor.isDevicePluggedIn();
+ mBatteryLevel = updateMonitor.getBatteryLevel();
+
+ mStatus = getCurrentStatus(updateMonitor.getSimState());
+ updateLayout(mStatus);
+
+ refreshBatteryStringAndIcon();
+ refreshAlarmDisplay();
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
+
+ mTimeFormat = DateFormat.getTimeFormat(getContext());
+ mDateFormatString = getContext().getString(R.string.full_wday_month_day_no_year);
+ refreshTimeAndDateDisplay();
+ updateStatusLines();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_MENU && mEnableMenuKeyInLockScreen) {
+ mCallback.goToUnlockScreen();
+ }
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public void onTrigger(View v, int whichHandle) {
+ if (whichHandle == SlidingTab.OnTriggerListener.LEFT_HANDLE) {
+ mCallback.goToUnlockScreen();
+ } else if (whichHandle == SlidingTab.OnTriggerListener.RIGHT_HANDLE) {
+ // toggle silent mode
+ mSilentMode = !mSilentMode;
+ if (mSilentMode) {
+ final boolean vibe = (Settings.System.getInt(
+ getContext().getContentResolver(),
+ Settings.System.VIBRATE_IN_SILENT, 1) == 1);
+
+ mAudioManager.setRingerMode(vibe
+ ? AudioManager.RINGER_MODE_VIBRATE
+ : AudioManager.RINGER_MODE_SILENT);
+ } else {
+ mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ }
+
+ updateRightTabResources();
+
+ String message = mSilentMode ?
+ getContext().getString(R.string.global_action_silent_mode_on_status) :
+ getContext().getString(R.string.global_action_silent_mode_off_status);
+
+ final int toastIcon = mSilentMode
+ ? R.drawable.ic_lock_ringer_off
+ : R.drawable.ic_lock_ringer_on;
+
+ final int toastColor = mSilentMode
+ ? getContext().getResources().getColor(R.color.keyguard_text_color_soundoff)
+ : getContext().getResources().getColor(R.color.keyguard_text_color_soundon);
+ toastMessage(mScreenLocked, message, toastColor, toastIcon);
+ mCallback.pokeWakelock();
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onGrabbedStateChange(View v, int grabbedState) {
+ if (grabbedState == SlidingTab.OnTriggerListener.RIGHT_HANDLE) {
+ mSilentMode = isSilentMode();
+ mSelector.setRightHintText(mSilentMode ? R.string.lockscreen_sound_on_label
+ : R.string.lockscreen_sound_off_label);
+ }
+ mCallback.pokeWakelock();
+ }
+
+ /**
+ * Displays a message in a text view and then restores the previous text.
+ * @param textView The text view.
+ * @param text The text.
+ * @param color The color to apply to the text, or 0 if the existing color should be used.
+ * @param iconResourceId The left hand icon.
+ */
+ private void toastMessage(final TextView textView, final String text, final int color, final int iconResourceId) {
+ if (DBG) android.util.Log.d("LockScreen", "toastMessage(text=" + text +", color=" + color + ")");
+
+ if (mPendingR1 != null) {
+ textView.removeCallbacks(mPendingR1);
+ mPendingR1 = null;
+ }
+ if (mPendingR2 != null) {
+ mPendingR2.run(); // fire immediately, restoring non-toasted appearance
+ textView.removeCallbacks(mPendingR2);
+ mPendingR2 = null;
+ }
+
+ final String oldText = textView.getText().toString();
+ final ColorStateList oldColors = textView.getTextColors();
+
+ mPendingR1 = new Runnable() {
+ public void run() {
+ textView.setText(text);
+ if (color != 0) {
+ textView.setTextColor(color);
+ }
+ textView.setCompoundDrawablesWithIntrinsicBounds(iconResourceId, 0, 0, 0);
+ }
+ };
+
+ textView.postDelayed(mPendingR1, 0);
+ mPendingR2 = new Runnable() {
+ public void run() {
+ textView.setText(oldText);
+ textView.setTextColor(oldColors);
+ textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
+ }
+ };
+ textView.postDelayed(mPendingR2, 3500);
+ }
+ private Runnable mPendingR1;
+ private Runnable mPendingR2;
+
+ private void refreshAlarmDisplay() {
+ mNextAlarm = mLockPatternUtils.getNextAlarm();
+ if (mNextAlarm != null) {
+ mAlarmIcon = getContext().getResources().getDrawable(R.drawable.ic_lock_idle_alarm);
+ }
+ updateStatusLines();
+ }
+
+ /** {@inheritDoc} */
+ public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn,
+ int batteryLevel) {
+ if (DBG) Log.d(TAG, "onRefreshBatteryInfo(" + showBatteryInfo + ", " + pluggedIn + ")");
+ mShowingBatteryInfo = showBatteryInfo;
+ mPluggedIn = pluggedIn;
+ mBatteryLevel = batteryLevel;
+
+ refreshBatteryStringAndIcon();
+ updateStatusLines();
+ }
+
+ private void refreshBatteryStringAndIcon() {
+ if (!mShowingBatteryInfo) {
+ mCharging = null;
+ return;
+ }
+
+ if (mChargingIcon == null) {
+ mChargingIcon =
+ getContext().getResources().getDrawable(R.drawable.ic_lock_idle_charging);
+ }
+
+ if (mPluggedIn) {
+ if (mBatteryLevel >= 100) {
+ mCharging = getContext().getString(R.string.lockscreen_charged);
+ } else {
+ mCharging = getContext().getString(R.string.lockscreen_plugged_in, mBatteryLevel);
+ }
+ } else {
+ mCharging = getContext().getString(R.string.lockscreen_low_battery);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onTimeChanged() {
+ refreshTimeAndDateDisplay();
+ }
+
+ private void refreshTimeAndDateDisplay() {
+ mDate.setText(DateFormat.format(mDateFormatString, new Date()));
+ }
+
+ private void updateStatusLines() {
+ if (!mStatus.showStatusLines()
+ || (mCharging == null && mNextAlarm == null)) {
+ mStatus1.setVisibility(View.INVISIBLE);
+ mStatus2.setVisibility(View.INVISIBLE);
+ } else if (mCharging != null && mNextAlarm == null) {
+ // charging only
+ mStatus1.setVisibility(View.VISIBLE);
+ mStatus2.setVisibility(View.INVISIBLE);
+
+ mStatus1.setText(mCharging);
+ mStatus1.setCompoundDrawablesWithIntrinsicBounds(mChargingIcon, null, null, null);
+ } else if (mNextAlarm != null && mCharging == null) {
+ // next alarm only
+ mStatus1.setVisibility(View.VISIBLE);
+ mStatus2.setVisibility(View.INVISIBLE);
+
+ mStatus1.setText(mNextAlarm);
+ mStatus1.setCompoundDrawablesWithIntrinsicBounds(mAlarmIcon, null, null, null);
+ } else if (mCharging != null && mNextAlarm != null) {
+ // both charging and next alarm
+ mStatus1.setVisibility(View.VISIBLE);
+ mStatus2.setVisibility(View.VISIBLE);
+
+ mStatus1.setText(mCharging);
+ mStatus1.setCompoundDrawablesWithIntrinsicBounds(mChargingIcon, null, null, null);
+ mStatus2.setText(mNextAlarm);
+ mStatus2.setCompoundDrawablesWithIntrinsicBounds(mAlarmIcon, null, null, null);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
+ if (DBG) Log.d(TAG, "onRefreshCarrierInfo(" + plmn + ", " + spn + ")");
+ updateLayout(mStatus);
+ }
+
+ /**
+ * Determine the current status of the lock screen given the sim state and other stuff.
+ */
+ private Status getCurrentStatus(IccCard.State simState) {
+ boolean missingAndNotProvisioned = (!mUpdateMonitor.isDeviceProvisioned()
+ && simState == IccCard.State.ABSENT);
+ if (missingAndNotProvisioned) {
+ return Status.SimMissingLocked;
+ }
+
+ switch (simState) {
+ case ABSENT:
+ return Status.SimMissing;
+ case NETWORK_LOCKED:
+ return Status.SimMissingLocked;
+ case NOT_READY:
+ return Status.SimMissing;
+ case PIN_REQUIRED:
+ return Status.SimLocked;
+ case PUK_REQUIRED:
+ return Status.SimPukLocked;
+ case READY:
+ return Status.Normal;
+ case UNKNOWN:
+ return Status.SimMissing;
+ }
+ return Status.SimMissing;
+ }
+
+ /**
+ * Update the layout to match the current status.
+ */
+ private void updateLayout(Status status) {
+ // The emergency call button appears where the carrier would
+ // ordinarily be shown, so if one is VISIBLE the other must be
+ // INVISIBLE.
+ switch (status) {
+ case Normal:
+ // text
+ mCarrier.setText(
+ getCarrierString(
+ mUpdateMonitor.getTelephonyPlmn(),
+ mUpdateMonitor.getTelephonySpn()));
+// mScreenLocked.setText(R.string.lockscreen_screen_locked);
+
+ // layout
+ mScreenLocked.setVisibility(View.VISIBLE);
+ mSelector.setVisibility(View.VISIBLE);
+ mEmergencyCallButton.setVisibility(View.GONE);
+ break;
+ case NetworkLocked:
+ // The carrier string shows both sim card status (i.e. No Sim Card) and
+ // carrier's name and/or "Emergency Calls Only" status
+ mCarrier.setText(
+ getCarrierString(
+ mUpdateMonitor.getTelephonyPlmn(),
+ getContext().getText(R.string.lockscreen_network_locked_message)));
+ mScreenLocked.setText(R.string.lockscreen_instructions_when_pattern_disabled);
+
+ // layout
+ mScreenLocked.setVisibility(View.VISIBLE);
+ mSelector.setVisibility(View.VISIBLE);
+ mEmergencyCallButton.setVisibility(View.GONE);
+ break;
+ case SimMissing:
+ // text
+ mCarrier.setText("");
+ mScreenLocked.setText(
+ getCarrierString(
+ mUpdateMonitor.getTelephonyPlmn(),
+ getContext().getText(R.string.lockscreen_missing_sim_message_short)));
+ // previously shown here: lockscreen_instructions_when_pattern_disabled
+
+ // layout
+ mScreenLocked.setVisibility(View.VISIBLE);
+ mSelector.setVisibility(View.VISIBLE);
+ mEmergencyCallButton.setVisibility(View.VISIBLE);
+ break;
+ case SimMissingLocked:
+ // text
+ mCarrier.setText("");
+ mScreenLocked.setText(
+ getCarrierString(
+ mUpdateMonitor.getTelephonyPlmn(),
+ getContext().getText(R.string.lockscreen_missing_sim_message_short)));
+ // previously shown here: lockscreen_missing_sim_instructions
+
+ // layout
+ mScreenLocked.setVisibility(View.VISIBLE);
+ mSelector.setVisibility(View.GONE);
+ mEmergencyCallButton.setVisibility(View.VISIBLE);
+ break;
+ case SimLocked:
+ // text
+ mCarrier.setText(
+ getCarrierString(
+ mUpdateMonitor.getTelephonyPlmn(),
+ getContext().getText(R.string.lockscreen_sim_locked_message)));
+
+ // layout
+ mScreenLocked.setVisibility(View.INVISIBLE);
+ mSelector.setVisibility(View.VISIBLE);
+ mEmergencyCallButton.setVisibility(View.GONE);
+ break;
+ case SimPukLocked:
+ // text
+ mCarrier.setText("");
+ mScreenLocked.setText(
+ getCarrierString(
+ mUpdateMonitor.getTelephonyPlmn(),
+ getContext().getText(R.string.lockscreen_sim_puk_locked_message)));
+ // previously shown here: lockscreen_sim_puk_locked_instructions);
+
+ // layout
+ mScreenLocked.setVisibility(View.VISIBLE);
+ mSelector.setVisibility(View.GONE);
+ mEmergencyCallButton.setVisibility(View.VISIBLE);
+ break;
+ }
+ }
+
+ static CharSequence getCarrierString(CharSequence telephonyPlmn, CharSequence telephonySpn) {
+ if (telephonyPlmn != null && telephonySpn == null) {
+ return telephonyPlmn;
+ } else if (telephonyPlmn != null && telephonySpn != null) {
+ return telephonyPlmn + "|" + telephonySpn;
+ } else if (telephonyPlmn == null && telephonySpn != null) {
+ return telephonySpn;
+ } else {
+ return "";
+ }
+ }
+
+ public void onSimStateChanged(IccCard.State simState) {
+ if (DBG) Log.d(TAG, "onSimStateChanged(" + simState + ")");
+ mStatus = getCurrentStatus(simState);
+ updateLayout(mStatus);
+ updateStatusLines();
+ }
+
+ void updateConfiguration() {
+ Configuration newConfig = getResources().getConfiguration();
+ if (newConfig.orientation != mCreationOrientation) {
+ mCallback.recreateMe(newConfig);
+ } else if (newConfig.hardKeyboardHidden != mKeyboardHidden) {
+ mKeyboardHidden = newConfig.hardKeyboardHidden;
+ final boolean isKeyboardOpen = mKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
+ if (mUpdateMonitor.isKeyguardBypassEnabled() && isKeyboardOpen) {
+ mCallback.goToUnlockScreen();
+ }
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (LockPatternKeyguardView.DEBUG_CONFIGURATION) {
+ Log.v(TAG, "***** LOCK ATTACHED TO WINDOW");
+ Log.v(TAG, "Cur orient=" + mCreationOrientation
+ + ", new config=" + getResources().getConfiguration());
+ }
+ updateConfiguration();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (LockPatternKeyguardView.DEBUG_CONFIGURATION) {
+ Log.w(TAG, "***** LOCK CONFIG CHANGING", new RuntimeException());
+ Log.v(TAG, "Cur orient=" + mCreationOrientation
+ + ", new config=" + newConfig);
+ }
+ updateConfiguration();
+ }
+
+ /** {@inheritDoc} */
+ public boolean needsInput() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public void onPause() {
+
+ }
+
+ /** {@inheritDoc} */
+ public void onResume() {
+ resetStatusInfo(mUpdateMonitor);
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
+ }
+
+ /** {@inheritDoc} */
+ public void cleanUp() {
+ mUpdateMonitor.removeCallback(this);
+ }
+
+ /** {@inheritDoc} */
+ public void onRingerModeChanged(int state) {
+ boolean silent = AudioManager.RINGER_MODE_NORMAL != state;
+ if (silent != mSilentMode) {
+ mSilentMode = silent;
+ updateRightTabResources();
+ }
+ }
+
+ public void onPhoneStateChanged(String newState) {
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
+ }
+}
diff --git a/policy/com/android/internal/policy/impl/PasswordUnlockScreen.java b/policy/com/android/internal/policy/impl/PasswordUnlockScreen.java
new file mode 100644
index 0000000..a0e4f93
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/PasswordUnlockScreen.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import com.android.internal.policy.impl.PatternUnlockScreen.FooterMode;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.PasswordEntryKeyboardView;
+
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.telephony.TelephonyManager;
+import android.text.method.DigitsKeyListener;
+import android.text.method.TextKeyListener;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import com.android.internal.R;
+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 PasswordUnlockScreen extends LinearLayout implements KeyguardScreen,
+ View.OnClickListener, KeyguardUpdateMonitor.InfoCallback, OnEditorActionListener {
+
+ private final KeyguardUpdateMonitor mUpdateMonitor;
+ private final KeyguardScreenCallback mCallback;
+
+ private EditText mPasswordEntry;
+ private Button mEmergencyCallButton;
+ private LockPatternUtils mLockPatternUtils;
+ private PasswordEntryKeyboardView mKeyboardView;
+ private PasswordEntryKeyboardHelper mKeyboardHelper;
+
+ private int mCreationOrientation;
+ private int mKeyboardHidden;
+ private CountDownTimer mCountdownTimer;
+ private TextView mTitle;
+
+ // 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 PasswordUnlockScreen(Context context, Configuration configuration,
+ LockPatternUtils lockPatternUtils, KeyguardUpdateMonitor updateMonitor,
+ KeyguardScreenCallback callback) {
+ super(context);
+
+ mKeyboardHidden = configuration.hardKeyboardHidden;
+ mCreationOrientation = configuration.orientation;
+ mUpdateMonitor = updateMonitor;
+ mCallback = callback;
+ mLockPatternUtils = lockPatternUtils;
+
+ LayoutInflater layoutInflater = LayoutInflater.from(context);
+ if (mCreationOrientation != Configuration.ORIENTATION_LANDSCAPE) {
+ layoutInflater.inflate(R.layout.keyguard_screen_password_portrait, this, true);
+ } else {
+ layoutInflater.inflate(R.layout.keyguard_screen_password_landscape, this, true);
+ }
+
+ final int quality = lockPatternUtils.getKeyguardStoredPasswordQuality();
+ final boolean isAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == quality
+ || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == quality;
+
+ mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
+ mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
+ mPasswordEntry.setOnEditorActionListener(this);
+ mEmergencyCallButton = (Button) findViewById(R.id.emergencyCall);
+ mEmergencyCallButton.setOnClickListener(this);
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
+ mTitle = (TextView) findViewById(R.id.enter_password_label);
+
+ mKeyboardHelper = new PasswordEntryKeyboardHelper(context, mKeyboardView, this);
+ mKeyboardHelper.setKeyboardMode(isAlpha ? PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA
+ : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
+
+ mKeyboardView.setVisibility(mKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO
+ ? View.INVISIBLE : View.VISIBLE);
+ mPasswordEntry.requestFocus();
+
+ // This allows keyboards with overlapping qwerty/numeric keys to choose just the
+ // numeric keys.
+ if (isAlpha) {
+ mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
+ } else {
+ mPasswordEntry.setKeyListener(DigitsKeyListener.getInstance());
+ }
+
+ mKeyboardHelper.setVibratePattern(mLockPatternUtils.isTactileFeedbackEnabled() ?
+ com.android.internal.R.array.config_virtualKeyVibePattern : 0);
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ // send focus to the password field
+ return mPasswordEntry.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ /** {@inheritDoc} */
+ public boolean needsInput() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public void onPause() {
+
+ }
+
+ /** {@inheritDoc} */
+ public void onResume() {
+ // start fresh
+ mPasswordEntry.setText("");
+ mPasswordEntry.requestFocus();
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
+
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
+ if (deadline != 0) {
+ handleAttemptLockout(deadline);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void cleanUp() {
+ mUpdateMonitor.removeCallback(this);
+ }
+
+ public void onClick(View v) {
+ if (v == mEmergencyCallButton) {
+ mCallback.takeEmergencyCallAction();
+ }
+ mCallback.pokeWakelock();
+ }
+
+ private void verifyPasswordAndUnlock() {
+ String entry = mPasswordEntry.getText().toString();
+ if (mLockPatternUtils.checkPassword(entry)) {
+ mCallback.keyguardDone(true);
+ mCallback.reportSuccessfulUnlockAttempt();
+ } 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 == (mUpdateMonitor.getFailedAttempts()
+ % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
+ long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
+ handleAttemptLockout(deadline);
+ }
+ }
+ 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();
+ mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ int secondsRemaining = (int) (millisUntilFinished / 1000);
+ String instructions = getContext().getString(
+ R.string.lockscreen_too_many_failed_attempts_countdown,
+ secondsRemaining);
+ mTitle.setText(instructions);
+ }
+
+ @Override
+ public void onFinish() {
+ mPasswordEntry.setEnabled(true);
+ mTitle.setText(R.string.keyguard_password_enter_password_code);
+ mKeyboardView.setEnabled(true);
+ }
+ }.start();
+ }
+
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ mCallback.pokeWakelock();
+ return false;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (getResources().getConfiguration().orientation != mCreationOrientation) {
+ mCallback.recreateMe(getResources().getConfiguration());
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (newConfig.orientation != mCreationOrientation) {
+ mCallback.recreateMe(newConfig);
+ }
+ }
+
+ public void onKeyboardChange(boolean isKeyboardOpen) {
+ // Don't show the soft keyboard when the real keyboard is open
+ mKeyboardView.setVisibility(isKeyboardOpen ? View.INVISIBLE : View.VISIBLE);
+ }
+
+ 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) {
+ verifyPasswordAndUnlock();
+ return true;
+ }
+ return false;
+ }
+
+ public void onPhoneStateChanged(String newState) {
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
+ }
+
+ public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) {
+
+ }
+
+ public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
+
+ }
+
+ public void onRingerModeChanged(int state) {
+
+ }
+
+ public void onTimeChanged() {
+
+ }
+
+}
diff --git a/policy/com/android/internal/policy/impl/PatternUnlockScreen.java b/policy/com/android/internal/policy/impl/PatternUnlockScreen.java
new file mode 100644
index 0000000..418e243
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/PatternUnlockScreen.java
@@ -0,0 +1,580 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.MotionEvent;
+import android.widget.Button;
+import android.widget.TextView;
+import android.text.format.DateFormat;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.internal.R;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockPatternView.Cell;
+
+import java.util.List;
+import java.util.Date;
+
+/**
+ * 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.
+ */
+class PatternUnlockScreen extends LinearLayoutWithDefaultTouchRecepient
+ implements KeyguardScreen, KeyguardUpdateMonitor.InfoCallback,
+ KeyguardUpdateMonitor.SimStateCallback {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "UnlockScreen";
+
+ // 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 final LockPatternUtils mLockPatternUtils;
+ private final KeyguardUpdateMonitor mUpdateMonitor;
+ private final KeyguardScreenCallback mCallback;
+
+ /**
+ * whether there is a fallback option available when the pattern is forgotten.
+ */
+ private boolean mEnableFallback;
+
+ private String mDateFormatString;
+
+ private TextView mCarrier;
+ private TextView mDate;
+
+ // 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;
+
+ private String mNextAlarm = null;
+
+ private String mInstructions = null;
+ private TextView mStatus1;
+ private TextView mStatusSep;
+ private TextView mStatus2;
+
+
+ private LockPatternView mLockPatternView;
+
+ private ViewGroup mFooterNormal;
+ private ViewGroup mFooterForgotPattern;
+
+ /**
+ * Keeps track of the last time we poked the wake lock during dispatching
+ * of the touch event, initalized to something gauranteed to make us
+ * poke it 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();
+ }
+ };
+
+ private Button mForgotPatternButton;
+ private Button mEmergencyAlone;
+ private Button mEmergencyTogether;
+ private int mCreationOrientation;
+
+ enum FooterMode {
+ Normal,
+ ForgotLockPattern,
+ VerifyUnlocked
+ }
+
+ private void updateFooter(FooterMode mode) {
+ switch (mode) {
+ case Normal:
+ mFooterNormal.setVisibility(View.VISIBLE);
+ mFooterForgotPattern.setVisibility(View.GONE);
+ break;
+ case ForgotLockPattern:
+ mFooterNormal.setVisibility(View.GONE);
+ mFooterForgotPattern.setVisibility(View.VISIBLE);
+ mForgotPatternButton.setVisibility(View.VISIBLE);
+ break;
+ case VerifyUnlocked:
+ mFooterNormal.setVisibility(View.GONE);
+ mFooterForgotPattern.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * @param context The context.
+ * @param configuration
+ * @param lockPatternUtils Used to lookup lock pattern settings.
+ * @param updateMonitor Used to lookup state affecting keyguard.
+ * @param callback Used to notify the manager when we're done, etc.
+ * @param totalFailedAttempts The current number of failed attempts.
+ * @param enableFallback True if a backup unlock option is available when the user has forgotten
+ * their pattern (e.g they have a google account so we can show them the account based
+ * backup option).
+ */
+ PatternUnlockScreen(Context context,
+ Configuration configuration, LockPatternUtils lockPatternUtils,
+ KeyguardUpdateMonitor updateMonitor,
+ KeyguardScreenCallback callback,
+ int totalFailedAttempts) {
+ super(context);
+ mLockPatternUtils = lockPatternUtils;
+ mUpdateMonitor = updateMonitor;
+ mCallback = callback;
+ mTotalFailedPatternAttempts = totalFailedAttempts;
+ mFailedPatternAttemptsSinceLastTimeout =
+ totalFailedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT;
+
+ if (DEBUG) Log.d(TAG,
+ "UnlockScreen() ctor: totalFailedAttempts="
+ + totalFailedAttempts + ", mFailedPat...="
+ + mFailedPatternAttemptsSinceLastTimeout
+ );
+
+ mCreationOrientation = configuration.orientation;
+
+ LayoutInflater inflater = LayoutInflater.from(context);
+ if (mCreationOrientation != Configuration.ORIENTATION_LANDSCAPE) {
+ inflater.inflate(R.layout.keyguard_screen_unlock_portrait, this, true);
+ } else {
+ inflater.inflate(R.layout.keyguard_screen_unlock_landscape, this, true);
+ }
+
+ mCarrier = (TextView) findViewById(R.id.carrier);
+ mDate = (TextView) findViewById(R.id.date);
+
+ mDateFormatString = getContext().getString(R.string.full_wday_month_day_no_year);
+ refreshTimeAndDateDisplay();
+
+ mStatus1 = (TextView) findViewById(R.id.status1);
+ mStatusSep = (TextView) findViewById(R.id.statusSep);
+ mStatus2 = (TextView) findViewById(R.id.status2);
+
+ resetStatusInfo();
+
+
+ mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
+
+ mFooterNormal = (ViewGroup) findViewById(R.id.footerNormal);
+ mFooterForgotPattern = (ViewGroup) findViewById(R.id.footerForgotPattern);
+
+ // emergency call buttons
+ final OnClickListener emergencyClick = new OnClickListener() {
+ public void onClick(View v) {
+ mCallback.takeEmergencyCallAction();
+ }
+ };
+
+ mEmergencyAlone = (Button) findViewById(R.id.emergencyCallAlone);
+ mEmergencyAlone.setFocusable(false); // touch only!
+ mEmergencyAlone.setOnClickListener(emergencyClick);
+ mEmergencyTogether = (Button) findViewById(R.id.emergencyCallTogether);
+ mEmergencyTogether.setFocusable(false);
+ mEmergencyTogether.setOnClickListener(emergencyClick);
+ refreshEmergencyButtonText();
+
+ mForgotPatternButton = (Button) findViewById(R.id.forgotPattern);
+ mForgotPatternButton.setText(R.string.lockscreen_forgot_pattern_button_text);
+ mForgotPatternButton.setOnClickListener(new OnClickListener() {
+
+ public void onClick(View v) {
+ mCallback.forgotPattern(true);
+ }
+ });
+
+ // make it so unhandled touch events within the unlock screen go to the
+ // lock pattern view.
+ setDefaultTouchRecepient(mLockPatternView);
+
+ 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());
+
+ // assume normal footer mode for now
+ updateFooter(FooterMode.Normal);
+
+ updateMonitor.registerInfoCallback(this);
+ updateMonitor.registerSimStateCallback(this);
+ setFocusableInTouchMode(true);
+
+ // Required to get Marquee to work.
+ mCarrier.setSelected(true);
+ mCarrier.setTextColor(0xffffffff);
+
+ // until we get an update...
+ mCarrier.setText(
+ LockScreen.getCarrierString(
+ mUpdateMonitor.getTelephonyPlmn(),
+ mUpdateMonitor.getTelephonySpn()));
+ }
+
+ private void refreshEmergencyButtonText() {
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyAlone);
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyTogether);
+ }
+
+ public void setEnableFallback(boolean state) {
+ if (DEBUG) Log.d(TAG, "setEnableFallback(" + state + ")");
+ mEnableFallback = state;
+ }
+
+ private void resetStatusInfo() {
+ mInstructions = null;
+ mShowingBatteryInfo = mUpdateMonitor.shouldShowBatteryInfo();
+ mPluggedIn = mUpdateMonitor.isDevicePluggedIn();
+ mBatteryLevel = mUpdateMonitor.getBatteryLevel();
+ mNextAlarm = mLockPatternUtils.getNextAlarm();
+ updateStatusLines();
+ }
+
+ private void updateStatusLines() {
+ if (mInstructions != null) {
+ // instructions only
+ mStatus1.setText(mInstructions);
+ if (TextUtils.isEmpty(mInstructions)) {
+ mStatus1.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
+ } else {
+ mStatus1.setCompoundDrawablesWithIntrinsicBounds(
+ R.drawable.ic_lock_idle_lock, 0, 0, 0);
+ }
+
+ mStatus1.setVisibility(View.VISIBLE);
+ mStatusSep.setVisibility(View.GONE);
+ mStatus2.setVisibility(View.GONE);
+ } else if (mShowingBatteryInfo && mNextAlarm == null) {
+ // battery only
+ if (mPluggedIn) {
+ if (mBatteryLevel >= 100) {
+ mStatus1.setText(getContext().getString(R.string.lockscreen_charged));
+ } else {
+ mStatus1.setText(getContext().getString(R.string.lockscreen_plugged_in, mBatteryLevel));
+ }
+ } else {
+ mStatus1.setText(getContext().getString(R.string.lockscreen_low_battery));
+ }
+ mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_charging, 0, 0, 0);
+
+ mStatus1.setVisibility(View.VISIBLE);
+ mStatusSep.setVisibility(View.GONE);
+ mStatus2.setVisibility(View.GONE);
+
+ } else if (mNextAlarm != null && !mShowingBatteryInfo) {
+ // alarm only
+ mStatus1.setText(mNextAlarm);
+ mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_alarm, 0, 0, 0);
+
+ mStatus1.setVisibility(View.VISIBLE);
+ mStatusSep.setVisibility(View.GONE);
+ mStatus2.setVisibility(View.GONE);
+ } else if (mNextAlarm != null && mShowingBatteryInfo) {
+ // both battery and next alarm
+ mStatus1.setText(mNextAlarm);
+ mStatusSep.setText("|");
+ mStatus2.setText(getContext().getString(
+ R.string.lockscreen_battery_short,
+ Math.min(100, mBatteryLevel)));
+ mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_alarm, 0, 0, 0);
+ if (mPluggedIn) {
+ mStatus2.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_charging, 0, 0, 0);
+ } else {
+ mStatus2.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
+ }
+
+ mStatus1.setVisibility(View.VISIBLE);
+ mStatusSep.setVisibility(View.VISIBLE);
+ mStatus2.setVisibility(View.VISIBLE);
+ } else {
+ // nothing specific to show; show general instructions
+ mStatus1.setText(R.string.lockscreen_pattern_instructions);
+ mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_lock, 0, 0, 0);
+
+ mStatus1.setVisibility(View.VISIBLE);
+ mStatusSep.setVisibility(View.GONE);
+ mStatus2.setVisibility(View.GONE);
+ }
+ }
+
+
+ private void refreshTimeAndDateDisplay() {
+ mDate.setText(DateFormat.format(mDateFormatString, new Date()));
+ }
+
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent 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 boolean result = super.dispatchTouchEvent(ev);
+ if (result &&
+ ((SystemClock.elapsedRealtime() - mLastPokeTime)
+ > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) {
+ mLastPokeTime = SystemClock.elapsedRealtime();
+ }
+ return result;
+ }
+
+
+ // ---------- InfoCallback
+
+ /** {@inheritDoc} */
+ public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) {
+ mShowingBatteryInfo = showBatteryInfo;
+ mPluggedIn = pluggedIn;
+ mBatteryLevel = batteryLevel;
+ updateStatusLines();
+ }
+
+ /** {@inheritDoc} */
+ public void onTimeChanged() {
+ refreshTimeAndDateDisplay();
+ }
+
+ /** {@inheritDoc} */
+ public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
+ mCarrier.setText(LockScreen.getCarrierString(plmn, spn));
+ }
+
+ /** {@inheritDoc} */
+ public void onRingerModeChanged(int state) {
+ // not currently used
+ }
+
+ // ---------- SimStateCallback
+
+ /** {@inheritDoc} */
+ public void onSimStateChanged(IccCard.State simState) {
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (LockPatternKeyguardView.DEBUG_CONFIGURATION) {
+ Log.v(TAG, "***** PATTERN ATTACHED TO WINDOW");
+ Log.v(TAG, "Cur orient=" + mCreationOrientation
+ + ", new config=" + getResources().getConfiguration());
+ }
+ if (getResources().getConfiguration().orientation != mCreationOrientation) {
+ mCallback.recreateMe(getResources().getConfiguration());
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (LockPatternKeyguardView.DEBUG_CONFIGURATION) {
+ Log.v(TAG, "***** PATTERN CONFIGURATION CHANGED");
+ Log.v(TAG, "Cur orient=" + mCreationOrientation
+ + ", new config=" + getResources().getConfiguration());
+ }
+ if (newConfig.orientation != mCreationOrientation) {
+ mCallback.recreateMe(newConfig);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onKeyboardChange(boolean isKeyboardOpen) {}
+
+ /** {@inheritDoc} */
+ public boolean needsInput() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public void onPause() {
+ if (mCountdownTimer != null) {
+ mCountdownTimer.cancel();
+ mCountdownTimer = null;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onResume() {
+ // reset header
+ resetStatusInfo();
+
+ // reset lock pattern
+ mLockPatternView.enableInput();
+ mLockPatternView.setEnabled(true);
+ mLockPatternView.clearPattern();
+
+ // show "forgot pattern?" button if we have an alternate authentication method
+ mForgotPatternButton.setVisibility(mCallback.doesFallbackUnlockScreenExist()
+ ? View.VISIBLE : View.INVISIBLE);
+
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
+ if (deadline != 0) {
+ handleAttemptLockout(deadline);
+ }
+
+ // 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);
+ }
+
+ refreshEmergencyButtonText();
+ }
+
+ /** {@inheritDoc} */
+ public void cleanUp() {
+ mUpdateMonitor.removeCallback(this);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (hasWindowFocus) {
+ // when timeout dialog closes we want to update our state
+ onResume();
+ }
+ }
+
+ private class UnlockPatternListener
+ implements LockPatternView.OnPatternListener {
+
+ public void onPatternStart() {
+ mLockPatternView.removeCallbacks(mCancelPatternRunnable);
+ }
+
+ public void onPatternCleared() {
+ }
+
+ public void onPatternCellAdded(List<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.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
+ } else {
+ // Give just a little extra time if they hit one of the first few dots
+ mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS);
+ }
+ }
+
+ public void onPatternDetected(List<LockPatternView.Cell> pattern) {
+ if (mLockPatternUtils.checkPattern(pattern)) {
+ mLockPatternView
+ .setDisplayMode(LockPatternView.DisplayMode.Correct);
+ mInstructions = "";
+ updateStatusLines();
+ mCallback.keyguardDone(true);
+ mCallback.reportSuccessfulUnlockAttempt();
+ } else {
+ if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
+ mCallback.pokeWakelock(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 {
+ // TODO mUnlockIcon.setVisibility(View.VISIBLE);
+ mInstructions = getContext().getString(R.string.lockscreen_pattern_wrong);
+ updateStatusLines();
+ mLockPatternView.postDelayed(
+ mCancelPatternRunnable,
+ PATTERN_CLEAR_TIMEOUT_MS);
+ }
+ }
+ }
+ }
+
+ private void handleAttemptLockout(long elapsedRealtimeDeadline) {
+ mLockPatternView.clearPattern();
+ mLockPatternView.setEnabled(false);
+ long elapsedRealtime = SystemClock.elapsedRealtime();
+ mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ int secondsRemaining = (int) (millisUntilFinished / 1000);
+ mInstructions = getContext().getString(
+ R.string.lockscreen_too_many_failed_attempts_countdown,
+ secondsRemaining);
+ updateStatusLines();
+ }
+
+ @Override
+ public void onFinish() {
+ mLockPatternView.setEnabled(true);
+ mInstructions = getContext().getString(R.string.lockscreen_pattern_instructions);
+ updateStatusLines();
+ // TODO mUnlockIcon.setVisibility(View.VISIBLE);
+ mFailedPatternAttemptsSinceLastTimeout = 0;
+ if (mEnableFallback) {
+ updateFooter(FooterMode.ForgotLockPattern);
+ } else {
+ updateFooter(FooterMode.Normal);
+ }
+ }
+ }.start();
+ }
+
+ public void onPhoneStateChanged(String newState) {
+ refreshEmergencyButtonText();
+ }
+}
diff --git a/policy/com/android/internal/policy/impl/PhoneLayoutInflater.java b/policy/com/android/internal/policy/impl/PhoneLayoutInflater.java
new file mode 100644
index 0000000..6bf4beb
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/PhoneLayoutInflater.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl;
+
+import java.util.Map;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.LayoutInflater;
+
+public class PhoneLayoutInflater extends LayoutInflater {
+ private static final String[] sClassPrefixList = {
+ "android.widget.",
+ "android.webkit."
+ };
+
+ /**
+ * Instead of instantiating directly, you should retrieve an instance
+ * through {@link Context#getSystemService}
+ *
+ * @param context The Context in which in which to find resources and other
+ * application-specific things.
+ *
+ * @see Context#getSystemService
+ */
+ public PhoneLayoutInflater(Context context) {
+ super(context);
+ }
+
+ protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
+ super(original, newContext);
+ }
+
+ /** Override onCreateView to instantiate names that correspond to the
+ widgets known to the Widget factory. If we don't find a match,
+ call through to our super class.
+ */
+ @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
+ for (String prefix : sClassPrefixList) {
+ try {
+ View view = createView(name, prefix, attrs);
+ if (view != null) {
+ return view;
+ }
+ } catch (ClassNotFoundException e) {
+ // In this case we want to let the base class take a crack
+ // at it.
+ }
+ }
+
+ return super.onCreateView(name, attrs);
+ }
+
+ public LayoutInflater cloneInContext(Context newContext) {
+ return new PhoneLayoutInflater(this, newContext);
+ }
+}
+
diff --git a/policy/com/android/internal/policy/impl/PhoneWindow.java b/policy/com/android/internal/policy/impl/PhoneWindow.java
new file mode 100644
index 0000000..5592b6d
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/PhoneWindow.java
@@ -0,0 +1,2769 @@
+/*
+ *
+ * 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;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+
+import com.android.internal.view.menu.ContextMenuBuilder;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuDialogHelper;
+import com.android.internal.view.menu.MenuView;
+import com.android.internal.view.menu.SubMenuBuilder;
+
+import android.app.KeyguardManager;
+import android.app.SearchManager;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.telephony.TelephonyManager;
+import android.util.AndroidRuntimeException;
+import android.util.Config;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewManager;
+import android.view.VolumePanel;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+/**
+ * Android-specific Window.
+ * <p>
+ * todo: need to pull the generic functionality out into a base class
+ * in android.widget.
+ */
+public class PhoneWindow extends Window implements MenuBuilder.Callback {
+
+ private final static String TAG = "PhoneWindow";
+
+ private final static boolean SWEEP_OPEN_MENU = false;
+
+ /**
+ * Simple callback used by the context menu and its submenus. The options
+ * menu submenus do not use this (their behavior is more complex).
+ */
+ ContextMenuCallback mContextMenuCallback = new ContextMenuCallback(FEATURE_CONTEXT_MENU);
+
+ // This is the top-level view of the window, containing the window decor.
+ private DecorView mDecor;
+
+ // This is the view in which the window contents are placed. It is either
+ // mDecor itself, or a child of mDecor where the contents go.
+ private ViewGroup mContentParent;
+
+ private boolean mIsFloating;
+
+ private LayoutInflater mLayoutInflater;
+
+ private TextView mTitleView;
+
+ private DrawableFeatureState[] mDrawables;
+
+ private PanelFeatureState[] mPanels;
+
+ /**
+ * The panel that is prepared or opened (the most recent one if there are
+ * multiple panels). Shortcuts will go to this panel. It gets set in
+ * {@link #preparePanel} and cleared in {@link #closePanel}.
+ */
+ private PanelFeatureState mPreparedPanel;
+
+ /**
+ * The keycode that is currently held down (as a modifier) for chording. If
+ * this is 0, there is no key held down.
+ */
+ private int mPanelChordingKey;
+ private boolean mPanelMayLongPress;
+
+ private ImageView mLeftIconView;
+
+ private ImageView mRightIconView;
+
+ private ProgressBar mCircularProgressBar;
+
+ private ProgressBar mHorizontalProgressBar;
+
+ private int mBackgroundResource = 0;
+
+ private Drawable mBackgroundDrawable;
+
+ private int mFrameResource = 0;
+
+ private int mTextColor = 0;
+
+ private CharSequence mTitle = null;
+
+ private int mTitleColor = 0;
+
+ private ContextMenuBuilder mContextMenu;
+ private MenuDialogHelper mContextMenuHelper;
+
+ private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
+ private long mVolumeKeyUpTime;
+
+ private KeyguardManager mKeyguardManager = null;
+
+ private SearchManager mSearchManager = null;
+
+ private TelephonyManager mTelephonyManager = null;
+
+ public PhoneWindow(Context context) {
+ super(context);
+ mLayoutInflater = LayoutInflater.from(context);
+ }
+
+ @Override
+ public final void setContainer(Window container) {
+ super.setContainer(container);
+ }
+
+ @Override
+ public boolean requestFeature(int featureId) {
+ if (mContentParent != null) {
+ throw new AndroidRuntimeException("requestFeature() must be called before adding content");
+ }
+ final int features = getFeatures();
+ if ((features != DEFAULT_FEATURES) && (featureId == FEATURE_CUSTOM_TITLE)) {
+
+ /* Another feature is enabled and the user is trying to enable the custom title feature */
+ throw new AndroidRuntimeException("You cannot combine custom titles with other title features");
+ }
+ if (((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) && (featureId != FEATURE_CUSTOM_TITLE)) {
+
+ /* Custom title feature is enabled and the user is trying to enable another feature */
+ throw new AndroidRuntimeException("You cannot combine custom titles with other title features");
+ }
+ if (featureId == FEATURE_OPENGL) {
+ getAttributes().memoryType = WindowManager.LayoutParams.MEMORY_TYPE_GPU;
+ }
+ return super.requestFeature(featureId);
+ }
+
+ @Override
+ public void setContentView(int layoutResID) {
+ if (mContentParent == null) {
+ installDecor();
+ } else {
+ mContentParent.removeAllViews();
+ }
+ mLayoutInflater.inflate(layoutResID, mContentParent);
+ final Callback cb = getCallback();
+ if (cb != null) {
+ cb.onContentChanged();
+ }
+ }
+
+ @Override
+ public void setContentView(View view) {
+ setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ if (mContentParent == null) {
+ installDecor();
+ } else {
+ mContentParent.removeAllViews();
+ }
+ mContentParent.addView(view, params);
+ final Callback cb = getCallback();
+ if (cb != null) {
+ cb.onContentChanged();
+ }
+ }
+
+ @Override
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ if (mContentParent == null) {
+ installDecor();
+ }
+ mContentParent.addView(view, params);
+ final Callback cb = getCallback();
+ if (cb != null) {
+ cb.onContentChanged();
+ }
+ }
+
+ @Override
+ public View getCurrentFocus() {
+ return mDecor != null ? mDecor.findFocus() : null;
+ }
+
+ @Override
+ public boolean isFloating() {
+ return mIsFloating;
+ }
+
+ /**
+ * Return a LayoutInflater instance that can be used to inflate XML view layout
+ * resources for use in this Window.
+ *
+ * @return LayoutInflater The shared LayoutInflater.
+ */
+ @Override
+ public LayoutInflater getLayoutInflater() {
+ return mLayoutInflater;
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ if (mTitleView != null) {
+ mTitleView.setText(title);
+ }
+ mTitle = title;
+ }
+
+ @Override
+ public void setTitleColor(int textColor) {
+ if (mTitleView != null) {
+ mTitleView.setTextColor(textColor);
+ }
+ mTitleColor = textColor;
+ }
+
+ /**
+ * Prepares the panel to either be opened or chorded. This creates the Menu
+ * instance for the panel and populates it via the Activity callbacks.
+ *
+ * @param st The panel state to prepare.
+ * @param event The event that triggered the preparing of the panel.
+ * @return Whether the panel was prepared. If the panel should not be shown,
+ * returns false.
+ */
+ public final boolean preparePanel(PanelFeatureState st, KeyEvent event) {
+ // Already prepared (isPrepared will be reset to false later)
+ if (st.isPrepared)
+ return true;
+
+ if ((mPreparedPanel != null) && (mPreparedPanel != st)) {
+ // Another Panel is prepared and possibly open, so close it
+ closePanel(mPreparedPanel, false);
+ }
+
+ final Callback cb = getCallback();
+
+ if (cb != null) {
+ st.createdPanelView = cb.onCreatePanelView(st.featureId);
+ }
+
+ if (st.createdPanelView == null) {
+ // Init the panel state's menu--return false if init failed
+ if (st.menu == null) {
+ if (!initializePanelMenu(st) || (st.menu == null)) {
+ return false;
+ }
+ // Call callback, and return if it doesn't want to display menu
+ if ((cb == null) || !cb.onCreatePanelMenu(st.featureId, st.menu)) {
+ // Ditch the menu created above
+ st.menu = null;
+
+ return false;
+ }
+ }
+
+ // Callback and return if the callback does not want to show the menu
+ if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) {
+ return false;
+ }
+
+ // Set the proper keymap
+ KeyCharacterMap kmap = KeyCharacterMap.load(event != null ? event.getDeviceId() : 0);
+ st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC;
+ st.menu.setQwertyMode(st.qwertyMode);
+ }
+
+ // Set other state
+ st.isPrepared = true;
+ st.isHandled = false;
+ mPreparedPanel = st;
+
+ return true;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+ if ((st != null) && (st.menu != null)) {
+ final MenuBuilder menuBuilder = (MenuBuilder) st.menu;
+
+ if (st.isOpen) {
+ // Freeze state
+ final Bundle state = new Bundle();
+ menuBuilder.saveHierarchyState(state);
+
+ // Remove the menu views since they need to be recreated
+ // according to the new configuration
+ clearMenuViews(st);
+
+ // Re-open the same menu
+ reopenMenu(false);
+
+ // Restore state
+ menuBuilder.restoreHierarchyState(state);
+
+ } else {
+ // Clear menu views so on next menu opening, it will use
+ // the proper layout
+ clearMenuViews(st);
+ }
+ }
+
+ }
+
+ private static void clearMenuViews(PanelFeatureState st) {
+
+ // This can be called on config changes, so we should make sure
+ // the views will be reconstructed based on the new orientation, etc.
+
+ // Allow the callback to create a new panel view
+ st.createdPanelView = null;
+
+ // Causes the decor view to be recreated
+ st.refreshDecorView = true;
+
+ ((MenuBuilder) st.menu).clearMenuViews();
+ }
+
+ @Override
+ public final void openPanel(int featureId, KeyEvent event) {
+ openPanel(getPanelState(featureId, true), event);
+ }
+
+ private void openPanel(PanelFeatureState st, KeyEvent event) {
+ // System.out.println("Open panel: isOpen=" + st.isOpen);
+
+ // Already open, return
+ if (st.isOpen) {
+ return;
+ }
+
+ Callback cb = getCallback();
+ if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) {
+ // Callback doesn't want the menu to open, reset any state
+ closePanel(st, true);
+ return;
+ }
+
+ final WindowManager wm = getWindowManager();
+ if (wm == null) {
+ return;
+ }
+
+ // Prepare panel (should have been done before, but just in case)
+ if (!preparePanel(st, event)) {
+ return;
+ }
+
+ if (st.decorView == null || st.refreshDecorView) {
+ if (st.decorView == null) {
+ // Initialize the panel decor, this will populate st.decorView
+ if (!initializePanelDecor(st) || (st.decorView == null))
+ return;
+ } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) {
+ // Decor needs refreshing, so remove its views
+ st.decorView.removeAllViews();
+ }
+
+ // This will populate st.shownPanelView
+ if (!initializePanelContent(st) || (st.shownPanelView == null)) {
+ return;
+ }
+
+ ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams();
+ if (lp == null) {
+ lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ }
+
+ int backgroundResId;
+ if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
+ // If the contents is fill parent for the width, set the
+ // corresponding background
+ backgroundResId = st.fullBackground;
+ } else {
+ // Otherwise, set the normal panel background
+ backgroundResId = st.background;
+ }
+ st.decorView.setWindowBackground(getContext().getResources().getDrawable(
+ backgroundResId));
+
+
+ st.decorView.addView(st.shownPanelView, lp);
+
+ /*
+ * Give focus to the view, if it or one of its children does not
+ * already have it.
+ */
+ if (!st.shownPanelView.hasFocus()) {
+ st.shownPanelView.requestFocus();
+ }
+ }
+
+ st.isOpen = true;
+ st.isHandled = false;
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WRAP_CONTENT, WRAP_CONTENT,
+ st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG,
+ WindowManager.LayoutParams.FLAG_DITHER
+ | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ st.decorView.mDefaultOpacity);
+
+ lp.gravity = st.gravity;
+ lp.windowAnimations = st.windowAnimations;
+
+ wm.addView(st.decorView, lp);
+ // Log.v(TAG, "Adding main menu to window manager.");
+ }
+
+ @Override
+ public final void closePanel(int featureId) {
+ if (featureId == FEATURE_CONTEXT_MENU) {
+ closeContextMenu();
+ } else {
+ closePanel(getPanelState(featureId, true), true);
+ }
+ }
+
+ /**
+ * Closes the given panel.
+ *
+ * @param st The panel to be closed.
+ * @param doCallback Whether to notify the callback that the panel was
+ * closed. If the panel is in the process of re-opening or
+ * opening another panel (e.g., menu opening a sub menu), the
+ * callback should not happen and this variable should be false.
+ * In addition, this method internally will only perform the
+ * callback if the panel is open.
+ */
+ public final void closePanel(PanelFeatureState st, boolean doCallback) {
+ // System.out.println("Close panel: isOpen=" + st.isOpen);
+ final ViewManager wm = getWindowManager();
+ if ((wm != null) && st.isOpen) {
+ if (st.decorView != null) {
+ wm.removeView(st.decorView);
+ // Log.v(TAG, "Removing main menu from window manager.");
+ }
+
+ if (doCallback) {
+ callOnPanelClosed(st.featureId, st, null);
+ }
+ }
+ st.isPrepared = false;
+ st.isHandled = false;
+ st.isOpen = false;
+
+ // This view is no longer shown, so null it out
+ st.shownPanelView = null;
+
+ if (st.isInExpandedMode) {
+ // Next time the menu opens, it should not be in expanded mode, so
+ // force a refresh of the decor
+ st.refreshDecorView = true;
+ st.isInExpandedMode = false;
+ }
+
+ if (mPreparedPanel == st) {
+ mPreparedPanel = null;
+ mPanelChordingKey = 0;
+ }
+ }
+
+ @Override
+ public final void togglePanel(int featureId, KeyEvent event) {
+ PanelFeatureState st = getPanelState(featureId, true);
+ if (st.isOpen) {
+ closePanel(st, true);
+ } else {
+ openPanel(st, event);
+ }
+ }
+
+ /**
+ * Called when the panel key is pushed down.
+ * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}.
+ * @param event The key event.
+ * @return Whether the key was handled.
+ */
+ public final boolean onKeyDownPanel(int featureId, KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+
+ if (event.getRepeatCount() == 0) {
+ // The panel key was pushed, so set the chording key
+ mPanelChordingKey = keyCode;
+ mPanelMayLongPress = false;
+
+ PanelFeatureState st = getPanelState(featureId, true);
+ if (!st.isOpen) {
+ if (getContext().getResources().getConfiguration().keyboard
+ == Configuration.KEYBOARD_NOKEYS) {
+ mPanelMayLongPress = true;
+ }
+ return preparePanel(st, event);
+ }
+
+ } else if (mPanelMayLongPress && mPanelChordingKey == keyCode
+ && (event.getFlags()&KeyEvent.FLAG_LONG_PRESS) != 0) {
+ // We have had a long press while in a state where this
+ // should be executed... do it!
+ mPanelChordingKey = 0;
+ mPanelMayLongPress = false;
+ InputMethodManager imm = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (imm != null) {
+ mDecor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
+ }
+
+ }
+
+ return false;
+ }
+
+ /**
+ * Called when the panel key is released.
+ * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}.
+ * @param event The key event.
+ */
+ public final void onKeyUpPanel(int featureId, KeyEvent event) {
+ // The panel key was released, so clear the chording key
+ if (mPanelChordingKey != 0) {
+ mPanelChordingKey = 0;
+ mPanelMayLongPress = false;
+
+ if (event.isCanceled()) {
+ return;
+ }
+
+ boolean playSoundEffect = false;
+ PanelFeatureState st = getPanelState(featureId, true);
+ if (st.isOpen || st.isHandled) {
+
+ // Play the sound effect if the user closed an open menu (and not if
+ // they just released a menu shortcut)
+ playSoundEffect = st.isOpen;
+
+ // Close menu
+ closePanel(st, true);
+
+ } else if (st.isPrepared) {
+
+ // Write 'menu opened' to event log
+ EventLog.writeEvent(50001, 0);
+
+ // Show menu
+ openPanel(st, event);
+
+ playSoundEffect = true;
+ }
+
+ if (playSoundEffect) {
+ AudioManager audioManager = (AudioManager) getContext().getSystemService(
+ Context.AUDIO_SERVICE);
+ if (audioManager != null) {
+ audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
+ } else {
+ Log.w(TAG, "Couldn't get audio manager");
+ }
+ }
+ }
+ }
+
+ @Override
+ public final void closeAllPanels() {
+ final ViewManager wm = getWindowManager();
+ if (wm == null) {
+ return;
+ }
+
+ final PanelFeatureState[] panels = mPanels;
+ final int N = panels != null ? panels.length : 0;
+ for (int i = 0; i < N; i++) {
+ final PanelFeatureState panel = panels[i];
+ if (panel != null) {
+ closePanel(panel, true);
+ }
+ }
+
+ closeContextMenu();
+ }
+
+ /**
+ * Closes the context menu. This notifies the menu logic of the close, along
+ * with dismissing it from the UI.
+ */
+ private synchronized void closeContextMenu() {
+ if (mContextMenu != null) {
+ mContextMenu.close();
+ dismissContextMenu();
+ }
+ }
+
+ /**
+ * Dismisses just the context menu UI. To close the context menu, use
+ * {@link #closeContextMenu()}.
+ */
+ private synchronized void dismissContextMenu() {
+ mContextMenu = null;
+
+ if (mContextMenuHelper != null) {
+ mContextMenuHelper.dismiss();
+ mContextMenuHelper = null;
+ }
+ }
+
+ @Override
+ public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) {
+ return performPanelShortcut(getPanelState(featureId, true), keyCode, event, flags);
+ }
+
+ private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event,
+ int flags) {
+ if (event.isSystem() || (st == null)) {
+ return false;
+ }
+
+ boolean handled = false;
+
+ // Only try to perform menu shortcuts if preparePanel returned true (possible false
+ // return value from application not wanting to show the menu).
+ if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) {
+ // The menu is prepared now, perform the shortcut on it
+ handled = st.menu.performShortcut(keyCode, event, flags);
+ }
+
+ if (handled) {
+ // Mark as handled
+ st.isHandled = true;
+
+ if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0) {
+ closePanel(st, true);
+ }
+ }
+
+ return handled;
+ }
+
+ @Override
+ public boolean performPanelIdentifierAction(int featureId, int id, int flags) {
+
+ PanelFeatureState st = getPanelState(featureId, true);
+ if (!preparePanel(st, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU))) {
+ return false;
+ }
+ if (st.menu == null) {
+ return false;
+ }
+
+ boolean res = st.menu.performIdentifierAction(id, flags);
+
+ closePanel(st, true);
+
+ return res;
+ }
+
+ public PanelFeatureState findMenuPanel(Menu menu) {
+ final PanelFeatureState[] panels = mPanels;
+ final int N = panels != null ? panels.length : 0;
+ for (int i = 0; i < N; i++) {
+ final PanelFeatureState panel = panels[i];
+ if (panel != null && panel.menu == menu) {
+ return panel;
+ }
+ }
+ return null;
+ }
+
+ public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ final Callback cb = getCallback();
+ if (cb != null) {
+ final PanelFeatureState panel = findMenuPanel(menu.getRootMenu());
+ if (panel != null) {
+ return cb.onMenuItemSelected(panel.featureId, item);
+ }
+ }
+ return false;
+ }
+
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ final PanelFeatureState panel = findMenuPanel(menu);
+ if (panel != null) {
+ // Close the panel and only do the callback if the menu is being
+ // closed
+ // completely, not if opening a sub menu
+ closePanel(panel, allMenusAreClosing);
+ }
+ }
+
+ public void onCloseSubMenu(SubMenuBuilder subMenu) {
+ final Menu parentMenu = subMenu.getRootMenu();
+ final PanelFeatureState panel = findMenuPanel(parentMenu);
+
+ // Callback
+ if (panel != null) {
+ callOnPanelClosed(panel.featureId, panel, parentMenu);
+ closePanel(panel, true);
+ }
+ }
+
+ public boolean onSubMenuSelected(final SubMenuBuilder subMenu) {
+ if (!subMenu.hasVisibleItems()) {
+ return true;
+ }
+
+ // The window manager will give us a valid window token
+ new MenuDialogHelper(subMenu).show(null);
+
+ return true;
+ }
+
+ public void onMenuModeChange(MenuBuilder menu) {
+ reopenMenu(true);
+ }
+
+ private void reopenMenu(boolean toggleMenuMode) {
+ PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
+
+ // Save the future expanded mode state since closePanel will reset it
+ boolean newExpandedMode = toggleMenuMode ? !st.isInExpandedMode : st.isInExpandedMode;
+
+ st.refreshDecorView = true;
+ closePanel(st, false);
+
+ // Set the expanded mode state
+ st.isInExpandedMode = newExpandedMode;
+
+ openPanel(st, null);
+ }
+
+ /**
+ * Initializes the menu associated with the given panel feature state. You
+ * must at the very least set PanelFeatureState.menu to the Menu to be
+ * associated with the given panel state. The default implementation creates
+ * a new menu for the panel state.
+ *
+ * @param st The panel whose menu is being initialized.
+ * @return Whether the initialization was successful.
+ */
+ protected boolean initializePanelMenu(final PanelFeatureState st) {
+ final MenuBuilder menu = new MenuBuilder(getContext());
+
+ menu.setCallback(this);
+ st.setMenu(menu);
+
+ return true;
+ }
+
+ /**
+ * Perform initial setup of a panel. This should at the very least set the
+ * style information in the PanelFeatureState and must set
+ * PanelFeatureState.decor to the panel's window decor view.
+ *
+ * @param st The panel being initialized.
+ */
+ protected boolean initializePanelDecor(PanelFeatureState st) {
+ st.decorView = new DecorView(getContext(), st.featureId);
+ st.gravity = Gravity.CENTER | Gravity.BOTTOM;
+ st.setStyle(getContext());
+
+ return true;
+ }
+
+ /**
+ * Initializes the panel associated with the panel feature state. You must
+ * at the very least set PanelFeatureState.panel to the View implementing
+ * its contents. The default implementation gets the panel from the menu.
+ *
+ * @param st The panel state being initialized.
+ * @return Whether the initialization was successful.
+ */
+ protected boolean initializePanelContent(PanelFeatureState st) {
+
+ if (st.createdPanelView != null) {
+ st.shownPanelView = st.createdPanelView;
+ return true;
+ }
+
+ final MenuBuilder menu = (MenuBuilder)st.menu;
+ if (menu == null) {
+ return false;
+ }
+
+ st.shownPanelView = menu.getMenuView((st.isInExpandedMode) ? MenuBuilder.TYPE_EXPANDED
+ : MenuBuilder.TYPE_ICON, st.decorView);
+
+ if (st.shownPanelView != null) {
+ // Use the menu View's default animations if it has any
+ final int defaultAnimations = ((MenuView) st.shownPanelView).getWindowAnimations();
+ if (defaultAnimations != 0) {
+ st.windowAnimations = defaultAnimations;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean performContextMenuIdentifierAction(int id, int flags) {
+ return (mContextMenu != null) ? mContextMenu.performIdentifierAction(id, flags) : false;
+ }
+
+ @Override
+ public final void setBackgroundDrawable(Drawable drawable) {
+ if (drawable != mBackgroundDrawable || mBackgroundResource != 0) {
+ mBackgroundResource = 0;
+ mBackgroundDrawable = drawable;
+ if (mDecor != null) {
+ mDecor.setWindowBackground(drawable);
+ }
+ }
+ }
+
+ @Override
+ public final void setFeatureDrawableResource(int featureId, int resId) {
+ if (resId != 0) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ if (st.resid != resId) {
+ st.resid = resId;
+ st.uri = null;
+ st.local = getContext().getResources().getDrawable(resId);
+ updateDrawable(featureId, st, false);
+ }
+ } else {
+ setFeatureDrawable(featureId, null);
+ }
+ }
+
+ @Override
+ public final void setFeatureDrawableUri(int featureId, Uri uri) {
+ if (uri != null) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ if (st.uri == null || !st.uri.equals(uri)) {
+ st.resid = 0;
+ st.uri = uri;
+ st.local = loadImageURI(uri);
+ updateDrawable(featureId, st, false);
+ }
+ } else {
+ setFeatureDrawable(featureId, null);
+ }
+ }
+
+ @Override
+ public final void setFeatureDrawable(int featureId, Drawable drawable) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ st.resid = 0;
+ st.uri = null;
+ if (st.local != drawable) {
+ st.local = drawable;
+ updateDrawable(featureId, st, false);
+ }
+ }
+
+ @Override
+ public void setFeatureDrawableAlpha(int featureId, int alpha) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ if (st.alpha != alpha) {
+ st.alpha = alpha;
+ updateDrawable(featureId, st, false);
+ }
+ }
+
+ protected final void setFeatureDefaultDrawable(int featureId, Drawable drawable) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ if (st.def != drawable) {
+ st.def = drawable;
+ updateDrawable(featureId, st, false);
+ }
+ }
+
+ @Override
+ public final void setFeatureInt(int featureId, int value) {
+ // XXX Should do more management (as with drawable features) to
+ // deal with interactions between multiple window policies.
+ updateInt(featureId, value, false);
+ }
+
+ /**
+ * Update the state of a drawable feature. This should be called, for every
+ * drawable feature supported, as part of onActive(), to make sure that the
+ * contents of a containing window is properly updated.
+ *
+ * @see #onActive
+ * @param featureId The desired drawable feature to change.
+ * @param fromActive Always true when called from onActive().
+ */
+ protected final void updateDrawable(int featureId, boolean fromActive) {
+ final DrawableFeatureState st = getDrawableState(featureId, false);
+ if (st != null) {
+ updateDrawable(featureId, st, fromActive);
+ }
+ }
+
+ /**
+ * Called when a Drawable feature changes, for the window to update its
+ * graphics.
+ *
+ * @param featureId The feature being changed.
+ * @param drawable The new Drawable to show, or null if none.
+ * @param alpha The new alpha blending of the Drawable.
+ */
+ protected void onDrawableChanged(int featureId, Drawable drawable, int alpha) {
+ ImageView view;
+ if (featureId == FEATURE_LEFT_ICON) {
+ view = getLeftIconView();
+ } else if (featureId == FEATURE_RIGHT_ICON) {
+ view = getRightIconView();
+ } else {
+ return;
+ }
+
+ if (drawable != null) {
+ drawable.setAlpha(alpha);
+ view.setImageDrawable(drawable);
+ view.setVisibility(View.VISIBLE);
+ } else {
+ view.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Called when an int feature changes, for the window to update its
+ * graphics.
+ *
+ * @param featureId The feature being changed.
+ * @param value The new integer value.
+ */
+ protected void onIntChanged(int featureId, int value) {
+ if (featureId == FEATURE_PROGRESS || featureId == FEATURE_INDETERMINATE_PROGRESS) {
+ updateProgressBars(value);
+ } else if (featureId == FEATURE_CUSTOM_TITLE) {
+ FrameLayout titleContainer = (FrameLayout) findViewById(com.android.internal.R.id.title_container);
+ if (titleContainer != null) {
+ mLayoutInflater.inflate(value, titleContainer);
+ }
+ }
+ }
+
+ /**
+ * Updates the progress bars that are shown in the title bar.
+ *
+ * @param value Can be one of {@link Window#PROGRESS_VISIBILITY_ON},
+ * {@link Window#PROGRESS_VISIBILITY_OFF},
+ * {@link Window#PROGRESS_INDETERMINATE_ON},
+ * {@link Window#PROGRESS_INDETERMINATE_OFF}, or a value
+ * starting at {@link Window#PROGRESS_START} through
+ * {@link Window#PROGRESS_END} for setting the default
+ * progress (if {@link Window#PROGRESS_END} is given,
+ * the progress bar widgets in the title will be hidden after an
+ * animation), a value between
+ * {@link Window#PROGRESS_SECONDARY_START} -
+ * {@link Window#PROGRESS_SECONDARY_END} for the
+ * secondary progress (if
+ * {@link Window#PROGRESS_SECONDARY_END} is given, the
+ * progress bar widgets will still be shown with the secondary
+ * progress bar will be completely filled in.)
+ */
+ private void updateProgressBars(int value) {
+ ProgressBar circularProgressBar = getCircularProgressBar(true);
+ ProgressBar horizontalProgressBar = getHorizontalProgressBar(true);
+
+ final int features = getLocalFeatures();
+ if (value == PROGRESS_VISIBILITY_ON) {
+ if ((features & (1 << FEATURE_PROGRESS)) != 0) {
+ int level = horizontalProgressBar.getProgress();
+ int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ?
+ View.VISIBLE : View.INVISIBLE;
+ horizontalProgressBar.setVisibility(visibility);
+ }
+ if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
+ circularProgressBar.setVisibility(View.VISIBLE);
+ }
+ } else if (value == PROGRESS_VISIBILITY_OFF) {
+ if ((features & (1 << FEATURE_PROGRESS)) != 0) {
+ horizontalProgressBar.setVisibility(View.GONE);
+ }
+ if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
+ circularProgressBar.setVisibility(View.GONE);
+ }
+ } else if (value == PROGRESS_INDETERMINATE_ON) {
+ horizontalProgressBar.setIndeterminate(true);
+ } else if (value == PROGRESS_INDETERMINATE_OFF) {
+ horizontalProgressBar.setIndeterminate(false);
+ } else if (PROGRESS_START <= value && value <= PROGRESS_END) {
+ // We want to set the progress value before testing for visibility
+ // so that when the progress bar becomes visible again, it has the
+ // correct level.
+ horizontalProgressBar.setProgress(value - PROGRESS_START);
+
+ if (value < PROGRESS_END) {
+ showProgressBars(horizontalProgressBar, circularProgressBar);
+ } else {
+ hideProgressBars(horizontalProgressBar, circularProgressBar);
+ }
+ } else if (PROGRESS_SECONDARY_START <= value && value <= PROGRESS_SECONDARY_END) {
+ horizontalProgressBar.setSecondaryProgress(value - PROGRESS_SECONDARY_START);
+
+ showProgressBars(horizontalProgressBar, circularProgressBar);
+ }
+
+ }
+
+ private void showProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) {
+ final int features = getLocalFeatures();
+ if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 &&
+ spinnyProgressBar.getVisibility() == View.INVISIBLE) {
+ spinnyProgressBar.setVisibility(View.VISIBLE);
+ }
+ // Only show the progress bars if the primary progress is not complete
+ if ((features & (1 << FEATURE_PROGRESS)) != 0 &&
+ horizontalProgressBar.getProgress() < 10000) {
+ horizontalProgressBar.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void hideProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) {
+ final int features = getLocalFeatures();
+ Animation anim = AnimationUtils.loadAnimation(getContext(), com.android.internal.R.anim.fade_out);
+ anim.setDuration(1000);
+ if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 &&
+ spinnyProgressBar.getVisibility() == View.VISIBLE) {
+ spinnyProgressBar.startAnimation(anim);
+ spinnyProgressBar.setVisibility(View.INVISIBLE);
+ }
+ if ((features & (1 << FEATURE_PROGRESS)) != 0 &&
+ horizontalProgressBar.getVisibility() == View.VISIBLE) {
+ horizontalProgressBar.startAnimation(anim);
+ horizontalProgressBar.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ /**
+ * Request that key events come to this activity. Use this if your activity
+ * has no views with focus, but the activity still wants a chance to process
+ * key events.
+ */
+ @Override
+ public void takeKeyEvents(boolean get) {
+ mDecor.setFocusable(get);
+ }
+
+ @Override
+ public boolean superDispatchKeyEvent(KeyEvent event) {
+ return mDecor.superDispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean superDispatchTouchEvent(MotionEvent event) {
+ return mDecor.superDispatchTouchEvent(event);
+ }
+
+ @Override
+ public boolean superDispatchTrackballEvent(MotionEvent event) {
+ return mDecor.superDispatchTrackballEvent(event);
+ }
+
+ /**
+ * A key was pressed down and not handled by anything else in the window.
+ *
+ * @see #onKeyUp
+ * @see android.view.KeyEvent
+ */
+ protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {
+ final KeyEvent.DispatcherState dispatcher =
+ mDecor != null ? mDecor.getKeyDispatcherState() : null;
+ //Log.i(TAG, "Key down: repeat=" + event.getRepeatCount()
+ // + " flags=0x" + Integer.toHexString(event.getFlags()));
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN: {
+ AudioManager audioManager = (AudioManager) getContext().getSystemService(
+ Context.AUDIO_SERVICE);
+ if (audioManager != null) {
+ /*
+ * Adjust the volume in on key down since it is more
+ * responsive to the user.
+ */
+ audioManager.adjustSuggestedStreamVolume(
+ keyCode == KeyEvent.KEYCODE_VOLUME_UP
+ ? AudioManager.ADJUST_RAISE
+ : AudioManager.ADJUST_LOWER,
+ mVolumeControlStreamType,
+ AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE);
+ }
+ return true;
+ }
+
+
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ /* Suppress PLAYPAUSE 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_FAST_FORWARD: {
+ Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
+ intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
+ getContext().sendOrderedBroadcast(intent, null);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_CAMERA: {
+ if (getKeyguardManager().inKeyguardRestrictedInputMode()
+ || dispatcher == null) {
+ break;
+ }
+ if (event.getRepeatCount() == 0) {
+ dispatcher.startTracking(event, this);
+ } else if (event.isLongPress() && dispatcher.isTracking(event)) {
+ dispatcher.performedLongPress(event);
+ mDecor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ sendCloseSystemWindows();
+ // Broadcast an intent that the Camera button was longpressed
+ Intent intent = new Intent(Intent.ACTION_CAMERA_BUTTON, null);
+ intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
+ getContext().sendOrderedBroadcast(intent, null);
+ }
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_MENU: {
+ onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_BACK: {
+ if (event.getRepeatCount() > 0) break;
+ if (featureId < 0) break;
+ // Currently don't do anything with long press.
+ dispatcher.startTracking(event, this);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_CALL: {
+ if (getKeyguardManager().inKeyguardRestrictedInputMode()
+ || dispatcher == null) {
+ break;
+ }
+ if (event.getRepeatCount() == 0) {
+ dispatcher.startTracking(event, this);
+ } else if (event.isLongPress() && dispatcher.isTracking(event)) {
+ dispatcher.performedLongPress(event);
+ mDecor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ // launch the VoiceDialer
+ Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ sendCloseSystemWindows();
+ getContext().startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ startCallActivity();
+ }
+ }
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_SEARCH: {
+ if (getKeyguardManager().inKeyguardRestrictedInputMode()
+ || dispatcher == null) {
+ break;
+ }
+ if (event.getRepeatCount() == 0) {
+ dispatcher.startTracking(event, this);
+ } else if (event.isLongPress() && dispatcher.isTracking(event)) {
+ Configuration config = getContext().getResources().getConfiguration();
+ if (config.keyboard == Configuration.KEYBOARD_NOKEYS
+ || config.hardKeyboardHidden
+ == Configuration.HARDKEYBOARDHIDDEN_YES) {
+ // launch the search activity
+ Intent intent = new Intent(Intent.ACTION_SEARCH_LONG_PRESS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ mDecor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ sendCloseSystemWindows();
+ getSearchManager().stopSearch();
+ getContext().startActivity(intent);
+ // Only clear this if we successfully start the
+ // activity; otherwise we will allow the normal short
+ // press action to be performed.
+ dispatcher.performedLongPress(event);
+ return true;
+ } catch (ActivityNotFoundException e) {
+ // Ignore
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @return A handle to the keyguard manager.
+ */
+ private KeyguardManager getKeyguardManager() {
+ if (mKeyguardManager == null) {
+ mKeyguardManager = (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
+ }
+ return mKeyguardManager;
+ }
+
+ /**
+ * @return A handle to the search manager.
+ */
+ private SearchManager getSearchManager() {
+ if (mSearchManager == null) {
+ mSearchManager = (SearchManager) getContext().getSystemService(Context.SEARCH_SERVICE);
+ }
+ return mSearchManager;
+ }
+
+ /**
+ * A key was released and not handled by anything else in the window.
+ *
+ * @see #onKeyDown
+ * @see android.view.KeyEvent
+ */
+ protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) {
+ final KeyEvent.DispatcherState dispatcher =
+ mDecor != null ? mDecor.getKeyDispatcherState() : null;
+ if (dispatcher != null) {
+ dispatcher.handleUpEvent(event);
+ }
+ //Log.i(TAG, "Key up: repeat=" + event.getRepeatCount()
+ // + " flags=0x" + Integer.toHexString(event.getFlags()));
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN: {
+ AudioManager audioManager = (AudioManager) getContext().getSystemService(
+ Context.AUDIO_SERVICE);
+ if (audioManager != null) {
+ /*
+ * Play a sound. This is done on key up since we don't want the
+ * sound to play when a user holds down volume down to mute.
+ */
+ audioManager.adjustSuggestedStreamVolume(
+ AudioManager.ADJUST_SAME,
+ mVolumeControlStreamType,
+ AudioManager.FLAG_PLAY_SOUND);
+ mVolumeKeyUpTime = SystemClock.uptimeMillis();
+ }
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_MENU: {
+ onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId,
+ event);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_BACK: {
+ if (featureId < 0) break;
+ if (event.isTracking() && !event.isCanceled()) {
+ if (featureId == FEATURE_OPTIONS_PANEL) {
+ PanelFeatureState st = getPanelState(featureId, false);
+ if (st != null && st.isInExpandedMode) {
+ // If the user is in an expanded menu and hits back, it
+ // should go back to the icon menu
+ reopenMenu(true);
+ return true;
+ }
+ }
+ closePanel(featureId);
+ return true;
+ }
+ break;
+ }
+
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ 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_FAST_FORWARD: {
+ Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
+ intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
+ getContext().sendOrderedBroadcast(intent, null);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_CAMERA: {
+ if (getKeyguardManager().inKeyguardRestrictedInputMode()) {
+ break;
+ }
+ if (event.isTracking() && !event.isCanceled()) {
+ // Add short press behavior here if desired
+ }
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_CALL: {
+ if (getKeyguardManager().inKeyguardRestrictedInputMode()) {
+ break;
+ }
+ if (event.isTracking() && !event.isCanceled()) {
+ startCallActivity();
+ }
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_SEARCH: {
+ /*
+ * Do this in onKeyUp since the Search key is also used for
+ * chording quick launch shortcuts.
+ */
+ if (getKeyguardManager().inKeyguardRestrictedInputMode()) {
+ break;
+ }
+ if (event.isTracking() && !event.isCanceled()) {
+ launchDefaultSearch();
+ }
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void startCallActivity() {
+ sendCloseSystemWindows();
+ Intent intent = new Intent(Intent.ACTION_CALL_BUTTON);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(intent);
+ }
+
+ @Override
+ protected void onActive() {
+ }
+
+ @Override
+ public final View getDecorView() {
+ if (mDecor == null) {
+ installDecor();
+ }
+ return mDecor;
+ }
+
+ @Override
+ public final View peekDecorView() {
+ return mDecor;
+ }
+
+ static private final String FOCUSED_ID_TAG = "android:focusedViewId";
+ static private final String VIEWS_TAG = "android:views";
+ static private final String PANELS_TAG = "android:Panels";
+
+ /** {@inheritDoc} */
+ @Override
+ public Bundle saveHierarchyState() {
+ Bundle outState = new Bundle();
+ if (mContentParent == null) {
+ return outState;
+ }
+
+ SparseArray<Parcelable> states = new SparseArray<Parcelable>();
+ mContentParent.saveHierarchyState(states);
+ outState.putSparseParcelableArray(VIEWS_TAG, states);
+
+ // save the focused view id
+ View focusedView = mContentParent.findFocus();
+ if (focusedView != null) {
+ if (focusedView.getId() != View.NO_ID) {
+ outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
+ } else {
+ if (Config.LOGD) {
+ Log.d(TAG, "couldn't save which view has focus because the focused view "
+ + focusedView + " has no id.");
+ }
+ }
+ }
+
+ // save the panels
+ SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();
+ savePanelState(panelStates);
+ if (panelStates.size() > 0) {
+ outState.putSparseParcelableArray(PANELS_TAG, panelStates);
+ }
+
+ return outState;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void restoreHierarchyState(Bundle savedInstanceState) {
+ if (mContentParent == null) {
+ return;
+ }
+
+ SparseArray<Parcelable> savedStates
+ = savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
+ if (savedStates != null) {
+ mContentParent.restoreHierarchyState(savedStates);
+ }
+
+ // restore the focused view
+ int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID);
+ if (focusedViewId != View.NO_ID) {
+ View needsFocus = mContentParent.findViewById(focusedViewId);
+ if (needsFocus != null) {
+ needsFocus.requestFocus();
+ } else {
+ Log.w(TAG,
+ "Previously focused view reported id " + focusedViewId
+ + " during save, but can't be found during restore.");
+ }
+ }
+
+ // restore the panels
+ SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG);
+ if (panelStates != null) {
+ restorePanelState(panelStates);
+ }
+ }
+
+ /**
+ * Invoked when the panels should freeze their state.
+ *
+ * @param icicles Save state into this. This is usually indexed by the
+ * featureId. This will be given to {@link #restorePanelState} in the
+ * future.
+ */
+ private void savePanelState(SparseArray<Parcelable> icicles) {
+ PanelFeatureState[] panels = mPanels;
+ if (panels == null) {
+ return;
+ }
+
+ for (int curFeatureId = panels.length - 1; curFeatureId >= 0; curFeatureId--) {
+ if (panels[curFeatureId] != null) {
+ icicles.put(curFeatureId, panels[curFeatureId].onSaveInstanceState());
+ }
+ }
+ }
+
+ /**
+ * Invoked when the panels should thaw their state from a previously frozen state.
+ *
+ * @param icicles The state saved by {@link #savePanelState} that needs to be thawed.
+ */
+ private void restorePanelState(SparseArray<Parcelable> icicles) {
+ PanelFeatureState st;
+ for (int curFeatureId = icicles.size() - 1; curFeatureId >= 0; curFeatureId--) {
+ st = getPanelState(curFeatureId, false /* required */);
+ if (st == null) {
+ // The panel must not have been required, and is currently not around, skip it
+ continue;
+ }
+
+ st.onRestoreInstanceState(icicles.get(curFeatureId));
+ }
+
+ /*
+ * Implementation note: call openPanelsAfterRestore later to actually open the
+ * restored panels.
+ */
+ }
+
+ /**
+ * Opens the panels that have had their state restored. This should be
+ * called sometime after {@link #restorePanelState} when it is safe to add
+ * to the window manager.
+ */
+ private void openPanelsAfterRestore() {
+ PanelFeatureState[] panels = mPanels;
+
+ if (panels == null) {
+ return;
+ }
+
+ PanelFeatureState st;
+ for (int i = panels.length - 1; i >= 0; i--) {
+ st = panels[i];
+ // We restore the panel if it was last open; we skip it if it
+ // now is open, to avoid a race condition if the user immediately
+ // opens it when we are resuming.
+ if ((st != null) && !st.isOpen && st.wasLastOpen) {
+ st.isInExpandedMode = st.wasLastExpanded;
+ openPanel(st, null);
+ }
+ }
+ }
+
+ private final class DecorView extends FrameLayout {
+ /* package */int mDefaultOpacity = PixelFormat.OPAQUE;
+
+ /** The feature ID of the panel, or -1 if this is the application's DecorView */
+ private final int mFeatureId;
+
+ private final Rect mDrawingBounds = new Rect();
+
+ private final Rect mBackgroundPadding = new Rect();
+
+ private final Rect mFramePadding = new Rect();
+
+ private final Rect mFrameOffsets = new Rect();
+
+ private boolean mChanging;
+
+ private Drawable mMenuBackground;
+ private boolean mWatchingForMenu;
+ private int mDownY;
+
+ public DecorView(Context context, int featureId) {
+ super(context);
+ mFeatureId = featureId;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ final boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN;
+
+ /*
+ * If the user hits another key within the play sound delay, then
+ * cancel the sound
+ */
+ if (keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && keyCode != KeyEvent.KEYCODE_VOLUME_UP
+ && mVolumeKeyUpTime + VolumePanel.PLAY_SOUND_DELAY
+ > SystemClock.uptimeMillis()) {
+ /*
+ * The user has hit another key during the delay (e.g., 300ms)
+ * since the last volume key up, so cancel any sounds.
+ */
+ AudioManager audioManager = (AudioManager) getContext().getSystemService(
+ Context.AUDIO_SERVICE);
+ if (audioManager != null) {
+ audioManager.adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME,
+ mVolumeControlStreamType, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+ }
+ }
+
+ if (isDown && (event.getRepeatCount() == 0)) {
+ // First handle chording of panel key: if a panel key is held
+ // but not released, try to execute a shortcut in it.
+ if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) {
+ // Perform the shortcut (mPreparedPanel can be null since
+ // global shortcuts (such as search) don't rely on a
+ // prepared panel or menu).
+ boolean handled = performPanelShortcut(mPreparedPanel, keyCode, event,
+ Menu.FLAG_PERFORM_NO_CLOSE);
+
+ if (!handled) {
+ /*
+ * If not handled, then pass it to the view hierarchy
+ * and anyone else that may be interested.
+ */
+ handled = dispatchKeyShortcutEvent(event);
+
+ if (handled && mPreparedPanel != null) {
+ mPreparedPanel.isHandled = true;
+ }
+ }
+
+ if (handled) {
+ return true;
+ }
+ }
+
+ // If a panel is open, perform a shortcut on it without the
+ // chorded panel key
+ if ((mPreparedPanel != null) && mPreparedPanel.isOpen) {
+ if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) {
+ return true;
+ }
+ }
+ }
+
+ final Callback cb = getCallback();
+ final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
+ : super.dispatchKeyEvent(event);
+ if (handled) {
+ return true;
+ }
+ return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
+ : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ final Callback cb = getCallback();
+ return cb != null && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super
+ .dispatchTouchEvent(ev);
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent ev) {
+ final Callback cb = getCallback();
+ return cb != null && mFeatureId < 0 ? cb.dispatchTrackballEvent(ev) : super
+ .dispatchTrackballEvent(ev);
+ }
+
+ public boolean superDispatchKeyEvent(KeyEvent event) {
+ return super.dispatchKeyEvent(event);
+ }
+
+ public boolean superDispatchTouchEvent(MotionEvent event) {
+ return super.dispatchTouchEvent(event);
+ }
+
+ public boolean superDispatchTrackballEvent(MotionEvent event) {
+ return super.dispatchTrackballEvent(event);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return onInterceptTouchEvent(event);
+ }
+
+ private boolean isOutOfBounds(int x, int y) {
+ return x < -5 || y < -5 || x > (getWidth() + 5)
+ || y > (getHeight() + 5);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ int action = event.getAction();
+ if (mFeatureId >= 0) {
+ if (action == MotionEvent.ACTION_DOWN) {
+ int x = (int)event.getX();
+ int y = (int)event.getY();
+ if (isOutOfBounds(x, y)) {
+ closePanel(mFeatureId);
+ return true;
+ }
+ }
+ }
+
+ if (!SWEEP_OPEN_MENU) {
+ return false;
+ }
+
+ if (mFeatureId >= 0) {
+ if (action == MotionEvent.ACTION_DOWN) {
+ Log.i(TAG, "Watchiing!");
+ mWatchingForMenu = true;
+ mDownY = (int) event.getY();
+ return false;
+ }
+
+ if (!mWatchingForMenu) {
+ return false;
+ }
+
+ int y = (int)event.getY();
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (y > (mDownY+30)) {
+ Log.i(TAG, "Closing!");
+ closePanel(mFeatureId);
+ mWatchingForMenu = false;
+ return true;
+ }
+ } else if (action == MotionEvent.ACTION_UP) {
+ mWatchingForMenu = false;
+ }
+
+ return false;
+ }
+
+ //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY()
+ // + " (in " + getHeight() + ")");
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ int y = (int)event.getY();
+ if (y >= (getHeight()-5) && !hasChildren()) {
+ Log.i(TAG, "Watchiing!");
+ mWatchingForMenu = true;
+ }
+ return false;
+ }
+
+ if (!mWatchingForMenu) {
+ return false;
+ }
+
+ int y = (int)event.getY();
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (y < (getHeight()-30)) {
+ Log.i(TAG, "Opening!");
+ openPanel(FEATURE_OPTIONS_PANEL, new KeyEvent(
+ KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
+ mWatchingForMenu = false;
+ return true;
+ }
+ } else if (action == MotionEvent.ACTION_UP) {
+ mWatchingForMenu = false;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void sendAccessibilityEvent(int eventType) {
+ if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
+ return;
+ }
+
+ // if we are showing a feature that should be announced and one child
+ // make this child the event source since this is the feature itself
+ // otherwise the callback will take over and announce its client
+ if ((mFeatureId == FEATURE_OPTIONS_PANEL ||
+ mFeatureId == FEATURE_CONTEXT_MENU ||
+ mFeatureId == FEATURE_PROGRESS ||
+ mFeatureId == FEATURE_INDETERMINATE_PROGRESS)
+ && getChildCount() == 1) {
+ getChildAt(0).sendAccessibilityEvent(eventType);
+ } else {
+ super.sendAccessibilityEvent(eventType);
+ }
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ final Callback cb = getCallback();
+ if (cb != null) {
+ if (cb.dispatchPopulateAccessibilityEvent(event)) {
+ return true;
+ }
+ }
+ return super.dispatchPopulateAccessibilityEvent(event);
+ }
+
+ @Override
+ protected boolean setFrame(int l, int t, int r, int b) {
+ boolean changed = super.setFrame(l, t, r, b);
+ if (changed) {
+ final Rect drawingBounds = mDrawingBounds;
+ getDrawingRect(drawingBounds);
+
+ Drawable fg = getForeground();
+ if (fg != null) {
+ final Rect frameOffsets = mFrameOffsets;
+ drawingBounds.left += frameOffsets.left;
+ drawingBounds.top += frameOffsets.top;
+ drawingBounds.right -= frameOffsets.right;
+ drawingBounds.bottom -= frameOffsets.bottom;
+ fg.setBounds(drawingBounds);
+ final Rect framePadding = mFramePadding;
+ drawingBounds.left += framePadding.left - frameOffsets.left;
+ drawingBounds.top += framePadding.top - frameOffsets.top;
+ drawingBounds.right -= framePadding.right - frameOffsets.right;
+ drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom;
+ }
+
+ Drawable bg = getBackground();
+ if (bg != null) {
+ bg.setBounds(drawingBounds);
+ }
+
+ if (SWEEP_OPEN_MENU) {
+ if (mMenuBackground == null && mFeatureId < 0
+ && getAttributes().height
+ == WindowManager.LayoutParams.MATCH_PARENT) {
+ mMenuBackground = getContext().getResources().getDrawable(
+ com.android.internal.R.drawable.menu_background);
+ }
+ if (mMenuBackground != null) {
+ mMenuBackground.setBounds(drawingBounds.left,
+ drawingBounds.bottom-6, drawingBounds.right,
+ drawingBounds.bottom+20);
+ }
+ }
+ }
+ return changed;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ if (mMenuBackground != null) {
+ mMenuBackground.draw(canvas);
+ }
+ }
+
+
+ @Override
+ public boolean showContextMenuForChild(View originalView) {
+ // Reuse the context menu builder
+ if (mContextMenu == null) {
+ mContextMenu = new ContextMenuBuilder(getContext());
+ mContextMenu.setCallback(mContextMenuCallback);
+ } else {
+ mContextMenu.clearAll();
+ }
+
+ mContextMenuHelper = mContextMenu.show(originalView, originalView.getWindowToken());
+ return mContextMenuHelper != null;
+ }
+
+ public void startChanging() {
+ mChanging = true;
+ }
+
+ public void finishChanging() {
+ mChanging = false;
+ drawableChanged();
+ }
+
+ public void setWindowBackground(Drawable drawable) {
+ if (getBackground() != drawable) {
+ setBackgroundDrawable(drawable);
+ if (drawable != null) {
+ drawable.getPadding(mBackgroundPadding);
+ } else {
+ mBackgroundPadding.setEmpty();
+ }
+ drawableChanged();
+ }
+ }
+
+ public void setWindowFrame(Drawable drawable) {
+ if (getForeground() != drawable) {
+ setForeground(drawable);
+ if (drawable != null) {
+ drawable.getPadding(mFramePadding);
+ } else {
+ mFramePadding.setEmpty();
+ }
+ drawableChanged();
+ }
+ }
+
+ @Override
+ protected boolean fitSystemWindows(Rect insets) {
+ mFrameOffsets.set(insets);
+ if (getForeground() != null) {
+ drawableChanged();
+ }
+ return super.fitSystemWindows(insets);
+ }
+
+ private void drawableChanged() {
+ if (mChanging) {
+ return;
+ }
+
+ setPadding(mFramePadding.left + mBackgroundPadding.left, mFramePadding.top
+ + mBackgroundPadding.top, mFramePadding.right + mBackgroundPadding.right,
+ mFramePadding.bottom + mBackgroundPadding.bottom);
+ requestLayout();
+ invalidate();
+
+ int opacity = PixelFormat.OPAQUE;
+
+ // Note: if there is no background, we will assume opaque. The
+ // common case seems to be that an application sets there to be
+ // no background so it can draw everything itself. For that,
+ // we would like to assume OPAQUE and let the app force it to
+ // the slower TRANSLUCENT mode if that is really what it wants.
+ Drawable bg = getBackground();
+ Drawable fg = getForeground();
+ if (bg != null) {
+ if (fg == null) {
+ opacity = bg.getOpacity();
+ } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0
+ && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) {
+ // If the frame padding is zero, then we can be opaque
+ // if either the frame -or- the background is opaque.
+ int fop = fg.getOpacity();
+ int bop = bg.getOpacity();
+ if (Config.LOGV)
+ Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop);
+ if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) {
+ opacity = PixelFormat.OPAQUE;
+ } else if (fop == PixelFormat.UNKNOWN) {
+ opacity = bop;
+ } else if (bop == PixelFormat.UNKNOWN) {
+ opacity = fop;
+ } else {
+ opacity = Drawable.resolveOpacity(fop, bop);
+ }
+ } else {
+ // For now we have to assume translucent if there is a
+ // frame with padding... there is no way to tell if the
+ // frame and background together will draw all pixels.
+ if (Config.LOGV)
+ Log.v(TAG, "Padding: " + mFramePadding);
+ opacity = PixelFormat.TRANSLUCENT;
+ }
+ }
+
+ if (Config.LOGV)
+ Log.v(TAG, "Background: " + bg + ", Frame: " + fg);
+ if (Config.LOGV)
+ Log.v(TAG, "Selected default opacity: " + opacity);
+
+ mDefaultOpacity = opacity;
+ if (mFeatureId < 0) {
+ setDefaultWindowFormat(opacity);
+ }
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+
+ mPanelMayLongPress = false;
+
+ // If the user is chording a menu shortcut, release the chord since
+ // this window lost focus
+ if (!hasWindowFocus && mPanelChordingKey != 0) {
+ closePanel(FEATURE_OPTIONS_PANEL);
+ }
+
+ final Callback cb = getCallback();
+ if (cb != null && mFeatureId < 0) {
+ cb.onWindowFocusChanged(hasWindowFocus);
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ final Callback cb = getCallback();
+ if (cb != null && mFeatureId < 0) {
+ cb.onAttachedToWindow();
+ }
+
+ if (mFeatureId == -1) {
+ /*
+ * The main window has been attached, try to restore any panels
+ * that may have been open before. This is called in cases where
+ * an activity is being killed for configuration change and the
+ * menu was open. When the activity is recreated, the menu
+ * should be shown again.
+ */
+ openPanelsAfterRestore();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ final Callback cb = getCallback();
+ if (cb != null && mFeatureId < 0) {
+ cb.onDetachedFromWindow();
+ }
+ }
+
+ @Override
+ public void onCloseSystemDialogs(String reason) {
+ if (mFeatureId >= 0) {
+ closeAllPanels();
+ }
+ }
+ }
+
+ protected DecorView generateDecor() {
+ return new DecorView(getContext(), -1);
+ }
+
+ protected void setFeatureFromAttrs(int featureId, TypedArray attrs,
+ int drawableAttr, int alphaAttr) {
+ Drawable d = attrs.getDrawable(drawableAttr);
+ if (d != null) {
+ requestFeature(featureId);
+ setFeatureDefaultDrawable(featureId, d);
+ }
+ if ((getFeatures() & (1 << featureId)) != 0) {
+ int alpha = attrs.getInt(alphaAttr, -1);
+ if (alpha >= 0) {
+ setFeatureDrawableAlpha(featureId, alpha);
+ }
+ }
+ }
+
+ protected ViewGroup generateLayout(DecorView decor) {
+ // Apply data from current theme.
+
+ TypedArray a = getWindowStyle();
+
+ if (false) {
+ System.out.println("From style:");
+ String s = "Attrs:";
+ for (int i = 0; i < com.android.internal.R.styleable.Window.length; i++) {
+ s = s + " " + Integer.toHexString(com.android.internal.R.styleable.Window[i]) + "="
+ + a.getString(i);
+ }
+ System.out.println(s);
+ }
+
+ mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);
+ int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
+ & (~getForcedWindowFlags());
+ if (mIsFloating) {
+ setLayout(WRAP_CONTENT, WRAP_CONTENT);
+ setFlags(0, flagsToUpdate);
+ } else {
+ setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
+ }
+
+ if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
+ requestFeature(FEATURE_NO_TITLE);
+ }
+
+ if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
+ setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN&(~getForcedWindowFlags()));
+ }
+
+ if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {
+ setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
+ }
+
+ WindowManager.LayoutParams params = getAttributes();
+
+ if (!hasSoftInputMode()) {
+ params.softInputMode = a.getInt(
+ com.android.internal.R.styleable.Window_windowSoftInputMode,
+ params.softInputMode);
+ }
+
+ if (a.getBoolean(com.android.internal.R.styleable.Window_backgroundDimEnabled,
+ mIsFloating)) {
+ /* All dialogs should have the window dimmed */
+ if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
+ params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+ }
+ params.dimAmount = a.getFloat(
+ android.R.styleable.Window_backgroundDimAmount, 0.5f);
+ }
+
+ if (params.windowAnimations == 0) {
+ params.windowAnimations = a.getResourceId(
+ com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
+ }
+
+ // The rest are only done if this window is not embedded; otherwise,
+ // the values are inherited from our container.
+ if (getContainer() == null) {
+ if (mBackgroundDrawable == null) {
+ if (mBackgroundResource == 0) {
+ mBackgroundResource = a.getResourceId(
+ com.android.internal.R.styleable.Window_windowBackground, 0);
+ }
+ if (mFrameResource == 0) {
+ mFrameResource = a.getResourceId(com.android.internal.R.styleable.Window_windowFrame, 0);
+ }
+ if (false) {
+ System.out.println("Background: "
+ + Integer.toHexString(mBackgroundResource) + " Frame: "
+ + Integer.toHexString(mFrameResource));
+ }
+ }
+ mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000);
+ }
+
+ // Inflate the window decor.
+
+ int layoutResource;
+ int features = getLocalFeatures();
+ // System.out.println("Features: 0x" + Integer.toHexString(features));
+ if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
+ if (mIsFloating) {
+ layoutResource = com.android.internal.R.layout.dialog_title_icons;
+ } else {
+ layoutResource = com.android.internal.R.layout.screen_title_icons;
+ }
+ // System.out.println("Title Icons!");
+ } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) {
+ // Special case for a window with only a progress bar (and title).
+ // XXX Need to have a no-title version of embedded windows.
+ layoutResource = com.android.internal.R.layout.screen_progress;
+ // System.out.println("Progress!");
+ } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
+ // Special case for a window with a custom title.
+ // If the window is floating, we need a dialog layout
+ if (mIsFloating) {
+ layoutResource = com.android.internal.R.layout.dialog_custom_title;
+ } else {
+ layoutResource = com.android.internal.R.layout.screen_custom_title;
+ }
+ } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
+ // If no other features and not embedded, only need a title.
+ // If the window is floating, we need a dialog layout
+ if (mIsFloating) {
+ layoutResource = com.android.internal.R.layout.dialog_title;
+ } else {
+ layoutResource = com.android.internal.R.layout.screen_title;
+ }
+ // System.out.println("Title!");
+ } else {
+ // Embedded, so no decoration is needed.
+ layoutResource = com.android.internal.R.layout.screen_simple;
+ // System.out.println("Simple!");
+ }
+
+ mDecor.startChanging();
+
+ View in = mLayoutInflater.inflate(layoutResource, null);
+ decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+ ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
+ if (contentParent == null) {
+ throw new RuntimeException("Window couldn't find content container view");
+ }
+
+ if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
+ ProgressBar progress = getCircularProgressBar(false);
+ if (progress != null) {
+ progress.setIndeterminate(true);
+ }
+ }
+
+ // Remaining setup -- of background and title -- that only applies
+ // to top-level windows.
+ if (getContainer() == null) {
+ Drawable drawable = mBackgroundDrawable;
+ if (mBackgroundResource != 0) {
+ drawable = getContext().getResources().getDrawable(mBackgroundResource);
+ }
+ mDecor.setWindowBackground(drawable);
+ drawable = null;
+ if (mFrameResource != 0) {
+ drawable = getContext().getResources().getDrawable(mFrameResource);
+ }
+ mDecor.setWindowFrame(drawable);
+
+ // System.out.println("Text=" + Integer.toHexString(mTextColor) +
+ // " Sel=" + Integer.toHexString(mTextSelectedColor) +
+ // " Title=" + Integer.toHexString(mTitleColor));
+
+ if (mTitleColor == 0) {
+ mTitleColor = mTextColor;
+ }
+
+ if (mTitle != null) {
+ setTitle(mTitle);
+ }
+ setTitleColor(mTitleColor);
+ }
+
+ mDecor.finishChanging();
+
+ return contentParent;
+ }
+
+ private void installDecor() {
+ if (mDecor == null) {
+ mDecor = generateDecor();
+ mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ mDecor.setIsRootNamespace(true);
+ }
+ if (mContentParent == null) {
+ mContentParent = generateLayout(mDecor);
+
+ mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
+ if (mTitleView != null) {
+ if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
+ View titleContainer = findViewById(com.android.internal.R.id.title_container);
+ if (titleContainer != null) {
+ titleContainer.setVisibility(View.GONE);
+ } else {
+ mTitleView.setVisibility(View.GONE);
+ }
+ if (mContentParent instanceof FrameLayout) {
+ ((FrameLayout)mContentParent).setForeground(null);
+ }
+ } else {
+ mTitleView.setText(mTitle);
+ }
+ }
+ }
+ }
+
+ private Drawable loadImageURI(Uri uri) {
+ try {
+ return Drawable.createFromStream(
+ getContext().getContentResolver().openInputStream(uri), null);
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to open content: " + uri);
+ }
+ return null;
+ }
+
+ private DrawableFeatureState getDrawableState(int featureId, boolean required) {
+ if ((getFeatures() & (1 << featureId)) == 0) {
+ if (!required) {
+ return null;
+ }
+ throw new RuntimeException("The feature has not been requested");
+ }
+
+ DrawableFeatureState[] ar;
+ if ((ar = mDrawables) == null || ar.length <= featureId) {
+ DrawableFeatureState[] nar = new DrawableFeatureState[featureId + 1];
+ if (ar != null) {
+ System.arraycopy(ar, 0, nar, 0, ar.length);
+ }
+ mDrawables = ar = nar;
+ }
+
+ DrawableFeatureState st = ar[featureId];
+ if (st == null) {
+ ar[featureId] = st = new DrawableFeatureState(featureId);
+ }
+ return st;
+ }
+
+ /**
+ * Gets a panel's state based on its feature ID.
+ *
+ * @param featureId The feature ID of the panel.
+ * @param required Whether the panel is required (if it is required and it
+ * isn't in our features, this throws an exception).
+ * @return The panel state.
+ */
+ private PanelFeatureState getPanelState(int featureId, boolean required) {
+ return getPanelState(featureId, required, null);
+ }
+
+ /**
+ * Gets a panel's state based on its feature ID.
+ *
+ * @param featureId The feature ID of the panel.
+ * @param required Whether the panel is required (if it is required and it
+ * isn't in our features, this throws an exception).
+ * @param convertPanelState Optional: If the panel state does not exist, use
+ * this as the panel state.
+ * @return The panel state.
+ */
+ private PanelFeatureState getPanelState(int featureId, boolean required,
+ PanelFeatureState convertPanelState) {
+ if ((getFeatures() & (1 << featureId)) == 0) {
+ if (!required) {
+ return null;
+ }
+ throw new RuntimeException("The feature has not been requested");
+ }
+
+ PanelFeatureState[] ar;
+ if ((ar = mPanels) == null || ar.length <= featureId) {
+ PanelFeatureState[] nar = new PanelFeatureState[featureId + 1];
+ if (ar != null) {
+ System.arraycopy(ar, 0, nar, 0, ar.length);
+ }
+ mPanels = ar = nar;
+ }
+
+ PanelFeatureState st = ar[featureId];
+ if (st == null) {
+ ar[featureId] = st = (convertPanelState != null)
+ ? convertPanelState
+ : new PanelFeatureState(featureId);
+ }
+ return st;
+ }
+
+ @Override
+ public final void setChildDrawable(int featureId, Drawable drawable) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ st.child = drawable;
+ updateDrawable(featureId, st, false);
+ }
+
+ @Override
+ public final void setChildInt(int featureId, int value) {
+ updateInt(featureId, value, false);
+ }
+
+ @Override
+ public boolean isShortcutKey(int keyCode, KeyEvent event) {
+ PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
+ return st.menu != null && st.menu.isShortcutKey(keyCode, event);
+ }
+
+ private void updateDrawable(int featureId, DrawableFeatureState st, boolean fromResume) {
+ // Do nothing if the decor is not yet installed... an update will
+ // need to be forced when we eventually become active.
+ if (mContentParent == null) {
+ return;
+ }
+
+ final int featureMask = 1 << featureId;
+
+ if ((getFeatures() & featureMask) == 0 && !fromResume) {
+ return;
+ }
+
+ Drawable drawable = null;
+ if (st != null) {
+ drawable = st.child;
+ if (drawable == null)
+ drawable = st.local;
+ if (drawable == null)
+ drawable = st.def;
+ }
+ if ((getLocalFeatures() & featureMask) == 0) {
+ if (getContainer() != null) {
+ if (isActive() || fromResume) {
+ getContainer().setChildDrawable(featureId, drawable);
+ }
+ }
+ } else if (st != null && (st.cur != drawable || st.curAlpha != st.alpha)) {
+ // System.out.println("Drawable changed: old=" + st.cur
+ // + ", new=" + drawable);
+ st.cur = drawable;
+ st.curAlpha = st.alpha;
+ onDrawableChanged(featureId, drawable, st.alpha);
+ }
+ }
+
+ private void updateInt(int featureId, int value, boolean fromResume) {
+
+ // Do nothing if the decor is not yet installed... an update will
+ // need to be forced when we eventually become active.
+ if (mContentParent == null) {
+ return;
+ }
+
+ final int featureMask = 1 << featureId;
+
+ if ((getFeatures() & featureMask) == 0 && !fromResume) {
+ return;
+ }
+
+ if ((getLocalFeatures() & featureMask) == 0) {
+ if (getContainer() != null) {
+ getContainer().setChildInt(featureId, value);
+ }
+ } else {
+ onIntChanged(featureId, value);
+ }
+ }
+
+ private ImageView getLeftIconView() {
+ if (mLeftIconView != null) {
+ return mLeftIconView;
+ }
+ if (mContentParent == null) {
+ installDecor();
+ }
+ return (mLeftIconView = (ImageView)findViewById(com.android.internal.R.id.left_icon));
+ }
+
+ private ProgressBar getCircularProgressBar(boolean shouldInstallDecor) {
+ if (mCircularProgressBar != null) {
+ return mCircularProgressBar;
+ }
+ if (mContentParent == null && shouldInstallDecor) {
+ installDecor();
+ }
+ mCircularProgressBar = (ProgressBar)findViewById(com.android.internal.R.id.progress_circular);
+ mCircularProgressBar.setVisibility(View.INVISIBLE);
+ return mCircularProgressBar;
+ }
+
+ private ProgressBar getHorizontalProgressBar(boolean shouldInstallDecor) {
+ if (mHorizontalProgressBar != null) {
+ return mHorizontalProgressBar;
+ }
+ if (mContentParent == null && shouldInstallDecor) {
+ installDecor();
+ }
+ mHorizontalProgressBar = (ProgressBar)findViewById(com.android.internal.R.id.progress_horizontal);
+ mHorizontalProgressBar.setVisibility(View.INVISIBLE);
+ return mHorizontalProgressBar;
+ }
+
+ private ImageView getRightIconView() {
+ if (mRightIconView != null) {
+ return mRightIconView;
+ }
+ if (mContentParent == null) {
+ installDecor();
+ }
+ return (mRightIconView = (ImageView)findViewById(com.android.internal.R.id.right_icon));
+ }
+
+ /**
+ * Helper method for calling the {@link Callback#onPanelClosed(int, Menu)}
+ * callback. This method will grab whatever extra state is needed for the
+ * callback that isn't given in the parameters. If the panel is not open,
+ * this will not perform the callback.
+ *
+ * @param featureId Feature ID of the panel that was closed. Must be given.
+ * @param panel Panel that was closed. Optional but useful if there is no
+ * menu given.
+ * @param menu The menu that was closed. Optional, but give if you have.
+ */
+ private void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) {
+ final Callback cb = getCallback();
+ if (cb == null)
+ return;
+
+ // Try to get a menu
+ if (menu == null) {
+ // Need a panel to grab the menu, so try to get that
+ if (panel == null) {
+ if ((featureId >= 0) && (featureId < mPanels.length)) {
+ panel = mPanels[featureId];
+ }
+ }
+
+ if (panel != null) {
+ // menu still may be null, which is okay--we tried our best
+ menu = panel.menu;
+ }
+ }
+
+ // If the panel is not open, do not callback
+ if ((panel != null) && (!panel.isOpen))
+ return;
+
+ cb.onPanelClosed(featureId, menu);
+ }
+
+ /**
+ * Helper method for adding launch-search to most applications. Opens the
+ * search window using default settings.
+ *
+ * @return true if search window opened
+ */
+ private boolean launchDefaultSearch() {
+ final Callback cb = getCallback();
+ if (cb == null) {
+ return false;
+ } else {
+ sendCloseSystemWindows("search");
+ return cb.onSearchRequested();
+ }
+ }
+
+ @Override
+ public void setVolumeControlStream(int streamType) {
+ mVolumeControlStreamType = streamType;
+ }
+
+ @Override
+ public int getVolumeControlStream() {
+ return mVolumeControlStreamType;
+ }
+
+ private static final class DrawableFeatureState {
+ DrawableFeatureState(int _featureId) {
+ featureId = _featureId;
+ }
+
+ final int featureId;
+
+ int resid;
+
+ Uri uri;
+
+ Drawable local;
+
+ Drawable child;
+
+ Drawable def;
+
+ Drawable cur;
+
+ int alpha = 255;
+
+ int curAlpha = 255;
+ }
+
+ private static final class PanelFeatureState {
+
+ /** Feature ID for this panel. */
+ int featureId;
+
+ // Information pulled from the style for this panel.
+
+ int background;
+
+ /** The background when the panel spans the entire available width. */
+ int fullBackground;
+
+ int gravity;
+
+ int x;
+
+ int y;
+
+ int windowAnimations;
+
+ /** Dynamic state of the panel. */
+ DecorView decorView;
+
+ /** The panel that was returned by onCreatePanelView(). */
+ View createdPanelView;
+
+ /** The panel that we are actually showing. */
+ View shownPanelView;
+
+ /** Use {@link #setMenu} to set this. */
+ Menu menu;
+
+ /**
+ * Whether the panel has been prepared (see
+ * {@link PhoneWindow#preparePanel}).
+ */
+ boolean isPrepared;
+
+ /**
+ * Whether an item's action has been performed. This happens in obvious
+ * scenarios (user clicks on menu item), but can also happen with
+ * chording menu+(shortcut key).
+ */
+ boolean isHandled;
+
+ boolean isOpen;
+
+ /**
+ * True if the menu is in expanded mode, false if the menu is in icon
+ * mode
+ */
+ boolean isInExpandedMode;
+
+ public boolean qwertyMode;
+
+ boolean refreshDecorView;
+
+ boolean wasLastOpen;
+
+ boolean wasLastExpanded;
+
+ /**
+ * Contains the state of the menu when told to freeze.
+ */
+ Bundle frozenMenuState;
+
+ PanelFeatureState(int featureId) {
+ this.featureId = featureId;
+
+ refreshDecorView = false;
+ }
+
+ void setStyle(Context context) {
+ TypedArray a = context.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
+ background = a.getResourceId(
+ com.android.internal.R.styleable.Theme_panelBackground, 0);
+ fullBackground = a.getResourceId(
+ com.android.internal.R.styleable.Theme_panelFullBackground, 0);
+ windowAnimations = a.getResourceId(
+ com.android.internal.R.styleable.Theme_windowAnimationStyle, 0);
+ a.recycle();
+ }
+
+ void setMenu(Menu menu) {
+ this.menu = menu;
+
+ if (frozenMenuState != null) {
+ ((MenuBuilder) menu).restoreHierarchyState(frozenMenuState);
+ frozenMenuState = null;
+ }
+ }
+
+ Parcelable onSaveInstanceState() {
+ SavedState savedState = new SavedState();
+ savedState.featureId = featureId;
+ savedState.isOpen = isOpen;
+ savedState.isInExpandedMode = isInExpandedMode;
+
+ if (menu != null) {
+ savedState.menuState = new Bundle();
+ ((MenuBuilder) menu).saveHierarchyState(savedState.menuState);
+ }
+
+ return savedState;
+ }
+
+ void onRestoreInstanceState(Parcelable state) {
+ SavedState savedState = (SavedState) state;
+ featureId = savedState.featureId;
+ wasLastOpen = savedState.isOpen;
+ wasLastExpanded = savedState.isInExpandedMode;
+ frozenMenuState = savedState.menuState;
+
+ /*
+ * A LocalActivityManager keeps the same instance of this class around.
+ * The first time the menu is being shown after restoring, the
+ * Activity.onCreateOptionsMenu should be called. But, if it is the
+ * same instance then menu != null and we won't call that method.
+ * So, clear this. Also clear any cached views.
+ */
+ menu = null;
+ createdPanelView = null;
+ shownPanelView = null;
+ decorView = null;
+ }
+
+ private static class SavedState implements Parcelable {
+ int featureId;
+ boolean isOpen;
+ boolean isInExpandedMode;
+ Bundle menuState;
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(featureId);
+ dest.writeInt(isOpen ? 1 : 0);
+ dest.writeInt(isInExpandedMode ? 1 : 0);
+
+ if (isOpen) {
+ dest.writeBundle(menuState);
+ }
+ }
+
+ private static SavedState readFromParcel(Parcel source) {
+ SavedState savedState = new SavedState();
+ savedState.featureId = source.readInt();
+ savedState.isOpen = source.readInt() == 1;
+ savedState.isInExpandedMode = source.readInt() == 1;
+
+ if (savedState.isOpen) {
+ savedState.menuState = source.readBundle();
+ }
+
+ return savedState;
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR
+ = new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return readFromParcel(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ }
+
+ /**
+ * Simple implementation of MenuBuilder.Callback that:
+ * <li> Opens a submenu when selected.
+ * <li> Calls back to the callback's onMenuItemSelected when an item is
+ * selected.
+ */
+ private final class ContextMenuCallback implements MenuBuilder.Callback {
+ private int mFeatureId;
+ private MenuDialogHelper mSubMenuHelper;
+
+ public ContextMenuCallback(int featureId) {
+ mFeatureId = featureId;
+ }
+
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (allMenusAreClosing) {
+ Callback callback = getCallback();
+ if (callback != null) callback.onPanelClosed(mFeatureId, menu);
+
+ if (menu == mContextMenu) {
+ dismissContextMenu();
+ }
+
+ // Dismiss the submenu, if it is showing
+ if (mSubMenuHelper != null) {
+ mSubMenuHelper.dismiss();
+ mSubMenuHelper = null;
+ }
+ }
+ }
+
+ public void onCloseSubMenu(SubMenuBuilder menu) {
+ Callback callback = getCallback();
+ if (callback != null) callback.onPanelClosed(mFeatureId, menu.getRootMenu());
+ }
+
+ public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ Callback callback = getCallback();
+ return (callback != null) && callback.onMenuItemSelected(mFeatureId, item);
+ }
+
+ public void onMenuModeChange(MenuBuilder menu) {
+ }
+
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ // Set a simple callback for the submenu
+ subMenu.setCallback(this);
+
+ // The window manager will give us a valid window token
+ mSubMenuHelper = new MenuDialogHelper(subMenu);
+ mSubMenuHelper.show(null);
+
+ return true;
+ }
+ }
+
+ void sendCloseSystemWindows() {
+ PhoneWindowManager.sendCloseSystemWindows(getContext(), null);
+ }
+
+ void sendCloseSystemWindows(String reason) {
+ PhoneWindowManager.sendCloseSystemWindows(getContext(), reason);
+ }
+}
diff --git a/policy/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/com/android/internal/policy/impl/PhoneWindowManager.java
new file mode 100755
index 0000000..cfdce5a
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -0,0 +1,2391 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl;
+
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.IStatusBar;
+import android.app.IUiModeManager;
+import android.app.UiModeManager;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.LocalPowerManager;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Vibrator;
+import android.provider.Settings;
+
+import com.android.internal.policy.PolicyManager;
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.widget.PointerLocationView;
+
+import android.util.Config;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
+import android.view.IWindowManager;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.WindowOrientationListener;
+import android.view.RawInputEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.Window;
+import android.view.WindowManager;
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+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_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_SEARCH_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import android.view.WindowManagerImpl;
+import android.view.WindowManagerPolicy;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.media.IAudioService;
+import android.media.AudioManager;
+
+import java.util.ArrayList;
+
+/**
+ * WindowManagerPolicy implementation for the Android phone UI. This
+ * introduces a new method suffix, Lp, for an internal lock of the
+ * PhoneWindowManager. This is used to protect some internal state, and
+ * can be acquired with either thw Lw and Li lock held, so has the restrictions
+ * of both of those when held.
+ */
+public class PhoneWindowManager implements WindowManagerPolicy {
+ static final String TAG = "WindowManager";
+ static final boolean DEBUG = false;
+ static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ static final boolean DEBUG_LAYOUT = false;
+ static final boolean SHOW_STARTING_ANIMATIONS = true;
+ static final boolean SHOW_PROCESSES_ON_ALT_MENU = false;
+
+ // wallpaper is at the bottom, though the window manager may move it.
+ static final int WALLPAPER_LAYER = 2;
+ static final int APPLICATION_LAYER = 2;
+ static final int PHONE_LAYER = 3;
+ static final int SEARCH_BAR_LAYER = 4;
+ static final int STATUS_BAR_PANEL_LAYER = 5;
+ static final int SYSTEM_DIALOG_LAYER = 6;
+ // toasts and the plugged-in battery thing
+ static final int TOAST_LAYER = 7;
+ static final int STATUS_BAR_LAYER = 8;
+ // SIM errors and unlock. Not sure if this really should be in a high layer.
+ static final int PRIORITY_PHONE_LAYER = 9;
+ // like the ANR / app crashed dialogs
+ static final int SYSTEM_ALERT_LAYER = 10;
+ // system-level error dialogs
+ static final int SYSTEM_ERROR_LAYER = 11;
+ // on-screen keyboards and other such input method user interfaces go here.
+ static final int INPUT_METHOD_LAYER = 12;
+ // on-screen keyboards and other such input method user interfaces go here.
+ static final int INPUT_METHOD_DIALOG_LAYER = 13;
+ // the keyguard; nothing on top of these can take focus, since they are
+ // responsible for power management when displayed.
+ static final int KEYGUARD_LAYER = 14;
+ static final int KEYGUARD_DIALOG_LAYER = 15;
+ // things in here CAN NOT take focus, but are shown on top of everything else.
+ static final int SYSTEM_OVERLAY_LAYER = 16;
+
+ static final int APPLICATION_MEDIA_SUBLAYER = -2;
+ static final int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1;
+ static final int APPLICATION_PANEL_SUBLAYER = 1;
+ static final int APPLICATION_SUB_PANEL_SUBLAYER = 2;
+
+ static final float SLIDE_TOUCH_EVENT_SIZE_LIMIT = 0.6f;
+
+ // Debugging: set this to have the system act like there is no hard keyboard.
+ static final boolean KEYBOARD_ALWAYS_HIDDEN = false;
+
+ static public final String SYSTEM_DIALOG_REASON_KEY = "reason";
+ static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
+ static public final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
+ static public final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
+
+ final Object mLock = new Object();
+
+ Context mContext;
+ IWindowManager mWindowManager;
+ LocalPowerManager mPowerManager;
+ Vibrator mVibrator; // Vibrator for giving feedback of orientation changes
+
+ // Vibrator pattern for haptic feedback of a long press.
+ long[] mLongPressVibePattern;
+
+ // Vibrator pattern for haptic feedback of virtual key press.
+ long[] mVirtualKeyVibePattern;
+
+ // Vibrator pattern for a short vibration.
+ long[] mKeyboardTapVibePattern;
+
+ // Vibrator pattern for haptic feedback during boot when safe mode is disabled.
+ long[] mSafeModeDisabledVibePattern;
+
+ // Vibrator pattern for haptic feedback during boot when safe mode is enabled.
+ long[] mSafeModeEnabledVibePattern;
+
+ /** If true, hitting shift & menu will broadcast Intent.ACTION_BUG_REPORT */
+ boolean mEnableShiftMenuBugReports = false;
+
+ boolean mSafeMode;
+ WindowState mStatusBar = null;
+ final ArrayList<WindowState> mStatusBarPanels = new ArrayList<WindowState>();
+ WindowState mKeyguard = null;
+ KeyguardViewMediator mKeyguardMediator;
+ GlobalActions mGlobalActions;
+ boolean mShouldTurnOffOnKeyUp;
+ RecentApplicationsDialog mRecentAppsDialog;
+ Handler mHandler;
+
+ boolean mSystemReady;
+ boolean mLidOpen;
+ int mUiMode = Configuration.UI_MODE_TYPE_NORMAL;
+ int mLidOpenRotation;
+ int mCarDockRotation;
+ int mDeskDockRotation;
+ boolean mCarDockEnablesAccelerometer;
+ boolean mDeskDockEnablesAccelerometer;
+ int mLidKeyboardAccessibility;
+ int mLidNavigationAccessibility;
+ boolean mScreenOn = false;
+ boolean mOrientationSensorEnabled = false;
+ int mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ static final int DEFAULT_ACCELEROMETER_ROTATION = 0;
+ int mAccelerometerDefault = DEFAULT_ACCELEROMETER_ROTATION;
+ boolean mHasSoftInput = false;
+
+ int mPointerLocationMode = 0;
+ PointerLocationView mPointerLocationView = null;
+
+ // The current size of the screen.
+ int mW, mH;
+ // During layout, the current screen borders with all outer decoration
+ // (status bar, input method dock) accounted for.
+ int mCurLeft, mCurTop, mCurRight, mCurBottom;
+ // During layout, the frame in which content should be displayed
+ // to the user, accounting for all screen decoration except for any
+ // space they deem as available for other content. This is usually
+ // the same as mCur*, but may be larger if the screen decor has supplied
+ // content insets.
+ int mContentLeft, mContentTop, mContentRight, mContentBottom;
+ // During layout, the current screen borders along with input method
+ // windows are placed.
+ int mDockLeft, mDockTop, mDockRight, mDockBottom;
+ // During layout, the layer at which the doc window is placed.
+ int mDockLayer;
+
+ static final Rect mTmpParentFrame = new Rect();
+ static final Rect mTmpDisplayFrame = new Rect();
+ static final Rect mTmpContentFrame = new Rect();
+ static final Rect mTmpVisibleFrame = new Rect();
+
+ WindowState mTopFullscreenOpaqueWindowState;
+ boolean mForceStatusBar;
+ boolean mHideLockScreen;
+ boolean mDismissKeyguard;
+ boolean mHomePressed;
+ Intent mHomeIntent;
+ Intent mCarDockIntent;
+ Intent mDeskDockIntent;
+ boolean mSearchKeyPressed;
+ boolean mConsumeSearchKeyUp;
+
+ // support for activating the lock screen while the screen is on
+ boolean mAllowLockscreenWhenOn;
+ int mLockScreenTimeout;
+ boolean mLockScreenTimerActive;
+
+ // Behavior of ENDCALL Button. (See Settings.System.END_BUTTON_BEHAVIOR.)
+ int mEndcallBehavior;
+
+ // Behavior of POWER button while in-call and screen on.
+ // (See Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR.)
+ int mIncallPowerBehavior;
+
+ int mLandscapeRotation = -1;
+ int mPortraitRotation = -1;
+
+ // Nothing to see here, move along...
+ int mFancyRotationAnimation;
+
+ ShortcutManager mShortcutManager;
+ PowerManager.WakeLock mBroadcastWakeLock;
+
+ class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ void observe() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.END_BUTTON_BEHAVIOR), false, this);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR), false, this);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.ACCELEROMETER_ROTATION), false, this);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.SCREEN_OFF_TIMEOUT), false, this);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.POINTER_LOCATION), false, this);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ "fancy_rotation_anim"), false, this);
+ updateSettings();
+ }
+
+ @Override public void onChange(boolean selfChange) {
+ updateSettings();
+ try {
+ mWindowManager.setRotation(USE_LAST_ROTATION, false,
+ mFancyRotationAnimation);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+ }
+
+ class MyOrientationListener extends WindowOrientationListener {
+ MyOrientationListener(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onOrientationChanged(int rotation) {
+ // Send updates based on orientation value
+ if (localLOGV) Log.v(TAG, "onOrientationChanged, rotation changed to " +rotation);
+ try {
+ mWindowManager.setRotation(rotation, false,
+ mFancyRotationAnimation);
+ } catch (RemoteException e) {
+ // Ignore
+
+ }
+ }
+ }
+ MyOrientationListener mOrientationListener;
+
+ boolean useSensorForOrientationLp(int appOrientation) {
+ // The app says use the sensor.
+ if (appOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR) {
+ return true;
+ }
+ // The user preference says we can rotate, and the app is willing to rotate.
+ if (mAccelerometerDefault != 0 &&
+ (appOrientation == ActivityInfo.SCREEN_ORIENTATION_USER
+ || appOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)) {
+ return true;
+ }
+ // We're in a dock that has a rotation affinity, an the app is willing to rotate.
+ if ((mCarDockEnablesAccelerometer && mUiMode == Configuration.UI_MODE_TYPE_CAR)
+ || (mDeskDockEnablesAccelerometer && mUiMode == Configuration.UI_MODE_TYPE_DESK)) {
+ // Note we override the nosensor flag here.
+ if (appOrientation == ActivityInfo.SCREEN_ORIENTATION_USER
+ || appOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+ || appOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
+ return true;
+ }
+ }
+ // Else, don't use the sensor.
+ return false;
+ }
+
+ /*
+ * We always let the sensor be switched on by default except when
+ * the user has explicitly disabled sensor based rotation or when the
+ * screen is switched off.
+ */
+ boolean needSensorRunningLp() {
+ if (mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR) {
+ // If the application has explicitly requested to follow the
+ // orientation, then we need to turn the sensor or.
+ return true;
+ }
+ if ((mCarDockEnablesAccelerometer && mUiMode == Configuration.UI_MODE_TYPE_CAR) ||
+ (mDeskDockEnablesAccelerometer && mUiMode == Configuration.UI_MODE_TYPE_DESK)) {
+ // enable accelerometer if we are docked in a dock that enables accelerometer
+ // orientation management,
+ return true;
+ }
+ if (mAccelerometerDefault == 0) {
+ // If the setting for using the sensor by default is enabled, then
+ // we will always leave it on. Note that the user could go to
+ // a window that forces an orientation that does not use the
+ // sensor and in theory we could turn it off... however, when next
+ // turning it on we won't have a good value for the current
+ // orientation for a little bit, which can cause orientation
+ // changes to lag, so we'd like to keep it always on. (It will
+ // still be turned off when the screen is off.)
+ return false;
+ }
+ return true;
+ }
+
+ /*
+ * Various use cases for invoking this function
+ * screen turning off, should always disable listeners if already enabled
+ * screen turned on and current app has sensor based orientation, enable listeners
+ * if not already enabled
+ * screen turned on and current app does not have sensor orientation, disable listeners if
+ * already enabled
+ * screen turning on and current app has sensor based orientation, enable listeners if needed
+ * screen turning on and current app has nosensor based orientation, do nothing
+ */
+ void updateOrientationListenerLp() {
+ if (!mOrientationListener.canDetectOrientation()) {
+ // If sensor is turned off or nonexistent for some reason
+ return;
+ }
+ //Could have been invoked due to screen turning on or off or
+ //change of the currently visible window's orientation
+ if (localLOGV) Log.v(TAG, "Screen status="+mScreenOn+
+ ", current orientation="+mCurrentAppOrientation+
+ ", SensorEnabled="+mOrientationSensorEnabled);
+ boolean disable = true;
+ if (mScreenOn) {
+ if (needSensorRunningLp()) {
+ disable = false;
+ //enable listener if not already enabled
+ if (!mOrientationSensorEnabled) {
+ mOrientationListener.enable();
+ if(localLOGV) Log.v(TAG, "Enabling listeners");
+ mOrientationSensorEnabled = true;
+ }
+ }
+ }
+ //check if sensors need to be disabled
+ if (disable && mOrientationSensorEnabled) {
+ mOrientationListener.disable();
+ if(localLOGV) Log.v(TAG, "Disabling listeners");
+ mOrientationSensorEnabled = false;
+ }
+ }
+
+ Runnable mPowerLongPress = new Runnable() {
+ public void run() {
+ mShouldTurnOffOnKeyUp = false;
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
+ showGlobalActionsDialog();
+ }
+ };
+
+ void showGlobalActionsDialog() {
+ if (mGlobalActions == null) {
+ mGlobalActions = new GlobalActions(mContext);
+ }
+ final boolean keyguardShowing = mKeyguardMediator.isShowingAndNotHidden();
+ mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
+ if (keyguardShowing) {
+ // since it took two seconds of long press to bring this up,
+ // poke the wake lock so they have some time to see the dialog.
+ mKeyguardMediator.pokeWakelock();
+ }
+ }
+
+ boolean isDeviceProvisioned() {
+ return Settings.Secure.getInt(
+ mContext.getContentResolver(), Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
+ }
+
+ /**
+ * When a home-key longpress expires, close other system windows and launch the recent apps
+ */
+ Runnable mHomeLongPress = new Runnable() {
+ public void run() {
+ /*
+ * Eat the longpress so it won't dismiss the recent apps dialog when
+ * the user lets go of the home key
+ */
+ mHomePressed = false;
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
+ showRecentAppsDialog();
+ }
+ };
+
+ /**
+ * Create (if necessary) and launch the recent apps dialog
+ */
+ void showRecentAppsDialog() {
+ if (mRecentAppsDialog == null) {
+ mRecentAppsDialog = new RecentApplicationsDialog(mContext);
+ }
+ mRecentAppsDialog.show();
+ }
+
+ /** {@inheritDoc} */
+ public void init(Context context, IWindowManager windowManager,
+ LocalPowerManager powerManager) {
+ mContext = context;
+ mWindowManager = windowManager;
+ mPowerManager = powerManager;
+ mKeyguardMediator = new KeyguardViewMediator(context, this, powerManager);
+ mHandler = new Handler();
+ mOrientationListener = new MyOrientationListener(mContext);
+ SettingsObserver settingsObserver = new SettingsObserver(mHandler);
+ settingsObserver.observe();
+ mShortcutManager = new ShortcutManager(context, mHandler);
+ mShortcutManager.observe();
+ mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
+ mHomeIntent.addCategory(Intent.CATEGORY_HOME);
+ mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ mCarDockIntent = new Intent(Intent.ACTION_MAIN, null);
+ mCarDockIntent.addCategory(Intent.CATEGORY_CAR_DOCK);
+ mCarDockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ mDeskDockIntent = new Intent(Intent.ACTION_MAIN, null);
+ mDeskDockIntent.addCategory(Intent.CATEGORY_DESK_DOCK);
+ mDeskDockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ mBroadcastWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "PhoneWindowManager.mBroadcastWakeLock");
+ mEnableShiftMenuBugReports = "1".equals(SystemProperties.get("ro.debuggable"));
+ mLidOpenRotation = readRotation(
+ com.android.internal.R.integer.config_lidOpenRotation);
+ mCarDockRotation = readRotation(
+ com.android.internal.R.integer.config_carDockRotation);
+ mDeskDockRotation = readRotation(
+ com.android.internal.R.integer.config_deskDockRotation);
+ mCarDockEnablesAccelerometer = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_carDockEnablesAccelerometer);
+ mDeskDockEnablesAccelerometer = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_deskDockEnablesAccelerometer);
+ mLidKeyboardAccessibility = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_lidKeyboardAccessibility);
+ mLidNavigationAccessibility = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_lidNavigationAccessibility);
+ // register for dock events
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE);
+ filter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE);
+ filter.addAction(UiModeManager.ACTION_ENTER_DESK_MODE);
+ filter.addAction(UiModeManager.ACTION_EXIT_DESK_MODE);
+ context.registerReceiver(mDockReceiver, filter);
+ mVibrator = new Vibrator();
+ mLongPressVibePattern = getLongIntArray(mContext.getResources(),
+ com.android.internal.R.array.config_longPressVibePattern);
+ mVirtualKeyVibePattern = getLongIntArray(mContext.getResources(),
+ com.android.internal.R.array.config_virtualKeyVibePattern);
+ mKeyboardTapVibePattern = getLongIntArray(mContext.getResources(),
+ com.android.internal.R.array.config_keyboardTapVibePattern);
+ mSafeModeDisabledVibePattern = getLongIntArray(mContext.getResources(),
+ com.android.internal.R.array.config_safeModeDisabledVibePattern);
+ mSafeModeEnabledVibePattern = getLongIntArray(mContext.getResources(),
+ com.android.internal.R.array.config_safeModeEnabledVibePattern);
+ }
+
+ public void updateSettings() {
+ ContentResolver resolver = mContext.getContentResolver();
+ boolean updateRotation = false;
+ View addView = null;
+ View removeView = null;
+ synchronized (mLock) {
+ mEndcallBehavior = Settings.System.getInt(resolver,
+ Settings.System.END_BUTTON_BEHAVIOR,
+ Settings.System.END_BUTTON_BEHAVIOR_DEFAULT);
+ mIncallPowerBehavior = Settings.Secure.getInt(resolver,
+ Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
+ Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT);
+ mFancyRotationAnimation = Settings.System.getInt(resolver,
+ "fancy_rotation_anim", 0) != 0 ? 0x80 : 0;
+ int accelerometerDefault = Settings.System.getInt(resolver,
+ Settings.System.ACCELEROMETER_ROTATION, DEFAULT_ACCELEROMETER_ROTATION);
+ if (mAccelerometerDefault != accelerometerDefault) {
+ mAccelerometerDefault = accelerometerDefault;
+ updateOrientationListenerLp();
+ }
+ if (mSystemReady) {
+ int pointerLocation = Settings.System.getInt(resolver,
+ Settings.System.POINTER_LOCATION, 0);
+ if (mPointerLocationMode != pointerLocation) {
+ mPointerLocationMode = pointerLocation;
+ if (pointerLocation != 0) {
+ if (mPointerLocationView == null) {
+ mPointerLocationView = new PointerLocationView(mContext);
+ mPointerLocationView.setPrintCoords(false);
+ addView = mPointerLocationView;
+ }
+ } else {
+ removeView = mPointerLocationView;
+ mPointerLocationView = null;
+ }
+ }
+ }
+ // use screen off timeout setting as the timeout for the lockscreen
+ mLockScreenTimeout = Settings.System.getInt(resolver,
+ Settings.System.SCREEN_OFF_TIMEOUT, 0);
+ String imId = Settings.Secure.getString(resolver,
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+ boolean hasSoftInput = imId != null && imId.length() > 0;
+ if (mHasSoftInput != hasSoftInput) {
+ mHasSoftInput = hasSoftInput;
+ updateRotation = true;
+ }
+ }
+ if (updateRotation) {
+ updateRotation(0);
+ }
+ if (addView != null) {
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.MATCH_PARENT);
+ lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+ lp.flags =
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+ lp.format = PixelFormat.TRANSLUCENT;
+ lp.setTitle("PointerLocation");
+ WindowManagerImpl wm = (WindowManagerImpl)
+ mContext.getSystemService(Context.WINDOW_SERVICE);
+ wm.addView(addView, lp);
+ }
+ if (removeView != null) {
+ WindowManagerImpl wm = (WindowManagerImpl)
+ mContext.getSystemService(Context.WINDOW_SERVICE);
+ wm.removeView(removeView);
+ }
+ }
+
+ private int readRotation(int resID) {
+ try {
+ int rotation = mContext.getResources().getInteger(resID);
+ switch (rotation) {
+ case 0:
+ return Surface.ROTATION_0;
+ case 90:
+ return Surface.ROTATION_90;
+ case 180:
+ return Surface.ROTATION_180;
+ case 270:
+ return Surface.ROTATION_270;
+ }
+ } catch (Resources.NotFoundException e) {
+ // fall through
+ }
+ return -1;
+ }
+
+ /** {@inheritDoc} */
+ public int checkAddPermission(WindowManager.LayoutParams attrs) {
+ int type = attrs.type;
+
+ if (type < WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW
+ || type > WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
+ return WindowManagerImpl.ADD_OKAY;
+ }
+ String permission = null;
+ switch (type) {
+ case TYPE_TOAST:
+ // XXX right now the app process has complete control over
+ // this... should introduce a token to let the system
+ // monitor/control what they are doing.
+ break;
+ case TYPE_INPUT_METHOD:
+ case TYPE_WALLPAPER:
+ // The window manager will check these.
+ break;
+ case TYPE_PHONE:
+ case TYPE_PRIORITY_PHONE:
+ case TYPE_SYSTEM_ALERT:
+ case TYPE_SYSTEM_ERROR:
+ case TYPE_SYSTEM_OVERLAY:
+ permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW;
+ break;
+ default:
+ permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+ }
+ if (permission != null) {
+ if (mContext.checkCallingOrSelfPermission(permission)
+ != PackageManager.PERMISSION_GRANTED) {
+ return WindowManagerImpl.ADD_PERMISSION_DENIED;
+ }
+ }
+ return WindowManagerImpl.ADD_OKAY;
+ }
+
+ public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
+ switch (attrs.type) {
+ case TYPE_SYSTEM_OVERLAY:
+ case TYPE_TOAST:
+ // These types of windows can't receive input events.
+ attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ break;
+ }
+ }
+
+ void readLidState() {
+ try {
+ int sw = mWindowManager.getSwitchState(RawInputEvent.SW_LID);
+ if (sw >= 0) {
+ mLidOpen = sw == 0;
+ }
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+
+ private int determineHiddenState(boolean lidOpen,
+ int mode, int hiddenValue, int visibleValue) {
+ switch (mode) {
+ case 1:
+ return lidOpen ? visibleValue : hiddenValue;
+ case 2:
+ return lidOpen ? hiddenValue : visibleValue;
+ }
+ return visibleValue;
+ }
+
+ /** {@inheritDoc} */
+ public void adjustConfigurationLw(Configuration config) {
+ readLidState();
+ final boolean lidOpen = !KEYBOARD_ALWAYS_HIDDEN && mLidOpen;
+ mPowerManager.setKeyboardVisibility(lidOpen);
+ config.hardKeyboardHidden = determineHiddenState(lidOpen,
+ mLidKeyboardAccessibility, Configuration.HARDKEYBOARDHIDDEN_YES,
+ Configuration.HARDKEYBOARDHIDDEN_NO);
+ config.navigationHidden = determineHiddenState(lidOpen,
+ mLidNavigationAccessibility, Configuration.NAVIGATIONHIDDEN_YES,
+ Configuration.NAVIGATIONHIDDEN_NO);
+ config.keyboardHidden = (config.hardKeyboardHidden
+ == Configuration.HARDKEYBOARDHIDDEN_NO || mHasSoftInput)
+ ? Configuration.KEYBOARDHIDDEN_NO
+ : Configuration.KEYBOARDHIDDEN_YES;
+ }
+
+ public boolean isCheekPressedAgainstScreen(MotionEvent ev) {
+ if(ev.getSize() > SLIDE_TOUCH_EVENT_SIZE_LIMIT) {
+ return true;
+ }
+ int size = ev.getHistorySize();
+ for(int i = 0; i < size; i++) {
+ if(ev.getHistoricalSize(i) > SLIDE_TOUCH_EVENT_SIZE_LIMIT) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void dispatchedPointerEventLw(MotionEvent ev, int targetX, int targetY) {
+ if (mPointerLocationView == null) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mPointerLocationView == null) {
+ return;
+ }
+ ev.offsetLocation(targetX, targetY);
+ mPointerLocationView.addTouchEvent(ev);
+ ev.offsetLocation(-targetX, -targetY);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int windowTypeToLayerLw(int type) {
+ if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
+ return APPLICATION_LAYER;
+ }
+ switch (type) {
+ case TYPE_STATUS_BAR:
+ return STATUS_BAR_LAYER;
+ case TYPE_STATUS_BAR_PANEL:
+ return STATUS_BAR_PANEL_LAYER;
+ case TYPE_SYSTEM_DIALOG:
+ return SYSTEM_DIALOG_LAYER;
+ case TYPE_SEARCH_BAR:
+ return SEARCH_BAR_LAYER;
+ case TYPE_PHONE:
+ return PHONE_LAYER;
+ case TYPE_KEYGUARD:
+ return KEYGUARD_LAYER;
+ case TYPE_KEYGUARD_DIALOG:
+ return KEYGUARD_DIALOG_LAYER;
+ case TYPE_SYSTEM_ALERT:
+ return SYSTEM_ALERT_LAYER;
+ case TYPE_SYSTEM_ERROR:
+ return SYSTEM_ERROR_LAYER;
+ case TYPE_INPUT_METHOD:
+ return INPUT_METHOD_LAYER;
+ case TYPE_INPUT_METHOD_DIALOG:
+ return INPUT_METHOD_DIALOG_LAYER;
+ case TYPE_SYSTEM_OVERLAY:
+ return SYSTEM_OVERLAY_LAYER;
+ case TYPE_PRIORITY_PHONE:
+ return PRIORITY_PHONE_LAYER;
+ case TYPE_TOAST:
+ return TOAST_LAYER;
+ case TYPE_WALLPAPER:
+ return WALLPAPER_LAYER;
+ }
+ Log.e(TAG, "Unknown window type: " + type);
+ return APPLICATION_LAYER;
+ }
+
+ /** {@inheritDoc} */
+ public int subWindowTypeToLayerLw(int type) {
+ switch (type) {
+ case TYPE_APPLICATION_PANEL:
+ case TYPE_APPLICATION_ATTACHED_DIALOG:
+ return APPLICATION_PANEL_SUBLAYER;
+ case TYPE_APPLICATION_MEDIA:
+ return APPLICATION_MEDIA_SUBLAYER;
+ case TYPE_APPLICATION_MEDIA_OVERLAY:
+ return APPLICATION_MEDIA_OVERLAY_SUBLAYER;
+ case TYPE_APPLICATION_SUB_PANEL:
+ return APPLICATION_SUB_PANEL_SUBLAYER;
+ }
+ Log.e(TAG, "Unknown sub-window type: " + type);
+ return 0;
+ }
+
+ public int getMaxWallpaperLayer() {
+ return STATUS_BAR_LAYER;
+ }
+
+ public boolean doesForceHide(WindowState win, WindowManager.LayoutParams attrs) {
+ return attrs.type == WindowManager.LayoutParams.TYPE_KEYGUARD;
+ }
+
+ public boolean canBeForceHidden(WindowState win, WindowManager.LayoutParams attrs) {
+ return attrs.type != WindowManager.LayoutParams.TYPE_STATUS_BAR
+ && attrs.type != WindowManager.LayoutParams.TYPE_WALLPAPER;
+ }
+
+ /** {@inheritDoc} */
+ public View addStartingWindow(IBinder appToken, String packageName,
+ int theme, CharSequence nonLocalizedLabel,
+ int labelRes, int icon) {
+ if (!SHOW_STARTING_ANIMATIONS) {
+ return null;
+ }
+ if (packageName == null) {
+ return null;
+ }
+
+ Context context = mContext;
+ boolean setTheme = false;
+ //Log.i(TAG, "addStartingWindow " + packageName + ": nonLocalizedLabel="
+ // + nonLocalizedLabel + " theme=" + Integer.toHexString(theme));
+ if (theme != 0 || labelRes != 0) {
+ try {
+ context = context.createPackageContext(packageName, 0);
+ if (theme != 0) {
+ context.setTheme(theme);
+ setTheme = true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore
+ }
+ }
+ if (!setTheme) {
+ context.setTheme(com.android.internal.R.style.Theme);
+ }
+
+ Window win = PolicyManager.makeNewWindow(context);
+ if (win.getWindowStyle().getBoolean(
+ com.android.internal.R.styleable.Window_windowDisablePreview, false)) {
+ return null;
+ }
+
+ Resources r = context.getResources();
+ win.setTitle(r.getText(labelRes, nonLocalizedLabel));
+
+ win.setType(
+ WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
+ // Force the window flags: this is a fake window, so it is not really
+ // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM
+ // flag because we do know that the next window will take input
+ // focus, so we want to get the IME window up on top of us right away.
+ win.setFlags(
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+
+ win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.MATCH_PARENT);
+
+ final WindowManager.LayoutParams params = win.getAttributes();
+ params.token = appToken;
+ params.packageName = packageName;
+ params.windowAnimations = win.getWindowStyle().getResourceId(
+ com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
+ params.setTitle("Starting " + packageName);
+
+ try {
+ WindowManagerImpl wm = (WindowManagerImpl)
+ context.getSystemService(Context.WINDOW_SERVICE);
+ View view = win.getDecorView();
+
+ if (win.isFloating()) {
+ // Whoops, there is no way to display an animation/preview
+ // of such a thing! After all that work... let's skip it.
+ // (Note that we must do this here because it is in
+ // getDecorView() where the theme is evaluated... maybe
+ // we should peek the floating attribute from the theme
+ // earlier.)
+ return null;
+ }
+
+ if (localLOGV) Log.v(
+ TAG, "Adding starting window for " + packageName
+ + " / " + appToken + ": "
+ + (view.getParent() != null ? view : null));
+
+ wm.addView(view, params);
+
+ // Only return the view if it was successfully added to the
+ // window manager... which we can tell by it having a parent.
+ return view.getParent() != null ? view : null;
+ } catch (WindowManagerImpl.BadTokenException e) {
+ // ignore
+ Log.w(TAG, appToken + " already running, starting window not displayed");
+ }
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public void removeStartingWindow(IBinder appToken, View window) {
+ // RuntimeException e = new RuntimeException();
+ // Log.i(TAG, "remove " + appToken + " " + window, e);
+
+ if (localLOGV) Log.v(
+ TAG, "Removing starting window for " + appToken + ": " + window);
+
+ if (window != null) {
+ WindowManagerImpl wm = (WindowManagerImpl) mContext.getSystemService(Context.WINDOW_SERVICE);
+ wm.removeView(window);
+ }
+ }
+
+ /**
+ * Preflight adding a window to the system.
+ *
+ * Currently enforces that three window types are singletons:
+ * <ul>
+ * <li>STATUS_BAR_TYPE</li>
+ * <li>KEYGUARD_TYPE</li>
+ * </ul>
+ *
+ * @param win The window to be added
+ * @param attrs Information about the window to be added
+ *
+ * @return If ok, WindowManagerImpl.ADD_OKAY. If too many singletons, WindowManagerImpl.ADD_MULTIPLE_SINGLETON
+ */
+ public int prepareAddWindowLw(WindowState win, WindowManager.LayoutParams attrs) {
+ switch (attrs.type) {
+ case TYPE_STATUS_BAR:
+ if (mStatusBar != null) {
+ return WindowManagerImpl.ADD_MULTIPLE_SINGLETON;
+ }
+ mStatusBar = win;
+ break;
+ case TYPE_STATUS_BAR_PANEL:
+ mStatusBarPanels.add(win);
+ break;
+ case TYPE_KEYGUARD:
+ if (mKeyguard != null) {
+ return WindowManagerImpl.ADD_MULTIPLE_SINGLETON;
+ }
+ mKeyguard = win;
+ break;
+ }
+ return WindowManagerImpl.ADD_OKAY;
+ }
+
+ /** {@inheritDoc} */
+ public void removeWindowLw(WindowState win) {
+ if (mStatusBar == win) {
+ mStatusBar = null;
+ }
+ else if (mKeyguard == win) {
+ mKeyguard = null;
+ } else {
+ mStatusBarPanels.remove(win);
+ }
+ }
+
+ static final boolean PRINT_ANIM = false;
+
+ /** {@inheritDoc} */
+ public int selectAnimationLw(WindowState win, int transit) {
+ if (PRINT_ANIM) Log.i(TAG, "selectAnimation in " + win
+ + ": transit=" + transit);
+ if (transit == TRANSIT_PREVIEW_DONE) {
+ if (win.hasAppShownWindows()) {
+ if (PRINT_ANIM) Log.i(TAG, "**** STARTING EXIT");
+ return com.android.internal.R.anim.app_starting_exit;
+ }
+ }
+
+ return 0;
+ }
+
+ public Animation createForceHideEnterAnimation() {
+ return AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.lock_screen_behind_enter);
+ }
+
+ static ITelephony getPhoneInterface() {
+ return ITelephony.Stub.asInterface(ServiceManager.checkService(Context.TELEPHONY_SERVICE));
+ }
+
+ static IAudioService getAudioInterface() {
+ return IAudioService.Stub.asInterface(ServiceManager.checkService(Context.AUDIO_SERVICE));
+ }
+
+ boolean keyguardOn() {
+ return keyguardIsShowingTq() || inKeyguardRestrictedKeyInputMode();
+ }
+
+ private static final int[] WINDOW_TYPES_WHERE_HOME_DOESNT_WORK = {
+ WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
+ WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
+ };
+
+ /** {@inheritDoc} */
+ public boolean interceptKeyTi(WindowState win, int code, int metaKeys, boolean down,
+ int repeatCount, int flags) {
+ boolean keyguardOn = keyguardOn();
+
+ if (false) {
+ Log.d(TAG, "interceptKeyTi code=" + code + " down=" + down + " repeatCount="
+ + repeatCount + " keyguardOn=" + keyguardOn + " mHomePressed=" + mHomePressed);
+ }
+
+ // Clear a pending HOME longpress if the user releases Home
+ // TODO: This could probably be inside the next bit of logic, but that code
+ // turned out to be a bit fragile so I'm doing it here explicitly, for now.
+ if ((code == KeyEvent.KEYCODE_HOME) && !down) {
+ mHandler.removeCallbacks(mHomeLongPress);
+ }
+
+ // If the HOME button is currently being held, then we do special
+ // chording with it.
+ if (mHomePressed) {
+
+ // If we have released the home key, and didn't do anything else
+ // while it was pressed, then it is time to go home!
+ if (code == KeyEvent.KEYCODE_HOME) {
+ if (!down) {
+ mHomePressed = false;
+
+ if ((flags&KeyEvent.FLAG_CANCELED) == 0) {
+ // If an incoming call is ringing, HOME is totally disabled.
+ // (The user is already on the InCallScreen at this point,
+ // and his ONLY options are to answer or reject the call.)
+ boolean incomingRinging = false;
+ try {
+ ITelephony phoneServ = getPhoneInterface();
+ if (phoneServ != null) {
+ incomingRinging = phoneServ.isRinging();
+ } else {
+ Log.w(TAG, "Unable to find ITelephony interface");
+ }
+ } catch (RemoteException ex) {
+ Log.w(TAG, "RemoteException from getPhoneInterface()", ex);
+ }
+
+ if (incomingRinging) {
+ Log.i(TAG, "Ignoring HOME; there's a ringing incoming call.");
+ } else {
+ launchHomeFromHotKey();
+ }
+ } else {
+ Log.i(TAG, "Ignoring HOME; event canceled.");
+ }
+ }
+ }
+
+ return true;
+ }
+
+ // First we always handle the home key here, so applications
+ // can never break it, although if keyguard is on, we do let
+ // it handle it, because that gives us the correct 5 second
+ // timeout.
+ if (code == KeyEvent.KEYCODE_HOME) {
+
+ // If a system window has focus, then it doesn't make sense
+ // right now to interact with applications.
+ WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
+ if (attrs != null) {
+ final int type = attrs.type;
+ if (type == WindowManager.LayoutParams.TYPE_KEYGUARD
+ || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
+ // the "app" is keyguard, so give it the key
+ return false;
+ }
+ final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;
+ for (int i=0; i<typeCount; i++) {
+ if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {
+ // don't do anything, but also don't pass it to the app
+ return true;
+ }
+ }
+ }
+
+ if (down && repeatCount == 0) {
+ if (!keyguardOn) {
+ mHandler.postDelayed(mHomeLongPress, ViewConfiguration.getGlobalActionKeyTimeout());
+ }
+ mHomePressed = true;
+ }
+ return true;
+ } else if (code == KeyEvent.KEYCODE_MENU) {
+ // Hijack modified menu keys for debugging features
+ final int chordBug = KeyEvent.META_SHIFT_ON;
+
+ if (down && repeatCount == 0) {
+ if (mEnableShiftMenuBugReports && (metaKeys & chordBug) == chordBug) {
+ Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
+ mContext.sendOrderedBroadcast(intent, null);
+ return true;
+ } else if (SHOW_PROCESSES_ON_ALT_MENU &&
+ (metaKeys & KeyEvent.META_ALT_ON) == KeyEvent.META_ALT_ON) {
+ Intent service = new Intent();
+ service.setClassName(mContext, "com.android.server.LoadAverageService");
+ ContentResolver res = mContext.getContentResolver();
+ boolean shown = Settings.System.getInt(
+ res, Settings.System.SHOW_PROCESSES, 0) != 0;
+ if (!shown) {
+ mContext.startService(service);
+ } else {
+ mContext.stopService(service);
+ }
+ Settings.System.putInt(
+ res, Settings.System.SHOW_PROCESSES, shown ? 0 : 1);
+ return true;
+ }
+ }
+ } else if (code == KeyEvent.KEYCODE_NOTIFICATION) {
+ if (down) {
+ // this key doesn't exist on current hardware, but if a device
+ // didn't have a touchscreen, it would want one of these to open
+ // the status bar.
+ IStatusBar sbs = IStatusBar.Stub.asInterface(ServiceManager.getService("statusbar"));
+ if (sbs != null) {
+ try {
+ sbs.toggle();
+ } catch (RemoteException e) {
+ // we're screwed anyway, since it's in this process
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return true;
+ } else if (code == KeyEvent.KEYCODE_SEARCH) {
+ if (down) {
+ if (repeatCount == 0) {
+ mSearchKeyPressed = true;
+ }
+ } else {
+ mSearchKeyPressed = false;
+
+ if (mConsumeSearchKeyUp) {
+ // Consume the up-event
+ mConsumeSearchKeyUp = false;
+ return true;
+ }
+ }
+ }
+
+ // Shortcuts are invoked through Search+key, so intercept those here
+ if (mSearchKeyPressed) {
+ if (down && repeatCount == 0 && !keyguardOn) {
+ Intent shortcutIntent = mShortcutManager.getIntent(code, metaKeys);
+ if (shortcutIntent != null) {
+ shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(shortcutIntent);
+
+ /*
+ * We launched an app, so the up-event of the search key
+ * should be consumed
+ */
+ mConsumeSearchKeyUp = true;
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * A home key -> launch home action was detected. Take the appropriate action
+ * given the situation with the keyguard.
+ */
+ void launchHomeFromHotKey() {
+ if (mKeyguardMediator.isShowingAndNotHidden()) {
+ // don't launch home if keyguard showing
+ } else if (!mHideLockScreen && mKeyguardMediator.isInputRestricted()) {
+ // when in keyguard restricted mode, must first verify unlock
+ // before launching home
+ mKeyguardMediator.verifyUnlock(new OnKeyguardExitResult() {
+ public void onKeyguardExitResult(boolean success) {
+ if (success) {
+ try {
+ ActivityManagerNative.getDefault().stopAppSwitches();
+ } catch (RemoteException e) {
+ }
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+ startDockOrHome();
+ }
+ }
+ });
+ } else {
+ // no keyguard stuff to worry about, just launch home!
+ try {
+ ActivityManagerNative.getDefault().stopAppSwitches();
+ } catch (RemoteException e) {
+ }
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+ startDockOrHome();
+ }
+ }
+
+ public void getContentInsetHintLw(WindowManager.LayoutParams attrs, Rect contentInset) {
+ final int fl = attrs.flags;
+
+ if ((fl &
+ (FLAG_LAYOUT_IN_SCREEN | FLAG_FULLSCREEN | FLAG_LAYOUT_INSET_DECOR))
+ == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
+ contentInset.set(mCurLeft, mCurTop, mW - mCurRight, mH - mCurBottom);
+ } else {
+ contentInset.setEmpty();
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void beginLayoutLw(int displayWidth, int displayHeight) {
+ mW = displayWidth;
+ mH = displayHeight;
+ mDockLeft = mContentLeft = mCurLeft = 0;
+ mDockTop = mContentTop = mCurTop = 0;
+ mDockRight = mContentRight = mCurRight = displayWidth;
+ mDockBottom = mContentBottom = mCurBottom = displayHeight;
+ mDockLayer = 0x10000000;
+
+ // decide where the status bar goes ahead of time
+ if (mStatusBar != null) {
+ final Rect pf = mTmpParentFrame;
+ final Rect df = mTmpDisplayFrame;
+ final Rect vf = mTmpVisibleFrame;
+ pf.left = df.left = vf.left = 0;
+ pf.top = df.top = vf.top = 0;
+ pf.right = df.right = vf.right = displayWidth;
+ pf.bottom = df.bottom = vf.bottom = displayHeight;
+
+ mStatusBar.computeFrameLw(pf, df, vf, vf);
+ if (mStatusBar.isVisibleLw()) {
+ // If the status bar is hidden, we don't want to cause
+ // windows behind it to scroll.
+ mDockTop = mContentTop = mCurTop = mStatusBar.getFrameLw().bottom;
+ if (DEBUG_LAYOUT) Log.v(TAG, "Status bar: mDockBottom="
+ + mDockBottom + " mContentBottom="
+ + mContentBottom + " mCurBottom=" + mCurBottom);
+ }
+ }
+ }
+
+ void setAttachedWindowFrames(WindowState win, int fl, int sim,
+ WindowState attached, boolean insetDecors, Rect pf, Rect df, Rect cf, Rect vf) {
+ if (win.getSurfaceLayer() > mDockLayer && attached.getSurfaceLayer() < mDockLayer) {
+ // Here's a special case: if this attached window is a panel that is
+ // above the dock window, and the window it is attached to is below
+ // the dock window, then the frames we computed for the window it is
+ // attached to can not be used because the dock is effectively part
+ // of the underlying window and the attached window is floating on top
+ // of the whole thing. So, we ignore the attached window and explicitly
+ // compute the frames that would be appropriate without the dock.
+ df.left = cf.left = vf.left = mDockLeft;
+ df.top = cf.top = vf.top = mDockTop;
+ df.right = cf.right = vf.right = mDockRight;
+ df.bottom = cf.bottom = vf.bottom = mDockBottom;
+ } else {
+ // The effective display frame of the attached window depends on
+ // whether it is taking care of insetting its content. If not,
+ // we need to use the parent's content frame so that the entire
+ // window is positioned within that content. Otherwise we can use
+ // the display frame and let the attached window take care of
+ // positioning its content appropriately.
+ if ((sim & SOFT_INPUT_MASK_ADJUST) != SOFT_INPUT_ADJUST_RESIZE) {
+ cf.set(attached.getDisplayFrameLw());
+ } else {
+ // If the window is resizing, then we want to base the content
+ // frame on our attached content frame to resize... however,
+ // things can be tricky if the attached window is NOT in resize
+ // mode, in which case its content frame will be larger.
+ // Ungh. So to deal with that, make sure the content frame
+ // we end up using is not covering the IM dock.
+ cf.set(attached.getContentFrameLw());
+ if (attached.getSurfaceLayer() < mDockLayer) {
+ if (cf.left < mContentLeft) cf.left = mContentLeft;
+ if (cf.top < mContentTop) cf.top = mContentTop;
+ if (cf.right > mContentRight) cf.right = mContentRight;
+ if (cf.bottom > mContentBottom) cf.bottom = mContentBottom;
+ }
+ }
+ df.set(insetDecors ? attached.getDisplayFrameLw() : cf);
+ vf.set(attached.getVisibleFrameLw());
+ }
+ // The LAYOUT_IN_SCREEN flag is used to determine whether the attached
+ // window should be positioned relative to its parent or the entire
+ // screen.
+ pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0
+ ? attached.getFrameLw() : df);
+ }
+
+ /** {@inheritDoc} */
+ public void layoutWindowLw(WindowState win, WindowManager.LayoutParams attrs,
+ WindowState attached) {
+ // we've already done the status bar
+ if (win == mStatusBar) {
+ return;
+ }
+
+ if (false) {
+ if ("com.google.android.youtube".equals(attrs.packageName)
+ && attrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
+ Log.i(TAG, "GOTCHA!");
+ }
+ }
+
+ final int fl = attrs.flags;
+ final int sim = attrs.softInputMode;
+
+ final Rect pf = mTmpParentFrame;
+ final Rect df = mTmpDisplayFrame;
+ final Rect cf = mTmpContentFrame;
+ final Rect vf = mTmpVisibleFrame;
+
+ if (attrs.type == TYPE_INPUT_METHOD) {
+ pf.left = df.left = cf.left = vf.left = mDockLeft;
+ pf.top = df.top = cf.top = vf.top = mDockTop;
+ pf.right = df.right = cf.right = vf.right = mDockRight;
+ pf.bottom = df.bottom = cf.bottom = vf.bottom = mDockBottom;
+ // IM dock windows always go to the bottom of the screen.
+ attrs.gravity = Gravity.BOTTOM;
+ mDockLayer = win.getSurfaceLayer();
+ } else {
+ if ((fl &
+ (FLAG_LAYOUT_IN_SCREEN | FLAG_FULLSCREEN | FLAG_LAYOUT_INSET_DECOR))
+ == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
+ // This is the case for a normal activity window: we want it
+ // to cover all of the screen space, and it can take care of
+ // moving its contents to account for screen decorations that
+ // intrude into that space.
+ if (attached != null) {
+ // If this window is attached to another, our display
+ // frame is the same as the one we are attached to.
+ setAttachedWindowFrames(win, fl, sim, attached, true, pf, df, cf, vf);
+ } else {
+ pf.left = df.left = 0;
+ pf.top = df.top = 0;
+ pf.right = df.right = mW;
+ pf.bottom = df.bottom = mH;
+ if ((sim & SOFT_INPUT_MASK_ADJUST) != SOFT_INPUT_ADJUST_RESIZE) {
+ cf.left = mDockLeft;
+ cf.top = mDockTop;
+ cf.right = mDockRight;
+ cf.bottom = mDockBottom;
+ } else {
+ cf.left = mContentLeft;
+ cf.top = mContentTop;
+ cf.right = mContentRight;
+ cf.bottom = mContentBottom;
+ }
+ vf.left = mCurLeft;
+ vf.top = mCurTop;
+ vf.right = mCurRight;
+ vf.bottom = mCurBottom;
+ }
+ } else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0) {
+ // A window that has requested to fill the entire screen just
+ // gets everything, period.
+ pf.left = df.left = cf.left = 0;
+ pf.top = df.top = cf.top = 0;
+ pf.right = df.right = cf.right = mW;
+ pf.bottom = df.bottom = cf.bottom = mH;
+ vf.left = mCurLeft;
+ vf.top = mCurTop;
+ vf.right = mCurRight;
+ vf.bottom = mCurBottom;
+ } else if (attached != null) {
+ // A child window should be placed inside of the same visible
+ // frame that its parent had.
+ setAttachedWindowFrames(win, fl, sim, attached, false, pf, df, cf, vf);
+ } else {
+ // Otherwise, a normal window must be placed inside the content
+ // of all screen decorations.
+ pf.left = mContentLeft;
+ pf.top = mContentTop;
+ pf.right = mContentRight;
+ pf.bottom = mContentBottom;
+ if ((sim & SOFT_INPUT_MASK_ADJUST) != SOFT_INPUT_ADJUST_RESIZE) {
+ df.left = cf.left = mDockLeft;
+ df.top = cf.top = mDockTop;
+ df.right = cf.right = mDockRight;
+ df.bottom = cf.bottom = mDockBottom;
+ } else {
+ df.left = cf.left = mContentLeft;
+ df.top = cf.top = mContentTop;
+ df.right = cf.right = mContentRight;
+ df.bottom = cf.bottom = mContentBottom;
+ }
+ vf.left = mCurLeft;
+ vf.top = mCurTop;
+ vf.right = mCurRight;
+ vf.bottom = mCurBottom;
+ }
+ }
+
+ if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0) {
+ df.left = df.top = cf.left = cf.top = vf.left = vf.top = -10000;
+ df.right = df.bottom = cf.right = cf.bottom = vf.right = vf.bottom = 10000;
+ }
+
+ if (DEBUG_LAYOUT) Log.v(TAG, "Compute frame " + attrs.getTitle()
+ + ": sim=#" + Integer.toHexString(sim)
+ + " pf=" + pf.toShortString() + " df=" + df.toShortString()
+ + " cf=" + cf.toShortString() + " vf=" + vf.toShortString());
+
+ if (false) {
+ if ("com.google.android.youtube".equals(attrs.packageName)
+ && attrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
+ if (true || localLOGV) Log.v(TAG, "Computing frame of " + win +
+ ": sim=#" + Integer.toHexString(sim)
+ + " pf=" + pf.toShortString() + " df=" + df.toShortString()
+ + " cf=" + cf.toShortString() + " vf=" + vf.toShortString());
+ }
+ }
+
+ win.computeFrameLw(pf, df, cf, vf);
+
+ // Dock windows carve out the bottom of the screen, so normal windows
+ // can't appear underneath them.
+ if (attrs.type == TYPE_INPUT_METHOD && !win.getGivenInsetsPendingLw()) {
+ int top = win.getContentFrameLw().top;
+ top += win.getGivenContentInsetsLw().top;
+ if (mContentBottom > top) {
+ mContentBottom = top;
+ }
+ top = win.getVisibleFrameLw().top;
+ top += win.getGivenVisibleInsetsLw().top;
+ if (mCurBottom > top) {
+ mCurBottom = top;
+ }
+ if (DEBUG_LAYOUT) Log.v(TAG, "Input method: mDockBottom="
+ + mDockBottom + " mContentBottom="
+ + mContentBottom + " mCurBottom=" + mCurBottom);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int finishLayoutLw() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ public void beginAnimationLw(int displayWidth, int displayHeight) {
+ mTopFullscreenOpaqueWindowState = null;
+ mForceStatusBar = false;
+
+ mHideLockScreen = false;
+ mAllowLockscreenWhenOn = false;
+ mDismissKeyguard = false;
+ }
+
+ /** {@inheritDoc} */
+ public void animatingWindowLw(WindowState win,
+ WindowManager.LayoutParams attrs) {
+ if (mTopFullscreenOpaqueWindowState == null &&
+ win.isVisibleOrBehindKeyguardLw()) {
+ if ((attrs.flags & FLAG_FORCE_NOT_FULLSCREEN) != 0) {
+ mForceStatusBar = true;
+ }
+ if (attrs.type >= FIRST_APPLICATION_WINDOW
+ && attrs.type <= LAST_APPLICATION_WINDOW
+ && win.fillsScreenLw(mW, mH, false, false)) {
+ if (DEBUG_LAYOUT) Log.v(TAG, "Fullscreen window: " + win);
+ mTopFullscreenOpaqueWindowState = win;
+ if ((attrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0) {
+ if (localLOGV) Log.v(TAG, "Setting mHideLockScreen to true by win " + win);
+ mHideLockScreen = true;
+ }
+ if ((attrs.flags & FLAG_DISMISS_KEYGUARD) != 0) {
+ if (localLOGV) Log.v(TAG, "Setting mDismissKeyguard to true by win " + win);
+ mDismissKeyguard = true;
+ }
+ if ((attrs.flags & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) {
+ mAllowLockscreenWhenOn = true;
+ }
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int finishAnimationLw() {
+ int changes = 0;
+
+ boolean hiding = false;
+ if (mStatusBar != null) {
+ if (localLOGV) Log.i(TAG, "force=" + mForceStatusBar
+ + " top=" + mTopFullscreenOpaqueWindowState);
+ if (mForceStatusBar) {
+ if (DEBUG_LAYOUT) Log.v(TAG, "Showing status bar");
+ if (mStatusBar.showLw(true)) changes |= FINISH_LAYOUT_REDO_LAYOUT;
+ } else if (mTopFullscreenOpaqueWindowState != null) {
+ //Log.i(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw()
+ // + " shown frame: " + mTopFullscreenOpaqueWindowState.getShownFrameLw());
+ //Log.i(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs());
+ WindowManager.LayoutParams lp =
+ mTopFullscreenOpaqueWindowState.getAttrs();
+ boolean hideStatusBar =
+ (lp.flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0;
+ if (hideStatusBar) {
+ if (DEBUG_LAYOUT) Log.v(TAG, "Hiding status bar");
+ if (mStatusBar.hideLw(true)) changes |= FINISH_LAYOUT_REDO_LAYOUT;
+ hiding = true;
+ } else {
+ if (DEBUG_LAYOUT) Log.v(TAG, "Showing status bar");
+ if (mStatusBar.showLw(true)) changes |= FINISH_LAYOUT_REDO_LAYOUT;
+ }
+ }
+ }
+
+ if (changes != 0 && hiding) {
+ IStatusBar sbs = IStatusBar.Stub.asInterface(ServiceManager.getService("statusbar"));
+ if (sbs != null) {
+ try {
+ // Make sure the window shade is hidden.
+ sbs.deactivate();
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ // Hide the key guard if a visible window explicitly specifies that it wants to be displayed
+ // when the screen is locked
+ if (mKeyguard != null) {
+ if (localLOGV) Log.v(TAG, "finishLayoutLw::mHideKeyguard="+mHideLockScreen);
+ if (mDismissKeyguard && !mKeyguardMediator.isSecure()) {
+ if (mKeyguard.hideLw(true)) {
+ changes |= FINISH_LAYOUT_REDO_LAYOUT
+ | FINISH_LAYOUT_REDO_CONFIG
+ | FINISH_LAYOUT_REDO_WALLPAPER;
+ }
+ if (mKeyguardMediator.isShowing()) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ mKeyguardMediator.keyguardDone(false, false);
+ }
+ });
+ }
+ } else if (mHideLockScreen) {
+ if (mKeyguard.hideLw(true)) {
+ changes |= FINISH_LAYOUT_REDO_LAYOUT
+ | FINISH_LAYOUT_REDO_CONFIG
+ | FINISH_LAYOUT_REDO_WALLPAPER;
+ }
+ mKeyguardMediator.setHidden(true);
+ } else {
+ if (mKeyguard.showLw(true)) {
+ changes |= FINISH_LAYOUT_REDO_LAYOUT
+ | FINISH_LAYOUT_REDO_CONFIG
+ | FINISH_LAYOUT_REDO_WALLPAPER;
+ }
+ mKeyguardMediator.setHidden(false);
+ }
+ }
+
+ // update since mAllowLockscreenWhenOn might have changed
+ updateLockScreenTimeout();
+ return changes;
+ }
+
+ public boolean allowAppAnimationsLw() {
+ if (mKeyguard != null && mKeyguard.isVisibleLw()) {
+ // If keyguard is currently visible, no reason to animate
+ // behind it.
+ return false;
+ }
+ if (mStatusBar != null && mStatusBar.isVisibleLw()) {
+ Rect rect = new Rect(mStatusBar.getShownFrameLw());
+ for (int i=mStatusBarPanels.size()-1; i>=0; i--) {
+ WindowState w = mStatusBarPanels.get(i);
+ if (w.isVisibleLw()) {
+ rect.union(w.getShownFrameLw());
+ }
+ }
+ final int insetw = mW/10;
+ final int inseth = mH/10;
+ if (rect.contains(insetw, inseth, mW-insetw, mH-inseth)) {
+ // All of the status bar windows put together cover the
+ // screen, so the app can't be seen. (Note this test doesn't
+ // work if the rects of these windows are at off offsets or
+ // sizes, causing gaps in the rect union we have computed.)
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public boolean preprocessInputEventTq(RawInputEvent event) {
+ switch (event.type) {
+ case RawInputEvent.EV_SW:
+ if (event.keycode == RawInputEvent.SW_LID) {
+ // lid changed state
+ mLidOpen = event.value == 0;
+ boolean awakeNow = mKeyguardMediator.doLidChangeTq(mLidOpen);
+ updateRotation(Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE);
+ if (awakeNow) {
+ // If the lid opening and we don't have to keep the
+ // keyguard up, then we can turn on the screen
+ // immediately.
+ mKeyguardMediator.pokeWakelock();
+ } else if (keyguardIsShowingTq()) {
+ if (mLidOpen) {
+ // If we are opening the lid and not hiding the
+ // keyguard, then we need to have it turn on the
+ // screen once it is shown.
+ mKeyguardMediator.onWakeKeyWhenKeyguardShowingTq(
+ KeyEvent.KEYCODE_POWER);
+ }
+ } else {
+ // Light up the keyboard if we are sliding up.
+ if (mLidOpen) {
+ mPowerManager.userActivity(SystemClock.uptimeMillis(), false,
+ LocalPowerManager.BUTTON_EVENT);
+ } else {
+ mPowerManager.userActivity(SystemClock.uptimeMillis(), false,
+ LocalPowerManager.OTHER_EVENT);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean isAppSwitchKeyTqTiLwLi(int keycode) {
+ return keycode == KeyEvent.KEYCODE_HOME
+ || keycode == KeyEvent.KEYCODE_ENDCALL;
+ }
+
+ /** {@inheritDoc} */
+ public boolean isMovementKeyTi(int keycode) {
+ switch (keycode) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * @return Whether a telephone call is in progress right now.
+ */
+ boolean isInCall() {
+ final ITelephony phone = getPhoneInterface();
+ if (phone == null) {
+ Log.w(TAG, "couldn't get ITelephony reference");
+ return false;
+ }
+ try {
+ return phone.isOffhook();
+ } catch (RemoteException e) {
+ Log.w(TAG, "ITelephony.isOffhhook threw RemoteException " + e);
+ return false;
+ }
+ }
+
+ /**
+ * @return Whether music is being played right now.
+ */
+ boolean isMusicActive() {
+ final AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
+ if (am == null) {
+ Log.w(TAG, "isMusicActive: couldn't get AudioManager reference");
+ return false;
+ }
+ return am.isMusicActive();
+ }
+
+ /**
+ * Tell the audio service to adjust the volume appropriate to the event.
+ * @param keycode
+ */
+ void handleVolumeKey(int stream, int keycode) {
+ final IAudioService audio = getAudioInterface();
+ if (audio == null) {
+ Log.w(TAG, "handleVolumeKey: couldn't get IAudioService reference");
+ return;
+ }
+ try {
+ // since audio is playing, we shouldn't have to hold a wake lock
+ // during the call, but we do it as a precaution for the rare possibility
+ // that the music stops right before we call this
+ mBroadcastWakeLock.acquire();
+ audio.adjustStreamVolume(stream,
+ keycode == KeyEvent.KEYCODE_VOLUME_UP
+ ? AudioManager.ADJUST_RAISE
+ : AudioManager.ADJUST_LOWER,
+ 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IAudioService.adjustStreamVolume() threw RemoteException " + e);
+ } finally {
+ mBroadcastWakeLock.release();
+ }
+ }
+
+ static boolean isMediaKey(int code) {
+ if (code == KeyEvent.KEYCODE_HEADSETHOOK ||
+ code == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ||
+ code == KeyEvent.KEYCODE_MEDIA_STOP ||
+ code == KeyEvent.KEYCODE_MEDIA_NEXT ||
+ code == KeyEvent.KEYCODE_MEDIA_PREVIOUS ||
+ code == KeyEvent.KEYCODE_MEDIA_REWIND ||
+ code == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
+ return true;
+ }
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public int interceptKeyTq(RawInputEvent event, boolean screenIsOn) {
+ int result = ACTION_PASS_TO_USER;
+ final boolean isWakeKey = isWakeKeyTq(event);
+ // If screen is off then we treat the case where the keyguard is open but hidden
+ // the same as if it were open and in front.
+ // This will prevent any keys other than the power button from waking the screen
+ // when the keyguard is hidden by another activity.
+ final boolean keyguardActive = (screenIsOn ?
+ mKeyguardMediator.isShowingAndNotHidden() :
+ mKeyguardMediator.isShowing());
+
+ if (false) {
+ Log.d(TAG, "interceptKeyTq event=" + event + " keycode=" + event.keycode
+ + " screenIsOn=" + screenIsOn + " keyguardActive=" + keyguardActive);
+ }
+
+ if (keyguardActive) {
+ if (screenIsOn) {
+ // when the screen is on, always give the event to the keyguard
+ result |= ACTION_PASS_TO_USER;
+ } else {
+ // otherwise, don't pass it to the user
+ result &= ~ACTION_PASS_TO_USER;
+
+ final boolean isKeyDown =
+ (event.type == RawInputEvent.EV_KEY) && (event.value != 0);
+ if (isWakeKey && isKeyDown) {
+
+ // tell the mediator about a wake key, it may decide to
+ // turn on the screen depending on whether the key is
+ // appropriate.
+ if (!mKeyguardMediator.onWakeKeyWhenKeyguardShowingTq(event.keycode)
+ && (event.keycode == KeyEvent.KEYCODE_VOLUME_DOWN
+ || event.keycode == KeyEvent.KEYCODE_VOLUME_UP)) {
+ // when keyguard is showing and screen off, we need
+ // to handle the volume key for calls and music here
+ if (isInCall()) {
+ handleVolumeKey(AudioManager.STREAM_VOICE_CALL, event.keycode);
+ } else if (isMusicActive()) {
+ handleVolumeKey(AudioManager.STREAM_MUSIC, event.keycode);
+ }
+ }
+ }
+ }
+ } else if (!screenIsOn) {
+ // If we are in-call with screen off and keyguard is not showing,
+ // then handle the volume key ourselves.
+ // This is necessary because the phone app will disable the keyguard
+ // when the proximity sensor is in use.
+ if (isInCall() && event.type == RawInputEvent.EV_KEY &&
+ (event.keycode == KeyEvent.KEYCODE_VOLUME_DOWN
+ || event.keycode == KeyEvent.KEYCODE_VOLUME_UP)) {
+ result &= ~ACTION_PASS_TO_USER;
+ handleVolumeKey(AudioManager.STREAM_VOICE_CALL, event.keycode);
+ }
+ if (isWakeKey) {
+ // a wake key has a sole purpose of waking the device; don't pass
+ // it to the user
+ result |= ACTION_POKE_USER_ACTIVITY;
+ result &= ~ACTION_PASS_TO_USER;
+ }
+ }
+
+ int type = event.type;
+ int code = event.keycode;
+ boolean down = event.value != 0;
+
+ if (type == RawInputEvent.EV_KEY) {
+ if (code == KeyEvent.KEYCODE_ENDCALL
+ || code == KeyEvent.KEYCODE_POWER) {
+ if (down) {
+ boolean handled = false;
+ boolean hungUp = false;
+ // key repeats are generated by the window manager, and we don't see them
+ // here, so unless the driver is doing something it shouldn't be, we know
+ // this is the real press event.
+ ITelephony phoneServ = getPhoneInterface();
+ if (phoneServ != null) {
+ try {
+ if (code == KeyEvent.KEYCODE_ENDCALL) {
+ handled = hungUp = phoneServ.endCall();
+ } else if (code == KeyEvent.KEYCODE_POWER) {
+ if (phoneServ.isRinging()) {
+ // Pressing Power while there's a ringing incoming
+ // call should silence the ringer.
+ phoneServ.silenceRinger();
+ handled = true;
+ } else if (phoneServ.isOffhook() &&
+ ((mIncallPowerBehavior
+ & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP)
+ != 0)) {
+ // Otherwise, if "Power button ends call" is enabled,
+ // the Power button will hang up any current active call.
+ handled = hungUp = phoneServ.endCall();
+ }
+ }
+ } catch (RemoteException ex) {
+ Log.w(TAG, "ITelephony threw RemoteException" + ex);
+ }
+ } else {
+ Log.w(TAG, "!!! Unable to find ITelephony interface !!!");
+ }
+
+ if (!screenIsOn
+ || (handled && code != KeyEvent.KEYCODE_POWER)
+ || (handled && hungUp && code == KeyEvent.KEYCODE_POWER)) {
+ mShouldTurnOffOnKeyUp = false;
+ } else {
+ // only try to turn off the screen if we didn't already hang up
+ mShouldTurnOffOnKeyUp = true;
+ mHandler.postDelayed(mPowerLongPress,
+ ViewConfiguration.getGlobalActionKeyTimeout());
+ result &= ~ACTION_PASS_TO_USER;
+ }
+ } else {
+ mHandler.removeCallbacks(mPowerLongPress);
+ if (mShouldTurnOffOnKeyUp) {
+ mShouldTurnOffOnKeyUp = false;
+ boolean gohome, sleeps;
+ if (code == KeyEvent.KEYCODE_ENDCALL) {
+ gohome = (mEndcallBehavior
+ & Settings.System.END_BUTTON_BEHAVIOR_HOME) != 0;
+ sleeps = (mEndcallBehavior
+ & Settings.System.END_BUTTON_BEHAVIOR_SLEEP) != 0;
+ } else {
+ gohome = false;
+ sleeps = true;
+ }
+ if (keyguardActive
+ || (sleeps && !gohome)
+ || (gohome && !goHome() && sleeps)) {
+ // they must already be on the keyguad or home screen,
+ // go to sleep instead
+ Log.d(TAG, "I'm tired mEndcallBehavior=0x"
+ + Integer.toHexString(mEndcallBehavior));
+ result &= ~ACTION_POKE_USER_ACTIVITY;
+ result |= ACTION_GO_TO_SLEEP;
+ }
+ result &= ~ACTION_PASS_TO_USER;
+ }
+ }
+ } else if (isMediaKey(code)) {
+ // This key needs to be handled even if the screen is off.
+ // If others need to be handled while it's off, this is a reasonable
+ // pattern to follow.
+ if ((result & ACTION_PASS_TO_USER) == 0) {
+ // Only do this if we would otherwise not pass it to the user. In that
+ // case, the PhoneWindow class will do the same thing, except it will
+ // only do it if the showing app doesn't process the key on its own.
+ KeyEvent keyEvent = new KeyEvent(event.when, event.when,
+ down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP,
+ code, 0);
+ mBroadcastWakeLock.acquire();
+ mHandler.post(new PassHeadsetKey(keyEvent));
+ }
+ } else if (code == KeyEvent.KEYCODE_CALL) {
+ // If an incoming call is ringing, answer it!
+ // (We handle this key here, rather than in the InCallScreen, to make
+ // sure we'll respond to the key even if the InCallScreen hasn't come to
+ // the foreground yet.)
+
+ // We answer the call on the DOWN event, to agree with
+ // the "fallback" behavior in the InCallScreen.
+ if (down) {
+ try {
+ ITelephony phoneServ = getPhoneInterface();
+ if (phoneServ != null) {
+ if (phoneServ.isRinging()) {
+ Log.i(TAG, "interceptKeyTq:"
+ + " CALL key-down while ringing: Answer the call!");
+ phoneServ.answerRingingCall();
+
+ // And *don't* pass this key thru to the current activity
+ // (which is presumably the InCallScreen.)
+ result &= ~ACTION_PASS_TO_USER;
+ }
+ } else {
+ Log.w(TAG, "CALL button: Unable to find ITelephony interface");
+ }
+ } catch (RemoteException ex) {
+ Log.w(TAG, "CALL button: RemoteException from getPhoneInterface()", ex);
+ }
+ }
+ } else if ((code == KeyEvent.KEYCODE_VOLUME_UP)
+ || (code == KeyEvent.KEYCODE_VOLUME_DOWN)) {
+ // If an incoming call is ringing, either VOLUME key means
+ // "silence ringer". We handle these keys here, rather than
+ // in the InCallScreen, to make sure we'll respond to them
+ // even if the InCallScreen hasn't come to the foreground yet.
+
+ // Look for the DOWN event here, to agree with the "fallback"
+ // behavior in the InCallScreen.
+ if (down) {
+ try {
+ ITelephony phoneServ = getPhoneInterface();
+ if (phoneServ != null) {
+ if (phoneServ.isRinging()) {
+ Log.i(TAG, "interceptKeyTq:"
+ + " VOLUME key-down while ringing: Silence ringer!");
+ // Silence the ringer. (It's safe to call this
+ // even if the ringer has already been silenced.)
+ phoneServ.silenceRinger();
+
+ // And *don't* pass this key thru to the current activity
+ // (which is probably the InCallScreen.)
+ result &= ~ACTION_PASS_TO_USER;
+ }
+ } else {
+ Log.w(TAG, "VOLUME button: Unable to find ITelephony interface");
+ }
+ } catch (RemoteException ex) {
+ Log.w(TAG, "VOLUME button: RemoteException from getPhoneInterface()", ex);
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ class PassHeadsetKey implements Runnable {
+ KeyEvent mKeyEvent;
+
+ PassHeadsetKey(KeyEvent keyEvent) {
+ mKeyEvent = keyEvent;
+ }
+
+ public void run() {
+ if (ActivityManagerNative.isSystemReady()) {
+ Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
+ intent.putExtra(Intent.EXTRA_KEY_EVENT, mKeyEvent);
+ mContext.sendOrderedBroadcast(intent, null, mBroadcastDone,
+ mHandler, Activity.RESULT_OK, null, null);
+ }
+ }
+ }
+
+ BroadcastReceiver mBroadcastDone = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ mBroadcastWakeLock.release();
+ }
+ };
+
+ BroadcastReceiver mDockReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ try {
+ IUiModeManager uiModeService = IUiModeManager.Stub.asInterface(
+ ServiceManager.getService(Context.UI_MODE_SERVICE));
+ mUiMode = uiModeService.getCurrentModeType();
+ } catch (RemoteException e) {
+ }
+ updateRotation(Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE);
+ updateOrientationListenerLp();
+ }
+ };
+
+ /** {@inheritDoc} */
+ public boolean isWakeRelMovementTq(int device, int classes,
+ RawInputEvent event) {
+ // if it's tagged with one of the wake bits, it wakes up the device
+ return ((event.flags & (FLAG_WAKE | FLAG_WAKE_DROPPED)) != 0);
+ }
+
+ /** {@inheritDoc} */
+ public boolean isWakeAbsMovementTq(int device, int classes,
+ RawInputEvent event) {
+ // if it's tagged with one of the wake bits, it wakes up the device
+ return ((event.flags & (FLAG_WAKE | FLAG_WAKE_DROPPED)) != 0);
+ }
+
+ /**
+ * Given the current state of the world, should this key wake up the device?
+ */
+ protected boolean isWakeKeyTq(RawInputEvent event) {
+ // There are not key maps for trackball devices, but we'd still
+ // like to have pressing it wake the device up, so force it here.
+ int keycode = event.keycode;
+ int flags = event.flags;
+ if (keycode == RawInputEvent.BTN_MOUSE) {
+ flags |= WindowManagerPolicy.FLAG_WAKE;
+ }
+ return (flags
+ & (WindowManagerPolicy.FLAG_WAKE | WindowManagerPolicy.FLAG_WAKE_DROPPED)) != 0;
+ }
+
+ /** {@inheritDoc} */
+ public void screenTurnedOff(int why) {
+ EventLog.writeEvent(70000, 0);
+ mKeyguardMediator.onScreenTurnedOff(why);
+ synchronized (mLock) {
+ mScreenOn = false;
+ updateOrientationListenerLp();
+ updateLockScreenTimeout();
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void screenTurnedOn() {
+ EventLog.writeEvent(70000, 1);
+ mKeyguardMediator.onScreenTurnedOn();
+ synchronized (mLock) {
+ mScreenOn = true;
+ updateOrientationListenerLp();
+ updateLockScreenTimeout();
+ }
+ }
+
+ /** {@inheritDoc} */
+ public boolean isScreenOn() {
+ return mScreenOn;
+ }
+
+ /** {@inheritDoc} */
+ public void enableKeyguard(boolean enabled) {
+ mKeyguardMediator.setKeyguardEnabled(enabled);
+ }
+
+ /** {@inheritDoc} */
+ public void exitKeyguardSecurely(OnKeyguardExitResult callback) {
+ mKeyguardMediator.verifyUnlock(callback);
+ }
+
+ private boolean keyguardIsShowingTq() {
+ return mKeyguardMediator.isShowingAndNotHidden();
+ }
+
+ /** {@inheritDoc} */
+ public boolean inKeyguardRestrictedKeyInputMode() {
+ return mKeyguardMediator.isInputRestricted();
+ }
+
+ void sendCloseSystemWindows() {
+ sendCloseSystemWindows(mContext, null);
+ }
+
+ void sendCloseSystemWindows(String reason) {
+ sendCloseSystemWindows(mContext, reason);
+ }
+
+ static void sendCloseSystemWindows(Context context, String reason) {
+ if (ActivityManagerNative.isSystemReady()) {
+ try {
+ ActivityManagerNative.getDefault().closeSystemDialogs(reason);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ public int rotationForOrientationLw(int orientation, int lastRotation,
+ boolean displayEnabled) {
+
+ if (mPortraitRotation < 0) {
+ // Initialize the rotation angles for each orientation once.
+ Display d = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
+ .getDefaultDisplay();
+ if (d.getWidth() > d.getHeight()) {
+ mPortraitRotation = Surface.ROTATION_90;
+ mLandscapeRotation = Surface.ROTATION_0;
+ } else {
+ mPortraitRotation = Surface.ROTATION_0;
+ mLandscapeRotation = Surface.ROTATION_90;
+ }
+ }
+
+ synchronized (mLock) {
+ switch (orientation) {
+ case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
+ //always return landscape if orientation set to landscape
+ return mLandscapeRotation;
+ case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
+ //always return portrait if orientation set to portrait
+ return mPortraitRotation;
+ }
+ // case for nosensor meaning ignore sensor and consider only lid
+ // or orientation sensor disabled
+ //or case.unspecified
+ if (mLidOpen) {
+ return mLidOpenRotation;
+ } else if (mUiMode == Configuration.UI_MODE_TYPE_CAR && mCarDockRotation >= 0) {
+ return mCarDockRotation;
+ } else if (mUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskDockRotation >= 0) {
+ return mDeskDockRotation;
+ } else {
+ if (useSensorForOrientationLp(orientation)) {
+ // If the user has enabled auto rotation by default, do it.
+ int curRotation = mOrientationListener.getCurrentRotation();
+ return curRotation >= 0 ? curRotation : lastRotation;
+ }
+ return Surface.ROTATION_0;
+ }
+ }
+ }
+
+ public boolean detectSafeMode() {
+ try {
+ int menuState = mWindowManager.getKeycodeState(KeyEvent.KEYCODE_MENU);
+ int sState = mWindowManager.getKeycodeState(KeyEvent.KEYCODE_S);
+ int dpadState = mWindowManager.getDPadKeycodeState(KeyEvent.KEYCODE_DPAD_CENTER);
+ int trackballState = mWindowManager.getTrackballScancodeState(RawInputEvent.BTN_MOUSE);
+ mSafeMode = menuState > 0 || sState > 0 || dpadState > 0 || trackballState > 0;
+ performHapticFeedbackLw(null, mSafeMode
+ ? HapticFeedbackConstants.SAFE_MODE_ENABLED
+ : HapticFeedbackConstants.SAFE_MODE_DISABLED, true);
+ if (mSafeMode) {
+ Log.i(TAG, "SAFE MODE ENABLED (menu=" + menuState + " s=" + sState
+ + " dpad=" + dpadState + " trackball=" + trackballState + ")");
+ } else {
+ Log.i(TAG, "SAFE MODE not enabled");
+ }
+ return mSafeMode;
+ } catch (RemoteException e) {
+ // Doom! (it's also local)
+ throw new RuntimeException("window manager dead");
+ }
+ }
+
+ static long[] getLongIntArray(Resources r, int resid) {
+ int[] ar = r.getIntArray(resid);
+ if (ar == null) {
+ return null;
+ }
+ long[] out = new long[ar.length];
+ for (int i=0; i<ar.length; i++) {
+ out[i] = ar[i];
+ }
+ return out;
+ }
+
+ /** {@inheritDoc} */
+ public void systemReady() {
+ // tell the keyguard
+ mKeyguardMediator.onSystemReady();
+ android.os.SystemProperties.set("dev.bootcomplete", "1");
+ synchronized (mLock) {
+ updateOrientationListenerLp();
+ mSystemReady = true;
+ mHandler.post(new Runnable() {
+ public void run() {
+ updateSettings();
+ }
+ });
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void userActivity() {
+ synchronized (mScreenLockTimeout) {
+ if (mLockScreenTimerActive) {
+ // reset the timer
+ mHandler.removeCallbacks(mScreenLockTimeout);
+ mHandler.postDelayed(mScreenLockTimeout, mLockScreenTimeout);
+ }
+ }
+ }
+
+ Runnable mScreenLockTimeout = new Runnable() {
+ public void run() {
+ synchronized (this) {
+ if (localLOGV) Log.v(TAG, "mScreenLockTimeout activating keyguard");
+ mKeyguardMediator.doKeyguardTimeout();
+ mLockScreenTimerActive = false;
+ }
+ }
+ };
+
+ private void updateLockScreenTimeout() {
+ synchronized (mScreenLockTimeout) {
+ boolean enable = (mAllowLockscreenWhenOn && mScreenOn && mKeyguardMediator.isSecure());
+ if (mLockScreenTimerActive != enable) {
+ if (enable) {
+ if (localLOGV) Log.v(TAG, "setting lockscreen timer");
+ mHandler.postDelayed(mScreenLockTimeout, mLockScreenTimeout);
+ } else {
+ if (localLOGV) Log.v(TAG, "clearing lockscreen timer");
+ mHandler.removeCallbacks(mScreenLockTimeout);
+ }
+ mLockScreenTimerActive = enable;
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void enableScreenAfterBoot() {
+ readLidState();
+ updateRotation(Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE);
+ }
+
+ void updateRotation(int animFlags) {
+ mPowerManager.setKeyboardVisibility(mLidOpen);
+ int rotation = Surface.ROTATION_0;
+ if (mLidOpen) {
+ rotation = mLidOpenRotation;
+ } else if (mUiMode == Configuration.UI_MODE_TYPE_CAR && mCarDockRotation >= 0) {
+ rotation = mCarDockRotation;
+ } else if (mUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskDockRotation >= 0) {
+ rotation = mDeskDockRotation;
+ }
+ //if lid is closed orientation will be portrait
+ try {
+ //set orientation on WindowManager
+ mWindowManager.setRotation(rotation, true,
+ mFancyRotationAnimation | animFlags);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+
+ /**
+ * Return an Intent to launch the currently active dock as home. Returns
+ * null if the standard home should be launched.
+ * @return
+ */
+ Intent createHomeDockIntent() {
+ Intent intent;
+ if (mUiMode == Configuration.UI_MODE_TYPE_CAR) {
+ intent = mCarDockIntent;
+ } else if (mUiMode == Configuration.UI_MODE_TYPE_DESK) {
+ intent = mDeskDockIntent;
+ } else {
+ return null;
+ }
+
+ ActivityInfo ai = intent.resolveActivityInfo(
+ mContext.getPackageManager(), PackageManager.GET_META_DATA);
+ if (ai == null) {
+ return null;
+ }
+
+ if (ai.metaData != null && ai.metaData.getBoolean(Intent.METADATA_DOCK_HOME)) {
+ intent = new Intent(intent);
+ intent.setClassName(ai.packageName, ai.name);
+ return intent;
+ }
+
+ return null;
+ }
+
+ void startDockOrHome() {
+ Intent dock = createHomeDockIntent();
+ if (dock != null) {
+ try {
+ mContext.startActivity(dock);
+ return;
+ } catch (ActivityNotFoundException e) {
+ }
+ }
+ mContext.startActivity(mHomeIntent);
+ }
+
+ /**
+ * goes to the home screen
+ * @return whether it did anything
+ */
+ boolean goHome() {
+ if (false) {
+ // This code always brings home to the front.
+ try {
+ ActivityManagerNative.getDefault().stopAppSwitches();
+ } catch (RemoteException e) {
+ }
+ sendCloseSystemWindows();
+ startDockOrHome();
+ } else {
+ // This code brings home to the front or, if it is already
+ // at the front, puts the device to sleep.
+ try {
+ if (SystemProperties.getInt("persist.sys.uts-test-mode", 0) == 1) {
+ /// Roll back EndcallBehavior as the cupcake design to pass P1 lab entry.
+ Log.d(TAG, "UTS-TEST-MODE");
+ } else {
+ ActivityManagerNative.getDefault().stopAppSwitches();
+ sendCloseSystemWindows();
+ Intent dock = createHomeDockIntent();
+ if (dock != null) {
+ int result = ActivityManagerNative.getDefault()
+ .startActivity(null, dock,
+ dock.resolveTypeIfNeeded(mContext.getContentResolver()),
+ null, 0, null, null, 0, true /* onlyIfNeeded*/, false);
+ if (result == IActivityManager.START_RETURN_INTENT_TO_CALLER) {
+ return false;
+ }
+ }
+ }
+ int result = ActivityManagerNative.getDefault()
+ .startActivity(null, mHomeIntent,
+ mHomeIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ null, 0, null, null, 0, true /* onlyIfNeeded*/, false);
+ if (result == IActivityManager.START_RETURN_INTENT_TO_CALLER) {
+ return false;
+ }
+ } catch (RemoteException ex) {
+ // bummer, the activity manager, which is in this process, is dead
+ }
+ }
+ return true;
+ }
+
+ public void setCurrentOrientationLw(int newOrientation) {
+ synchronized (mLock) {
+ if (newOrientation != mCurrentAppOrientation) {
+ mCurrentAppOrientation = newOrientation;
+ updateOrientationListenerLp();
+ }
+ }
+ }
+
+ public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always) {
+ final boolean hapticsDisabled = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) == 0;
+ if (!always && (hapticsDisabled || mKeyguardMediator.isShowingAndNotHidden())) {
+ return false;
+ }
+ long[] pattern = null;
+ switch (effectId) {
+ case HapticFeedbackConstants.LONG_PRESS:
+ pattern = mLongPressVibePattern;
+ break;
+ case HapticFeedbackConstants.VIRTUAL_KEY:
+ pattern = mVirtualKeyVibePattern;
+ break;
+ case HapticFeedbackConstants.KEYBOARD_TAP:
+ pattern = mKeyboardTapVibePattern;
+ break;
+ case HapticFeedbackConstants.SAFE_MODE_DISABLED:
+ pattern = mSafeModeDisabledVibePattern;
+ break;
+ case HapticFeedbackConstants.SAFE_MODE_ENABLED:
+ pattern = mSafeModeEnabledVibePattern;
+ break;
+ default:
+ return false;
+ }
+ if (pattern.length == 1) {
+ // One-shot vibration
+ mVibrator.vibrate(pattern[0]);
+ } else {
+ // Pattern vibration
+ mVibrator.vibrate(pattern, -1);
+ }
+ return true;
+ }
+
+ public void keyFeedbackFromInput(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN
+ && (event.getFlags()&KeyEvent.FLAG_VIRTUAL_HARD_KEY) != 0) {
+ performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false);
+ }
+ }
+
+ public void screenOnStoppedLw() {
+ if (!mKeyguardMediator.isShowingAndNotHidden() && mPowerManager.isScreenOn()) {
+ long curTime = SystemClock.uptimeMillis();
+ mPowerManager.userActivity(curTime, false, LocalPowerManager.OTHER_EVENT);
+ }
+ }
+
+ public boolean allowKeyRepeat() {
+ // disable key repeat when screen is off
+ return mScreenOn;
+ }
+}
diff --git a/policy/com/android/internal/policy/impl/Policy.java b/policy/com/android/internal/policy/impl/Policy.java
new file mode 100644
index 0000000..17f3e91
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/Policy.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.internal.policy.IPolicy;
+import com.android.internal.policy.impl.PhoneLayoutInflater;
+import com.android.internal.policy.impl.PhoneWindow;
+import com.android.internal.policy.impl.PhoneWindowManager;
+
+/**
+ * {@hide}
+ */
+
+// Simple implementation of the policy interface that spawns the right
+// set of objects
+public class Policy implements IPolicy {
+ private static final String TAG = "PhonePolicy";
+
+ private static final String[] preload_classes = {
+ "com.android.internal.policy.impl.PhoneLayoutInflater",
+ "com.android.internal.policy.impl.PhoneWindow",
+ "com.android.internal.policy.impl.PhoneWindow$1",
+ "com.android.internal.policy.impl.PhoneWindow$ContextMenuCallback",
+ "com.android.internal.policy.impl.PhoneWindow$DecorView",
+ "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
+ "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
+ };
+
+ static {
+ // For performance reasons, preload some policy specific classes when
+ // the policy gets loaded.
+ for (String s : preload_classes) {
+ try {
+ Class.forName(s);
+ } catch (ClassNotFoundException ex) {
+ Log.e(TAG, "Could not preload class for phone policy: " + s);
+ }
+ }
+ }
+
+ public PhoneWindow makeNewWindow(Context context) {
+ return new PhoneWindow(context);
+ }
+
+ public PhoneLayoutInflater makeNewLayoutInflater(Context context) {
+ return new PhoneLayoutInflater(context);
+ }
+
+ public PhoneWindowManager makeNewWindowManager() {
+ return new PhoneWindowManager();
+ }
+}
diff --git a/policy/com/android/internal/policy/impl/PowerDialog.java b/policy/com/android/internal/policy/impl/PowerDialog.java
new file mode 100644
index 0000000..de35bd7
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/PowerDialog.java
@@ -0,0 +1,182 @@
+/*
+ * 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;
+
+import com.android.internal.R;
+
+import android.app.Dialog;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.LocalPowerManager;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+
+import com.android.internal.app.ShutdownThread;
+import com.android.internal.telephony.ITelephony;
+import android.view.KeyEvent;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.View.OnClickListener;
+import android.view.View.OnKeyListener;
+import android.widget.Button;
+
+/**
+ * @deprecated use {@link GlobalActions} instead.
+ */
+public class PowerDialog extends Dialog implements OnClickListener,
+ OnKeyListener {
+ private static final String TAG = "PowerDialog";
+
+ static private StatusBarManager sStatusBar;
+ private Button mKeyguard;
+ private Button mPower;
+ private Button mRadioPower;
+ private Button mSilent;
+
+ private LocalPowerManager mPowerManager;
+
+ public PowerDialog(Context context, LocalPowerManager powerManager) {
+ super(context);
+ mPowerManager = powerManager;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Context context = getContext();
+
+ if (sStatusBar == null) {
+ sStatusBar = (StatusBarManager)context.getSystemService(Context.STATUS_BAR_SERVICE);
+ }
+
+ setContentView(com.android.internal.R.layout.power_dialog);
+
+ getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+ if (!getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_sf_slowBlur)) {
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
+ WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+ }
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+
+ setTitle(context.getText(R.string.power_dialog));
+
+ mKeyguard = (Button) findViewById(R.id.keyguard);
+ mPower = (Button) findViewById(R.id.off);
+ mRadioPower = (Button) findViewById(R.id.radio_power);
+ mSilent = (Button) findViewById(R.id.silent);
+
+ if (mKeyguard != null) {
+ mKeyguard.setOnKeyListener(this);
+ mKeyguard.setOnClickListener(this);
+ }
+ if (mPower != null) {
+ mPower.setOnClickListener(this);
+ }
+ if (mRadioPower != null) {
+ mRadioPower.setOnClickListener(this);
+ }
+ if (mSilent != null) {
+ mSilent.setOnClickListener(this);
+ // XXX: HACK for now hide the silent until we get mute support
+ mSilent.setVisibility(View.GONE);
+ }
+
+ CharSequence text;
+
+ // set the keyguard button's text
+ text = context.getText(R.string.screen_lock);
+ mKeyguard.setText(text);
+ mKeyguard.requestFocus();
+
+ try {
+ ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+ if (phone != null) {
+ text = phone.isRadioOn() ? context
+ .getText(R.string.turn_off_radio) : context
+ .getText(R.string.turn_on_radio);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ mRadioPower.setText(text);
+ }
+
+ public void onClick(View v) {
+ this.dismiss();
+ if (v == mPower) {
+ // shutdown by making sure radio and power are handled accordingly.
+ ShutdownThread.shutdown(getContext(), true);
+ } else if (v == mRadioPower) {
+ try {
+ ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+ if (phone != null) {
+ phone.toggleRadioOnOff();
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ } else if (v == mSilent) {
+ // do something
+ } else if (v == mKeyguard) {
+ if (v.isInTouchMode()) {
+ // only in touch mode for the reasons explained in onKey.
+ this.dismiss();
+ mPowerManager.goToSleep(SystemClock.uptimeMillis() + 1);
+ }
+ }
+ }
+
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ // The activate keyguard button needs to put the device to sleep on the
+ // key up event. If we try to put it to sleep on the click or down
+ // action
+ // the the up action will cause the device to wake back up.
+
+ // Log.i(TAG, "keyCode: " + keyCode + " action: " + event.getAction());
+ if (keyCode != KeyEvent.KEYCODE_DPAD_CENTER
+ || event.getAction() != KeyEvent.ACTION_UP) {
+ // Log.i(TAG, "getting out of dodge...");
+ return false;
+ }
+
+ // Log.i(TAG, "Clicked mKeyguard! dimissing dialog");
+ this.dismiss();
+ // Log.i(TAG, "onKey: turning off the screen...");
+ // XXX: This is a hack for now
+ mPowerManager.goToSleep(event.getEventTime() + 1);
+ return true;
+ }
+
+ public void show() {
+ super.show();
+ Log.d(TAG, "show... disabling expand");
+ sStatusBar.disable(StatusBarManager.DISABLE_EXPAND);
+ }
+
+ public void dismiss() {
+ super.dismiss();
+ Log.d(TAG, "dismiss... reenabling expand");
+ sStatusBar.disable(StatusBarManager.DISABLE_NONE);
+ }
+}
diff --git a/policy/com/android/internal/policy/impl/RecentApplicationsBackground.java b/policy/com/android/internal/policy/impl/RecentApplicationsBackground.java
new file mode 100644
index 0000000..7c99e87
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/RecentApplicationsBackground.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+
+/**
+ * A vertical linear layout. However, instead of drawing the background
+ * behnd the items, it draws the background outside the items based on the
+ * padding. If there isn't enough room to draw both, it clips the background
+ * instead of the contents.
+ */
+public class RecentApplicationsBackground extends LinearLayout {
+ private static final String TAG = "RecentApplicationsBackground";
+
+ private boolean mBackgroundSizeChanged;
+ private Drawable mBackground;
+ private Rect mTmp0 = new Rect();
+ private Rect mTmp1 = new Rect();
+
+ public RecentApplicationsBackground(Context context) {
+ this(context, null);
+ init();
+ }
+
+ public RecentApplicationsBackground(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ private void init() {
+ mBackground = getBackground();
+ setBackgroundDrawable(null);
+ setPadding(0, 0, 0, 0);
+ setGravity(Gravity.CENTER);
+ }
+
+ @Override
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ setWillNotDraw(false);
+ if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
+ mBackgroundSizeChanged = true;
+ }
+ return super.setFrame(left, top, right, bottom);
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return who == mBackground || super.verifyDrawable(who);
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ Drawable d = mBackground;
+ if (d != null && d.isStateful()) {
+ d.setState(getDrawableState());
+ }
+ super.drawableStateChanged();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ final Drawable background = mBackground;
+ if (background != null) {
+ if (mBackgroundSizeChanged) {
+ mBackgroundSizeChanged = false;
+ Rect chld = mTmp0;
+ Rect bkg = mTmp1;
+ mBackground.getPadding(bkg);
+ getChildBounds(chld);
+ // This doesn't clamp to this view's bounds, which is what we want,
+ // so that the drawing is clipped.
+ final int top = chld.top - bkg.top;
+ final int bottom = chld.bottom + bkg.bottom;
+ // The background here is a gradient that wants to
+ // extend the full width of the screen (whatever that
+ // may be).
+ int left, right;
+ if (false) {
+ // This limits the width of the drawable.
+ left = chld.left - bkg.left;
+ right = chld.right + bkg.right;
+ } else {
+ // This expands it to full width.
+ left = 0;
+ right = getRight();
+ }
+ background.setBounds(left, top, right, bottom);
+ }
+ }
+ mBackground.draw(canvas);
+
+ if (false) {
+ android.graphics.Paint p = new android.graphics.Paint();
+ p.setColor(0x88ffff00);
+ canvas.drawRect(background.getBounds(), p);
+ }
+ canvas.drawARGB((int)(0.75*0xff), 0, 0, 0);
+
+ super.draw(canvas);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mBackground.setCallback(this);
+ setWillNotDraw(false);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mBackground.setCallback(null);
+ }
+
+ private void getChildBounds(Rect r) {
+ r.left = r.top = Integer.MAX_VALUE;
+ r.bottom = r.right = Integer.MIN_VALUE;
+ final int N = getChildCount();
+ for (int i=0; i<N; i++) {
+ View v = getChildAt(i);
+ if (v.getVisibility() == View.VISIBLE) {
+ r.left = Math.min(r.left, v.getLeft());
+ r.top = Math.min(r.top, v.getTop());
+ r.right = Math.max(r.right, v.getRight());
+ r.bottom = Math.max(r.bottom, v.getBottom());
+ }
+ }
+ }
+}
diff --git a/policy/com/android/internal/policy/impl/RecentApplicationsDialog.java b/policy/com/android/internal/policy/impl/RecentApplicationsDialog.java
new file mode 100644
index 0000000..8287253
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/RecentApplicationsDialog.java
@@ -0,0 +1,256 @@
+/*
+ * 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;
+
+import android.app.ActivityManager;
+import android.app.Dialog;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.View.OnClickListener;
+import android.widget.TextView;
+
+import java.util.List;
+
+public class RecentApplicationsDialog extends Dialog implements OnClickListener {
+ // Elements for debugging support
+// private static final String LOG_TAG = "RecentApplicationsDialog";
+ private static final boolean DBG_FORCE_EMPTY_LIST = false;
+
+ static private StatusBarManager sStatusBar;
+
+ private static final int NUM_BUTTONS = 8;
+ private static final int MAX_RECENT_TASKS = NUM_BUTTONS * 2; // allow for some discards
+
+ final TextView[] mIcons = new TextView[NUM_BUTTONS];
+ View mNoAppsText;
+ IntentFilter mBroadcastIntentFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+
+ private int mIconSize;
+
+ public RecentApplicationsDialog(Context context) {
+ super(context, com.android.internal.R.style.Theme_Dialog_RecentApplications);
+
+ final Resources resources = context.getResources();
+ mIconSize = (int) resources.getDimension(android.R.dimen.app_icon_size);
+ }
+
+ /**
+ * We create the recent applications dialog just once, and it stays around (hidden)
+ * until activated by the user.
+ *
+ * @see PhoneWindowManager#showRecentAppsDialog
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Context context = getContext();
+
+ if (sStatusBar == null) {
+ sStatusBar = (StatusBarManager)context.getSystemService(Context.STATUS_BAR_SERVICE);
+ }
+
+ Window window = getWindow();
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+ window.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ window.setTitle("Recents");
+
+ setContentView(com.android.internal.R.layout.recent_apps_dialog);
+
+ final WindowManager.LayoutParams params = window.getAttributes();
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.height = WindowManager.LayoutParams.MATCH_PARENT;
+ window.setAttributes(params);
+ window.setFlags(0, WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+ mIcons[0] = (TextView)findViewById(com.android.internal.R.id.button0);
+ mIcons[1] = (TextView)findViewById(com.android.internal.R.id.button1);
+ mIcons[2] = (TextView)findViewById(com.android.internal.R.id.button2);
+ mIcons[3] = (TextView)findViewById(com.android.internal.R.id.button3);
+ mIcons[4] = (TextView)findViewById(com.android.internal.R.id.button4);
+ mIcons[5] = (TextView)findViewById(com.android.internal.R.id.button5);
+ mIcons[6] = (TextView)findViewById(com.android.internal.R.id.button6);
+ mIcons[7] = (TextView)findViewById(com.android.internal.R.id.button7);
+ mNoAppsText = findViewById(com.android.internal.R.id.no_applications_message);
+
+ for (TextView b: mIcons) {
+ b.setOnClickListener(this);
+ }
+ }
+
+ /**
+ * Handler for user clicks. If a button was clicked, launch the corresponding activity.
+ */
+ public void onClick(View v) {
+
+ for (TextView b: mIcons) {
+ if (b == v) {
+ // prepare a launch intent and send it
+ Intent intent = (Intent)b.getTag();
+ if (intent != null) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
+ getContext().startActivity(intent);
+ }
+ break;
+ }
+ }
+ dismiss();
+ }
+
+ /**
+ * Set up and show the recent activities dialog.
+ */
+ @Override
+ public void onStart() {
+ super.onStart();
+ reloadButtons();
+ if (sStatusBar != null) {
+ sStatusBar.disable(StatusBarManager.DISABLE_EXPAND);
+ }
+
+ // receive broadcasts
+ getContext().registerReceiver(mBroadcastReceiver, mBroadcastIntentFilter);
+ }
+
+ /**
+ * Dismiss the recent activities dialog.
+ */
+ @Override
+ public void onStop() {
+ super.onStop();
+
+ // dump extra memory we're hanging on to
+ for (TextView icon: mIcons) {
+ icon.setCompoundDrawables(null, null, null, null);
+ icon.setTag(null);
+ }
+
+ if (sStatusBar != null) {
+ sStatusBar.disable(StatusBarManager.DISABLE_NONE);
+ }
+
+ // stop receiving broadcasts
+ getContext().unregisterReceiver(mBroadcastReceiver);
+ }
+
+ /**
+ * Reload the 6 buttons with recent activities
+ */
+ private void reloadButtons() {
+
+ final Context context = getContext();
+ final PackageManager pm = context.getPackageManager();
+ final ActivityManager am = (ActivityManager)
+ context.getSystemService(Context.ACTIVITY_SERVICE);
+ final List<ActivityManager.RecentTaskInfo> recentTasks =
+ am.getRecentTasks(MAX_RECENT_TASKS, 0);
+
+ ResolveInfo homeInfo = pm.resolveActivity(
+ new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),
+ 0);
+
+ IconUtilities iconUtilities = new IconUtilities(getContext());
+
+ // Performance note: Our android performance guide says to prefer Iterator when
+ // using a List class, but because we know that getRecentTasks() always returns
+ // an ArrayList<>, we'll use a simple index instead.
+ int index = 0;
+ int numTasks = recentTasks.size();
+ for (int i = 0; i < numTasks && (index < NUM_BUTTONS); ++i) {
+ final ActivityManager.RecentTaskInfo info = recentTasks.get(i);
+
+ // for debug purposes only, disallow first result to create empty lists
+ if (DBG_FORCE_EMPTY_LIST && (i == 0)) continue;
+
+ Intent intent = new Intent(info.baseIntent);
+ if (info.origActivity != null) {
+ intent.setComponent(info.origActivity);
+ }
+
+ // Skip the current home activity.
+ if (homeInfo != null) {
+ if (homeInfo.activityInfo.packageName.equals(
+ intent.getComponent().getPackageName())
+ && homeInfo.activityInfo.name.equals(
+ intent.getComponent().getClassName())) {
+ continue;
+ }
+ }
+
+ intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+ | Intent.FLAG_ACTIVITY_NEW_TASK);
+ final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
+ if (resolveInfo != null) {
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ final String title = activityInfo.loadLabel(pm).toString();
+ Drawable icon = activityInfo.loadIcon(pm);
+
+ if (title != null && title.length() > 0 && icon != null) {
+ final TextView tv = mIcons[index];
+ tv.setText(title);
+ icon = iconUtilities.createIconDrawable(icon);
+ tv.setCompoundDrawables(null, icon, null, null);
+ tv.setTag(intent);
+ tv.setVisibility(View.VISIBLE);
+ tv.setPressed(false);
+ tv.clearFocus();
+ ++index;
+ }
+ }
+ }
+
+ // handle the case of "no icons to show"
+ mNoAppsText.setVisibility((index == 0) ? View.VISIBLE : View.GONE);
+
+ // hide the rest
+ for (; index < NUM_BUTTONS; ++index) {
+ mIcons[index].setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * This is the listener for the ACTION_CLOSE_SYSTEM_DIALOGS intent. It's an indication that
+ * we should close ourselves immediately, in order to allow a higher-priority UI to take over
+ * (e.g. phone call received).
+ */
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
+ String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY);
+ if (! PhoneWindowManager.SYSTEM_DIALOG_REASON_RECENT_APPS.equals(reason)) {
+ dismiss();
+ }
+ }
+ }
+ };
+}
diff --git a/policy/com/android/internal/policy/impl/ShortcutManager.java b/policy/com/android/internal/policy/impl/ShortcutManager.java
new file mode 100644
index 0000000..d86ac44
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/ShortcutManager.java
@@ -0,0 +1,120 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.KeyCharacterMap;
+
+import java.net.URISyntaxException;
+
+/**
+ * Manages quick launch shortcuts by:
+ * <li> Keeping the local copy in sync with the database (this is an observer)
+ * <li> Returning a shortcut-matching intent to clients
+ */
+class ShortcutManager extends ContentObserver {
+
+ private static final String TAG = "ShortcutManager";
+
+ private static final int COLUMN_SHORTCUT = 0;
+ private static final int COLUMN_INTENT = 1;
+ private static final String[] sProjection = new String[] {
+ Settings.Bookmarks.SHORTCUT, Settings.Bookmarks.INTENT
+ };
+
+ private Context mContext;
+ private Cursor mCursor;
+ /** Map of a shortcut to its intent. */
+ private SparseArray<Intent> mShortcutIntents;
+
+ public ShortcutManager(Context context, Handler handler) {
+ super(handler);
+
+ mContext = context;
+ mShortcutIntents = new SparseArray<Intent>();
+ }
+
+ /** Observes the provider of shortcut+intents */
+ public void observe() {
+ mCursor = mContext.getContentResolver().query(
+ Settings.Bookmarks.CONTENT_URI, sProjection, null, null, null);
+ mCursor.registerContentObserver(this);
+ updateShortcuts();
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ updateShortcuts();
+ }
+
+ private void updateShortcuts() {
+ Cursor c = mCursor;
+ if (!c.requery()) {
+ Log.e(TAG, "ShortcutObserver could not re-query shortcuts.");
+ return;
+ }
+
+ mShortcutIntents.clear();
+ while (c.moveToNext()) {
+ int shortcut = c.getInt(COLUMN_SHORTCUT);
+ if (shortcut == 0) continue;
+ String intentURI = c.getString(COLUMN_INTENT);
+ Intent intent = null;
+ try {
+ intent = Intent.getIntent(intentURI);
+ } catch (URISyntaxException e) {
+ Log.w(TAG, "Intent URI for shortcut invalid.", e);
+ }
+ if (intent == null) continue;
+ mShortcutIntents.put(shortcut, intent);
+ }
+ }
+
+ /**
+ * Gets the shortcut intent for a given keycode+modifier. Make sure you
+ * strip whatever modifier is used for invoking shortcuts (for example,
+ * if 'Sym+A' should invoke a shortcut on 'A', you should strip the
+ * 'Sym' bit from the modifiers before calling this method.
+ * <p>
+ * This will first try an exact match (with modifiers), and then try a
+ * match without modifiers (primary character on a key).
+ *
+ * @param keyCode The keycode of the key pushed.
+ * @param modifiers The modifiers without any that are used for chording
+ * to invoke a shortcut.
+ * @return The intent that matches the shortcut, or null if not found.
+ */
+ public Intent getIntent(int keyCode, int modifiers) {
+ KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
+ // First try the exact keycode (with modifiers)
+ int shortcut = kcm.get(keyCode, modifiers);
+ Intent intent = shortcut != 0 ? mShortcutIntents.get(shortcut) : null;
+ if (intent != null) return intent;
+
+ // Next try the keycode without modifiers (the primary character on that key)
+ shortcut = Character.toLowerCase(kcm.get(keyCode, 0));
+ return shortcut != 0 ? mShortcutIntents.get(shortcut) : null;
+ }
+
+}
diff --git a/policy/com/android/internal/policy/impl/SimUnlockScreen.java b/policy/com/android/internal/policy/impl/SimUnlockScreen.java
new file mode 100644
index 0000000..5518e11
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/SimUnlockScreen.java
@@ -0,0 +1,423 @@
+/*
+ * 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;
+
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.widget.LockPatternUtils;
+
+import android.text.Editable;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import com.android.internal.R;
+
+/**
+ * Displays a dialer like interface to unlock the SIM PIN.
+ */
+public class SimUnlockScreen extends LinearLayout implements KeyguardScreen, View.OnClickListener,
+ KeyguardUpdateMonitor.InfoCallback {
+
+ private static final int DIGIT_PRESS_WAKE_MILLIS = 5000;
+
+ private final KeyguardUpdateMonitor mUpdateMonitor;
+ private final KeyguardScreenCallback mCallback;
+
+ private TextView mHeaderText;
+ private TextView mPinText;
+
+ private TextView mOkButton;
+ private Button mEmergencyCallButton;
+
+ private View mBackSpaceButton;
+
+ private final int[] mEnteredPin = {0, 0, 0, 0, 0, 0, 0, 0};
+ private int mEnteredDigits = 0;
+
+ private ProgressDialog mSimUnlockProgressDialog = null;
+
+ private LockPatternUtils mLockPatternUtils;
+
+ private int mCreationOrientation;
+
+ private int mKeyboardHidden;
+
+ private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
+
+ public SimUnlockScreen(Context context, Configuration configuration,
+ KeyguardUpdateMonitor updateMonitor, KeyguardScreenCallback callback,
+ LockPatternUtils lockpatternutils) {
+ super(context);
+ mUpdateMonitor = updateMonitor;
+ mCallback = callback;
+
+ mCreationOrientation = configuration.orientation;
+ mKeyboardHidden = configuration.hardKeyboardHidden;
+ mLockPatternUtils = lockpatternutils;
+
+ LayoutInflater inflater = LayoutInflater.from(context);
+ if (mKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
+ inflater.inflate(R.layout.keyguard_screen_sim_pin_landscape, this, true);
+ } else {
+ inflater.inflate(R.layout.keyguard_screen_sim_pin_portrait, this, true);
+ new TouchInput();
+ }
+
+ mHeaderText = (TextView) findViewById(R.id.headerText);
+ mPinText = (TextView) findViewById(R.id.pinDisplay);
+ mBackSpaceButton = findViewById(R.id.backspace);
+ mBackSpaceButton.setOnClickListener(this);
+
+ mEmergencyCallButton = (Button) findViewById(R.id.emergencyCall);
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
+ mOkButton = (TextView) findViewById(R.id.ok);
+
+ mHeaderText.setText(R.string.keyguard_password_enter_pin_code);
+ mPinText.setFocusable(false);
+
+ mEmergencyCallButton.setOnClickListener(this);
+ mOkButton.setOnClickListener(this);
+
+ setFocusableInTouchMode(true);
+ }
+
+ /** {@inheritDoc} */
+ public boolean needsInput() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public void onPause() {
+
+ }
+
+ /** {@inheritDoc} */
+ public void onResume() {
+ // start fresh
+ mHeaderText.setText(R.string.keyguard_password_enter_pin_code);
+
+ // make sure that the number of entered digits is consistent when we
+ // erase the SIM unlock code, including orientation changes.
+ mPinText.setText("");
+ mEnteredDigits = 0;
+
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
+ }
+
+ /** {@inheritDoc} */
+ public void cleanUp() {
+ // hide the dialog.
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.hide();
+ }
+ mUpdateMonitor.removeCallback(this);
+ }
+
+
+ /**
+ * 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 void onClick(View v) {
+ if (v == mBackSpaceButton) {
+ final Editable digits = mPinText.getEditableText();
+ final int len = digits.length();
+ if (len > 0) {
+ digits.delete(len-1, len);
+ mEnteredDigits--;
+ }
+ mCallback.pokeWakelock();
+ } else if (v == mEmergencyCallButton) {
+ mCallback.takeEmergencyCallAction();
+ } else if (v == mOkButton) {
+ checkPin();
+ }
+ }
+
+ private Dialog getSimUnlockProgressDialog() {
+ if (mSimUnlockProgressDialog == null) {
+ mSimUnlockProgressDialog = new ProgressDialog(mContext);
+ mSimUnlockProgressDialog.setMessage(
+ mContext.getString(R.string.lockscreen_sim_unlock_progress_dialog_message));
+ mSimUnlockProgressDialog.setIndeterminate(true);
+ mSimUnlockProgressDialog.setCancelable(false);
+ mSimUnlockProgressDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ if (!mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_sf_slowBlur)) {
+ mSimUnlockProgressDialog.getWindow().setFlags(
+ WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
+ WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+ }
+ }
+ return mSimUnlockProgressDialog;
+ }
+
+ private void checkPin() {
+
+ // make sure that the pin is at least 4 digits long.
+ if (mEnteredDigits < 4) {
+ // otherwise, display a message to the user, and don't submit.
+ mHeaderText.setText(R.string.invalidPin);
+ mPinText.setText("");
+ mEnteredDigits = 0;
+ mCallback.pokeWakelock();
+ return;
+ }
+ getSimUnlockProgressDialog().show();
+
+ new CheckSimPin(mPinText.getText().toString()) {
+ void onSimLockChangedResponse(boolean success) {
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.hide();
+ }
+ if (success) {
+ // before closing the keyguard, report back that
+ // the sim is unlocked so it knows right away
+ mUpdateMonitor.reportSimPinUnlocked();
+ mCallback.goToUnlockScreen();
+ } else {
+ mHeaderText.setText(R.string.keyguard_password_wrong_pin_code);
+ mPinText.setText("");
+ mEnteredDigits = 0;
+ }
+ mCallback.pokeWakelock();
+ }
+ }.start();
+ }
+
+
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ mCallback.goToLockScreen();
+ return true;
+ }
+
+ final char match = event.getMatch(DIGITS);
+ if (match != 0) {
+ reportDigit(match - '0');
+ return true;
+ }
+ if (keyCode == KeyEvent.KEYCODE_DEL) {
+ if (mEnteredDigits > 0) {
+ mPinText.onKeyDown(keyCode, event);
+ mEnteredDigits--;
+ }
+ return true;
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_ENTER) {
+ checkPin();
+ return true;
+ }
+
+ return false;
+ }
+
+ private void reportDigit(int digit) {
+ if (mEnteredDigits == 0) {
+ mPinText.setText("");
+ }
+ if (mEnteredDigits == 8) {
+ return;
+ }
+ mPinText.append(Integer.toString(digit));
+ mEnteredPin[mEnteredDigits++] = digit;
+ }
+
+ void updateConfiguration() {
+ Configuration newConfig = getResources().getConfiguration();
+ if (newConfig.orientation != mCreationOrientation) {
+ mCallback.recreateMe(newConfig);
+ } else if (newConfig.hardKeyboardHidden != mKeyboardHidden) {
+ mKeyboardHidden = newConfig.hardKeyboardHidden;
+ final boolean isKeyboardOpen = mKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
+ if (mUpdateMonitor.isKeyguardBypassEnabled() && isKeyboardOpen) {
+ mCallback.goToUnlockScreen();
+ }
+ }
+
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ updateConfiguration();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateConfiguration();
+ }
+
+ /**
+ * Helper class to handle input from touch dialer. Only relevant when
+ * the keyboard is shut.
+ */
+ private class TouchInput implements View.OnClickListener {
+ private TextView mZero;
+ private TextView mOne;
+ private TextView mTwo;
+ private TextView mThree;
+ private TextView mFour;
+ private TextView mFive;
+ private TextView mSix;
+ private TextView mSeven;
+ private TextView mEight;
+ private TextView mNine;
+ private TextView mCancelButton;
+
+ private TouchInput() {
+ mZero = (TextView) findViewById(R.id.zero);
+ mOne = (TextView) findViewById(R.id.one);
+ mTwo = (TextView) findViewById(R.id.two);
+ mThree = (TextView) findViewById(R.id.three);
+ mFour = (TextView) findViewById(R.id.four);
+ mFive = (TextView) findViewById(R.id.five);
+ mSix = (TextView) findViewById(R.id.six);
+ mSeven = (TextView) findViewById(R.id.seven);
+ mEight = (TextView) findViewById(R.id.eight);
+ mNine = (TextView) findViewById(R.id.nine);
+ mCancelButton = (TextView) findViewById(R.id.cancel);
+
+ mZero.setText("0");
+ mOne.setText("1");
+ mTwo.setText("2");
+ mThree.setText("3");
+ mFour.setText("4");
+ mFive.setText("5");
+ mSix.setText("6");
+ mSeven.setText("7");
+ mEight.setText("8");
+ mNine.setText("9");
+
+ mZero.setOnClickListener(this);
+ mOne.setOnClickListener(this);
+ mTwo.setOnClickListener(this);
+ mThree.setOnClickListener(this);
+ mFour.setOnClickListener(this);
+ mFive.setOnClickListener(this);
+ mSix.setOnClickListener(this);
+ mSeven.setOnClickListener(this);
+ mEight.setOnClickListener(this);
+ mNine.setOnClickListener(this);
+ mCancelButton.setOnClickListener(this);
+ }
+
+
+ public void onClick(View v) {
+ if (v == mCancelButton) {
+ mCallback.goToLockScreen();
+ return;
+ }
+
+ final int digit = checkDigit(v);
+ if (digit >= 0) {
+ mCallback.pokeWakelock(DIGIT_PRESS_WAKE_MILLIS);
+ reportDigit(digit);
+ }
+ }
+
+ private int checkDigit(View v) {
+ int digit = -1;
+ if (v == mZero) {
+ digit = 0;
+ } else if (v == mOne) {
+ digit = 1;
+ } else if (v == mTwo) {
+ digit = 2;
+ } else if (v == mThree) {
+ digit = 3;
+ } else if (v == mFour) {
+ digit = 4;
+ } else if (v == mFive) {
+ digit = 5;
+ } else if (v == mSix) {
+ digit = 6;
+ } else if (v == mSeven) {
+ digit = 7;
+ } else if (v == mEight) {
+ digit = 8;
+ } else if (v == mNine) {
+ digit = 9;
+ }
+ return digit;
+ }
+ }
+
+ public void onPhoneStateChanged(String newState) {
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
+ }
+
+ public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) {
+
+ }
+
+ public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
+
+ }
+
+ public void onRingerModeChanged(int state) {
+
+ }
+
+ public void onTimeChanged() {
+
+ }
+}
diff --git a/policy/com/android/internal/policy/impl/package.html b/policy/com/android/internal/policy/impl/package.html
new file mode 100644
index 0000000..c9f96a6
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/preloaded-classes b/preloaded-classes
index 54c7303..5d2fd68 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -269,7 +269,6 @@
android.location.ILocationManager$Stub$Proxy
android.location.Location
android.location.LocationManager
-android.location.LocationProviderInterface
android.media.AudioFormat
android.media.AudioManager
android.media.AudioRecord
@@ -633,7 +632,6 @@
com.android.internal.content.SyncStateContentProviderHelper
com.android.internal.graphics.NativeUtils
com.android.internal.location.DummyLocationProvider
-com.android.internal.location.GpsLocationProvider
com.android.internal.logging.AndroidHandler
com.android.internal.os.AndroidPrintStream
com.android.internal.os.BinderInternal
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 91dfaf3..a074023 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -421,7 +421,7 @@
Settings.Secure.BACKUP_AUTO_RESTORE, 1) != 0;
// If Encrypted file systems is enabled or disabled, this call will return the
// correct directory.
- mBaseStateDir = new File(Environment.getDataDirectory(), "backup");
+ mBaseStateDir = new File(Environment.getSecureDataDirectory(), "backup");
mBaseStateDir.mkdirs();
mDataDir = Environment.getDownloadCacheDirectory();
diff --git a/services/java/com/android/server/Installer.java b/services/java/com/android/server/Installer.java
index 2eaa58c..1f34eba 100644
--- a/services/java/com/android/server/Installer.java
+++ b/services/java/com/android/server/Installer.java
@@ -166,11 +166,17 @@
}
}
- public int install(String name, int uid, int gid) {
+ public int install(String name, boolean useEncryptedFilesystem, int uid, int gid) {
StringBuilder builder = new StringBuilder("install");
builder.append(' ');
builder.append(name);
builder.append(' ');
+ if (useEncryptedFilesystem) {
+ builder.append('1');
+ } else {
+ builder.append('0');
+ }
+ builder.append(' ');
builder.append(uid);
builder.append(' ');
builder.append(gid);
@@ -203,33 +209,57 @@
return execute(builder.toString());
}
- public int remove(String name) {
+ public int remove(String name, boolean useEncryptedFilesystem) {
StringBuilder builder = new StringBuilder("remove");
builder.append(' ');
builder.append(name);
+ builder.append(' ');
+ if (useEncryptedFilesystem) {
+ builder.append('1');
+ } else {
+ builder.append('0');
+ }
return execute(builder.toString());
}
- public int rename(String oldname, String newname) {
+ public int rename(String oldname, String newname, boolean useEncryptedFilesystem) {
StringBuilder builder = new StringBuilder("rename");
builder.append(' ');
builder.append(oldname);
builder.append(' ');
builder.append(newname);
+ builder.append(' ');
+ if (useEncryptedFilesystem) {
+ builder.append('1');
+ } else {
+ builder.append('0');
+ }
return execute(builder.toString());
}
- public int deleteCacheFiles(String name) {
+ public int deleteCacheFiles(String name, boolean useEncryptedFilesystem) {
StringBuilder builder = new StringBuilder("rmcache");
builder.append(' ');
builder.append(name);
+ builder.append(' ');
+ if (useEncryptedFilesystem) {
+ builder.append('1');
+ } else {
+ builder.append('0');
+ }
return execute(builder.toString());
}
- public int clearUserData(String name) {
+ public int clearUserData(String name, boolean useEncryptedFilesystem) {
StringBuilder builder = new StringBuilder("rmuserdata");
builder.append(' ');
builder.append(name);
+ builder.append(' ');
+ if (useEncryptedFilesystem) {
+ builder.append('1');
+ } else {
+ builder.append('0');
+ }
return execute(builder.toString());
}
@@ -263,7 +293,7 @@
}
public int getSizeInfo(String pkgName, String apkPath,
- String fwdLockApkPath, PackageStats pStats) {
+ String fwdLockApkPath, PackageStats pStats, boolean useEncryptedFilesystem) {
StringBuilder builder = new StringBuilder("getsize");
builder.append(' ');
builder.append(pkgName);
@@ -271,6 +301,13 @@
builder.append(apkPath);
builder.append(' ');
builder.append(fwdLockApkPath != null ? fwdLockApkPath : "!");
+ builder.append(' ');
+ if (useEncryptedFilesystem) {
+ builder.append('1');
+ } else {
+ builder.append('0');
+ }
+
String s = transaction(builder.toString());
String res[] = s.split(" ");
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index ef57056..f9c1a93 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -16,17 +16,6 @@
package com.android.server;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Observable;
-import java.util.Observer;
-import java.util.Set;
-
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -50,7 +39,6 @@
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationProvider;
-import android.location.LocationProviderInterface;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
@@ -68,12 +56,25 @@
import android.util.Slog;
import android.util.PrintWriterPrinter;
-import com.android.internal.location.GeocoderProxy;
-import com.android.internal.location.GpsLocationProvider;
import com.android.internal.location.GpsNetInitiatedHandler;
-import com.android.internal.location.LocationProviderProxy;
-import com.android.internal.location.MockProvider;
-import com.android.internal.location.PassiveProvider;
+
+import com.android.server.location.GeocoderProxy;
+import com.android.server.location.GpsLocationProvider;
+import com.android.server.location.LocationProviderInterface;
+import com.android.server.location.LocationProviderProxy;
+import com.android.server.location.MockProvider;
+import com.android.server.location.PassiveProvider;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Observable;
+import java.util.Observer;
+import java.util.Set;
/**
* The service class that manages LocationProviders and issues location
diff --git a/services/java/com/android/server/MasterClearReceiver.java b/services/java/com/android/server/MasterClearReceiver.java
index 27a8a74..4d04cee 100644
--- a/services/java/com/android/server/MasterClearReceiver.java
+++ b/services/java/com/android/server/MasterClearReceiver.java
@@ -39,7 +39,11 @@
try {
Slog.w(TAG, "!!! FACTORY RESET !!!");
- RecoverySystem.rebootWipeUserData(context);
+ if (intent.hasExtra("enableEFS")) {
+ RecoverySystem.rebootToggleEFS(context, intent.getBooleanExtra("enableEFS", false));
+ } else {
+ RecoverySystem.rebootWipeUserData(context);
+ }
Log.wtf(TAG, "Still running after master clear?!");
} catch (IOException e) {
Slog.e(TAG, "Can't perform master clear/factory reset", e);
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 0a30816..3ac0d14 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -148,6 +148,8 @@
private static final boolean GET_CERTIFICATES = true;
+ private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";
+
private static final int REMOVE_EVENTS =
FileObserver.CLOSE_WRITE | FileObserver.DELETE | FileObserver.MOVED_FROM;
private static final int ADD_EVENTS =
@@ -200,6 +202,10 @@
// This is where all application persistent data goes.
final File mAppDataDir;
+ // If Encrypted File System feature is enabled, all application persistent data
+ // should go here instead.
+ final File mSecureAppDataDir;
+
// This is the object monitoring the framework dir.
final FileObserver mFrameworkInstallObserver;
@@ -747,6 +753,7 @@
File dataDir = Environment.getDataDirectory();
mAppDataDir = new File(dataDir, "data");
+ mSecureAppDataDir = new File(dataDir, "secure/data");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
if (mInstaller == null) {
@@ -756,6 +763,7 @@
File miscDir = new File(dataDir, "misc");
miscDir.mkdirs();
mAppDataDir.mkdirs();
+ mSecureAppDataDir.mkdirs();
mDrmAppPrivateInstallDir.mkdirs();
}
@@ -916,7 +924,9 @@
+ " no longer exists; wiping its data";
reportSettingsProblem(Log.WARN, msg);
if (mInstaller != null) {
- mInstaller.remove(ps.name);
+ // XXX how to set useEncryptedFSDir for packages that
+ // are not encrypted?
+ mInstaller.remove(ps.name, true);
}
}
}
@@ -999,7 +1009,8 @@
void cleanupInstallFailedPackage(PackageSetting ps) {
Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name);
if (mInstaller != null) {
- int retCode = mInstaller.remove(ps.name);
+ boolean useSecureFS = useEncryptedFilesystemForPackage(ps.pkg);
+ int retCode = mInstaller.remove(ps.name, useSecureFS);
if (retCode < 0) {
Slog.w(TAG, "Couldn't remove app data directory for package: "
+ ps.name + ", retcode=" + retCode);
@@ -2712,6 +2723,11 @@
return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
}
+
+ private static boolean useEncryptedFilesystemForPackage(PackageParser.Package pkg) {
+ return Environment.isEncryptedFilesystemEnabled() &&
+ ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_NEVER_ENCRYPT) == 0);
+ }
private boolean verifyPackageUpdate(PackageSetting oldPkg, PackageParser.Package newPkg) {
if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
@@ -2729,7 +2745,14 @@
}
private File getDataPathForPackage(PackageParser.Package pkg) {
- return new File(mAppDataDir, pkg.packageName);
+ boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(pkg);
+ File dataPath;
+ if (useEncryptedFSDir) {
+ dataPath = new File(mSecureAppDataDir, pkg.packageName);
+ } else {
+ dataPath = new File(mAppDataDir, pkg.packageName);
+ }
+ return dataPath;
}
private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
@@ -3080,6 +3103,7 @@
pkg.applicationInfo.dataDir = dataPath.getPath();
} else {
// This is a normal package, need to make its data directory.
+ boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(pkg);
dataPath = getDataPathForPackage(pkg);
boolean uidError = false;
@@ -3096,7 +3120,7 @@
// If this is a system app, we can at least delete its
// current data so the application will still work.
if (mInstaller != null) {
- int ret = mInstaller.remove(pkgName);
+ int ret = mInstaller.remove(pkgName, useEncryptedFSDir);
if (ret >= 0) {
// Old data gone!
String msg = "System package " + pkg.packageName
@@ -3107,7 +3131,7 @@
recovered = true;
// And now re-install the app.
- ret = mInstaller.install(pkgName, pkg.applicationInfo.uid,
+ ret = mInstaller.install(pkgName, useEncryptedFSDir, pkg.applicationInfo.uid,
pkg.applicationInfo.uid);
if (ret == -1) {
// Ack should not happen!
@@ -3147,7 +3171,7 @@
Log.v(TAG, "Want this data dir: " + dataPath);
//invoke installer to do the actual installation
if (mInstaller != null) {
- int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid,
+ int ret = mInstaller.install(pkgName, useEncryptedFSDir, pkg.applicationInfo.uid,
pkg.applicationInfo.uid);
if(ret < 0) {
// Error from installer
@@ -6112,8 +6136,9 @@
deletedPs = mSettings.mPackages.get(packageName);
}
if ((flags&PackageManager.DONT_DELETE_DATA) == 0) {
+ boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p);
if (mInstaller != null) {
- int retCode = mInstaller.remove(packageName);
+ int retCode = mInstaller.remove(packageName, useEncryptedFSDir);
if (retCode < 0) {
Slog.w(TAG, "Couldn't remove app data or cache directory for package: "
+ packageName + ", retcode=" + retCode);
@@ -6352,6 +6377,7 @@
p = ps.pkg;
}
}
+ boolean useEncryptedFSDir = false;
if(!dataOnly) {
//need to check this only for fully installed applications
@@ -6364,9 +6390,10 @@
Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
return false;
}
+ useEncryptedFSDir = useEncryptedFilesystemForPackage(p);
}
if (mInstaller != null) {
- int retCode = mInstaller.clearUserData(packageName);
+ int retCode = mInstaller.clearUserData(packageName, useEncryptedFSDir);
if (retCode < 0) {
Slog.w(TAG, "Couldn't remove cache files for package: "
+ packageName);
@@ -6417,8 +6444,9 @@
Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
return false;
}
+ boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p);
if (mInstaller != null) {
- int retCode = mInstaller.deleteCacheFiles(packageName);
+ int retCode = mInstaller.deleteCacheFiles(packageName, useEncryptedFSDir);
if (retCode < 0) {
Slog.w(TAG, "Couldn't remove cache files for package: "
+ packageName);
@@ -6480,9 +6508,10 @@
}
publicSrcDir = isForwardLocked(p) ? applicationInfo.publicSourceDir : null;
}
+ boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p);
if (mInstaller != null) {
int res = mInstaller.getSizeInfo(packageName, p.mPath,
- publicSrcDir, pStats);
+ publicSrcDir, pStats, useEncryptedFSDir);
if (res < 0) {
return false;
} else {
@@ -7532,7 +7561,8 @@
this.pkgFlags = pkgFlags & (
ApplicationInfo.FLAG_SYSTEM |
ApplicationInfo.FLAG_FORWARD_LOCK |
- ApplicationInfo.FLAG_EXTERNAL_STORAGE);
+ ApplicationInfo.FLAG_EXTERNAL_STORAGE |
+ ApplicationInfo.FLAG_NEVER_ENCRYPT);
}
}
@@ -7799,11 +7829,17 @@
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
// TODO(oam): This secure dir creation needs to be moved somewhere else (later)
+ File systemSecureDir = new File(dataDir, "secure/system");
systemDir.mkdirs();
+ systemSecureDir.mkdirs();
FileUtils.setPermissions(systemDir.toString(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG
|FileUtils.S_IROTH|FileUtils.S_IXOTH,
-1, -1);
+ FileUtils.setPermissions(systemSecureDir.toString(),
+ FileUtils.S_IRWXU|FileUtils.S_IRWXG
+ |FileUtils.S_IROTH|FileUtils.S_IXOTH,
+ -1, -1);
mSettingsFilename = new File(systemDir, "packages.xml");
mBackupSettingsFilename = new File(systemDir, "packages-backup.xml");
mPackageListFilename = new File(systemDir, "packages.list");
diff --git a/location/java/com/android/internal/location/GeocoderProxy.java b/services/java/com/android/server/location/GeocoderProxy.java
similarity index 98%
rename from location/java/com/android/internal/location/GeocoderProxy.java
rename to services/java/com/android/server/location/GeocoderProxy.java
index b06297b..3c05da2 100644
--- a/location/java/com/android/internal/location/GeocoderProxy.java
+++ b/services/java/com/android/server/location/GeocoderProxy.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.location;
+package com.android.server.location;
import android.content.ComponentName;
import android.content.Context;
diff --git a/location/java/com/android/internal/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java
similarity index 96%
rename from location/java/com/android/internal/location/GpsLocationProvider.java
rename to services/java/com/android/server/location/GpsLocationProvider.java
index 15d692c..daa198f 100755
--- a/location/java/com/android/internal/location/GpsLocationProvider.java
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.location;
+package com.android.server.location;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -30,7 +30,6 @@
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationProvider;
-import android.location.LocationProviderInterface;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.SntpClient;
@@ -77,37 +76,6 @@
private static final boolean DEBUG = false;
private static final boolean VERBOSE = false;
- /**
- * Broadcast intent action indicating that the GPS has either been
- * enabled or disabled. An intent extra provides this state as a boolean,
- * where {@code true} means enabled.
- * @see #EXTRA_ENABLED
- *
- * {@hide}
- */
- public static final String GPS_ENABLED_CHANGE_ACTION =
- "android.location.GPS_ENABLED_CHANGE";
-
- /**
- * Broadcast intent action indicating that the GPS has either started or
- * stopped receiving GPS fixes. An intent extra provides this state as a
- * boolean, where {@code true} means that the GPS is actively receiving fixes.
- * @see #EXTRA_ENABLED
- *
- * {@hide}
- */
- public static final String GPS_FIX_CHANGE_ACTION =
- "android.location.GPS_FIX_CHANGE";
-
- /**
- * The lookup key for a boolean that indicates whether GPS is enabled or
- * disabled. {@code true} means GPS is enabled. Retrieve it with
- * {@link android.content.Intent#getBooleanExtra(String,boolean)}.
- *
- * {@hide}
- */
- public static final String EXTRA_ENABLED = "enabled";
-
// these need to match GpsPositionMode enum in gps.h
private static final int GPS_POSITION_MODE_STANDALONE = 0;
private static final int GPS_POSITION_MODE_MS_BASED = 1;
@@ -348,7 +316,7 @@
public GpsLocationProvider(Context context, ILocationManager locationManager) {
mContext = context;
mLocationManager = locationManager;
- mNIHandler = new GpsNetInitiatedHandler(context, this);
+ mNIHandler = new GpsNetInitiatedHandler(context);
mLocation.setExtras(mLocationExtras);
@@ -1031,8 +999,8 @@
}
// send an intent to notify that the GPS is receiving fixes.
- Intent intent = new Intent(GPS_FIX_CHANGE_ACTION);
- intent.putExtra(EXTRA_ENABLED, true);
+ Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION);
+ intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, true);
mContext.sendBroadcast(intent);
updateStatus(LocationProvider.AVAILABLE, mSvCount);
}
@@ -1108,8 +1076,8 @@
}
// send an intent to notify that the GPS has been enabled or disabled.
- Intent intent = new Intent(GPS_ENABLED_CHANGE_ACTION);
- intent.putExtra(EXTRA_ENABLED, mNavigating);
+ Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION);
+ intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, mNavigating);
mContext.sendBroadcast(intent);
}
@@ -1165,8 +1133,8 @@
if (mNavigating && mStatus == LocationProvider.AVAILABLE && mLastFixTime > 0 &&
System.currentTimeMillis() - mLastFixTime > RECENT_FIX_TIMEOUT * 1000) {
// send an intent to notify that the GPS is no longer receiving fixes.
- Intent intent = new Intent(GPS_FIX_CHANGE_ACTION);
- intent.putExtra(EXTRA_ENABLED, false);
+ Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION);
+ intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, false);
mContext.sendBroadcast(intent);
updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, mSvCount);
}
diff --git a/location/java/com/android/internal/location/GpsXtraDownloader.java b/services/java/com/android/server/location/GpsXtraDownloader.java
similarity index 98%
rename from location/java/com/android/internal/location/GpsXtraDownloader.java
rename to services/java/com/android/server/location/GpsXtraDownloader.java
index 978bda2..bc96980 100644
--- a/location/java/com/android/internal/location/GpsXtraDownloader.java
+++ b/services/java/com/android/server/location/GpsXtraDownloader.java
@@ -14,7 +14,13 @@
* limitations under the License.
*/
-package com.android.internal.location;
+package com.android.server.location;
+
+import android.content.Context;
+import android.net.Proxy;
+import android.net.http.AndroidHttpClient;
+import android.util.Config;
+import android.util.Log;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
@@ -30,14 +36,6 @@
import java.util.Properties;
import java.util.Random;
-import android.content.Context;
-import android.net.Proxy;
-import android.net.http.AndroidHttpClient;
-import android.util.Config;
-import android.util.Log;
-
-
-
/**
* A class for downloading GPS XTRA data.
*
diff --git a/location/java/android/location/LocationProviderInterface.java b/services/java/com/android/server/location/LocationProviderInterface.java
similarity index 97%
rename from location/java/android/location/LocationProviderInterface.java
rename to services/java/com/android/server/location/LocationProviderInterface.java
index 5ffe15c..a472143 100644
--- a/location/java/android/location/LocationProviderInterface.java
+++ b/services/java/com/android/server/location/LocationProviderInterface.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.location;
+package com.android.server.location;
import android.location.Location;
import android.net.NetworkInfo;
diff --git a/location/java/com/android/internal/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java
similarity index 98%
rename from location/java/com/android/internal/location/LocationProviderProxy.java
rename to services/java/com/android/server/location/LocationProviderProxy.java
index 31ec09a..3e118f9 100644
--- a/location/java/com/android/internal/location/LocationProviderProxy.java
+++ b/services/java/com/android/server/location/LocationProviderProxy.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.location;
+package com.android.server.location;
import android.content.ComponentName;
import android.content.Context;
@@ -22,7 +22,6 @@
import android.content.ServiceConnection;
import android.location.ILocationProvider;
import android.location.Location;
-import android.location.LocationProviderInterface;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
@@ -31,6 +30,8 @@
import android.os.SystemClock;
import android.util.Log;
+import com.android.internal.location.DummyLocationProvider;
+
/**
* A class for proxying location providers implemented as services.
*
diff --git a/location/java/com/android/internal/location/MockProvider.java b/services/java/com/android/server/location/MockProvider.java
similarity index 97%
rename from location/java/com/android/internal/location/MockProvider.java
rename to services/java/com/android/server/location/MockProvider.java
index d912740..e3f33469 100644
--- a/location/java/com/android/internal/location/MockProvider.java
+++ b/services/java/com/android/server/location/MockProvider.java
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-package com.android.internal.location;
+package com.android.server.location;
import android.location.ILocationManager;
import android.location.Location;
import android.location.LocationProvider;
-import android.location.LocationProviderInterface;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.RemoteException;
diff --git a/location/java/com/android/internal/location/PassiveProvider.java b/services/java/com/android/server/location/PassiveProvider.java
similarity index 96%
rename from location/java/com/android/internal/location/PassiveProvider.java
rename to services/java/com/android/server/location/PassiveProvider.java
index ab90937..5ed1558 100644
--- a/location/java/com/android/internal/location/PassiveProvider.java
+++ b/services/java/com/android/server/location/PassiveProvider.java
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-package com.android.internal.location;
+package com.android.server.location;
import android.location.ILocationManager;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationProvider;
-import android.location.LocationProviderInterface;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.RemoteException;
diff --git a/services/java/com/android/server/status/StatusBarPolicy.java b/services/java/com/android/server/status/StatusBarPolicy.java
index 465ff2e..c9b649d 100644
--- a/services/java/com/android/server/status/StatusBarPolicy.java
+++ b/services/java/com/android/server/status/StatusBarPolicy.java
@@ -30,6 +30,7 @@
import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
+import android.location.LocationManager;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
@@ -62,7 +63,6 @@
import com.android.internal.R;
import com.android.internal.app.IBatteryStats;
-import com.android.internal.location.GpsLocationProvider;
import com.android.internal.telephony.IccCard;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.cdma.EriInfo;
@@ -387,8 +387,8 @@
action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
updateWifi(intent);
}
- else if (action.equals(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION) ||
- action.equals(GpsLocationProvider.GPS_FIX_CHANGE_ACTION)) {
+ else if (action.equals(LocationManager.GPS_ENABLED_CHANGE_ACTION) ||
+ action.equals(LocationManager.GPS_FIX_CHANGE_ACTION)) {
updateGps(intent);
}
else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION) ||
@@ -533,8 +533,8 @@
filter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
- filter.addAction(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION);
- filter.addAction(GpsLocationProvider.GPS_FIX_CHANGE_ACTION);
+ filter.addAction(LocationManager.GPS_ENABLED_CHANGE_ACTION);
+ filter.addAction(LocationManager.GPS_FIX_CHANGE_ACTION);
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION);
mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
@@ -1287,13 +1287,13 @@
private final void updateGps(Intent intent) {
final String action = intent.getAction();
- final boolean enabled = intent.getBooleanExtra(GpsLocationProvider.EXTRA_ENABLED, false);
+ final boolean enabled = intent.getBooleanExtra(LocationManager.EXTRA_GPS_ENABLED, false);
- if (action.equals(GpsLocationProvider.GPS_FIX_CHANGE_ACTION) && enabled) {
+ if (action.equals(LocationManager.GPS_FIX_CHANGE_ACTION) && enabled) {
// GPS is getting fixes
mService.updateIcon(mGpsIcon, mGpsFixIconData, null);
mService.setIconVisibility(mGpsIcon, true);
- } else if (action.equals(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION) && !enabled) {
+ } else if (action.equals(LocationManager.GPS_ENABLED_CHANGE_ACTION) && !enabled) {
// GPS is off
mService.setIconVisibility(mGpsIcon, false);
} else {
diff --git a/services/jni/Android.mk b/services/jni/Android.mk
index 9d2760e..b90e327 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -9,6 +9,7 @@
com_android_server_SensorService.cpp \
com_android_server_SystemServer.cpp \
com_android_server_VibratorService.cpp \
+ com_android_server_location_GpsLocationProvider.cpp \
onload.cpp
LOCAL_C_INCLUDES += \
diff --git a/core/jni/android_location_GpsLocationProvider.cpp b/services/jni/com_android_server_location_GpsLocationProvider.cpp
similarity index 93%
rename from core/jni/android_location_GpsLocationProvider.cpp
rename to services/jni/com_android_server_location_GpsLocationProvider.cpp
index f60fe6d..7a74fd4 100755
--- a/core/jni/android_location_GpsLocationProvider.cpp
+++ b/services/jni/com_android_server_location_GpsLocationProvider.cpp
@@ -20,8 +20,8 @@
#include "JNIHelp.h"
#include "jni.h"
-#include "hardware_legacy/gps.h"
-#include "hardware_legacy/gps_ni.h"
+#include "hardware/hardware.h"
+#include "hardware/gps.h"
#include "utils/Log.h"
#include "utils/misc.h"
@@ -41,7 +41,6 @@
static const GpsInterface* sGpsInterface = NULL;
static const GpsXtraInterface* sGpsXtraInterface = NULL;
static const AGpsInterface* sAGpsInterface = NULL;
-static const GpsPrivacyInterface* sGpsPrivacyInterface = NULL;
static const GpsNiInterface* sGpsNiInterface = NULL;
static const GpsDebugInterface* sGpsDebugInterface = NULL;
@@ -205,16 +204,34 @@
method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification", "(IIIIILjava/lang/String;Ljava/lang/String;IILjava/lang/String;)V");
}
+static const GpsInterface* get_gps_interface() {
+ int err;
+ hw_module_t* module;
+ const GpsInterface* interface = NULL;
+
+ err = hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
+ if (err == 0) {
+ hw_device_t* device;
+ err = module->methods->open(module, GPS_HARDWARE_MODULE_ID, &device);
+ if (err == 0) {
+ gps_device_t* gps_device = (gps_device_t *)device;
+ interface = gps_device->get_gps_interface(gps_device);
+ }
+ }
+
+ return interface;
+}
+
static jboolean android_location_GpsLocationProvider_is_supported(JNIEnv* env, jclass clazz) {
if (!sGpsInterface)
- sGpsInterface = gps_get_interface();
+ sGpsInterface = get_gps_interface();
return (sGpsInterface != NULL);
}
static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject obj)
{
if (!sGpsInterface)
- sGpsInterface = gps_get_interface();
+ sGpsInterface = get_gps_interface();
if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
return false;
@@ -224,15 +241,9 @@
sAGpsInterface->init(&sAGpsCallbacks);
if (!sGpsNiInterface)
- sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
+ sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
if (sGpsNiInterface)
- sGpsNiInterface->init(&sGpsNiCallbacks);
-
- // Clear privacy lock while enabled
- if (!sGpsPrivacyInterface)
- sGpsPrivacyInterface = (const GpsPrivacyInterface*)sGpsInterface->get_extension(GPS_PRIVACY_INTERFACE);
- if (sGpsPrivacyInterface)
- sGpsPrivacyInterface->set_privacy_lock(0);
+ sGpsNiInterface->init(&sGpsNiCallbacks);
if (!sGpsDebugInterface)
sGpsDebugInterface = (const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE);
@@ -242,12 +253,6 @@
static void android_location_GpsLocationProvider_disable(JNIEnv* env, jobject obj)
{
- // Enable privacy lock while disabled
- if (!sGpsPrivacyInterface)
- sGpsPrivacyInterface = (const GpsPrivacyInterface*)sGpsInterface->get_extension(GPS_PRIVACY_INTERFACE);
- if (sGpsPrivacyInterface)
- sGpsPrivacyInterface->set_privacy_lock(1);
-
pthread_mutex_lock(&sEventMutex);
sPendingCallbacks |= kDisableRequest;
pthread_cond_signal(&sEventCond);
@@ -489,10 +494,12 @@
static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* env, jobject obj,
jint notifId, jint response)
{
- if (!sGpsNiInterface)
+ if (!sGpsNiInterface) {
sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
- if (sGpsNiInterface)
+ }
+ if (sGpsNiInterface) {
sGpsNiInterface->respond(notifId, response);
+ }
}
static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* env, jobject obj)
@@ -534,9 +541,9 @@
{"native_get_internal_state", "()Ljava/lang/String;", (void*)android_location_GpsLocationProvider_get_internal_state},
};
-int register_android_location_GpsLocationProvider(JNIEnv* env)
+int register_android_server_location_GpsLocationProvider(JNIEnv* env)
{
- return jniRegisterNativeMethods(env, "com/android/internal/location/GpsLocationProvider", sMethods, NELEM(sMethods));
+ return jniRegisterNativeMethods(env, "com/android/server/location/GpsLocationProvider", sMethods, NELEM(sMethods));
}
} /* namespace android */
diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp
index c16fdb8..d11e7e1 100644
--- a/services/jni/onload.cpp
+++ b/services/jni/onload.cpp
@@ -11,6 +11,7 @@
int register_android_server_SensorService(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
+int register_android_server_location_GpsLocationProvider(JNIEnv* env);
};
using namespace android;
@@ -33,6 +34,7 @@
register_android_server_SensorService(env);
register_android_server_VibratorService(env);
register_android_server_SystemServer(env);
+ register_android_server_location_GpsLocationProvider(env);
return JNI_VERSION_1_4;
}
diff --git a/tests/framework-tests/src/Android.mk b/tests/framework-tests/src/Android.mk
index 54e33a4..f537b52 100644
--- a/tests/framework-tests/src/Android.mk
+++ b/tests/framework-tests/src/Android.mk
@@ -5,6 +5,6 @@
LOCAL_MODULE := framework-tests
-LOCAL_JAVA_LIBRARIES := android.policy_phone android.test.runner
+LOCAL_JAVA_LIBRARIES := android.policy android.test.runner
include $(BUILD_JAVA_LIBRARY)
diff --git a/vpn/java/android/net/vpn/VpnManager.java b/vpn/java/android/net/vpn/VpnManager.java
index ce522c8..ce40b5d 100644
--- a/vpn/java/android/net/vpn/VpnManager.java
+++ b/vpn/java/android/net/vpn/VpnManager.java
@@ -85,7 +85,8 @@
// TODO(oam): Test VPN when EFS is enabled (will do later)...
public static String getProfilePath() {
- return Environment.getDataDirectory().getPath() + PROFILES_PATH;
+ // This call will return the correct path if Encrypted FS is enabled or not.
+ return Environment.getSecureDataDirectory().getPath() + PROFILES_PATH;
}
/**