summaryrefslogtreecommitdiff
path: root/app/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/com/javispedro/wallmotion/MainActivity.java117
-rw-r--r--app/src/main/java/com/javispedro/wallmotion/Renderer.java183
-rw-r--r--app/src/main/java/com/javispedro/wallmotion/SettingsActivity.java174
-rw-r--r--app/src/main/java/com/javispedro/wallmotion/WallService.java65
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();
+ }
+ }
+ }
+}