Where to manipulate views in android app? - android

I have a MainActivity, which creates MyOwnTextToSpeech object which is responsible for some tts stuff. It also has to manipulate some UI views (e.g. set text in some TextViews). How should I implement this manipulations of UI? Is it good idea to pass reference to MainActivity to MyOwnTextToSpeech (and other objects) and prepare some public methods in MainActivity for manipulating UI or maybe I should create getters for all views (TextView etc.) to have access to them? Or maybe there is a way not to pass reference to MainActivity (I read somewhere that passing reference to Activities is not good because of some memory leaks stuff)?
UPDATE
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyOwnTextToSpeech myTts = new MyOwnTextToSpeech(context);
}
}
And my class responsible for tts.
public class MyOwnTextToSpeech implements TextToSpeech.OnInitListener {
private TextToSpeech tts;
public MyOwnTextToSpeech (Context ctx) {
tts = new TextToSpeech(ctx, this);
}
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
#Override
public void onStart(String utteranceId) {
}
#Override
public void onError(String utteranceId) {
}
#Override
public void onDone(final String utteranceId) {
// here I want some UI update stuff
}
});
int result = tts.setLanguage(new Locale("pl", "PL"));
if (result == TextToSpeech.LANG_MISSING_DATA
|| result == TextToSpeech.LANG_NOT_SUPPORTED) {
Log.e("TTS", "This Language is not supported");
}
} else {
Log.e("TTS", "Initilization Failed!");
}
}

Related

Check Continuos Connection not launching in BaseActivity

I'm using the next library ReactiveNetwork to listen the changes on the network to detect when the network is disconnected, but I've implemented this on the BaseActivity but is not doing nothing.
Have I something wrong? Let me know.
public class BaseActivity extends AppCompatActivity {
private Disposable networkDisposable;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
connectivityMonitorized();
safelyDispose(networkDisposable);
}
private void safelyDispose(Disposable... disposables) {
for (Disposable subscription : disposables) {
if (subscription != null && !subscription.isDisposed()) {
subscription.dispose();
}
}
}
#SuppressLint("CheckResult")
public void connectivityMonitorized(){
networkDisposable = ReactiveNetwork
.observeNetworkConnectivity(this)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(connectivity -> {
if (connectivity.state() == NetworkInfo.State.DISCONNECTED || connectivity.state() == NetworkInfo.State.DISCONNECTING) {
//TODO DIALOG SHOWING DISCONNECTED
}
});
}
}
I'm using this BaseActivity to extends in other activities.
Thanks
You are disposing the networkDisposable as soon as subscribed ideally it should be disposed on the activity destroy that's why you are not getting any network updates move the safelyDispose() to on destroy
#Override
protected void onDestroy() {
super.onDestroy();
safelyDispose(networkDisposable);
}

TextToSpeech in non-activity class/View Adapter class, speak failed: not bound to TTS engine

