Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2014-07-04 14:05:54 +02:00
commit 6b573fb299
36 changed files with 1055 additions and 197 deletions

View File

@ -19,11 +19,11 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b597b86274ab109d7ef530bc0c4b6adccddae4e4"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3326b51017252e09ccdd715dec6c5e12a7d1ecfe"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bf9aaf39dd5a6491925a022db167c460f8207d34"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
<!-- Stock Android things -->
@ -98,7 +98,7 @@
<project name="platform/system/vold" path="system/vold" revision="919829940468066a32f403980b43f6ebfee5d314"/>
<!-- Emulator specific things -->
<project name="android-development" path="development" remote="b2g" revision="9abf0ab68376afae3e1c7beefa3e9cbee2fde202"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="3c42f55d85fd311de2ed9268a59ba1db30be2b21"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="f486d0316aef2b3dea772dfb186e447e7a6a5de5"/>
<project name="platform/external/iproute2" path="external/iproute2" revision="c66c5716d5335e450f7a7b71ccc6a604fb2f41d2"/>
<project name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="d2685281e2e54ca14d1df304867aa82c37b27162"/>
<project name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="627f9b20fc518937b93747a7ff1ed4f5ed46e06f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b597b86274ab109d7ef530bc0c4b6adccddae4e4"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
@ -128,7 +128,7 @@
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="0e31f35a2a77301e91baa8a237aa9e9fa4076084"/>
<project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bfbb47844a20625d9bb6c9212fa554d7808bd58e"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="efd87a5797ca40fa2df256630c07e0dfb2f762dc"/>
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="832f4acaf481a19031e479a40b03d9ce5370ddee"/>
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="d0aa65b140a45016975ed0ecf35f280dd336e1d3"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="276ce45e78b09c4a4ee643646f691d22804754c1">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b597b86274ab109d7ef530bc0c4b6adccddae4e4"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -19,11 +19,11 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b597b86274ab109d7ef530bc0c4b6adccddae4e4"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3326b51017252e09ccdd715dec6c5e12a7d1ecfe"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bf9aaf39dd5a6491925a022db167c460f8207d34"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
<!-- Stock Android things -->
@ -98,7 +98,7 @@
<project name="platform/system/vold" path="system/vold" revision="919829940468066a32f403980b43f6ebfee5d314"/>
<!-- Emulator specific things -->
<project name="android-development" path="development" remote="b2g" revision="9abf0ab68376afae3e1c7beefa3e9cbee2fde202"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="3c42f55d85fd311de2ed9268a59ba1db30be2b21"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="f486d0316aef2b3dea772dfb186e447e7a6a5de5"/>
<project name="platform/external/iproute2" path="external/iproute2" revision="c66c5716d5335e450f7a7b71ccc6a604fb2f41d2"/>
<project name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="d2685281e2e54ca14d1df304867aa82c37b27162"/>
<project name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="627f9b20fc518937b93747a7ff1ed4f5ed46e06f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b597b86274ab109d7ef530bc0c4b6adccddae4e4"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "92bddc1a99aa2d80d58fc28749fecedbf5c692f1",
"revision": "333f877ae4029d7cb1a0893f89da484d8e3cc14f",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b597b86274ab109d7ef530bc0c4b6adccddae4e4"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b597b86274ab109d7ef530bc0c4b6adccddae4e4"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b597b86274ab109d7ef530bc0c4b6adccddae4e4"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b597b86274ab109d7ef530bc0c4b6adccddae4e4"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -786,8 +786,10 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DLL_SUFFIX@
#ifndef MOZ_WIDGET_GONK
#ifdef XP_WIN32
@BINPATH@/xpcshell.exe
@BINPATH@/ssltunnel.exe
#else
@BINPATH@/xpcshell
@BINPATH@/ssltunnel
#endif
#endif
@BINPATH@/chrome/icons/

View File

@ -9,6 +9,7 @@ support-files =
doc_media-node-creation.html
doc_destroy-nodes.html
doc_connect-toggle.html
doc_connect-param.html
440hz_sine.ogg
head.js
@ -20,6 +21,7 @@ support-files =
[browser_audionode-actor-is-source.js]
[browser_webaudio-actor-simple.js]
[browser_webaudio-actor-destroy-node.js]
[browser_webaudio-actor-connect-param.js]
[browser_wa_destroy-node-01.js]

View File

@ -0,0 +1,26 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test the `connect-param` event on the web audio actor.
*/
function spawnTest () {
let [target, debuggee, front] = yield initBackend(CONNECT_PARAM_URL);
let [_, _, [destNode, carrierNode, modNode, gainNode], _, connectParam] = yield Promise.all([
front.setup({ reload: true }),
once(front, "start-context"),
getN(front, "create-node", 4),
get2(front, "connect-node"),
once(front, "connect-param")
]);
info(connectParam);
is(connectParam.source.actorID, modNode.actorID, "`connect-param` has correct actor for `source`");
is(connectParam.dest.actorID, gainNode.actorID, "`connect-param` has correct actor for `dest`");
is(connectParam.param, "gain", "`connect-param` has correct parameter name for `param`");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,28 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Web Audio Editor test page</title>
</head>
<body>
<script type="text/javascript;version=1.8">
"use strict";
let ctx = new AudioContext();
let carrier = ctx.createOscillator();
let modulator = ctx.createOscillator();
let gain = ctx.createGain();
carrier.connect(gain);
gain.connect(ctx.destination);
modulator.connect(gain.gain);
modulator.start(0);
carrier.start(0);
</script>
</body>
</html>

View File

@ -28,6 +28,7 @@ const MEDIA_NODES_URL = EXAMPLE_URL + "doc_media-node-creation.html";
const BUFFER_AND_ARRAY_URL = EXAMPLE_URL + "doc_buffer-and-array.html";
const DESTROY_NODES_URL = EXAMPLE_URL + "doc_destroy-nodes.html";
const CONNECT_TOGGLE_URL = EXAMPLE_URL + "doc_connect-toggle.html";
const CONNECT_PARAM_URL = EXAMPLE_URL + "doc_connect-param.html";
// All tests are asynchronous.
waitForExplicitFinish();

View File

@ -132,7 +132,10 @@ GonkCameraHardware::postDataTimestamp(nsecs_t aTimestamp, int32_t aMsgType, cons
if (mListener.get()) {
DOM_CAMERA_LOGI("Listener registered, posting recording frame!");
mListener->postDataTimestamp(aTimestamp, aMsgType, aDataPtr);
if (!mListener->postDataTimestamp(aTimestamp, aMsgType, aDataPtr)) {
DOM_CAMERA_LOGW("Listener unable to process. Drop a recording frame.");
mCamera->releaseRecordingFrame(aDataPtr);
}
} else {
DOM_CAMERA_LOGW("No listener was set. Drop a recording frame.");
mCamera->releaseRecordingFrame(aDataPtr);

View File

@ -27,9 +27,9 @@ class GonkCameraListener: virtual public RefBase
{
public:
virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2) = 0;
virtual void postData(int32_t msgType, const sp<IMemory>& dataPtr,
virtual bool postData(int32_t msgType, const sp<IMemory>& dataPtr,
camera_frame_metadata_t *metadata) = 0;
virtual void postDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr) = 0;
virtual bool postDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr) = 0;
};
}; // namespace android

