How to test code for Memory Leaks? - java

Help to understand whether there is a memory leak or not.
Fragment
public class NewsFragment extends Fragment {
//some code
private OnNewsFeedsContentClickListener onNewsFeedsContentClickListener = new OnNewsFeedsContentClickListener()
{
#Override
public void onClick(String sYoutubeId, ContentType type)
{
//some code
}
}
}
Adapter
public class HowToAdapter extends BaseAdapter {
public static final String TAG = "HowToAdapter";
private List<ContentSkill> mContentList = null;
private Context mContext = null;
private ImageLoader mImageLoader = null;
private OnNewsFeedsContentClickListener mOnNewsFeedsContentClickListener = null;
public HowToAdapter(Context context, List<ContentSkill> contetnList, OnNewsFeedsContentClickListener onNewsFeedsContentClickListener)
{
mContext = context;
mContentList = contetnList;
mImageLoader = new ImageLoader(mContext, ImageType.HOW_TO);
mOnNewsFeedsContentClickListener = onNewsFeedsContentClickListener;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
HowToView view = null;
if (convertView == null)
{
view = new HowToView(mContext);
}
else
{
view = (HowToView) convertView;
}
ContentSkill content = getItem(position);
if (content != null)
{
ViewHolder holder = (ViewHolder) view.getTag();
final String url = Utils.getYouTubeUrlImageFromUrl(content.getUrl());
final String youtubeId = Utils.getYoutubeIdFromUrl(content.getUrl());
mImageLoader.displayImage(url, holder.pIvThumb);
holder.pIvThumb.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
mOnNewsFeedsContentClickListener.onClick(youtubeId, ContentType.VIDEO);
}
});
}
return view;
}
}
Questions:
The adapter variable final String youtubeId will always be created,
but not killed? That is, each time getView() will be created but
the old youtubeId not clear from memory?
Each time call getView() OnClickListener will also be created, and the old
will be cleaned out of memory?
I think that the variables will be re-created each time and catch-up with the old memory.

Related

Cannot display Video using ListView - java.io.IOException:setDataSource failed

