According to the Android docs
TV Inputs provided and signed by the device manufacturer (signature
apps) or other apps installed in the system partition will have access
to the entire TV Provider database. This access can be used to
construct apps to browse and search across all available TV channels
and programs.
Assuming I signed as device manufacturer or installed app in system partition, how can I access the TvProvider and thus its channel information?
EDIT:
val tifSupport: Boolean = packageManager.hasSystemFeature(PackageManager.FEATURE_LIVE_TV)
Log.d("XXX", "TIF Support ? $tifSupport")
This line says true. Then I run these lines:
val tvInputManager = applicationContext.getSystemService(TV_INPUT_SERVICE) as TvInputManager?
Log.d("XXX", "TvInputManager $tvInputManager")
val il = tvInputManager?.tvInputList
Log.d("XXX", "TvInputList size --> ${il?.size}")
tvInputManager?.tvInputList?.forEach { info ->
Log.d("XXX", "TvInputListInfo ${info.id} ${info.serviceInfo} # ${info.extras.size()}")
}
First log prints
TvInputManager android.media.tv.TvInputManager#95728c2
so the tvInputManager looks valid. Second shows 0 as the TvInputList size and thus third log (in forEach()) is not printed at all.
You have to start with background for Android. By provider in the doc, they mean ContentProvider, which will share information between process within Android. Now to start with:
If TV Provider supported by our system.
If All the Manifest Permission set to the application
If Application installed under the system apps (and has right SE Policy)
Then you will be able to use ContentProvider to fetch all kind of information you need. To see the full support for TVContent Provider you can refer to this file (ensure it's aligned with your Android OS version) and other AOSP information. For ex.
/**
* Returns the current list of channels your app provides.
*
* #param resolver Application's ContentResolver.
* #return List of channels.
*/
public static List<Channel> getChannels(ContentResolver resolver) {
List<Channel> channels = new ArrayList<>();
// TvProvider returns programs in chronological order by default.
Cursor cursor = null;
try {
cursor = resolver.query(Channels.CONTENT_URI, Channel.PROJECTION, null, null, null);
if (cursor == null || cursor.getCount() == 0) {
return channels;
}
while (cursor.moveToNext()) {
channels.add(Channel.fromCursor(cursor));
}
} catch (Exception e) {
Log.w(TAG, "Unable to get channels", e);
} finally {
if (cursor != null) {
cursor.close();
}
}
return channels;
}
Another ex.
TvInputManager tv = (TvInputManager)getApplicationContext().getSystemService(Context.TV_INPUT_SERVICE);
List<TvInputInfo> list = tv.getTvInputList();
String[] projection = {
TvContract.Channels._ID,
TvContract.Channels.COLUMN_DISPLAY_NUMBER
};
ContentResolver cr = getContentResolver();
Iterator<TvInputInfo> it = list.iterator();
while(it.hasNext()) {
TvInputInfo aux = it.next();
Uri uri = TvContract.buildChannelsUriForInput(aux.getId());
Log.d("TAG", uri.toString());
Log.d("TAG", aux.toString());
Cursor cur = cr.query(uri, projection, null, null ,null);
Log.d("TAG", cur.toString());
if(cur.moveToFirst()) {
Log.d("TAG", "not empty cursors");
}
}
UPDATE:
The basic permission you should have(Also refer to the official documentation):
<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
<uses-permission android:name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"/>
Related
I'm writing a wrapper for SAF wrapper for Dropbox since everyone (including Google) is too lazy to implement this "very rich" (ie: awful) API. I've got my root in the picker, but I thought queryChildren should be called first. However, queryChildren is never called and it goes straight toqueryDocument`.
override fun queryRoots(projection: Array<out String>?): Cursor {
// TODO: Likely need to be more strict about projection (ie: map to supported)
val result = MatrixCursor(projection ?: DEFAULT_ROOT_PROJECTION)
val row = result.newRow()
row.add(DocumentsContract.Root.COLUMN_ROOT_ID, "com.anthonymandra.cloudprovider.dropbox")
row.add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_dropbox_gray)
row.add(DocumentsContract.Root.COLUMN_TITLE, "Dropbox")
row.add(DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.FLAG_SUPPORTS_CREATE) // TODO:
row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, ROOT_DOCUMENT_ID)
return result
}
override fun queryChildDocuments(
parentDocumentId: String?,
projection: Array<out String>?,
sortOrder: String?
): Cursor {
// TODO: Likely need to be more strict about projection (ie: map to supported)
val result = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
val dropboxPath = if (parentDocumentId == ROOT_DOCUMENT_ID) "" else parentDocumentId
try {
val client = DropboxClientFactory.client
var childFolders = client.files().listFolder(dropboxPath)
while (true) {
for (metadata in childFolders.entries) {
addDocumentRow(result, metadata)
}
if (!childFolders.hasMore) {
break
}
childFolders = client.files().listFolderContinue(childFolders.cursor)
}
} catch(e: IllegalStateException) { // Test if we can attempt auth thru the provider
context?.let {
Auth.startOAuth2Authentication(it, appKey) // TODO: appKey
}
}
return result
}
override fun queryDocument(documentId: String?, projection: Array<out String>?): Cursor {
// TODO: Likely need to be more strict about projection (ie: map to supported)
val result = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
try {
val client = DropboxClientFactory.client
val metadata = client.files().getMetadata(documentId)
addDocumentRow(result, metadata)
} catch(e: IllegalStateException) { // Test if we can attempt auth thru the provider
context?.let {
Auth.startOAuth2Authentication(it, appKey) // TODO: appKey
}
}
return result
}
Error:
java.lang.IllegalArgumentException: String 'path' does not match pattern
at com.dropbox.core.v2.files.GetMetadataArg.<init>(GetMetadataArg.java:58)
at com.dropbox.core.v2.files.GetMetadataArg.<init>(GetMetadataArg.java:80)
at com.dropbox.core.v2.files.DbxUserFilesRequests.getMetadata(DbxUserFilesRequests.java:1285)
at com.anthonymandra.cloudprovider.dropbox.DropboxProvider.queryDocument(DropboxProvider.kt:98)
at android.provider.DocumentsProvider.query(DocumentsProvider.java:797)
at android.content.ContentProvider$Transport.query(ContentProvider.java:240)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:102)
at android.os.Binder.execTransact(Binder.java:731)
path is ROOT_DOCUMENT_ID which I'm expecting to go to queryChildDocuments first.
What am I missing here?
I have also written a SAF DropBox implementation, and I also was a bit confused at first about this.
From the documentation:
Note the following:
Each document provider reports one or more "roots" which are starting
points into exploring a tree of documents. Each root has a unique
COLUMN_ROOT_ID, and it points to a document (a directory)
representing the contents under that root. Roots are dynamic by
design to support use cases like multiple accounts, transient USB
storage devices, or user login/log out.
Under each root is a single document. That document points to 1 to N
documents, each of which in turn can point to 1 to N documents.
Each storage backend surfaces individual files and directories by
referencing them with a unique COLUMN_DOCUMENT_ID. Document IDs must
be unique and not change once issued, since they are used for
persistent URI grants across device reboots.
Documents can be either an openable file (with a specific MIME type),
or a directory containing additional documents (with the
MIME_TYPE_DIR MIME type).
Each document can have different capabilities, as described by
COLUMN_FLAGS. For example, FLAG_SUPPORTS_WRITE,
FLAG_SUPPORTS_DELETE, and FLAG_SUPPORTS_THUMBNAIL. The same
COLUMN_DOCUMENT_ID can be included in multiple directories.
That second bullet is the key bullet. After the return from queryRoots(), for each root you passed back, the SAF makes a call to queryDocument(). This is essentially to create the "root file folder" document that appears in the list. What I did was in queryDocument() I check to see if the documentId passed in matches the unique value I gave to DocumentsContract.Root.COLUMN_ROOT_ID in the queryRoots() call. If it is, then you know this queryDocument() call needs to return a folder representing that root. Otherwise, I use the path from DropBox as my documentId everywhere else, so I use that documentID value in calls via DbxClientV2.
Here is some sample code - note that in my case I created an AbstractStorageProvider class from which all my various providers (Dropbox, Instagram, etc.) extend. The base class handles receiving the calls from SAF, and it does some housekeeping (like creating the cursors) and then calls the methods in the implementing classes to populate the cursors as required by that particular service:
Base Class
public Cursor queryRoots(final String[] projection) {
Timber.d( "Lifecycle: queryRoots called");
// If they are not paid up, they do not get to use any of these implementations
if (!InTouchUtils.isLoginPaidSubscription()) {
return null;
}
// Create a cursor with either the requested fields, or the default projection if "projection" is null.
final MatrixCursor cursor = new MatrixCursor(projection != null ? projection : getDefaultRootProjection());
// Classes that extend this one must implement this method
addRowsToQueryRootsCursor(cursor);
return cursor;
}
From DropboxProvider addRowsToQueryRootsCursor:
protected void addRowsToQueryRootsCursor(MatrixCursor cursor) {
// See if we need to init
long l = System.currentTimeMillis();
if ( !InTouchUtils.initDropboxClient()) {
return;
}
Timber.d( "Time to test initialization of DropboxClient: %dms.", (System.currentTimeMillis() - l));
l = System.currentTimeMillis();
try {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(Objects.requireNonNull(getContext()).getApplicationContext());
String displayname = sharedPrefs.getString(getContext().getString(R.string.pref_dropbox_displayname_token_key),
getContext().getResources().getString(R.string.pref_dropbox_displayname_token_default));
batchSize = Long.valueOf(Objects.requireNonNull(sharedPrefs.getString(getContext().getString(R.string.pref_dropbox_query_limit_key),
getContext().getResources().getString(R.string.pref_dropbox_query_limit_key_default))));
final MatrixCursor.RowBuilder row = cursor.newRow();
row.add(DocumentsContract.Root.COLUMN_ROOT_ID, <YOUR_UNIQUE_ROOTS_KEY_HERE>);
row.add(DocumentsContract.Root.COLUMN_TITLE,
String.format(getContext().getString(R.string.dropbox_root_title),getContext().getString(R.string.app_name)));
row.add(DocumentsContract.Root.COLUMN_SUMMARY,displayname+
getContext().getResources().getString(R.string.dropbox_root_summary));
row.add(DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.FLAG_SUPPORTS_RECENTS | DocumentsContract.Root.FLAG_SUPPORTS_SEARCH);
row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID,<YOUR_UNIQUE_ROOT_FOLDER_ID_HERE>);
row.add(DocumentsContract.Root.COLUMN_ICON, R.drawable.intouch_for_dropbox);
} catch (Exception e) {
Timber.d( "Called addRowsToQueryRootsCursor got exception, message was: %s", e.getMessage());
}
Timber.d( "Time to queryRoots(): %dms.", (System.currentTimeMillis() - l));
}
Then queryDocument() method in the base class:
#Override
public Cursor queryDocument(final String documentId, final String[] projection) {
Timber.d( "Lifecycle: queryDocument called for: %s", documentId);
// Create a cursor with either the requested fields, or the default projection if "projection" is null.
// Return a cursor with a getExtras() method, to avoid the immutable ArrayMap problem.
final MatrixCursor cursor = new MatrixCursor(projection != null ? projection : getDefaultDocumentProjection()){
Bundle cursorExtras = new Bundle();
#Override
public Bundle getExtras() {
return cursorExtras;
}
};
addRowToQueryDocumentCursor(cursor, documentId);
return cursor;
}
And addRowToQueryDocumentCursor() in DropboxProvider:
protected void addRowToQueryDocumentCursor(MatrixCursor cursor,
String documentId) {
try {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(Objects.requireNonNull(getContext()).getApplicationContext());
String displayname = sharedPrefs.getString(getContext().getString(R.string.pref_dropbox_displayname_token_key),
getContext().getString(R.string.pref_dropbox_displayname_token_default));
if ( !InTouchUtils.initDropboxClient()) {
return;
}
if ( documentId.equals(<YOUR_UNIQUE_ROOTS_ID_HERE>)) {
// root Dir
Timber.d( "addRowToQueryDocumentCursor called for the root");
final MatrixCursor.RowBuilder row = cursor.newRow();
row.add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, <YOUR_UNIQUE_FOLDER_ID_HERE>);
row.add(DocumentsContract.Document.COLUMN_DISPLAY_NAME,
String.format(getContext().getString(R.string.dropbox_root_title),
getContext().getString(R.string.app_name)));
row.add(DocumentsContract.Document.COLUMN_SUMMARY,displayname+
getContext().getString(R.string.dropbox_root_summary));
row.add(DocumentsContract.Document.COLUMN_ICON, R.drawable.folder_icon_dropbox);
row.add(DocumentsContract.Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
row.add(DocumentsContract.Document.COLUMN_FLAGS, 0);
row.add(DocumentsContract.Document.COLUMN_SIZE, null);
row.add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, null);
return;
}
Timber.d( "addRowToQueryDocumentCursor called for documentId: %s", documentId);
DbxClientV2 mDbxClient = DropboxClientFactory.getClient();
Metadata metadata = mDbxClient.files().getMetadata(documentId);
if ( metadata instanceof FolderMetadata) {
Timber.d( "Document was a folder");
includeFolder(cursor, (FolderMetadata)metadata);
} else {
Timber.d( "Document was a file");
includeFile(cursor, (FileMetadata) metadata);
}
} catch (Exception e ) {
Timber.d( "Called addRowToQueryDocumentCursor got exception, message was: %s documentId was: %s.", e.getMessage(), documentId);
}
}
The documentation for implementing a DocumentsProvider is... limited. In particular, there is no documented guarantee of the order of calls. As such, a DocumentsProvider really should be implemented to make as few assumptions as possible about the order of those calls.
For example, I would not assume that queryRoots() is called first. It probably will be first, if the first use of the DocumentsProvider for this process happens to be the Storage Access Framework UI. However, given that clients can (with care) persist a document or document tree Uri, you might wind up being called with something else first in your process, if the first thing happens to be a client using a persisted Uri.
And, in your specific case, I would not assume that queryChildDocuments() occurs before or after queryDocument().
I am having trouble obtaining the "server", "mms_proxy", and "mms_port". I haven't found anything official anywhere from Google about why this is? I would assume if this is something I cannot do then that would be stated by Google.
The Android developer website shows the Telephony.Carriers class and all that is available but says nothing about restrictions of any kind so at this point I believe it is fair to assume I can access these rather than have to ask the user to find the APN values manually which will make many people give up instantly and not use my App.
Can we please try to find an official answer as to what is going on here, I have attempted to get this information in many ways all leading to very confusing errors that seemingly cannot be explained.
I am running Android 6.01 on the device ZTE Axon 7. The lowest api that my App will allow is api_21 and the Telephony.Carriers class in available from api_19 and up.
I have declared all then necessary permission in the Manifest file and since I am currently testing on sdk "M" (Android 6), I have also acquired the permissions explicitly at Runtime from the user.
This is the code inside my Activity that I run and receive a an error stating "Error getting Mms Carrier values"...this is the whole problem and where I have become stuck.
ContentResolver contentResolver = getContentResolver();
int index_ID;
final String[] PROJECT =
{
Telephony.Carriers.TYPE,
Telephony.Carriers.MMSC,
Telephony.Carriers.MMSPROXY,
Telephony.Carriers.MMSPORT,
};
grantMyUriPermission(activity, Telephony.Carriers.CONTENT_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
grantMyUriPermission(activity, Telephony.Carriers.CONTENT_URI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Cursor carriers_cursor = SqliteWrapper.query(activity, contentResolver,
Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "current"), PROJECT, null, null, null);
if (carriers_cursor != null)
{
/*This crap below isn't needed for this situation but helps avoid ERROR*/
index_ID = carriers_cursor.getColumnIndex(Telephony.Carriers._ID);
//--------------------------------------------------------------------------------------
if (index_ID < 0 || !carriers_cursor.moveToFirst())
{
Log.i(TAG, "Error getting Mms Carrier values");
carriers_cursor.close();
return;
}
//--------------------------------------------------------------------------------------
do
{
//Confirm cursor has value assigned to it
Log.i(TAG, "cursor: "+ carriers_cursor);
//Get the available columns names
for (String item : carriers_cursor.getColumnNames())
{
//Name of each column available with Cursor
Log.i(TAG, "item: " + item);
//Attempt to get String value for each available column
try
{
//Use the name of the column, to get the index, to get the String value
Log.i(TAG, "getString(): " + carriers_cursor.getString(carriers_cursor.getColumnIndex(item)));
}
catch (Exception e)
{
Log.i(TAG, "Exception: " + e);
}
}
}
while (carriers_cursor.moveToNext());
carriers_cursor.close();
}
The Log output is:
I/SmsReceiveIntentService: Running grantMyUriPermission()
I/SmsReceiveIntentService: Running grantMyUriPermission()
I/NewConversationActivity: Error getting Mms Carrier values
And that's it! Nothing else to go on...?
UPDATE:
My initial question may be misleading so I want to rephrase it:
I want to traverse through the hierarchy tree from an MTP connected device through Android's Storage Access Framework. I can't seem to achieve this because I get a SecurityException stating that a subnode is not a descendant of its parent node. Is there a way to workaround this issue? Or is this a known issue? Thanks.
I'm writing an Android application that attempts to traverse and access documents through the hierarchy tree using Android's Storage Access Framework (SAF) via the MtpDocumentsProvider. I am more or less following the code example described in https://github.com/googlesamples/android-DirectorySelection on how to launch the SAF Picker from my app, select the MTP data source, and then, in onActivityResult, use the returned Uri to traverse through the hierarchy. Unfortunately, this doesn't seem to work because as soon as I access a sub-folder and try to traverse that, I always get a SecurityException stating that document xx is not a descendant of yy
So my question is, using the MtpDocumentProvider, how can I successfully traverse through the hierarchy tree from my app and avoid this exception?
To be specific, in my app, first, I call the following method to launch the SAF Picker:
private void launchStoragePicker() {
Intent browseIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
browseIntent.addFlags(
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
| Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
);
startActivityForResult(browseIntent, REQUEST_CODE_OPEN_DIRECTORY);
}
The Android SAF picker then launches, and I see my connected device recognized as the MTP data source. I select said data source and I get the Uri from my onActivityResult:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_OPEN_DIRECTORY && resultCode == Activity.RESULT_OK) {
traverseDirectoryEntries(data.getData()); // getData() returns the root uri node
}
}
Then, using the returned Uri, I call DocumentsContract.buildChildDocumentsUriUsingTree to get a Uri which I can then use to query and access the tree hierarchy:
void traverseDirectoryEntries(Uri rootUri) {
ContentResolver contentResolver = getActivity().getContentResolver();
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, DocumentsContract.getTreeDocumentId(rootUri));
// Keep track of our directory hierarchy
List<Uri> dirNodes = new LinkedList<>();
dirNodes.add(childrenUri);
while(!dirNodes.isEmpty()) {
childrenUri = dirNodes.remove(0); // get the item from top
Log.d(TAG, "node uri: ", childrenUri);
Cursor c = contentResolver.query(childrenUri, new String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_MIME_TYPE}, null, null, null);
try {
while (c.moveToNext()) {
final String docId = c.getString(0);
final String name = c.getString(1);
final String mime = c.getString(2);
Log.d(TAG, "docId: " + id + ", name: " + name + ", mime: " + mime);
if(isDirectory(mime)) {
final Uri newNode = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, docId);
dirNodes.add(newNode);
}
}
} finally {
closeQuietly(c);
}
}
}
// Util method to check if the mime type is a directory
private static boolean isDirectory(String mimeType) {
return DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType);
}
// Util method to close a closeable
private static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException re) {
throw re;
} catch (Exception ignore) {
// ignore exception
}
}
}
The first iteration on the outer while loop succeeds: the call to query returned a valid Cursor for me to traverse. The problem is the second iteration: when I try to query for the Uri, which happens to be a subnode of rootUri, I get a SecurityException stating the document xx is not a descendent of yy.
D/MyApp(19241): node uri: content://com.android.mtp.documents/tree/2/document/2/children
D/MyApp(19241): docId: 4, name: DCIM, mime: vnd.android.document/directory
D/MyApp(19241): node uri: content://com.android.mtp.documents/tree/2/document/4/children
E/DatabaseUtils(20944): Writing exception to parcel
E/DatabaseUtils(20944): java.lang.SecurityException: Document 4 is not a descendant of 2
Can anyone provide some insight as to what I'm doing wrong? If I use a different data source provider, for example, one that is from external storage (i.e. an SD Card attached via a standard USB OTG reader), everything works fine.
Additional information:
I'm running this on a Nexus 6P, Android 7.1.1, and my app minSdkVersion is 19.
I am using your code to go in sub folders with adding lines:
Uri childrenUri;
try {
//for childs and sub child dirs
childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getDocumentId(uri));
} catch (Exception e) {
// for parent dir
childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri));
}
The flags added before start activity for result do nothing.
I do not understand that you use DocumentsContract. The user picks a directory. From the uri you get you can construct a DocumentFile for that directory.
After that use DocumentFile::listFiles() on that instance to get a list of subdirectories and files.
After different attempts, I'm not sure there is a way around this SecurityException for an MTP Data Source (unless someone can refute me on this). Looking at the DocumentProvider.java source code and the stack trace, it appears that the call to DocumentProvider#isChildDocument inside DocumentProvider#enforceTree may not have been properly overriden in the MtpDocumentProvider implementation in the Android framework. Default implementation always returns false. #sadface
I need to uniquely identify an Android device. I read about ANDROID_ID but it seems to have problems with Android 2.2. Then there are other identifiers related to TelephonyManager, but I reckon they don't work on tablets.
So, looking for something working on every device I stumbled upon the GSF ID KEY (google service framework id). Do you guys think this is a reliable and always working solution?
This is the code I found to retrieve the GSF ID KEY:
private static String getGsfAndroidId(Context context)
{
Uri URI = Uri.parse("content://com.google.android.gsf.gservices");
String ID_KEY = "android_id";
String params[] = {ID_KEY};
Cursor c = context.getContentResolver().query(URI, null, null, params, null);
if (!c.moveToFirst() || c.getColumnCount() < 2)
return null;
try
{
return Long.toHexString(Long.parseLong(c.getString(1)));
}
catch (NumberFormatException e)
{
return null;
}
}
In case someone is wondering if this method works the answer is yes, I tried it (and used it in an app I put on the Android market with thousands of downloads) and it works. Note: the GSF ID KEY changes every time the user does a factory reset or messes up with Google Services, but it was good enough for my purpose.
using Android Studio, I get auto-recommendations from lint. here is the code, after revised. it may solve exceptions reported by https://stackoverflow.com/users/423171/cprcrack
private static String getGsfAndroidId(Context context)
{
Uri URI = Uri.parse("content://com.google.android.gsf.gservices");
String ID_KEY = "android_id";
String params[] = {ID_KEY};
Cursor c = context.getContentResolver().query(URI, null, null, params, null);
if (c != null && (!c.moveToFirst() || c.getColumnCount() < 2)){
if(!c.isClosed())
c.close();
return null;
}
try {
if (c != null) {
String result = Long.toHexString(Long.parseLong(c.getString(1)));
if(!c.isClosed())
c.close();
return result;
}else {
return null;
}
} catch (NumberFormatException e) {
if(!c.isClosed())
c.close();
return null;
}
}
Can't talk about production tests, but I noticed that in my Nexus 5 with Android 5.0, I had to add the following permission: com.google.android.providers.gsf.permission.READ_GSERVICES. Otherwise an exception is raised when using your code.
Can't talk about production tests, but I noticed that in my Nexus 5 with Android 5.0, I had to add the following permission: com.google.android.providers.gsf.permission.READ_GSERVICES. Otherwise an exception is raised when using your code.
Share Improve this answ
I have do an android application, and now, this application needs to read emails. I found this code:
protected ArrayList<Mail> doInBackground(Void... params) {
ArrayList<Mail> mails = new ArrayList<Mail>(32);
boolean finish = false;
try {
String direcCompleta = URI_PREFIX + email;
Uri a = Uri.parse(direcCompleta);
Cursor cCursor = resolver.query(a, null, null , null, null);
while (cCursor.moveToNext() && (! finish)) {
finish = fromTime.before(new Date(cCursor.getLong(1)));
if (! finish){
String conv_id = cCursor.getString(cCursor.getColumnIndex("_id"));
Uri uri = Uri.parse(URI_PREFIX + email + "/" + conv_id + "/messages");
Cursor mCursor = resolver.query(uri, MESSAGE_PROJECTION, null, null, null);
while (mCursor.moveToNext() && (! finish)){
long mtime = mCursor.getLong(4);
finish = fromTime.before(new Date(mtime));
if (! finish){
mails.add(new Mail(mCursor.getString(0), mCursor.getString(2), mCursor.getString(1), mCursor.getString(4), mtime));
}
}
}
}
} catch (Exception ex){
Log.e("GmailReadApp", ex.toString());
mails.add(new Mail(null, null, ex.toString(), ex.toString(), 0));
}
return mails;
}
But cCursor is null. I have my mail account in app: "Email", because i work with android sdk, and it doesn´t have app: "GMAIL". And android sdk doesn´t have market.
Somebody can help me please? thanks. (no matter if it's for gmail, yahoo, hotmail,....)
I have do an android application, and now, this application needs to read emails
Write your own email client from scratch, then. There is an Android port of JavaMail floating around somewhere. Or, grab an existing open source email client (e.g., K9) and modify it to suit.
I found this code
There is no possible value of URI_PREFIX that is documented and supported for use on Android. Even for the undocumented and unsupported values, you cannot hold the permissions necessary to read those messages.
You can do it with Kotlin.
Download jar files: additionnal.jar, mail.jar, activation.jar
Add those jars in External Libraries in android studio
Add internet permission in manifest file:
<uses-permission android:name="android.permission.INTERNET" />
Enable less secure apps to access your Gmail
Add this code to your app:
GlobalScope.launch {
val props = Properties()
props.setProperty("mail.store.protocol", "imaps")
try{
val session = Session.getInstance(props, null)
val store = session.store
store.connect("imap.gmail.com", "youremail#gmail.com", "password")
val inbox = store.getFolder("INBOX")
inbox.open(Folder.READ_ONLY)
Log.d("MyLog", inbox.messageCount.toString())
val msg = inbox.getMessage(inbox.messageCount)
val address = msg.from
for (adr in address) {
Log.d("MyLog", adr.toString())
}
val mp = msg.content as Multipart
val bp = mp.getBodyPart(0)
Log.d("MyLog", bp.content.toString())
}catch (e: Exception){
Log.d("MyLog", "Error $e")
}
}