📜 ⬆️ ⬇️

Daydream: Interactive screensaver for Android 4.2

The new version of Android has the opportunity to create interactive screensavers. When the device is in standby mode or charging, it can display photos from the gallery, data from the Internet (news, photos, and so on) or just an animation. To activate the screensaver, you need in the settings, in the Display category, select a screensaver and specify when to display it, for example, while charging the device.

Daydream architecture



Each saver implementation is a subclass of android.service.dreams.DreamService. When you create a DreamService, you get access to an API similar to the Activity Lifecycle API. Basic DreamService methods for overriding in a subclass (do not forget to call in the implementation of the superclass):

Important DreamService methods that you can call:

To create a splash screen, you need to create a service in the manifest file:
 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.app"> <uses-sdk android:targetSdkVersion="17" android:minSdkVersion="17" /> <application> <service android:name=".TestDaydream" android:exported="true" android:label="@string/my_daydream_name"> <intent-filter> <action android:name="android.service.dreams.DreamService" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <meta-data android:name="android.service.dream" android:resource="@xml/dream_info" /> </service> </application> </manifest> 

The <meta-data> optional. It allows you to point to an XML resource in which the parameters specific to Daydream are described. The user has access to them, for this he must click on the settings icon next to the name of the screen saver.
 <!-- res/xml/dream_info.xml --> <?xml version="1.0" encoding="utf-8"?> <dream xmlns:android="http://schemas.android.com/apk/res/android" android:settingsActivity="com.example.app/.ExampleDreamSettingsActivity" /> 

The following code creates a classic screensaver with a jumping logo. Animation is implemented using TimeAnimator, which gives the effect of smooth animation.

')
 public class TestDreamer extends DreamService { @Override public void onDreamingStarted() { super.onDreamingStarted(); // Our content view will take care of animating its children. final Bouncer bouncer = new Bouncer(this); bouncer.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); bouncer.setSpeed(200); // pixels/sec // Add some views that will be bounced around. // Here I'm using ImageViews but they could be any kind of // View or ViewGroup, constructed in Java or inflated from // resources. for (int i=0; i<5; i++) { final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); final ImageView image = new ImageView(this); image.setImageResource(R.drawable.android); image.setBackgroundColor(0xFF004000); bouncer.addView(image, lp); } setContentView(bouncer); } } public class Bouncer extends FrameLayout implements TimeAnimator.TimeListener { private float mMaxSpeed; private final TimeAnimator mAnimator; private int mWidth, mHeight; public Bouncer(Context context) { this(context, null); } public Bouncer(Context context, AttributeSet attrs) { this(context, attrs, 0); } public Bouncer(Context context, AttributeSet attrs, int flags) { super(context, attrs, flags); mAnimator = new TimeAnimator(); mAnimator.setTimeListener(this); } /** * Start the bouncing as soon as we're on screen. */ @Override public void onAttachedToWindow() { super.onAttachedToWindow(); mAnimator.start(); } /** * Stop animations when the view hierarchy is torn down. */ @Override public void onDetachedFromWindow() { mAnimator.cancel(); super.onDetachedFromWindow(); } /** * Whenever a view is added, place it randomly. */ @Override public void addView(View v, ViewGroup.LayoutParams lp) { super.addView(v, lp); setupView(v); } /** * Reposition all children when the container size changes. */ @Override protected void onSizeChanged (int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; for (int i=0; i<getChildCount(); i++) { setupView(getChildAt(i)); } } /** * Bouncing view setup: random placement, random velocity. */ private void setupView(View v) { final PointF p = new PointF(); final float a = (float) (Math.random()*360); px = mMaxSpeed * (float)(Math.cos(a)); py = mMaxSpeed * (float)(Math.sin(a)); v.setTag(p); v.setX((float) (Math.random() * (mWidth - v.getWidth()))); v.setY((float) (Math.random() * (mHeight - v.getHeight()))); } /** * Every TimeAnimator frame, nudge each bouncing view along. */ public void onTimeUpdate(TimeAnimator animation, long elapsed, long dt_ms) { final float dt = dt_ms / 1000f; // seconds for (int i=0; i<getChildCount(); i++) { final View view = getChildAt(i); final PointF v = (PointF) view.getTag(); // step view for velocity * time view.setX(view.getX() + vx * dt); view.setY(view.getY() + vy * dt); // handle reflections final float l = view.getX(); final float t = view.getY(); final float r = l + view.getWidth(); final float b = t + view.getHeight(); boolean flipX = false, flipY = false; if (r > mWidth) { view.setX(view.getX() - 2 * (r - mWidth)); flipX = true; } else if (l < 0) { view.setX(-l); flipX = true; } if (b > mHeight) { view.setY(view.getY() - 2 * (b - mHeight)); flipY = true; } else if (t < 0) { view.setY(-t); flipY = true; } if (flipX) vx *= -1; if (flipY) vy *= -1; } } public void setSpeed(float s) { mMaxSpeed = s; } } 

Let's try to create something with their own hands.


Let's create a new project with the minimum SDK version - 17. Edit AndroidManifest.xml, telling it that we want to make a splash screen.
 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.habrdreamer" > <!-- uses-permission android:name="android.permission.WRITE_SETTINGS" --> <uses-permission android:name="android.permission.INTERNET" /> <uses-sdk android:minSdkVersion="17" android:maxSdkVersion="17" android:targetSdkVersion="17"/> <application android:label="HabrDreamer"> <service android:name="ScreensaverHabr" android:exported="true" android:label="ScreensaverHabr"> <intent-filter> <action android:name="android.service.dreams.DreamService" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service> <activity android:name="SetURL" android:label="Dream URL" android:theme="@android:style/Theme.Translucent.NoTitleBar"> <intent-filter> <action android:name="android.intent.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /> </intent-filter> </activity> <activity android:name="SetURLInteractive" android:label="Dream URL (Interactive)" android:theme="@android:style/Theme.Translucent.NoTitleBar"> <intent-filter> <action android:name="android.intent.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /> </intent-filter> </activity> </application> </manifest> 

The main class of our screensaver will look like this:
 package com.example.habrdreamer; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.graphics.PorterDuff; import android.net.Uri; import android.os.BatteryManager; import android.os.Handler; import android.provider.Settings; import android.service.dreams.DreamService; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.widget.TextView; public class ScreensaverHabr extends DreamService { private class LinkLauncher extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); finish(); return true; } } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); setContentView(R.layout.screensaver_habr); setFullscreen(true); final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); final String url = prefs.getString("url", "http://habrahabr.ru"); final boolean interactive = prefs.getBoolean("interactive", false); Log.v("WebViewDream", String.format("loading %s in %s mode", url, interactive ? "interactive" : "noninteractive")); setInteractive(interactive); WebView webview = (WebView) findViewById(R.id.webview); webview.setWebViewClient(new LinkLauncher()); WebSettings webSettings = webview.getSettings(); webSettings.setJavaScriptEnabled(true); webview.loadUrl(url); } } 

