5

I'm writing a live wallpaper that creates an effect over a background. I want the user to be able to choose the background from any of the system wallpapers and camera photos. What I would like is for the user to be able to press a button in the Settings menu, have the list of options show up just like setting the wallpaper from the home screen, minus the Live Wallpapers options. Once the user navigates the choices and picks an actually image I would load it to my canvas.

How do I do this?

I can't find an API anywhere for getting a list of wallpapers.

I've been able to come up with a list of wallpaper providers using Intents. I then get a list of live wallpaper providers also using Intents and remove those from my first list. The gives me a list of wallpaper providers that are not live.

Now what? Are there other ways to do this that I'm missing?

Please help.

Josh Lee
  • 171,072
  • 38
  • 269
  • 275
DarthNoodles
  • 370
  • 3
  • 11

2 Answers2

25

I do this by putting a preference into the Settings xml as this (mine is flash_setting.xml);

<Preference
    android:key="image_custom"
    android:title="Choose Background"
    android:summary="Select a Custom Image"
     />

I created a custom class to take the get the OnPreferenceClick Listener and watch for the user clicking the preference as so (this is call mySettings.java) (please note that the getRealPathFromURI routine isn't mine and was found elsewhere on here);


Your class should start by extending the PreferenceActivity and implementing the Sharedpreference change listener

public class flashSettings extends PreferenceActivityimplements SharedPreferences.OnSharedPreferenceChangeListener {    

Link to the preference name and register the listener

@Override
protected void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    getPreferenceManager().setSharedPreferencesName(
            fingerflashpro.SHARED_PREFS_NAME);
    addPreferencesFromResource(R.xml.flash_settings);      getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(
            this);

Next we will set the on preference listener to listen for 'image_custom'. When it is clicked we will start a new intent to display the photo picker. We start with a StartActivityForResult so we can get the URI of the image back from the intent.

getPreferenceManager().findPreference("image_custom").setOnPreferenceClickListener(new OnPreferenceClickListener()
{
    @Override
    public boolean onPreferenceClick(Preference preference)
    {
        Display display = getWindowManager().getDefaultDisplay(); 
        int width = display.getWidth();
        int height = display.getHeight();
        Toast.makeText(getBaseContext(), "Select Image - " + (width) + " x " + height , Toast.LENGTH_LONG).show(); 
        Intent photoPickerIntent = new Intent(Intent.ACTION_PICK); 
        photoPickerIntent.setType("image/*");
        startActivityForResult(photoPickerIntent, 1);
        return true;
    }
});}

Next we wait for the activity to return the result and we parse the URI to a real path.

@Override 
public void onActivityResult(int requestCode, int resultCode, Intent data) { 
super.onActivityResult(requestCode, resultCode, data); 
if (requestCode == 1) {
if (resultCode == Activity.RESULT_OK) { 
  Uri selectedImage = data.getData();   
  String RealPath;
  SharedPreferences customSharedPreference = getSharedPreferences(fingerflashpro.SHARED_PREFS_NAME, Context.MODE_PRIVATE); 
  SharedPreferences.Editor editor = customSharedPreference.edit ();
  RealPath = getRealPathFromURI (selectedImage);
  editor.putString("image_custom", RealPath); 
  editor.commit(); 
}}

The following piece of code was found on this site (by PercyPercy on this thread)and I'm only including it for completeness. It does however, work perfectly.

public String getRealPathFromURI(Uri contentUri) {          
String [] proj={MediaColumns.DATA};  
Cursor cursor = managedQuery( contentUri,  
        proj, // Which columns to return  
        null,       // WHERE clause; which rows to return (all rows)  
        null,       // WHERE clause selection arguments (none)  
        null); // Order-by clause (ascending by name)  
int column_index = cursor.getColumnIndexOrThrow(MediaColumns.DATA);  
cursor.moveToFirst();  
return cursor.getString(column_index);}

Make sure we implement the required Overrides;

@Override
protected void onResume() {
    super.onResume();
}

@Override
protected void onDestroy() {
    getPreferenceManager().getSharedPreferences().
       unregisterOnSharedPreferenceChangeListener(this);
    super.onDestroy();
}

@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
        String key) {
}}

Then in your main wallpaper service activity you can extract the Path of the image from your shared preferences.

@Override
    public void onSharedPreferenceChanged(SharedPreferences prefs,
            String key) {

        imageBg = prefs.getString("image_custom", "Bad Image");
            getBackground();}