I have been trying to include Videos in list view but the videos cannot be played due to error in IOException. I've tried changing the video content and yet I still get the same result
The array that contains the text and the video path
String[] phraVideos ={
"android.resource://com.example.signit" + "/" + R.raw.verbs,
"android.resource://com.example.signit" + "/" + R.raw.eat
};
String[] phraText = {
"verbs",
"eat"
};
The modal class
package com.example.signit;
public class PhraseModel {
private String phraVideo;
private String phraText;
public PhraseModel(String phraVideo, String phraText) {
this.phraVideo = phraVideo;
this.phraText = phraText;
}
public String getPhraVideo() {
return phraVideo;
}
public void setPhraVideo(String phraVideo) {
this.phraVideo = phraVideo;
}
public String getPhraText() {
return phraText;
}
public void setPhraText(String phraText) {
this.phraText = phraText;
}
}
The custom adapter that I use and the error is in here(I assume)
public class PhraseAdapter extends BaseAdapter implements Filterable {
Context context;
ArrayList <PhraseModel> phraseModels; // data source
CustomeFilter filter;
ArrayList<PhraseModel> filterList;
public PhraseAdapter(Context context, ArrayList<PhraseModel> phraseModels) {
this.context = context;
this.phraseModels = phraseModels;
this.filterList = phraseModels;
}
#Override
public int getCount() {
return phraseModels.size();
}
#Override
public Object getItem(int position) {
return phraseModels.get(position); // return the position for a
single object
}
#Override
public long getItemId(int position) {
return phraseModels.indexOf(getItem(position));
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater =(LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if(convertView == null){
convertView = inflater.inflate(R.layout.phrasedisplaylist,null);
}
TextView contentText = (TextView)
convertView.findViewById(R.id.contentText);
VideoView contentVideo= (VideoView)
convertView.findViewById(R.id.contentVideo);
//Set data to the views
contentText.setText(phraseModels.get(position).getPhraText());
String videoPath = phraseModels.get(position).getPhraVideo();
Uri uri = Uri.parse(videoPath);
contentVideo.setVideoURI(uri);
contentVideo.start();
return convertView;
}

How to pass onClickHandler as parameter while setting adapter for a RecyclerView in a Fragment?

I made an Adapter for my recyclerView. This adapter works when I use on any xxxActivity.java but when I try to use is on a Fragment Its hows error. Doesn't let me Pass the onClickHandler() that I created in Adapter.
I am Setting Adapter Like this -
events_recyclerview.setAdapter(new FixedPlaceListAdapter(getContext(), placelist, mClickHandler)); //ClickListener doesn't work :'(
Another try was like --
events_recyclerview.setAdapter(new FixedPlaceListAdapter(getContext(), placelist, getmClickHandler()));
Here, I implemented getClickHandler() in the Fragment--
public FixedPlaceListAdapter.FixedPlaceListAdapterOnclickHandler getmClickHandler() {
return mClickHandler;
} //Still doesn't work :'(
and the Adapter Part--
Constructor like this-
public FixedPlaceListAdapter(Context mContext, List<PlaceBean>
placeBeanList, FixedPlaceListAdapterOnclickHandler mClickHandler) {
this.mContext = mContext;
this.placeBeanList = placeBeanList;
this.mClickHandler = mClickHandler;
}
I tried to do this ... but still doesn't work--
events_recyclerview.setAdapter(new FixedPlaceListAdapter(getContext(), placelist, FixedPlaceListAdapter.FixedPlaceListAdapterOnclickHandler.mClickhandler));
here is my full adapter code-
public class FixedPlaceListAdapter extends RecyclerView.Adapter<FixedPlaceListAdapter.FixedPlaceListAdapterViewHolder> {
private final FixedPlaceListAdapterOnclickHandler mClickHandler;
Context mContext;
List<PlaceBean> placeBeanList;
public FixedPlaceListAdapter(Context mContext, List<PlaceBean> placeBeanList, FixedPlaceListAdapterOnclickHandler mClickHandler) {
this.mContext = mContext;
this.placeBeanList = placeBeanList;
this.mClickHandler = mClickHandler;
}
public void setData(List<PlaceBean> placeBeanList) {
this.placeBeanList = placeBeanList;
notifyDataSetChanged();
}
#Override
public FixedPlaceListAdapterViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(mContext);
View view = inflater.inflate(R.layout.top_list_single, parent, false);
return new FixedPlaceListAdapterViewHolder(view);
}
#Override
public void onBindViewHolder(FixedPlaceListAdapterViewHolder holder, int position) {
PlaceBean pb = placeBeanList.get(position);
holder.nameTextView.setText(pb.getName());
holder.addressTextView.setText(pb.getVicinity());
holder.rating.setRating(pb.getRating());
if (pb.getPhotoref() != null) {
String imageUrl = UrlsUtil.getSinglePhotoUrlString(mContext, pb.getPhotoref(), "350", "300");
Picasso.with(mContext)
.load(imageUrl)
.into(holder.thumbnailImage);
}
}
#Override
public int getItemCount() {
return placeBeanList.size();
}
public interface FixedPlaceListAdapterOnclickHandler {
void onClick(String id);
}
public class FixedPlaceListAdapterViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView nameTextView;
TextView addressTextView;
RatingBar rating;
ImageView thumbnailImage;
public FixedPlaceListAdapterViewHolder(View itemView) {
super(itemView);
nameTextView = (TextView) itemView.findViewById(R.id.place_name_now_in_list);
addressTextView = (TextView) itemView.findViewById(R.id.address_in_list);
rating = (RatingBar) itemView.findViewById(R.id.rating_single_place_in_list);
thumbnailImage = (ImageView) itemView.findViewById(R.id.place_image_thumb);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
String placeID = placeBeanList.get(getAdapterPosition()).getPlaceref();
mClickHandler.onClick(placeID);
}
}
}
Need Help!
public class CategoryNewsListAdapter extends RecyclerView.Adapter<CategoryNewsListAdapter.ViewHolder> {
private List<CategoryNews> actors = new ArrayList<>();
private Context mContext;
private Queue<List<CategoryNews>> pendingUpdates =
new ArrayDeque<>();
**public interface NewsItemClickListener {
void onNewsItemClick(int pos, CategoryNews categoryNews, ImageView shareImageView);
}**
**NewsItemClickListener newsItemClickListener;**
public CategoryNewsListAdapter(List<CategoryNews> personList, Context mContext, NewsItemClickListener newsItemClickListener) {
this.actors.addAll(personList);
this.mContext = mContext;
this.newsItemClickListener = newsItemClickListener;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final View view = inflater.inflate(R.layout.particular_cat_news_list_row, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Log.d("BusinessSubCategoryListAdapter", "Bindviewholder without payload");
final CategoryNews newsCategory = actors.get(position);
holder.news_title.setText(newsCategory.getPostTitle());
holder.news_date.setText(newsCategory.getPostDate());
holder.news_category.setText(newsCategory.getCategoryName());
holder.news_description.setText(newsCategory.getPostContent());
// GlideApp
// .with(mContext).load(newsCategory.getPostImage())
// .placeholder(R.mipmap.ic_launcher) // can also be a drawable
// .error(R.drawable.placeholder_image) // will be displayed if the image cannot be loaded
// .crossFade()
// .into(holder.news_image);
Picasso.with(mContext).load(newsCategory.getPostImage()).placeholder(R.drawable.placeholder_image).error(R.drawable.placeholder_image).into(holder.news_image);
**holder.itemView.setOnClickListener(v -> {
newsItemClickListener.onNewsItemClick(position, newsCategory, holder.news_image);
});**
}
#Override
public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) {
if (payloads.isEmpty()) {
// if empty, do full binding;
onBindViewHolder(holder, position);
return;
}
Bundle bundle = (Bundle) payloads.get(0);
String newTitle = bundle.getString("NAME_CHANGE");
if (newTitle != null) {
// add some animation if you want
final CategoryNews actor = actors.get(position);
holder.news_title.setText(actor.getPostTitle());
holder.news_date.setText(actor.getPostDate());
holder.news_category.setText(actor.getCategoryName());
holder.news_description.setText(actor.getPostContent());
}
}
public void addItems(List<CategoryNews> newItems) {
// record this value before making any changes to the existing list
int curSize = getItemCount();
// update the existing list
actors.addAll(newItems);
// curSize should represent the first element that got added
// newItems.size() represents the itemCount
notifyItemRangeInserted(curSize, newItems.size());
}
public void updtateItems(List<CategoryNews> newItems) {
// record this value before making any changes to the existing list
int curSize = getItemCount();
// update the existing list
actors.addAll(newItems);
// curSize should represent the first element that got added
// newItems.size() represents the itemCount
notifyItemRangeInserted(0,newItems.size()- getItemCount()-1);
}
#Override
public int getItemCount() {
return actors.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private TextView news_title, news_date, news_category, news_description;
private ImageView news_image;
public ViewHolder(View itemView) {
super(itemView);
news_title = (TextView) itemView.findViewById(R.id.news_title);
news_date = (TextView) itemView.findViewById(R.id.news_date);
news_category = (TextView) itemView.findViewById(R.id.news_category);
news_description = (TextView) itemView.findViewById(R.id.news_description);
news_image = (ImageView) itemView.findViewById(R.id.news_image);
}
}
}
And In your activity
implements NewsItemClickListener
create object NewsItemClickListener newsItemClickListner;
inside oncreate newsItemClickListner=this; (you mush have implemented 1)
pass that object to recyclerview.
In adapter it is received by this
public CategoryNewsListAdapter(List personList, Context mContext, NewsItemClickListener newsItemClickListener) {
this.actors.addAll(personList);
this.mContext = mContext;
this.newsItemClickListener = newsItemClickListener;
}
In your activity
CategoryNewsListAdapter categoryNewsListAdapter= new CategoryNewsListAdapter(list,this,newsItemClickListner)
After doing all this your overriden method will be called when you click in item of recycler view where you have set onclick listner
Okay .. The Problem is Fixed now ...
I was passing the wrong value or maybe the method of passing the ClickHandler was wrong.
The Solution of the Problem :
I Created another Class for ClickHandler-
public class PlaceCardClickHandler implements
FixedPlaceListAdapter.FixedPlaceListAdapterOnclickHandler,
PlaceListAdapter.PlaceListAdapterOnclickHandler {
Context mContext;
public PlaceCardClickHandler(Context mContext) {
this.mContext = mContext;
}
#Override
public void onClick(String id) {
Intent intentToStartDetail = new Intent(mContext, PlaceDetailActivity.class);
intentToStartDetail.putExtra("id", id);
mContext.startActivity(intentToStartDetail);
}
}
and the change in the Fragment was like this- (Just passed a object from the ClickHandler class)
events_recyclerview.setAdapter(new FixedPlaceListAdapter(getContext(), placelist, new PlaceCardClickHandler(getContext())));