I've developed a module for my app using RecyclerView in Tabbed-Fragments.
Scenario
There are multiple dynamically loaded images shown in each fragment using RecyclerView. When user taps on any image, system speaks out some information about it using TextToSpeech.
Current code
I'm using an adapter class for RecyclerView that successfully loads images to views. Here's the code:
import android.speech.tts.TextToSpeech;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>{
private ArrayList<CreateList> galleryList;
private Context context;
TextToSpeech tts;
public MyAdapter(Context context, ArrayList<CreateList> galleryList) {
this.galleryList = galleryList;
this.context = context;
}
#Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
.....
}
#Override
public void onBindViewHolder(MyAdapter.ViewHolder viewHolder, int i) {
.....
.....
viewHolder.img.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
.....
.....
tts=new TextToSpeech(context, new TextToSpeech.OnInitListener() {
#Override
public void onInit(int status) {
// TODO Auto-generated method stub
if(status !=TextToSpeech.ERROR){
tts.setLanguage(Locale.UK);
}
}
});
tts.setPitch(pitch);
tts.setSpeechRate(speechRate);
tts.speak(StringToSpeak, TextToSpeech.QUEUE_FLUSH, null);
}
#Override
public int getItemCount() {
return galleryList.size();
}
.....
.....
}
As you can see I've added a OnClickListener on viewHolder, which initializes TTS object with onInitListener and speaks information. But whenever i click on any image/view/item in Fragment it doesn't speaks out anything. There's no crash, no exception in LogCat, all i get is following message with other messages:
I/TextToSpeech: Sucessfully bound to com.google.android.tts
W/TextToSpeech: speak failed: not bound to TTS engine
I tried debugging the application and found out that while initializing TTS object it returns engine=Null at this line:
tts=new TextToSpeech(context, new TextToSpeech.OnInitListener() {
....});
In other classes that extend Activity, code and TTS is working prefectly but in my Adapter/Non-Activity class it doesn't initiate TTS because it is unable to bind to TTS engine. I've tried to implement interface implements TextToSpeech.OnInitListener in my adapter class with following function:
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
tts.setLanguage(Locale.UK);
}
}
But no results :( I've also tried to create an abstract class extending Activity, that implements TTS in its OnCreate() and includes a custom function SpeakMessage() which i tried calling in my Adapter Class but failed. It's been several hours i'm trying to figure out the problem and it's solution, deeply studied every TTS related question on StackOverFlow and other sites but couldn't find any solution for my problem. Please help me with identifying the problem and its proper solution. Thanks alot in advance. One more thing, in same action listener for Views, Playing recorded audio message using MediaPlayer works perfect. The only problem is with speaking string messages using TTS.
I have implemented Text to speech successfully
Below is my Code
I have implemented two method one for <20 api and one is for >21api
#SuppressWarnings("deprecation")
private void ttsUnder20(String text) {
HashMap<String, String> map = new HashMap<>();
map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "MessageId");
tts.speak(text, TextToSpeech.QUEUE_FLUSH, map);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void ttsGreater21(String text) {
String utteranceId = this.hashCode() + "";
Bundle params = new Bundle();
params.putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "");
tts.speak(text, TextToSpeech.QUEUE_FLUSH, params, utteranceId);
}
Here is method which calling this methods.
private void playNextChunk(String text) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ttsGreater21(text);
} else {
ttsUnder20(text);
}}
Call playChunk method from onInit method
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
playNextChunk(String text)
}
}
One more suggestion text to speech is able to speak 4000 character at a time if you have string with >4000 character you need to play it in chunks. for that you need to implement this listener tts.setOnUtteranceProgressListener
Try this code i have added code in your adapter.
public class MyAdapter extends
RecyclerView.Adapter<MyAdapter.ViewHolder>{
private ArrayList<CreateList> galleryList;
private Context context;
TextToSpeech tts;
public MyAdapter(Context context, ArrayList<CreateList> galleryList) {
this.galleryList = galleryList;
this.context = context;
}
#Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
.....
}
#Override
public void onBindViewHolder(MyAdapter.ViewHolder viewHolder, int i) {
.....
.....
viewHolder.img.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
.....
.....
tts=new TextToSpeech(context, new TextToSpeech.OnInitListener() {
#Override
public void onInit(int status) {
// TODO Auto-generated method stub
// edit from original answer: I put double equal on this line
if(status == TextToSpeech.SUCCESS){
tts.setLanguage(Locale.UK);
playNextChunk(StringToSpeak);
}
}
});
}
private void playNextChunk(String text) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ttsGreater21(text);
} else {
ttsUnder20(text);
}}
#SuppressWarnings("deprecation")
private void ttsUnder20(String text) {
HashMap<String, String> map = new HashMap<>();
map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "MessageId");
tts.speak(text, TextToSpeech.QUEUE_FLUSH, map);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void ttsGreater21(String text) {
String utteranceId = this.hashCode() + "";
Bundle params = new Bundle();
params.putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "");
tts.speak(text, TextToSpeech.QUEUE_FLUSH, params, utteranceId);
}
#Override
public int getItemCount() {
return galleryList.size();
}
.....
.....
}

GoogleApiClient not connected if used from fragment

