Java tutorial
/* * Copyright (c) NoticeDog 2017. * GNU LESSER GENERAL PUBLIC LICENSE * Version 3, 29 June 2007 * * Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> * Everyone is permitted to copy and distribute verbatim copies * of this license document, but changing it is not allowed. * * * This version of the GNU Lesser General Public License incorporates * the terms and conditions of version 3 of the GNU General Public * License, supplemented by the additional permissions listed below. * * 0. Additional Definitions. * * As used herein, "this License" refers to version 3 of the GNU Lesser * General Public License, and the "GNU GPL" refers to version 3 of the GNU * General Public License. * * "The Library" refers to a covered work governed by this License, * other than an Application or a Combined Work as defined below. * * An "Application" is any work that makes use of an interface provided * by the Library, but which is not otherwise based on the Library. * Defining a subclass of a class defined by the Library is deemed a mode * of using an interface provided by the Library. * * A "Combined Work" is a work produced by combining or linking an * Application with the Library. The particular version of the Library * with which the Combined Work was made is also called the "Linked * Version". * * The "Minimal Corresponding Source" for a Combined Work means the * Corresponding Source for the Combined Work, excluding any source code * for portions of the Combined Work that, considered in isolation, are * based on the Application, and not on the Linked Version. * * The "Corresponding Application Code" for a Combined Work means the * object code and/or source code for the Application, including any data * and utility programs needed for reproducing the Combined Work from the * Application, but excluding the System Libraries of the Combined Work. * * 1. Exception to Section 3 of the GNU GPL. * * You may convey a covered work under sections 3 and 4 of this License * without being bound by section 3 of the GNU GPL. * * 2. Conveying Modified Versions. * * If you modify a copy of the Library, and, in your modifications, a * facility refers to a function or data to be supplied by an Application * that uses the facility (other than as an argument passed when the * facility is invoked), then you may convey a copy of the modified * version: * * a) under this License, provided that you make a good faith effort to * ensure that, in the event an Application does not supply the * function or data, the facility still operates, and performs * whatever part of its purpose remains meaningful, or * * b) under the GNU GPL, with none of the additional permissions of * this License applicable to that copy. * * 3. Object Code Incorporating Material from Library Header Files. * * The object code form of an Application may incorporate material from * a header file that is part of the Library. You may convey such object * code under terms of your choice, provided that, if the incorporated * material is not limited to numerical parameters, data structure * layouts and accessors, or small macros, inline functions and templates * (ten or fewer lines in length), you do both of the following: * * a) Give prominent notice with each copy of the object code that the * Library is used in it and that the Library and its use are * covered by this License. * * b) Accompany the object code with a copy of the GNU GPL and this license * document. * * 4. Combined Works. * * You may convey a Combined Work under terms of your choice that, * taken together, effectively do not restrict modification of the * portions of the Library contained in the Combined Work and reverse * engineering for debugging such modifications, if you also do each of * the following: * * a) Give prominent notice with each copy of the Combined Work that * the Library is used in it and that the Library and its use are * covered by this License. * * b) Accompany the Combined Work with a copy of the GNU GPL and this license * document. * * c) For a Combined Work that displays copyright notices during * execution, include the copyright notice for the Library among * these notices, as well as a reference directing the user to the * copies of the GNU GPL and this license document. * * d) Do one of the following: * * 0) Convey the Minimal Corresponding Source under the terms of this * License, and the Corresponding Application Code in a form * suitable for, and under terms that permit, the user to * recombine or relink the Application with a modified version of * the Linked Version to produce a modified Combined Work, in the * manner specified by section 6 of the GNU GPL for conveying * Corresponding Source. * * 1) Use a suitable shared library mechanism for linking with the * Library. A suitable mechanism is one that (a) uses at run time * a copy of the Library already present on the user's computer * system, and (b) will operate properly with a modified version * of the Library that is interface-compatible with the Linked * Version. * * e) Provide Installation Information, but only if you would otherwise * be required to provide such information under section 6 of the * GNU GPL, and only to the extent that such information is * necessary to install and execute a modified version of the * Combined Work produced by recombining or relinking the * Application with a modified version of the Linked Version. (If * you use option 4d0, the Installation Information must accompany * the Minimal Corresponding Source and Corresponding Application * Code. If you use option 4d1, you must provide the Installation * Information in the manner specified by section 6 of the GNU GPL * for conveying Corresponding Source.) * * 5. Combined Libraries. * * You may place library facilities that are a work based on the * Library side by side in a single library together with other library * facilities that are not Applications and are not covered by this * License, and convey such a combined library under terms of your * choice, if you do both of the following: * * a) Accompany the combined library with a copy of the same work based * on the Library, uncombined with any other library facilities, * conveyed under the terms of this License. * * b) Give prominent notice with the combined library that part of it * is a work based on the Library, and explaining where to find the * accompanying uncombined form of the same work. * * 6. Revised Versions of the GNU Lesser General Public License. * * The Free Software Foundation may publish revised and/or new versions * of the GNU Lesser General Public License from time to time. Such new * versions will be similar in spirit to the present version, but may * differ in detail to address new problems or concerns. * * Each version is given a distinguishing version number. If the * Library as you received it specifies that a certain numbered version * of the GNU Lesser General Public License "or any later version" * applies to it, you have the option of following the terms and * conditions either of that published version or of any later version * published by the Free Software Foundation. If the Library as you * received it does not specify a version number of the GNU Lesser * General Public License, you may choose any version of the GNU Lesser * General Public License ever published by the Free Software Foundation. * * If the Library as you received it specifies that a proxy can decide * whether future versions of the GNU Lesser General Public License shall * apply, that proxy's public statement of acceptance of any version is * permanent authorization for you to choose that version for the * Library. */ package io.bunnyblue.noticedog.app.lockscreen.ui; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.util.Log; import android.view.GestureDetector; import android.view.GestureDetector.OnGestureListener; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.widget.ImageView; import android.widget.LinearLayout; import com.android.volley.DefaultRetryPolicy; import com.google.inject.Inject; import io.bunnyblue.noticedog.app.R; import io.bunnyblue.noticedog.app.core.GuiceModule; import io.bunnyblue.noticedog.app.inbox.InboxListener; import io.bunnyblue.noticedog.app.inbox.InboxManager; import io.bunnyblue.noticedog.app.inbox.Message; import io.bunnyblue.noticedog.app.settings.Settings; import io.bunnyblue.noticedog.app.ui.InterceptTouchFrameLayout; public class LockScreenWidget implements InboxListener { private static final int CLEAR_TIME = 200; private static final int FADE_TIME = 200; private static final int MAX_NUM_PAGES = 5; private static final float RELOCATE_WINDOW_SCALE = 1.05f; private static final String TAG = "LockScreenWidget"; @Inject Context context; WidgetState currentState; LinearLayout fullscreenView; boolean hasMoveStarted; @Inject InboxManager inboxManager; boolean isPagerBeingDragged; boolean isTouchDown; float lastY; LockscreenWidgetListener listener; LockScreenWidgetPagerAdapter pagerAdapter; ViewPager pagerView; LockscreenWidgetPagesLayout pagesView; InterceptTouchFrameLayout rootView; View screenshotView; int showSinceLastMessageId; View unlockToContinueView; View widgetView; public LockScreenWidget() { GuiceModule.get().injectMembers(this); } public void start(int showSinceLastMessageId, LockscreenWidgetListener listener) { this.showSinceLastMessageId = showSinceLastMessageId; this.listener = listener; this.isPagerBeingDragged = false; this.isTouchDown = false; this.hasMoveStarted = false; this.currentState = WidgetState.stateHidden; this.inboxManager.addListener(this); createView(); setupMoveHandlers(); } public void stop() { performAction(WidgetAction.actionTerminate); this.inboxManager.removeListener(this); this.listener = null; } public void showUnlockToContinue() { performAction(WidgetAction.actionShowUnlockToContinue); } public void hideUnlockToContinue() { performAction(WidgetAction.actionHideUnlockToContinue); } public void setShowSinceLastMessageId(int showSinceLastMessageId) { this.showSinceLastMessageId = showSinceLastMessageId; this.pagerAdapter.setShowSinceMessageId(showSinceLastMessageId); } void createView() { LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); this.unlockToContinueView = inflater.inflate(R.layout.lockscreen_unlock_to_continue_view, null); this.widgetView = inflater.inflate(R.layout.lockscreen_widget, null); this.rootView = new InterceptTouchFrameLayout(this.context); LayoutParams params = new LayoutParams(-1, -2, 2010, 16777496, -3); WindowManager windowManager = (WindowManager) this.context.getSystemService("window"); params.gravity = 51; params.y = 0; windowManager.addView(this.rootView, params); this.rootView.addView(this.widgetView); this.widgetView.setAlpha(0.0f); this.fullscreenView = new LinearLayout(this.context); params = new LayoutParams(-1, -2, 2010, 16777496, -3); params.gravity = 51; params.width = -1; params.height = -1; windowManager.addView(this.fullscreenView, params); this.fullscreenView.setAlpha(DefaultRetryPolicy.DEFAULT_BACKOFF_MULT); this.pagerView = (ViewPager) this.widgetView.findViewById(R.id.lockscreen_widget_pager); this.pagesView = (LockscreenWidgetPagesLayout) this.widgetView.findViewById(R.id.lockscreen_widget_pages); this.pagerAdapter = new LockScreenWidgetPagerAdapter(); this.pagerAdapter.start(this.context, this.showSinceLastMessageId, 5); this.pagerView.setAdapter(this.pagerAdapter); this.pagerView.setOnPageChangeListener(new OnPageChangeListener() { public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } public void onPageSelected(int position) { LockScreenWidget.this.pagesView.setSelectedPage(position); } public void onPageScrollStateChanged(int state) { if (state == 0) { Log.d(LockScreenWidget.TAG, "Pager is NOT being dragged"); LockScreenWidget.this.isPagerBeingDragged = false; return; } Log.d(LockScreenWidget.TAG, "Pager is being dragged"); LockScreenWidget.this.isPagerBeingDragged = true; } }); resetPageAdapter(); } void setupMoveHandlers() { this.rootView.setOnInterceptTouchListener(new OnTouchListener() { GestureDetector gestureDetector = new GestureDetector(LockScreenWidget.this.context, new OnGestureListener() { public boolean onDown(MotionEvent motionEvent) { return false; } public void onShowPress(MotionEvent motionEvent) { } public boolean onSingleTapUp(MotionEvent motionEvent) { return false; } public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent2, float v, float v2) { return false; } public void onLongPress(MotionEvent motionEvent) { } public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent2, float v, float v2) { if (v2 >= -1000.0f || LockScreenWidget.this.isPagerBeingDragged) { return false; } LockScreenWidget.this.performAction(WidgetAction.actionClear); return true; } }); public boolean onTouch(View view, MotionEvent motionEvent) { this.gestureDetector.onTouchEvent(motionEvent); boolean moved; switch (motionEvent.getAction()) { case 1: case 3: moved = false; if (LockScreenWidget.this.hasMoveStarted) { moved = true; LockScreenWidget.this.performAction(WidgetAction.actionStopMove); } LockScreenWidget.this.isTouchDown = false; LockScreenWidget.this.hasMoveStarted = false; if (moved) { return true; } return false; case 2: moved = false; if (!LockScreenWidget.this.isTouchDown || LockScreenWidget.this.isPagerBeingDragged) { return false; } if (LockScreenWidget.this.hasMoveStarted && LockScreenWidget.this.screenshotView != null) { float newY = LockScreenWidget.this.screenshotView.getY() + (motionEvent.getRawY() - LockScreenWidget.this.lastY); int maxY = LockScreenWidget.this.fullscreenView.getHeight() - LockScreenWidget.this.screenshotView.getHeight(); if (newY < 0.0f) { Log.d(LockScreenWidget.TAG, "Hit top edge with newY = " + newY + " | setting newY to 0"); newY = 0.0f; } else if (newY > ((float) maxY)) { Log.d(LockScreenWidget.TAG, "Hit bottom edge with newY = " + newY + " | setting newY " + maxY); newY = (float) maxY; } LockScreenWidget.this.screenshotView.setY(newY); moved = true; } LockScreenWidget.this.lastY = motionEvent.getRawY(); if (moved) { return true; } return false; default: return false; } } }); } void performAction(WidgetAction action) { switch (action) { case actionShow: if (this.currentState == WidgetState.stateCleared || this.currentState == WidgetState.stateInterpolatingToCleared || this.currentState == WidgetState.stateHidden || this.currentState == WidgetState.stateInterpolatingToHidden) { changeState(WidgetState.stateShown); return; } else if (this.currentState == WidgetState.stateUnlockToContinueShown || this.currentState == WidgetState.stateInterpolatingToUnlockToContinueShown) { changeState(WidgetState.stateUnlockToContinueHidden); return; } else { return; } case actionShowUnlockToContinue: if (this.currentState == WidgetState.stateShown || this.currentState == WidgetState.stateInterpolatingToShown) { changeState(WidgetState.stateUnlockToContinueShown); return; } return; case actionHideUnlockToContinue: if (this.currentState == WidgetState.stateUnlockToContinueShown || this.currentState == WidgetState.stateInterpolatingToUnlockToContinueShown) { changeState(WidgetState.stateUnlockToContinueHidden); return; } return; case actionClear: if (this.currentState == WidgetState.stateShown) { changeState(WidgetState.stateCleared); return; } return; case actionHide: if (this.currentState == WidgetState.stateShown || this.currentState == WidgetState.stateInterpolatingToShown) { changeState(WidgetState.stateHidden); return; } return; case actionStartMove: if (this.currentState == WidgetState.stateShown) { changeState(WidgetState.stateMoving); return; } return; case actionStopMove: if (this.currentState == WidgetState.stateMoving || this.currentState == WidgetState.stateInterpolatingToMoving) { changeState(WidgetState.stateStopMoving); return; } return; case actionTerminate: if (this.currentState != WidgetState.stateTerminated && this.currentState != WidgetState.stateInterpolatingToTerminated) { changeState(WidgetState.stateTerminated); return; } return; default: return; } } void changeState(WidgetState nextState) { Log.d(TAG, "Switching state from: " + this.currentState + " to: " + nextState); LayoutParams layoutParams; switch (nextState) { case stateShown: this.widgetView.clearAnimation(); this.widgetView.setX(0.0f); this.widgetView.setAlpha(0.0f); this.widgetView.animate().alpha(0.0f).setDuration(0).withEndAction(new Runnable() { public void run() { LockScreenWidget.this.widgetView.animate().alpha(DefaultRetryPolicy.DEFAULT_BACKOFF_MULT) .setDuration(200).setInterpolator(new DecelerateInterpolator()) .withEndAction(new Runnable() { public void run() { LockScreenWidget.this.enableTouchOnRootView(true); LockScreenWidget.this.currentState = WidgetState.stateShown; } }); } }); this.currentState = WidgetState.stateInterpolatingToShown; return; case stateUnlockToContinueShown: enableTouchOnRootView(false); this.unlockToContinueView = ((LayoutInflater) this.context .getSystemService(Context.LAYOUT_INFLATER_SERVICE)) .inflate(R.layout.lockscreen_unlock_to_continue_view, null); addToFullscreenView(this.unlockToContinueView); ViewGroup.LayoutParams layoutParams2 = this.unlockToContinueView.getLayoutParams(); layoutParams2.width = -1; this.unlockToContinueView.setLayoutParams(layoutParams2); this.unlockToContinueView.setAlpha(0.0f); this.widgetView.clearAnimation(); this.widgetView.animate().alpha(0.0f).setDuration(200).setInterpolator(new AccelerateInterpolator()) .withEndAction(new Runnable() { public void run() { LockScreenWidget.this.unlockToContinueView.animate() .alpha(DefaultRetryPolicy.DEFAULT_BACKOFF_MULT).setDuration(200) .setStartDelay(100).setInterpolator(new DecelerateInterpolator()) .withEndAction(new Runnable() { public void run() { LockScreenWidget.this.currentState = WidgetState.stateUnlockToContinueShown; } }); } }); this.currentState = WidgetState.stateInterpolatingToUnlockToContinueShown; return; case stateUnlockToContinueHidden: enableTouchOnRootView(false); this.widgetView.clearAnimation(); this.unlockToContinueView.clearAnimation(); this.unlockToContinueView.animate().alpha(0.0f).setDuration(200) .setInterpolator(new AccelerateInterpolator()).withEndAction(new Runnable() { public void run() { LockScreenWidget.this.widgetView.animate() .alpha(DefaultRetryPolicy.DEFAULT_BACKOFF_MULT).setDuration(200) .setStartDelay(100).setInterpolator(new DecelerateInterpolator()) .withEndAction(new Runnable() { public void run() { LockScreenWidget.this.currentState = WidgetState.stateShown; LockScreenWidget.this.clearFullscreenView(); LockScreenWidget.this.unlockToContinueView = null; LockScreenWidget.this.enableTouchOnRootView(true); LockScreenWidget.this.currentState = WidgetState.stateShown; } }); } }); this.currentState = WidgetState.stateInterpolatingToUnlockToContinueHidden; return; case stateCleared: enableTouchOnRootView(false); this.widgetView.clearAnimation(); this.widgetView.animate().alpha(0.0f).setDuration(200).setInterpolator(new AccelerateInterpolator()) .withEndAction(new Runnable() { public void run() { if (LockScreenWidget.this.listener != null) { LockScreenWidget.this.listener.onWidgetCleared(); } LockScreenWidget.this.currentState = WidgetState.stateCleared; } }); this.currentState = WidgetState.stateInterpolatingToCleared; return; case stateMoving: this.widgetView.clearAnimation(); layoutParams = (LayoutParams) this.rootView.getLayoutParams(); this.screenshotView = duplicateView(this.widgetView); this.screenshotView.setY((float) layoutParams.y); Log.d(TAG, "Setting widget view screenshot to Y position = " + layoutParams.y); addToFullscreenView(this.screenshotView); this.widgetView.animate().alpha(0.0f).setDuration(0).setStartDelay(100); this.screenshotView.animate().scaleX(RELOCATE_WINDOW_SCALE).scaleY(RELOCATE_WINDOW_SCALE) .setDuration(200).withEndAction(new Runnable() { public void run() { LockScreenWidget.this.currentState = WidgetState.stateMoving; } }); this.screenshotView.performHapticFeedback(3, 2); this.currentState = WidgetState.stateInterpolatingToMoving; return; case stateStopMoving: WindowManager windowManager = (WindowManager) this.context.getSystemService("window"); layoutParams = (LayoutParams) this.rootView.getLayoutParams(); layoutParams.y = (int) this.screenshotView.getY(); windowManager.updateViewLayout(this.rootView, layoutParams); this.screenshotView.setScaleY(RELOCATE_WINDOW_SCALE); this.screenshotView.setScaleX(RELOCATE_WINDOW_SCALE); this.screenshotView.animate().scaleY(DefaultRetryPolicy.DEFAULT_BACKOFF_MULT) .scaleX(DefaultRetryPolicy.DEFAULT_BACKOFF_MULT).setDuration(200).withEndAction(new Runnable() { public void run() { LockScreenWidget.this.widgetView.animate() .alpha(DefaultRetryPolicy.DEFAULT_BACKOFF_MULT).setDuration(0) .withEndAction(new Runnable() { public void run() { LockScreenWidget.this.clearFullscreenView(); LockScreenWidget.this.screenshotView = null; LockScreenWidget.this.currentState = WidgetState.stateShown; } }); } }); ((Settings) GuiceModule.get().getInstance(Settings.class)) .updateLockscreenWidgetY(this.screenshotView.getY()); this.currentState = WidgetState.stateInterpolatingToStopMoving; Log.d(TAG, "Restoring widget view at Y position = " + layoutParams.y + " with height = " + layoutParams.height); return; case stateHidden: enableTouchOnRootView(false); this.widgetView.clearAnimation(); this.widgetView.animate().alpha(0.0f).setDuration(200).setInterpolator(new AccelerateInterpolator()) .withEndAction(new Runnable() { public void run() { LockScreenWidget.this.enableTouchOnRootView(false); LockScreenWidget.this.currentState = WidgetState.stateHidden; } }); this.currentState = WidgetState.stateInterpolatingToHidden; return; case stateTerminated: enableTouchOnRootView(false); this.widgetView.clearAnimation(); this.fullscreenView.clearAnimation(); if (this.unlockToContinueView != null) { this.unlockToContinueView.clearAnimation(); this.unlockToContinueView.animate().alpha(0.0f).setDuration(200) .setInterpolator(new AccelerateInterpolator()); } this.widgetView.animate().alpha(0.0f).setDuration(200).setInterpolator(new AccelerateInterpolator()) .withEndAction(new Runnable() { public void run() { WindowManager wm = (WindowManager) LockScreenWidget.this.context .getSystemService("window"); try { wm.removeView(LockScreenWidget.this.fullscreenView); } catch (Exception e) { } try { wm.removeView(LockScreenWidget.this.rootView); } catch (Exception e2) { } LockScreenWidget.this.widgetView = null; LockScreenWidget.this.screenshotView = null; LockScreenWidget.this.unlockToContinueView = null; LockScreenWidget.this.fullscreenView = null; LockScreenWidget.this.rootView = null; LockScreenWidget.this.context = null; LockScreenWidget.this.currentState = WidgetState.stateTerminated; } }); this.currentState = WidgetState.stateInterpolatingToTerminated; return; default: return; } } public void onInboxUpdated() { } public void onInboxMessageAdded(Message message) { resetPageAdapter(); } public void onInboxCleared() { } void clearFullscreenView() { this.fullscreenView.removeAllViews(); this.fullscreenView.addView(new View(this.context)); } void addToFullscreenView(View view) { this.fullscreenView.removeAllViews(); this.fullscreenView.addView(view); } void enableTouchOnRootView(boolean enableTouch) { WindowManager wm = (WindowManager) this.context.getSystemService("window"); LayoutParams layoutParams = (LayoutParams) this.rootView.getLayoutParams(); boolean isTouchEnabled = (layoutParams.flags & 16) == 0; if (isTouchEnabled && !enableTouch) { layoutParams.flags |= 16; wm.updateViewLayout(this.rootView, layoutParams); } else if (!isTouchEnabled && enableTouch) { layoutParams.flags &= -17; wm.updateViewLayout(this.rootView, layoutParams); } } void resetPageAdapter() { this.pagerAdapter.reset(); int count = this.pagerAdapter.getCount(); this.pagesView.setNumPages(count); this.pagesView.setSelectedPage(0); this.pagerView.setCurrentItem(0); if (count > 0) { performAction(WidgetAction.actionShow); } else { performAction(WidgetAction.actionHide); } } View duplicateView(View view) { Bitmap b = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Config.ARGB_8888); view.draw(new Canvas(b)); ImageView imageView = new ImageView(this.context); imageView.setImageBitmap(b); return imageView; } void safeSetVisibility(View view, int visibility) { if (visibility == 0) { view.setAlpha(DefaultRetryPolicy.DEFAULT_BACKOFF_MULT); } else { view.setAlpha(0.0f); } } enum WidgetAction { actionShow, actionHide, actionShowUnlockToContinue, actionHideUnlockToContinue, actionStartMove, actionStopMove, actionClear, actionTerminate } enum WidgetState { stateShown, stateInterpolatingToShown, stateHidden, stateInterpolatingToHidden, stateMoving, stateInterpolatingToMoving, stateStopMoving, stateInterpolatingToStopMoving, stateCleared, stateInterpolatingToCleared, stateUnlockToContinueShown, stateInterpolatingToUnlockToContinueShown, stateUnlockToContinueHidden, stateInterpolatingToUnlockToContinueHidden, stateTerminated, stateInterpolatingToTerminated } public interface LockscreenWidgetListener { void onWidgetCleared(); } }