Changing image dynamically on a dynamic list on Android

I have created an app with a listview with arrayadapter that the user can dynamically populate. I store the info entered as a json string in the preferences and when I refresh the app, I get the list with the entries. The thing is that I want the image next to each entry to change after a network operation. The problem I'm facing seems to be that since the elements in the list are added dynamically, I dont seem to find a good way neither to update the imageview on the onPostExecute() method, either to be able to target each entry specifically since they share the same layout ids.
Here is my getView() method inside my adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.product_list_item, null);
holder = new ViewHolder();
holder.deviceName = (TextView) convertView
.findViewById(R.id.txt_pc_name);
holder.deviceIp = (TextView) convertView
.findViewById(R.id.txt_pdt_desc);
holder.devicePort = (TextView) convertView
.findViewById(R.id.txt_pdt_price);
holder.favoriteImg = (ImageView) convertView
.findViewById(R.id.imgbtn_favorite);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Devices device = (Devices) getItem(position);
holder.deviceName.setText(device.getName());
holder.deviceIp.setText(device.getIpOnline());
holder.devicePort.setText(device.getPortOnline() + "");
return convertView;
}
Here is my AsyncTask:
public class Connection extends AsyncTask<String, Void, String> {
private String ipOnline;
private int portOnline;
private String ipWol;
private int portWol;
private String macAddress;
private ImageView img;
private Context context;
public interface AsyncResponse {
void processFinish(String output);
}
public AsyncResponse delegate = null;
public Connection(Context mContext, ImageView Img,String IpOnline, int PortOnline, String IpWol, int PortWol, String MacAddress, AsyncResponse delegate) {
ipOnline = IpOnline;
portOnline = PortOnline;
ipWol = IpWol;
portWol = PortWol;
macAddress = MacAddress;
context = mContext;
// inflate = Inflate;
img = Img;
// spin = spinner;
this.delegate = delegate;
}
public int status;
#Override
protected String doInBackground(String... arg0) {PreferenceManager.getDefaultSharedPreferences(lastContext);
try {
Socket echoSocket = new Socket();
echoSocket.connect(new InetSocketAddress(ipOnline,portOnline),2000);
if(echoSocket.isConnected())
status = 1;
} catch (Exception e) {
status = 0;
}
if(status == 0)
return "0";
else
return "1";
}
#Override
protected void onProgressUpdate(Void... values) {}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
ImageView img = (ImageView) activity.findViewById(R.id.imgbtn_favorite);
if (status == 0)
img.setImageResource(android.R.drawable.presence_offline);
else
img.setImageResource(android.R.drawable.presence_online);
delegate.processFinish(result);
}
}
And here is my call to it:
new Connection(activity, img, product.getIpOnline(), Integer.parseInt(product.getPortOnline()), product.getIpWol(), Integer.parseInt(product.getPortWol()), product.getMacAddress(), new Connection.AsyncResponse() {
#Override
public void processFinish(String output) {
}
}).execute();
You can use view holder pattern or recyclerview .
You need to store the reference of image view in holder and can update the image with the help of this reference instead of id of view
I came up with a solution.
I created a public variable on my adapter and I'm adding all the images:
public List<ImageView> allImages = new ArrayList<ImageView>();
public List<ImageView> getAllImages(){
return this.allImages;
}
this.allImages.add((ImageView) convertView
.findViewById(R.id.imgbtn_favorite));
Then on my fragmented activity onCreateView method I deployed a delayed runnable:
(new Handler()).postDelayed(new Runnable() {
#Override
public void run() {
updateStatus();
}
}, 500);
the updateStatus() method initializes the images variable and begins network checks to determine which image to use. Then it applies it accordingly.
public void updateStatus() {
List<ImageView> images = deviceListAdapter.getAllImages();
if(count > 0 && images.size() > 0) {
for (int i = 0; i < deviceListAdapter.getCount() ; i++) {
Devices product = (Devices) deviceListAdapter.getItem(i);
if((TextUtils.isDigitsOnly(product.getPortOnline()) && TextUtils.isDigitsOnly(product.getPortWol())) && (!product.getPortOnline().isEmpty() && !product.getPortWol().isEmpty())) {
new Connection(activity, images.get(i), product.getIpOnline(), Integer.parseInt(product.getPortOnline()), product.getIpWol(), Integer.parseInt(product.getPortWol()), product.getMacAddress(), new Connection.AsyncResponse() {
#Override
public void processFinish(Boolean output) {
}
}).execute();
}
}
}
}
It might not be optimal but feel free to add a better solution.

