diff options
Diffstat (limited to 'cmds')
26 files changed, 2029 insertions, 234 deletions
diff --git a/cmds/am/Android.mk b/cmds/am/Android.mk index 170849d..8b321b3 100644 --- a/cmds/am/Android.mk +++ b/cmds/am/Android.mk @@ -12,3 +12,20 @@ ALL_PREBUILT += $(TARGET_OUT)/bin/am $(TARGET_OUT)/bin/am : $(LOCAL_PATH)/am | $(ACP) $(transform-prebuilt-to-target) +NOTICE_FILE := NOTICE +files_noticed := bin/am + +# Generate rules for a single file. The argument is the file path relative to +# the installation root +define make-notice-file + +$(TARGET_OUT_NOTICE_FILES)/src/$(1).txt: $(LOCAL_PATH)/$(NOTICE_FILE) + @echo Notice file: $$< -- $$@ + @mkdir -p $$(dir $$@) + @cat $$< >> $$@ + +$(TARGET_OUT_NOTICE_FILES)/hash-timestamp: $(TARGET_OUT_NOTICE_FILES)/src/$(1).txt + +endef + +$(foreach file,$(files_noticed),$(eval $(call make-notice-file,$(file)))) diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index f2aa91f..38cacdd 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -18,12 +18,14 @@ package com.android.commands.am; +import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.IActivityController; import android.app.IActivityManager; import android.app.IInstrumentationWatcher; import android.app.Instrumentation; import android.content.ComponentName; +import android.content.Context; import android.content.IIntentReceiver; import android.content.Intent; import android.net.Uri; @@ -102,8 +104,14 @@ public class Am { sendBroadcast(); } else if (op.equals("profile")) { runProfile(); + } else if (op.equals("dumpheap")) { + runDumpHeap(); } else if (op.equals("monitor")) { runMonitor(); + } else if (op.equals("screen-compat")) { + runScreenCompat(); + } else if (op.equals("display-size")) { + runDisplaySize(); } else { throw new IllegalArgumentException("Unknown command: " + op); } @@ -146,6 +154,31 @@ public class Am { String value = nextArgRequired(); intent.putExtra(key, Integer.valueOf(value)); hasIntentInfo = true; + } else if (opt.equals("--eia")) { + String key = nextArgRequired(); + String value = nextArgRequired(); + String[] strings = value.split(","); + int[] list = new int[strings.length]; + for (int i = 0; i < strings.length; i++) { + list[i] = Integer.valueOf(strings[i]); + } + intent.putExtra(key, list); + hasIntentInfo = true; + } else if (opt.equals("--el")) { + String key = nextArgRequired(); + String value = nextArgRequired(); + intent.putExtra(key, Long.valueOf(value)); + hasIntentInfo = true; + } else if (opt.equals("--ela")) { + String key = nextArgRequired(); + String value = nextArgRequired(); + String[] strings = value.split(","); + long[] list = new long[strings.length]; + for (int i = 0; i < strings.length; i++) { + list[i] = Long.valueOf(strings[i]); + } + intent.putExtra(key, list); + hasIntentInfo = true; } else if (opt.equals("--ez")) { String key = nextArgRequired(); String value = nextArgRequired(); @@ -430,6 +463,28 @@ public class Am { } } + private void runDumpHeap() throws Exception { + boolean managed = !"-n".equals(nextOption()); + String process = nextArgRequired(); + String heapFile = nextArgRequired(); + ParcelFileDescriptor fd = null; + + try { + fd = ParcelFileDescriptor.open( + new File(heapFile), + ParcelFileDescriptor.MODE_CREATE | + ParcelFileDescriptor.MODE_TRUNCATE | + ParcelFileDescriptor.MODE_READ_WRITE); + } catch (FileNotFoundException e) { + System.err.println("Error: Unable to open file: " + heapFile); + return; + } + + if (!mAm.dumpHeap(process, managed, heapFile, fd)) { + throw new AndroidException("HEAP DUMP FAILED on process " + process); + } + } + class MyActivityController extends IActivityController.Stub { final String mGdbPort; @@ -727,6 +782,78 @@ public class Am { controller.run(); } + private void runScreenCompat() throws Exception { + String mode = nextArgRequired(); + boolean enabled; + if ("on".equals(mode)) { + enabled = true; + } else if ("off".equals(mode)) { + enabled = false; + } else { + System.err.println("Error: enabled mode must be 'on' or 'off' at " + mode); + showUsage(); + return; + } + + String packageName = nextArgRequired(); + do { + try { + mAm.setPackageScreenCompatMode(packageName, enabled + ? ActivityManager.COMPAT_MODE_ENABLED + : ActivityManager.COMPAT_MODE_DISABLED); + } catch (RemoteException e) { + } + packageName = nextArg(); + } while (packageName != null); + } + + private void runDisplaySize() throws Exception { + String size = nextArgRequired(); + int m, n; + if ("reset".equals(size)) { + m = n = -1; + } else { + int div = size.indexOf('x'); + if (div <= 0 || div >= (size.length()-1)) { + System.err.println("Error: bad size " + size); + showUsage(); + return; + } + String mstr = size.substring(0, div); + String nstr = size.substring(div+1); + try { + m = Integer.parseInt(mstr); + n = Integer.parseInt(nstr); + } catch (NumberFormatException e) { + System.err.println("Error: bad number " + e); + showUsage(); + return; + } + } + + if (m < n) { + int tmp = m; + m = n; + n = tmp; + } + + IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.checkService( + Context.WINDOW_SERVICE)); + if (wm == null) { + System.err.println(NO_SYSTEM_ERROR_CODE); + throw new AndroidException("Can't connect to window manager; is the system running?"); + } + + try { + if (m >= 0 && n >= 0) { + wm.setForcedDisplaySize(m, n); + } else { + wm.clearForcedDisplaySize(); + } + } catch (RemoteException e) { + } + } + private class IntentReceiver extends IIntentReceiver.Stub { private boolean mFinished = false; @@ -894,19 +1021,33 @@ public class Am { " -p <FILE>: write profiling data to <FILE>\n" + " -w: wait for instrumentation to finish before returning\n" + "\n" + + " run a test package against an application: am instrument [flags] <TEST_PACKAGE>/<RUNNER_CLASS>\n" + + " -e <testrunner_flag> <testrunner_value> [,<testrunner_value>]\n" + + " -w wait for the test to finish (required)\n" + + " -r use with -e perf true to generate raw output for performance measurements\n" + + "\n" + " start profiling: am profile <PROCESS> start <FILE>\n" + " stop profiling: am profile <PROCESS> stop\n" + + " dump heap: am dumpheap [flags] <PROCESS> <FILE>\n" + + " -n: dump native heap instead of managed heap\n" + "\n" + " start monitoring: am monitor [--gdb <port>]\n" + " --gdb: start gdbserv on the given port at crash/ANR\n" + "\n" + + " control screen compatibility: am screen-compat [on|off] [package]\n" + + "\n" + + " override display size: am display-size [reset|MxN]\n" + + "\n" + " <INTENT> specifications include these flags:\n" + " [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" + " [-c <CATEGORY> [-c <CATEGORY>] ...]\n" + " [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]\n" + " [--esn <EXTRA_KEY> ...]\n" + " [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]\n" + - " [-e|--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" + + " [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" + + " [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]\n" + + " [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" + + " [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" + " [-n <COMPONENT>] [-f <FLAGS>]\n" + " [--grant-read-uri-permission] [--grant-write-uri-permission]\n" + " [--debug-log-resolution]\n" + diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp index 7decf9a..0159edd 100644 --- a/cmds/app_process/app_main.cpp +++ b/cmds/app_process/app_main.cpp @@ -171,9 +171,9 @@ int main(int argc, const char* const argv[]) runtime.start(); } } else { - LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); fprintf(stderr, "Error: no class name or --zygote supplied.\n"); app_usage(); + LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); return 10; } diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index a1cc4ce..ac0e410 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -386,11 +386,13 @@ public final class Bmgr { if (err == 0) { observer.waitForCompletion(); sets = observer.sets; - for (RestoreSet s : sets) { - if (s.token == token) { - System.out.println("Scheduling restore: " + s.name); - didRestore = (mRestore.restoreAll(token, observer) == 0); - break; + if (sets != null) { + for (RestoreSet s : sets) { + if (s.token == token) { + System.out.println("Scheduling restore: " + s.name); + didRestore = (mRestore.restoreAll(token, observer) == 0); + break; + } } } } diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 6650a71..e07495d 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -23,6 +23,8 @@ #include <utils/misc.h> #include <signal.h> +#include <cutils/properties.h> + #include <binder/IPCThreadState.h> #include <utils/threads.h> #include <utils/Atomic.h> @@ -51,6 +53,7 @@ #define USER_BOOTANIMATION_FILE "/data/local/bootanimation.zip" #define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip" +#define SYSTEM_ENCRYPTED_BOOTANIMATION_FILE "/system/media/bootanimation-encrypted.zip" namespace android { @@ -248,11 +251,25 @@ status_t BootAnimation::readyToRun() { mFlingerSurface = s; mAndroidAnimation = true; - if ((access(USER_BOOTANIMATION_FILE, R_OK) == 0) && - (mZip.open(USER_BOOTANIMATION_FILE) == NO_ERROR) || - (access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) && - (mZip.open(SYSTEM_BOOTANIMATION_FILE) == NO_ERROR)) + + // If the device has encryption turned on or is in process + // of being encrypted we show the encrypted boot animation. + char decrypt[PROPERTY_VALUE_MAX]; + property_get("vold.decrypt", decrypt, ""); + + bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt); + + if ((encryptedAnimation && + (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) && + (mZip.open(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE) == NO_ERROR)) || + + ((access(USER_BOOTANIMATION_FILE, R_OK) == 0) && + (mZip.open(USER_BOOTANIMATION_FILE) == NO_ERROR)) || + + ((access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) && + (mZip.open(SYSTEM_BOOTANIMATION_FILE) == NO_ERROR))) { mAndroidAnimation = false; + } return NO_ERROR; } diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c index 0723f67..ccc4fd7 100644 --- a/cmds/dumpstate/dumpstate.c +++ b/cmds/dumpstate/dumpstate.c @@ -39,6 +39,8 @@ static char cmdline_buf[16384] = "(unknown)"; static const char *dump_traces_path = NULL; +static char screenshot_path[PATH_MAX] = ""; + /* dumps the current system state to stdout */ static void dumpstate() { time_t now = time(NULL); @@ -76,7 +78,13 @@ static void dumpstate() { dump_file("SLAB INFO", "/proc/slabinfo"); dump_file("ZONEINFO", "/proc/zoneinfo"); - run_command("SYSTEM LOG", 20, "logcat", "-v", "time", "-d", "*:v", NULL); + if (screenshot_path[0]) { + LOGI("taking screenshot\n"); + run_command(NULL, 5, "su", "root", "screenshot", screenshot_path, NULL); + LOGI("wrote screenshot: %s\n", screenshot_path); + } + + run_command("SYSTEM LOG", 20, "logcat", "-v", "threadtime", "-d", "*:v", NULL); /* show the traces we collected in main(), if that was done */ if (dump_traces_path != NULL) { @@ -96,18 +104,33 @@ static void dumpstate() { } // dump_file("EVENT LOG TAGS", "/etc/event-log-tags"); - run_command("EVENT LOG", 20, "logcat", "-b", "events", "-v", "time", "-d", "*:v", NULL); - run_command("RADIO LOG", 20, "logcat", "-b", "radio", "-v", "time", "-d", "*:v", NULL); + run_command("EVENT LOG", 20, "logcat", "-b", "events", "-v", "threadtime", "-d", "*:v", NULL); + run_command("RADIO LOG", 20, "logcat", "-b", "radio", "-v", "threadtime", "-d", "*:v", NULL); - run_command("NETWORK INTERFACES", 10, "netcfg", NULL); + run_command("NETWORK INTERFACES", 10, "su", "root", "netcfg", NULL); dump_file("NETWORK ROUTES", "/proc/net/route"); + dump_file("NETWORK ROUTES IPV6", "/proc/net/ipv6_route"); dump_file("ARP CACHE", "/proc/net/arp"); + run_command("IPTABLES", 10, "su", "root", "iptables", "-L", NULL); + run_command("IPTABLE NAT", 10, "su", "root", "iptables", "-t", "nat", "-L", NULL); + + run_command("WIFI NETWORKS", 20, + "su", "root", "wpa_cli", "list_networks", NULL); #ifdef FWDUMP_bcm4329 - run_command("DUMP WIFI FIRMWARE LOG", 60, - "su", "root", "dhdutil", "-i", "eth0", "upload", "/data/local/tmp/wlan_crash.dump", NULL); + run_command("DUMP WIFI STATUS", 20, + "su", "root", "dhdutil", "-i", "wlan0", "dump", NULL); + run_command("DUMP WIFI INTERNAL COUNTERS", 20, + "su", "root", "wlutil", "counters", NULL); #endif + char ril_dumpstate_timeout[PROPERTY_VALUE_MAX] = {0}; + property_get("ril.dumpstate.timeout", ril_dumpstate_timeout, "30"); + if (strlen(ril_dumpstate_timeout) > 0) { + run_command("DUMP VENDOR RIL LOGS", atoi(ril_dumpstate_timeout), + "su", "root", "vril-dump", NULL); + } + print_properties(); run_command("KERNEL LOG", 20, "dmesg", NULL); @@ -128,7 +151,7 @@ static void dumpstate() { dump_file("BINDER STATS", "/sys/kernel/debug/binder/stats"); dump_file("BINDER STATE", "/sys/kernel/debug/binder/state"); - run_command("FILESYSTEMS & FREE SPACE", 10, "df", NULL); + run_command("FILESYSTEMS & FREE SPACE", 10, "su", "root", "df", NULL); dump_file("PACKAGE SETTINGS", "/data/system/packages.xml"); dump_file("PACKAGE UID ERRORS", "/data/system/uiderrors.txt"); @@ -161,21 +184,41 @@ static void dumpstate() { to increase its timeout. we really need to do the timeouts in dumpsys itself... */ run_command("DUMPSYS", 60, "dumpsys", NULL); + + printf("========================================================\n"); + printf("== Running Application Activities\n"); + printf("========================================================\n"); + + run_command("APP ACTIVITIES", 30, "dumpsys", "activity", "all", NULL); + + printf("========================================================\n"); + printf("== Running Application Services\n"); + printf("========================================================\n"); + + run_command("APP SERVICES", 30, "dumpsys", "activity", "service", "all", NULL); + } static void usage() { - fprintf(stderr, "usage: dumpstate [-d] [-o file] [-s] [-z]\n" - " -d: append date to filename (requires -o)\n" + fprintf(stderr, "usage: dumpstate [-b soundfile] [-e soundfile] [-o file [-d] [-p] [-z]] [-s]\n" " -o: write to file (instead of stdout)\n" + " -d: append date to filename (requires -o)\n" + " -z: gzip output (requires -o)\n" + " -p: capture screenshot to filename.png (requires -o)\n" " -s: write output to control socket (for init)\n" - " -z: gzip output (requires -o)\n"); + " -b: play sound file instead of vibrate, at beginning of job\n" + " -e: play sound file instead of vibrate, at end of job\n" + ); } int main(int argc, char *argv[]) { int do_add_date = 0; int do_compress = 0; char* use_outfile = 0; + char* begin_sound = 0; + char* end_sound = 0; int use_socket = 0; + int do_fb = 0; LOGI("begin\n"); @@ -191,13 +234,16 @@ int main(int argc, char *argv[]) { dump_traces_path = dump_vm_traces(); int c; - while ((c = getopt(argc, argv, "dho:svz")) != -1) { + while ((c = getopt(argc, argv, "b:de:ho:svzp")) != -1) { switch (c) { + case 'b': begin_sound = optarg; break; case 'd': do_add_date = 1; break; + case 'e': end_sound = optarg; break; case 'o': use_outfile = optarg; break; case 's': use_socket = 1; break; case 'v': break; // compatibility no-op case 'z': do_compress = 6; break; + case 'p': do_fb = 1; break; case '?': printf("\n"); case 'h': usage(); @@ -246,6 +292,10 @@ int main(int argc, char *argv[]) { strftime(date, sizeof(date), "-%Y-%m-%d-%H-%M-%S", localtime(&now)); strlcat(path, date, sizeof(path)); } + if (do_fb) { + strlcpy(screenshot_path, path, sizeof(screenshot_path)); + strlcat(screenshot_path, ".png", sizeof(screenshot_path)); + } strlcat(path, ".txt", sizeof(path)); if (do_compress) strlcat(path, ".gz", sizeof(path)); strlcpy(tmp_path, path, sizeof(tmp_path)); @@ -253,16 +303,18 @@ int main(int argc, char *argv[]) { gzip_pid = redirect_to_file(stdout, tmp_path, do_compress); } - /* bzzzzzz */ - if (vibrator) { + if (begin_sound) { + play_sound(begin_sound); + } else if (vibrator) { fputs("150", vibrator); fflush(vibrator); } dumpstate(); - /* bzzz bzzz bzzz */ - if (vibrator) { + if (end_sound) { + play_sound(end_sound); + } else if (vibrator) { int i; for (i = 0; i < 3; i++) { fputs("75\n", vibrator); diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h index 682eafd..83b1d11 100644 --- a/cmds/dumpstate/dumpstate.h +++ b/cmds/dumpstate/dumpstate.h @@ -44,4 +44,7 @@ void for_each_pid(void (*func)(int, const char *), const char *header); /* Displays a blocked processes in-kernel wait channel */ void show_wchan(int pid, const char *name); +/* Play a sound via Stagefright */ +void play_sound(const char* path); + #endif /* _DUMPSTATE_H_ */ diff --git a/cmds/dumpstate/utils.c b/cmds/dumpstate/utils.c index c7a78cc..b2f9e80 100644 --- a/cmds/dumpstate/utils.c +++ b/cmds/dumpstate/utils.c @@ -167,6 +167,7 @@ int run_command(const char *title, int timeout_seconds, const char *command, ... execvp(command, (char**) args); printf("*** exec(%s): %s\n", command, strerror(errno)); + fflush(stdout); _exit(-1); } @@ -178,7 +179,7 @@ int run_command(const char *title, int timeout_seconds, const char *command, ... if (p == pid) { if (WIFSIGNALED(status)) { printf("*** %s: Killed by signal %d\n", command, WTERMSIG(status)); - } else if (WEXITSTATUS(status) > 0) { + } else if (WIFEXITED(status) && WEXITSTATUS(status) > 0) { printf("*** %s: Exit code %d\n", command, WEXITSTATUS(status)); } if (title) printf("[%s: %.1fs elapsed]\n\n", command, elapsed); @@ -429,3 +430,7 @@ const char *dump_vm_traces() { rename(anr_traces_path, traces_path); return dump_traces_path; } + +void play_sound(const char* path) { + run_command(NULL, 5, "/system/bin/stagefright", "-o", "-a", path, NULL); +} diff --git a/cmds/input/src/com/android/commands/input/Input.java b/cmds/input/src/com/android/commands/input/Input.java index 3a1accd..df1d0bf 100755 --- a/cmds/input/src/com/android/commands/input/Input.java +++ b/cmds/input/src/com/android/commands/input/Input.java @@ -91,7 +91,7 @@ public class Input { char[] chars = buff.toString().toCharArray(); KeyCharacterMap mKeyCharacterMap = KeyCharacterMap. - load(KeyCharacterMap.BUILT_IN_KEYBOARD); + load(KeyCharacterMap.VIRTUAL_KEYBOARD); KeyEvent[] events = mKeyCharacterMap.getEvents(chars); diff --git a/cmds/installd/Android.mk b/cmds/installd/Android.mk index 6673862..8641c30 100644 --- a/cmds/installd/Android.mk +++ b/cmds/installd/Android.mk @@ -1,21 +1,24 @@ ifneq ($(TARGET_SIMULATOR),true) -LOCAL_PATH:= $(call my-dir) +LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES:= \ +LOCAL_SRC_FILES := \ installd.c commands.c utils.c -LOCAL_C_INCLUDES := \ - $(call include-path-for, system-core)/cutils +#LOCAL_C_INCLUDES := \ +# $(call include-path-for, system-core)/cutils LOCAL_SHARED_LIBRARIES := \ libcutils -LOCAL_STATIC_LIBRARIES := +LOCAL_STATIC_LIBRARIES := \ + libdiskusage -LOCAL_MODULE:= installd +LOCAL_MODULE := installd + +LOCAL_MODULE_TAGS := optional include $(BUILD_EXECUTABLE) -endif # !simulator)) +endif # !simulator diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c index 2f03c7a..4d49c30 100644 --- a/cmds/installd/commands.c +++ b/cmds/installd/commands.c @@ -15,8 +15,9 @@ */ #include "installd.h" +#include <diskusage/dirsize.h> -int install(const char *pkgname, int encrypted_fs_flag, uid_t uid, gid_t gid) +int install(const char *pkgname, uid_t uid, gid_t gid) { char pkgdir[PKG_PATH_MAX]; char libdir[PKG_PATH_MAX]; @@ -26,17 +27,10 @@ int install(const char *pkgname, int encrypted_fs_flag, uid_t uid, gid_t gid) 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 (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 (mkdir(pkgdir, 0751) < 0) { LOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno)); @@ -61,38 +55,26 @@ int install(const char *pkgname, int encrypted_fs_flag, uid_t uid, gid_t gid) return 0; } -int uninstall(const char *pkgname, int encrypted_fs_flag) +int uninstall(const char *pkgname) { char pkgdir[PKG_PATH_MAX]; - 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; - } + if (create_pkg_path(pkgdir, PKG_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 encrypted_fs_flag) +int renamepkg(const char *oldpkgname, const char *newpkgname) { char oldpkgdir[PKG_PATH_MAX]; char newpkgdir[PKG_PATH_MAX]; - 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 (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 (rename(oldpkgdir, newpkgdir) < 0) { LOGE("cannot rename dir '%s' to '%s': %s\n", oldpkgdir, newpkgdir, strerror(errno)); @@ -101,41 +83,28 @@ int renamepkg(const char *oldpkgname, const char *newpkgname, int encrypted_fs_f return 0; } -int delete_user_data(const char *pkgname, int encrypted_fs_flag) +int delete_user_data(const char *pkgname) { char pkgdir[PKG_PATH_MAX]; - 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; - } + if (create_pkg_path(pkgdir, PKG_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 encrypted_fs_flag) +int delete_cache(const char *pkgname) { char cachedir[PKG_PATH_MAX]; - 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; - } + if (create_pkg_path(cachedir, CACHE_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 int64_t disk_free() { struct statfs sfs; @@ -168,39 +137,6 @@ int free_cache(int64_t free_size) LOGI("free_cache(%" PRId64 ") avail %" PRId64 "\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: %s\n", PKG_SEC_DIR_PREFIX, strerror(errno)); - } 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: %s\n", PKG_DIR_PREFIX, strerror(errno)); @@ -327,58 +263,9 @@ int protect(char *pkgname, gid_t gid) return 0; } -static int64_t stat_size(struct stat *s) -{ - int64_t blksize = s->st_blksize; - int64_t size = s->st_size; - - if (blksize) { - /* round up to filesystem block size */ - size = (size + blksize - 1) & (~(blksize - 1)); - } - - return size; -} - -static int64_t calculate_dir_size(int dfd) -{ - int64_t size = 0; - struct stat s; - DIR *d; - struct dirent *de; - - d = fdopendir(dfd); - if (d == NULL) { - close(dfd); - return 0; - } - - while ((de = readdir(d))) { - const char *name = de->d_name; - if (de->d_type == DT_DIR) { - int subfd; - /* 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) { - size += calculate_dir_size(subfd); - } - } else { - if (fstatat(dfd, name, &s, AT_SYMLINK_NOFOLLOW) == 0) { - size += stat_size(&s); - } - } - } - closedir(d); - return size; -} - int get_size(const char *pkgname, const char *apkpath, const char *fwdlock_apkpath, - int64_t *_codesize, int64_t *_datasize, int64_t *_cachesize, int encrypted_fs_flag) + int64_t *_codesize, int64_t *_datasize, int64_t *_cachesize) { DIR *d; int dfd; @@ -413,14 +300,8 @@ int get_size(const char *pkgname, const char *apkpath, } } - 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; - } + if (create_pkg_path(path, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX)) { + goto done; } d = opendir(path); diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c index 9ba6402..d2b2f7f 100644 --- a/cmds/installd/installd.c +++ b/cmds/installd/installd.c @@ -29,7 +29,7 @@ static int do_ping(char **arg, char reply[REPLY_MAX]) static int do_install(char **arg, char reply[REPLY_MAX]) { - return install(arg[0], atoi(arg[1]), atoi(arg[2]), atoi(arg[3])); /* pkgname, uid, gid */ + return install(arg[0], atoi(arg[1]), atoi(arg[2])); /* pkgname, uid, gid */ } static int do_dexopt(char **arg, char reply[REPLY_MAX]) @@ -50,12 +50,12 @@ static int do_rm_dex(char **arg, char reply[REPLY_MAX]) static int do_remove(char **arg, char reply[REPLY_MAX]) { - return uninstall(arg[0], atoi(arg[1])); /* pkgname */ + return uninstall(arg[0]); /* pkgname */ } static int do_rename(char **arg, char reply[REPLY_MAX]) { - return renamepkg(arg[0], arg[1], atoi(arg[2])); /* oldpkgname, newpkgname */ + return renamepkg(arg[0], arg[1]); /* oldpkgname, newpkgname */ } static int do_free_cache(char **arg, char reply[REPLY_MAX]) /* TODO int:free_size */ @@ -65,7 +65,7 @@ static int do_free_cache(char **arg, char reply[REPLY_MAX]) /* TODO int:free_siz static int do_rm_cache(char **arg, char reply[REPLY_MAX]) { - return delete_cache(arg[0], atoi(arg[1])); /* pkgname */ + return delete_cache(arg[0]); /* pkgname */ } static int do_protect(char **arg, char reply[REPLY_MAX]) @@ -81,7 +81,7 @@ static int do_get_size(char **arg, char reply[REPLY_MAX]) int res = 0; /* pkgdir, apkpath */ - res = get_size(arg[0], arg[1], arg[2], &codesize, &datasize, &cachesize, atoi(arg[3])); + res = get_size(arg[0], arg[1], arg[2], &codesize, &datasize, &cachesize); /* * Each int64_t can take up 22 characters printed out. Make sure it @@ -93,7 +93,7 @@ static int do_get_size(char **arg, char reply[REPLY_MAX]) static int do_rm_user_data(char **arg, char reply[REPLY_MAX]) { - return delete_user_data(arg[0], atoi(arg[1])); /* pkgname */ + return delete_user_data(arg[0]); /* pkgname */ } static int do_movefiles(char **arg, char reply[REPLY_MAX]) @@ -119,17 +119,17 @@ struct cmdinfo { struct cmdinfo cmds[] = { { "ping", 0, do_ping }, - { "install", 4, do_install }, + { "install", 3, do_install }, { "dexopt", 3, do_dexopt }, { "movedex", 2, do_move_dex }, { "rmdex", 1, do_rm_dex }, - { "remove", 2, do_remove }, - { "rename", 3, do_rename }, + { "remove", 1, do_remove }, + { "rename", 2, do_rename }, { "freecache", 1, do_free_cache }, - { "rmcache", 2, do_rm_cache }, + { "rmcache", 1, do_rm_cache }, { "protect", 2, do_protect }, - { "getsize", 4, do_get_size }, - { "rmuserdata", 2, do_rm_user_data }, + { "getsize", 3, do_get_size }, + { "rmuserdata", 1, do_rm_user_data }, { "movefiles", 0, do_movefiles }, { "linklib", 2, do_linklib }, { "unlinklib", 1, do_unlinklib }, diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h index 59475e9..77b58ec 100644 --- a/cmds/installd/installd.h +++ b/cmds/installd/installd.h @@ -50,23 +50,16 @@ /* 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/" @@ -98,16 +91,16 @@ int delete_dir_contents_fd(int dfd, const char *name); /* commands.c */ -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 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 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, - int64_t *codesize, int64_t *datasize, int64_t *cachesize, int encrypted_fs_flag); + int64_t *codesize, int64_t *datasize, int64_t *cachesize); int free_cache(int64_t free_size); int dexopt(const char *apk_path, uid_t uid, int is_public); int movefiles(); diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index f2b6fce..1b2326a 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -194,6 +194,7 @@ public final class Pm { private void runListPackages(boolean showApplicationPackage) { int getFlags = 0; boolean listDisabled = false, listEnabled = false; + boolean listSystem = false, listThirdParty = false; try { String opt; while ((opt=nextOption()) != null) { @@ -207,6 +208,10 @@ public final class Pm { listDisabled = true; } else if (opt.equals("-e")) { listEnabled = true; + } else if (opt.equals("-s")) { + listSystem = true; + } else if (opt.equals("-3")) { + listThirdParty = true; } else if (opt.equals("-u")) { getFlags |= PackageManager.GET_UNINSTALLED_PACKAGES; } else { @@ -232,8 +237,12 @@ public final class Pm { if (filter != null && !info.packageName.contains(filter)) { continue; } + final boolean isSystem = + (info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0; if ((!listDisabled || !info.applicationInfo.enabled) && - (!listEnabled || info.applicationInfo.enabled)) { + (!listEnabled || info.applicationInfo.enabled) && + (!listSystem || isSystem) && + (!listThirdParty || !isSystem)) { System.out.print("package:"); if (showApplicationPackage) { System.out.print(info.applicationInfo.sourceDir); @@ -276,7 +285,7 @@ public final class Pm { for (int i=0; i<rawList.length; i++) { list.add(rawList[i]); } - + // Sort by name Collections.sort(list, new Comparator<FeatureInfo>() { @@ -784,10 +793,10 @@ public final class Pm { boolean finished; boolean result; - public void packageDeleted(boolean succeeded) { + public void packageDeleted(String packageName, int returnCode) { synchronized (this) { finished = true; - result = succeeded; + result = returnCode == PackageManager.DELETE_SUCCEEDED; notifyAll(); } } @@ -1010,7 +1019,7 @@ public final class Pm { private static void showUsage() { System.err.println("usage: pm [list|path|install|uninstall]"); - System.err.println(" pm list packages [-f] [-d] [-e] [-u] [FILTER]"); + System.err.println(" pm list packages [-f] [-d] [-e] [-s] [-e] [-u] [FILTER]"); System.err.println(" pm list permission-groups"); System.err.println(" pm list permissions [-g] [-f] [-d] [-u] [GROUP]"); System.err.println(" pm list instrumentation [-f] [TARGET-PACKAGE]"); @@ -1027,8 +1036,10 @@ public final class Pm { System.err.println("The list packages command prints all packages, optionally only"); System.err.println("those whose package name contains the text in FILTER. Options:"); System.err.println(" -f: see their associated file."); - System.err.println(" -d: filter to include disbled packages."); - System.err.println(" -e: filter to include enabled packages."); + System.err.println(" -d: filter to only show disbled packages."); + System.err.println(" -e: filter to only show enabled packages."); + System.err.println(" -s: filter to only show system packages."); + System.err.println(" -3: filter to only show third party packages."); System.err.println(" -u: also include uninstalled packages."); System.err.println(""); System.err.println("The list permission-groups command prints all known"); @@ -1045,6 +1056,9 @@ public final class Pm { System.err.println("The list instrumentation command prints all instrumentations,"); System.err.println("or only those that target a specified package. Options:"); System.err.println(" -f: see their associated file."); + System.err.println("(Use this command to list all test packages, or use <TARGET-PACKAGE> "); + System.err.println(" to list the test packages for a particular application. The -f "); + System.err.println(" option lists the .apk file for the test package.)"); System.err.println(""); System.err.println("The list features command prints all features of the system."); System.err.println(""); diff --git a/cmds/rawbu/backup.cpp b/cmds/rawbu/backup.cpp index c4fa765..9ea046d 100644 --- a/cmds/rawbu/backup.cpp +++ b/cmds/rawbu/backup.cpp @@ -14,6 +14,7 @@ #include <utime.h> #include <sys/stat.h> #include <sys/types.h> +#include <stdint.h> #include <cutils/properties.h> diff --git a/cmds/screencap/Android.mk b/cmds/screencap/Android.mk index 1a6e23e..400a36b 100644 --- a/cmds/screencap/Android.mk +++ b/cmds/screencap/Android.mk @@ -8,6 +8,7 @@ LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils \ libbinder \ + libskia \ libui \ libsurfaceflinger_client @@ -15,4 +16,11 @@ LOCAL_MODULE:= screencap LOCAL_MODULE_TAGS := optional +LOCAL_C_INCLUDES += \ + external/skia/include/core \ + external/skia/include/effects \ + external/skia/include/images \ + external/skia/src/ports \ + external/skia/include/utils + include $(BUILD_EXECUTABLE) diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp index bc5e10d..7a599e9 100644 --- a/cmds/screencap/screencap.cpp +++ b/cmds/screencap/screencap.cpp @@ -14,29 +14,171 @@ * limitations under the License. */ +#include <errno.h> #include <unistd.h> +#include <stdio.h> #include <fcntl.h> +#include <linux/fb.h> +#include <sys/ioctl.h> +#include <sys/mman.h> + #include <binder/IMemory.h> #include <surfaceflinger/SurfaceComposerClient.h> +#include <SkImageEncoder.h> +#include <SkBitmap.h> +#include <SkStream.h> + using namespace android; +static void usage(const char* pname) +{ + fprintf(stderr, + "usage: %s [-hp] [FILENAME]\n" + " -h: this message\n" + " -p: save the file as a png.\n" + "If FILENAME ends with .png it will be saved as a png.\n" + "If FILENAME is not given, the results will be printed to stdout.\n", + pname + ); +} + +static SkBitmap::Config flinger2skia(PixelFormat f) +{ + switch (f) { + case PIXEL_FORMAT_A_8: + case PIXEL_FORMAT_L_8: + return SkBitmap::kA8_Config; + case PIXEL_FORMAT_RGB_565: + return SkBitmap::kRGB_565_Config; + case PIXEL_FORMAT_RGBA_4444: + return SkBitmap::kARGB_4444_Config; + default: + return SkBitmap::kARGB_8888_Config; + } +} + +static status_t vinfoToPixelFormat(const fb_var_screeninfo& vinfo, + uint32_t* bytespp, uint32_t* f) +{ + + switch (vinfo.bits_per_pixel) { + case 16: + *f = PIXEL_FORMAT_RGB_565; + *bytespp = 2; + break; + case 24: + *f = PIXEL_FORMAT_RGB_888; + *bytespp = 3; + break; + case 32: + // TODO: do better decoding of vinfo here + *f = PIXEL_FORMAT_RGBX_8888; + *bytespp = 4; + break; + default: + return BAD_VALUE; + } + return NO_ERROR; +} + int main(int argc, char** argv) { + const char* pname = argv[0]; + bool png = false; + int c; + while ((c = getopt(argc, argv, "ph")) != -1) { + switch (c) { + case 'p': + png = true; + break; + case '?': + case 'h': + usage(pname); + return 1; + } + } + argc -= optind; + argv += optind; + + int fd = -1; + if (argc == 0) { + fd = dup(STDOUT_FILENO); + } else if (argc == 1) { + const char* fn = argv[0]; + fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664); + if (fd == -1) { + fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno)); + return 1; + } + const int len = strlen(fn); + if (len >= 4 && 0 == strcmp(fn+len-4, ".png")) { + png = true; + } + } + + if (fd == -1) { + usage(pname); + return 1; + } + + void const* mapbase = MAP_FAILED; + ssize_t mapsize = -1; + + void const* base = 0; + uint32_t w, h, f; + size_t size = 0; + ScreenshotClient screenshot; - if (screenshot.update() != NO_ERROR) - return 0; - - void const* base = screenshot.getPixels(); - uint32_t w = screenshot.getWidth(); - uint32_t h = screenshot.getHeight(); - uint32_t f = screenshot.getFormat(); - int fd = dup(STDOUT_FILENO); - write(fd, &w, 4); - write(fd, &h, 4); - write(fd, &f, 4); - write(fd, base, w*h*4); + if (screenshot.update() == NO_ERROR) { + base = screenshot.getPixels(); + w = screenshot.getWidth(); + h = screenshot.getHeight(); + f = screenshot.getFormat(); + size = screenshot.getSize(); + } else { + const char* fbpath = "/dev/graphics/fb0"; + int fb = open(fbpath, O_RDONLY); + if (fb >= 0) { + struct fb_var_screeninfo vinfo; + if (ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) == 0) { + uint32_t bytespp; + if (vinfoToPixelFormat(vinfo, &bytespp, &f) == NO_ERROR) { + size_t offset = (vinfo.xoffset + vinfo.yoffset*vinfo.xres) * bytespp; + w = vinfo.xres; + h = vinfo.yres; + size = w*h*bytespp; + mapsize = offset + size; + mapbase = mmap(0, mapsize, PROT_READ, MAP_PRIVATE, fb, 0); + if (mapbase != MAP_FAILED) { + base = (void const *)((char const *)mapbase + offset); + } + } + } + close(fb); + } + } + + if (base) { + if (png) { + SkBitmap b; + b.setConfig(flinger2skia(f), w, h); + b.setPixels((void*)base); + SkDynamicMemoryWStream stream; + SkImageEncoder::EncodeStream(&stream, b, + SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality); + write(fd, stream.getStream(), stream.getOffset()); + } else { + write(fd, &w, 4); + write(fd, &h, 4); + write(fd, &f, 4); + write(fd, base, size); + } + } close(fd); + if (mapbase != MAP_FAILED) { + munmap((void *)mapbase, mapsize); + } return 0; } diff --git a/cmds/screenshot/Android.mk b/cmds/screenshot/Android.mk new file mode 100644 index 0000000..99c7aeb --- /dev/null +++ b/cmds/screenshot/Android.mk @@ -0,0 +1,16 @@ +ifneq ($(TARGET_SIMULATOR),true) + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := screenshot.c + +LOCAL_MODULE := screenshot + +LOCAL_SHARED_LIBRARIES := libcutils libz +LOCAL_STATIC_LIBRARIES := libpng +LOCAL_C_INCLUDES += external/zlib + +include $(BUILD_EXECUTABLE) + +endif diff --git a/cmds/screenshot/screenshot.c b/cmds/screenshot/screenshot.c new file mode 100644 index 0000000..048636c --- /dev/null +++ b/cmds/screenshot/screenshot.c @@ -0,0 +1,171 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> + +#include <linux/fb.h> + +#include <zlib.h> +#include <libpng/png.h> + +#include "private/android_filesystem_config.h" + +#define LOG_TAG "screenshot" +#include <utils/Log.h> + +void take_screenshot(FILE *fb_in, FILE *fb_out) { + int fb; + char imgbuf[0x10000]; + struct fb_var_screeninfo vinfo; + png_structp png; + png_infop info; + unsigned int r,c,rowlen; + unsigned int bytespp,offset; + + fb = fileno(fb_in); + if(fb < 0) { + LOGE("failed to open framebuffer\n"); + return; + } + fb_in = fdopen(fb, "r"); + + if(ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) < 0) { + LOGE("failed to get framebuffer info\n"); + return; + } + fcntl(fb, F_SETFD, FD_CLOEXEC); + + png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png == NULL) { + LOGE("failed png_create_write_struct\n"); + fclose(fb_in); + return; + } + + png_init_io(png, fb_out); + info = png_create_info_struct(png); + if (info == NULL) { + LOGE("failed png_create_info_struct\n"); + png_destroy_write_struct(&png, NULL); + fclose(fb_in); + return; + } + if (setjmp(png_jmpbuf(png))) { + LOGE("failed png setjmp\n"); + png_destroy_write_struct(&png, NULL); + fclose(fb_in); + return; + } + + bytespp = vinfo.bits_per_pixel / 8; + png_set_IHDR(png, info, + vinfo.xres, vinfo.yres, vinfo.bits_per_pixel / 4, + PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + png_write_info(png, info); + + rowlen=vinfo.xres * bytespp; + if (rowlen > sizeof(imgbuf)) { + LOGE("crazy rowlen: %d\n", rowlen); + png_destroy_write_struct(&png, NULL); + fclose(fb_in); + return; + } + + offset = vinfo.xoffset * bytespp + vinfo.xres * vinfo.yoffset * bytespp; + fseek(fb_in, offset, SEEK_SET); + + for(r=0; r<vinfo.yres; r++) { + int len = fread(imgbuf, 1, rowlen, fb_in); + if (len <= 0) break; + png_write_row(png, (png_bytep)imgbuf); + } + + png_write_end(png, info); + fclose(fb_in); + png_destroy_write_struct(&png, NULL); +} + +void fork_sound(const char* path) { + pid_t pid = fork(); + if (pid == 0) { + execl("/system/bin/stagefright", "stagefright", "-o", "-a", path, NULL); + } +} + +void usage() { + fprintf(stderr, + "usage: screenshot [-s soundfile] filename.png\n" + " -s: play a sound effect to signal success\n" + " -i: autoincrement to avoid overwriting filename.png\n" + ); +} + +int main(int argc, char**argv) { + FILE *png = NULL; + FILE *fb_in = NULL; + char outfile[PATH_MAX] = ""; + + char * soundfile = NULL; + int do_increment = 0; + + int c; + while ((c = getopt(argc, argv, "s:i")) != -1) { + switch (c) { + case 's': soundfile = optarg; break; + case 'i': do_increment = 1; break; + case '?': + case 'h': + usage(); exit(1); + } + } + argc -= optind; + argv += optind; + + if (argc < 1) { + usage(); exit(1); + } + + strlcpy(outfile, argv[0], PATH_MAX); + if (do_increment) { + struct stat st; + char base[PATH_MAX] = ""; + int i = 0; + while (stat(outfile, &st) == 0) { + if (!base[0]) { + char *p = strrchr(outfile, '.'); + if (p) *p = '\0'; + strcpy(base, outfile); + } + snprintf(outfile, PATH_MAX, "%s-%d.png", base, ++i); + } + } + + fb_in = fopen("/dev/graphics/fb0", "r"); + if (!fb_in) { + fprintf(stderr, "error: could not read framebuffer\n"); + exit(1); + } + + /* switch to non-root user and group */ + gid_t groups[] = { AID_LOG, AID_SDCARD_RW }; + setgroups(sizeof(groups)/sizeof(groups[0]), groups); + setuid(AID_SHELL); + + png = fopen(outfile, "w"); + if (!png) { + fprintf(stderr, "error: writing file %s: %s\n", + outfile, strerror(errno)); + exit(1); + } + + take_screenshot(fb_in, png); + + if (soundfile) { + fork_sound(soundfile); + } + + exit(0); +} diff --git a/cmds/servicemanager/service_manager.c b/cmds/servicemanager/service_manager.c index 14536bd..2df450f 100644 --- a/cmds/servicemanager/service_manager.c +++ b/cmds/servicemanager/service_manager.c @@ -34,7 +34,6 @@ static struct { { AID_MEDIA, "media.player" }, { AID_MEDIA, "media.camera" }, { AID_MEDIA, "media.audio_policy" }, - { AID_DRMIO, "drm.drmIOService" }, { AID_DRM, "drm.drmManager" }, { AID_NFC, "nfc" }, { AID_RADIO, "radio.phone" }, @@ -161,9 +160,9 @@ int do_add_service(struct binder_state *bs, si = find_svc(s, len); if (si) { if (si->ptr) { - LOGE("add_service('%s',%p) uid=%d - ALREADY REGISTERED\n", + LOGE("add_service('%s',%p) uid=%d - ALREADY REGISTERED, OVERRIDE\n", str8(s), ptr, uid); - return -1; + svcinfo_death(bs, si); } si->ptr = ptr; } else { diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk index 5b74007..1b13dd9 100644 --- a/cmds/stagefright/Android.mk +++ b/cmds/stagefright/Android.mk @@ -7,13 +7,16 @@ LOCAL_SRC_FILES:= \ SineSource.cpp LOCAL_SHARED_LIBRARIES := \ - libstagefright libmedia libutils libbinder libstagefright_foundation + libstagefright libmedia libutils libbinder libstagefright_foundation \ + libskia libsurfaceflinger_client libgui LOCAL_C_INCLUDES:= \ $(JNI_H_INCLUDE) \ frameworks/base/media/libstagefright \ frameworks/base/media/libstagefright/include \ - $(TOP)/frameworks/base/include/media/stagefright/openmax + $(TOP)/frameworks/base/include/media/stagefright/openmax \ + external/skia/include/core \ + external/skia/include/images \ LOCAL_CFLAGS += -Wno-multichar @@ -53,6 +56,31 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ SineSource.cpp \ + recordvideo.cpp + +LOCAL_SHARED_LIBRARIES := \ + libstagefright liblog libutils libbinder + +LOCAL_C_INCLUDES:= \ + $(JNI_H_INCLUDE) \ + frameworks/base/media/libstagefright \ + $(TOP)/frameworks/base/include/media/stagefright/openmax + +LOCAL_CFLAGS += -Wno-multichar + +LOCAL_MODULE_TAGS := debug + +LOCAL_MODULE:= recordvideo + +include $(BUILD_EXECUTABLE) + + +################################################################################ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + SineSource.cpp \ audioloop.cpp LOCAL_SHARED_LIBRARIES := \ @@ -70,3 +98,53 @@ LOCAL_MODULE_TAGS := debug LOCAL_MODULE:= audioloop include $(BUILD_EXECUTABLE) + +################################################################################ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + stream.cpp \ + +LOCAL_SHARED_LIBRARIES := \ + libstagefright liblog libutils libbinder libsurfaceflinger_client \ + libstagefright_foundation libmedia + +LOCAL_C_INCLUDES:= \ + $(JNI_H_INCLUDE) \ + frameworks/base/media/libstagefright \ + $(TOP)/frameworks/base/include/media/stagefright/openmax + +LOCAL_CFLAGS += -Wno-multichar + +LOCAL_MODULE_TAGS := debug + +LOCAL_MODULE:= stream + +include $(BUILD_EXECUTABLE) + +################################################################################ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + sf2.cpp \ + +LOCAL_SHARED_LIBRARIES := \ + libstagefright liblog libutils libbinder libstagefright_foundation \ + libmedia libsurfaceflinger_client libcutils libui + +LOCAL_C_INCLUDES:= \ + $(JNI_H_INCLUDE) \ + frameworks/base/media/libstagefright \ + $(TOP)/frameworks/base/include/media/stagefright/openmax + +LOCAL_CFLAGS += -Wno-multichar + +LOCAL_MODULE_TAGS := debug + +LOCAL_MODULE:= sf2 + +include $(BUILD_EXECUTABLE) + + diff --git a/cmds/stagefright/recordvideo.cpp b/cmds/stagefright/recordvideo.cpp new file mode 100644 index 0000000..1264215 --- /dev/null +++ b/cmds/stagefright/recordvideo.cpp @@ -0,0 +1,303 @@ +/* + * 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. + */ + +#include "SineSource.h" + +#include <binder/ProcessState.h> +#include <media/stagefright/AudioPlayer.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaDebug.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MPEG4Writer.h> +#include <media/stagefright/OMXClient.h> +#include <media/stagefright/OMXCodec.h> +#include <media/MediaPlayerInterface.h> + +using namespace android; + +// Print usage showing how to use this utility to record videos +static void usage(const char *me) { + fprintf(stderr, "usage: %s\n", me); + fprintf(stderr, " -h(elp)\n"); + fprintf(stderr, " -b bit rate in bits per second (default: 300000)\n"); + fprintf(stderr, " -c YUV420 color format: [0] semi planar or [1] planar (default: 1)\n"); + fprintf(stderr, " -f frame rate in frames per second (default: 30)\n"); + fprintf(stderr, " -i I frame interval in seconds (default: 1)\n"); + fprintf(stderr, " -n number of frames to be recorded (default: 300)\n"); + fprintf(stderr, " -w width in pixels (default: 176)\n"); + fprintf(stderr, " -t height in pixels (default: 144)\n"); + fprintf(stderr, " -l encoder level. see omx il header (default: encoder specific)\n"); + fprintf(stderr, " -p encoder profile. see omx il header (default: encoder specific)\n"); + fprintf(stderr, " -v video codec: [0] AVC [1] M4V [2] H263 (default: 0)\n"); + fprintf(stderr, "The output file is /sdcard/output.mp4\n"); + exit(1); +} + +class DummySource : public MediaSource { + +public: + DummySource(int width, int height, int nFrames, int fps, int colorFormat) + : mWidth(width), + mHeight(height), + mMaxNumFrames(nFrames), + mFrameRate(fps), + mColorFormat(colorFormat), + mSize((width * height * 3) / 2) { + + mGroup.add_buffer(new MediaBuffer(mSize)); + + // Check the color format to make sure + // that the buffer size mSize it set correctly above. + CHECK(colorFormat == OMX_COLOR_FormatYUV420SemiPlanar || + colorFormat == OMX_COLOR_FormatYUV420Planar); + } + + virtual sp<MetaData> getFormat() { + sp<MetaData> meta = new MetaData; + meta->setInt32(kKeyWidth, mWidth); + meta->setInt32(kKeyHeight, mHeight); + meta->setInt32(kKeyColorFormat, mColorFormat); + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW); + + return meta; + } + + virtual status_t start(MetaData *params) { + mNumFramesOutput = 0; + return OK; + } + + virtual status_t stop() { + return OK; + } + + virtual status_t read( + MediaBuffer **buffer, const MediaSource::ReadOptions *options) { + + if (mNumFramesOutput % 10 == 0) { + fprintf(stderr, "."); + } + if (mNumFramesOutput == mMaxNumFrames) { + return ERROR_END_OF_STREAM; + } + + status_t err = mGroup.acquire_buffer(buffer); + if (err != OK) { + return err; + } + + // We don't care about the contents. we just test video encoder + // Also, by skipping the content generation, we can return from + // read() much faster. + //char x = (char)((double)rand() / RAND_MAX * 255); + //memset((*buffer)->data(), x, mSize); + (*buffer)->set_range(0, mSize); + (*buffer)->meta_data()->clear(); + (*buffer)->meta_data()->setInt64( + kKeyTime, (mNumFramesOutput * 1000000) / mFrameRate); + ++mNumFramesOutput; + + return OK; + } + +protected: + virtual ~DummySource() {} + +private: + MediaBufferGroup mGroup; + int mWidth, mHeight; + int mMaxNumFrames; + int mFrameRate; + int mColorFormat; + size_t mSize; + int64_t mNumFramesOutput;; + + DummySource(const DummySource &); + DummySource &operator=(const DummySource &); +}; + +enum { + kYUV420SP = 0, + kYUV420P = 1, +}; + +// returns -1 if mapping of the given color is unsuccessful +// returns an omx color enum value otherwise +static int translateColorToOmxEnumValue(int color) { + switch (color) { + case kYUV420SP: + return OMX_COLOR_FormatYUV420SemiPlanar; + case kYUV420P: + return OMX_COLOR_FormatYUV420Planar; + default: + fprintf(stderr, "Unsupported color: %d\n", color); + return -1; + } +} + +int main(int argc, char **argv) { + + // Default values for the program if not overwritten + int frameRateFps = 30; + int width = 176; + int height = 144; + int bitRateBps = 300000; + int iFramesIntervalSeconds = 1; + int colorFormat = OMX_COLOR_FormatYUV420Planar; + int nFrames = 300; + int level = -1; // Encoder specific default + int profile = -1; // Encoder specific default + int codec = 0; + const char *fileName = "/sdcard/output.mp4"; + + android::ProcessState::self()->startThreadPool(); + int res; + while ((res = getopt(argc, argv, "b:c:f:i:n:w:t:l:p:v:h")) >= 0) { + switch (res) { + case 'b': + { + bitRateBps = atoi(optarg); + break; + } + + case 'c': + { + colorFormat = translateColorToOmxEnumValue(atoi(optarg)); + if (colorFormat == -1) { + usage(argv[0]); + } + break; + } + + case 'f': + { + frameRateFps = atoi(optarg); + break; + } + + case 'i': + { + iFramesIntervalSeconds = atoi(optarg); + break; + } + + case 'n': + { + nFrames = atoi(optarg); + break; + } + + case 'w': + { + width = atoi(optarg); + break; + } + + case 't': + { + height = atoi(optarg); + break; + } + + case 'l': + { + level = atoi(optarg); + break; + } + + case 'p': + { + profile = atoi(optarg); + break; + } + + case 'v': + { + codec = atoi(optarg); + if (codec < 0 || codec > 2) { + usage(argv[0]); + } + break; + } + + case 'h': + default: + { + usage(argv[0]); + break; + } + } + } + + OMXClient client; + CHECK_EQ(client.connect(), OK); + + status_t err = OK; + sp<MediaSource> source = + new DummySource(width, height, nFrames, frameRateFps, colorFormat); + + sp<MetaData> enc_meta = new MetaData; + switch (codec) { + case 1: + enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4); + break; + case 2: + enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263); + break; + default: + enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC); + break; + } + enc_meta->setInt32(kKeyWidth, width); + enc_meta->setInt32(kKeyHeight, height); + enc_meta->setInt32(kKeyFrameRate, frameRateFps); + enc_meta->setInt32(kKeyBitRate, bitRateBps); + enc_meta->setInt32(kKeyStride, width); + enc_meta->setInt32(kKeySliceHeight, height); + enc_meta->setInt32(kKeyIFramesInterval, iFramesIntervalSeconds); + enc_meta->setInt32(kKeyColorFormat, colorFormat); + if (level != -1) { + enc_meta->setInt32(kKeyVideoLevel, level); + } + if (profile != -1) { + enc_meta->setInt32(kKeyVideoProfile, profile); + } + + sp<MediaSource> encoder = + OMXCodec::Create( + client.interface(), enc_meta, true /* createEncoder */, source); + + sp<MPEG4Writer> writer = new MPEG4Writer(fileName); + writer->addSource(encoder); + int64_t start = systemTime(); + CHECK_EQ(OK, writer->start()); + while (!writer->reachedEOS()) { + } + err = writer->stop(); + int64_t end = systemTime(); + + fprintf(stderr, "$\n"); + client.disconnect(); + + if (err != OK && err != ERROR_END_OF_STREAM) { + fprintf(stderr, "record failed: %d\n", err); + return 1; + } + fprintf(stderr, "encoding %d frames in %lld us\n", nFrames, (end-start)/1000); + fprintf(stderr, "encoding speed is: %.2f fps\n", (nFrames * 1E9) / (end-start)); + return 0; +} diff --git a/cmds/stagefright/sf2.cpp b/cmds/stagefright/sf2.cpp new file mode 100644 index 0000000..c1d0803 --- /dev/null +++ b/cmds/stagefright/sf2.cpp @@ -0,0 +1,581 @@ +/* + * 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. + */ + +#include <binder/ProcessState.h> + +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/foundation/AMessage.h> + +#include <media/stagefright/ACodec.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaExtractor.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> + +#include <surfaceflinger/ISurfaceComposer.h> +#include <surfaceflinger/SurfaceComposerClient.h> + +#include "include/ESDS.h" + +using namespace android; + +struct Controller : public AHandler { + Controller(const char *uri, bool decodeAudio, const sp<Surface> &surface) + : mURI(uri), + mDecodeAudio(decodeAudio), + mSurface(surface), + mCodec(new ACodec) { + CHECK(!mDecodeAudio || mSurface == NULL); + } + + void startAsync() { + (new AMessage(kWhatStart, id()))->post(); + } + +protected: + virtual ~Controller() { + } + + virtual void onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatStart: + { +#if 1 + mDecodeLooper = looper(); +#else + mDecodeLooper = new ALooper; + mDecodeLooper->setName("sf2 decode looper"); + mDecodeLooper->start(); +#endif + + sp<DataSource> dataSource = + DataSource::CreateFromURI(mURI.c_str()); + + sp<MediaExtractor> extractor = + MediaExtractor::Create(dataSource); + + for (size_t i = 0; i < extractor->countTracks(); ++i) { + sp<MetaData> meta = extractor->getTrackMetaData(i); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + if (!strncasecmp(mDecodeAudio ? "audio/" : "video/", + mime, 6)) { + mSource = extractor->getTrack(i); + break; + } + } + CHECK(mSource != NULL); + + CHECK_EQ(mSource->start(), (status_t)OK); + + mDecodeLooper->registerHandler(mCodec); + + mCodec->setNotificationMessage( + new AMessage(kWhatCodecNotify, id())); + + sp<AMessage> format = makeFormat(mSource->getFormat()); + + if (mSurface != NULL) { + format->setObject("surface", mSurface); + } + + mCodec->initiateSetup(format); + + mCSDIndex = 0; + mStartTimeUs = ALooper::GetNowUs(); + mNumOutputBuffersReceived = 0; + mTotalBytesReceived = 0; + mLeftOverBuffer = NULL; + mFinalResult = OK; + mSeekState = SEEK_NONE; + + // (new AMessage(kWhatSeek, id()))->post(5000000ll); + break; + } + + case kWhatSeek: + { + printf("+"); + fflush(stdout); + + CHECK(mSeekState == SEEK_NONE + || mSeekState == SEEK_FLUSH_COMPLETED); + + if (mLeftOverBuffer != NULL) { + mLeftOverBuffer->release(); + mLeftOverBuffer = NULL; + } + + mSeekState = SEEK_FLUSHING; + mSeekTimeUs = 30000000ll; + + mCodec->signalFlush(); + break; + } + + case kWhatStop: + { + if (mLeftOverBuffer != NULL) { + mLeftOverBuffer->release(); + mLeftOverBuffer = NULL; + } + + CHECK_EQ(mSource->stop(), (status_t)OK); + mSource.clear(); + + mCodec->initiateShutdown(); + break; + } + + case kWhatCodecNotify: + { + int32_t what; + CHECK(msg->findInt32("what", &what)); + + if (what == ACodec::kWhatFillThisBuffer) { + onFillThisBuffer(msg); + } else if (what == ACodec::kWhatDrainThisBuffer) { + if ((mNumOutputBuffersReceived++ % 16) == 0) { + printf("."); + fflush(stdout); + } + + onDrainThisBuffer(msg); + } else if (what == ACodec::kWhatEOS) { + printf("$\n"); + + int64_t delayUs = ALooper::GetNowUs() - mStartTimeUs; + + if (mDecodeAudio) { + printf("%lld bytes received. %.2f KB/sec\n", + mTotalBytesReceived, + mTotalBytesReceived * 1E6 / 1024 / delayUs); + } else { + printf("%d frames decoded, %.2f fps. %lld bytes " + "received. %.2f KB/sec\n", + mNumOutputBuffersReceived, + mNumOutputBuffersReceived * 1E6 / delayUs, + mTotalBytesReceived, + mTotalBytesReceived * 1E6 / 1024 / delayUs); + } + + (new AMessage(kWhatStop, id()))->post(); + } else if (what == ACodec::kWhatFlushCompleted) { + mSeekState = SEEK_FLUSH_COMPLETED; + mCodec->signalResume(); + + (new AMessage(kWhatSeek, id()))->post(5000000ll); + } else if (what == ACodec::kWhatOutputFormatChanged) { + } else { + CHECK_EQ(what, (int32_t)ACodec::kWhatShutdownCompleted); + + mDecodeLooper->unregisterHandler(mCodec->id()); + + if (mDecodeLooper != looper()) { + mDecodeLooper->stop(); + } + + looper()->stop(); + } + break; + } + + default: + TRESPASS(); + break; + } + } + +private: + enum { + kWhatStart = 'strt', + kWhatStop = 'stop', + kWhatCodecNotify = 'noti', + kWhatSeek = 'seek', + }; + + sp<ALooper> mDecodeLooper; + + AString mURI; + bool mDecodeAudio; + sp<Surface> mSurface; + sp<ACodec> mCodec; + sp<MediaSource> mSource; + + Vector<sp<ABuffer> > mCSD; + size_t mCSDIndex; + + MediaBuffer *mLeftOverBuffer; + status_t mFinalResult; + + int64_t mStartTimeUs; + int32_t mNumOutputBuffersReceived; + int64_t mTotalBytesReceived; + + enum SeekState { + SEEK_NONE, + SEEK_FLUSHING, + SEEK_FLUSH_COMPLETED, + }; + SeekState mSeekState; + int64_t mSeekTimeUs; + + sp<AMessage> makeFormat(const sp<MetaData> &meta) { + CHECK(mCSD.isEmpty()); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + sp<AMessage> msg = new AMessage; + msg->setString("mime", mime); + + if (!strncasecmp("video/", mime, 6)) { + int32_t width, height; + CHECK(meta->findInt32(kKeyWidth, &width)); + CHECK(meta->findInt32(kKeyHeight, &height)); + + msg->setInt32("width", width); + msg->setInt32("height", height); + } else { + CHECK(!strncasecmp("audio/", mime, 6)); + + int32_t numChannels, sampleRate; + CHECK(meta->findInt32(kKeyChannelCount, &numChannels)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + + msg->setInt32("channel-count", numChannels); + msg->setInt32("sample-rate", sampleRate); + } + + uint32_t type; + const void *data; + size_t size; + if (meta->findData(kKeyAVCC, &type, &data, &size)) { + // Parse the AVCDecoderConfigurationRecord + + const uint8_t *ptr = (const uint8_t *)data; + + CHECK(size >= 7); + CHECK_EQ((unsigned)ptr[0], 1u); // configurationVersion == 1 + uint8_t profile = ptr[1]; + uint8_t level = ptr[3]; + + // There is decodable content out there that fails the following + // assertion, let's be lenient for now... + // CHECK((ptr[4] >> 2) == 0x3f); // reserved + + size_t lengthSize = 1 + (ptr[4] & 3); + + // commented out check below as H264_QVGA_500_NO_AUDIO.3gp + // violates it... + // CHECK((ptr[5] >> 5) == 7); // reserved + + size_t numSeqParameterSets = ptr[5] & 31; + + ptr += 6; + size -= 6; + + sp<ABuffer> buffer = new ABuffer(1024); + buffer->setRange(0, 0); + + for (size_t i = 0; i < numSeqParameterSets; ++i) { + CHECK(size >= 2); + size_t length = U16_AT(ptr); + + ptr += 2; + size -= 2; + + CHECK(size >= length); + + memcpy(buffer->data() + buffer->size(), "\x00\x00\x00\x01", 4); + memcpy(buffer->data() + buffer->size() + 4, ptr, length); + buffer->setRange(0, buffer->size() + 4 + length); + + ptr += length; + size -= length; + } + + buffer->meta()->setInt32("csd", true); + mCSD.push(buffer); + + buffer = new ABuffer(1024); + buffer->setRange(0, 0); + + CHECK(size >= 1); + size_t numPictureParameterSets = *ptr; + ++ptr; + --size; + + for (size_t i = 0; i < numPictureParameterSets; ++i) { + CHECK(size >= 2); + size_t length = U16_AT(ptr); + + ptr += 2; + size -= 2; + + CHECK(size >= length); + + memcpy(buffer->data() + buffer->size(), "\x00\x00\x00\x01", 4); + memcpy(buffer->data() + buffer->size() + 4, ptr, length); + buffer->setRange(0, buffer->size() + 4 + length); + + ptr += length; + size -= length; + } + + buffer->meta()->setInt32("csd", true); + mCSD.push(buffer); + + msg->setObject("csd", buffer); + } else if (meta->findData(kKeyESDS, &type, &data, &size)) { + ESDS esds((const char *)data, size); + CHECK_EQ(esds.InitCheck(), (status_t)OK); + + const void *codec_specific_data; + size_t codec_specific_data_size; + esds.getCodecSpecificInfo( + &codec_specific_data, &codec_specific_data_size); + + sp<ABuffer> buffer = new ABuffer(codec_specific_data_size); + + memcpy(buffer->data(), codec_specific_data, + codec_specific_data_size); + + buffer->meta()->setInt32("csd", true); + mCSD.push(buffer); + } + + int32_t maxInputSize; + if (meta->findInt32(kKeyMaxInputSize, &maxInputSize)) { + msg->setInt32("max-input-size", maxInputSize); + } + + return msg; + } + + void onFillThisBuffer(const sp<AMessage> &msg) { + sp<AMessage> reply; + CHECK(msg->findMessage("reply", &reply)); + + if (mSeekState == SEEK_FLUSHING) { + reply->post(); + return; + } + + sp<RefBase> obj; + CHECK(msg->findObject("buffer", &obj)); + sp<ABuffer> outBuffer = static_cast<ABuffer *>(obj.get()); + + if (mCSDIndex < mCSD.size()) { + outBuffer = mCSD.editItemAt(mCSDIndex++); + outBuffer->meta()->setInt64("timeUs", 0); + } else { + size_t sizeLeft = outBuffer->capacity(); + outBuffer->setRange(0, 0); + + int32_t n = 0; + + for (;;) { + MediaBuffer *inBuffer; + + if (mLeftOverBuffer != NULL) { + inBuffer = mLeftOverBuffer; + mLeftOverBuffer = NULL; + } else if (mFinalResult != OK) { + break; + } else { + MediaSource::ReadOptions options; + if (mSeekState == SEEK_FLUSH_COMPLETED) { + options.setSeekTo(mSeekTimeUs); + mSeekState = SEEK_NONE; + } + status_t err = mSource->read(&inBuffer, &options); + + if (err != OK) { + mFinalResult = err; + break; + } + } + + if (inBuffer->range_length() > sizeLeft) { + if (outBuffer->size() == 0) { + LOGE("Unable to fit even a single input buffer of size %d.", + inBuffer->range_length()); + } + CHECK_GT(outBuffer->size(), 0u); + + mLeftOverBuffer = inBuffer; + break; + } + + ++n; + + if (outBuffer->size() == 0) { + int64_t timeUs; + CHECK(inBuffer->meta_data()->findInt64(kKeyTime, &timeUs)); + + outBuffer->meta()->setInt64("timeUs", timeUs); + } + + memcpy(outBuffer->data() + outBuffer->size(), + (const uint8_t *)inBuffer->data() + + inBuffer->range_offset(), + inBuffer->range_length()); + + outBuffer->setRange( + 0, outBuffer->size() + inBuffer->range_length()); + + sizeLeft -= inBuffer->range_length(); + + inBuffer->release(); + inBuffer = NULL; + + // break; // Don't coalesce + } + + LOGV("coalesced %d input buffers", n); + + if (outBuffer->size() == 0) { + CHECK_NE(mFinalResult, (status_t)OK); + + reply->setInt32("err", mFinalResult); + reply->post(); + return; + } + } + + reply->setObject("buffer", outBuffer); + reply->post(); + } + + void onDrainThisBuffer(const sp<AMessage> &msg) { + sp<RefBase> obj; + CHECK(msg->findObject("buffer", &obj)); + + sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + mTotalBytesReceived += buffer->size(); + + sp<AMessage> reply; + CHECK(msg->findMessage("reply", &reply)); + + reply->post(); + } + + DISALLOW_EVIL_CONSTRUCTORS(Controller); +}; + +static void usage(const char *me) { + fprintf(stderr, "usage: %s\n", me); + fprintf(stderr, " -h(elp)\n"); + fprintf(stderr, " -a(udio)\n"); + + fprintf(stderr, + " -s(surface) Allocate output buffers on a surface.\n"); +} + +int main(int argc, char **argv) { + android::ProcessState::self()->startThreadPool(); + + bool decodeAudio = false; + bool useSurface = false; + + int res; + while ((res = getopt(argc, argv, "has")) >= 0) { + switch (res) { + case 'a': + decodeAudio = true; + break; + + case 's': + useSurface = true; + break; + + case '?': + case 'h': + default: + { + usage(argv[0]); + return 1; + } + } + } + + argc -= optind; + argv += optind; + + if (argc != 1) { + usage(argv[-optind]); + return 1; + } + + DataSource::RegisterDefaultSniffers(); + + sp<ALooper> looper = new ALooper; + looper->setName("sf2"); + + sp<SurfaceComposerClient> composerClient; + sp<SurfaceControl> control; + sp<Surface> surface; + + if (!decodeAudio && useSurface) { + composerClient = new SurfaceComposerClient; + CHECK_EQ(composerClient->initCheck(), (status_t)OK); + + control = composerClient->createSurface( + getpid(), + String8("A Surface"), + 0, + 1280, + 800, + PIXEL_FORMAT_RGB_565, + 0); + + CHECK(control != NULL); + CHECK(control->isValid()); + + CHECK_EQ(composerClient->openTransaction(), (status_t)OK); + CHECK_EQ(control->setLayer(30000), (status_t)OK); + CHECK_EQ(control->show(), (status_t)OK); + CHECK_EQ(composerClient->closeTransaction(), (status_t)OK); + + surface = control->getSurface(); + CHECK(surface != NULL); + } + + sp<Controller> controller = new Controller(argv[0], decodeAudio, surface); + looper->registerHandler(controller); + + controller->startAsync(); + + CHECK_EQ(looper->start(true /* runOnCallingThread */), (status_t)OK); + + looper->unregisterHandler(controller->id()); + + if (!decodeAudio && useSurface) { + composerClient->dispose(); + } + + return 0; +} + diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp index ff92431..a875c3a 100644 --- a/cmds/stagefright/stagefright.cpp +++ b/cmds/stagefright/stagefright.cpp @@ -31,7 +31,7 @@ #include <media/IMediaPlayerService.h> #include <media/stagefright/foundation/ALooper.h> #include "include/ARTSPController.h" -#include "include/LiveSource.h" +#include "include/LiveSession.h" #include "include/NuCachedSource2.h" #include <media/stagefright/AudioPlayer.h> #include <media/stagefright/DataSource.h> @@ -49,8 +49,17 @@ #include <media/stagefright/MPEG2TSWriter.h> #include <media/stagefright/MPEG4Writer.h> +#include <private/media/VideoFrame.h> +#include <SkBitmap.h> +#include <SkImageEncoder.h> + #include <fcntl.h> +#include <gui/SurfaceTextureClient.h> + +#include <surfaceflinger/ISurfaceComposer.h> +#include <surfaceflinger/SurfaceComposerClient.h> + using namespace android; static long gNumRepetitions; @@ -59,8 +68,13 @@ static long gReproduceBug; // if not -1. static bool gPreferSoftwareCodec; static bool gPlaybackAudio; static bool gWriteMP4; +static bool gDisplayHistogram; static String8 gWriteMP4Filename; +static sp<ANativeWindow> gSurface; + +#define USE_SURFACE_COMPOSER 0 + static int64_t getNowUs() { struct timeval tv; gettimeofday(&tv, NULL); @@ -68,6 +82,58 @@ static int64_t getNowUs() { return (int64_t)tv.tv_usec + tv.tv_sec * 1000000ll; } +static int CompareIncreasing(const int64_t *a, const int64_t *b) { + return (*a) < (*b) ? -1 : (*a) > (*b) ? 1 : 0; +} + +static void displayDecodeHistogram(Vector<int64_t> *decodeTimesUs) { + printf("decode times:\n"); + + decodeTimesUs->sort(CompareIncreasing); + + size_t n = decodeTimesUs->size(); + int64_t minUs = decodeTimesUs->itemAt(0); + int64_t maxUs = decodeTimesUs->itemAt(n - 1); + + printf("min decode time %lld us (%.2f secs)\n", minUs, minUs / 1E6); + printf("max decode time %lld us (%.2f secs)\n", maxUs, maxUs / 1E6); + + size_t counts[100]; + for (size_t i = 0; i < 100; ++i) { + counts[i] = 0; + } + + for (size_t i = 0; i < n; ++i) { + int64_t x = decodeTimesUs->itemAt(i); + + size_t slot = ((x - minUs) * 100) / (maxUs - minUs); + if (slot == 100) { slot = 99; } + + ++counts[slot]; + } + + for (size_t i = 0; i < 100; ++i) { + int64_t slotUs = minUs + (i * (maxUs - minUs) / 100); + + double fps = 1E6 / slotUs; + printf("[%.2f fps]: %d\n", fps, counts[i]); + } +} + +static void displayAVCProfileLevelIfPossible(const sp<MetaData>& meta) { + uint32_t type; + const void *data; + size_t size; + if (meta->findData(kKeyAVCC, &type, &data, &size)) { + const uint8_t *ptr = (const uint8_t *)data; + CHECK(size >= 7); + CHECK(ptr[0] == 1); // configurationVersion == 1 + uint8_t profile = ptr[1]; + uint8_t level = ptr[3]; + fprintf(stderr, "AVC video profile %d and level %d\n", profile, level); + } +} + static void playSource(OMXClient *client, sp<MediaSource> &source) { sp<MetaData> meta = source->getFormat(); @@ -81,12 +147,14 @@ static void playSource(OMXClient *client, sp<MediaSource> &source) { rawSource = OMXCodec::Create( client->interface(), meta, false /* createEncoder */, source, NULL /* matchComponentName */, - gPreferSoftwareCodec ? OMXCodec::kPreferSoftwareCodecs : 0); + gPreferSoftwareCodec ? OMXCodec::kPreferSoftwareCodecs : 0, + gSurface); if (rawSource == NULL) { fprintf(stderr, "Failed to instantiate decoder for '%s'.\n", mime); return; } + displayAVCProfileLevelIfPossible(meta); } source.clear(); @@ -201,6 +269,8 @@ static void playSource(OMXClient *client, sp<MediaSource> &source) { int64_t sumDecodeUs = 0; int64_t totalBytes = 0; + Vector<int64_t> decodeTimesUs; + while (numIterationsLeft-- > 0) { long numFrames = 0; @@ -224,9 +294,17 @@ static void playSource(OMXClient *client, sp<MediaSource> &source) { break; } - if (buffer->range_length() > 0 && (n++ % 16) == 0) { - printf("."); - fflush(stdout); + if (buffer->range_length() > 0) { + if (gDisplayHistogram && n > 0) { + // Ignore the first time since it includes some setup + // cost. + decodeTimesUs.push(delayDecodeUs); + } + + if ((n++ % 16) == 0) { + printf("."); + fflush(stdout); + } } sumDecodeUs += delayDecodeUs; @@ -266,6 +344,10 @@ static void playSource(OMXClient *client, sp<MediaSource> &source) { (double)sumDecodeUs / n); printf("decoded a total of %d frame(s).\n", n); + + if (gDisplayHistogram) { + displayDecodeHistogram(&decodeTimesUs); + } } else if (!strncasecmp("audio/", mime, 6)) { // Frame count makes less sense for audio, as the output buffer // sizes may be different across decoders. @@ -466,6 +548,9 @@ static void usage(const char *me) { fprintf(stderr, " -o playback audio\n"); fprintf(stderr, " -w(rite) filename (write to .mp4 file)\n"); fprintf(stderr, " -k seek test\n"); + fprintf(stderr, " -x display a histogram of decoding times/fps " + "(video only)\n"); + fprintf(stderr, " -S allocate buffers from a surface\n"); } int main(int argc, char **argv) { @@ -476,18 +561,21 @@ int main(int argc, char **argv) { bool dumpProfiles = false; bool extractThumbnail = false; bool seekTest = false; + bool useSurfaceAlloc = false; gNumRepetitions = 1; gMaxNumFrames = 0; gReproduceBug = -1; gPreferSoftwareCodec = false; gPlaybackAudio = false; gWriteMP4 = false; + gDisplayHistogram = false; sp<ALooper> looper; sp<ARTSPController> rtspController; + sp<LiveSession> liveSession; int res; - while ((res = getopt(argc, argv, "han:lm:b:ptsow:k")) >= 0) { + while ((res = getopt(argc, argv, "han:lm:b:ptsow:kxS")) >= 0) { switch (res) { case 'a': { @@ -560,6 +648,18 @@ int main(int argc, char **argv) { break; } + case 'x': + { + gDisplayHistogram = true; + break; + } + + case 'S': + { + useSurfaceAlloc = true; + break; + } + case '?': case 'h': default: @@ -602,6 +702,19 @@ int main(int argc, char **argv) { if (mem != NULL) { printf("getFrameAtTime(%s) => OK\n", filename); + + VideoFrame *frame = (VideoFrame *)mem->pointer(); + + SkBitmap bitmap; + bitmap.setConfig( + SkBitmap::kRGB_565_Config, frame->mWidth, frame->mHeight); + + bitmap.setPixels((uint8_t *)frame + sizeof(VideoFrame)); + + CHECK(SkImageEncoder::EncodeFile( + "/sdcard/out.jpg", bitmap, + SkImageEncoder::kJPEG_Type, + SkImageEncoder::kDefaultQuality)); } else { mem = retriever->extractAlbumArt(); @@ -685,6 +798,39 @@ int main(int argc, char **argv) { } } + sp<SurfaceComposerClient> composerClient; + sp<SurfaceControl> control; + + if (useSurfaceAlloc && !audioOnly) { +#if USE_SURFACE_COMPOSER + composerClient = new SurfaceComposerClient; + CHECK_EQ(composerClient->initCheck(), (status_t)OK); + + control = composerClient->createSurface( + getpid(), + String8("A Surface"), + 0, + 1280, + 800, + PIXEL_FORMAT_RGB_565, + 0); + + CHECK(control != NULL); + CHECK(control->isValid()); + + CHECK_EQ(composerClient->openTransaction(), (status_t)OK); + CHECK_EQ(control->setLayer(30000), (status_t)OK); + CHECK_EQ(control->show(), (status_t)OK); + CHECK_EQ(composerClient->closeTransaction(), (status_t)OK); + + gSurface = control->getSurface(); + CHECK(gSurface != NULL); +#else + sp<SurfaceTexture> texture = new SurfaceTexture(0 /* tex */); + gSurface = new SurfaceTextureClient(texture); +#endif + } + DataSource::RegisterDefaultSniffers(); OMXClient client; @@ -754,8 +900,15 @@ int main(int argc, char **argv) { String8 uri("http://"); uri.append(filename + 11); - dataSource = new LiveSource(uri.string()); - dataSource = new NuCachedSource2(dataSource); + if (looper == NULL) { + looper = new ALooper; + looper->start(); + } + liveSession = new LiveSession; + looper->registerHandler(liveSession); + + liveSession->connect(uri.string()); + dataSource = liveSession->getDataSource(); extractor = MediaExtractor::Create( @@ -855,6 +1008,14 @@ int main(int argc, char **argv) { } } + if (useSurfaceAlloc && !audioOnly) { + gSurface.clear(); + +#if USE_SURFACE_COMPOSER + composerClient->dispose(); +#endif + } + client.disconnect(); return 0; diff --git a/cmds/stagefright/stream.cpp b/cmds/stagefright/stream.cpp new file mode 100644 index 0000000..bb84bd1 --- /dev/null +++ b/cmds/stagefright/stream.cpp @@ -0,0 +1,206 @@ +/* + * 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. + */ + +#include <binder/ProcessState.h> + +#include <media/IStreamSource.h> +#include <media/mediaplayer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> + +#include <binder/IServiceManager.h> +#include <media/IMediaPlayerService.h> +#include <surfaceflinger/ISurfaceComposer.h> +#include <surfaceflinger/SurfaceComposerClient.h> + +#include <fcntl.h> + +using namespace android; + +struct MyStreamSource : public BnStreamSource { + // Caller retains ownership of fd. + MyStreamSource(int fd); + + virtual void setListener(const sp<IStreamListener> &listener); + virtual void setBuffers(const Vector<sp<IMemory> > &buffers); + + virtual void onBufferAvailable(size_t index); + +protected: + virtual ~MyStreamSource(); + +private: + int mFd; + off64_t mFileSize; + int64_t mNextSeekTimeUs; + + sp<IStreamListener> mListener; + Vector<sp<IMemory> > mBuffers; + + DISALLOW_EVIL_CONSTRUCTORS(MyStreamSource); +}; + +MyStreamSource::MyStreamSource(int fd) + : mFd(fd), + mFileSize(0), + mNextSeekTimeUs(-1) { // ALooper::GetNowUs() + 5000000ll) { + CHECK_GE(fd, 0); + + mFileSize = lseek64(fd, 0, SEEK_END); + lseek64(fd, 0, SEEK_SET); +} + +MyStreamSource::~MyStreamSource() { +} + +void MyStreamSource::setListener(const sp<IStreamListener> &listener) { + mListener = listener; +} + +void MyStreamSource::setBuffers(const Vector<sp<IMemory> > &buffers) { + mBuffers = buffers; +} + +void MyStreamSource::onBufferAvailable(size_t index) { + CHECK_LT(index, mBuffers.size()); + + if (mNextSeekTimeUs >= 0 && mNextSeekTimeUs <= ALooper::GetNowUs()) { + off64_t offset = (off64_t)(((float)rand() / RAND_MAX) * mFileSize * 0.8); + offset = (offset / 188) * 188; + + lseek(mFd, offset, SEEK_SET); + + mListener->issueCommand( + IStreamListener::DISCONTINUITY, false /* synchronous */); + + mNextSeekTimeUs = -1; + mNextSeekTimeUs = ALooper::GetNowUs() + 5000000ll; + } + + sp<IMemory> mem = mBuffers.itemAt(index); + + ssize_t n = read(mFd, mem->pointer(), mem->size()); + if (n <= 0) { + mListener->issueCommand(IStreamListener::EOS, false /* synchronous */); + } else { + mListener->queueBuffer(index, n); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +struct MyClient : public BnMediaPlayerClient { + MyClient() + : mEOS(false) { + } + + virtual void notify(int msg, int ext1, int ext2) { + Mutex::Autolock autoLock(mLock); + + if (msg == MEDIA_ERROR || msg == MEDIA_PLAYBACK_COMPLETE) { + mEOS = true; + mCondition.signal(); + } + } + + void waitForEOS() { + Mutex::Autolock autoLock(mLock); + while (!mEOS) { + mCondition.wait(mLock); + } + } + +protected: + virtual ~MyClient() { + } + +private: + Mutex mLock; + Condition mCondition; + + bool mEOS; + + DISALLOW_EVIL_CONSTRUCTORS(MyClient); +}; + +int main(int argc, char **argv) { + android::ProcessState::self()->startThreadPool(); + + if (argc != 2) { + fprintf(stderr, "Usage: %s filename\n", argv[0]); + return 1; + } + + sp<SurfaceComposerClient> composerClient = new SurfaceComposerClient; + CHECK_EQ(composerClient->initCheck(), (status_t)OK); + + sp<SurfaceControl> control = + composerClient->createSurface( + getpid(), + String8("A Surface"), + 0, + 1280, + 800, + PIXEL_FORMAT_RGB_565, + 0); + + CHECK(control != NULL); + CHECK(control->isValid()); + + CHECK_EQ(composerClient->openTransaction(), (status_t)OK); + CHECK_EQ(control->setLayer(30000), (status_t)OK); + CHECK_EQ(control->show(), (status_t)OK); + CHECK_EQ(composerClient->closeTransaction(), (status_t)OK); + + sp<Surface> surface = control->getSurface(); + CHECK(surface != NULL); + + sp<IServiceManager> sm = defaultServiceManager(); + sp<IBinder> binder = sm->getService(String16("media.player")); + sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder); + + CHECK(service.get() != NULL); + + int fd = open(argv[1], O_RDONLY); + + if (fd < 0) { + fprintf(stderr, "Failed to open file '%s'.", argv[1]); + return 1; + } + + sp<MyClient> client = new MyClient; + + sp<IMediaPlayer> player = + service->create(getpid(), client, new MyStreamSource(fd), 0); + + if (player != NULL) { + player->setVideoSurface(surface); + player->start(); + + client->waitForEOS(); + + player->stop(); + } else { + fprintf(stderr, "failed to instantiate player.\n"); + } + + close(fd); + fd = -1; + + composerClient->dispose(); + + return 0; +} diff --git a/cmds/system_server/library/Android.mk b/cmds/system_server/library/Android.mk index 457cbd4..e8afce3 100644 --- a/cmds/system_server/library/Android.mk +++ b/cmds/system_server/library/Android.mk @@ -21,6 +21,7 @@ LOCAL_SHARED_LIBRARIES := \ libaudioflinger \ libcameraservice \ libmediaplayerservice \ + libinput \ libutils \ libbinder \ libcutils |