1 /* 2 * Copyright 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.notificationstudio; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.app.Activity; 22 import android.app.Notification; 23 import android.app.NotificationManager; 24 import android.content.Context; 25 import android.os.Build; 26 import android.os.Bundle; 27 import android.os.Handler; 28 import android.util.DisplayMetrics; 29 import android.util.Log; 30 import android.view.Menu; 31 import android.view.MenuInflater; 32 import android.view.MenuItem; 33 import android.view.MotionEvent; 34 import android.view.View; 35 import android.view.View.OnLayoutChangeListener; 36 import android.view.ViewGroup; 37 import android.view.inputmethod.InputMethodManager; 38 import android.widget.EditText; 39 import android.widget.FrameLayout; 40 import android.widget.FrameLayout.LayoutParams; 41 import android.widget.ImageView; 42 import android.widget.LinearLayout; 43 import android.widget.RemoteViews; 44 import android.widget.TextView; 45 46 import com.android.notificationstudio.action.ShareCodeAction; 47 import com.android.notificationstudio.action.ShareMockupAction; 48 import com.android.notificationstudio.editor.Editors; 49 import com.android.notificationstudio.generator.NotificationGenerator; 50 import com.android.notificationstudio.model.EditableItem; 51 import com.android.notificationstudio.model.EditableItemConstants; 52 53 import java.util.concurrent.ExecutorService; 54 import java.util.concurrent.Executors; 55 56 public class NotificationStudioActivity extends Activity implements EditableItemConstants{ 57 private static final String TAG = NotificationStudioActivity.class.getSimpleName(); 58 private static final int PREVIEW_NOTIFICATION = 1; 59 private static final int REFRESH_DELAY = 50; 60 private static final ExecutorService BACKGROUND = Executors.newSingleThreadExecutor(); 61 62 private boolean mRefreshPending; 63 64 private final Handler mHandler = new Handler(); 65 private final Runnable mRefreshNotificationInner = new Runnable() { 66 public void run() { 67 refreshNotificationInner(); 68 }}; 69 70 @Override 71 public void onCreate(Bundle savedInstanceState) { 72 super.onCreate(savedInstanceState); 73 74 getWindow().setBackgroundDrawableResource(android.R.color.black); 75 setContentView(R.layout.studio); 76 initPreviewScroller(); 77 78 EditableItem.initIfNecessary(this); 79 80 initEditors(); 81 } 82 83 private void initPreviewScroller() { 84 85 MaxHeightScrollView preview = (MaxHeightScrollView) findViewById(R.id.preview_scroller); 86 if (preview == null) 87 return; 88 final int margin = ((ViewGroup.MarginLayoutParams) preview.getLayoutParams()).bottomMargin; 89 preview.addOnLayoutChangeListener(new OnLayoutChangeListener(){ 90 public void onLayoutChange(View v, int left, int top, int right, int bottom, 91 int oldLeft, int oldTop, int oldRight, int oldBottom) { 92 // animate preview height changes 93 if (oldBottom != bottom) { 94 final View e = findViewById(R.id.editors); 95 final int y = bottom + margin; 96 e.animate() 97 .translationY(y - oldBottom) 98 .setListener(new AnimatorListenerAdapter() { 99 public void onAnimationEnd(Animator animation) { 100 FrameLayout.LayoutParams lp = (LayoutParams) e.getLayoutParams(); 101 lp.topMargin = y; 102 e.setTranslationY(0); 103 e.setLayoutParams(lp); 104 } 105 }); 106 } 107 }}); 108 109 // limit the max height for preview, leave room for editors + soft keyboard 110 DisplayMetrics dm = new DisplayMetrics(); 111 getWindowManager().getDefaultDisplay().getMetrics(dm); 112 float actualHeight = dm.heightPixels / dm.ydpi; 113 float pct = actualHeight < 3.5 ? .32f : 114 actualHeight < 4 ? .35f : 115 .38f; 116 preview.setMaxHeight((int)(dm.heightPixels * pct)); 117 } 118 119 private void initEditors() { 120 LinearLayout items = (LinearLayout) findViewById(R.id.items); 121 items.removeAllViews(); 122 String currentCategory = null; 123 for (EditableItem item : EditableItem.values()) { 124 String itemCategory = item.getCategory(this); 125 if (!itemCategory.equals(currentCategory)) { 126 View dividerView = getLayoutInflater().inflate(R.layout.divider, null); 127 ((TextView) dividerView.findViewById(R.id.divider_text)).setText(itemCategory); 128 items.addView(dividerView); 129 currentCategory = itemCategory; 130 } 131 View editorView = Editors.newEditor(this, items, item); 132 if (editorView != null) 133 items.addView(editorView); 134 } 135 refreshNotification(); 136 } 137 138 @Override 139 protected void onRestoreInstanceState(Bundle savedInstanceState) { 140 // we'll take care of restoring state 141 } 142 143 public void refreshNotification() { 144 mRefreshPending = true; 145 mHandler.postDelayed(mRefreshNotificationInner, REFRESH_DELAY); 146 } 147 148 private void refreshNotificationInner() { 149 if (!mRefreshPending) { 150 return; 151 } 152 final Notification notification = NotificationGenerator.build(this); 153 ViewGroup oneU = (ViewGroup) findViewById(R.id.oneU); 154 ViewGroup fourU = (ViewGroup) findViewById(R.id.fourU); 155 View oneUView = refreshRemoteViews(oneU, notification.contentView); 156 if (Build.VERSION.SDK_INT >= 16) 157 refreshRemoteViews(fourU, notification.bigContentView); 158 else if (Build.VERSION.SDK_INT >= 11) { 159 ImageView largeIcon = (ImageView) findViewById(R.id.large_icon); 160 largeIcon.setVisibility(notification.largeIcon == null ? View.GONE : View.VISIBLE); 161 if (notification.largeIcon != null) 162 largeIcon.setImageBitmap(notification.largeIcon); 163 } else if (oneUView != null) { 164 oneUView.setBackgroundColor(getResources().getColor(R.color.gb_background)); 165 oneUView.setMinimumHeight(100); 166 } 167 mRefreshPending = false; 168 169 // this can take a while, run on a background thread 170 BACKGROUND.submit(new Runnable() { 171 public void run() { 172 NotificationManager mgr = 173 (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 174 try { 175 mgr.notify(PREVIEW_NOTIFICATION, notification); 176 } catch (Throwable t) { 177 Log.w(TAG, "Error displaying notification", t); 178 } 179 }}); 180 } 181 182 private View refreshRemoteViews(ViewGroup parent, RemoteViews remoteViews) { 183 parent.removeAllViews(); 184 if (remoteViews != null) { 185 parent.setVisibility(View.VISIBLE); 186 try { 187 View v = remoteViews.apply(this, parent); 188 parent.addView(v); 189 return v; 190 } catch (Exception e) { 191 TextView exceptionView = new TextView(this); 192 exceptionView.setText(e.getClass().getSimpleName() + ": " + e.getMessage()); 193 parent.addView(exceptionView); 194 return exceptionView; 195 } 196 } else { 197 parent.setVisibility(View.GONE); 198 return null; 199 } 200 } 201 202 // action bar setup 203 @Override 204 public boolean onCreateOptionsMenu(Menu menu) { 205 MenuInflater inflater = getMenuInflater(); 206 inflater.inflate(R.menu.action_bar, menu); 207 return true; 208 } 209 210 @Override 211 public boolean onOptionsItemSelected(MenuItem item) { 212 switch (item.getItemId()) { 213 case R.id.action_share_code: 214 ShareCodeAction.launch(this, item.getTitle()); 215 return true; 216 case R.id.action_share_mockup: 217 ShareMockupAction.launch(this, item.getTitle()); 218 return true; 219 } 220 return false; 221 } 222 223 // hides the soft keyboard more aggressively when leaving text editors 224 @Override 225 public boolean dispatchTouchEvent(MotionEvent event) { 226 View v = getCurrentFocus(); 227 boolean ret = super.dispatchTouchEvent(event); 228 229 if (v instanceof EditText) { 230 View currentFocus = getCurrentFocus(); 231 int screenCoords[] = new int[2]; 232 currentFocus.getLocationOnScreen(screenCoords); 233 float x = event.getRawX() + currentFocus.getLeft() - screenCoords[0]; 234 float y = event.getRawY() + currentFocus.getTop() - screenCoords[1]; 235 236 if (event.getAction() == MotionEvent.ACTION_UP 237 && (x < currentFocus.getLeft() || 238 x >= currentFocus.getRight() || 239 y < currentFocus.getTop() || 240 y > currentFocus.getBottom())) { 241 InputMethodManager imm = 242 (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 243 imm.hideSoftInputFromWindow(getWindow().getCurrentFocus().getWindowToken(), 0); 244 } 245 } 246 return ret; 247 } 248 249 } 250