How to set GridViewCustomAdapter from asyncTask?

In my app I have PostersFragment that will be a GridView with movies posters, in this fragment I need to initialize the GridView and customGridViewAdapter.
My customGridViewAdapter need to get the Bitmaps array and then work with it.
The problem that I call to AsyncTask that is in different class that gets all information from JSON and stores the information with the movies posters on local database.
I cant understand how and when to use the .setAdapter(gridViewCustomAdapter);
There is my code.
public class PostersFragment extends Fragment implements AdapterView.OnItemClickListener {
private GridView gv_posters;
private GridViewCustomAdapter gridViewCustomAdapter;
public PostersFragment() {
setHasOptionsMenu(true);
}
#Override
public void onStart() {
super.onStart();
UpdatePosters();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d("TESTAG","onCreateView");
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
gv_posters = (GridView) rootView.findViewById(R.id.gv_posters);
gv_posters.setAdapter(gridViewCustomAdapter);
gv_posters.setOnScrollListener(new PostersScrollListener(getActivity().getApplicationContext()));
gv_posters.setOnItemClickListener(this);
// Define new adapter for grid view
return rootView;
}
private void UpdatePosters() {
Log.d("TESTAG","UpdatePoster");
MoviesTask task = new MoviesTask(getActivity(),gridViewCustomAdapter);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
String path = prefs.getString(getString(R.string.sort_method_posters_pref), getString(R.string.default_path));
task.execute(path);
}
MoviesTask.java
public class MoviesTask extends AsyncTask<String, Void, Bitmap[]> {
// Byte array to handle bitmaps
private byte[] imgByte;
// Context and custom adapter variables
private final Context mContext;
private GridViewCustomAdapter mGridCustomAdapter;
// MovieTask Constructor
public MoviesTask(Context context, GridViewCustomAdapter gridAdapter) {
mContext = context;
mGridCustomAdapter = gridAdapter;
}
Parsing JSON methods...
#Override
protected void onPostExecute(Bitmap[] bitmaps){
Log.d("TESTAG","onPostExecute");
if (bitmaps != null)
mGridCustomAdapter = new GridViewCustomAdapter(mContext,R.id.single_gv_poster,bitmaps);
}
GridViewCustomAdapter.java
public class GridViewCustomAdapter extends BaseAdapter{
private Context context;
int layoutResource;
private Bitmap[] bitmaps;
public GridViewCustomAdapter(Context context,int layoutResource, Bitmap[] bitmaps){
this.context = context;
this.layoutResource = layoutResource;
this.bitmaps = bitmaps;
}
#Override
public int getCount() {
return 0;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
viewHolder holder = null;
if (view == null){
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
view = inflater.inflate(layoutResource,parent,false);
holder = new viewHolder();
holder.imageView = (ImageView)view.findViewById(R.id.single_gv_poster);
view.setTag(holder);
}else
holder = (viewHolder)view.getTag();
Bitmap bitmap = bitmaps[position];
holder.imageView.setImageBitmap(bitmap);
return view;
}
static class viewHolder{
ImageView imageView;
}
}
Anyone can help me with this issue please?
You can do this way:
Delete line from onCreate():
gv_posters.setAdapter(gridViewCustomAdapter);
Your PostExecute should looks like this:
#Override
protected void onPostExecute(Bitmap[] bitmaps){
Log.d("TESTAG","onPostExecute");
if (bitmaps != null)
mGridCustomAdapter = new GridViewCustomAdapter(mContext,R.id.single_gv_poster,bitmaps);
gv_posters.setAdapter(mGridCustomAdapter);
}
}
Hope this will help you.

Android java.lang.IllegalStateException on ListView

I use ListActivity with SpecialAdapter that show some log information.
I do not use background thread but I get an error when scroll list:
java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(16908298, class android.widget.ListView) with Adapter(class ru.FoxGSM.ui.DebugActivity$SpecialAdapter)]
Please, help me.
(FoxLog is static class that get log information, perhaps from another thread. But in list a want show FoxLog snapshot)
public class DebugActivity extends ListActivity {
private class SpecialAdapter extends BaseAdapter {
private LayoutInflater mInflater;
public SpecialAdapter(Context context) {
super();
// Cache the LayoutInflate to avoid asking for a new one each time.
mInflater = LayoutInflater.from(context);
}
public int getCount() {
return FoxLog.count();
}
public long getItemId(int index) {
return index;
}
public Object getItem(int index) {
return FoxLog.get(FoxLog.count()-index-1);
}
// Get the type of View
public View getView(int position, View convertView, ViewGroup parent) {
TextView text;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.debug_list, null);
text = (TextView)convertView.findViewById(R.id.text);
convertView.setTag(text);
} else
text = (TextView) convertView.getTag();
String s = (String) getItem(position);
if (s==null)
return convertView;
text.setText(s);
boolean isError = false;
if (s!=null && s.length()>0) {
String prefix = s.substring(0, 1);
if (prefix.equals("E") || prefix.equals("W"))
isError = true;
}
if (isError)
text.setBackgroundResource(R.color.red);
else
text.setBackgroundDrawable(null);
return convertView;
}
}
private void RefreshData() {
FoxLog.ReInit(this);
SpecialAdapter adapter = (SpecialAdapter)this.getListAdapter();
adapter.notifyDataSetChanged();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.debug);
FoxLog.ReInit(this);
SpecialAdapter adapter = new SpecialAdapter(this);
setListAdapter(adapter);
Button mExit = (Button)findViewById(R.id.exit);
mExit.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
finish();
}
});
}
}

Categories