diff options
Diffstat (limited to 'app/src/main/java/com/javispedro')
4 files changed, 539 insertions, 0 deletions
diff --git a/app/src/main/java/com/javispedro/wallmotion/MainActivity.java b/app/src/main/java/com/javispedro/wallmotion/MainActivity.java new file mode 100644 index 0000000..c6859ae --- /dev/null +++ b/app/src/main/java/com/javispedro/wallmotion/MainActivity.java @@ -0,0 +1,117 @@ +package com.javispedro.wallmotion; + +import android.app.WallpaperManager; +import android.content.ComponentName; +import android.content.Intent; +import android.os.Bundle; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; + +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.Menu; +import android.view.MenuItem; + +public class MainActivity extends AppCompatActivity { + private static final String TAG = "MainActivity"; + + private Renderer renderer; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + FloatingActionButton fab = findViewById(R.id.set_wallpaper); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + openWallpaperSelector(); + } + }); + + SurfaceView view = findViewById(R.id.wallpaper_view); + view.getHolder().addCallback(new WallpaperViewCallback()); + + renderer = new Renderer(this); + } + + @Override + protected void onDestroy() { + renderer.stop(); + renderer = null; + + super.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + if (id == R.id.action_settings) { + openSettingsActivity(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onPause() { + renderer.stop(); + super.onPause(); + } + + private class WallpaperViewCallback implements SurfaceHolder.Callback { + @Override + public void surfaceCreated(SurfaceHolder holder) { + Log.d(TAG, "surfaceCreated"); + renderer.start(holder.getSurface()); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.d(TAG, "surfaceDestroyed"); + renderer.stop(); + } + } + + private void openSettingsActivity() { + Intent intent = new Intent(this, SettingsActivity.class); + startActivity(intent); + } + + private void openWallpaperSelector() { + Intent intent = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER); + intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, + new ComponentName(this, WallService.class)); + startActivity(intent); + } +} diff --git a/app/src/main/java/com/javispedro/wallmotion/Renderer.java b/app/src/main/java/com/javispedro/wallmotion/Renderer.java new file mode 100644 index 0000000..6730afe --- /dev/null +++ b/app/src/main/java/com/javispedro/wallmotion/Renderer.java @@ -0,0 +1,183 @@ +package com.javispedro.wallmotion; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.SharedPreferences; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.Build; +import android.text.TextUtils; +import android.util.Log; +import android.view.Surface; + +import androidx.preference.PreferenceManager; + +import java.io.IOException; + +public class Renderer extends ContextWrapper { + private static final String TAG = "Renderer"; + + private SharedPreferences prefs; + private MediaPlayer player; + + private int savedPosition; + + public Renderer(Context context) { + super(context); + + prefs = PreferenceManager.getDefaultSharedPreferences(context); + prefs.registerOnSharedPreferenceChangeListener(new PrefsListener()); + } + + public boolean isActive() { + return player != null; + } + + public void start(Surface surface) { + preparePlayer(surface); + } + + public void stop() { + if (player != null && player.isPlaying() && shouldSaveRestorePosition()) { + savedPosition = player.getCurrentPosition(); + Log.d(TAG, "storing current position: " + savedPosition + " ms"); + } + releasePlayer(); + } + + public void reset() { + releasePlayer(); + savedPosition = 0; + } + + private class PrefsListener implements SharedPreferences.OnSharedPreferenceChangeListener { + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equals(getString(R.string.settings_video_file_key))) { + Log.d(TAG, "video file key changed"); + reset(); + } else if (key.equals(getString(R.string.settings_display_restart_key))) { + savedPosition = 0; + } + } + } + + private class PlayerListener implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnSeekCompleteListener { + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + Log.e(TAG, "MediaPlayer error: " + what + " extra:" + extra); + return false; + } + + @Override + public void onPrepared(MediaPlayer mp) { + Log.d(TAG, "onPrepared"); + if (player != null && !player.isPlaying()) { + Log.d(TAG, "start playing"); + if (savedPosition > 0 && shouldSaveRestorePosition()) { + restorePlayerPosition(); + } + player.start(); + } + } + + @Override + public void onSeekComplete(MediaPlayer mp) { + Log.d(TAG, "onSeekComplete: " + mp.getCurrentPosition() + " ms"); + } + } + + private void preparePlayer(Surface surface) { + releasePlayer(); + + int scaling = getVideoScaling(); + Log.d(TAG, "video scaling mode: " + scaling); + + Uri uri = getVideoFileUri(); + if (uri == null) { + Log.d(TAG, "no video URI: using missing video URI"); + uri = getResourceVideoFileUri(R.raw.video_file_missing); + scaling = MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT; + } + + try { + preparePlayer(surface, uri, scaling); + } catch (Exception ex) { + Log.e(TAG, "Could not open video URI: " + uri.toString() + " : " + ex.toString()); + ex.printStackTrace(); + releasePlayer(); + + uri = getResourceVideoFileUri(R.raw.video_file_error); + scaling = MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT; + + try { + preparePlayer(surface, uri, scaling); + } catch (Exception ex2) { + Log.e(TAG, "Could not open error video URI either: " + ex2.toString()); + ex2.printStackTrace(); + releasePlayer(); + } + } + } + + private void preparePlayer(Surface surface, Uri uri, int scaling) throws IOException { + Log.d(TAG, "creating player"); + player = new MediaPlayer(); + PlayerListener listener = new PlayerListener(); + player.setOnErrorListener(listener); + player.setOnPreparedListener(listener); + player.setOnSeekCompleteListener(listener); + player.setLooping(true); + player.setVolume(0, 0); + player.setSurface(surface); + Log.d(TAG, "setting data source to " + uri.toString()); + player.setDataSource(this, uri); + player.setVideoScalingMode(scaling); + player.prepareAsync(); + } + + private void releasePlayer() { + if (player != null) { + Log.d(TAG, "releasing player"); + player.release(); + player = null; + } + } + + private void restorePlayerPosition() { + Log.d(TAG, "seeking to " + savedPosition + " ms"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + player.seekTo(savedPosition, MediaPlayer.SEEK_CLOSEST); + } else { + player.seekTo(savedPosition); + } + } + + private Uri getVideoFileUri() { + String new_value = prefs.getString(getString(R.string.settings_video_file_key), null); + if (new_value != null) { + return Uri.parse(new_value); + } else { + return null; + } + } + + private Uri getResourceVideoFileUri(int resId) { + Uri.Builder builder = new Uri.Builder(); + builder.scheme("android.resource").authority(getPackageName()).appendPath(Integer.toString(resId)); + return builder.build(); + } + + private int getVideoScaling() { + String value = prefs.getString(getString(R.string.settings_display_scale_key), null); + if (TextUtils.equals(value, getString(R.string.settings_display_scale_to_fit_with_cropping_key))) { + return MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING; + } else { + return MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT; + } + } + + private boolean shouldSaveRestorePosition() { + return !prefs.getBoolean(getString(R.string.settings_display_restart_key), false); + } +} diff --git a/app/src/main/java/com/javispedro/wallmotion/SettingsActivity.java b/app/src/main/java/com/javispedro/wallmotion/SettingsActivity.java new file mode 100644 index 0000000..6919ea1 --- /dev/null +++ b/app/src/main/java/com/javispedro/wallmotion/SettingsActivity.java @@ -0,0 +1,174 @@ +package com.javispedro.wallmotion; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.OpenableColumns; +import android.text.TextUtils; +import android.util.Log; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; + +public class SettingsActivity extends AppCompatActivity { + private static final String TAG = "SettingsActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.settings_activity); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.settings, new SettingsFragment()) + .commit(); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + } + + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; + } + + public static class SettingsFragment extends PreferenceFragmentCompat { + private final static int PICK_VIDEO_FILE = 1; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.root_preferences, rootKey); + Preference video_file = findPreference(getString(R.string.settings_video_file_key)); + video_file.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("video/*"); + intent.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + startActivityForResult(intent, PICK_VIDEO_FILE); + return true; + } + }); + video_file.setSummary(getVideoFileSummary()); + Preference video_file_clear = findPreference(getString(R.string.settings_video_file_clear_key)); + video_file_clear.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + setVideoFile(null); + return true; + } + }); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, + Intent resultData) { + switch (requestCode) { + case PICK_VIDEO_FILE: + if (resultCode == Activity.RESULT_OK && resultData != null) { + Uri uri = resultData.getData(); + setVideoFile(uri); + } + break; + } + } + + private void setVideoFile(Uri uri) { + ContentResolver resolver = getContext().getContentResolver(); + + // First, release any permission request on the current uri + String cur_value = getStringPref(getString(R.string.settings_video_file_key)); + if (!TextUtils.isEmpty(cur_value)) { + Uri cur_uri = Uri.parse(cur_value); + Log.d(TAG, "release persistable uri permission on uri: " + cur_uri.toString()); + try { + resolver.releasePersistableUriPermission(cur_uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (Exception ex) { + Log.w(TAG, "could not release persistable uri permission on uri: " + cur_uri.toString()); + ex.printStackTrace(); + } + } + + // Then, store the new setting + if (uri != null) { + Log.d(TAG, "storing video_file pref uri: " + uri.toString()); + setStringPref(getString(R.string.settings_video_file_key), uri.toString()); + + // Take a persistent permission on it + Log.d(TAG, "take persistable uri permission on uri: " + uri.toString()); + resolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } else { + Log.d(TAG, "clearing video_file pref uri"); + clearPref(getString(R.string.settings_video_file_key)); + } + + // Refresh the UI summary + Preference video_file = findPreference(getString(R.string.settings_video_file_key)); + video_file.setSummary(getVideoFileSummary()); + } + + private String getVideoFileSummary() { + String cur_value = getStringPref(getString(R.string.settings_video_file_key)); + if (!TextUtils.isEmpty(cur_value)) { + ContentResolver resolver = getContext().getContentResolver(); + Uri uri = Uri.parse(cur_value); + String[] projection = {OpenableColumns.DISPLAY_NAME}; + try { + Cursor cursor = resolver.query(uri, projection, null, null, null); + if (cursor != null) { + int col = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + if (col >= 0 && cursor.moveToFirst()) { + String name = cursor.getString(col); + cursor.close(); + Log.d(TAG, "got video file display_name: " + name); + return name; + } else { + Log.w(TAG, "Could not navigate cursor for URI: " + uri.toString()); + } + cursor.close(); + } else { + Log.w(TAG, "Could not get cursor for URI: " + uri.toString()); + } + } catch (java.lang.SecurityException ex) { + ex.printStackTrace(); + Log.w(TAG, "Security exception while reading URI: " + uri.toString()); + } + return uri.getLastPathSegment(); + } + return getString(R.string.settings_not_set); + } + + protected String getStringPref(String pref) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + return prefs.getString(pref, null); + } + + protected void setStringPref(String pref, String new_value) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + String cur_value = prefs.getString(pref, null); + if (!TextUtils.equals(cur_value, new_value)) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(pref, new_value); + editor.apply(); + } + } + + protected void clearPref(String pref) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + if (prefs.contains(pref)) { + SharedPreferences.Editor editor = prefs.edit(); + editor.remove(pref); + editor.apply(); + } + } + } +}
\ No newline at end of file diff --git a/app/src/main/java/com/javispedro/wallmotion/WallService.java b/app/src/main/java/com/javispedro/wallmotion/WallService.java new file mode 100644 index 0000000..6a004ff --- /dev/null +++ b/app/src/main/java/com/javispedro/wallmotion/WallService.java @@ -0,0 +1,65 @@ +package com.javispedro.wallmotion; + +import android.service.wallpaper.WallpaperService; +import android.util.Log; +import android.view.SurfaceHolder; + +public class WallService extends WallpaperService { + private static final String TAG = "WallService"; + + @Override + public Engine onCreateEngine() { + return new WallpaperEngine(); + } + + private class WallpaperEngine extends Engine { + private static final String TAG = "WallpaperEngine"; + + private Renderer renderer; + + @Override + public void onCreate(SurfaceHolder surfaceHolder) { + Log.d(TAG, "onCreate"); + renderer = new Renderer(WallService.this); + } + + @Override + public void onDestroy() { + Log.d(TAG, "onDestroy"); + renderer.stop(); + renderer = null; + } + + @Override + public void onSurfaceDestroyed(SurfaceHolder holder) { + Log.d(TAG, "onSurfaceDestroyed"); + renderer.stop(); + } + + @Override + public void onSurfaceCreated(SurfaceHolder holder) { + Log.d(TAG, "onSurfaceCreated"); + } + + @Override + public void onSurfaceChanged(SurfaceHolder holder, int format, + int width, int height) { + Log.d(TAG, "onSurfaceChanged"); + } + + @Override + public void onSurfaceRedrawNeeded(SurfaceHolder holder) { + Log.d(TAG, "onSurfaceRedrawNeeded"); + } + + @Override + public void onVisibilityChanged(boolean visible) { + Log.d(TAG, "onVisibilityChanged(visible: " + visible + ")"); + if (visible) { + renderer.start(getSurfaceHolder().getSurface()); + } else { + renderer.stop(); + } + } + } +} |