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
onCreate(Bundle savedInstanceState)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 
initPreviewScroller()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 
refreshRemoteViews(ViewGroup parent, RemoteViews remoteViews)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
onCreateOptionsMenu(Menu menu)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
onOptionsItemSelected(MenuItem item)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
dispatchTouchEvent(MotionEvent event)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