I discovered a strange behaviour today.
I have my activity which connects to the GoogleApiClient in onStart() and disconnects in the onStop()
The activity uses a GridViewPager to show my fragments. To send messages through the Data Layer i use a callback interface between activity and fragment.
If i call sendMessage() from a button within the Activity layout it works fine. If sendMessage() is executed by the fragment using the callback interface sendMessage() shows the "not connected" Toast.
In both ways the same method in the Activity is called so how is it possible that it behaves different ?
I should mention that the problem only occours after the application is restarted for the first time.
Activity
public class WearPlex extends WearableActivity implements
NavigationRemoteCallbacks,
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
private List<Node> nodeList = new ArrayList<Node>();
private List<Fragment> fragmentList = new ArrayList<Fragment>();
private GoogleApiClient googleApiClient;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wear_plex);
setAmbientEnabled();
fragmentList.add(NavigationRemoteFragment.getInstance(this));
GridViewPager mGridPager = (GridViewPager)findViewById(R.id.gridViewPager);
mGridPager.setAdapter(new MainGridPageAdapter(getFragmentManager(), fragmentList));
googleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
#Override
protected void onStart() {
super.onStart();
googleApiClient.connect();
}
#Override
protected void onStop() {
googleApiClient.disconnect();
super.onStop();
}
#Override
public void onConnected(Bundle bundle) {
Toast.makeText(this, "Connected", Toast.LENGTH_SHORT).show();
nodeList.clear();
Wearable.NodeApi.getConnectedNodes(googleApiClient).setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
#Override
public void onResult(NodeApi.GetConnectedNodesResult nodes) {
for (Node node : nodes.getNodes()) nodeList.add(node);
}
});
}
#Override
public void navigationRemoteSendCommand(String commandPath) {
sendMessage(commandPath, null);
}
public void debugOnClick(View view) {
sendMessage("/debug", null);
}
public void sendMessage(String path, byte[] data) {
if (googleApiClient.isConnected()) {
for (int i = 0; i < nodeList.size(); i++) {
if (nodeList.get(i).isNearby()) {
Toast.makeText(this, "Send message", Toast.LENGTH_SHORT).show();
Wearable.MessageApi.sendMessage(googleApiClient, nodeList.get(i).getId(), path, data);
}
}
} else {
Toast.makeText(this, "Not connected", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Toast.makeText(this, "Connection failed", Toast.LENGTH_SHORT).show();
}
Fragment
public class NavigationRemoteFragment extends Fragment {
private static NavigationRemoteFragment navigationRemoteFragment = null;
private NavigationRemoteCallbacks callbackHandler = null;
private ImageButton navBtnCenter;
public static NavigationRemoteFragment getInstance(NavigationRemoteCallbacks handler) {
if (navigationRemoteFragment == null) {
navigationRemoteFragment = new NavigationRemoteFragment();
navigationRemoteFragment.callbackHandler = handler;
}
return navigationRemoteFragment;
}
public NavigationRemoteFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_navigation_remote, container, false);
navBtnCenter = (ImageButton)v.findViewById(R.id.navBtnCenter);
navBtnCenter.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
callbackHandler.navigationRemoteSendCommand("/debug");
}
});
return v;
}
}
Callback interface
public interface NavigationRemoteCallbacks {
public void navigationRemoteSendCommand(String commandPath);
}
EDIT 1 code for MainGridPageAdapter
public class MainGridPageAdapter extends FragmentGridPagerAdapter {
private List<Fragment> fragmentList = null;
public MainGridPageAdapter(FragmentManager fm, List<Fragment> fragmentList) {
super(fm);
this.fragmentList = fragmentList;
}
#Override
public Fragment getFragment(int i, int i1) {
if (i1 < fragmentList.size()) return fragmentList.get(i1);
return null;
}
#Override
public int getRowCount() {
return 1;
}
#Override
public int getColumnCount(int i) {
return fragmentList.size();
}
You don't show the code for MainGridPageAdapter so I don't know how it is managing fragments. You mention that the problem occurs after a restart. Looking at the code in WearPlex.onCreate(), I suspect that the problem is caused fragments that are holding a reference to an old, destroyed instance of the activity.
A poorly documented behavior of FragmentManager is that it saves its state across restarts. This is often overlooked, resulting in duplicate fragment instances after a restart. The correct pattern for managing fragment creation in the onCreate() method of the host activity is:
if (savedInstanceState == null) {
// Not a restart
// Create a new instance of the fragment
// Add it to the fragment manager
} else {
// Restart
// The fragment manager has saved and restored the fragment instances
// Use findFragmentById() to get the fragment if you need it
}
You are not using savedInstanceState in onCreate() to test for restart. Are you seeing more fragments than you expect after restart? If so, the original fragments are holding a reference to the old activity, which was stopped, and has a disconnected GoogleApiClient. If the NavBtn of one of those fragments is clicked, you will see the "not connected" toast.
Update
The problem is caused by the way you are creating new instances of NavigationRemoteFragment, specifically the use of static member navigationRemoteFragment. After a restart, when the activity is recreated, the code calls NavigationRemoteFragment.getInstance(this). getInstance() finds navigationRemoteFragment not null because it is static, and does not create a new fragment. The fragment returned is the old one, which holds a reference to the old activity, which has been stopped and has a disconnected GoogleApiClient.
This could be confirmed by using the isDestroyed method and adding a some debug logging:
#Override
public void navigationRemoteSendCommand(String commandPath) {
if (isDestroyed()) {
Log.w("TEST", "This is an old instance of the activity");
}
sendMessage(commandPath, null);
}

AsyncTask : passing value to an Activity (onCreate method )

Update1
activity:
public Integer _number = 0;
#Override
public void onCreate(Bundle savedInstanceState) {
if (_number >0)
{
Log.d("onSuccessfulExecute", ""+_number);
}
else
{
Log.d("onSuccessfulExecute", "nope empty songs lists");
}
}
public int onSuccessfulExecute(int numberOfSongList) {
_number = numberOfSongList;
if (numberOfSongList >0)
{
Log.d("onSuccessfulExecute", ""+numberOfSongList);
}
else
{
Log.d("onSuccessfulExecute", "nope empty songs lists");
}
return numberOfSongList;
}
end Update1
UPDATE: AsynchTask has its own external class.
How to pass an value from AsyncTask onPostExecute()... to activity
my code does returning value from onPostExecute() and updating on UI but i am looking for a way to set the activity variable (NumberOfSongList) coming from AsynchTask.
AsyncTask class:
#Override
public void onPostExecute(asynctask.Payload payload)
{
AsyncTemplateActivity app = (AsyncTemplateActivity) payload.data[0];
//the below code DOES UPDATE the UI textView control
int answer = ((Integer) payload.result).intValue();
app.taskStatus.setText("Success: answer = "+answer);
//PROBLEM:
//i am trying to populate the value to an variable but does not seems like the way i am doing:
app.NumberOfSongList = payload.answer;
..............
..............
}
Activity:
public Integer NumberOfSongList;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//Several UI Code
new ConnectingTask().execute();
Log.d("onCreate", ""+NumberOfSongList);
}
What about using a setter method? e.g.
private int _number;
public int setNumber(int number) {
_number = number;
}
UPDATE:
Please look at this code. This will do what you're trying to accomplish.
Activity class
public class TestActivity extends Activity {
public int Number;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
Button btnDisplay = (Button) findViewById(R.id.btnDisplay);
btnDisplay.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Toast toast = Toast.makeText(v.getContext(), "Generated number: " + String.valueOf(Number), Toast.LENGTH_LONG);
toast.show();
}
});
new TestTask(this).execute();
}
}
AsyncTask class
public class TestTask extends AsyncTask<Void, Void, Integer> {
private final Context _context;
private final String TAG = "TestTask";
private final Random _rnd;
public TestTask(Context context){
_context = context;
_rnd = new Random();
}
#Override
protected void onPreExecute() {
//TODO: Do task init.
super.onPreExecute();
}
#Override
protected Integer doInBackground(Void... params) {
//Simulate a long-running procedure.
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Log.e(TAG, e.getMessage());
}
return _rnd.nextInt();
}
#Override
protected void onPostExecute(Integer result) {
TestActivity test = (TestActivity) _context;
test.Number = result;
super.onPostExecute(result);
}
}
Just a word of caution: Be very careful when attempting to hold a reference to an Activity instance in an AsyncTask - I found this out the hard way :). If the user happens to rotate the device while your background task is still running, your activity will be destroyed and recreated thus invalidating the reference being to the Activity.
Create a listener.
Make a new class file. Called it something like MyAsyncListener and make it look like this:
public interface MyAsyncListener() {
onSuccessfulExecute(int numberOfSongList);
}
Make your activity implement MyAsyncListener, ie,
public class myActivity extends Activity implements MyAsyncListener {
Add the listener to the constructor for your AsyncTask and set it to a global var in the Async class. Then call the listener's method in onPostExecute and pass the data.
public class MyCustomAsync extends AsyncTask<Void,Void,Void> {
MyAsyncListener mal;
public MyCustomAsync(MyAsyncListener listener) {
this.mal = listener;
}
#Override
public void onPostExecute(asynctask.Payload payload) {
\\update UI
mal.onSuccessfulExecute(int numberOfSongList);
}
}
Now, whenever your AsyncTask is done, it will call the method onSuccessfulExecute in your Activity class which should look like:
#Override
public void onSuccessfulExecute(int numberOfSongList) {
\\do whatever
}
Good luck.

How to implement text to speech in non-activity class

I want to implement text to speech in non-activity class, I want when user click on custom ListView to listen the word who is written.
The code is next:
public class BankAdapter extends BaseAdapter {
List<BankItem> items;
LayoutInflater inflater;
//class who implements TextToSpeech
**TextToSpeach ttl1;**
OnClickListener l;
static class BankItemHolder {
TextView wordView;
TextView descriptionView;
}
Activity myMainActivity;
public BankAdapter(Activity mainActivity) {
// TODO Auto-generated constructor stub
super();
this.myMainActivity=mainActivity;
}
public BankAdapter(Context ctx, List<BankItem> items) {
this.items = items;
inflater =(LayoutInflater)ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
.
.
.
.
.
public View getView(final int position, View convertView, ViewGroup parent) {
final BankItemHolder bih;
if (convertView == null) {
RelativeLayout rl = (RelativeLayout) inflater.inflate(R.layout.v_bank_item, null);
convertView = rl;
bih = new BankItemHolder();
bih.wordView = (TextView) rl.findViewById(R.id.txtWord);
bih.descriptionView = (TextView) rl.findViewById(R.id.txtDescription);
convertView.setTag(bih);
} else {
bih = (BankItemHolder) convertView.getTag();
}
bih.wordView.setText(items.get(position).getWord());
bih.descriptionView.setText(items.get(position).getDescriprion());
l=new OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub
String w1 = items.get(position).getWord();
int i1 = w1.indexOf(" ");
String w2=w1.substring(0, i1);
**ttl1.speakWords(w2);**
}
};;;
convertView.setOnClickListener(l);
return convertView;
.
.
.
.
}
}
Now the class who implements TextToSpeech
public class TextToSpeach extends Activity implements OnInitListener {
private int MY_DATA_CHECK_CODE = 0;
private TextToSpeech tts;
/**
* Called when the activity is first created.
*/
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// setContentView(R.layout.main);
// Fire off an intent to check if a TTS engine is installed
Intent checkIntent = new Intent();
checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkIntent, MY_DATA_CHECK_CODE);
}
public void speakWords(String word) {
tts.speak(word, TextToSpeech.QUEUE_ADD, null);
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == MY_DATA_CHECK_CODE) {
if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
// success, create the TTS instance
tts = new TextToSpeech(this, this);
}
else {
// missing data, install it
Intent installIntent = new Intent();
installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installIntent);
}
}
}
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
Toast.makeText(TextToSpeach.this, "Text-To-Speech engine is initialized", Toast.LENGTH_LONG).show();
}
else if (status == TextToSpeech.ERROR) {
Toast.makeText(TextToSpeach.this, "Error occurred while initializing Text-To-Speech engine",
Toast.LENGTH_LONG).show();
}
}
/**
* Be kind, once you've finished with the TTS engine, shut it down so other
* applications can use it without us interfering with it :)
*/
#Override
public void onDestroy()
{
// Don't forget to shutdown!
if (tts != null)
{
tts.stop();
tts.shutdown();
}
super.onDestroy();
}
}
This question is symptomatic when you want to use Android framework outside of a android context..
From my little experience and lecture,
Here my own best practice in this question context.
FIRST:
Custom Service, Activity, Broadcastreceiver, ContentProvider are android context and/or are provided with android context.
This context is very important to get access to android services.
TTS is not in exception : it needs to be intantiated with a context and a listener to notify when it is ready (not ready at contruction time)
So you may do TextToSpeech actions in non-GUI component like a service for instance.
SECOND:
Avoid to design your code with a mix of App Logic and GUI in same code
THIRD:
if logic need to act on android framework it's a good way to provide context only when needed at runtime (as a parameter for instance)
as example : context can be a service or activity instance.
FOURTH:
Avoid as much as possible to keep reference to a context.
because android framework,for memory allocation strategy, may destroy /reconstruct context at it's own discretion.
hope that help

Categories

Resources