View File

@ -57,10 +57,10 @@ struct GonkCameraSourceListener : public GonkCameraListener {
GonkCameraSourceListener(const sp<GonkCameraSource> &source);
virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2);
virtual void postData(int32_t msgType, const sp<IMemory> &dataPtr,
virtual bool postData(int32_t msgType, const sp<IMemory> &dataPtr,
camera_frame_metadata_t *metadata);
virtual void postDataTimestamp(
virtual bool postDataTimestamp(
nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr);
protected:
@ -84,7 +84,7 @@ void GonkCameraSourceListener::notify(int32_t msgType, int32_t ext1, int32_t ext
CS_LOGV("notify(%d, %d, %d)", msgType, ext1, ext2);
}
void GonkCameraSourceListener::postData(int32_t msgType, const sp<IMemory> &dataPtr,
bool GonkCameraSourceListener::postData(int32_t msgType, const sp<IMemory> &dataPtr,
camera_frame_metadata_t *metadata) {
CS_LOGV("postData(%d, ptr:%p, size:%d)",
msgType, dataPtr->pointer(), dataPtr->size());
@ -92,16 +92,20 @@ void GonkCameraSourceListener::postData(int32_t msgType, const sp<IMemory> &data
sp<GonkCameraSource> source = mSource.promote();
if (source.get() != NULL) {
source->dataCallback(msgType, dataPtr);
return true;
}
return false;
}
void GonkCameraSourceListener::postDataTimestamp(
bool GonkCameraSourceListener::postDataTimestamp(
nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr) {
sp<GonkCameraSource> source = mSource.promote();
if (source.get() != NULL) {
source->dataCallbackTimestamp(timestamp/1000, msgType, dataPtr);
return true;
}
return false;
}
static int32_t getColorFormat(const char* colorFormat) {

View File

@ -240,9 +240,12 @@ MobileMessageCallback::NotifyGetSegmentInfoForTextFailed(int32_t aError)
NS_IMETHODIMP
MobileMessageCallback::NotifyGetSmscAddress(const nsAString& aSmscAddress)
{
AutoJSContext cx;
JSString* smsc = JS_NewUCStringCopyN(cx,
static_cast<const jschar *>(aSmscAddress.BeginReading()),
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(mDOMRequest->GetOwner()))) {
return NotifyError(nsIMobileMessageCallback::INTERNAL_ERROR);
}
JSContext* cx = jsapi.cx();
JSString* smsc = JS_NewUCStringCopyN(cx, aSmscAddress.BeginReading(),
aSmscAddress.Length());
if (!smsc) {

View File

@ -27,6 +27,155 @@
#include "ScopedNSSTypes.h"
using namespace mozilla::ipc;
#if ANDROID_VERSION >= 18
// After Android 4.3, it uses binder to access keystore instead of unix socket.
#include <android/log.h>
#include <binder/BinderService.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <security/keystore/include/keystore/IKeystoreService.h>
#include <security/keystore/include/keystore/keystore.h>
using namespace android;
namespace android {
// This class is used to make compiler happy.
class BpKeystoreService : public BpInterface<IKeystoreService>
{
public:
BpKeystoreService(const sp<IBinder>& impl)
: BpInterface<IKeystoreService>(impl)
{
}
virtual int32_t get(const String16& name, uint8_t** item, size_t* itemLength) {return 0;}
virtual int32_t test() {return 0;}
virtual int32_t insert(const String16& name, const uint8_t* item, size_t itemLength, int uid, int32_t flags) {return 0;}
virtual int32_t del(const String16& name, int uid) {return 0;}
virtual int32_t exist(const String16& name, int uid) {return 0;}
virtual int32_t saw(const String16& name, int uid, Vector<String16>* matches) {return 0;}
virtual int32_t reset() {return 0;}
virtual int32_t password(const String16& password) {return 0;}
virtual int32_t lock() {return 0;}
virtual int32_t unlock(const String16& password) {return 0;}
virtual int32_t zero() {return 0;}
virtual int32_t import(const String16& name, const uint8_t* data, size_t length, int uid, int32_t flags) {return 0;}
virtual int32_t sign(const String16& name, const uint8_t* data, size_t length, uint8_t** out, size_t* outLength) {return 0;}
virtual int32_t verify(const String16& name, const uint8_t* data, size_t dataLength, const uint8_t* signature, size_t signatureLength) {return 0;}
virtual int32_t get_pubkey(const String16& name, uint8_t** pubkey, size_t* pubkeyLength) {return 0;}
virtual int32_t del_key(const String16& name, int uid) {return 0;}
virtual int32_t grant(const String16& name, int32_t granteeUid) {return 0;}
virtual int32_t ungrant(const String16& name, int32_t granteeUid) {return 0;}
virtual int64_t getmtime(const String16& name) {return 0;}
virtual int32_t duplicate(const String16& srcKey, int32_t srcUid, const String16& destKey, int32_t destUid) {return 0;}
virtual int32_t clear_uid(int64_t uid) {return 0;}
#if ANDROID_VERSION == 18
virtual int32_t generate(const String16& name, int uid, int32_t flags) {return 0;}
virtual int32_t is_hardware_backed() {return 0;}
#else
virtual int32_t generate(const String16& name, int32_t uid, int32_t keyType, int32_t keySize, int32_t flags, Vector<sp<KeystoreArg> >* args) {return 0;}
virtual int32_t is_hardware_backed(const String16& keyType) {return 0;}
#endif
};
IMPLEMENT_META_INTERFACE(KeystoreService, "android.security.keystore");
// Here comes binder requests.
status_t BnKeystoreService::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch(code) {
case TEST: {
CHECK_INTERFACE(IKeystoreService, data, reply);
reply->writeNoException();
reply->writeInt32(test());
return NO_ERROR;
} break;
case GET: {
CHECK_INTERFACE(IKeystoreService, data, reply);
String16 name = data.readString16();
String8 tmp(name);
uint8_t* data = NULL;
size_t dataLength = 0;
int32_t ret = get(name, &data, &dataLength);
reply->writeNoException();
if (ret == 1) {
reply->writeInt32(dataLength);
void* buf = reply->writeInplace(dataLength);
memcpy(buf, data, dataLength);
free(data);
} else {
reply->writeInt32(-1);
}
return NO_ERROR;
} break;
default:
return NO_ERROR;
}
}
// Provide service for binder.
class KeyStoreService: public BnKeystoreService
{
public:
int32_t test() {
uid_t callingUid = IPCThreadState::self()->getCallingUid();
if (!mozilla::ipc::checkPermission(callingUid)) {
return ::PERMISSION_DENIED;
}
return ::NO_ERROR;
}
int32_t get(const String16& name, uint8_t** item, size_t* itemLength) {
uid_t callingUid = IPCThreadState::self()->getCallingUid();
if (!mozilla::ipc::checkPermission(callingUid)) {
return ::PERMISSION_DENIED;
}
String8 certName(name);
return mozilla::ipc::getCertificate(certName.string(), (const uint8_t **)item, (int *)itemLength);
}
int32_t insert(const String16& name, const uint8_t* item, size_t itemLength, int uid, int32_t flags) {return ::UNDEFINED_ACTION;}
int32_t del(const String16& name, int uid) {return ::UNDEFINED_ACTION;}
int32_t exist(const String16& name, int uid) {return ::UNDEFINED_ACTION;}
int32_t saw(const String16& name, int uid, Vector<String16>* matches) {return ::UNDEFINED_ACTION;}
int32_t reset() {return ::UNDEFINED_ACTION;}
int32_t password(const String16& password) {return ::UNDEFINED_ACTION;}
int32_t lock() {return ::UNDEFINED_ACTION;}
int32_t unlock(const String16& password) {return ::UNDEFINED_ACTION;}
int32_t zero() {return ::UNDEFINED_ACTION;}
int32_t import(const String16& name, const uint8_t* data, size_t length, int uid, int32_t flags) {return ::UNDEFINED_ACTION;}
int32_t sign(const String16& name, const uint8_t* data, size_t length, uint8_t** out, size_t* outLength) {return ::UNDEFINED_ACTION;}
int32_t verify(const String16& name, const uint8_t* data, size_t dataLength, const uint8_t* signature, size_t signatureLength) {return ::UNDEFINED_ACTION;}
int32_t get_pubkey(const String16& name, uint8_t** pubkey, size_t* pubkeyLength) {return ::UNDEFINED_ACTION;}
int32_t del_key(const String16& name, int uid) {return ::UNDEFINED_ACTION;}
int32_t grant(const String16& name, int32_t granteeUid) {return ::UNDEFINED_ACTION;}
int32_t ungrant(const String16& name, int32_t granteeUid) {return ::UNDEFINED_ACTION;}
int64_t getmtime(const String16& name) {return ::UNDEFINED_ACTION;}
int32_t duplicate(const String16& srcKey, int32_t srcUid, const String16& destKey, int32_t destUid) {return ::UNDEFINED_ACTION;}
int32_t clear_uid(int64_t uid) {return ::UNDEFINED_ACTION;}
#if ANDROID_VERSION == 18
virtual int32_t generate(const String16& name, int uid, int32_t flags) {return ::UNDEFINED_ACTION;}
virtual int32_t is_hardware_backed() {return ::UNDEFINED_ACTION;}
#else
virtual int32_t generate(const String16& name, int32_t uid, int32_t keyType, int32_t keySize, int32_t flags, Vector<sp<KeystoreArg> >* args) {return ::UNDEFINED_ACTION;}
virtual int32_t is_hardware_backed(const String16& keyType) {return ::UNDEFINED_ACTION;}
#endif
};
} // namespace android
void startKeyStoreService()
{
android::sp<android::IServiceManager> sm = android::defaultServiceManager();
android::sp<android::KeyStoreService> keyStoreService = new android::KeyStoreService();
sm->addService(String16("android.security.keystore"), keyStoreService);
}
#else
void startKeyStoreService() { return; }
#endif
namespace mozilla {
namespace ipc {
@ -45,6 +194,99 @@ static const char* KEYSTORE_ALLOWED_PREFIXES[] = {
NULL
};
// Transform base64 certification data into DER format
void
FormatCaData(const uint8_t *aCaData, int aCaDataLength,
const char *aName, const uint8_t **aFormatData,
int *aFormatDataLength)
{
int bufSize = strlen(CA_BEGIN) + strlen(CA_END) + strlen(CA_TAILER) * 2 +
strlen(aName) * 2 + aCaDataLength + aCaDataLength/CA_LINE_SIZE + 2;
char *buf = (char *)malloc(bufSize);
*aFormatDataLength = bufSize;
*aFormatData = (const uint8_t *)buf;
char *ptr = buf;
int len;
// Create DER header.
len = snprintf(ptr, bufSize, "%s%s%s", CA_BEGIN, aName, CA_TAILER);
ptr += len;
bufSize -= len;
// Split base64 data in lines.
int copySize;
while (aCaDataLength > 0) {
copySize = (aCaDataLength > CA_LINE_SIZE) ? CA_LINE_SIZE : aCaDataLength;
memcpy(ptr, aCaData, copySize);
ptr += copySize;
aCaData += copySize;
aCaDataLength -= copySize;
bufSize -= copySize;
*ptr = '\n';
ptr++;
bufSize--;
}
// Create DEA tailer.
snprintf(ptr, bufSize, "%s%s%s", CA_END, aName, CA_TAILER);
}
ResponseCode
getCertificate(const char *aCertName, const uint8_t **aCertData, int *aCertDataLength)
{
// certificate name prefix check.
if (!aCertName) {
return KEY_NOT_FOUND;
}
const char **prefix = KEYSTORE_ALLOWED_PREFIXES;
for (; *prefix; prefix++ ) {
if (!strncmp(*prefix, aCertName, strlen(*prefix))) {
break;
}
}
if (!(*prefix)) {
return KEY_NOT_FOUND;
}
// Get cert from NSS by name
ScopedCERTCertificate cert(CERT_FindCertByNickname(CERT_GetDefaultCertDB(),
aCertName));
if (!cert) {
return KEY_NOT_FOUND;
}
char *certDER = PL_Base64Encode((const char *)cert->derCert.data,
cert->derCert.len, nullptr);
if (!certDER) {
return SYSTEM_ERROR;
}
FormatCaData((const uint8_t *)certDER, strlen(certDER), "CERTIFICATE",
aCertData, aCertDataLength);
PL_strfree(certDER);
return SUCCESS;
}
bool
checkPermission(uid_t uid)
{
struct passwd *userInfo = getpwuid(uid);
for (const char **user = KEYSTORE_ALLOWED_USERS; *user; user++ ) {
if (!strcmp(*user, userInfo->pw_name)) {
return true;
}
}
return false;
}
int
KeyStoreConnector::Create()
{
@ -95,14 +337,7 @@ KeyStoreConnector::SetUp(int aFd)
return false;
}
struct passwd *userInfo = getpwuid(userCred.uid);
for (const char **user = KEYSTORE_ALLOWED_USERS; *user; user++ ) {
if (!strcmp(*user, userInfo->pw_name)) {
return true;
}
}
return false;
return ::checkPermission(userCred.uid);
}
bool
@ -124,9 +359,7 @@ KeyStoreConnector::GetSocketAddr(const sockaddr_any& aAddr,
KeyStore::KeyStore()
{
// Initial NSS
certdb = CERT_GetDefaultCertDB();
::startKeyStoreService();
Listen();
}
@ -257,47 +490,6 @@ KeyStore::ReadData(UnixSocketRawData *aMessage)
return SUCCESS;
}
// Transform base64 certification data into DER format
void
KeyStore::FormatCaData(const uint8_t *aCaData, int aCaDataLength,
const char *aName, const uint8_t **aFormatData,
int &aFormatDataLength)
{
int bufSize = strlen(CA_BEGIN) + strlen(CA_END) + strlen(CA_TAILER) * 2 +
strlen(aName) * 2 + aCaDataLength + aCaDataLength/CA_LINE_SIZE + 2;
char *buf = (char *)malloc(bufSize);
aFormatDataLength = bufSize;
*aFormatData = (const uint8_t *)buf;
char *ptr = buf;
int len;
// Create DER header.
len = snprintf(ptr, bufSize, "%s%s%s", CA_BEGIN, aName, CA_TAILER);
ptr += len;
bufSize -= len;
// Split base64 data in lines.
int copySize;
while (aCaDataLength > 0) {
copySize = (aCaDataLength > CA_LINE_SIZE) ? CA_LINE_SIZE : aCaDataLength;
memcpy(ptr, aCaData, copySize);
ptr += copySize;
aCaData += copySize;
aCaDataLength -= copySize;
bufSize -= copySize;
*ptr = '\n';
ptr++;
bufSize--;
}
// Create DEA tailer.
snprintf(ptr, bufSize, "%s%s%s", CA_END, aName, CA_TAILER);
}
// Status response
void
KeyStore::SendResponse(ResponseCode aResponse)
@ -349,39 +541,10 @@ KeyStore::ReceiveSocketData(nsAutoPtr<UnixSocketRawData>& aMessage)
int certDataLength;
const char *certName = (const char *)mHandlerInfo.param[0].data;
// certificate name prefix check.
if (!certName) {
result = KEY_NOT_FOUND;
result = getCertificate(certName, &certData, &certDataLength);
if (result != SUCCESS) {
break;
}
const char **prefix = KEYSTORE_ALLOWED_PREFIXES;
for (; *prefix; prefix++ ) {
if (!strncmp(*prefix, certName, strlen(*prefix))) {
break;
}
}
if (!(*prefix)) {
result = KEY_NOT_FOUND;
break;
}
// Get cert from NSS by name
ScopedCERTCertificate cert(CERT_FindCertByNickname(certdb, certName));
if (!cert) {
result = KEY_NOT_FOUND;
break;
}
char *certDER = PL_Base64Encode((const char *)cert->derCert.data,
cert->derCert.len, nullptr);
if (!certDER) {
result = SYSTEM_ERROR;
break;
}
FormatCaData((const uint8_t *)certDER, strlen(certDER), "CERTIFICATE",
&certData, certDataLength);
PL_strfree(certDER);
SendResponse(SUCCESS);
SendData(certData, certDataLength);

View File

@ -33,6 +33,15 @@ enum ResponseCode {
NO_RESPONSE
};
void FormatCaData(const uint8_t *aCaData, int aCaDataLength,
const char *aName, const uint8_t **aFormatData,
int *aFormatDataLength);
ResponseCode getCertificate(const char *aCertName, const uint8_t **aCertData,
int *aCertDataLength);
bool checkPermission(uid_t uid);
static const int MAX_PARAM = 2;
static const int KEY_SIZE = ((NAME_MAX - 15) / 2);
static const int VALUE_SIZE = 32768;
@ -110,9 +119,6 @@ private:
void ResetHandlerInfo();
void Listen();
void FormatCaData(const uint8_t *aCaData, int aCaDataLength, const char *aName,
const uint8_t **aFormatData, int &aFormatDataLength);
bool CheckSize(UnixSocketRawData *aMessage, size_t aExpectSize);
ResponseCode ReadCommand(UnixSocketRawData *aMessage);
ResponseCode ReadLength(UnixSocketRawData *aMessage);
@ -121,8 +127,6 @@ private:
void SendData(const uint8_t *data, int length);
bool mShutdown;
CERTCertDBHandle *certdb;
};
} // namespace ipc

View File

@ -290,7 +290,9 @@
</intent-filter>
</receiver>
<receiver android:name="org.mozilla.gecko.ReferrerReceiver" android:exported="true">
<!-- Catch install referrer so we can do post-install work. -->
<receiver android:name="org.mozilla.gecko.distribution.ReferrerReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER" />
</intent-filter>

View File

@ -200,6 +200,13 @@ public final class GeckoProfile {
getGuestDir(context).mkdir();
GeckoProfile profile = getGuestProfile(context);
profile.lock();
/*
* Now do the things that createProfileDirectory normally does --
* right now that's kicking off DB init.
*/
profile.enqueueInitialization();
return profile;
} catch (Exception ex) {
Log.e(LOGTAG, "Error creating guest profile", ex);

View File

@ -1,62 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import java.net.URLDecoder;
import java.util.HashMap;
public class ReferrerReceiver
extends BroadcastReceiver
{
private static final String LOGTAG = "GeckoReferrerReceiver";
public static final String ACTION_INSTALL_REFERRER = "com.android.vending.INSTALL_REFERRER";
public static final String UTM_SOURCE = "mozilla";
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_INSTALL_REFERRER.equals(intent.getAction())) {
String referrer = intent.getStringExtra("referrer");
if (referrer == null)
return;
HashMap<String, String> values = new HashMap<String, String>();
try {
String referrers[] = referrer.split("&");
for (String referrerValue : referrers) {
String keyValue[] = referrerValue.split("=");
values.put(URLDecoder.decode(keyValue[0]), URLDecoder.decode(keyValue[1]));
}
} catch (Exception e) {
}
String source = values.get("utm_source");
String campaign = values.get("utm_campaign");
if (source != null && UTM_SOURCE.equals(source) && campaign != null) {
try {
JSONObject data = new JSONObject();
data.put("id", "playstore");
data.put("version", campaign);
// Try to make sure the prefs are written as a group
GeckoEvent event = GeckoEvent.createBroadcastEvent("Campaign:Set", data.toString());
GeckoAppShell.sendEventToGecko(event);
} catch (JSONException e) {
Log.e(LOGTAG, "Error setting distribution", e);
}
}
}
}
}

View File

@ -5,12 +5,19 @@
package org.mozilla.gecko.distribution;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
@ -19,34 +26,95 @@ import java.util.Map;
import java.util.Queue;
import java.util.Scanner;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.net.ssl.SSLException;
import org.apache.http.protocol.HTTP;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.mozglue.RobocopTarget;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.SystemClock;
import android.util.Log;
/**
* Handles distribution file loading and fetching,
* and the corresponding hand-offs to Gecko.
*/
public final class Distribution {
@RobocopTarget
public class Distribution {
private static final String LOGTAG = "GeckoDistribution";
private static final int STATE_UNKNOWN = 0;
private static final int STATE_NONE = 1;
private static final int STATE_SET = 2;
private static final String FETCH_PROTOCOL = "https";
private static final String FETCH_HOSTNAME = "distro-download.cdn.mozilla.net";
private static final String FETCH_PATH = "/android/1/";
private static final String FETCH_EXTENSION = ".jar";
private static final String EXPECTED_CONTENT_TYPE = "application/java-archive";
private static final String DISTRIBUTION_PATH = "distribution/";
/**
* Telemetry constants.
*/
private static final String HISTOGRAM_REFERRER_INVALID = "FENNEC_DISTRIBUTION_REFERRER_INVALID";
private static final String HISTOGRAM_DOWNLOAD_TIME_MS = "FENNEC_DISTRIBUTION_DOWNLOAD_TIME_MS";
private static final String HISTOGRAM_CODE_CATEGORY = "FENNEC_DISTRIBUTION_CODE_CATEGORY";
/**
* Success/failure codes. Don't exceed the maximum listed in Histograms.json.
*/
private static final int CODE_CATEGORY_STATUS_OUT_OF_RANGE = 0;
// HTTP status 'codes' run from 1 to 5.
private static final int CODE_CATEGORY_OFFLINE = 6;
private static final int CODE_CATEGORY_FETCH_EXCEPTION = 7;
// It's a post-fetch exception if we were able to download, but not
// able to extract.
private static final int CODE_CATEGORY_POST_FETCH_EXCEPTION = 8;
private static final int CODE_CATEGORY_POST_FETCH_SECURITY_EXCEPTION = 9;
// It's a malformed distribution if we could extract, but couldn't
// process the contents.
private static final int CODE_CATEGORY_MALFORMED_DISTRIBUTION = 10;
// Specific fetch errors.
private static final int CODE_CATEGORY_FETCH_SOCKET_ERROR = 11;
private static final int CODE_CATEGORY_FETCH_SSL_ERROR = 12;
private static final int CODE_CATEGORY_FETCH_NON_SUCCESS_RESPONSE = 13;
private static final int CODE_CATEGORY_FETCH_INVALID_CONTENT_TYPE = 14;
// Corresponds to the high value in Histograms.json.
private static final long MAX_DOWNLOAD_TIME_MSEC = 40000; // 40 seconds.
/**
* Used as a drop-off point for ReferrerReceiver. Checked when we process
* first-run distribution.
*
* This is `protected` so that test code can clear it between runs.
*/
@RobocopTarget
protected static volatile ReferrerDescriptor referrer;
private static Distribution instance;
private final Context context;
@ -70,6 +138,7 @@ public final class Distribution {
return instance;
}
@RobocopTarget
public static class DistributionDescriptor {
public final boolean valid;
public final String id;
@ -140,6 +209,7 @@ public final class Distribution {
* Use <code>Context.getPackageResourcePath</code> to find an implicit
* package path. Reuses the existing Distribution if one exists.
*/
@RobocopTarget
public static void init(final Context context) {
Distribution.init(Distribution.getInstance(context));
}
@ -166,6 +236,17 @@ public final class Distribution {
this(context, context.getPackageResourcePath(), null);
}
/**
* This method is called by ReferrerReceiver when we receive a post-install
* notification from Google Play.
*
* @param ref a parsed referrer value from the store-supplied intent.
*/
public static void onReceivedReferrer(ReferrerDescriptor ref) {
// Track the referrer object for distribution handling.
referrer = ref;
}
/**
* Helper to grab a file in the distribution directory.
*
@ -214,9 +295,11 @@ public final class Distribution {
} catch (IOException e) {
Log.e(LOGTAG, "Error getting distribution descriptor file.", e);
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
return null;
} catch (JSONException e) {
Log.e(LOGTAG, "Error parsing preferences.json", e);
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
return null;
}
}
@ -232,11 +315,13 @@ public final class Distribution {
return new JSONArray(getFileContents(bookmarks));
} catch (IOException e) {
Log.e(LOGTAG, "Error getting bookmarks", e);
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
return null;
} catch (JSONException e) {
Log.e(LOGTAG, "Error parsing bookmarks.json", e);
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
return null;
}
return null;
}
/**
@ -245,9 +330,12 @@ public final class Distribution {
* Postcondition: if this returns true, distributionDir will have been
* set and populated.
*
* This method is *only* protected for use from testDistribution.
*
* @return true if we've set a distribution.
*/
private boolean doInit() {
@RobocopTarget
protected boolean doInit() {
ThreadUtils.assertNotOnUiThread();
// Bail if we've already tried to initialize the distribution, and
@ -274,8 +362,9 @@ public final class Distribution {
return true;
}
// We try the APK, then the system directory.
// We try the install intent, then the APK, then the system directory.
final boolean distributionSet =
checkIntentDistribution() ||
checkAPKDistribution() ||
checkSystemDistribution();
@ -286,6 +375,153 @@ public final class Distribution {
return distributionSet;
}
/**
* If applicable, download and select the distribution specified in
* the referrer intent.
*
* @return true if a referrer-supplied distribution was selected.
*/
private boolean checkIntentDistribution() {
if (referrer == null) {
return false;
}
URI uri = getReferredDistribution(referrer);
if (uri == null) {
return false;
}
long start = SystemClock.uptimeMillis();
Log.v(LOGTAG, "Downloading referred distribution: " + uri);
try {
HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
connection.setRequestProperty(HTTP.USER_AGENT, GeckoAppShell.getGeckoInterface().getDefaultUAString());
connection.setRequestProperty("Accept", EXPECTED_CONTENT_TYPE);
try {
final JarInputStream distro;
try {
distro = fetchDistribution(uri, connection);
} catch (Exception e) {
Log.e(LOGTAG, "Error fetching distribution from network.", e);
recordFetchTelemetry(e);
return false;
}
long end = SystemClock.uptimeMillis();
final long duration = end - start;
Log.d(LOGTAG, "Distro fetch took " + duration + "ms; result? " + (distro != null));
Telemetry.HistogramAdd(HISTOGRAM_DOWNLOAD_TIME_MS, clamp(MAX_DOWNLOAD_TIME_MSEC, duration));
if (distro == null) {
// Nothing to do.
return false;
}
// Try to copy distribution files from the fetched stream.
try {
Log.d(LOGTAG, "Copying files from fetched zip.");
if (copyFilesFromStream(distro)) {
// We always copy to the data dir, and we only copy files from
// a 'distribution' subdirectory. Track our dist dir now that
// we know it.
this.distributionDir = new File(getDataDir(), DISTRIBUTION_PATH);
return true;
}
} catch (SecurityException e) {
Log.e(LOGTAG, "Security exception copying files. Corrupt or malicious?", e);
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_POST_FETCH_SECURITY_EXCEPTION);
} catch (Exception e) {
Log.e(LOGTAG, "Error copying files from distribution.", e);
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_POST_FETCH_EXCEPTION);
} finally {
distro.close();
}
} finally {
connection.disconnect();
}
} catch (IOException e) {
Log.e(LOGTAG, "Error copying distribution files from network.", e);
recordFetchTelemetry(e);
}
return false;
}
private static final int clamp(long v, long c) {
return (int) Math.min(c, v);
}
/**
* Fetch the provided URI, returning a {@link JarInputStream} if the response body
* is appropriate.
*
* Protected to allow for mocking.
*
* @return the entity body as a stream, or null on failure.
*/
@SuppressWarnings("static-method")
@RobocopTarget
protected JarInputStream fetchDistribution(URI uri, HttpURLConnection connection) throws IOException {
final int status = connection.getResponseCode();
Log.d(LOGTAG, "Distribution fetch: " + status);
// We record HTTP statuses as 2xx, 3xx, 4xx, 5xx => 2, 3, 4, 5.
final int value;
if (status > 599 || status < 100) {
Log.wtf(LOGTAG, "Unexpected HTTP status code: " + status);
value = CODE_CATEGORY_STATUS_OUT_OF_RANGE;
} else {
value = status / 100;
}
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, value);
if (status != 200) {
Log.w(LOGTAG, "Got status " + status + " fetching distribution.");
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_NON_SUCCESS_RESPONSE);
return null;
}
final String contentType = connection.getContentType();
if (contentType == null || !contentType.startsWith(EXPECTED_CONTENT_TYPE)) {
Log.w(LOGTAG, "Malformed response: invalid Content-Type.");
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_INVALID_CONTENT_TYPE);
return null;
}
return new JarInputStream(new BufferedInputStream(connection.getInputStream()), true);
}
private static void recordFetchTelemetry(final Exception exception) {
if (exception == null) {
// Should never happen.
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_EXCEPTION);
return;
}
if (exception instanceof UnknownHostException) {
// Unknown host => we're offline.
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_OFFLINE);
return;
}
if (exception instanceof SSLException) {
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_SSL_ERROR);
return;
}
if (exception instanceof ProtocolException ||
exception instanceof SocketException) {
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_SOCKET_ERROR);
return;
}
Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_EXCEPTION);
}
/**
* Execute tasks that wanted to run when we were done loading
* the distribution. These tasks are expected to call {@link #exists()}
@ -308,7 +544,7 @@ public final class Distribution {
// We always copy to the data dir, and we only copy files from
// a 'distribution' subdirectory. Track our dist dir now that
// we know it.
this.distributionDir = new File(getDataDir(), "distribution/");
this.distributionDir = new File(getDataDir(), DISTRIBUTION_PATH);
return true;
}
} catch (IOException e) {
@ -330,6 +566,41 @@ public final class Distribution {
return false;
}
/**
* Unpack distribution files from a downloaded jar stream.
*
* The caller is responsible for closing the provided stream.
*/
private boolean copyFilesFromStream(JarInputStream jar) throws FileNotFoundException, IOException {
final byte[] buffer = new byte[1024];
boolean distributionSet = false;
JarEntry entry;
while ((entry = jar.getNextJarEntry()) != null) {
final String name = entry.getName();
if (entry.isDirectory()) {
// We'll let getDataFile deal with creating the directory hierarchy.
// Yes, we can do better, but it can wait.
continue;
}
if (!name.startsWith(DISTRIBUTION_PATH)) {
continue;
}
File outFile = getDataFile(name);
if (outFile == null) {
continue;
}
distributionSet = true;
writeStream(jar, outFile, entry.getTime(), buffer);
}
return distributionSet;
}
/**
* Copies the /distribution folder out of the APK and into the app's data directory.
* Returns true if distribution files were found and copied.
@ -352,7 +623,7 @@ public final class Distribution {
continue;
}
if (!name.startsWith("distribution/")) {
if (!name.startsWith(DISTRIBUTION_PATH)) {
continue;
}
@ -413,6 +684,29 @@ public final class Distribution {
return outFile;
}
private URI getReferredDistribution(ReferrerDescriptor descriptor) {
final String content = descriptor.content;
if (content == null) {
return null;
}
// We restrict here to avoid injection attacks. After all,
// we're downloading a distribution payload based on intent input.
if (!content.matches("^[a-zA-Z0-9]+$")) {
Log.e(LOGTAG, "Invalid referrer content: " + content);
Telemetry.HistogramAdd(HISTOGRAM_REFERRER_INVALID, 1);
return null;
}
try {
return new URI(FETCH_PROTOCOL, FETCH_HOSTNAME, FETCH_PATH + content + FETCH_EXTENSION, null);
} catch (URISyntaxException e) {
// This should never occur.
Log.wtf(LOGTAG, "Invalid URI with content " + content + "!");
return null;
}
}
/**
* After calling this method, either <code>distributionDir</code>
* will be set, or there is no distribution in use.
@ -432,7 +726,7 @@ public final class Distribution {
// the APK, or it exists in /system/.
// Look in each location in turn.
// (This could be optimized by caching the path in shared prefs.)
File copied = new File(getDataDir(), "distribution/");
File copied = new File(getDataDir(), DISTRIBUTION_PATH);
if (copied.exists()) {
return this.distributionDir = copied;
}

View File

@ -0,0 +1,55 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.distribution;
import org.mozilla.gecko.mozglue.RobocopTarget;
import android.net.Uri;
/**
* Encapsulates access to values encoded in the "referrer" extra of an install intent.
*
* This object is immutable.
*
* Example input:
*
* "utm_source=campsource&utm_medium=campmed&utm_term=term%2Bhere&utm_content=content&utm_campaign=name"
*/
@RobocopTarget
public class ReferrerDescriptor {
public final String source;
public final String medium;
public final String term;
public final String content;
public final String campaign;
public ReferrerDescriptor(final String referrer) {
if (referrer == null) {
source = null;
medium = null;
term = null;
content = null;
campaign = null;
return;
}
final Uri u = new Uri.Builder()
.scheme("http")
.authority("local")
.path("/")
.encodedQuery(referrer).build();
source = u.getQueryParameter("utm_source");
medium = u.getQueryParameter("utm_medium");
term = u.getQueryParameter("utm_term");
content = u.getQueryParameter("utm_content");
campaign = u.getQueryParameter("utm_campaign");
}
@Override
public String toString() {
return "{s: " + source + ", m: " + medium + ", t: " + term + ", c: " + content + ", c: " + campaign + "}";
}
}

View File

@ -0,0 +1,76 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.distribution;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Log;
public class ReferrerReceiver extends BroadcastReceiver {
private static final String LOGTAG = "GeckoReferrerReceiver";
private static final String ACTION_INSTALL_REFERRER = "com.android.vending.INSTALL_REFERRER";
/**
* If the install intent has this source, we'll track the campaign ID.
*/
private static final String MOZILLA_UTM_SOURCE = "mozilla";
/**
* If the install intent has this campaign, we'll load the specified distribution.
*/
private static final String DISTRIBUTION_UTM_CAMPAIGN = "distribution";
@Override
public void onReceive(Context context, Intent intent) {
Log.v(LOGTAG, "Received intent " + intent);
if (!ACTION_INSTALL_REFERRER.equals(intent.getAction())) {
// This should never happen.
return;
}
ReferrerDescriptor referrer = new ReferrerDescriptor(intent.getStringExtra("referrer"));
// Track the referrer object for distribution handling.
if (TextUtils.equals(referrer.campaign, DISTRIBUTION_UTM_CAMPAIGN)) {
Distribution.onReceivedReferrer(referrer);
} else {
Log.d(LOGTAG, "Not downloading distribution: non-matching campaign.");
}
// If this is a Mozilla campaign, pass the campaign along to Gecko.
if (TextUtils.equals(referrer.source, MOZILLA_UTM_SOURCE)) {
propagateMozillaCampaign(referrer);
}
}
private void propagateMozillaCampaign(ReferrerDescriptor referrer) {
if (referrer.campaign == null) {
return;
}
try {
final JSONObject data = new JSONObject();
data.put("id", "playstore");
data.put("version", referrer.campaign);
String payload = data.toString();
// Try to make sure the prefs are written as a group.
final GeckoEvent event = GeckoEvent.createBroadcastEvent("Campaign:Set", payload);
GeckoAppShell.sendEventToGecko(event);
} catch (JSONException e) {
Log.e(LOGTAG, "Error propagating campaign identifier.", e);
}
}
}

View File

@ -156,6 +156,8 @@ gbjar.sources += [
'db/TabsProvider.java',
'db/TopSitesCursorWrapper.java',
'distribution/Distribution.java',
'distribution/ReferrerDescriptor.java',
'distribution/ReferrerReceiver.java',
'DoorHangerPopup.java',
'DynamicToolbar.java',
'EditBookmarkDialog.java',
@ -354,7 +356,6 @@ gbjar.sources += [
'prompts/PromptService.java',
'prompts/TabInput.java',
'ReaderModeUtils.java',
'ReferrerReceiver.java',
'Restarter.java',
'ScrollAnimator.java',
'ServiceNotificationClient.java',

View File

@ -2,19 +2,29 @@ package org.mozilla.gecko.tests;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.jar.JarInputStream;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.Actions;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.distribution.ReferrerDescriptor;
import org.mozilla.gecko.mozglue.RobocopTarget;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
/**
* Tests distribution customization.
@ -28,6 +38,38 @@ import android.content.SharedPreferences;
* engine.xml
*/
public class testDistribution extends ContentProviderTest {
private static final String CLASS_REFERRER_RECEIVER = "org.mozilla.gecko.distribution.ReferrerReceiver";
private static final String ACTION_INSTALL_REFERRER = "com.android.vending.INSTALL_REFERRER";
private static final int WAIT_TIMEOUT_MSEC = 10000;
public static final String LOGTAG = "GeckoTestDistribution";
public static class TestableDistribution extends Distribution {
@Override
protected JarInputStream fetchDistribution(URI uri,
HttpURLConnection connection) throws IOException {
Log.i(LOGTAG, "Not downloading: this is a test.");
return null;
}
public TestableDistribution(Context context) {
super(context);
}
public void go() {
doInit();
}
@RobocopTarget
public static void clearReferrerDescriptorForTesting() {
referrer = null;
}
@RobocopTarget
public static ReferrerDescriptor getReferrerDescriptorForTesting() {
return referrer;
}
}
private static final String MOCK_PACKAGE = "mock-package.zip";
private static final int PREF_REQUEST_ID = 0x7357;
@ -65,7 +107,7 @@ public class testDistribution extends ContentProviderTest {
mAsserter.dumpLog("Background task completed. Proceeding.");
}
public void testDistribution() {
public void testDistribution() throws Exception {
mActivity = getActivity();
String mockPackagePath = getMockPackagePath();
@ -87,6 +129,90 @@ public class testDistribution extends ContentProviderTest {
setTestLocale("es-MX");
initDistribution(mockPackagePath);
checkLocalizedPreferences("es-MX");
// Test the (stubbed) download interaction.
setTestLocale("en-US");
clearDistributionPref();
doTestValidReferrerIntent();
clearDistributionPref();
doTestInvalidReferrerIntent();
}
public void doTestValidReferrerIntent() throws Exception {
// Send the faux-download intent.
// Equivalent to
// am broadcast -a com.android.vending.INSTALL_REFERRER \
// -n org.mozilla.fennec/org.mozilla.gecko.distribution.ReferrerReceiver \
// --es "referrer" "utm_source=mozilla&utm_medium=testmedium&utm_term=testterm&utm_content=testcontent&utm_campaign=distribution"
final String ref = "utm_source=mozilla&utm_medium=testmedium&utm_term=testterm&utm_content=testcontent&utm_campaign=distribution";
final Intent intent = new Intent(ACTION_INSTALL_REFERRER);
intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, CLASS_REFERRER_RECEIVER);
intent.putExtra("referrer", ref);
mActivity.sendBroadcast(intent);
// Wait for the intent to be processed.
final TestableDistribution distribution = new TestableDistribution(mActivity);
final Object wait = new Object();
distribution.addOnDistributionReadyCallback(new Runnable() {
@Override
public void run() {
mAsserter.ok(!distribution.exists(), "Not processed.", "No download because we're offline.");
ReferrerDescriptor referrerValue = TestableDistribution.getReferrerDescriptorForTesting();
mAsserter.dumpLog("Referrer was " + referrerValue);
mAsserter.is(referrerValue.content, "testcontent", "Referrer content");
mAsserter.is(referrerValue.medium, "testmedium", "Referrer medium");
mAsserter.is(referrerValue.campaign, "distribution", "Referrer campaign");
synchronized (wait) {
wait.notifyAll();
}
}
});
distribution.go();
synchronized (wait) {
wait.wait(WAIT_TIMEOUT_MSEC);
}
}
/**
* Test processing if the campaign isn't "distribution". The intent shouldn't
* result in a download, and won't be saved as the temporary referrer,
* even if we *do* include it in a Campaign:Set message.
*/
public void doTestInvalidReferrerIntent() throws Exception {
// Send the faux-download intent.
// Equivalent to
// am broadcast -a com.android.vending.INSTALL_REFERRER \
// -n org.mozilla.fennec/org.mozilla.gecko.distribution.ReferrerReceiver \
// --es "referrer" "utm_source=mozilla&utm_medium=testmedium&utm_term=testterm&utm_content=testcontent&utm_campaign=testname"
final String ref = "utm_source=mozilla&utm_medium=testmedium&utm_term=testterm&utm_content=testcontent&utm_campaign=testname";
final Intent intent = new Intent(ACTION_INSTALL_REFERRER);
intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, CLASS_REFERRER_RECEIVER);
intent.putExtra("referrer", ref);
mActivity.sendBroadcast(intent);
// Wait for the intent to be processed.
final TestableDistribution distribution = new TestableDistribution(mActivity);
final Object wait = new Object();
distribution.addOnDistributionReadyCallback(new Runnable() {
@Override
public void run() {
mAsserter.ok(!distribution.exists(), "Not processed.", "No download because campaign was wrong.");
ReferrerDescriptor referrerValue = TestableDistribution.getReferrerDescriptorForTesting();
mAsserter.is(referrerValue, null, "No referrer.");
synchronized (wait) {
wait.notifyAll();
}
}
});
distribution.go();
synchronized (wait) {
wait.wait(WAIT_TIMEOUT_MSEC);
}
}
// Initialize the distribution from the mock package.
@ -288,12 +414,16 @@ public class testDistribution extends ContentProviderTest {
return mockPackagePath;
}
// Clears the distribution pref to return distribution state to STATE_UNKNOWN
/**
* Clears the distribution pref to return distribution state to STATE_UNKNOWN,
* and wipes the in-memory referrer pigeonhole.
*/
private void clearDistributionPref() {
mAsserter.dumpLog("Clearing distribution pref.");
SharedPreferences settings = mActivity.getSharedPreferences("GeckoApp", Activity.MODE_PRIVATE);
String keyName = mActivity.getPackageName() + ".distribution_state";
settings.edit().remove(keyName).commit();
TestableDistribution.clearReferrerDescriptorForTesting();
}
@Override

View File

@ -11,6 +11,7 @@ jar.sources += [
'src/harness/BrowserInstrumentationTestRunner.java',
'src/harness/BrowserTestListener.java',
'src/tests/BrowserTestCase.java',
'src/tests/TestDistribution.java',
'src/tests/TestGeckoSharedPrefs.java',
'src/tests/TestJarReader.java',
'src/tests/TestRawResource.java',

View File

@ -0,0 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.browser.tests;
import org.mozilla.gecko.distribution.ReferrerDescriptor;
public class TestDistribution extends BrowserTestCase {
private static final String TEST_REFERRER_STRING = "utm_source=campsource&utm_medium=campmed&utm_term=term%2Bhere&utm_content=content&utm_campaign=name";
private static final String TEST_MALFORMED_REFERRER_STRING = "utm_source=campsource&utm_medium=campmed&utm_term=term%2";
public void testReferrerParsing() {
ReferrerDescriptor good = new ReferrerDescriptor(TEST_REFERRER_STRING);
assertEquals("campsource", good.source);
assertEquals("campmed", good.medium);
assertEquals("term+here", good.term);
assertEquals("content", good.content);
assertEquals("name", good.campaign);
// Uri.Builder is permissive.
ReferrerDescriptor bad = new ReferrerDescriptor(TEST_MALFORMED_REFERRER_STRING);
assertEquals("campsource", bad.source);
assertEquals("campmed", bad.medium);
assertFalse("term+here".equals(bad.term));
assertNull(bad.content);
assertNull(bad.campaign);
ReferrerDescriptor ugly = new ReferrerDescriptor(null);
assertNull(ugly.source);
assertNull(ugly.medium);
assertNull(ugly.term);
assertNull(ugly.content);
assertNull(ugly.campaign);
}
}

View File

@ -2959,6 +2959,28 @@
"extended_statistics_ok": true,
"description": "PLACES: Time to calculate the md5 hash for a backup"
},
"FENNEC_DISTRIBUTION_REFERRER_INVALID": {
"expires_in_version": "never",
"kind": "flag",
"description": "Whether the referrer intent specified an invalid distribution name",
"cpp_guard": "ANDROID"
},
"FENNEC_DISTRIBUTION_CODE_CATEGORY": {
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 20,
"description": "First digit of HTTP result code, or error category, during distribution download",
"cpp_guard": "ANDROID"
},
"FENNEC_DISTRIBUTION_DOWNLOAD_TIME_MS": {
"expires_in_version": "never",
"kind": "exponential",
"low": 100,
"high": "40000",
"n_buckets": 30,
"description": "Time taken to download a specified distribution file (msec)",
"cpp_guard": "ANDROID"
},
"FENNEC_FAVICONS_COUNT": {
"expires_in_version": "never",
"kind": "exponential",

View File

@ -39,10 +39,9 @@ const UDPSocket = CC("@mozilla.org/network/udp-socket;1",
"nsIUDPSocket",
"init");
// TODO Bug 1027456: May need to reserve these with IANA
const SCAN_PORT = 50624;
const UPDATE_PORT = 50625;
const ADDRESS = "224.0.0.200";
const ADDRESS = "224.0.0.115";
const REPLY_TIMEOUT = 5000;
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
@ -158,6 +157,8 @@ function Discovery() {
this._onRemoteUpdate = this._onRemoteUpdate.bind(this);
this._purgeMissingDevices = this._purgeMissingDevices.bind(this);
Services.obs.addObserver(this, "network-active-changed", false);
this._getSystemInfo();
}
@ -295,6 +296,35 @@ Discovery.prototype = {
this._transports.update = null;
},
observe: function(subject, topic, data) {
if (topic !== "network-active-changed") {
return;
}
let activeNetwork = subject;
if (!activeNetwork) {
log("No active network");
return;
}
activeNetwork = activeNetwork.QueryInterface(Ci.nsINetworkInterface);
log("Active network changed to: " + activeNetwork.type);
// UDP sockets go down when the device goes offline, so we'll restart them
// when the active network goes back to WiFi.
if (activeNetwork.type === Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
this._restartListening();
}
},
_restartListening: function() {
if (this._transports.scan) {
this._stopListeningForScan();
this._startListeningForScan();
}
if (this._transports.update) {
this._stopListeningForUpdate();
this._startListeningForUpdate();
}
},
/**
* When sending message, we can use either transport, so just pick the first
* one currently alive.

View File

@ -363,7 +363,7 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
let { caller, args, window, name } = functionCall.details;
let source = caller;
let dest = args[0];
let isAudioParam = dest instanceof window.AudioParam;
let isAudioParam = dest ? getConstructorName(dest) === "AudioParam" : false;
// audionode.connect(param)
if (name === "connect" && isAudioParam) {
@ -433,8 +433,9 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
},
"connect-param": {
type: "connectParam",
source: Arg(0, "audionode"),
param: Arg(1, "string")
source: Option(0, "audionode"),
dest: Option(0, "audionode"),
param: Option(0, "string")
},
"change-param": {
type: "changeParam",
@ -461,12 +462,30 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
// Ensure AudioNode is wrapped.
node = new XPCNativeWrapper(node);
this._instrumentParams(node);
let actor = new AudioNodeActor(this.conn, node);
this.manage(actor);
this._nativeToActorID.set(node.id, actor.actorID);
return actor;
},
/**
* Takes an XrayWrapper node, and attaches the node's `nativeID`
* to the AudioParams as `_parentID`, as well as the the type of param
* as a string on `_paramName`.
*/
_instrumentParams: function (node) {
let type = getConstructorName(node);
Object.keys(NODE_PROPERTIES[type])
.filter(isAudioParam.bind(null, node))
.forEach(paramName => {
let param = node[paramName];
param._parentID = node.id;
param._paramName = paramName;
});
},
/**
* Takes an AudioNode and returns the stored actor for it.
* In some cases, we won't have an actor stored (for example,
@ -505,10 +524,15 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
/**
* Called when an audio node is connected to an audio param.
* Implement in bug 986705
*/
_onConnectParam: function (source, dest) {
// TODO bug 986705
_onConnectParam: function (source, param) {
let sourceActor = this._getActorByNativeID(source.id);
let destActor = this._getActorByNativeID(param._parentID);
emit(this, "connect-param", {
source: sourceActor,
dest: destActor,
param: param._paramName
});
},
/**

View File

@ -187,7 +187,7 @@ FxAccountsService.prototype = {
error => {
log.error("get assertion failed: " + JSON.stringify(error));
// Cancellation is passed through an error channel; here we reroute.
if (error.details && (error.details.error == "DIALOG_CLOSED_BY_USER")) {
if (error.error && (error.error.details == "DIALOG_CLOSED_BY_USER")) {
return this.doCancel(aRPId);
}
this.doError(aRPId, error);

View File

@ -612,7 +612,6 @@ NO_PKG_FILES += \
res/samples \
res/throbber \
shlibsign* \
ssltunnel* \
certutil* \
pk12util* \
BadCertServer* \
@ -629,6 +628,13 @@ NO_PKG_FILES += \
*.dSYM \
$(NULL)
# If a manifest has not been supplied, the following
# files should be excluded from the package too
ifndef MOZ_PKG_MANIFEST
NO_PKG_FILES += \
ssltunnel*
endif
# browser/locales/Makefile uses this makefile for its variable defs, but
# doesn't want the libs:: rule.
ifndef PACKAGER_NO_LIBS