1 /* 2 * Copyright (C) 2011 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.example.android.hcgallery; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.animation.PropertyValuesHolder; 23 import android.animation.ValueAnimator; 24 import android.app.ActionBar; 25 import android.app.Activity; 26 import android.app.AlertDialog; 27 import android.app.Dialog; 28 import android.app.DialogFragment; 29 import android.app.FragmentManager; 30 import android.app.FragmentTransaction; 31 import android.app.Notification; 32 import android.app.NotificationManager; 33 import android.app.PendingIntent; 34 import android.content.DialogInterface; 35 import android.content.Intent; 36 import android.content.pm.PackageManager; 37 import android.content.res.Configuration; 38 import android.content.res.Resources; 39 import android.graphics.Bitmap; 40 import android.graphics.BitmapFactory; 41 import android.os.Bundle; 42 import android.view.Menu; 43 import android.view.MenuInflater; 44 import android.view.MenuItem; 45 import android.view.View; 46 import android.view.ViewGroup; 47 import android.widget.RemoteViews; 48 49 /** This is the main "launcher" activity. 50 * When running on a "large" or larger screen, this activity displays both the 51 * TitlesFragments and the Content Fragment. When on a smaller screen size, this 52 * activity displays only the TitlesFragment. In which case, selecting a list 53 * item opens the ContentActivity, holds only the ContentFragment. */ 54 public class MainActivity extends Activity implements TitlesFragment.OnItemSelectedListener { 55 56 private Animator mCurrentTitlesAnimator; 57 private String[] mToggleLabels = {"Show Titles", "Hide Titles"}; 58 private static final int NOTIFICATION_DEFAULT = 1; 59 private static final String ACTION_DIALOG = "com.example.android.hcgallery.action.DIALOG"; 60 private int mThemeId = -1; 61 private boolean mDualFragments = false; 62 private boolean mTitlesHidden = false; 63 64 @Override onCreate(Bundle savedInstanceState)65 public void onCreate(Bundle savedInstanceState) { 66 super.onCreate(savedInstanceState); 67 68 if(savedInstanceState != null) { 69 if (savedInstanceState.getInt("theme", -1) != -1) { 70 mThemeId = savedInstanceState.getInt("theme"); 71 this.setTheme(mThemeId); 72 } 73 mTitlesHidden = savedInstanceState.getBoolean("titlesHidden"); 74 } 75 76 setContentView(R.layout.main); 77 78 ActionBar bar = getActionBar(); 79 bar.setDisplayShowTitleEnabled(false); 80 81 ContentFragment frag = (ContentFragment) getFragmentManager() 82 .findFragmentById(R.id.content_frag); 83 if (frag != null) mDualFragments = true; 84 85 if (mTitlesHidden) { 86 getFragmentManager().beginTransaction() 87 .hide(getFragmentManager().findFragmentById(R.id.titles_frag)).commit(); 88 } 89 } 90 91 @Override onCreateOptionsMenu(Menu menu)92 public boolean onCreateOptionsMenu(Menu menu) { 93 MenuInflater inflater = getMenuInflater(); 94 inflater.inflate(R.menu.main_menu, menu); 95 // If the device doesn't support camera, remove the camera menu item 96 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) { 97 menu.removeItem(R.id.menu_camera); 98 } 99 return true; 100 } 101 102 @Override onPrepareOptionsMenu(Menu menu)103 public boolean onPrepareOptionsMenu(Menu menu) { 104 // If not showing both fragments, remove the "toggle titles" menu item 105 if (!mDualFragments) { 106 menu.removeItem(R.id.menu_toggleTitles); 107 } else { 108 menu.findItem(R.id.menu_toggleTitles).setTitle(mToggleLabels[mTitlesHidden ? 0 : 1]); 109 } 110 return super.onPrepareOptionsMenu(menu); 111 } 112 113 @Override onOptionsItemSelected(MenuItem item)114 public boolean onOptionsItemSelected(MenuItem item) { 115 switch (item.getItemId()) { 116 case R.id.menu_camera: 117 Intent intent = new Intent(this, CameraActivity.class); 118 intent.putExtra("theme", mThemeId); 119 startActivity(intent); 120 return true; 121 122 case R.id.menu_toggleTitles: 123 toggleVisibleTitles(); 124 return true; 125 126 case R.id.menu_toggleTheme: 127 if (mThemeId == R.style.AppTheme_Dark) { 128 mThemeId = R.style.AppTheme_Light; 129 } else { 130 mThemeId = R.style.AppTheme_Dark; 131 } 132 this.recreate(); 133 return true; 134 135 case R.id.menu_showDialog: 136 showDialog("This is indeed an awesome dialog."); 137 return true; 138 139 case R.id.menu_showStandardNotification: 140 showNotification(false); 141 return true; 142 143 case R.id.menu_showCustomNotification: 144 showNotification(true); 145 return true; 146 147 default: 148 return super.onOptionsItemSelected(item); 149 } 150 } 151 152 /** Respond to the "toogle titles" item in the action bar */ toggleVisibleTitles()153 public void toggleVisibleTitles() { 154 // Use these for custom animations. 155 final FragmentManager fm = getFragmentManager(); 156 final TitlesFragment f = (TitlesFragment) fm 157 .findFragmentById(R.id.titles_frag); 158 final View titlesView = f.getView(); 159 160 // Determine if we're in portrait, and whether we're showing or hiding the titles 161 // with this toggle. 162 final boolean isPortrait = getResources().getConfiguration().orientation == 163 Configuration.ORIENTATION_PORTRAIT; 164 165 final boolean shouldShow = f.isHidden() || mCurrentTitlesAnimator != null; 166 167 // Cancel the current titles animation if there is one. 168 if (mCurrentTitlesAnimator != null) 169 mCurrentTitlesAnimator.cancel(); 170 171 // Begin setting up the object animator. We'll animate the bottom or right edge of the 172 // titles view, as well as its alpha for a fade effect. 173 ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder( 174 titlesView, 175 PropertyValuesHolder.ofInt( 176 isPortrait ? "bottom" : "right", 177 shouldShow ? getResources().getDimensionPixelSize(R.dimen.titles_size) 178 : 0), 179 PropertyValuesHolder.ofFloat("alpha", shouldShow ? 1 : 0) 180 ); 181 182 // At each step of the animation, we'll perform layout by calling setLayoutParams. 183 final ViewGroup.LayoutParams lp = titlesView.getLayoutParams(); 184 objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 185 public void onAnimationUpdate(ValueAnimator valueAnimator) { 186 // *** WARNING ***: triggering layout at each animation frame highly impacts 187 // performance so you should only do this for simple layouts. More complicated 188 // layouts can be better served with individual animations on child views to 189 // avoid the performance penalty of layout. 190 if (isPortrait) { 191 lp.height = (Integer) valueAnimator.getAnimatedValue(); 192 } else { 193 lp.width = (Integer) valueAnimator.getAnimatedValue(); 194 } 195 titlesView.setLayoutParams(lp); 196 } 197 }); 198 199 if (shouldShow) { 200 fm.beginTransaction().show(f).commit(); 201 objectAnimator.addListener(new AnimatorListenerAdapter() { 202 @Override 203 public void onAnimationEnd(Animator animator) { 204 mCurrentTitlesAnimator = null; 205 mTitlesHidden = false; 206 invalidateOptionsMenu(); 207 } 208 }); 209 210 } else { 211 objectAnimator.addListener(new AnimatorListenerAdapter() { 212 boolean canceled; 213 214 @Override 215 public void onAnimationCancel(Animator animation) { 216 canceled = true; 217 super.onAnimationCancel(animation); 218 } 219 220 @Override 221 public void onAnimationEnd(Animator animator) { 222 if (canceled) 223 return; 224 mCurrentTitlesAnimator = null; 225 fm.beginTransaction().hide(f).commit(); 226 mTitlesHidden = true; 227 invalidateOptionsMenu(); 228 } 229 }); 230 } 231 232 // Start the animation. 233 objectAnimator.start(); 234 mCurrentTitlesAnimator = objectAnimator; 235 236 // Manually trigger onNewIntent to check for ACTION_DIALOG. 237 onNewIntent(getIntent()); 238 } 239 240 @Override onNewIntent(Intent intent)241 protected void onNewIntent(Intent intent) { 242 if (ACTION_DIALOG.equals(intent.getAction())) { 243 showDialog(intent.getStringExtra(Intent.EXTRA_TEXT)); 244 } 245 } 246 showDialog(String text)247 void showDialog(String text) { 248 // DialogFragment.show() will take care of adding the fragment 249 // in a transaction. We also want to remove any currently showing 250 // dialog, so make our own transaction and take care of that here. 251 FragmentTransaction ft = getFragmentManager().beginTransaction(); 252 253 DialogFragment newFragment = MyDialogFragment.newInstance(text); 254 255 // Show the dialog. 256 newFragment.show(ft, "dialog"); 257 } 258 showNotification(boolean custom)259 void showNotification(boolean custom) { 260 final Resources res = getResources(); 261 final NotificationManager notificationManager = (NotificationManager) getSystemService( 262 NOTIFICATION_SERVICE); 263 264 Notification.Builder builder = new Notification.Builder(this) 265 .setSmallIcon(R.drawable.ic_stat_notify_example) 266 .setAutoCancel(true) 267 .setTicker(getString(R.string.notification_text)) 268 .setContentIntent(getDialogPendingIntent("Tapped the notification entry.")); 269 270 if (custom) { 271 // Sets a custom content view for the notification, including an image button. 272 RemoteViews layout = new RemoteViews(getPackageName(), R.layout.notification); 273 layout.setTextViewText(R.id.notification_title, getString(R.string.app_name)); 274 layout.setOnClickPendingIntent(R.id.notification_button, 275 getDialogPendingIntent("Tapped the 'dialog' button in the notification.")); 276 builder.setContent(layout); 277 278 // Notifications in Android 3.0 now have a standard mechanism for displaying large 279 // bitmaps such as contact avatars. Here, we load an example image and resize it to the 280 // appropriate size for large bitmaps in notifications. 281 Bitmap largeIconTemp = BitmapFactory.decodeResource(res, 282 R.drawable.notification_default_largeicon); 283 Bitmap largeIcon = Bitmap.createScaledBitmap( 284 largeIconTemp, 285 res.getDimensionPixelSize(android.R.dimen.notification_large_icon_width), 286 res.getDimensionPixelSize(android.R.dimen.notification_large_icon_height), 287 false); 288 largeIconTemp.recycle(); 289 290 builder.setLargeIcon(largeIcon); 291 292 } else { 293 builder 294 .setNumber(7) // An example number. 295 .setContentTitle(getString(R.string.app_name)) 296 .setContentText(getString(R.string.notification_text)); 297 } 298 299 notificationManager.notify(NOTIFICATION_DEFAULT, builder.getNotification()); 300 } 301 getDialogPendingIntent(String dialogText)302 PendingIntent getDialogPendingIntent(String dialogText) { 303 return PendingIntent.getActivity( 304 this, 305 dialogText.hashCode(), // Otherwise previous PendingIntents with the same 306 // requestCode may be overwritten. 307 new Intent(ACTION_DIALOG) 308 .putExtra(Intent.EXTRA_TEXT, dialogText) 309 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 310 0); 311 } 312 313 @Override onSaveInstanceState(Bundle outState)314 public void onSaveInstanceState (Bundle outState) { 315 super.onSaveInstanceState(outState); 316 outState.putInt("theme", mThemeId); 317 outState.putBoolean("titlesHidden", mTitlesHidden); 318 } 319 320 /** Implementation for TitlesFragment.OnItemSelectedListener. 321 * When the TitlesFragment receives an onclick event for a list item, 322 * it's passed back to this activity through this method so that we can 323 * deliver it to the ContentFragment in the manner appropriate */ onItemSelected(int category, int position)324 public void onItemSelected(int category, int position) { 325 if (!mDualFragments) { 326 // If showing only the TitlesFragment, start the ContentActivity and 327 // pass it the info about the selected item 328 Intent intent = new Intent(this, ContentActivity.class); 329 intent.putExtra("category", category); 330 intent.putExtra("position", position); 331 intent.putExtra("theme", mThemeId); 332 startActivity(intent); 333 } else { 334 // If showing both fragments, directly update the ContentFragment 335 ContentFragment frag = (ContentFragment) getFragmentManager() 336 .findFragmentById(R.id.content_frag); 337 frag.updateContentAndRecycleBitmap(category, position); 338 } 339 } 340 341 342 /** Dialog implementation that shows a simple dialog as a fragment */ 343 public static class MyDialogFragment extends DialogFragment { 344 newInstance(String title)345 public static MyDialogFragment newInstance(String title) { 346 MyDialogFragment frag = new MyDialogFragment(); 347 Bundle args = new Bundle(); 348 args.putString("text", title); 349 frag.setArguments(args); 350 return frag; 351 } 352 353 @Override onCreateDialog(Bundle savedInstanceState)354 public Dialog onCreateDialog(Bundle savedInstanceState) { 355 String text = getArguments().getString("text"); 356 357 return new AlertDialog.Builder(getActivity()) 358 .setTitle("A Dialog of Awesome") 359 .setMessage(text) 360 .setPositiveButton(android.R.string.ok, 361 new DialogInterface.OnClickListener() { 362 public void onClick(DialogInterface dialog, int whichButton) { 363 } 364 } 365 ) 366 .create(); 367 } 368 } 369 } 370