Here is a fairly crude routine to load the image. I've attempted to put in some error trapping just in case the file is deleted, renamed or the SD card gets mounted (hence you lose your image). I've also attempted to put in some crude checks for the device orientation. I'm sure you can do better.

There also some Samplesize checking so you don't exceed the VM budget. This is the most important part of this code to prevent force closes and should definitely be included.

I also call this routine when the orientation of the phone is changed so that the background gets resized each time.

void getBackground() { 
        if (this.cvwidth == 0 || this.cvheight == 0 || this.visibleWidth == 0) {
               this.cvwidth = 480;
               this.cvheight = 854;
               this.visibleWidth = 480;}
        if(new File(imageBg).exists()) {
                int SampleSize = 1;
             do {
                 BitmapFactory.Options options = new BitmapFactory.Options();
                 options.inJustDecodeBounds = true;
                 bg = BitmapFactory.decodeFile(imageBg, options);
                SampleSize = (int) (Math.ceil(options.outWidth/(this.visibleWidth * 2))*2);
                options.inJustDecodeBounds = false;
                 try {options.inSampleSize = SampleSize;
                     bg = BitmapFactory.decodeFile(imageBg, options);}
                    catch (OutOfMemoryError e) {
                        SampleSize = SampleSize * 2;
                        }
                } while (bg == null);

           bg = Bitmap.createScaledBitmap(bg, this.cvwidth/2, this.cvheight, true);}
        else {bg = BitmapFactory.decodeResource(getResources(), R.drawable.bg);
            bg = Bitmap.createScaledBitmap(bg, this.cvwidth/2, this.cvheight, true);}
        LoadText = "";
    } 

I hope this helps. It took me an absolute age to come up with all of this and I know there are still some areas I can refine but at least it should get you going.

If anyone has suggestions on making this code better then I'm all ears.

Community
  • 1
  • 1
Dean
  • 533
  • 2
  • 5
  • 10
  • Does this provide the same list of wallpapers that the user sees from the home screen? That is what I want. The user needs to be able to pick from the system wallpapers and wallpapers provided by other apps, without the live wallpapers. – DarthNoodles Sep 14 '10 at 13:06
  • This lets you browse every image on your SD card. If you download the free Droid X Livewallpaper by wabbittdevs, this is exactly what you'll experience. That is where the code above is taken from. – Dean Sep 14 '10 at 13:54
  • I downloaded and checked it. That is not what I want. At the home screen on your device, press 'menu' and select 'wallpaper'. This is the list that I want the user to have, minus the Live Wallpapers. On my device I get Live Wallpaper, Photos and HTC Wallpapers. The emulator gives you Live Wallpaper, Photos and Wallpapers, plus a "Wallpaper" entry provided by the Home sample. These are the activities that satisfy the intent ACTION_SET_WALLPAPER. It appears that what I want can't easily be done with the API as it is. – DarthNoodles Sep 14 '10 at 14:12
  • Ah. I see what you are wanting now. I've never tackled the need to do that before so sorry, can't help. – Dean Sep 14 '10 at 14:32
  • I needed to do this and I must say your answer is superb – justinhj Aug 25 '11 at 05:17
  • You don't close your db cursor. – Blundell Mar 19 '12 at 16:13
  • Great answer, I just have a quick question: Can I reference/use the image they pick if I'm using mTextureManager.addTexture(texture) in my renderer? I'm using BitmapFactory.decodeResource(getResource(), R.drawable.image) Could I use "imageBg" in place of R.drawable.image? – Steve C. Apr 10 '13 at 06:26
0

Nope, that's pretty much the best way to go about it. Anything that responds to android.intent.action.SET_WALLPAPER would be a wallpaper provider of one form or another. The problem is that giving them that list will take your control out of the equation. Essentially having them pick something from another provider will unset your Live Wallpaper. You could probably get around this with with some creative Manifest settings and or some "startActivityForResult" type calls.

After they choose something though it's a simple matter of something like this in your WallpaperService.Engine:

@Override
public void onSurfaceChanged( SurfaceHolder holder, int format, int width, int height )
{
    super.onSurfaceChanged( holder, format, width, height );
    if( isVisible() )
    {
    mSurfaceHolder = holder;
    final Drawable drawable = WallpaperManager.getInstance( getBaseContext() ).getFastDrawable();
    mSurfaceHolder.setFormat( RenderThread.pixFormat );
    Canvas c = mSurfaceHolder.lockCanvas();
    if( c != null )
        {
            drawable.draw( c );
            mSurfaceHolder.unlockCanvasAndPost( c );
        }
    }
}
Justin Buser
  • 2,813
  • 25
  • 32