We also need to add two classes. The first will set the URL of the page that appears in our screensaver. Second, do the same thing as the first plus to make our screensaver interactive.
So, the first class is SetURL.java:
 package com.example.habrdreamer; import android.util.Log; import android.content.SharedPreferences.Editor; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.widget.Toast; import android.os.Bundle; import android.app.Activity; import android.content.Intent; import android.widget.Toast; public class SetURL extends Activity { @Override public void onCreate(Bundle stuff) { super.onCreate(stuff); final Intent intent = getIntent(); final String action = intent.getAction(); String url = intent.getStringExtra(Intent.EXTRA_TEXT); if (url == null) { finish(); } else if (Intent.ACTION_SEND.equals(action)) { set(url); finish(); } } protected void set(String url) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); final Editor editor = prefs.edit(); editor.putString("url", url); editor.putBoolean("interactive", false); editor.commit(); Toast.makeText(this, "WebView dream URL set to: " + url, Toast.LENGTH_SHORT).show(); } } 

And the second is SetURLInteractive.java:
 package com.example.habrdreamer; import android.util.Log; import android.content.SharedPreferences.Editor; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.widget.Toast; import android.os.Bundle; import android.app.Activity; import android.content.Intent; import android.widget.Toast; public class SetURLInteractive extends Activity { @Override public void onCreate(Bundle stuff) { super.onCreate(stuff); final Intent intent = getIntent(); final String action = intent.getAction(); String url = intent.getStringExtra(Intent.EXTRA_TEXT); if (url == null) { finish(); } else if (Intent.ACTION_SEND.equals(action)) { set(url); finish(); } } protected void set(String url) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); final Editor editor = prefs.edit(); editor.putString("url", url); editor.putBoolean("interactive", true); editor.commit(); Toast.makeText(this, "WebView dream URL set to: " + url, Toast.LENGTH_SHORT).show(); } } 

The style file, in this case, contains nothing but WebView:
 <?xml version="1.0" encoding="utf-8"?> <WebView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/webview" android:layout_width="match_parent" android:layout_height="match_parent" > </WebView> 

The final touch is to create the default.html html file in the assets folder. We will write a welcome message and small instructions in it.
 <html> <head> <style> body { background-color: black; color: gray; font-size: 16pt; font-family: "Roboto-Light", Roboto, sans-serif; text-align: center; } #text { margin-left: auto; margin-right: auto; margin-top: 96pt; } </style> </head> <body> <div id="text">     “  (Share)”,       . </div> </body> </html> 

That's basically all, you can compile and test.

Useful links:



In the next article, I plan to talk about creating screensavers using libGDX.

Source: https://habr.com/ru/post/165731/


All Articles