Java tutorial
package com.serenegiant.aceparrot; /* * By downloading, copying, installing or using the software you agree to this license. * If you do not agree to this license, do not download, install, * copy or use the software. * * * License Agreement * (3-clause BSD License) * * Copyright (C) 2015-2017, saki t_saki@serenegiant.com * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the names of the copyright holders nor the names of the contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * This software is provided by the copyright holders and contributors "as is" and * any express or implied warranties, including, but not limited to, the implied * warranties of merchantability and fitness for a particular purpose are disclaimed. * In no event shall copyright holders or contributors be liable for any direct, * indirect, incidental, special, exemplary, or consequential damages * (including, but not limited to, procurement of substitute goods or services; * loss of use, data, or profits; or business interruption) however caused * and on any theory of liability, whether in contract, strict liability, * or tort (including negligence or otherwise) arising in any way out of * the use of this software, even if advised of the possibility of such damage. */ import android.Manifest; import android.animation.Animator; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.annotation.SuppressLint; import android.app.Activity; import android.app.Fragment; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Vibrator; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.StringRes; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.Toast; import com.serenegiant.dialog.MessageDialogFragment; import com.serenegiant.arflight.R; import com.serenegiant.utils.BuildCheck; import com.serenegiant.utils.HandlerThreadHandler; import com.serenegiant.utils.PermissionCheck; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.HashMap; import java.util.Map; public class BaseFragment extends Fragment implements MessageDialogFragment.MessageDialogListener { private static final boolean DEBUG = false; // FIXME ??false???? private static final String TAG = BaseFragment.class.getSimpleName(); private final Handler mUIHandler = new Handler(Looper.getMainLooper()); private final long mUIThreadId = Looper.getMainLooper().getThread().getId(); private Handler mAsyncHandler; protected LocalBroadcastManager mLocalBroadcastManager; protected Vibrator mVibrator; private Toast mToast; public BaseFragment() { super(); // ?? } @SuppressWarnings("deprecation") @Override public void onAttach(final Activity activity) { super.onAttach(activity); mLocalBroadcastManager = LocalBroadcastManager.getInstance(activity); mVibrator = (Vibrator) getActivity().getSystemService(Activity.VIBRATOR_SERVICE); mIsReplacing = false; } @Override public synchronized void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); loadArguments(savedInstanceState); mAsyncHandler = HandlerThreadHandler.createHandler(TAG); mIsReplacing = false; } @Override public void onDestroy() { if (DEBUG) Log.v(TAG, "onDestroy:"); cancelToast(); mUIHandler.removeCallbacksAndMessages(null); if (mAsyncHandler != null) { mAsyncHandler.removeCallbacksAndMessages(null); try { mAsyncHandler.getLooper().quit(); } catch (final Exception e) { // ignore } mAsyncHandler = null; } super.onDestroy(); } @Override public void onDetach() { mLocalBroadcastManager = null; mVibrator = null; super.onDetach(); } @Override public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); final Bundle args = getArguments(); if (args != null) { outState.putAll(args); } if (DEBUG) Log.v(TAG, "onSaveInstanceState:" + outState); } @Override public void onViewStateRestored(Bundle savedInstanceState) { super.onViewStateRestored(savedInstanceState); loadArguments(savedInstanceState); } @Override public final void onStart() { super.onStart(); if (BuildCheck.isAndroid7()) { internalOnResume(); } } @Override public synchronized final void onResume() { super.onResume(); if (!BuildCheck.isAndroid7()) { internalOnResume(); } } @Override public synchronized final void onPause() { if (!BuildCheck.isAndroid7()) { internalOnPause(); } super.onPause(); } @Override public final void onStop() { if (BuildCheck.isAndroid7()) { internalOnPause(); } super.onStop(); } protected void internalOnResume() { if (DEBUG) Log.v(TAG, "internalOnResume:"); mIsReplacing = false; } protected void internalOnPause() { if (DEBUG) Log.v(TAG, "internalOnPause:"); removeRequestPopBackStack(); mResetColorFilterTasks.clear(); } protected void loadArguments(final Bundle savedInstanceState) { } private boolean mIsReplacing; protected boolean isReplacing() { return mIsReplacing; } /** * ??????backstack?? * @param fragment null???? * @return */ protected Fragment replace(final Fragment fragment) { if (fragment != null) { mIsReplacing = true; getFragmentManager().beginTransaction().addToBackStack(null).replace(R.id.container, fragment).commit(); } return fragment; } protected void clearReplacing() { mIsReplacing = false; } /** * ???? */ protected void popBackStack() { mIsReplacing = false; try { getFragmentManager().popBackStack(); } catch (final Exception e) { // } } /** * UI??? * @param task */ protected void runOnUiThread(final Runnable task) { if (task != null) { mUIHandler.removeCallbacks(task); try { if (mUIThreadId != Thread.currentThread().getId()) { mUIHandler.post(task); } else { task.run(); } } catch (final Exception e) { Log.w(TAG, e); } } } /** * UI???? * @param task */ protected void removeFromUIThread(final Runnable task) { if (task != null) { mUIHandler.removeCallbacks(task); } } /** * ???UI?? * @param task UI??? * @param delay_msec 0?runOnUiThread??? */ protected void runOnUiThread(final Runnable task, final long delay_msec) { if (task != null) { mUIHandler.removeCallbacks(task); if (delay_msec <= 0) { runOnUiThread(task); } else if (task != null) { mUIHandler.postDelayed(task, delay_msec); } } } /** * ???? * @param task */ protected void removeEvent(final Runnable task) { if (task != null) { if (mAsyncHandler != null) { mAsyncHandler.removeCallbacks(task); } else { removeFromUIThread(task); } } } /** * ????? * @param task * @param delay_msec */ protected void queueEvent(final Runnable task, final long delay_msec) { if (task != null) { if (mAsyncHandler != null) { mAsyncHandler.removeCallbacks(task); if (delay_msec <= 0) { mAsyncHandler.post(task); } else { mAsyncHandler.postDelayed(task, delay_msec); } } else { runOnUiThread(task, delay_msec); } } } /** * ???? * @param delay */ protected void requestPopBackStack(final long delay) { removeFromUIThread(mPopBackStackTask); runOnUiThread(mPopBackStackTask, delay); // UI?? } /** * ????? */ protected void removeRequestPopBackStack() { removeFromUIThread(mPopBackStackTask); } /** * ?????Runnable * ??? */ private final Runnable mPopBackStackTask = new Runnable() { @Override public void run() { try { mIsReplacing = false; popBackStack(); } catch (final Exception e) { Log.w(TAG, e); } } }; /** * ????? */ protected static final long TOUCH_RESPONSE_TIME_MS = 100; // 200 /** * ??? */ protected static final int TOUCH_RESPONSE_COLOR = 0x7f331133; /** * ?Runnable? */ private final Map<View, ResetColorFilterTask> mResetColorFilterTasks = new HashMap<View, ResetColorFilterTask>(); /** * ?????? * @param view */ protected void setColorFilter(final View view) { setColorFilter(view, TOUCH_RESPONSE_COLOR, TOUCH_RESPONSE_TIME_MS); } /** * ??ImageView?????? * reset_delay?0?????????? * @param view * @param color * @param reset_delay */ protected void setColorFilter(final View view, final int color, final long reset_delay) { if (view instanceof ImageView) { runOnUiThread(new Runnable() { @Override public void run() { ((ImageView) view).setColorFilter(color); } }); if (reset_delay > 0) { ResetColorFilterTask task = mResetColorFilterTasks.get(view); if (task == null) { task = new ResetColorFilterTask(((ImageView) view)); mResetColorFilterTasks.put(view, task); } removeFromUIThread(task); runOnUiThread(task, reset_delay); // UI?? } } else { runOnUiThread(new Runnable() { @Override public void run() { view.setBackgroundColor(color); } }); if (reset_delay > 0) { ResetColorFilterTask task = mResetColorFilterTasks.get(view); if (task == null) { task = new ResetColorFilterTask(view); } removeFromUIThread(task); runOnUiThread(task, reset_delay); // UI?? } } } /** * ?ImageView(?ImageButton)?????Runnable */ private static class ResetColorFilterTask implements Runnable { private final View mView; ResetColorFilterTask(final View view) { mView = view; } @Override public void run() { if (mView instanceof ImageView) { ((ImageView) mView).setColorFilter(0); } else { mView.setBackgroundColor(0); } } } protected interface AnimationCallback { public void onAnimationEnd(final View target, final int type); } protected static final int ANIM_CANCEL = 0; protected static final int ANIM_FADE_OUT = -1; protected static final int ANIM_FADE_IN = -2; protected static final int ANIM_ZOOM_OUT = -3; protected static final int ANIM_ZOOM_IN = -4; /** * start?stop???? * @param target * @param type ??????? * @param start [0.0f-1.0f] * @param stop [0.0f-1.0f] * @param duration [] * @param startDelay [] */ @SuppressLint("NewApi") protected final void alphaAnimation(final View target, final int type, final float start, final float stop, final long duration, final long startDelay, final AnimationCallback callback) { // if (DEBUG) Log.v(TAG, "fadeOut,target=" + target); if (target == null) return; target.clearAnimation(); if (target.getVisibility() == View.VISIBLE) { target.setTag(R.id.anim_type, type); target.setTag(R.id.anim_callback, callback); target.setScaleX(1.0f); target.setScaleY(1.0f); target.setAlpha(start); final ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(target, "alpha", start, stop); objectAnimator.addListener(mAnimatorListener); if (BuildCheck.isAndroid4_3()) objectAnimator.setAutoCancel(true); // API >= 18 ??????Animator???? objectAnimator.setDuration(duration > 0 ? duration : 500); // 0.5??? objectAnimator.setStartDelay(startDelay > 0 ? startDelay : 0); // ??? objectAnimator.start(); // } } /** * 01??(View)?? * @param target * @param startDelay */ @SuppressLint("NewApi") protected final void fadeIn(final View target, final long duration, final long startDelay) { // if (DEBUG) Log.v(TAG, "fadeIn:target=" + target); if (target == null) return; target.clearAnimation(); target.setVisibility(View.VISIBLE); target.setTag(R.id.anim_type, ANIM_FADE_IN); // ??? target.setScaleX(1.0f); target.setScaleY(1.0f); target.setAlpha(0.0f); final ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(target, "alpha", 0f, 1f); objectAnimator.addListener(mAnimatorListener); if (BuildCheck.isJellyBeanMR2()) objectAnimator.setAutoCancel(true); // API >= 18 ??????Animator???? objectAnimator.setDuration(duration > 0 ? duration : 500); // 0.5??? objectAnimator.setStartDelay(startDelay > 0 ? startDelay : 0); // ??? objectAnimator.start(); // } /** * 10??(View)?? * @param target * @param startDelay */ @SuppressLint("NewApi") protected final void fadeOut(final View target, final long duration, final long startDelay) { // if (DEBUG) Log.v(TAG, "fadeOut,target=" + target); if (target == null) return; target.clearAnimation(); if (target.getVisibility() == View.VISIBLE) { target.setTag(R.id.anim_type, ANIM_FADE_OUT); // ?? target.setScaleX(1.0f); target.setScaleY(1.0f); target.setAlpha(1.0f); final ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(target, "alpha", 1f, 0f); objectAnimator.addListener(mAnimatorListener); if (BuildCheck.isAndroid4_3()) objectAnimator.setAutoCancel(true); // API >= 18 ??????Animator???? objectAnimator.setDuration(duration > 0 ? duration : 500); // 0.5??? objectAnimator.setStartDelay(startDelay > 0 ? startDelay : 0); // ??? objectAnimator.start(); // } } /** * 01??(View)?? * @param target * @param startDelay */ @SuppressLint("NewApi") protected final void zoomIn(final View target, final long duration, final long startDelay) { // if (DEBUG) Log.v(TAG, "zoomIn:target=" + target); if (target == null) return; target.clearAnimation(); target.setVisibility(View.VISIBLE); target.setTag(R.id.anim_type, ANIM_ZOOM_IN); // ??? target.setScaleX(0.0f); target.setScaleY(0.0f); target.setAlpha(1.0f); final PropertyValuesHolder scale_x = PropertyValuesHolder.ofFloat("scaleX", 0.01f, 1f); final PropertyValuesHolder scale_y = PropertyValuesHolder.ofFloat("scaleY", 0.01f, 1f); final ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(target, scale_x, scale_y); objectAnimator.addListener(mAnimatorListener); if (BuildCheck.isJellyBeanMR2()) objectAnimator.setAutoCancel(true); // API >= 18 ??????Animator???? objectAnimator.setDuration(duration > 0 ? duration : 500); // 0.5??? objectAnimator.setStartDelay(startDelay > 0 ? startDelay : 0); // ??? objectAnimator.start(); // } /** * 10??(View)?? * @param target * @param startDelay */ @SuppressLint("NewApi") protected final void zoomOut(final View target, final long duration, final long startDelay) { // if (DEBUG) Log.v(TAG, "zoomIn:target=" + target); if (target == null) return; target.clearAnimation(); target.setVisibility(View.VISIBLE); target.setTag(R.id.anim_type, ANIM_ZOOM_OUT); // ??? target.setScaleX(1.0f); target.setScaleY(1.0f); target.setAlpha(1.0f); final PropertyValuesHolder scale_x = PropertyValuesHolder.ofFloat("scaleX", 1f, 0f); final PropertyValuesHolder scale_y = PropertyValuesHolder.ofFloat("scaleY", 1f, 0f); final ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(target, scale_x, scale_y); objectAnimator.addListener(mAnimatorListener); if (BuildCheck.isJellyBeanMR2()) objectAnimator.setAutoCancel(true); // API >= 18 ??????Animator???? objectAnimator.setDuration(duration > 0 ? duration : 500); // 0.5??? objectAnimator.setStartDelay(startDelay > 0 ? startDelay : 0); // ??? objectAnimator.start(); // } /** * ? */ private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() { @Override public void onAnimationStart(final Animator animation) { // if (DEBUG) Log.v(TAG, "onAnimationStart:"); } @Override public void onAnimationEnd(final Animator animation) { if (animation instanceof ObjectAnimator) { final Activity activity = getActivity(); if ((activity == null) || (activity.isFinishing())) return; final ObjectAnimator anim = (ObjectAnimator) animation; final View target = (View) anim.getTarget(); if (target == null) return; // ??????????? int anim_type = 0; try { anim_type = (Integer) target.getTag(R.id.anim_type); } catch (final Exception e) { } final int id = target.getId(); switch (anim_type) { case ANIM_CANCEL: break; case ANIM_FADE_OUT: // case ANIM_FADE_IN: // { final boolean fadeIn = anim_type == ANIM_FADE_IN; if (!fadeIn) { target.setVisibility(View.GONE); } break; } case ANIM_ZOOM_IN: // { // ??THUMBNAIL_HIDE_DELAY???? // ????????????. runOnUiThread(new Runnable() { @Override public void run() { fadeOut(target, -1, 500); } }, 0); break; } case ANIM_ZOOM_OUT: // { target.setVisibility(View.GONE); break; } default: { final AnimationCallback callback = (AnimationCallback) target.getTag(R.id.anim_callback); if (callback != null) { try { callback.onAnimationEnd(target, anim_type); } catch (final Exception e) { Log.w(TAG, e); } } } } } } @Override public void onAnimationCancel(final Animator animation) { } @Override public void onAnimationRepeat(final Animator animation) { } }; protected LayoutInflater getThemedLayoutInflater(final LayoutInflater inflater) { final Activity context = getActivity(); final SharedPreferences pref = context.getPreferences(0); final int layout_style = getLayoutStyle(pref.getInt(AppConst.KEY_ICON_TYPE, 0)); // create ContextThemeWrapper from the original Activity Context with the custom theme final Context contextThemeWrapper = new ContextThemeWrapper(context, layout_style); // clone the inflater using the ContextThemeWrapper return inflater.cloneInContext(contextThemeWrapper); } protected int getLayoutStyle(final int type) { final int result; switch (type) { case 1: result = R.style.AppTheme_001; break; case 2: result = R.style.AppTheme_002; break; // case 0: default: result = R.style.AppTheme; break; } return result; } //================================================================================ // Android6???? //================================================================================ /** * Callback listener from MessageDialogFragmentV4 * @param dialog * @param requestCode * @param permissions * @param result */ @SuppressLint("NewApi") @Override public void onMessageDialogResult(final MessageDialogFragment dialog, final int requestCode, final String[] permissions, final boolean result) { if (result) { // request permission(s) when user touched/clicked OK if (BuildCheck.isMarshmallow()) { requestPermissions(permissions, requestCode); return; } } // check permission and call #checkPermissionResult when user canceled or not Android6 final Context context = getActivity(); for (final String permission : permissions) { checkPermissionResult(requestCode, permission, PermissionCheck.hasPermission(context, permission)); } } /** * callback method when app(Fragment) receive the result of permission result from ANdroid system * @param requestCode * @param permissions * @param grantResults */ @Override public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); // ????????? final int n = Math.min(permissions.length, grantResults.length); for (int i = 0; i < n; i++) { checkPermissionResult(requestCode, permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED); } } /** * check the result of permission request * if app still has no permission, just show Toast * @param requestCode * @param permission * @param result */ protected void checkPermissionResult(final int requestCode, final String permission, final boolean result) { // show Toast when there is no permission if (Manifest.permission.RECORD_AUDIO.equals(permission)) { onUpdateAudioPermission(result); if (!result) { Toast.makeText(getActivity().getApplicationContext(), R.string.permission_audio, Toast.LENGTH_SHORT) .show(); } } if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission)) { onUpdateExternalStoragePermission(result); if (!result) { Toast.makeText(getActivity().getApplicationContext(), R.string.permission_ext_storage, Toast.LENGTH_SHORT).show(); } } if (Manifest.permission.INTERNET.equals(permission)) { onUpdateNetworkPermission(result); if (!result) { Toast.makeText(getActivity().getApplicationContext(), R.string.permission_network, Toast.LENGTH_SHORT).show(); } } if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permission) || Manifest.permission.ACCESS_FINE_LOCATION.equals(permission)) { onUpdateLocationPermission(permission, result); if (!result) { Toast.makeText(getActivity().getApplicationContext(), R.string.permission_location, Toast.LENGTH_SHORT).show(); } } } protected void onUpdateAudioPermission(final boolean hasPermission) { } protected void onUpdateExternalStoragePermission(final boolean hasPermission) { } protected void onUpdateNetworkPermission(final boolean hasPermission) { } protected void onUpdateLocationPermission(final String permission, final boolean hasPermission) { } protected static final int REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE = 0x01; protected static final int REQUEST_PERMISSION_AUDIO_RECORDING = 0x02; protected static final int REQUEST_PERMISSION_NETWORK = 0x03; protected static final int REQUEST_PERMISSION_LOCATION = 0x04; protected static final int REQUEST_PERMISSION_LOCATION_COARSE = 0x05; protected static final int REQUEST_PERMISSION_LOCATION_FINE = 0x06; /** * check whether this app has write external storage * if this app has no permission, show dialog * @return true this app has permission */ protected boolean checkPermissionWriteExternalStorage() { if (!PermissionCheck.hasWriteExternalStorage(getActivity())) { MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE, R.string.permission_title, R.string.permission_ext_storage_request, new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }); return false; } return true; } /** * check whether this app has permission of audio recording * if this app has no permission, show dialog * @return true this app has permission */ protected boolean checkPermissionAudio() { if (!PermissionCheck.hasAudio(getActivity())) { MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_AUDIO_RECORDING, R.string.permission_title, R.string.permission_audio_recording_request, new String[] { Manifest.permission.RECORD_AUDIO }); return false; } return true; } /** * check whether this app has permission of network access * if this app has no permission, show dialog * @return true this app has permission */ protected boolean checkPermissionNetwork() { if (!PermissionCheck.hasNetwork(getActivity())) { MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_NETWORK, R.string.permission_title, R.string.permission_network_request, new String[] { Manifest.permission.INTERNET }); return false; } return true; } /** * check whether this app has permission to access to coarse and fine location * if this app has no permission, show dialog * @return true this app has permission */ protected boolean checkPermissionLocation() { if (!PermissionCheck.hasAccessLocation(getActivity())) { MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_LOCATION, R.string.permission_title, R.string.permission_location_reason, new String[] { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION }); return false; } return true; } /** * check whether this app has permission to access to coarse location * if this app has no permission, show dialog * @return true this app has permission */ protected boolean checkPermissionLocationCoarse() { if (!PermissionCheck.hasAccessLocation(getActivity())) { MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_LOCATION, R.string.permission_title, R.string.permission_location_reason, new String[] { Manifest.permission.ACCESS_COARSE_LOCATION }); return false; } return true; } /** * check whether this app has permission to access to fine location * if this app has no permission, show dialog * @return true this app has permission */ protected boolean checkPermissionLocationFine() { if (!PermissionCheck.hasAccessLocation(getActivity())) { MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_LOCATION, R.string.permission_title, R.string.permission_location_reason, new String[] { Manifest.permission.ACCESS_FINE_LOCATION }); return false; } return true; } //================================================================================ @IntDef({ Toast.LENGTH_SHORT, Toast.LENGTH_LONG }) @Retention(RetentionPolicy.SOURCE) public @interface Duration { } protected void showToast(final String message, @Duration final int duration) { runOnUiThread(new Runnable() { @Override public void run() { if (mToast != null) { mToast.cancel(); mToast = null; } final Activity activity = getActivity(); if ((activity == null) || activity.isFinishing()) return; mToast = Toast.makeText(activity, message, duration); mToast.show(); } }); } protected void showToast(@StringRes final int messageId, @Duration final int duration) { runOnUiThread(new Runnable() { @Override public void run() { if (mToast != null) { mToast.cancel(); mToast = null; } final Activity activity = getActivity(); if ((activity == null) || activity.isFinishing()) return; mToast = Toast.makeText(activity, messageId, duration); mToast.show(); } }); } protected void cancelToast() { if (mToast != null) { try { mToast.cancel(); } catch (final Exception e) { // ignore } } mToast = null; } }