Related
https://android.clients.google.com/updates/signed-kila-ota-114235-prereq.TC4-RC19.zip
Not sure when this is from. I was tooling around trying to find a way to flash myself off of the RC29 test build that I stupidly put on. Stumbled across this. Tried to flash it over my RC29-Test, and it failed with the following error:
Code:
E:Failure at line 1:
assert file_contains(SYSTEM:build.prop", "test-keys"0 == "true" || file_contains
("SYSTEM:build.prop", "ro.build.fingerprint=tmobile/kila/dream/trout:1.0/
TC4-RC19/109652:user/ota-rel-keys,release-keys") == "true"
Installation aborted.
My build contains the following line:
Code:
ota-rel-keys,for-testing,release-keys
Let me know what y'all think.
I think its something good to add to my collection Thx
Darkrift said:
I think its something good to add to my collection Thx
Click to expand...
Click to collapse
I think it will be good for doing file diffs when we get to that point.
Thanks, It is good to have this...
Has anyone thought of trying to apply these updates to the RC1 from the emulator?
yeah, some guy named neoobs thought of it, not sure if he tried it yet though
Darkrift said:
yeah, some guy named neoobs thought of it, not sure if he tried it yet though
Click to expand...
Click to collapse
If I knew how to apply them I would have tried... but how do you hold the home button and the power button on the emulator? LOL
can you mis-match specific files?
I wonder if you can mis-match specific files in the updates.
I.e I wonder if you copied an RC19 file and built a custom version - but also copied the signatures into the appropriate files - since the file is still signed and it would still verify, I think it would still work to grab old versions and mix in with new versions.
The usefullness of making a frankenbuild is probably "not much" - but perhaps if there is a vulnerable file in one version it could be injected into a later version.
RyeBrye said:
I wonder if you can mis-match specific files in the updates.
I.e I wonder if you copied an RC19 file and built a custom version - but also copied the signatures into the appropriate files - since the file is still signed and it would still verify, I think it would still work to grab old versions and mix in with new versions.
The usefullness of making a frankenbuild is probably "not much" - but perhaps if there is a vulnerable file in one version it could be injected into a later version.
Click to expand...
Click to collapse
That won't work because there are 2 files with signatures... one of which is signed in the other. So if you change the signature file it will not validate. Pretty smart if you ask me.
neoobs said:
That won't work because there are 2 files with signatures... one of which is signed in the other. So if you change the signature file it will not validate. Pretty smart if you ask me.
Click to expand...
Click to collapse
I don't see where the one signs the other - the only META-INF file that is signed is the update-script that I can see in either file.
I do see in the code where it EXPLICITLY does NOT verify the .SF files however:
Code:
for (i = 0; i < mzZipEntryCount(pArchive); ++i) {
const ZipEntry *entry = mzGetZipEntryAt(pArchive, i);
UnterminatedString fn = mzGetZipEntryFileName(entry);
int len = mzGetZipEntryUncompLen(entry);
// Don't validate: directories, the manifest, *.RSA, and *.SF.
if (entry == mfEntry) {
LOGV("Skipping manifest %.*s\n", fn.len, fn.str);
} else if (fn.len > 0 && fn.str[fn.len-1] == '/' && len == 0) {
LOGV("Skipping directory %.*s\n", fn.len, fn.str);
} else if (!strncasecmp(fn.str, "META-INF/", 9) && (
!strncasecmp(fn.str + fn.len - 4, ".RSA", 4) ||
!strncasecmp(fn.str + fn.len - 3, ".SF", 3))) {
LOGV("Skipping signature %.*s\n", fn.len, fn.str);
} else {
unverified[i] = true;
totalBytes += len;
}
}
So I think it is entirely possible to swap in other files that are signed with the same key from an older version. If they change keys, I don't think it will work.
Another thought...
It doesn't make sense to me that the public key would be distributed along with the update - it looks like the phone is using the public key to verify the signed hashes - which is fine - they can see that the hashes were signed by the private-key pair of that RSA file...
But the .RSA is explicitly not verified, and I don't see in the verify.c file where it checks against any kind of keystore on the device...
I wonder if it is just checking that the files are signed, but not necessarily who they are signed by? Meaning - it's making sure the update hasn't been corrupted or tampered with - but not who made the update in the first place?
RyeBrye said:
I don't see where the one signs the other - the only META-INF file that is signed is the update-script that I can see in either file.
I do see in the code where it EXPLICITLY does NOT verify the .SF files however:
Code:
for (i = 0; i < mzZipEntryCount(pArchive); ++i) {
const ZipEntry *entry = mzGetZipEntryAt(pArchive, i);
UnterminatedString fn = mzGetZipEntryFileName(entry);
int len = mzGetZipEntryUncompLen(entry);
// Don't validate: directories, the manifest, *.RSA, and *.SF.
if (entry == mfEntry) {
LOGV("Skipping manifest %.*s\n", fn.len, fn.str);
} else if (fn.len > 0 && fn.str[fn.len-1] == '/' && len == 0) {
LOGV("Skipping directory %.*s\n", fn.len, fn.str);
} else if (!strncasecmp(fn.str, "META-INF/", 9) && (
!strncasecmp(fn.str + fn.len - 4, ".RSA", 4) ||
!strncasecmp(fn.str + fn.len - 3, ".SF", 3))) {
LOGV("Skipping signature %.*s\n", fn.len, fn.str);
} else {
unverified[i] = true;
totalBytes += len;
}
}
So I think it is entirely possible to swap in other files that are signed with the same key from an older version. If they change keys, I don't think it will work.
Click to expand...
Click to collapse
in one of the SF's (Signature Files HAHA never caught that till now) It has the SHA1-digest of the other. I believe it is Cert.sf, at the top it signs manifest.sf
This is why you can not alter the SF's at all because they both have all the files signed with one of them signing the other. Making it impossible to get around. And with SHA1 it just makes it hard to even think about it. Might as well do as others have said and find exploits.
RyeBrye said:
It doesn't make sense to me that the public key would be distributed along with the update - it looks like the phone is using the public key to verify the signed hashes - which is fine - they can see that the hashes were signed by the private-key pair of that RSA file...
But the .RSA is explicitly not verified, and I don't see in the verify.c file where it checks against any kind of keystore on the device...
I wonder if it is just checking that the files are signed, but not necessarily who they are signed by? Meaning - it's making sure the update hasn't been corrupted or tampered with - but not who made the update in the first place?
Click to expand...
Click to collapse
I like that idea... anyone know how to sign the files with your own rsa?
RyeBrye said:
It doesn't make sense to me that the public key would be distributed along with the update - it looks like the phone is using the public key to verify the signed hashes - which is fine - they can see that the hashes were signed by the private-key pair of that RSA file...
But the .RSA is explicitly not verified, and I don't see in the verify.c file where it checks against any kind of keystore on the device...
I wonder if it is just checking that the files are signed, but not necessarily who they are signed by? Meaning - it's making sure the update hasn't been corrupted or tampered with - but not who made the update in the first place?
Click to expand...
Click to collapse
Aperantly not many people know about private-public key cryptography.
Please, if you have the time, wiki private-public key cryptography or download the podcast from TWiT (This Week in Tech) Security now on that subject (episode ~15 or so, it was early on).
A simple explination:
Two keys are generated at random (or user input through an algorithm). A private key and a public key. The private key is used to encrypt files an ONLY the public key can decrypt it. IT IS NOT REVERSABLE.
The public key can be "publicly known", hence the name. It actually has to either be with the files or on the device that needs to unencrypt said files.
Now here's the awsome part a brute force attack on SHA1 would take millions of years to crack ON ONE KEY. (Much more detail to that)
Another added mesure of security is hash checking. Run a file through an algorythm and you get an unique ID for that file. You change as much as ONE BIT on that file and the output of the algorythm is COMPLETLY different.
Not imposible to crack, imposible to stay alive long enough to see it done.
Thats the Thing with PKey and Private Key and it Impossible to crack unless the Boot loader is not checking for Signing Authority.
Please Understand Signing a File and Encrypting the File is Different thing we have G1 Update.zip as Normal Zip file but The moment we change any byte in Zip Store the Checking gets failed. if G1 Boot loader is really not checking Singer and only rely on Signed Object whosmever may have signed then we got the Solution but i dont either HTC or Google is that foolish to do that.
We must have to look at G1 Source code for Details.
I think what cmonex and Olipro does is better. Get Source code of Boot loader Recompile it with disabled Signing Funda. Load the Boot Loader into Memory and that Jump to that Location. here Root access to Device is Necesory once we are Succefull to run Patched Bootloader from Memory Flashing Customised Image may not be a Problem
snoslicer8 said:
https://android.clients.google.com/updates/signed-kila-ota-114235-prereq.TC4-RC19.zip
Not sure when this is from. I was tooling around trying to find a way to flash myself off of the RC29 test build that I stupidly put on. Stumbled across this. Tried to flash it over my RC29-Test, and it failed with the following error:
Code:
E:Failure at line 1:
assert file_contains(SYSTEM:build.prop", "test-keys"0 == "true" || file_contains
("SYSTEM:build.prop", "ro.build.fingerprint=tmobile/kila/dream/trout:1.0/
TC4-RC19/109652:user/ota-rel-keys,release-keys") == "true"
Installation aborted.
My build contains the following line:
Code:
ota-rel-keys,for-testing,release-keys
Let me know what y'all think.
Click to expand...
Click to collapse
can anyone post this file somewhere i can get it? it's not there anymore. My PC no longer see's my device, i'm wondering if flashing back to RC19 will work.
canOpoop said:
can anyone post this file somewhere i can get it? it's not there anymore. My PC no longer see's my device, i'm wondering if flashing back to RC19 will work.
Click to expand...
Click to collapse
First, you can't flash the RC19 if you have RC29.
Second, what do you mean your PC doesn't see your device? If your talking about the SD card through the phone, at the top of your phone when you have connected, you will have a notification at the top of your phone.
Either drag the notification bar or press menu on your home screen and click on Notifications. Select the USB selectionan it will prompt you, select "Mount"
quedijo said:
First, you can't flash the RC19 if you have RC29.
Second, what do you mean your PC doesn't see your device? If your talking about the SD card through the phone, at the top of your phone when you have connected, you will have a notification at the top of your phone.
Either drag the notification bar or press menu on your home screen and click on Notifications. Select the USB selectionan it will prompt you, select "Mount"
Click to expand...
Click to collapse
First: check this out. http://www.blogsdna.com/1256/how-to-quickly-update-t1-mobile-g1-phone-firmware-manually-rc29.htm
i'm thinking if i get the file (RC19) i can flashback.
Second: It had worked before (before the OTA update). I have tried 3 different PC's 2 different USB cables, REFlash of RC29, and a hard reset (to Factory). i do not ever get a notification to mount in Notification bar. Windows throws out the error "One of the USB Devices attached to this computer has malfuctioned, and windows does not recognize it." And yes i have tried different USB ports.
i'm open to any more ideas...
canOpoop said:
First: check this out. http://www.blogsdna.com/1256/how-to-quickly-update-t1-mobile-g1-phone-firmware-manually-rc29.htm
i'm thinking if i get the file (RC19) i can flashback.
Second: It had worked before (before the OTA update). I have tried 3 different PC's 2 different USB cables, REFlash of RC29, and a hard reset (to Factory). i do not ever get a notification to mount in Notification bar. Windows throws out the error "One of the USB Devices attached to this computer has malfuctioned, and windows does not recognize it." And yes i have tried different USB ports.
i'm open to any more ideas...
Click to expand...
Click to collapse
Use anycut and create a shortcut to the activity "SD Card" and see what it says when you open it.
neoobs said:
Use anycut and create a shortcut to the activity "SD Card" and see what it says when you open it.
Click to expand...
Click to collapse
Tmobile is replacing device. I should get a new on Tuesday the 11th. they are out of stock.
Hi good people of N7 land,
I wanted to ask if it's possible to make keys get detected at boot-time so that a certain function is carried out. If so how?
For example:
The N7 is booting up. I press the Vol + key, and due to a script executed by the init.delta.rc, /system is mounted to a IMG file in the sdcard. A dual-boot mod.
I also ask if this is the best way to do this, and please for the love of God, don't point me to MultiROM. I want to work on my own mod.
I expect helpful answers. And if you can't answer my question, don't even bother posting here.
Although this is a question, this is more dev-related than anything I've encountered in the Q/A section. So I thought of posting it here.
Please move this if this is not the right place, and accept my apologies.
sgt. meow
I don't think this is possible using just some script - init has no way to get input events from keys, so you would have to either edit the init binary or use exec init command.
You could use it to run another binary, which would check if volwhatever is pressed and either did nothing or did the dual-boot stuff.
It depends on when you want to check for the keypress, because if it is after the /system partition is mounted, you could use busybox and run bash script, otherwise it would have to be real binary, because when init is started, you have pretty much "just" kernel running and no userspace.
Or you could pack busybox to boot.img, but if you can code, the binary is pretty easy to make.
@up
Exactly the kind of answer I was hoping to find. Sucks to realize that I still don't know a lot of coding. But I'm willing to learn if you could point me to the right direction.
sgt. meow said:
Hi good people of N7 land,
I wanted to ask if it's possible to make keys get detected at boot-time so that a certain function is carried out. If so how?
For example:
The N7 is booting up. I press the Vol + key, and due to a script executed by the init.delta.rc, /system is mounted to a IMG file in the sdcard. A dual-boot mod.
I also ask if this is the best way to do this, and please for the love of God, don't point me to MultiROM. I want to work on my own mod.
I expect helpful answers. And if you can't answer my question, don't even bother posting here.
Although this is a question, this is more dev-related than anything I've encountered in the Q/A section. So I thought of posting it here.
Please move this if this is not the right place, and accept my apologies.
sgt. meow
Click to expand...
Click to collapse
The first idea that comes to my mind would be to code a simple no-UI app which would use a Service executed by a BroadCastReceiver set to BOOT_COMPLETED.
Then, when triggered (=at boot-time), the service would wait for the user to press a defined key (i.e : volume up). When the service receives the key press, it would mount /system or whatever you wanna do, then stop running (so that other volume up presses don't trigger this event again).
The code could be something like :
Your AndroidManifest.xml :
Code:
< ?xml version="1.0" encoding="utf-8"?>
< manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sgtmeow.boot.service"
android:versionCode="1"
android:versionName="1.0">
< uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
< application android:icon="@drawable/icon" android:label="@string/app_name">
< service android:name=".BootService">
< intent-filter>
< action
android:name = "com.sgtmeow.boot.service.BootService">
< /action>
< /intent-filter>
< /service>
< receiver android:name=".BootReceiver">
< intent-filter>
< action
android:name ="android.intent.action.BOOT_COMPLETED">
< /action>
< /intent-filter>
< /receiver>
< /application>
< uses-sdk android:minSdkVersion="8"
android:targetSdkVersion="17"android:targetSdkVersion="17" />
< /manifest>
BootReceiver.java :
Code:
package com.sgtmeow.boot.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class BootDemoReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent bootintent) {
Intent mServiceIntent = new Intent();
mServiceIntent.setAction("com.sgtmeow.boot.service.BootService");
context.startService(mServiceIntent);
}
}
BootService.java
Code:
package com.sgtmeow.boot.service;
import java.io.DataOutputStream;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.view.KeyEvent;
import com.sgtmeow.boot.service.BootReceiver;
public class BootService extends Service {
Runtime mRuntime;
Process mProcess = null;
DataOutputStream mOutput = null;
@Override
public IBinder onBind(final Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
super.onKeyDown(keyCode, event);
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
mountSystem();
Log.w("SGT.MEOW", "Volume Up has been pressed");
return true;
}
return true;
}
public void onStartCommand(final Intent intent, final int startId) {
super.onStartCommand(intent, startId, startId);
mountSystem();
}
private void mountSystem() {
Runtime.getRuntime();
// The service is now started, ask for root and mount /system using
// busybox (or not)
try {
mProcess = mRuntime.exec("su");
mOutput = new DataOutputStream(mProcess.getOutputStream());
mOutput.writeBytes("busybox mount -o remount,rw /system");
mOutput.flush();
stopService(new Intent(BootService.this, BootReceiver.class));
} catch (Exception e) {
Log.e("SGT.MEOW", "An Error Has Occured", e);
e.printStackTrace();
}
}
}
EDIT : unfortunately after some research I found out that Services cannot act as an onKeyListener, you'll have to call an activity to actually listen to the keypress so maybe just a dialog or something, but you won't be able to do it completely UI-less this way, my bad.
This is not something i would attempt to if you dont know what your doing due to the chance of bricks. but your best bet would be to luanch a binary from early init in the ram disk
aaronpoweruser said:
This is not something i would attempt to if you dont know what your doing due to the chance of bricks. but your best bet would be to luanch a binary from early init in the ram disk
Click to expand...
Click to collapse
I know how to launch the binary during init. The binary is the problem I am running into.
Thanks though.
You might want to take a look at how custom recoveries on the Xperia T handles the initial recovery vs. boot selection. On that device, there is no recovery partition, so a single kernel image must support both recovery and normal operation. The bootloader doesn't support an "enter recovery" poweron keycombo, so on the T there are some tricks so that at boot, it:
Sets the LED to purple
Waits 3 seconds or so
If the VolUp key is pressed during those 3 seconds, boot to recovery
Otherwise, boot normally
I believe the same approach is also used on the Xperia Z.
Maybe you could have a custom recovery that allows this? TWRP and CWM Touch both have source code available that you can check out so you can make your own recovery, which could allow booting of separate ROMS. Recoveries generally boot faster than full ROMs.
That's not what I was aiming to achieve. If I can manage to map the keys and find a way to detect them at boot-time, the rest is easy as pie. . Thanks though.
Entropy512 said:
You might want to take a look at how custom recoveries on the Xperia T handles the initial recovery vs. boot selection. On that device, there is no recovery partition, so a single kernel image must support both recovery and normal operation. The bootloader doesn't support an "enter recovery" poweron keycombo, so on the T there are some tricks so that at boot, it:
Sets the LED to purple
Waits 3 seconds or so
If the VolUp key is pressed during those 3 seconds, boot to recovery
Otherwise, boot normally
I believe the same approach is also used on the Xperia Z.
Click to expand...
Click to collapse
sgt. meow said:
That's not what I was aiming to achieve. If I can manage to map the keys and find a way to detect them at boot-time, the rest is easy as pie. . Thanks though.
Click to expand...
Click to collapse
None of the phones made by Sony have a recovery partition so they have to use a work around to detect keys on boot up. This is what all Xperia devices do to get into recovery: on boot check to see if the desired key is being pressed and if it is unzip the recovery, if it isn't being pressed unzip the normal ramdisk. (I'll post the ramdisk here for you to see how everything works yourself) So with the Xperia Play CyanogenMod kernel that I have how it works is it has two .cpio files. One for the ramdisk and one for the recovery. It uses a .sh file in the sbin to check if keys are being pressed by using /dev/input/event# which in most cases is event1 for volume down (on Nexus 7 the is event0, event1, and event2. My guess is 0 = power button and one and two are vol + and vol -). It then has a link to the init.sh in the root of the ramdisk called init so that when the kernel starts and calls init it starts the script. So what you could do is the same but instead of having two .cpio files you could rename the init file to something else then place the script and link in your ramdisk so that when the key is pressed it calls both what you want to do and the rename init executable. (Not sure how well this would work this is just an idea, if not depending on what you want to do you can keep the compressed ramdisk idea...)
Here is a ramdisk of what I've been talking about that is pulled from my kernel (extracted from an already compiled kernel to make it easy read.):
Link
It's made for the Xperia Play so it also tries to start the LED and vibrator to tell you when to click the Vol - key so how you would tell people to click it on the Nexus 7 I'm not sure...probably just have to have people will just have to spam the key. If you have any questions feel free to ask or if this didn't help I'm sorry just an idea.
Also what it seems like you want to do with the dual boot mod being with the SDCard sounds a lot like what a guy named CosmicDan did on the Xperia Play. You had to change the updater-script in your flashable zip but it worked. Here is the thread: Turbo Kernel and the source code to his kernel: GitHub
I know what he did with his kernel. I am working on something similar to the HD2 dual boot method. Haven't had any success yet.
Sent from my Nexus 7 using xda app-developers app
sgt. meow said:
I know what he did with his kernel. I am working on something similar to the HD2 dual boot method. Haven't had any success yet.
Sent from my Nexus 7 using xda app-developers app
Click to expand...
Click to collapse
hi! were you able to solve this?
thanks!
In Android 4.4 KitKat, Google/AOSP made the following change to the API specification, much to the detriment of app developers and users:
"The WRITE_EXTERNAL_STORAGE permission must only grant write access to the primary external storage on a device. Apps must not be allowed to write to secondary external storage devices, except in their package-specific directories as allowed by synthesized permissions."
Click to expand...
Click to collapse
Source: http://source.android.com/devices/tech/storage/index.html
You can read my rather unhappy write-up about it here: https://plus.google.com/108338299717386673901/posts/gjnmuaDM8sn
This only applies to dual-storage devices, i.e., devices with a user-writable internal flash storage AND a removable SD card. But if your device has both, as of Android 4.4, apps will no longer be able to write arbitrarily to the "secondary" storage (the SD card).
There is however still an API exposed that will allow you to write to secondary storage, notably the media content provider. This is far from an ideal solution, and I imagine that someday it will not be possible.
I've written a tiny bit of code to let your applications continue to work with files on the SD card using the media content provider. This code should only be used to write to the secondary storage on Android 4.4+ devices if all else fails. I would strongly recommend that you NEVER rely on this code. This code DOES NOT use root access.
The class:
Code:
/*
* Copyright (C) 2014 NextApp, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package nextapp.mediafile;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.net.Uri;
import android.provider.MediaStore;
/**
* Wrapper for manipulating files via the Android Media Content Provider. As of Android 4.4 KitKat, applications can no longer write
* to the "secondary storage" of a device. Write operations using the java.io.File API will thus fail. This class restores access to
* those write operations by way of the Media Content Provider.
*
* Note that this class relies on the internal operational characteristics of the media content provider API, and as such is not
* guaranteed to be future-proof. Then again, we did all think the java.io.File API was going to be future-proof for media card
* access, so all bets are off.
*
* If you're forced to use this class, it's because Google/AOSP made a very poor API decision in Android 4.4 KitKat.
* Read more at https://plus.google.com/+TodLiebeck/posts/gjnmuaDM8sn
*
* Your application must declare the permission "android.permission.WRITE_EXTERNAL_STORAGE".
*/
public class MediaFile {
private final File file;
private final ContentResolver contentResolver;
private final Uri filesUri;
private final Uri imagesUri;
public MediaFile(ContentResolver contentResolver, File file) {
this.file = file;
this.contentResolver = contentResolver;
filesUri = MediaStore.Files.getContentUri("external");
imagesUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
}
/**
* Deletes the file. Returns true if the file has been successfully deleted or otherwise does not exist. This operation is not
* recursive.
*/
public boolean delete()
throws IOException {
if (!file.exists()) {
return true;
}
boolean directory = file.isDirectory();
if (directory) {
// Verify directory does not contain any files/directories within it.
String[] files = file.list();
if (files != null && files.length > 0) {
return false;
}
}
String where = MediaStore.MediaColumns.DATA + "=?";
String[] selectionArgs = new String[] { file.getAbsolutePath() };
// Delete the entry from the media database. This will actually delete media files (images, audio, and video).
contentResolver.delete(filesUri, where, selectionArgs);
if (file.exists()) {
// If the file is not a media file, create a new entry suggesting that this location is an image, even
// though it is not.
ContentValues values = new ContentValues();
values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
contentResolver.insert(imagesUri, values);
// Delete the created entry, such that content provider will delete the file.
contentResolver.delete(filesUri, where, selectionArgs);
}
return !file.exists();
}
public File getFile() {
return file;
}
/**
* Creates a new directory. Returns true if the directory was successfully created or exists.
*/
public boolean mkdir()
throws IOException {
if (file.exists()) {
return file.isDirectory();
}
ContentValues values;
Uri uri;
// Create a media database entry for the directory. This step will not actually cause the directory to be created.
values = new ContentValues();
values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
contentResolver.insert(filesUri, values);
// Create an entry for a temporary image file within the created directory.
// This step actually causes the creation of the directory.
values = new ContentValues();
values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath() + "/temp.jpg");
uri = contentResolver.insert(imagesUri, values);
// Delete the temporary entry.
contentResolver.delete(uri, null, null);
return file.exists();
}
/**
* Returns an OutputStream to write to the file. The file will be truncated immediately.
*/
public OutputStream write()
throws IOException {
if (file.exists() && file.isDirectory()) {
throw new IOException("File exists and is a directory.");
}
// Delete any existing entry from the media database.
// This may also delete the file (for media types), but that is irrelevant as it will be truncated momentarily in any case.
String where = MediaStore.MediaColumns.DATA + "=?";
String[] selectionArgs = new String[] { file.getAbsolutePath() };
contentResolver.delete(filesUri, where, selectionArgs);
ContentValues values = new ContentValues();
values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
Uri uri = contentResolver.insert(filesUri, values);
if (uri == null) {
// Should not occur.
throw new IOException("Internal error.");
}
return contentResolver.openOutputStream(uri);
}
}
Source download (or cut/paste the above): http://android.nextapp.com/content/mediawrite/v1/MediaFile.java
Eclipse project with test app: http://android.nextapp.com/content/mediawrite/v1/MediaWrite.zip
APK of test app: http://android.nextapp.com/content/mediawrite/v1/MediaWrite.apk
The test project is currently configured to target the path /storage/extSdCard/MediaWriteTest (this is correct for a Note3, at least on 4.3...make sure you don't have anything there). Edit MainActivity.java in the Eclipse project to change it.
And again, let me stress that the above code might not work in the future should Google dislike it. I wouldn't recommend that the average app developer make use of this code, but if you're writing a file manager (or something else that competes with any of my other apps) , it might be useful to you. And actually at the time of writing, this functionality is NOT in FX File Explorer or WebSharing.
Follow up:
My understanding is that creating and deleting files with this method does work on Android 4.4, but making new directories does not.
Creating directories does work on Android 4.3 (e.g. Google Play Edition) devices that have crippled secondary storage, so that mkdir() method will be useful for all five people who own GPE devices who are still using 4.3.
Too good to be true!
Wow! This is perfect..
This issue has been resolved in another thread using a much simpler method...
Sent from my GT-I9505 using Tapatalk
AMoosa said:
This issue has been resolved in another thread using a much simpler method...
Sent from my GT-I9505 using Tapatalk
Click to expand...
Click to collapse
If that solution is for non-root app developers, can you provide a link?
Bear in mind this solution is for app developers writing apps for users who may be on stock firmware without root access. I'm familiar with the proper solution for rooted users, where media_rw is added for the WRITE_EXTERNAL_STORAGE permission in /etc/permissions/platform.xml.
tliebeck said:
If that solution is for non-root app developers, can you provide a link?
Bear in mind this solution is for app developers writing apps for users who may be on stock firmware without root access. I'm familiar with the proper solution for rooted users, where media_rw is added for the WRITE_EXTERNAL_STORAGE permission in /etc/permissions/platform.xml.
Click to expand...
Click to collapse
Root solution... Possible to grant WRITE_MEDIA_STORAGE via pm grant in shell (non root via adb shell?)? Samsung File Manager uses it and it can access to extsdcard. Better than modifying system file.
But I agree that we need Fixer app for that (to fix things in etc/permissions/platform.xml)
I did this method when we first got kitkat a while back http://forum.xda-developers.com/showthread.php?t=2524277
But rooted devices. Good job bro
tliebeck said:
If that solution is for non-root app developers, can you provide a link?
Bear in mind this solution is for app developers writing apps for users who may be on stock firmware without root access. I'm familiar with the proper solution for rooted users, where media_rw is added for the WRITE_EXTERNAL_STORAGE permission in /etc/permissions/platform.xml.
Click to expand...
Click to collapse
Hi. Yes it's the one you have cited. Isn't everyone on xda rooted these days? Lol.
Sent from my GT-I9505 using Tapatalk
pyler said:
Root solution... Possible to grant WRITE_MEDIA_STORAGE via pm grant in shell (non root via adb shell?)? Samsung File Manager uses it and it can access to extsdcard. Better than modifying system file.
But I agree that we need Fixer app for that (to fix things in etc/permissions/platform.xml)
Click to expand...
Click to collapse
The Samsung file manager declares it in its manifest, and I believe it's available to any system app that does so. I'm not sure on the method by which these permissions are granted (i.e., if it's based on having a certain signature, or if just being in /system is enough). I didn't think there was a means to grant it to any non-system app without using root though.
I did try just try declaring that in the manifest, and it pm grant returns the following (for shell and root):
Operation not allowed: java.lang.SecurityException: Permission android.permission.WRITE_MEDIA_STORAGE is not a changeable permission type
Click to expand...
Click to collapse
Thanks for the suggestion though, was worth a try.
AMoosa said:
Hi. Yes it's the one you have cited. Isn't everyone on xda rooted these days? Lol.
Sent from my GT-I9505 using Tapatalk
Click to expand...
Click to collapse
Yeah, this certainly isn't going to be *personally* useful to anyone here...just posted it for app devs catering to users on non-rooted stock stuff.
tliebeck said:
Yeah, this certainly isn't going to be *personally* useful to anyone here...just posted it for app devs catering to users on non-rooted stock stuff.
Click to expand...
Click to collapse
I have a question, if I install the kitkat 4.4.2 update for sprint can I then install apk or is there a diffrent way also im not rooted. Cause this is the only thing stopping me from installing the update.
dadda said:
I have a question, if I install the kitkat 4.4.2 update for sprint can I then install apk or is there a diffrent way also im not rooted. Cause this is the only thing stopping me from installing the update.
Click to expand...
Click to collapse
Would not recommend it, this is still not confirmed to work, and only affects applications that implement it. Only real solution is still rooting.
Thanks, I'm still debating on rooting my phone cause of my evo 3D camera stop working.
Apparently the latest update of ES File Explorer should give write access to the external SD card without root, here's the changelog:
Code:
V3.1.1
-Samsung external card write issue on Android 4.4 Kitkat
-Multi-thread download & copy setting
-Video/audio thumbnails
-Download Facebook video when playing(use FB link in homepage)
It would be interesting to know the hack the app used to write and delete files there!
virtualdj said:
Apparently the latest update of ES File Explorer should give write access to the external SD card without root, here's the changelog:
Code:
V3.1.1
-Samsung external card write issue on Android 4.4 Kitkat
-Multi-thread download & copy setting
-Video/audio thumbnails
-Download Facebook video when playing(use FB link in homepage)
It would be interesting to know the hack the app used to write and delete files there!
Click to expand...
Click to collapse
The above code should work for creating and deleting files.
My question would be whether or not they are able to create new folders? If they can, they've found a new workaround.
Base directory in this app is storage/extSdCard/WriteMediaTest
My base directory is storage/sdcard1/WriteMediaTest
Can you change this?
Wysłane z mojego GT-P3100 przy użyciu Tapatalka
tliebeck said:
The above code should work for creating and deleting files.
My question would be whether or not they are able to create new folders? If they can, they've found a new workaround.
Click to expand...
Click to collapse
ES Explorer can create directory on ext sdcard without root.
Anyone get updated with how to do that?
jimmod said:
ES Explorer can create directory on ext sdcard without root.
Anyone get updated with how to do that?
Click to expand...
Click to collapse
I just got 4.4.2 on m G Tab 8.3. I can't do a File.mkdir(), but I could do it from the command line. I could also do a cp. Maybe they are doing a Runtime.exec()?
Update. Just tried that. Did not work, permission denied. Weird that that shell user can, but an app user can't.
dburckh said:
I just got 4.4.2 on m G Tab 8.3. I can't do a File.mkdir(), but I could do it from the command line. I could also do a cp. Maybe they are doing a Runtime.exec()?
Update. Just tried that. Did not work, permission denied. Weird that that shell user can, but an app user can't.
Click to expand...
Click to collapse
Yes, adb shell (shell user) is able to create folder.
But if using terminal emulator app it permission denied.
Most likely shell user have write permission. But to switch to shell user from app will need root access.
Sorry for not updating this thread promptly with this information, but there is now a 4.4 folder creation workaround, courtesy of the developer of X-plore File Manager. Works on Samsung devices, but apparently doesn't on Sony or HTC stuff. Not sure why this is the case.
I haven't had time to update the original standalone sample app/project, but here's the complete workaround class straight out of FX beta. This code is designed to be built against Android 2.1 (API level 7), hence the use of reflection. It's using the media API to generate album art for an MP3 track and place it in the folder you want created. Tiny MP3 file is attached, place that in /res/raw.
Code:
package nextapp.maui.io;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import nextapp.maui.AndroidEnvironment;
import nextapp.maui.Maui;
import nextapp.maui.R;
import nextapp.maui.storage.ContentUriUtil;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.util.Log;
/**
* Wrapper for manipulating files via the Android Media Content Provider. As of Android 4.4 KitKat, applications can no longer write
* to the "secondary storage" of a device. Write operations using the java.io.File API will thus fail. This class restores access to
* those write operations by way of the Media Content Provider.
*
* Note that this class relies on the internal operational characteristics of the media content provider API, and as such is not
* guaranteed to be future-proof. Then again, we did all think the java.io.File API was going to be future-proof for media card
* access, so all bets are off.
*
* If you're forced to use this class, it's because Google/AOSP made a very poor API decision in Android 4.4 KitKat.
* Read more at https://plus.google.com/+TodLiebeck/posts/gjnmuaDM8sn
*
* Your application must declare the permission "android.permission.WRITE_EXTERNAL_STORAGE".
*/
public class MediaFile {
private static final String NO_MEDIA = ".nomedia";
private static final String ALBUM_ART_URI = "content://media/external/audio/albumart";
private static final String[] ALBUM_PROJECTION = { BaseColumns._ID, MediaStore.Audio.AlbumColumns.ALBUM_ID, "media_type" };
private static File getExternalFilesDir(Context context) {
if (AndroidEnvironment.SDK < AndroidEnvironment.FROYO) {
return null;
}
try {
Method method = Context.class.getMethod("getExternalFilesDir", String.class);
return (File) method.invoke(context, (String) null);
} catch (SecurityException ex) {
Log.d(Maui.LOG_TAG, "Unexpected reflection error.", ex);
return null;
} catch (NoSuchMethodException ex) {
Log.d(Maui.LOG_TAG, "Unexpected reflection error.", ex);
return null;
} catch (IllegalArgumentException ex) {
Log.d(Maui.LOG_TAG, "Unexpected reflection error.", ex);
return null;
} catch (IllegalAccessException ex) {
Log.d(Maui.LOG_TAG, "Unexpected reflection error.", ex);
return null;
} catch (InvocationTargetException ex) {
Log.d(Maui.LOG_TAG, "Unexpected reflection error.", ex);
return null;
}
}
public static boolean SUPPORTED = ContentUriUtil.FILES_URI != null;
private final File file;
private final Context context;
private final ContentResolver contentResolver;
public MediaFile(Context context, File file) {
this.file = file;
this.context = context;
contentResolver = context.getContentResolver();
}
/**
* Deletes the file. Returns true if the file has been successfully deleted or otherwise does not exist. This operation is not
* recursive.
*/
public boolean delete()
throws IOException {
if (!SUPPORTED) {
throw new IOException("MediaFile API not supported by device.");
}
if (!file.exists()) {
return true;
}
boolean directory = file.isDirectory();
if (directory) {
// Verify directory does not contain any files/directories within it.
String[] files = file.list();
if (files != null && files.length > 0) {
return false;
}
}
String where = MediaStore.MediaColumns.DATA + "=?";
String[] selectionArgs = new String[] { file.getAbsolutePath() };
// Delete the entry from the media database. This will actually delete media files (images, audio, and video).
contentResolver.delete(ContentUriUtil.FILES_URI, where, selectionArgs);
if (file.exists()) {
// If the file is not a media file, create a new entry suggesting that this location is an image, even
// though it is not.
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath());
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
// Delete the created entry, such that content provider will delete the file.
contentResolver.delete(ContentUriUtil.FILES_URI, where, selectionArgs);
}
return !file.exists();
}
public File getFile() {
return file;
}
private int getTemporaryAlbumId() {
final File temporaryTrack;
try {
temporaryTrack = installTemporaryTrack();
} catch (IOException ex) {
return 0;
}
final String[] selectionArgs = { temporaryTrack.getAbsolutePath() };
Cursor cursor = contentResolver.query(ContentUriUtil.FILES_URI, ALBUM_PROJECTION, MediaStore.MediaColumns.DATA + "=?",
selectionArgs, null);
if (cursor == null || !cursor.moveToFirst()) {
if (cursor != null) {
cursor.close();
cursor = null;
}
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, temporaryTrack.getAbsolutePath());
values.put(MediaStore.MediaColumns.TITLE, "{MediaWrite Workaround}");
values.put(MediaStore.MediaColumns.SIZE, temporaryTrack.length());
values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mpeg");
values.put(MediaStore.Audio.AudioColumns.IS_MUSIC, true);
contentResolver.insert(ContentUriUtil.FILES_URI, values);
}
cursor = contentResolver.query(ContentUriUtil.FILES_URI, ALBUM_PROJECTION, MediaStore.MediaColumns.DATA + "=?",
selectionArgs, null);
if (cursor == null) {
return 0;
}
if (!cursor.moveToFirst()) {
cursor.close();
return 0;
}
int id = cursor.getInt(0);
int albumId = cursor.getInt(1);
int mediaType = cursor.getInt(2);
cursor.close();
ContentValues values = new ContentValues();
boolean updateRequired = false;
if (albumId == 0) {
values.put(MediaStore.Audio.AlbumColumns.ALBUM_ID, 13371337);
updateRequired = true;
}
if (mediaType != 2) {
values.put("media_type", 2);
updateRequired = true;
}
if (updateRequired) {
contentResolver.update(ContentUriUtil.FILES_URI, values, BaseColumns._ID + "=" + id, null);
}
cursor = contentResolver.query(ContentUriUtil.FILES_URI, ALBUM_PROJECTION, MediaStore.MediaColumns.DATA + "=?",
selectionArgs, null);
if (cursor == null) {
return 0;
}
try {
if (!cursor.moveToFirst()) {
return 0;
}
return cursor.getInt(1);
} finally {
cursor.close();
}
}
private File installTemporaryTrack()
throws IOException {
File externalFilesDir = getExternalFilesDir(context);
if (externalFilesDir == null) {
return null;
}
File temporaryTrack = new File(externalFilesDir, "temptrack.mp3");
if (!temporaryTrack.exists()) {
InputStream in = null;
OutputStream out = null;
try {
in = context.getResources().openRawResource(R.raw.temptrack);
out = new FileOutputStream(temporaryTrack);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ex) {
return null;
}
}
if (out != null) {
try {
out.close();
} catch (IOException ex) {
return null;
}
}
}
}
return temporaryTrack;
}
/**
* Creates a new directory. Returns true if the directory was successfully created or exists.
*/
public boolean mkdir()
throws IOException {
if (file.exists()) {
return file.isDirectory();
}
File tmpFile = new File(file, ".MediaWriteTemp");
int albumId = getTemporaryAlbumId();
if (albumId == 0) {
throw new IOException("Fail");
}
Uri albumUri = Uri.parse(ALBUM_ART_URI + '/' + albumId);
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, tmpFile.getAbsolutePath());
if (contentResolver.update(albumUri, values, null, null) == 0) {
values.put(MediaStore.Audio.AlbumColumns.ALBUM_ID, albumId);
contentResolver.insert(Uri.parse(ALBUM_ART_URI), values);
}
try {
ParcelFileDescriptor fd = contentResolver.openFileDescriptor(albumUri, "r");
fd.close();
} finally {
MediaFile tmpMediaFile = new MediaFile(context, tmpFile);
tmpMediaFile.delete();
}
return file.exists();
}
/**
* Returns an OutputStream to write to the file. The file will be truncated immediately.
*/
public OutputStream write(long size)
throws IOException {
if (!SUPPORTED) {
throw new IOException("MediaFile API not supported by device.");
}
if (NO_MEDIA.equals(file.getName().trim())) {
throw new IOException("Unable to create .nomedia file via media content provider API.");
}
if (file.exists() && file.isDirectory()) {
throw new IOException("File exists and is a directory.");
}
// Delete any existing entry from the media database.
// This may also delete the file (for media types), but that is irrelevant as it will be truncated momentarily in any case.
String where = MediaStore.MediaColumns.DATA + "=?";
String[] selectionArgs = new String[] { file.getAbsolutePath() };
contentResolver.delete(ContentUriUtil.FILES_URI, where, selectionArgs);
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath());
values.put(MediaStore.MediaColumns.SIZE, size);
Uri uri = contentResolver.insert(ContentUriUtil.FILES_URI, values);
if (uri == null) {
// Should not occur.
throw new IOException("Internal error.");
}
return contentResolver.openOutputStream(uri);
}
}
This is a development project I would have submitted under the DevDB forum but it says-
You do not have permission to edit a project. If reaching this message in error, please notify an administrator.
Click to expand...
Click to collapse
On to my trouble -
I am trying to post some data to my webservice but when I am testing it out in my device I am not getting error but at the same time nothing really happens, the tablet is responding just fine everything works but my webservice is not getting any data nor does the application show any error.
I have added some Toast messages to check the flow of logic and from what I can determine the app goes into the method to send data to the webservice and then just does nothing.
This is my code
Code:
//Method to post data to webservice
public void post() throws UnsupportedEncodingException
{
try {
Toast.makeText(getBaseContext(), "Creating new user", Toast.LENGTH_SHORT).show();
HttpURLConnection urlConnection = (HttpURLConnection) (new URL("some url that the forum wont let me post").openConnection());
urlConnection.setConnectTimeout(1500);
urlConnection.setRequestMethod("POST");
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("uname", uname));
params.add(new BasicNameValuePair("pass", password));
params.add(new BasicNameValuePair("email", email));
OutputStream os = urlConnection.getOutputStream();
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(os, "UTF-8"));
writer.write(getQuery(params));
writer.flush();
writer.close();
os.close();
urlConnection.connect();
if(urlConnection.getResponseCode() == 200){
InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream());
BufferedReader streamReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
StringBuilder responseStrBuilder = new StringBuilder();
String inputStr;
while ((inputStr = streamReader.readLine()) != null)
responseStrBuilder.append(inputStr);
JSONObject json = new JSONObject(responseStrBuilder.toString());
String message = json.getString("message");
boolean error = json.getBoolean("error");
error(error, message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
On execution I have found out that the flow dies at
Code:
OutputStream os = urlConnection.getOutputStream()
;
I get no error, no connection is ever established with my webserver.
Why is this happening?
techbr124 said:
This is a development project I would have submitted under the DevDB forum but it says-
On to my trouble -
I am trying to post some data to my webservice but when I am testing it out in my device I am not getting error but at the same time nothing really happens, the tablet is responding just fine everything works but my webservice is not getting any data nor does the application show any error.
I have added some Toast messages to check the flow of logic and from what I can determine the app goes into the method to send data to the webservice and then just does nothing.
This is my code
Code:
//Method to post data to webservice
public void post() throws UnsupportedEncodingException
{
try {
Toast.makeText(getBaseContext(), "Creating new user", Toast.LENGTH_SHORT).show();
HttpURLConnection urlConnection = (HttpURLConnection) (new URL("some url that the forum wont let me post").openConnection());
urlConnection.setConnectTimeout(1500);
urlConnection.setRequestMethod("POST");
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("uname", uname));
params.add(new BasicNameValuePair("pass", password));
params.add(new BasicNameValuePair("email", email));
OutputStream os = urlConnection.getOutputStream();
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(os, "UTF-8"));
writer.write(getQuery(params));
writer.flush();
writer.close();
os.close();
urlConnection.connect();
if(urlConnection.getResponseCode() == 200){
InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream());
BufferedReader streamReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
StringBuilder responseStrBuilder = new StringBuilder();
String inputStr;
while ((inputStr = streamReader.readLine()) != null)
responseStrBuilder.append(inputStr);
JSONObject json = new JSONObject(responseStrBuilder.toString());
String message = json.getString("message");
boolean error = json.getBoolean("error");
error(error, message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
On execution I have found out that the flow dies at
Code:
OutputStream os = urlConnection.getOutputStream()
;
I get no error, no connection is ever established with my webserver.
Why is this happening?
Click to expand...
Click to collapse
I'm not an expert for this kind of stuff.
Have a look here: How to call webservice function?.
Maybe this can help.
If not, please ask your question App Development Forums > Coding Discussion, Q&A, and Educational Resources > Java for Android App Development forum.
There you will get quicker and better help.
Thread closed and thank you.
I get a lot of question that my custom kernel breaks support their own AOSP-based ROMs.
So I'm here to how to fix that from your side.
The culprit is CyanogenMod's /init.environ.rc.
CM has been messing around that in recent months.
Changing BOOTCLASSPATH does not break cross-compatibility between different AOSP forks, as long as they don't remove stuffs and just adds stuff.
The problem is SYSTEMSERVERCLASSPATH. If any new stuffs are added, Android RunTime will just abort itself and refuse to boot.
And recently, CyanogenMod added Cyanogen Platform API.
That additionally adds /system/framework/org.cyanogenmod.platform.jar to the /init.environ.rc's SYSTEMSERVERCLASSPATH.
That breaks other AOSP-forks that doesn't use Cyanogen Platform API.
So those ROM developers have 3 options.
They can..
1. Just go and use Cyanogen Platform API. Undoubtedly, this would be super hard if your ROM is not already based on CyanogenMod.
2. Ask custom kernel developers to release a separate version just for your ROM. Very unlikely.
3. Solution below.
Most elegant solution I ended up so far is patch your Android RunTime(android_art) to just bypass extra SYSTEMSERVERCLASSPATH.
Just go to art/runtime/native/dalvik_system_DexFile.cc.
Around line 380..
Code:
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index 3298b46..50aa44c 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -382,14 +382,6 @@ static jbyte IsDexOptNeededInternal(JNIEnv* env, const char* filename,
// Logging of reason for returning kDexoptNeeded or kPatchoatNeeded.
const bool kReasonLogging = true;
- if ((filename == nullptr) || !OS::FileExists(filename)) {
- LOG(ERROR) << "DexFile_isDexOptNeeded file '" << filename << "' does not exist";
- ScopedLocalRef<jclass> fnfe(env, env->FindClass("java/io/FileNotFoundException"));
- const char* message = (filename == nullptr) ? "<empty file name>" : filename;
- env->ThrowNew(fnfe.get(), message);
- return kUpToDate;
- }
-
// Always treat elements of the bootclasspath as up-to-date. The
// fact that code is running at all means that this should be true.
Runtime* runtime = Runtime::Current();
Remove those 7 lines and your ROM will be now compatible with custom kernels targeting CyanogenMod.
(Git style patch is added for 'git am' command. I'd appreciate it if you maintain Git repository, apply that Git patch for authorship.)
The only problem so far would be that this will break compatibility with Xposed, which replaces Android RunTime thus reverting this modification.