package me.barrasso.android.volume.popup; import android.annotation.TargetApi; import android.content.DialogInterface; import android.content.Context; import android.content.res.Resources; import android.content.Intent; import android.content.IntentFilter; import android.util.TypedValue; import android.util.Log; import android.os.Bundle; import android.os.Build; import android.os.Handler; import android.os.Message; import android.widget.TextView; import android.widget.FrameLayout; import android.widget.ImageView; import android.text.TextUtils; import android.provider.Settings; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.view.View; import android.view.KeyEvent; import android.view.Gravity; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.LayoutInflater; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import me.barrasso.android.volume.BuildConfig; import me.barrasso.android.volume.R; import java.lang.reflect.Field; import java.lang.ref.WeakReference; /** * A {@link android.app.Dialog}-like {@link PopupWindow}. Unlike Dialog, this class * does not require an application {@link Context} and can be displayed from a background * {@link android.app.Service}. A Dialog is comprised of a title and a content view, the latter * managed by the user of this class. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class PopupDialog extends PopupWindow implements DialogInterface { /** * {@link Log} tag also used in identifying this {@link PopupWindow}. * @see {@link PopupWindow#getName} */ public static final String TAG = PopupDialog.class.getSimpleName(); public static final int THEME_DARK = R.drawable.dialog_full_holo_dark; public static final int THEME_LIGHT = R.drawable.dialog_full_holo_light; private static final int DISMISS = 0x43; private static final int CANCEL = 0x44; private static final int SHOW = 0x45; // Create and initialize our window parameters. // Note (from the API): "With the default gravity it (x and y) are ignored." private static WindowManager.LayoutParams getWindowLayoutParams() { int flags = (LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | LayoutParams.FLAG_DIM_BEHIND ); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) flags |= LayoutParams.FLAG_HARDWARE_ACCELERATED; if (!BuildConfig.DEBUG) flags |= LayoutParams.FLAG_SECURE; LayoutParams WPARAMS = new WindowManager.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, LayoutParams.TYPE_SYSTEM_ALERT, flags, PixelFormat.TRANSLUCENT); final int windowAnimations = getInternalStyle("Animation_Dialog"); if (windowAnimations > 0) WPARAMS.windowAnimations = windowAnimations; WPARAMS.dimAmount = 0.6f; WPARAMS.packageName = PopupDialog.class.getPackage().getName(); WPARAMS.setTitle(TAG); WPARAMS.gravity = Gravity.CENTER; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) WPARAMS.screenBrightness = WPARAMS.buttonBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE; return WPARAMS; } private Handler mListenersHandler; private Message mCancelMessage; private Message mDismissMessage; private Message mShowMessage; private int mTheme = THEME_DARK; private boolean mFullscreen; private WindowManager.LayoutParams mWindowAttributes; public PopupDialog(PopupWindowManager manager) { super(manager); mListenersHandler = new ListenersHandler(this); } @Override protected void onCreate() { Context context = pWindowManager.getContext(); Resources mResources = context.getResources(); updateDimens(); LayoutInflater inflater = LayoutInflater.from(context); mLayout = (ViewGroup) inflater.inflate(R.layout.dialog_title_holo, null); } /** Set whether or not to show a title icon. */ protected void setShowIcon(boolean show) { View icon = findViewById(R.id.icon); if (null != icon) icon.setVisibility(((show) ? View.VISIBLE : View.GONE)); } /** Set the drawable resource for the title icon; 0 to hide. */ public void setIcon(int resId) { if (resId <= 0) { setShowIcon(false); return; } ImageView icon = (ImageView) findViewById(R.id.icon); if (null != icon) { icon.setImageResource(resId); } } /** Set the drawable for the title icon; null to hide. */ public void setIcon(Drawable iconDrawable) { if (null == iconDrawable) { setShowIcon(false); return; } ImageView icon = (ImageView) findViewById(R.id.icon); if (null != icon) { icon.setImageDrawable(iconDrawable); } } /** Set whether the dialog should be fullscreen. */ public void setFullscreen(boolean fullscreen) { mFullscreen = fullscreen; updateDimens(); onWindowAttributesChanged(); } /** Set the theme: {@link #THEME_DARK} or {@link #THEME_LIGHT}. * Default is {@link #THEME_DARK}. */ public void setTheme(int theme) { if (mTheme != theme && (theme == THEME_DARK || theme == THEME_LIGHT)) { mTheme = theme; TextView messageView = (TextView) findViewById(android.R.id.message); if (null != messageView) messageView.setTextColor(((mTheme == THEME_DARK) ? Color.WHITE : Color.BLACK)); mLayout.setBackgroundResource(mTheme); FrameLayout contentContainer = (FrameLayout) findViewById(R.id.contentPanel); if (null != contentContainer) { switch (mTheme) { case THEME_DARK: contentContainer.setForeground( getResources().getDrawable(R.drawable.ab_solid_shadow_holo)); break; case THEME_LIGHT: contentContainer.setForeground(null); break; } } } } /** * Cancel the dialog. This is essentially the same as calling {@link #dismiss()}, but it will * also call your {@link DialogInterface.OnCancelListener} (if registered). */ @Override public void cancel() { if (isAttached() && mCancelMessage != null) { Message.obtain(mCancelMessage).sendToTarget(); } dismiss(); } /** * Dismiss the dialog. This will remove the window and destroy all associated * Views. Will call your {@link DialogInterface.OnDismissListener} (if registered). */ @Override public void dismiss() { if (isAttached() && mDismissMessage != null) { Message.obtain(mDismissMessage).sendToTarget(); } onDestroy(); } @Override public void show() { if (!isShowing() && mShowMessage != null) { Message.obtain(mShowMessage).sendToTarget(); } super.show(); } /** @see {@link #setTheme} */ public int getTheme() { return mTheme; } protected void updateDimens() { final int[] wDims = getWindowDimensions(); LayoutParams wParams = getWindowParams(); if (mFullscreen) { wParams.width = wParams.height = LayoutParams.MATCH_PARENT; } else { wParams.width = ((3 * wDims[0]) / 4); wParams.height = ((3 * wDims[1]) / 4); } } @Override protected WindowManager.LayoutParams getWindowParams() { if (null == mWindowAttributes) mWindowAttributes = getWindowLayoutParams(); return mWindowAttributes; } @Override public void onRotationChanged(int rotation) { super.onRotationChanged(rotation); updateDimens(); onWindowAttributesChanged(); } /** * Set the title text for this dialog's window. The text is retrieved * from the resources with the supplied identifier. * * @param titleId the title's text resource identifier */ public void setTitle(int titleId) { setTitle(pWindowManager.getContext().getText(titleId)); } /** * Set the message text for this dialog's window. The text is retrieved * from the resources with the supplied identifier. */ public void setMessage(int messageId) { setMessage(pWindowManager.getContext().getText(messageId)); } /** * Set the title text for this dialog's window. * * @param title The new text to display in the title. */ public void setTitle(CharSequence title) { setText(title, android.R.id.title); } /** * Set the message to display using the given string. */ public void setMessage(CharSequence message) { Log.d(TAG, "Set message: " + message); setText(message, android.R.id.message); } protected void setText(CharSequence title, int resId) { if (!(mLayout instanceof ViewGroup)) return; ViewGroup mDecor = (ViewGroup) mLayout; TextView titleView = (TextView) mDecor.findViewById(resId); if (null == titleView) return; titleView.setText(title); ((ViewGroup) titleView.getParent()).setVisibility(View.VISIBLE); } /** * Set the screen content from a layout resource. The resource will be * inflated, adding all top-level views to the screen. * * @param layoutResID Resource ID to be inflated. */ public void setContentView(int layoutResID) { LayoutInflater inflater = LayoutInflater.from(pWindowManager.getContext()); setContentView(inflater.inflate(layoutResID, null), null); } /** * Set the screen content to an explicit view. This view is placed * directly into the screen's view hierarchy. It can itself be a complex * view hierarhcy. * * @param view The desired content to display. */ public void setContentView(View view) { setContentView(view, null); } /** * Set the screen content to an explicit view. This view is placed * directly into the screen's view hierarchy. It can itself be a complex * view hierarhcy. * * @param view The desired content to display. * @param params Layout parameters for the view. */ public void setContentView(View view, ViewGroup.LayoutParams params) { if (!(mLayout instanceof ViewGroup)) return; ViewGroup mDecor = (ViewGroup) mLayout; ViewGroup mContent = (ViewGroup) mDecor.findViewById(android.R.id.content); if (null == mContent) return; if (null == params) mContent.addView(view); else mContent.addView(view, params); } // ========== Listeners ========== // Imported from /android/app/Dialog.java /** * Set a listener to be invoked when the dialog is dismissed. * @param listener The {@link DialogInterface.OnDismissListener} to use. */ public void setOnDismissListener(OnDismissListener listener) { if (listener != null) { mDismissMessage = mListenersHandler.obtainMessage(DISMISS, listener); } else { mDismissMessage = null; } } /** * Sets a listener to be invoked when the dialog is shown. * @param listener The {@link DialogInterface.OnShowListener} to use. */ public void setOnShowListener(OnShowListener listener) { if (listener != null) { mShowMessage = mListenersHandler.obtainMessage(SHOW, listener); } else { mShowMessage = null; } } /** * Set a listener to be invoked when the dialog is canceled. * <p> * This will only be invoked when the dialog is canceled, if the creator * needs to know when it is dismissed in general, use * {@link #setOnDismissListener}. * * @param listener The {@link DialogInterface.OnCancelListener} to use. */ public void setOnCancelListener(OnCancelListener listener) { if (listener != null) { mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener); } else { mCancelMessage = null; } } private static final class ListenersHandler extends Handler { private WeakReference<DialogInterface> mDialog; public ListenersHandler(PopupDialog dialog) { mDialog = new WeakReference<DialogInterface>(dialog); } @Override public void handleMessage(Message msg) { switch (msg.what) { case DISMISS: ((OnDismissListener) msg.obj).onDismiss(mDialog.get()); break; case CANCEL: ((OnCancelListener) msg.obj).onCancel(mDialog.get()); break; case SHOW: ((OnShowListener) msg.obj).onShow(mDialog.get()); break; } } } }