1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.egg.neko; 16 17 import android.Manifest; 18 import android.app.ActionBar; 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.content.DialogInterface.OnClickListener; 24 import android.content.Intent; 25 import android.content.pm.PackageManager; 26 import android.graphics.Bitmap; 27 import android.graphics.Color; 28 import android.graphics.drawable.Drawable; 29 import android.media.MediaScannerConnection; 30 import android.net.Uri; 31 import android.os.Bundle; 32 import android.os.Environment; 33 import android.provider.MediaStore.Images; 34 import android.support.v4.content.FileProvider; 35 import android.support.v7.widget.GridLayoutManager; 36 import android.support.v7.widget.RecyclerView; 37 import android.util.Log; 38 import android.view.ContextThemeWrapper; 39 import android.view.LayoutInflater; 40 import android.view.View; 41 import android.view.View.OnLongClickListener; 42 import android.view.ViewGroup; 43 import android.widget.EditText; 44 import android.widget.ImageView; 45 import android.widget.TextView; 46 47 import com.android.egg.R; 48 import com.android.egg.neko.PrefState.PrefsListener; 49 import com.android.internal.logging.MetricsLogger; 50 51 import java.io.File; 52 import java.io.FileOutputStream; 53 import java.io.IOException; 54 import java.io.OutputStream; 55 import java.util.Collections; 56 import java.util.Comparator; 57 import java.util.List; 58 59 public class NekoLand extends Activity implements PrefsListener { 60 public static String CHAN_ID = "EGG"; 61 62 public static boolean DEBUG = false; 63 public static boolean DEBUG_NOTIFICATIONS = false; 64 65 private static final int EXPORT_BITMAP_SIZE = 600; 66 67 private static final int STORAGE_PERM_REQUEST = 123; 68 69 private static boolean CAT_GEN = false; 70 private PrefState mPrefs; 71 private CatAdapter mAdapter; 72 private Cat mPendingShareCat; 73 74 75 @Override onCreate(Bundle savedInstanceState)76 public void onCreate(Bundle savedInstanceState) { 77 super.onCreate(savedInstanceState); 78 setContentView(R.layout.neko_activity); 79 final ActionBar actionBar = getActionBar(); 80 if (actionBar != null) { 81 actionBar.setLogo(Cat.create(this)); 82 actionBar.setDisplayUseLogoEnabled(false); 83 actionBar.setDisplayShowHomeEnabled(true); 84 } 85 86 mPrefs = new PrefState(this); 87 mPrefs.setListener(this); 88 final RecyclerView recyclerView = findViewById(R.id.holder); 89 mAdapter = new CatAdapter(); 90 recyclerView.setAdapter(mAdapter); 91 recyclerView.setLayoutManager(new GridLayoutManager(this, 3)); 92 int numCats = updateCats(); 93 MetricsLogger.histogram(this, "egg_neko_visit_gallery", numCats); 94 } 95 96 @Override onDestroy()97 protected void onDestroy() { 98 super.onDestroy(); 99 mPrefs.setListener(null); 100 } 101 updateCats()102 private int updateCats() { 103 Cat[] cats; 104 if (CAT_GEN) { 105 cats = new Cat[50]; 106 for (int i = 0; i < cats.length; i++) { 107 cats[i] = Cat.create(this); 108 } 109 } else { 110 final float[] hsv = new float[3]; 111 List<Cat> list = mPrefs.getCats(); 112 Collections.sort(list, new Comparator<Cat>() { 113 @Override 114 public int compare(Cat cat, Cat cat2) { 115 Color.colorToHSV(cat.getBodyColor(), hsv); 116 float bodyH1 = hsv[0]; 117 Color.colorToHSV(cat2.getBodyColor(), hsv); 118 float bodyH2 = hsv[0]; 119 return Float.compare(bodyH1, bodyH2); 120 } 121 }); 122 cats = list.toArray(new Cat[0]); 123 } 124 mAdapter.setCats(cats); 125 return cats.length; 126 } 127 onCatClick(Cat cat)128 private void onCatClick(Cat cat) { 129 if (CAT_GEN) { 130 mPrefs.addCat(cat); 131 new AlertDialog.Builder(NekoLand.this) 132 .setTitle("Cat added") 133 .setPositiveButton(android.R.string.ok, null) 134 .show(); 135 } else { 136 showNameDialog(cat); 137 } 138 // noman.notify(1, cat.buildNotification(NekoLand.this).build()); 139 } 140 onCatRemove(Cat cat)141 private void onCatRemove(Cat cat) { 142 cat.logRemove(this); 143 mPrefs.removeCat(cat); 144 } 145 showNameDialog(final Cat cat)146 private void showNameDialog(final Cat cat) { 147 final Context context = new ContextThemeWrapper(this, 148 android.R.style.Theme_Material_Light_Dialog_NoActionBar); 149 // TODO: Move to XML, add correct margins. 150 View view = LayoutInflater.from(context).inflate(R.layout.edit_text, null); 151 final EditText text = (EditText) view.findViewById(android.R.id.edit); 152 text.setText(cat.getName()); 153 text.setSelection(cat.getName().length()); 154 final int size = context.getResources() 155 .getDimensionPixelSize(android.R.dimen.app_icon_size); 156 Drawable catIcon = cat.createIcon(this, size, size).loadDrawable(this); 157 new AlertDialog.Builder(context) 158 .setTitle(" ") 159 .setIcon(catIcon) 160 .setView(view) 161 .setPositiveButton(android.R.string.ok, new OnClickListener() { 162 @Override 163 public void onClick(DialogInterface dialog, int which) { 164 cat.logRename(context); 165 cat.setName(text.getText().toString().trim()); 166 mPrefs.addCat(cat); 167 } 168 }).show(); 169 } 170 171 @Override onPrefsChanged()172 public void onPrefsChanged() { 173 updateCats(); 174 } 175 176 private class CatAdapter extends RecyclerView.Adapter<CatHolder> { 177 178 private Cat[] mCats; 179 setCats(Cat[] cats)180 public void setCats(Cat[] cats) { 181 mCats = cats; 182 notifyDataSetChanged(); 183 } 184 185 @Override onCreateViewHolder(ViewGroup parent, int viewType)186 public CatHolder onCreateViewHolder(ViewGroup parent, int viewType) { 187 return new CatHolder(LayoutInflater.from(parent.getContext()) 188 .inflate(R.layout.cat_view, parent, false)); 189 } 190 setContextGroupVisible(final CatHolder holder, boolean vis)191 private void setContextGroupVisible(final CatHolder holder, boolean vis) { 192 final View group = holder.contextGroup; 193 if (vis && group.getVisibility() != View.VISIBLE) { 194 group.setAlpha(0); 195 group.setVisibility(View.VISIBLE); 196 group.animate().alpha(1.0f).setDuration(333); 197 Runnable hideAction = new Runnable() { 198 @Override 199 public void run() { 200 setContextGroupVisible(holder, false); 201 } 202 }; 203 group.setTag(hideAction); 204 group.postDelayed(hideAction, 5000); 205 } else if (!vis && group.getVisibility() == View.VISIBLE) { 206 group.removeCallbacks((Runnable) group.getTag()); 207 group.animate().alpha(0f).setDuration(250).withEndAction(new Runnable() { 208 @Override 209 public void run() { 210 group.setVisibility(View.INVISIBLE); 211 } 212 }); 213 } 214 } 215 216 @Override onBindViewHolder(final CatHolder holder, int position)217 public void onBindViewHolder(final CatHolder holder, int position) { 218 Context context = holder.itemView.getContext(); 219 final int size = context.getResources().getDimensionPixelSize(R.dimen.neko_display_size); 220 holder.imageView.setImageIcon(mCats[position].createIcon(context, size, size)); 221 holder.textView.setText(mCats[position].getName()); 222 holder.itemView.setOnClickListener(new View.OnClickListener() { 223 @Override 224 public void onClick(View v) { 225 onCatClick(mCats[holder.getAdapterPosition()]); 226 } 227 }); 228 holder.itemView.setOnLongClickListener(new OnLongClickListener() { 229 @Override 230 public boolean onLongClick(View v) { 231 setContextGroupVisible(holder, true); 232 return true; 233 } 234 }); 235 holder.delete.setOnClickListener(new View.OnClickListener() { 236 @Override 237 public void onClick(View v) { 238 setContextGroupVisible(holder, false); 239 new AlertDialog.Builder(NekoLand.this) 240 .setTitle(getString(R.string.confirm_delete, mCats[position].getName())) 241 .setNegativeButton(android.R.string.cancel, null) 242 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 243 @Override 244 public void onClick(DialogInterface dialog, int which) { 245 onCatRemove(mCats[holder.getAdapterPosition()]); 246 } 247 }) 248 .show(); 249 } 250 }); 251 holder.share.setOnClickListener(new View.OnClickListener() { 252 @Override 253 public void onClick(View v) { 254 setContextGroupVisible(holder, false); 255 Cat cat = mCats[holder.getAdapterPosition()]; 256 if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) 257 != PackageManager.PERMISSION_GRANTED) { 258 mPendingShareCat = cat; 259 requestPermissions( 260 new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 261 STORAGE_PERM_REQUEST); 262 return; 263 } 264 shareCat(cat); 265 } 266 }); 267 } 268 269 @Override getItemCount()270 public int getItemCount() { 271 return mCats.length; 272 } 273 } 274 shareCat(Cat cat)275 private void shareCat(Cat cat) { 276 final File dir = new File( 277 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), 278 getString(R.string.directory_name)); 279 if (!dir.exists() && !dir.mkdirs()) { 280 Log.e("NekoLand", "save: error: can't create Pictures directory"); 281 return; 282 } 283 final File png = new File(dir, cat.getName().replaceAll("[/ #:]+", "_") + ".png"); 284 Bitmap bitmap = cat.createBitmap(EXPORT_BITMAP_SIZE, EXPORT_BITMAP_SIZE); 285 if (bitmap != null) { 286 try { 287 OutputStream os = new FileOutputStream(png); 288 bitmap.compress(Bitmap.CompressFormat.PNG, 0, os); 289 os.close(); 290 MediaScannerConnection.scanFile( 291 this, 292 new String[] {png.toString()}, 293 new String[] {"image/png"}, 294 null); 295 Log.v("Neko", "cat file: " + png); 296 Uri uri = FileProvider.getUriForFile(this, "com.android.egg.fileprovider", png); 297 Log.v("Neko", "cat uri: " + uri); 298 Intent intent = new Intent(Intent.ACTION_SEND); 299 intent.putExtra(Intent.EXTRA_STREAM, uri); 300 intent.putExtra(Intent.EXTRA_SUBJECT, cat.getName()); 301 intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 302 intent.setType("image/png"); 303 startActivity(Intent.createChooser(intent, null)); 304 cat.logShare(this); 305 } catch (IOException e) { 306 Log.e("NekoLand", "save: error: " + e); 307 } 308 } 309 } 310 311 @Override onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)312 public void onRequestPermissionsResult(int requestCode, 313 String permissions[], int[] grantResults) { 314 if (requestCode == STORAGE_PERM_REQUEST) { 315 if (mPendingShareCat != null) { 316 shareCat(mPendingShareCat); 317 mPendingShareCat = null; 318 } 319 } 320 } 321 322 private static class CatHolder extends RecyclerView.ViewHolder { 323 private final ImageView imageView; 324 private final TextView textView; 325 private final View contextGroup; 326 private final View delete; 327 private final View share; 328 CatHolder(View itemView)329 public CatHolder(View itemView) { 330 super(itemView); 331 imageView = (ImageView) itemView.findViewById(android.R.id.icon); 332 textView = (TextView) itemView.findViewById(android.R.id.title); 333 contextGroup = itemView.findViewById(R.id.contextGroup); 334 delete = itemView.findViewById(android.R.id.closeButton); 335 share = itemView.findViewById(android.R.id.shareText); 336 } 337 } 338 } 339