I want to create a bookmark in the stock android-browser from my own application. How can I do this?
I only found the Browser.saveBookmark-Method (api-doc) but this displays a new window where the user can change the data. As I want to import bookmarks from a external data-source I want to save the bookmark directly and not ask the user for input.
If you just want to allow the user to add a bookmark, android.provider.Browser.saveBookmark() is what you want. But it looks like you're wanting to do mass updates, so that's probably not enough since it just opens the browser's bookmarks page.
AFAIK there is no open API that ties directly into the browser's bookmarks. However, there is a content resolver for it which can be accessed android.provider.Browser.BOOKMARKS_URI. Once you've resolved the provider you can manipulate bookmarks by running queries, provided you have the com.android.browser.permission.READ_HISTORY_BOOKMARKS and com.android.browser.permission.WRITE_HISTORY_BOOKMARKS permissions.
If you're unfamiliar with content providers, they can get kinda hairy (doubly so if you're unfamiliar with SQL). However, the knowledge base has some good articles on them, and a quick google for "android content provider tutorial" should get you well on your way.
I took the following code from Android implementation (and disabled the "bookmark added" toast):
/**
* Add a bookmark to the database.
*
* #param context
* Context of the calling Activity. This is used to make Toast confirming that the bookmark has been added. If the caller provides null, the Toast will not be shown.
* #param cr
* The ContentResolver being used to add the bookmark to the db.
* #param url
* URL of the website to be bookmarked.
* #param name
* Provided name for the bookmark.
* #param thumbnail
* A thumbnail for the bookmark.
* #param retainIcon
* Whether to retain the page's icon in the icon database. This will usually be <code>true</code> except when bookmarks are added by a settings restore agent.
*/
static void addBookmark(Context context, ContentResolver cr, String url, String name, Bitmap thumbnail, boolean retainIcon) {
final String WHERE_CLAUSE = "url = ? OR url = ? OR url = ? OR url = ?";
final String WHERE_CLAUSE_SECURE = "url = ? OR url = ?";
String[] SELECTION_ARGS;
// Want to append to the beginning of the list
long creationTime = new Date().getTime();
// First we check to see if the user has already visited this
// site. They may have bookmarked it in a different way from
// how it's stored in the database, so allow different combos
// to map to the same url.
boolean secure = false;
String compareString = url;
if (compareString.startsWith("http://")) {
compareString = compareString.substring(7);
} else if (compareString.startsWith("https://")) {
compareString = compareString.substring(8);
secure = true;
}
if (compareString.startsWith("www.")) {
compareString = compareString.substring(4);
}
if (secure) {
SELECTION_ARGS = new String[2];
SELECTION_ARGS[0] = "https://" + compareString;
SELECTION_ARGS[1] = "https://www." + compareString;
} else {
SELECTION_ARGS = new String[4];
SELECTION_ARGS[0] = compareString;
SELECTION_ARGS[1] = "www." + compareString;
SELECTION_ARGS[2] = "http://" + compareString;
SELECTION_ARGS[3] = "http://" + SELECTION_ARGS[1];
}
Cursor cursor = cr.query(Browser.BOOKMARKS_URI, Browser.HISTORY_PROJECTION, secure ? WHERE_CLAUSE_SECURE : WHERE_CLAUSE, SELECTION_ARGS, null);
ContentValues map = new ContentValues();
if (cursor.moveToFirst() && cursor.getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX) == 0) {
// This means we have been to this site but not bookmarked
// it, so convert the history item to a bookmark
map.put(Browser.BookmarkColumns.CREATED, creationTime);
map.put(Browser.BookmarkColumns.TITLE, name);
map.put(Browser.BookmarkColumns.BOOKMARK, 1);
// map.put(Browser.BookmarkColumns.THUMBNAIL, bitmapToBytes(thumbnail));
cr.update(Browser.BOOKMARKS_URI, map, "_id = " + cursor.getInt(0), null);
} else {
int count = cursor.getCount();
boolean matchedTitle = false;
for (int i = 0; i < count; i++) {
// One or more bookmarks already exist for this site.
// Check the names of each
cursor.moveToPosition(i);
if (cursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX).equals(name)) {
// The old bookmark has the same name.
// Update its creation time.
map.put(Browser.BookmarkColumns.CREATED, creationTime);
cr.update(Browser.BOOKMARKS_URI, map, "_id = " + cursor.getInt(0), null);
matchedTitle = true;
break;
}
}
if (!matchedTitle) {
// Adding a bookmark for a site the user has visited,
// or a new bookmark (with a different name) for a site
// the user has visited
map.put(Browser.BookmarkColumns.TITLE, name);
map.put(Browser.BookmarkColumns.URL, url);
map.put(Browser.BookmarkColumns.CREATED, creationTime);
map.put(Browser.BookmarkColumns.BOOKMARK, 1);
map.put(Browser.BookmarkColumns.DATE, 0);
// map.put(Browser.BookmarkColumns.THUMBNAIL, bitmapToBytes(thumbnail));
int visits = 0;
if (count > 0) {
// The user has already bookmarked, and possibly
// visited this site. However, they are creating
// a new bookmark with the same url but a different
// name. The new bookmark should have the same
// number of visits as the already created bookmark.
visits = cursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX);
}
// Bookmark starts with 3 extra visits so that it will
// bubble up in the most visited and goto search box
map.put(Browser.BookmarkColumns.VISITS, visits + 3);
cr.insert(Browser.BOOKMARKS_URI, map);
}
}
if (retainIcon) {
WebIconDatabase.getInstance().retainIconForPageUrl(url);
}
cursor.close();
}
Related
I am trying to implement a feature like Instagram or WhatsApp, where the thumbnail of a single image that exists in a folder in android, is shown on top of a list item, more like a sample of what kinds of image are in the folder.
Help me to understand this feature.
How I implemented it. It might not be the best though, but it works.
I fetched the URIs of all the images using MediaStore, you can learn how to use it here.
The First step was done in a background thread to prevent it from blocking the UI thread.
I sorted out the images I got, grouping them in a List<Image>, which would represent a single directory.
I then added the List<Image> into a List<List<Image>>, which served as the overall images that were fetched and have their total size which I used later to track the number of images in the directory.
The code is below.
#Override
public void run() {
Uri storageUri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
storageUri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
} else {
storageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
}
// the queries to the MediaStore API (The image details or metadata I need
String[] projection = {
MediaStore.Images.Media._ID,
MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media.DISPLAY_NAME};
// now query the MediaStore API using ContentResolver
Cursor imgCursor = getApplicationContext().getContentResolver().query(storageUri, projection, null, null, null);
int bucketId = imgCursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID);
int imgSize = imgCursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE);
int name = imgCursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME);
int bucketName = imgCursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_DISPLAY_NAME);
// directoryDictionary is a temporary list of directory names that was found, while querying the MediaStore API
List<String> directoryDictionary = new ArrayList<>();
// generalList is just a list that would represent a general image list, where all images can be found. Just like Whatsapp
List<Image> generalList = new ArrayList<>();
while (imgCursor.moveToNext()) {
long id = imgCursor.getLong(bucketId);
int size = imgCursor.getInt(imgSize);
String fileName = imgCursor.getString(name);
String folderName = imgCursor.getString(bucketName);
// As recommended by the Android developers doc
Uri contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
// a single image
Image currentImage = new Image(contentUri, size, fileName, folderName);
// add all images to the general image list, but modifying the directory name
Image genImage = new Image(contentUri, size, fileName, "All Media");
generalList.add(genImage);
int directoryIndex = CollectionUtils.linearSearch(directoryDictionary, folderName);
// if search result (directoryIndex) passes this test, then it means that there is
// no such directory in list of directory names
if (directoryIndex < 0) {
imageDirectoryList.add(new ArrayList<>());
directoryDictionary.add(folderName);
directoryIndex = CollectionUtils.linearSearch(directoryDictionary, folderName);
if (directoryIndex >= 0)
imageDirectoryList.get(directoryIndex).add(currentImage);
} else {
imageDirectoryList.get(directoryIndex).add(currentImage);
}
}
//...then add it if the image list of folder is > 2
if (imageDirectoryList.size() > 2) imageDirectoryList.add(0, generalList);
imgCursor.close();
runOnUiThread(() -> {
// imageAdapter is the RecyclerView's list Adapter.
// notifyDataSetChanged() must be call to refresh list.
imageAdapter.notifyDataSetChanged();
// doViewUpdate was just used to turn on and off the visibility of some views
doViewUpdate();
});
}
My first activity contains a ListView that's getting a list of items from a database.
And in the OnClickListener, there is a new activity that's open getting all information related to the question from the database, I'm putting them in Intent extras and getting them in the next activity:
public void onItemClick(AdapterView<?> parent, View view, int i, long l) {
String quest = (String) parent.getAdapter().getItem(i);
int pos = lst.getCheckedItemPosition();
Intent inte = new Intent(view.getContext(),Reponse.class);
cursor = db.rawQuery("SELECT * FROM "+ ModelHelper.TABLE_QUESTION + " WHERE " + ModelHelper.KEY_QUESTION + " = ? ",new String[] {quest});
if (cursor != null)
if (cursor.getCount() > 0) {
cursor.moveToFirst();
String repA = cursor.getString(cursor.getColumnIndex(ModelHelper.KEY_PROFIL_WAITEDANSWER));
String cible = cursor.getString(cursor.getColumnIndex(ModelHelper.KEY_PROFIL_CIBLE));
String plan = cursor.getString(cursor.getColumnIndex(ModelHelper.KEY_PLANACT));
int ID = cursor.getColumnIndex(ModelHelper.KEY_ID_QUESTION);
inte.putExtra("question", quest);
inte.putExtra("repo", repA);
inte.putExtra("cible", cible);
inte.putExtra("plan", plan);
// inte.putExtra("id", ID);
// inte.putExtra("pos", pos);
startActivity(inte);
finish();
In the info activity, it's simple and I could get the information from the database, here's the code
Intent intent = getIntent();
String question = intent.getStringExtra("question");
String repo = intent.getStringExtra("repo");
String cible = intent.getStringExtra("cible");
String plan = intent.getStringExtra("plan");
final int[] ID = {0};
ID[0] = intent.getIntExtra("ID", ID[0]);
int pos = 0;
pos = intent.getIntExtra("pos",pos);
txtq.setText(question);
txtr.setText(repo);
txtc.setText(cible);
txtp.setText(plan);
I want to add a button NEXT that makes appear the next item in the list by refreshing the same page without going back to the list and click again.
I tried to get the position too with the intent and tried to set a new OnClickListener in the button to set the position = position +1, but it doesn't work.
Any ideas?
First solution
for that, you have to pass the entire list to next activity so you can directly do next item in the list by refreshing the same page without going back.
Second solution.
You have to separately apply query for next info page click on the next/previous button.
You just need to go through the sql Queries
select * from table where key=primaryKey limit Page_Limit offset Start_From
You need to change limit and offset on select of list limit.
I am working on Dibs Payment integration but unable to achieve success. All things are working good in demo mode but when merchant id is supplied to it then before opening card details form it gives an error "Data has been tampered. Checksome is not valid". I dont know what is it. After my googling i found it is something related to MAC calculated but how to calculate MAC in my code. My whole class for payment is as follows with all comments.
public class CheckOut extends Activity {
private static final String TAG = "DIBS." + CheckOut.class.getSimpleName();
private DibsPayment paymentWindow;
public static String total, resname, resid, userid, menunames, itemnames,
itemquantity, ordertype, address, city, contactno, pincode,
deliverttime, orderid;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.checkout);
RandomStringGenerator randomorderid = new RandomStringGenerator();
Intent i = getIntent();
// total=String.valueOf(1);
total = i.getStringExtra("grandTotal");
resname = i.getStringExtra("res_name");
resid = i.getStringExtra("res_id");
userid = i.getStringExtra("user_id");
menunames = i.getStringExtra("menu_names");
itemnames = i.getStringExtra("item_prices");
itemquantity = i.getStringExtra("item_quantity");
ordertype = i.getStringExtra("ordertype");
address = i.getStringExtra("address");
city = i.getStringExtra("city");
contactno = i.getStringExtra("phone");
pincode = i.getStringExtra("pin");
deliverttime = i.getStringExtra("delivery_time");
orderid = randomorderid.getAlphaNumeric(5);
Toast.makeText(
getApplicationContext(),
orderid + "\n" + resname + "\n" + resid + "\n" + userid + "\n"
+ ordertype + "\n" + address + "\n" + city + "\n"
+ pincode + "\n" + contactno + "\n" + deliverttime
+ "\n" + menunames + "\n" + itemnames + "\n"
+ itemquantity + "\n" + total, Toast.LENGTH_SHORT)
.show();
/*
* Intent intent=getIntent(); String []
* arrayList=intent.getStringArrayExtra("payment_item"); // int l_itr =
* arrayList.length; // while(l_itr.hasNext()) { for(int
* i=0;i<=arrayList.length-1;i++){
*
* #SuppressWarnings("rawtypes") //HashMap l_map = (HashMap)
* l_itr.next(); String item=arrayList[i]; Log.v(item, "item"); String
* item =(String)i.get(DatabaseHandler.KEY_ITEM); Log.v(item, "item");
* String unicost= (String)l_map.get(DatabaseHandler.KEY_UNITCOST);
* Log.v(unicost, "unicost"); String l_res_name = (String)
* l_map.get(DatabaseHandler.KEY_QUANTITY); Log.v(l_res_name,
* "quantity"); String l_street = (String)
* l_map.get(DatabaseHandler.KEY_TOTAL); Log.v(l_street, "total"); }
*/
paymentWindow = (DibsPayment) findViewById(R.id.DibsPayment);
// Set your listener implementation, to get callbacks in the life-cycle
// of a payment processing
paymentWindow
.setPaymentResultListener(new MyPaymentResultListener(this));
// Load the payment window with the payment data that suits the payment
// flow you need
// Please be patient, when loading on the emulator
paymentWindow.loadPaymentWindow(constructPaymentData());
}
/**
* Shows a "cancel" action in the options menu on the phone, which shows how
* to call cancel functionality into the payment window to cancel ongoing
* payment processing.
*/
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.payment_window_menu, menu);
return true;
}
/**
* If user chose "cancel" in options menu, we call "cancel" into payment
* window.
*/
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menuitem_payment_window_cancel:
//
// Calling cancel into payment window cancels the ongoing payment
// processing.
// Because cancelling is an asynchronous process, you will need to
// wait for a callback
// to paymentCancelled on your PaymentResultListener listener,
// before being positive that
// payment window is done cancelling.
//
paymentWindow.cancelPayment();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* #return the payment data instance that is needed as input to
* {#link DibsPayment#loadPaymentWindow(dk.dibs.android.library.PaymentData)}
*/
private PaymentData constructPaymentData() {
// IMPORTANT: This needs to be set to YOUR merchant number, that you
// have obtained through an
// agreement with DIBS.
// you can use the merchant "demo" for a demorun through the payment
// window // read information about demo mode in the documentation
String merchantId = "******";
//String merchantId = "demo";
// The currency the payment is to be processed in
String currencyCode = "DKK";
// You set this to your own orderId value
String yourOrderId = orderid;
// The amount to be paid, given in "least possible unit" (aka: "oerer")
long amount = (new Double(total)).longValue();
// The cards that is allowed to be used in payment window
List<String> payTypes = new ArrayList<String>();
payTypes.add("MC");
payTypes.add("MTRO");
payTypes.add("VISA");
// this will add fee to the payment window.
boolean calcfee = true;
// In this example, we simply use "PurchasePaymentData", which is a
// simple "buy-with-credit-card" flow,
// where no pre-authorization is performed.
//
// Look to other subclasses of PaymentData for the other supported
// flows.
//
PurchasePaymentData paymentData = new PurchasePaymentData(merchantId,
currencyCode, yourOrderId, amount, payTypes);
paymentData.setCalcfee(calcfee);
// Set this flag to "true", if you want to be able to use test cards.
// REMEMBER to reset this to false, in production !!!
paymentData.setTest(true);
// If you want checks (and payment failure) if the orderId you gave
// already have been payed.
paymentData.setUseUniqueOrderIdCheck(false);
// If you want MAC security calculations, you will need to pre-calculate
// a MAC value on your server,
// based on the values you give to this payment window, and set this
// pre-calculated MAC value like this.
//
paymentData.setCalculatedMAC("");
// Payment window supports loading cancel or callback URLs based on
// payment outcome.
// Another, and maybe better, way to do this in an app, is to listen for
// the proper callbacks
// on the listener you set on the payment window, and then do your own
// cancel or payment success
// handling against your own servers.
//
try {
paymentData.setCallbackUrl(new URL(
"http://****.demoprojects.in/accept.php"));
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
paymentData.setCancelUrl(new URL("http://****.demoprojects.in/accept.php"));
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
paymentData.setLanguage("en_UK");
paymentData.setTheme(Theme.ANDROID_DIBS);
// If you need to, you can pass custom options to the payment window,
// which will be posted back again.
//
// Map<String, String> yourCustomOptions = new HashMap<String,
// String>();
// yourCustomOptions.put("foo", "bar");
// paymentData.setCustomOptions(yourCustomOptions);
return paymentData;
}
/*
* public void delete() { DatabaseHandler readDatabase = new
* DatabaseHandler( getApplicationContext()); readDatabase.deleteAll(); }
*/
}
This is the first time i am working on payment. Please help me out as security is the main concern here.
Thanks in advance :)
Dibs allow MAC to be calculated by two algorithms The choice of algorithm is up to you. It currently handles MD5 and SHA-1. SHA-1 is recommended by Dibs as it provides better security.
SHA-1(data¤cy&method&SecretKey&)
or
MD5(data¤cy&method&SecretKey&)
data here is the string containing information regarding the amount and quantity of purchase.
Security > DebiTech DefenderTM > MAC Configuration, here you will find the secret key in dibs dashboard.
Click here for further reference:
I've seen apps doing this, but I don't have a clue where to start. Any hints? Some code snippets will be appreciated.
/**
* Add a bookmark to the database.
*
* #param context
* Context of the calling Activity. This is used to make Toast confirming that the bookmark has been added. If the caller provides null, the Toast will not be shown.
* #param cr
* The ContentResolver being used to add the bookmark to the db.
* #param url
* URL of the website to be bookmarked.
* #param name
* Provided name for the bookmark.
* #param thumbnail
* A thumbnail for the bookmark.
* #param retainIcon
* Whether to retain the page's icon in the icon database. This will usually be <code>true</code> except when bookmarks are added by a settings restore agent.
*/
static void addBookmark(Context context, ContentResolver cr, String url, String name, Bitmap thumbnail, boolean retainIcon) {
final String WHERE_CLAUSE = "url = ? OR url = ? OR url = ? OR url = ?";
final String WHERE_CLAUSE_SECURE = "url = ? OR url = ?";
String[] SELECTION_ARGS;
// Want to append to the beginning of the list
long creationTime = new Date().getTime();
// First we check to see if the user has already visited this
// site. They may have bookmarked it in a different way from
// how it's stored in the database, so allow different combos
// to map to the same url.
boolean secure = false;
String compareString = url;
if (compareString.startsWith("http://")) {
compareString = compareString.substring(7);
} else if (compareString.startsWith("https://")) {
compareString = compareString.substring(8);
secure = true;
}
if (compareString.startsWith("www.")) {
compareString = compareString.substring(4);
}
if (secure) {
SELECTION_ARGS = new String[2];
SELECTION_ARGS[0] = "https://" + compareString;
SELECTION_ARGS[1] = "https://www." + compareString;
} else {
SELECTION_ARGS = new String[4];
SELECTION_ARGS[0] = compareString;
SELECTION_ARGS[1] = "www." + compareString;
SELECTION_ARGS[2] = "http://" + compareString;
SELECTION_ARGS[3] = "http://" + SELECTION_ARGS[1];
}
Cursor cursor = cr.query(Browser.BOOKMARKS_URI, Browser.HISTORY_PROJECTION, secure ? WHERE_CLAUSE_SECURE : WHERE_CLAUSE, SELECTION_ARGS, null);
ContentValues map = new ContentValues();
if (cursor.moveToFirst() && cursor.getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX) == 0) {
// This means we have been to this site but not bookmarked
// it, so convert the history item to a bookmark
map.put(Browser.BookmarkColumns.CREATED, creationTime);
map.put(Browser.BookmarkColumns.TITLE, name);
map.put(Browser.BookmarkColumns.BOOKMARK, 1);
// map.put(Browser.BookmarkColumns.THUMBNAIL, bitmapToBytes(thumbnail));
cr.update(Browser.BOOKMARKS_URI, map, "_id = " + cursor.getInt(0), null);
} else {
int count = cursor.getCount();
boolean matchedTitle = false;
for (int i = 0; i < count; i++) {
// One or more bookmarks already exist for this site.
// Check the names of each
cursor.moveToPosition(i);
if (cursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX).equals(name)) {
// The old bookmark has the same name.
// Update its creation time.
map.put(Browser.BookmarkColumns.CREATED, creationTime);
cr.update(Browser.BOOKMARKS_URI, map, "_id = " + cursor.getInt(0), null);
matchedTitle = true;
break;
}
}
if (!matchedTitle) {
// Adding a bookmark for a site the user has visited,
// or a new bookmark (with a different name) for a site
// the user has visited
map.put(Browser.BookmarkColumns.TITLE, name);
map.put(Browser.BookmarkColumns.URL, url);
map.put(Browser.BookmarkColumns.CREATED, creationTime);
map.put(Browser.BookmarkColumns.BOOKMARK, 1);
map.put(Browser.BookmarkColumns.DATE, 0);
// map.put(Browser.BookmarkColumns.THUMBNAIL, bitmapToBytes(thumbnail));
int visits = 0;
if (count > 0) {
// The user has already bookmarked, and possibly
// visited this site. However, they are creating
// a new bookmark with the same url but a different
// name. The new bookmark should have the same
// number of visits as the already created bookmark.
visits = cursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX);
}
// Bookmark starts with 3 extra visits so that it will
// bubble up in the most visited and goto search box
map.put(Browser.BookmarkColumns.VISITS, visits + 3);
cr.insert(Browser.BOOKMARKS_URI, map);
}
}
if (retainIcon) {
WebIconDatabase.getInstance().retainIconForPageUrl(url);
}
cursor.close();
}
You can see the OS code here
I've been using the following code to remove an item from a playlist in my Android app:
private void removeFromPlaylist(long playlistId, int loc)
{
ContentResolver resolver = getApplicationContext().getContentResolver();
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
resolver.delete(uri, MediaStore.Audio.Playlists.Members.PLAY_ORDER+" = "+loc, null);
for(int i=loc+1;i<playSongIDs.length;i++) {
MediaStore.Audio.Playlists.Members.moveItem(resolver,playlistId,i, i-1);
}
}
I'm currently using the Android 2.2 library and this is the only thing that I need to change to use Android 2.1. Is there an alternate method for removing an item from a playlist and adjusting the order of the items after the deleted one?
looking at the code of the MediaStore we came out with this solution that seems to work fine:
/**
* Convenience method to move a playlist item to a new location
* #param res The content resolver to use
* #param playlistId The numeric id of the playlist
* #param from The position of the item to move
* #param to The position to move the item to
* #return true on success
*/
private boolean moveItem(ContentResolver res, long playlistId, int from, int to) {
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
playlistId)
.buildUpon()
.appendEncodedPath(String.valueOf(from))
.appendQueryParameter("move", "true")
.build();
ContentValues values = new ContentValues();
values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, to);
return res.update(uri, values, null, null) != 0;
}