diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6cbe56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..923e4ed --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +AndroidArsenal \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..96cc43e --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..d390b65 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..1a3eaff --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..099e35b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..6564d52 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..9701ea4 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + applicationId "com.mi.androidarsenal" + minSdkVersion 16 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + testCompile 'junit:junit:4.12' + compile 'com.android.support:appcompat-v7:23.1.1' + compile 'com.android.support:design:23.1.1' + compile 'com.android.support:support-v4:23.1.1' + compile 'com.mcxiaoke.volley:library:1.0.19' + compile 'com.squareup.okhttp:okhttp:2.7.2' + compile 'com.squareup.okhttp:okhttp-urlconnection:2.7.2' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..2a46b59 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Users\samir.sarosh\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/androidTest/java/com/mi/activity/ApplicationTest.java b/app/src/androidTest/java/com/mi/activity/ApplicationTest.java new file mode 100644 index 0000000..9082f51 --- /dev/null +++ b/app/src/androidTest/java/com/mi/activity/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.mi.activity; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..86f1936 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/mi/androidarsenal/activity/MainActivity.java b/app/src/main/java/com/mi/androidarsenal/activity/MainActivity.java new file mode 100644 index 0000000..abb844b --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/activity/MainActivity.java @@ -0,0 +1,522 @@ +package com.mi.androidarsenal.activity; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.graphics.drawable.ColorDrawable; +import android.os.Build; +import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.ViewPager; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewAnimationUtils; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.Toast; + + +import com.mi.androidarsenal.R; +import com.mi.androidarsenal.fragment.AndroidVersionsFragment; +import com.mi.androidarsenal.fragment.DeviceDetailFragment; +import com.mi.androidarsenal.fragment.DevicesFragment; +import com.mi.androidarsenal.fragment.VersionDetailFragment; +import com.mi.androidarsenal.network.RequestOperation; +import com.mi.androidarsenal.utility.AndroidResponseObserver; +import com.mi.androidarsenal.utility.AppConstants; +import com.mi.androidarsenal.utility.AppUtils; +import com.mi.androidarsenal.utility.OfflineUtil; +import com.mi.androidarsenal.utility.OnEditItemListener; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * This is the activity which hosts all the required fragment and is basically the base of the application + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class MainActivity extends AppCompatActivity implements AndroidResponseObserver, DevicesFragment.OnDeviceSelectedListener, AppConstants, OnEditItemListener, RequestOperation.OnRefreshDbListener, AndroidVersionsFragment.OnVersionSelectedListener { + + private TabLayout mTabLayout; + private ViewPager mViewPager; + private FloatingActionButton mFab; + private LinearLayout mTabContainer; + private AlertDialog mDialog; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + mViewPager = (ViewPager) findViewById(R.id.viewpager); + setupViewPager(mViewPager); + + mTabLayout = (TabLayout) findViewById(R.id.tabs); + mTabLayout.setupWithViewPager(mViewPager); + + mTabContainer = (LinearLayout) findViewById(R.id.tab_container); + mFab = (FloatingActionButton) findViewById(R.id.fab); + mFab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + showDialog(null); + } + }); + + AppUtils.setOnEditItemListener(this); + RequestOperation.setOnRefreshDbListener(this); + + //fetch the data from server + fetchDataFromServer(); + } + + /** + * initiates the network request for all the data from server + */ + private void fetchDataFromServer() { + //check network availability + if (AppUtils.isNetworkAvailable(MainActivity.this.getApplicationContext())) { + RequestOperation requestOperation = RequestOperation.getRequestOperationInstance(MainActivity.this); + requestOperation.getAllData(MainActivity.this); + } + } + + /** + * sets up the view pager for the application + * + * @param viewPager + */ + private void setupViewPager(ViewPager viewPager) { + ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager()); + adapter.addFragment(new DevicesFragment(), "Android Devices"); + adapter.addFragment(new AndroidVersionsFragment(), "Android Versions"); + viewPager.setAdapter(adapter); + } + + @Override + public void onAllDataGetResponse(String jsonResponse) { + if (!TextUtils.isEmpty(jsonResponse)) { + OfflineUtil offlineUtil = new OfflineUtil(MainActivity.this); + offlineUtil.populateResponseInMemory(jsonResponse); + } + } + + @Override + public void onEditItem(Bundle editBundle) { + //show the edit dialog + showDialog(editBundle); + } + + @Override + public void onRefreshDb() { + fetchDataFromServer(); + } + + /** + * viewpager adapter class + */ + class ViewPagerAdapter extends FragmentPagerAdapter { + private final List mFragmentList = new ArrayList<>(); + private final List mFragmentTitleList = new ArrayList<>(); + + public ViewPagerAdapter(FragmentManager manager) { + super(manager); + } + + @Override + public Fragment getItem(int position) { + return mFragmentList.get(position); + } + + @Override + public int getCount() { + return mFragmentList.size(); + } + + public void addFragment(Fragment fragment, String title) { + mFragmentList.add(fragment); + mFragmentTitleList.add(title); + } + + @Override + public CharSequence getPageTitle(int position) { + return mFragmentTitleList.get(position); + } + } + + /** + * Implements the edit and add dialog (for POST and PUT requests) + * + * @param editBundle + */ + private void showDialog(final Bundle editBundle) { + //initialize views + final View dialogView = View.inflate(MainActivity.this, R.layout.info_field, null); + final View view = dialogView.findViewById(R.id.reveal_view); + final LinearLayout deviceLayout = (LinearLayout) dialogView.findViewById(R.id.device_layout); + final EditText nameView = (EditText) dialogView.findViewById(R.id.device_dialog_name_field); + final EditText androidIdView = (EditText) dialogView.findViewById(R.id.device_dialog_id_field); + final EditText imageView = (EditText) dialogView.findViewById(R.id.device_dialog_image_field); + final EditText carrierView = (EditText) dialogView.findViewById(R.id.device_dialog_carrier_field); + final EditText descriptionView = (EditText) dialogView.findViewById(R.id.device_dialog_snippet_field); + final LinearLayout versionLayout = (LinearLayout) dialogView.findViewById(R.id.version_layout); + final EditText versionNameView = (EditText) dialogView.findViewById(R.id.version_dialog_name_field); + final EditText codenameView = (EditText) dialogView.findViewById(R.id.version_dialog_codename_field); + final EditText destributionView = (EditText) dialogView.findViewById(R.id.version_dialog_distribution_field); + final EditText targetView = (EditText) dialogView.findViewById(R.id.version_dialog_target_field); + final EditText versionView = (EditText) dialogView.findViewById(R.id.version_dialog_version_field); + Button saveBtn = (Button) dialogView.findViewById(R.id.btn_save); + final Spinner spinner = (Spinner) dialogView.findViewById(R.id.dialog_spinner); + + List list = Arrays.asList(getResources().getStringArray(R.array.android)); + + ArrayAdapter adapter = new ArrayAdapter(this, R.layout.spinner_item_dropdown, list); + spinner.setAdapter(adapter); + + //check for add or edit(POST or PUT) + if (editBundle == null) { + //Add operation + saveBtn.setText(getResources().getString(R.string.add)); + spinner.setSelection(mViewPager.getCurrentItem()); + } else { + spinner.setEnabled(false); + + //edit operation + saveBtn.setText(getResources().getString(R.string.update)); + + boolean isDevice = editBundle.getBoolean(EDIT_BUNDLE_DB_TYPE); + if (isDevice) { + //select the device option + spinner.setSelection(0); + + String carrier = editBundle.getString(KEY_CARRIER); + String snippet = editBundle.getString(KEY_SNIPPET); + String imageUrl = editBundle.getString(KEY_IMAGE_URL); + String androidId = editBundle.getString(KEY_ANDROID_ID); + String deviceName = editBundle.getString(KEY_NAME); + + nameView.setText(deviceName); + carrierView.setText(carrier); + descriptionView.setText(snippet); + imageView.setText(imageUrl); + androidIdView.setText(androidId); + + } else { + //select the version option + spinner.setSelection(1); + String versionName = editBundle.getString(KEY_VERSION_NAME); + String version = editBundle.getString(KEY_VERSION); + String target = editBundle.getString(KEY_TARGET); + String destribution = editBundle.getString(KEY_DESTRIBUTION); + String codename = editBundle.getString(KEY_CODENAME); + versionView.setText(version); + versionNameView.setText(versionName); + targetView.setText(target); + destributionView.setText(destribution); + codenameView.setText(codename); + } + } + + spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (position == 0) { + deviceLayout.setVisibility(View.VISIBLE); + versionLayout.setVisibility(View.GONE); + } else { + deviceLayout.setVisibility(View.GONE); + versionLayout.setVisibility(View.VISIBLE); + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + + AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); + builder.setView(dialogView) + .setCancelable(true); + + + mDialog = builder.create(); + mDialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + revealShow(view, true, null); + } else { + view.setVisibility(View.VISIBLE); + } + } + }); + dialogView.findViewById(R.id.btn_cancel).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + dismissDialogManeuver(view); + } + }); + + mDialog.setOnKeyListener(new DialogInterface.OnKeyListener() { + @Override + public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK && + event.getAction() == KeyEvent.ACTION_UP && + !event.isCanceled()) { + dismissDialogManeuver(view); + return true; + } + return false; + } + }); + + destributionView.setOnKeyListener(new View.OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + // If the event is a key-down event on the "enter" button + if ((event.getAction() == KeyEvent.ACTION_DOWN) + && (keyCode == KeyEvent.KEYCODE_ENTER)) { + submitRequest(editBundle, view, spinner, nameView, descriptionView, carrierView, androidIdView, imageView, versionNameView, versionView, codenameView, targetView, destributionView); + return true; + } + return false; + } + }); + + saveBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + submitRequest(editBundle, view, spinner, nameView, descriptionView, carrierView, androidIdView, imageView, versionNameView, versionView, codenameView, targetView, destributionView); + } + } + ); + + mDialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT)); + mDialog.show(); + } + + /** + * Submits the request to the server after verifying whether its for POST or PUT + * + * @param editBundle + * @param view + * @param spinner + * @param nameView + * @param descriptionView + * @param carrierView + * @param androidIdView + * @param imageView + * @param versionNameView + * @param versionView + * @param codenameView + * @param targetView + * @param destributionView + */ + private void submitRequest(Bundle editBundle, View view, Spinner spinner, EditText nameView, EditText descriptionView, EditText carrierView, EditText androidIdView, EditText imageView, EditText versionNameView, EditText versionView, EditText codenameView, EditText targetView, EditText destributionView) { + if (AppUtils.isNetworkAvailable(getApplicationContext())) { + RequestOperation requestOperation = RequestOperation.getRequestOperationInstance(MainActivity.this); + if (editBundle == null) { + //Add operation + if (spinner.getSelectedItemPosition() == 0) { + boolean isSomethingThere = validate(new EditText[]{nameView, descriptionView, carrierView, androidIdView, imageView}); + if (isSomethingThere) { + requestOperation.postDeviceInfo(nameView.getText().toString(), descriptionView.getText().toString(), carrierView.getText().toString(), androidIdView.getText().toString(), imageView.getText().toString()); + dismissDialogManeuver(view); + } else + Toast.makeText(MainActivity.this, EMPTY_FIELDS, Toast.LENGTH_SHORT).show(); + } else { + boolean isSomethingThere = validate(new EditText[]{versionNameView, versionView, codenameView, targetView, destributionView}); + if (isSomethingThere) { + requestOperation.postVersionInfo(versionNameView.getText().toString(), versionView.getText().toString(), codenameView.getText().toString(), targetView.getText().toString(), destributionView.getText().toString()); + dismissDialogManeuver(view); + } else + Toast.makeText(MainActivity.this, EMPTY_FIELDS, Toast.LENGTH_SHORT).show(); + } + + } else { + //Update operation + boolean isDevice = editBundle.getBoolean(EDIT_BUNDLE_DB_TYPE); + + if (isDevice) { + boolean isSomethingThere = validate(new EditText[]{nameView, descriptionView, carrierView, androidIdView, imageView}); + if (isSomethingThere) { + requestOperation.putDeviceInfo(editBundle.getString(KEY_DEVICE_ID), nameView.getText().toString(), descriptionView.getText().toString(), carrierView.getText().toString(), androidIdView.getText().toString(), imageView.getText().toString()); + dismissDialogManeuver(view); + } else + Toast.makeText(MainActivity.this, EMPTY_FIELDS, Toast.LENGTH_SHORT).show(); + } else { + boolean isSomethingThere = validate(new EditText[]{versionNameView, versionView, codenameView, targetView, destributionView}); + if (isSomethingThere) { + requestOperation.putVersionInfo(editBundle.getString(KEY_VERSION_ID), versionNameView.getText().toString(), versionView.getText().toString(), codenameView.getText().toString(), targetView.getText().toString(), destributionView.getText().toString()); + dismissDialogManeuver(view); + } else + Toast.makeText(MainActivity.this, EMPTY_FIELDS, Toast.LENGTH_SHORT).show(); + } + } + } + } + + /** + * Validate for all empty fields + * returns false if all the fields are empty. + * + * @param fields + * @return + */ + private boolean validate(EditText[] fields) { + for (int i = 0; i < fields.length; i++) { + EditText currentField = fields[i]; + if (currentField.getText().toString().length() > 0) { + return true; + } + } + return false; + } + + /** + * Dismiss dialog with animation + * + * @param view + */ + private void dismissDialogManeuver(View view) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + revealShow(view, false, mDialog); + } else { + mDialog.dismiss(); + view.setVisibility(View.INVISIBLE); + } + + } + + /** + * reveal animation for dialog + * + * @param view + * @param reveal + * @param dialog + */ + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private void revealShow(final View view, boolean reveal, final AlertDialog dialog) { + int w = view.getWidth(); + int h = view.getHeight(); + float maxRadius = (float) Math.sqrt(w * w / 4 + h * h / 4); + + if (reveal) { + Animator revealAnimator = ViewAnimationUtils.createCircularReveal(view, + w / 2, h / 2, 0, maxRadius); + + view.setVisibility(View.VISIBLE); + revealAnimator.start(); + + } else { + Animator anim = ViewAnimationUtils.createCircularReveal(view, w / 2, h / 2, maxRadius, 0); + + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + dialog.dismiss(); + view.setVisibility(View.INVISIBLE); + } + }); + anim.start(); + } + + } + + @Override + public void onVersionItemSelected(String id, String name, String version, String codename, String target, String distribution) { + VersionDetailFragment fragment = new VersionDetailFragment(); + Bundle bundle = new Bundle(); + bundle.putString(KEY_VERSION_ID, id); + bundle.putString(KEY_VERSION_NAME, name); + bundle.putString(KEY_VERSION, version); + bundle.putString(KEY_CODENAME, codename); + bundle.putString(KEY_TARGET, target); + bundle.putString(KEY_DESTRIBUTION, distribution); + fragment.setArguments(bundle); + addFragment(fragment); + } + + @Override + public void onDeviceItemSelected(String id, String name, String imageUrl, String snippet, String carrier, String android_id) { + DeviceDetailFragment fragment = new DeviceDetailFragment(); + Bundle bundle = new Bundle(); + bundle.putString(KEY_DEVICE_ID, id); + bundle.putString(KEY_NAME, name); + bundle.putString(KEY_IMAGE_URL, imageUrl); + bundle.putString(KEY_SNIPPET, snippet); + bundle.putString(KEY_CARRIER, carrier); + bundle.putString(KEY_ANDROID_ID, android_id); + + fragment.setArguments(bundle); + addFragment(fragment); + } + + /** + * Adds fragment + * + * @param fragment + */ + private void addFragment(Fragment fragment) { + hideFabAndTab(); + FragmentManager fm = MainActivity.this.getSupportFragmentManager(); + + FragmentTransaction ft = fm.beginTransaction(); + + if (fragment != null) { + ft.add(R.id.detail_fragment_container, fragment); + ft.addToBackStack(null); + ft.commit(); + } + } + + /** + * hides Floating action button and Tabs for fragment to show up + */ + private void hideFabAndTab() { + mFab.setVisibility(View.GONE); + mTabContainer.setVisibility(View.GONE); + } + + /** + * shows Floating action button and Tabs + */ + private void showFabAndTab() { + mFab.setVisibility(View.VISIBLE); + mTabContainer.setVisibility(View.VISIBLE); + } + + @Override + public void onBackPressed() { + int backCount = getSupportFragmentManager().getBackStackEntryCount(); + if (backCount > 0) { + showFabAndTab(); + } + super.onBackPressed(); + } + + + @Override + protected void onDestroy() { + if (mDialog != null && mDialog.isShowing()) { + mDialog.dismiss(); + } + super.onDestroy(); + } +} diff --git a/app/src/main/java/com/mi/androidarsenal/adapter/DevicesAdapter.java b/app/src/main/java/com/mi/androidarsenal/adapter/DevicesAdapter.java new file mode 100644 index 0000000..cd72177 --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/adapter/DevicesAdapter.java @@ -0,0 +1,147 @@ +package com.mi.androidarsenal.adapter; + +import android.content.Context; +import android.database.Cursor; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.CursorAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.volley.toolbox.NetworkImageView; +import com.mi.androidarsenal.R; +import com.mi.androidarsenal.network.VolleyHelper; +import com.mi.androidarsenal.utility.AppConstants; +import com.mi.androidarsenal.utility.AppUtils; +import com.mi.androidarsenal.utility.DeleteItemOnClickListener; + +/** + * This is the adapter for the device list + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class DevicesAdapter extends CursorAdapter implements AppConstants { + private final Context mContext; + private LayoutInflater mInflater; + private int lastPosition; + + public DevicesAdapter(Context context, Cursor cursor) { + super(context, cursor, 0); + mContext = context; + mInflater = LayoutInflater.from(context); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + View view = mInflater.inflate(R.layout.devices_list_item, parent, false); + ViewHolder holder = new ViewHolder(); + // Find fields to populate in inflated template + holder.thumbnail = (NetworkImageView) view.findViewById(R.id.device_thumbnail); + holder.nameView = (TextView) view.findViewById(R.id.device_name); + holder.carrierView = (TextView) view.findViewById(R.id.device_carrier); + holder.deviceDelete = (ImageView) view.findViewById(R.id.device_delete); + holder.deviceEdit = (ImageView) view.findViewById(R.id.device_edit); + view.setTag(holder); + return view; + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + ViewHolder holder = (ViewHolder) view.getTag(); + // Extract properties from cursor + String id = cursor.getString(cursor.getColumnIndexOrThrow(KEY_DEVICE_ID)); + String name = cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME)); + String imageUrl = cursor.getString(cursor.getColumnIndexOrThrow(KEY_IMAGE_URL)); + String snippet = cursor.getString(cursor.getColumnIndexOrThrow(KEY_SNIPPET)); + String carrier = cursor.getString(cursor.getColumnIndexOrThrow(KEY_CARRIER)); + String android_id = cursor.getString(cursor.getColumnIndexOrThrow(KEY_ANDROID_ID)); + + // Populate fields with extracted properties + holder.nameView.setText(name); + holder.carrierView.setText(carrier); + holder.thumbnail.setImageUrl(imageUrl, + VolleyHelper.getInstance(context).getImageLoader()); + holder.deviceEdit.setOnClickListener(new EditItemOnClickListener(id, name, snippet, carrier, android_id, imageUrl)); + holder.deviceDelete.setOnClickListener(new DeleteItemOnClickListener(mContext, id, true)); + + listScrollAnimation(view, cursor); + } + + /** + * When the list is scrolled, the item show up with an animation + * + * @param view + * @param cursor + */ + private void listScrollAnimation(View view, Cursor cursor) { + Animation animation = AnimationUtils.loadAnimation(mContext, (cursor.getPosition() > lastPosition) ? R.anim.list_up_from_bottom : R.anim.list_down_from_top); + view.startAnimation(animation); + lastPosition = cursor.getPosition(); + } + + /** + * Edit button click listener + */ + public class EditItemOnClickListener implements View.OnClickListener { + private String mName; + private String mSnippet; + private String mCarrier; + private String mAndroidId; + private String mImageUrl; + private String mId; + + public EditItemOnClickListener(String id, String name, String snippet, String carrier, String android_id, String imageUrl) { + mId = id; + mName = name; + mSnippet = snippet; + mCarrier = carrier; + mAndroidId = android_id; + mImageUrl = imageUrl; + } + + @Override + public void onClick(View v) { + editItem(mId, mName, mSnippet, mCarrier, mAndroidId, mImageUrl); + } + } + + /** + * Calls the edit functionality + * + * @param id + * @param name + * @param snippet + * @param carrier + * @param android_id + * @param imageUrl + */ + private void editItem(String id, String name, String snippet, String carrier, String android_id, String imageUrl) { + if (AppUtils.mOnEditItemListener != null) { + Bundle editBundle = new Bundle(); + editBundle.putBoolean(EDIT_BUNDLE_DB_TYPE, true); + editBundle.putString(KEY_DEVICE_ID, id); + editBundle.putString(KEY_NAME, name); + editBundle.putString(KEY_SNIPPET, snippet); + editBundle.putString(KEY_CARRIER, carrier); + editBundle.putString(KEY_ANDROID_ID, android_id); + editBundle.putString(KEY_IMAGE_URL, imageUrl); + AppUtils.mOnEditItemListener.onEditItem(editBundle); + } + } + + /** + * view holder for listview item. + */ + static class ViewHolder { + TextView nameView; + TextView carrierView; + NetworkImageView thumbnail; + ImageView deviceDelete; + ImageView deviceEdit; + } +} diff --git a/app/src/main/java/com/mi/androidarsenal/adapter/VersionsAdapter.java b/app/src/main/java/com/mi/androidarsenal/adapter/VersionsAdapter.java new file mode 100644 index 0000000..a27be9c --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/adapter/VersionsAdapter.java @@ -0,0 +1,145 @@ +package com.mi.androidarsenal.adapter; + +import android.content.Context; +import android.database.Cursor; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.CursorAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.mi.androidarsenal.R; +import com.mi.androidarsenal.utility.AppConstants; +import com.mi.androidarsenal.utility.AppUtils; +import com.mi.androidarsenal.utility.DeleteItemOnClickListener; + +/** + * This is the adapter for the versions list + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class VersionsAdapter extends CursorAdapter implements AppConstants { + private final Context mContext; + private LayoutInflater mInflater; + private int lastPosition; + + public VersionsAdapter(Context context, Cursor cursor) { + super(context, cursor, 0); + mContext = context; + mInflater = LayoutInflater.from(context); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + View view = mInflater.inflate(R.layout.version_list_item, parent, false); + ViewHolder holder = new ViewHolder(); + // Find fields to populate in inflated template + holder.nameView = (TextView) view.findViewById(R.id.versions_name); + holder.versionView = (TextView) view.findViewById(R.id.versions_version); + holder.versionDelete = (ImageView) view.findViewById(R.id.versions_delete); + holder.versionEdit = (ImageView) view.findViewById(R.id.versions_edit); + view.setTag(holder); + return view; + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + ViewHolder holder = (ViewHolder)view.getTag(); + // Extract properties from cursor + String id = cursor.getString(cursor.getColumnIndexOrThrow(KEY_VERSION_ID)); + String name = cursor.getString(cursor.getColumnIndexOrThrow(KEY_VERSION_NAME)); + String version = cursor.getString(cursor.getColumnIndexOrThrow(KEY_VERSION)); + String codename = cursor.getString(cursor.getColumnIndexOrThrow(KEY_CODENAME)); + String distribution = cursor.getString(cursor.getColumnIndexOrThrow(KEY_DESTRIBUTION)); + String target = cursor.getString(cursor.getColumnIndexOrThrow(KEY_TARGET)); + + // Populate fields with extracted properties + holder.nameView.setText(name); + holder.versionView.setText(version); + holder.versionEdit.setOnClickListener(new EditItemOnClickListener(id, name, version, codename, target, distribution)); + holder.versionDelete.setOnClickListener(new DeleteItemOnClickListener(mContext, id, false)); + + listScrollAnimation(view, cursor); + } + + /** + * When the list is scrolled, the item show up with an animation + * + * @param view + * @param cursor + */ + private void listScrollAnimation(View view, Cursor cursor){ + Animation animation = AnimationUtils.loadAnimation(mContext, (cursor.getPosition() > lastPosition) ? R.anim.list_up_from_bottom : R.anim.list_down_from_top); + view.startAnimation(animation); + lastPosition = cursor.getPosition(); + } + + /** + * Edit button click listener + */ + public class EditItemOnClickListener implements View.OnClickListener + { + private String mName; + private String mVersion; + private String mCodename; + private String mTarget; + private String mDistribution; + private String mId; + + public EditItemOnClickListener(String id, String name, String version, String codename, String target, String distribution) { + mId = id; + mName = name; + mVersion = version; + mCodename = codename; + mTarget = target; + mDistribution = distribution; + } + + @Override + public void onClick(View v) + { + editItem(mId, mName, mVersion, mCodename, mTarget, mDistribution); + + } + }; + + + /** + * Calls the edit functionality + * + * @param id + * @param name + * @param version + * @param codename + * @param target + * @param distribution + */ + private void editItem(String id, String name, String version, String codename, String target, String distribution) { + if(AppUtils.mOnEditItemListener != null){ + Bundle editBundle = new Bundle(); + editBundle.putBoolean(EDIT_BUNDLE_DB_TYPE, false); + editBundle.putString(KEY_VERSION_ID, id); + editBundle.putString(KEY_VERSION_NAME, name); + editBundle.putString(KEY_VERSION, version); + editBundle.putString(KEY_CODENAME, codename); + editBundle.putString(KEY_TARGET, target); + editBundle.putString(KEY_DESTRIBUTION, distribution); + AppUtils.mOnEditItemListener.onEditItem(editBundle); + } + } + + /** + * view holder for listview item. + */ + static class ViewHolder { + TextView nameView; + TextView versionView; + ImageView versionDelete; + ImageView versionEdit; + } +} diff --git a/app/src/main/java/com/mi/androidarsenal/app/ApplicationSubClass.java b/app/src/main/java/com/mi/androidarsenal/app/ApplicationSubClass.java new file mode 100644 index 0000000..ca6edf4 --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/app/ApplicationSubClass.java @@ -0,0 +1,51 @@ +package com.mi.androidarsenal.app; + +import android.app.Application; +import android.content.Intent; +import android.content.res.Configuration; + +import com.mi.androidarsenal.activity.MainActivity; + +/** + * Application sub class basically to catch any uncaught exceptions. + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class ApplicationSubClass extends Application { + + @Override + public void onCreate() { + super.onCreate(); + + // Setup handler for uncaught exceptions. + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread thread, Throwable e) { + handleUncaughtException(thread, e); + } + }); + } + + public void handleUncaughtException(Thread thread, Throwable e) { + //TODO log exception, we can send for analytics + + e.printStackTrace(); + Intent intent = new Intent(getApplicationContext(), MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // required when starting from Application + startActivity(intent); + + System.exit(1); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + } + +} diff --git a/app/src/main/java/com/mi/androidarsenal/database/DatabaseHandler.java b/app/src/main/java/com/mi/androidarsenal/database/DatabaseHandler.java new file mode 100644 index 0000000..dea2199 --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/database/DatabaseHandler.java @@ -0,0 +1,193 @@ +package com.mi.androidarsenal.database; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import com.mi.androidarsenal.model.AndroidInfo; +import com.mi.androidarsenal.model.Devices; +import com.mi.androidarsenal.model.Versions; +import com.mi.androidarsenal.utility.AppConstants; + +import java.util.ArrayList; + +/** + * This deals with all the SQlite operations, basically for offline support + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class DatabaseHandler extends SQLiteOpenHelper implements AppConstants { + + private static DatabaseHandler mDatabaseHandler = null; + + /** + * Fetches the same instance of the Fragment sub-class + * + * @return The same instance of the Fragment sub-class every time + */ + public static DatabaseHandler getDatabaseHandlerInstance(Context context) { + if (mDatabaseHandler == null) { + mDatabaseHandler = new DatabaseHandler(context); + } + return mDatabaseHandler; + } + + /** + * Private constructor to fulfill the singleton design pattern + */ + private DatabaseHandler(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + String CREATE_DEVICES_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_DEVICES + "(" + + KEY_ID + " INTEGER PRIMARY KEY," + KEY_DEVICE_ID + " TEXT," + KEY_ANDROID_ID + " TEXT," + + KEY_NAME + " TEXT," + KEY_SNIPPET + " TEXT," + + KEY_IMAGE_URL + " TEXT," + KEY_CARRIER + " TEXT" + ")"; + + // creating version table. + String CREATE_VERSIONS_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_VERSIONS + "(" + + KEY_ID + " INTEGER PRIMARY KEY," + KEY_VERSION_ID + " TEXT," + KEY_VERSION_NAME + + " TEXT," + KEY_VERSION + " TEXT," + KEY_CODENAME + " TEXT," + + KEY_DESTRIBUTION + " TEXT," + KEY_TARGET + " TEXT" + ")"; + + db.execSQL(CREATE_DEVICES_TABLE); + db.execSQL(CREATE_VERSIONS_TABLE); + + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_DEVICES); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_VERSIONS); + + // Create tables again + onCreate(db); + + } + + /** + * Adds all the data to the in memory database for oofline support + * + * @param androidInfo + */ + public void addAndroidInfo(AndroidInfo androidInfo) { + ArrayList devicePropList = androidInfo.getDevicePropList(); + addDevices(devicePropList); + ArrayList versionPropList = androidInfo.getVersionPropList(); + addVersions(versionPropList); + } + + // Adding devices + public void addDevices(ArrayList devicePropList) { + SQLiteDatabase db = null; + try { + db = this.getWritableDatabase(); + db.delete(TABLE_DEVICES, null, null); + ContentValues values = new ContentValues(); + + for (Devices devices : devicePropList) { + values.put(KEY_DEVICE_ID, devices.getId()); + values.put(KEY_ANDROID_ID, devices.getAndroidId()); + values.put(KEY_NAME, devices.getName()); + values.put(KEY_SNIPPET, devices.getSnippet()); + + values.put(KEY_IMAGE_URL, devices.getImageUrl()); + values.put(KEY_CARRIER, devices.getCarrier()); + // Inserting Row + db.insert(TABLE_DEVICES, null, values); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (db != null) + db.close(); + } + + + } + + // Adding versions + public void addVersions(ArrayList versionPropList) { + SQLiteDatabase db = null; + try { + db = this.getWritableDatabase(); + db.delete(TABLE_VERSIONS, null, null); + ContentValues values = new ContentValues(); + + for (Versions versions : versionPropList) { + values.put(KEY_VERSION_ID, versions.getId()); + values.put(KEY_VERSION_NAME, versions.getName()); + values.put(KEY_VERSION, versions.getVersion()); + values.put(KEY_CODENAME, versions.getCodename()); + + values.put(KEY_DESTRIBUTION, versions.getDistribution()); + values.put(KEY_TARGET, versions.getTarget()); + + // Inserting Row + db.insert(TABLE_VERSIONS, null, values); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (db != null) + db.close(); + } + } + + public Cursor getDevicesCursor() { + Cursor cursor = null; + try { + // Select All Query + SQLiteDatabase db = this.getWritableDatabase(); + cursor = db.query(TABLE_DEVICES, null, null, null, null, + null, null); + + } catch (NullPointerException e) { + e.printStackTrace(); + } + + return cursor; + } + + public Cursor getVersionsCursor() { + Cursor cursor = null; + try { + // Select All Query + SQLiteDatabase db = this.getWritableDatabase(); + cursor = db.query(TABLE_VERSIONS, null, null, null, null, + null, null); + + } catch (NullPointerException e) { + e.printStackTrace(); + } + + return cursor; + } + + /** + * deletes a row from the table + * + * @param tableName + * @param key + * @param id + * @return + */ + public boolean deleteRow(String tableName, String key, String id) { + try { + String table = tableName; + String whereClause = key + "=?"; + String[] whereArgs = new String[]{id}; + SQLiteDatabase db = this.getWritableDatabase(); + int result = db.delete(table, whereClause, whereArgs); + return result > 0; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } +} diff --git a/app/src/main/java/com/mi/androidarsenal/database/SharedPreferenceUtil.java b/app/src/main/java/com/mi/androidarsenal/database/SharedPreferenceUtil.java new file mode 100644 index 0000000..b78d021 --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/database/SharedPreferenceUtil.java @@ -0,0 +1,63 @@ +package com.mi.androidarsenal.database; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.mi.androidarsenal.R; + +/** + * This is a utility singleton class which deals with the sharedpreferences + * transactions + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class SharedPreferenceUtil { + + private Context mContext; + private SharedPreferences mSharedPreferences; + private final String OFFLINE_CHECK = "offline"; + + + private static SharedPreferenceUtil mSharedPreferenceUtilInstance = null; + + /** + * Passes the same instance of this singleton class + * + * @param context + * @return + */ + public static SharedPreferenceUtil getSharedPreferenceUtilInstance( + Context context) { + if (mSharedPreferenceUtilInstance == null) { + mSharedPreferenceUtilInstance = new SharedPreferenceUtil(context); + } + return mSharedPreferenceUtilInstance; + } + + private SharedPreferenceUtil(Context context) { + mContext = context; + final String preferencesName = context.getResources().getString( + R.string.preferences); + mSharedPreferences = mContext.getSharedPreferences(preferencesName, + Context.MODE_PRIVATE); + } + + /** + * save if data is successfully added to the database + */ + public void saveDbPopulatedSuccess() { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(OFFLINE_CHECK, true); + editor.commit(); + } + + /** + * check if data is successfully added to the database + * + * @return + */ + public boolean isDbPopulatedSuccess() { + return mSharedPreferences.getBoolean(OFFLINE_CHECK, false); + } +} diff --git a/app/src/main/java/com/mi/androidarsenal/fragment/AndroidVersionsFragment.java b/app/src/main/java/com/mi/androidarsenal/fragment/AndroidVersionsFragment.java new file mode 100644 index 0000000..66e335e --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/fragment/AndroidVersionsFragment.java @@ -0,0 +1,133 @@ +package com.mi.androidarsenal.fragment; + +import android.content.Context; +import android.database.Cursor; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.ProgressBar; + +import com.mi.androidarsenal.R; +import com.mi.androidarsenal.adapter.VersionsAdapter; +import com.mi.androidarsenal.database.DatabaseHandler; +import com.mi.androidarsenal.database.SharedPreferenceUtil; +import com.mi.androidarsenal.network.RequestOperation; +import com.mi.androidarsenal.utility.AppConstants; +import com.mi.androidarsenal.utility.AppUtils; +import com.mi.androidarsenal.utility.OfflineUtil; + +/** + * This fragment shows the versions list + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class AndroidVersionsFragment extends Fragment implements RequestOperation.DeleteItemFromVersionObserver, AppConstants, OfflineUtil.RefreshVersionListObserver, AdapterView.OnItemClickListener{ + private VersionsAdapter mVersionsAdapter; + private LinearLayout mLoadingViewGroup; + private OnVersionSelectedListener mOnVersionSelectedListener; + + public interface OnVersionSelectedListener { + public void onVersionItemSelected(String id, String name, String version, String codename, String target, String distribution); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + OfflineUtil.setRefreshVersionListObserver(this); + RequestOperation.setDeleteItemFromVersionObserver(this); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + + try { + mOnVersionSelectedListener = (OnVersionSelectedListener) context; + } catch (ClassCastException e) { + e.printStackTrace(); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View view = inflater.inflate(R.layout.fragment_android_versions, container, false); + + mLoadingViewGroup = (LinearLayout) view + .findViewById(R.id.loading_layout); + ProgressBar progressBar = (ProgressBar) view + .findViewById(R.id.progress_loading_bar); + + int color = AppUtils.getColorFromRes(getActivity(), R.color.colorPrimary); + progressBar.getIndeterminateDrawable().setColorFilter(color, + android.graphics.PorterDuff.Mode.SRC_IN); + + ListView versionListView = (ListView) view.findViewById(R.id.version_listview); + + Cursor versionCursor = DatabaseHandler.getDatabaseHandlerInstance(getActivity()).getVersionsCursor(); + // Setup cursor adapter using cursor from last step + mVersionsAdapter = new VersionsAdapter(getActivity(), versionCursor); + // Attach cursor adapter to the ListView + versionListView.setAdapter(mVersionsAdapter); + + versionListView.setOnItemClickListener(this); + return view; + } + + @Override + public void onResume() { + super.onResume(); + if (SharedPreferenceUtil.getSharedPreferenceUtilInstance(getActivity()).isDbPopulatedSuccess()) { + mLoadingViewGroup.setVisibility(View.GONE); + } + } + + + @Override + public void onDeleteItemFromVersionTable(String id) { + DatabaseHandler.getDatabaseHandlerInstance(getActivity()).deleteRow(TABLE_VERSIONS, KEY_VERSION_ID, id); + refreshList(); + } + + + + @Override + public void onRefreshVersionList() { + refreshList(); + } + + /** + * + */ + private void refreshList(){ + Cursor versionCursor = DatabaseHandler.getDatabaseHandlerInstance(getActivity()).getVersionsCursor(); + mVersionsAdapter.changeCursor(versionCursor); + mVersionsAdapter.notifyDataSetChanged(); + mLoadingViewGroup.setVisibility(View.GONE); + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long arg3) { + Cursor cursor = (Cursor) mVersionsAdapter.getItem(position); + String id = cursor.getString(cursor.getColumnIndexOrThrow(KEY_VERSION_ID)); + String name = cursor.getString(cursor.getColumnIndexOrThrow(KEY_VERSION_NAME)); + String version = cursor.getString(cursor.getColumnIndexOrThrow(KEY_VERSION)); + String codename = cursor.getString(cursor.getColumnIndexOrThrow(KEY_CODENAME)); + String target = cursor.getString(cursor.getColumnIndexOrThrow(KEY_TARGET)); + String distribution = cursor.getString(cursor.getColumnIndexOrThrow(KEY_DESTRIBUTION)); + + if (mOnVersionSelectedListener != null) { + mOnVersionSelectedListener.onVersionItemSelected(id, name, version, codename, target, distribution); + } + } + + +} diff --git a/app/src/main/java/com/mi/androidarsenal/fragment/DeviceDetailFragment.java b/app/src/main/java/com/mi/androidarsenal/fragment/DeviceDetailFragment.java new file mode 100644 index 0000000..f66ddea --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/fragment/DeviceDetailFragment.java @@ -0,0 +1,75 @@ +package com.mi.androidarsenal.fragment; + + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.android.volley.toolbox.NetworkImageView; +import com.mi.androidarsenal.R; +import com.mi.androidarsenal.network.VolleyHelper; +import com.mi.androidarsenal.utility.AppConstants; + +/** + * This fragment shows the device detail + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class DeviceDetailFragment extends Fragment implements AppConstants { + + public DeviceDetailFragment() { + // Required empty public constructor + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View view = inflater.inflate(R.layout.fragment_device_detail, container, false); + + + Bundle bundle = getArguments(); + + String id = null; + String name = NOT_AVAILABLE_TEXT; + String imageUrl = null; + String description = NOT_AVAILABLE_TEXT; + String carrier = NOT_AVAILABLE_TEXT; + String android_id = NOT_AVAILABLE_TEXT; + + if (bundle != null) { + id = bundle.getString(KEY_DEVICE_ID); + name = bundle.getString(KEY_NAME); + imageUrl = bundle.getString(KEY_IMAGE_URL); + description = bundle.getString(KEY_SNIPPET); + carrier = bundle.getString(KEY_CARRIER); + android_id = bundle.getString(KEY_ANDROID_ID); + } + + TextView nameView = (TextView) view.findViewById(R.id.device_detail_name); + TextView androidIdView = (TextView) view.findViewById(R.id.device_detail_androidid); + TextView carrierView = (TextView) view.findViewById(R.id.device_detail_carrier); + TextView descriptionView = (TextView) view.findViewById(R.id.device_detail_description); + NetworkImageView imageView = (NetworkImageView) view.findViewById(R.id.device_detail_image); + + + nameView.setText(name); + androidIdView.setText(android_id); + carrierView.setText(carrier); + descriptionView.setText(description); + + if (!TextUtils.isEmpty(imageUrl)) { + imageView.setImageUrl(imageUrl, + VolleyHelper.getInstance(getActivity()).getImageLoader()); + } else { + imageView.setVisibility(View.GONE); + } + return view; + } + +} diff --git a/app/src/main/java/com/mi/androidarsenal/fragment/DevicesFragment.java b/app/src/main/java/com/mi/androidarsenal/fragment/DevicesFragment.java new file mode 100644 index 0000000..3a3312c --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/fragment/DevicesFragment.java @@ -0,0 +1,137 @@ +package com.mi.androidarsenal.fragment; + +import android.content.Context; +import android.database.Cursor; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.Toast; + +import com.mi.androidarsenal.R; +import com.mi.androidarsenal.adapter.DevicesAdapter; +import com.mi.androidarsenal.database.DatabaseHandler; +import com.mi.androidarsenal.database.SharedPreferenceUtil; +import com.mi.androidarsenal.network.RequestOperation; +import com.mi.androidarsenal.utility.AndroidResponseObserver; +import com.mi.androidarsenal.utility.AppConstants; +import com.mi.androidarsenal.utility.AppUtils; +import com.mi.androidarsenal.utility.OfflineUtil; + +/** + * This fragment shows the device list + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class DevicesFragment extends Fragment implements RequestOperation.DeleteItemFromDeviceObserver, AppConstants, OfflineUtil.RefreshDeviceListObserver, AdapterView.OnItemClickListener { + + private DevicesAdapter mDevicesAdapter; + OnDeviceSelectedListener mOnDeviceSelectedListener; + private LinearLayout mLoadingViewGroup; + private LinearLayout mNoListViewGroup; + + public interface OnDeviceSelectedListener { + public void onDeviceItemSelected(String id, String name, String imageUrl, String snippet, String carrier, String android_id); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + OfflineUtil.setRefreshDeviceListObserver(this); + RequestOperation.setDeleteItemFromDeviceObserver(this); + } + + + @Override + public void onAttach(Context context) { + super.onAttach(context); + + try { + mOnDeviceSelectedListener = (OnDeviceSelectedListener) context; + } catch (ClassCastException e) { + e.printStackTrace(); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View view = inflater.inflate(R.layout.fragment_devices, container, false); + + mLoadingViewGroup = (LinearLayout) view + .findViewById(R.id.loading_layout); + ProgressBar progressBar = (ProgressBar) view + .findViewById(R.id.progress_loading_bar); + + int color = AppUtils.getColorFromRes(getActivity(), R.color.colorPrimary); + progressBar.getIndeterminateDrawable().setColorFilter(color, + android.graphics.PorterDuff.Mode.SRC_IN); + + mNoListViewGroup = (LinearLayout) view + .findViewById(R.id.no_list_layout); + ListView devicesListView = (ListView) view.findViewById(R.id.devices_listview); + + Cursor devicesCursor = DatabaseHandler.getDatabaseHandlerInstance(getActivity()).getDevicesCursor(); + // Setup cursor adapter using cursor from last step + mDevicesAdapter = new DevicesAdapter(getActivity(), devicesCursor); + // Attach cursor adapter to the ListView + devicesListView.setAdapter(mDevicesAdapter); + + devicesListView.setOnItemClickListener(this); + return view; + } + + @Override + public void onResume() { + super.onResume(); + if (SharedPreferenceUtil.getSharedPreferenceUtilInstance(getActivity()).isDbPopulatedSuccess()) { + mLoadingViewGroup.setVisibility(View.GONE); + } + } + + @Override + public void onDeleteItemFromDeviceTable(String id) { + DatabaseHandler.getDatabaseHandlerInstance(getActivity()).deleteRow(TABLE_DEVICES, KEY_DEVICE_ID, id); + + refreshList(); + } + + @Override + public void onRefreshDeviceList() { + refreshList(); + } + + + private void refreshList() { + Cursor devicesCursor = DatabaseHandler.getDatabaseHandlerInstance(getActivity()).getDevicesCursor(); + mDevicesAdapter.changeCursor(devicesCursor); + mDevicesAdapter.notifyDataSetChanged(); + mLoadingViewGroup.setVisibility(View.GONE); + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long arg3) { + Cursor cursor = (Cursor) mDevicesAdapter.getItem(position); + String id = cursor.getString(cursor.getColumnIndexOrThrow(KEY_DEVICE_ID)); + String name = cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME)); + String imageUrl = cursor.getString(cursor.getColumnIndexOrThrow(KEY_IMAGE_URL)); + String snippet = cursor.getString(cursor.getColumnIndexOrThrow(KEY_SNIPPET)); + String carrier = cursor.getString(cursor.getColumnIndexOrThrow(KEY_CARRIER)); + String android_id = cursor.getString(cursor.getColumnIndexOrThrow(KEY_ANDROID_ID)); + + if (mOnDeviceSelectedListener != null) { + mOnDeviceSelectedListener.onDeviceItemSelected(id, name, imageUrl, snippet, carrier, android_id); + } + } +} diff --git a/app/src/main/java/com/mi/androidarsenal/fragment/VersionDetailFragment.java b/app/src/main/java/com/mi/androidarsenal/fragment/VersionDetailFragment.java new file mode 100644 index 0000000..4929341 --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/fragment/VersionDetailFragment.java @@ -0,0 +1,70 @@ +package com.mi.androidarsenal.fragment; + + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.mi.androidarsenal.R; +import com.mi.androidarsenal.utility.AppConstants; + +/** + * This fragment shows the version detail + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class VersionDetailFragment extends Fragment implements AppConstants { + + + + public VersionDetailFragment() { + // Required empty public constructor + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View view = inflater.inflate(R.layout.fragment_version_detail, container, false); + + + Bundle bundle = getArguments(); + + String id = null; + String name = NOT_AVAILABLE_TEXT; + String version = null; + String codename = NOT_AVAILABLE_TEXT; + String target = NOT_AVAILABLE_TEXT; + String distribution = NOT_AVAILABLE_TEXT; + + if (bundle != null) { + id = bundle.getString(KEY_VERSION_ID); + name = bundle.getString(KEY_VERSION_NAME); + version = bundle.getString(KEY_VERSION); + codename = bundle.getString(KEY_CODENAME); + target = bundle.getString(KEY_TARGET); + distribution = bundle.getString(KEY_DESTRIBUTION); + } + + TextView nameView = (TextView)view.findViewById(R.id.version_detail_name); + TextView versionView = (TextView)view.findViewById(R.id.version_detail_version); + TextView codenameView = (TextView)view.findViewById(R.id.version_detail_codename); + TextView targetView = (TextView)view.findViewById(R.id.version_detail_target); + TextView distributionView = (TextView)view.findViewById(R.id.version_detail_distribution); + + + nameView.setText(name); + versionView.setText(version); + codenameView.setText(codename); + targetView.setText(target); + distributionView.setText(distribution); + + return view; + } + +} diff --git a/app/src/main/java/com/mi/androidarsenal/model/AndroidInfo.java b/app/src/main/java/com/mi/androidarsenal/model/AndroidInfo.java new file mode 100644 index 0000000..aae4bab --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/model/AndroidInfo.java @@ -0,0 +1,30 @@ +package com.mi.androidarsenal.model; + + +import java.util.ArrayList; + +/** + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class AndroidInfo { + private ArrayList devicePropList; + + private ArrayList versionPropList; + + public ArrayList getDevicePropList() { + return devicePropList; + } + + public void setDevicePropList(ArrayList devicePropList) { + this.devicePropList = devicePropList; + } + + public ArrayList getVersionPropList() { + return versionPropList; + } + + public void setVersionPropList(ArrayList versionPropList) { + this.versionPropList = versionPropList; + } +} diff --git a/app/src/main/java/com/mi/androidarsenal/model/Devices.java b/app/src/main/java/com/mi/androidarsenal/model/Devices.java new file mode 100644 index 0000000..164a4cc --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/model/Devices.java @@ -0,0 +1,66 @@ +package com.mi.androidarsenal.model; + + +/** + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class Devices { + + private String id; + private String androidId; + private String carrier; + private String imageUrl; + private String name; + private String snippet; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAndroidId() { + return androidId; + } + + public void setAndroidId(String androidId) { + this.androidId = androidId; + } + + public String getCarrier() { + return carrier; + } + + public void setCarrier(String carrier) { + this.carrier = carrier; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSnippet() { + return snippet; + } + + public void setSnippet(String snippet) { + this.snippet = snippet; + } + + +} diff --git a/app/src/main/java/com/mi/androidarsenal/model/Versions.java b/app/src/main/java/com/mi/androidarsenal/model/Versions.java new file mode 100644 index 0000000..c9e6966 --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/model/Versions.java @@ -0,0 +1,62 @@ +package com.mi.androidarsenal.model; + +/** + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class Versions { + private String id; + private String name; + private String version; + private String codename; + private String target; + private String distribution; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getCodename() { + return codename; + } + + public void setCodename(String codename) { + this.codename = codename; + } + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } + + public String getDistribution() { + return distribution; + } + + public void setDistribution(String distribution) { + this.distribution = distribution; + } +} diff --git a/app/src/main/java/com/mi/androidarsenal/network/BitmapCache.java b/app/src/main/java/com/mi/androidarsenal/network/BitmapCache.java new file mode 100644 index 0000000..f2457ac --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/network/BitmapCache.java @@ -0,0 +1,33 @@ +package com.mi.androidarsenal.network; + +import android.graphics.Bitmap; +import android.support.v4.util.LruCache; + +import com.android.volley.toolbox.ImageLoader; + +/** + * Cache implementation for images + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class BitmapCache extends LruCache implements ImageLoader.ImageCache { + public BitmapCache(int maxSize) { + super(maxSize); + } + + @Override + protected int sizeOf(String key, Bitmap value) { + return value.getRowBytes() * value.getHeight(); + } + + @Override + public Bitmap getBitmap(String url) { + return get(url); + } + + @Override + public void putBitmap(String url, Bitmap bitmap) { + put(url, bitmap); + } +} diff --git a/app/src/main/java/com/mi/androidarsenal/network/OkHttpStack.java b/app/src/main/java/com/mi/androidarsenal/network/OkHttpStack.java new file mode 100644 index 0000000..51a9a27 --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/network/OkHttpStack.java @@ -0,0 +1,34 @@ +package com.mi.androidarsenal.network; + +import com.android.volley.toolbox.HurlStack; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.OkUrlFactory; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * uses OkHttp as its transport. + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class OkHttpStack extends HurlStack { + private final OkUrlFactory mFactory; + + public OkHttpStack() { + this(new OkHttpClient()); + } + + public OkHttpStack(OkHttpClient client) { + if (client == null) { + throw new NullPointerException("Client must not be null."); + } + mFactory = new OkUrlFactory(client); + } + + @Override protected HttpURLConnection createConnection(URL url) throws IOException { + return mFactory.open(url); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mi/androidarsenal/network/RequestOperation.java b/app/src/main/java/com/mi/androidarsenal/network/RequestOperation.java new file mode 100644 index 0000000..6973bb9 --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/network/RequestOperation.java @@ -0,0 +1,505 @@ +package com.mi.androidarsenal.network; + +import android.content.Context; +import android.util.Log; + +import com.android.volley.AuthFailureError; +import com.android.volley.Request; +import com.android.volley.Response; +import com.android.volley.VolleyError; +import com.android.volley.toolbox.StringRequest; +import com.mi.androidarsenal.activity.MainActivity; +import com.mi.androidarsenal.utility.AppConstants; +import com.mi.androidarsenal.utility.AndroidResponseObserver; +import com.mi.androidarsenal.utility.DeleteItemOnClickListener; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; + + +/** + * This class is responsible for all the network requests. It uses volley with okhttp as its transport for faster networking + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class RequestOperation implements AppConstants { + + private final Context context; + + private static RequestOperation mRequestOperation = null; + private AndroidResponseObserver mAndroidResponseObserver; + + /** + * + */ + public interface OnRefreshDbListener { + public void onRefreshDb(); + } + + private static OnRefreshDbListener mOnRefreshDbListener; + + public static void setOnRefreshDbListener(OnRefreshDbListener observer) { + mOnRefreshDbListener = observer; + } + + /** + * + */ + public interface DeleteItemFromDeviceObserver { + public void onDeleteItemFromDeviceTable(String id); + } + + private static DeleteItemFromDeviceObserver mDeleteItemFromDeviceObserver; + + public static void setDeleteItemFromDeviceObserver(DeleteItemFromDeviceObserver observer) { + mDeleteItemFromDeviceObserver = observer; + } + + /** + * + */ + public interface DeleteItemFromVersionObserver { + public void onDeleteItemFromVersionTable(String id); + } + + private static DeleteItemFromVersionObserver mDeleteItemFromVersionObserver; + + public static void setDeleteItemFromVersionObserver(DeleteItemFromVersionObserver observer) { + mDeleteItemFromVersionObserver = observer; + } + + /** + * singleton implementation + * + * @param context + * @return + */ + public static RequestOperation getRequestOperationInstance(Context context) { + if (mRequestOperation == null) { + mRequestOperation = new RequestOperation(context); + } + return mRequestOperation; + } + + private RequestOperation(Context context) { + this.context = context; + } + + /** + * GET request + * + * @param mainActivity + */ + public void getAllData(MainActivity mainActivity) { + try { + mAndroidResponseObserver = (AndroidResponseObserver) mainActivity; + } catch (ClassCastException e) { + + } + + StringBuilder url = new StringBuilder(BASE_URL); + url.append(ALL_SUB_URL); + + // Request a string response from the provided URL. + StringRequest stringRequest = new StringRequest(Request.Method.GET, + url.toString(), getAllDataSuccess, getAllDataError); + + // Add the request to the RequestQueue. + VolleyHelper + .getInstance( + this.context.getApplicationContext()) + .getRequestQueue().add(stringRequest); + } + + /** + * Sends a DELETE request to the server from the device database + * + * @param id + * @param deleteItemOnClickListener + */ + public void deleteDeviceData(String id, DeleteItemOnClickListener deleteItemOnClickListener) { + StringBuilder url = new StringBuilder(BASE_URL); + url.append(DEVICES_SUB_URL); + url.append("/"); + url.append(id); + + DeleteDeviceItemSuccess deleteDeviceItemSuccess = new DeleteDeviceItemSuccess(id, true); + + // Request a string response from the provided URL. + StringRequest stringRequest = new StringRequest(Request.Method.DELETE, + url.toString(), deleteDeviceItemSuccess, deleteDeviceItemError); + + // Add the request to the RequestQueue. + VolleyHelper + .getInstance( + this.context.getApplicationContext()) + .getRequestQueue().add(stringRequest); + + } + + /** + * Sends a DELETE request to the server from the version database + * + * @param id + * @param deleteItemOnClickListener + */ + public void deleteVersionData(String id, DeleteItemOnClickListener deleteItemOnClickListener) { + StringBuilder url = new StringBuilder(BASE_URL); + url.append(VERSIONS_SUB_URL); + url.append("/"); + url.append(id); + + DeleteDeviceItemSuccess deleteDeviceItemSuccess = new DeleteDeviceItemSuccess(id, false); + + // Request a string response from the provided URL. + StringRequest stringRequest = new StringRequest(Request.Method.DELETE, + url.toString(), deleteDeviceItemSuccess, deleteDeviceItemError); + + // Add the request to the RequestQueue. + VolleyHelper + .getInstance( + this.context.getApplicationContext()) + .getRequestQueue().add(stringRequest); + + } + + /** + * Configures the POST request to the device database of server + * + * @param name + * @param snippet + * @param carrier + * @param android_id + * @param imageUrl + */ + public void postDeviceInfo(String name, String snippet, String carrier, String android_id, String imageUrl) { + + StringBuilder url = new StringBuilder(BASE_URL); + url.append(DEVICES_SUB_URL); + + JSONObject json = new JSONObject(); + try { + json.put("androidId", android_id); + json.put("carrier", carrier); + json.put("imageUrl", imageUrl); + json.put("name", name); + json.put("snippet", snippet); + } catch (JSONException e) { + // handle exception (not supposed to happen) + } + postData(url.toString(), json.toString()); + } + + /** + * Configures the POST request to the Version database of server + * + * @param name + * @param version + * @param codename + * @param target + * @param distribution + */ + public void postVersionInfo(String name, String version, String codename, String target, String distribution) { + StringBuilder url = new StringBuilder(BASE_URL); + url.append(VERSIONS_SUB_URL); + + JSONObject json = new JSONObject(); + try { + json.put("name", name); + json.put("version", version); + json.put("codename", codename); + json.put("target", target); + json.put("distribution", distribution); + } catch (JSONException e) { + // handle exception (not supposed to happen) + } + postData(url.toString(), json.toString()); + } + + /** + * Sends a POST request to the server according to the database configured + * + * @param url + * @param jsonString + */ + private void postData(String url, String jsonString) { + PostDataSuccess postDataSuccess = new PostDataSuccess(); + + // Request a string response from the provided URL. + PostDataRequest stringRequest = new PostDataRequest(Request.Method.POST, + url, jsonString, postDataSuccess, postDataError); + + try { + stringRequest.getHeaders(); + } catch (AuthFailureError e) { + e.printStackTrace(); + } + + // Add the request to the RequestQueue. + VolleyHelper + .getInstance( + this.context.getApplicationContext()) + .getRequestQueue().add(stringRequest); + } + + /** + * POST request + */ + public class PostDataRequest extends StringRequest { + + private final String jsonString; + + public PostDataRequest(int method, String url, String jsonString, + Response.Listener listener, Response.ErrorListener errorListener) { + super(method, url, listener, errorListener); + this.jsonString = jsonString; + } + + @Override + public String getBodyContentType() { + return "application/json"; + } + + + @Override + public Map getHeaders() throws AuthFailureError { + Map header = new HashMap(); + header.put("Content-Type", "application/json"); + return header; + } + + @Override + public byte[] getBody() throws AuthFailureError { + + try { + return jsonString.getBytes("utf-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return null; + } + } + } + + /** + * Configures the PUT request to the device database of server + * + * @param id + * @param name + * @param snippet + * @param carrier + * @param android_id + * @param imageUrl + */ + public void putDeviceInfo(String id, String name, String snippet, String carrier, String android_id, String imageUrl) { + StringBuilder url = new StringBuilder(BASE_URL); + url.append(DEVICES_SUB_URL); + url.append("/"); + url.append(id); + + JSONObject json = new JSONObject(); + try { + json.put("androidId", android_id); + json.put("carrier", carrier); + json.put("imageUrl", imageUrl); + json.put("name", name); + json.put("snippet", snippet); + } catch (JSONException e) { + // handle exception (not supposed to happen) + } + putData(url.toString(), json.toString()); + } + + /** + * Configures the PUT request to the version database of server + * + * @param id + * @param name + * @param version + * @param codename + * @param target + * @param distribution + */ + public void putVersionInfo(String id, String name, String version, String codename, String target, String distribution) { + StringBuilder url = new StringBuilder(BASE_URL); + url.append(VERSIONS_SUB_URL); + url.append("/"); + url.append(id); + + JSONObject json = new JSONObject(); + try { + json.put("name", name); + json.put("version", version); + json.put("codename", codename); + json.put("target", target); + json.put("distribution", distribution); + } catch (JSONException e) { + // handle exception (not supposed to happen) + } + putData(url.toString(), json.toString()); + } + + /** + * Sends PUT request + * + * @param url + * @param jsonString + */ + private void putData(String url, String jsonString) { + PostDataSuccess postDataSuccess = new PostDataSuccess(); + + // Request a string response from the provided URL. + PutDataRequest stringRequest = new PutDataRequest(Request.Method.PUT, + url, jsonString, postDataSuccess, postDataError); + + + try { + stringRequest.getHeaders(); + } catch (AuthFailureError e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + // Add the request to the RequestQueue. + VolleyHelper + .getInstance( + this.context.getApplicationContext()) + .getRequestQueue().add(stringRequest); + } + + /** + * PUT request + */ + public class PutDataRequest extends StringRequest { + + private final String jsonString; + + public PutDataRequest(int method, String url, String jsonString, + Response.Listener listener, Response.ErrorListener errorListener) { + super(method, url, listener, errorListener); + this.jsonString = jsonString; + } + + @Override + public String getBodyContentType() { + return "application/json"; + } + + + @Override + public Map getHeaders() throws AuthFailureError { + Map header = new HashMap(); + header.put("Content-Type", "application/json"); + return header; + } + + @Override + public byte[] getBody() throws AuthFailureError { + + try { + return jsonString.getBytes("utf-8"); + } catch (UnsupportedEncodingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + } + + + private Response.Listener getAllDataSuccess = new Response.Listener() { + + @Override + public void onResponse(String response) { + Log.v("", "RESPONSE : " + response); + if (mAndroidResponseObserver != null) { + mAndroidResponseObserver.onAllDataGetResponse(response); + } + } + + }; + + private Response.ErrorListener getAllDataError = new Response.ErrorListener() { + + @Override + public void onErrorResponse(VolleyError volleyError) { + + } + }; + + private Response.Listener getVersionsSuccess = new Response.Listener() { + + @Override + public void onResponse(String response) { + Log.v("", "RESPONSE : " + response); + + + } + + }; + + private Response.ErrorListener getVersionsError = new Response.ErrorListener() { + + @Override + public void onErrorResponse(VolleyError volleyError) { + + } + }; + + + class PostDataSuccess implements Response.Listener { + + @Override + public void onResponse(String response) { + Log.v("", "RESPONSE : " + response); + if (mOnRefreshDbListener != null) + mOnRefreshDbListener.onRefreshDb(); + } + } + + private Response.ErrorListener postDataError = new Response.ErrorListener() { + + @Override + public void onErrorResponse(VolleyError volleyError) { + + } + }; + + /** + * + */ + class DeleteDeviceItemSuccess implements Response.Listener { + + private String mId; + private boolean mIsDevice; + + public DeleteDeviceItemSuccess(String id, boolean isDevice) { + mId = id; + mIsDevice = isDevice; + } + + @Override + public void onResponse(String response) { + Log.v("", "RESPONSE : " + response); + if (mIsDevice) { + if (mDeleteItemFromDeviceObserver != null) + mDeleteItemFromDeviceObserver.onDeleteItemFromDeviceTable(mId); + } else { + if (mDeleteItemFromVersionObserver != null) + mDeleteItemFromVersionObserver.onDeleteItemFromVersionTable(mId); + } + } + } + + private Response.ErrorListener deleteDeviceItemError = new Response.ErrorListener() { + + @Override + public void onErrorResponse(VolleyError volleyError) { + + } + }; +} diff --git a/app/src/main/java/com/mi/androidarsenal/network/VolleyHelper.java b/app/src/main/java/com/mi/androidarsenal/network/VolleyHelper.java new file mode 100644 index 0000000..14d0959 --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/network/VolleyHelper.java @@ -0,0 +1,110 @@ +package com.mi.androidarsenal.network; + +import android.app.ActivityManager; +import android.content.Context; + +import com.android.volley.Request; +import com.android.volley.RequestQueue; +import com.android.volley.toolbox.BasicNetwork; +import com.android.volley.toolbox.DiskBasedCache; +import com.android.volley.toolbox.ImageLoader; +import com.squareup.okhttp.OkHttpClient; + +import java.io.File; + +/** + * Volley helper class + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class VolleyHelper { + + private RequestQueue mRequestQueue; + private ImageLoader mImageLoader; + private static VolleyHelper sVolleyHelper; + private Context mContext; + private final int DISK_USAGE_BYTES = 60 * 1024 * 1024; + public final String CACHE_DIR = "mi_photos"; + private File cacheDir; + + + private RequestQueue newRequestQueue(Context context) { + File rootCache = context.getExternalCacheDir(); + if (rootCache == null) { + rootCache = context.getCacheDir(); + } + cacheDir = new File(rootCache, CACHE_DIR); + cacheDir.mkdirs(); + OkHttpStack stack = new OkHttpStack(new OkHttpClient()); + + BasicNetwork network = new BasicNetwork(stack); + DiskBasedCache diskBasedCache = new DiskBasedCache(cacheDir, DISK_USAGE_BYTES); + RequestQueue queue = new RequestQueue(diskBasedCache, network); + + queue.start(); + return queue; + } + + + private RequestQueue.RequestFilter mRequestFilter = new RequestQueue.RequestFilter() { + @Override + public boolean apply(Request request) { + return true; + } + }; + + public static synchronized VolleyHelper getInstance(Context context) { + if (sVolleyHelper == null) { + sVolleyHelper = new VolleyHelper(context); + } + return sVolleyHelper; + } + + private VolleyHelper(Context context) { + mContext = context; + init(); + } + + public void init() { + initRequestQueue(); + initImageLoader(); + } + + private void initRequestQueue() { + mRequestQueue = newRequestQueue(mContext); + } + + private void initImageLoader() { + int memClass = ((ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); + int cacheSize = 1024 * 1024 * memClass / 8; + mImageLoader = new ImageLoader(mRequestQueue, new BitmapCache(cacheSize)); + } + + public RequestQueue getRequestQueue() { + if (mRequestQueue != null) { + return mRequestQueue; + } else { + init(); + return mRequestQueue; + } + } + + public ImageLoader getImageLoader() { + if (mImageLoader != null) { + return mImageLoader; + } else { + init(); + return mImageLoader; + } + } + + public void release() { + if (mRequestQueue != null) { + mRequestQueue.cancelAll(mRequestFilter); + } + + } + + +} diff --git a/app/src/main/java/com/mi/androidarsenal/parser/ParseResponse.java b/app/src/main/java/com/mi/androidarsenal/parser/ParseResponse.java new file mode 100644 index 0000000..6d8746a --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/parser/ParseResponse.java @@ -0,0 +1,98 @@ +package com.mi.androidarsenal.parser; + + +import com.mi.androidarsenal.model.AndroidInfo; +import com.mi.androidarsenal.model.Devices; +import com.mi.androidarsenal.model.Versions; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; + +/** + * Responsible for parsing data + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class ParseResponse { + + + /** + * @param jsonResponse + * @return + */ + public AndroidInfo parseAndroidInfoResponse(String jsonResponse) { + AndroidInfo androidInfo = null; + try { + + JSONObject mainObject = new JSONObject(jsonResponse); + + androidInfo = new AndroidInfo(); + ArrayList versionPropList = new ArrayList(); + ArrayList devicePropList = new ArrayList(); + + if (mainObject.has("android")) { + JSONArray androidArray = mainObject.getJSONArray("android"); + + for (int i = 0; i < androidArray.length(); i++) { + Versions versions = new Versions(); + JSONObject versionObj = androidArray.getJSONObject(i); + + + String id = versionObj.optString("id"); + String name = versionObj.optString("name"); + String version = versionObj.optString("version"); + String codename = versionObj.optString("codename"); + String target = versionObj.optString("target"); + String distribution = versionObj.optString("distribution"); + + versions.setId(id); + versions.setName(name); + versions.setVersion(version); + versions.setCodename(codename); + versions.setTarget(target); + versions.setDistribution(distribution); + + versionPropList.add(versions); + } + androidInfo.setVersionPropList(versionPropList); + } + + if (mainObject.has("devices")) { + JSONArray devicesArray = mainObject.getJSONArray("devices"); + + for (int i = 0; i < devicesArray.length(); i++) { + Devices devices = new Devices(); + JSONObject devicesObj = devicesArray.getJSONObject(i); + + String id = devicesObj.optString("id"); + String androidId = devicesObj.optString("androidId"); + String carrier = devicesObj.optString("carrier"); + String imageUrl = devicesObj.optString("imageUrl"); + String name = devicesObj.optString("name"); + String snippet = devicesObj.optString("snippet"); + + devices.setId(id); + devices.setAndroidId(androidId); + devices.setCarrier(carrier); + devices.setImageUrl(imageUrl); + devices.setName(name); + devices.setSnippet(snippet); + + devicePropList.add(devices); + } + androidInfo.setDevicePropList(devicePropList); + } + + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + return androidInfo; + } + } + +} diff --git a/app/src/main/java/com/mi/androidarsenal/utility/AndroidResponseObserver.java b/app/src/main/java/com/mi/androidarsenal/utility/AndroidResponseObserver.java new file mode 100644 index 0000000..a335355 --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/utility/AndroidResponseObserver.java @@ -0,0 +1,11 @@ +package com.mi.androidarsenal.utility; + + +/** + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public interface AndroidResponseObserver { + + public void onAllDataGetResponse(String jsonResponse); +} diff --git a/app/src/main/java/com/mi/androidarsenal/utility/AppConstants.java b/app/src/main/java/com/mi/androidarsenal/utility/AppConstants.java new file mode 100644 index 0000000..a554c43 --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/utility/AppConstants.java @@ -0,0 +1,49 @@ +package com.mi.androidarsenal.utility; + +/** + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public interface AppConstants { + + public final String BASE_URL = "http://mobilesandboxdev.azurewebsites.net"; + public final String ALL_SUB_URL = "/db"; + public final String DEVICES_SUB_URL = "/devices"; + public final String VERSIONS_SUB_URL = "/android"; + + // Database Version + public final int DATABASE_VERSION = 1; + + // Database Name + public final String DATABASE_NAME = "AndroidArsenal"; + + // Devices,Versions table name + public final String TABLE_DEVICES = "AndroidDevices"; + public final String TABLE_VERSIONS = "AndroidVersions"; + + public final String KEY_ID = "_id"; + + // Devices Table Columns names + public final String KEY_DEVICE_ID = "device_id"; + public final String KEY_ANDROID_ID = "android_id"; + public final String KEY_NAME = "name"; + public final String KEY_SNIPPET = "snippet"; + public final String KEY_IMAGE_URL = "image_url"; + public final String KEY_CARRIER = "carrier"; + + // versions Table Columns names + public final String KEY_VERSION_ID = "version_id"; + public final String KEY_VERSION_NAME = "name"; + public final String KEY_VERSION = "version"; + public final String KEY_CODENAME = "codename"; + public final String KEY_DESTRIBUTION = "destribution"; + public final String KEY_TARGET = "target"; + + public final String NOT_AVAILABLE_TEXT = "N/A"; + + public final String EDIT_BUNDLE_DB_TYPE = "db_type"; + + public final String EMPTY_FIELDS = "All fields are empty"; + + public final String NO_NETWORK = "No network available"; +} diff --git a/app/src/main/java/com/mi/androidarsenal/utility/AppUtils.java b/app/src/main/java/com/mi/androidarsenal/utility/AppUtils.java new file mode 100644 index 0000000..d87df5c --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/utility/AppUtils.java @@ -0,0 +1,55 @@ +package com.mi.androidarsenal.utility; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Build; +import android.support.v4.app.FragmentActivity; +import android.widget.Toast; + +/** + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class AppUtils implements AppConstants { + + public static OnEditItemListener mOnEditItemListener; + + public static void setOnEditItemListener(OnEditItemListener observer) { + mOnEditItemListener = observer; + } + + /** + * Checks the network connectivity + * + * @return + */ + public static boolean isNetworkAvailable(Context ctx) { + ConnectivityManager connectivityManager = (ConnectivityManager) ctx + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetworkInfo = connectivityManager + .getActiveNetworkInfo(); + + boolean isAvailable = activeNetworkInfo != null + && activeNetworkInfo.isConnected(); + + if (!isAvailable) { + Toast.makeText(ctx, NO_NETWORK, Toast.LENGTH_LONG).show(); + } + + return isAvailable; + } + + public static int getColorFromRes(FragmentActivity activity, int colorNeeded) { + int color; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + color = activity.getColor(colorNeeded); + } else { + //noinspection deprecation + color = activity.getResources().getColor(colorNeeded); + } + return color; + } + + +} diff --git a/app/src/main/java/com/mi/androidarsenal/utility/DeleteItemOnClickListener.java b/app/src/main/java/com/mi/androidarsenal/utility/DeleteItemOnClickListener.java new file mode 100644 index 0000000..2fc67a1 --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/utility/DeleteItemOnClickListener.java @@ -0,0 +1,73 @@ +package com.mi.androidarsenal.utility; + +import android.app.AlertDialog; +import android.content.Context; +import android.graphics.drawable.ColorDrawable; +import android.view.View; + +import com.mi.androidarsenal.R; +import com.mi.androidarsenal.network.RequestOperation; + +/** + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class DeleteItemOnClickListener implements View.OnClickListener { + private final Context mContext; + private boolean mIsDevice; + private String mId; + + public DeleteItemOnClickListener(Context context, String id, boolean isDevice) { + mContext = context; + mId = id; + mIsDevice = isDevice; + } + + @Override + public void onClick(View v) { + showConfirmationDialog(); + + + } + + private void showConfirmationDialog() { + + final View dialogView = View.inflate(mContext, R.layout.confirmation_dialog, null); + + AlertDialog.Builder builder = new AlertDialog.Builder(mContext); + builder.setView(dialogView) + .setCancelable(false); + + + final AlertDialog mDialog = builder.create(); + dialogView.findViewById(R.id.no).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mDialog.dismiss(); + } + }); + dialogView.findViewById(R.id.yes).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + deleteItem(); + mDialog.dismiss(); + } + }); + + + mDialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT)); + mDialog.show(); + } + + private void deleteItem() { + if (AppUtils.isNetworkAvailable(mContext.getApplicationContext())) { + RequestOperation requestOperation = RequestOperation.getRequestOperationInstance(mContext); + if (mIsDevice) { + requestOperation.deleteDeviceData(mId, this); + } else { + requestOperation.deleteVersionData(mId, this); + } + } + } + +} diff --git a/app/src/main/java/com/mi/androidarsenal/utility/OfflineUtil.java b/app/src/main/java/com/mi/androidarsenal/utility/OfflineUtil.java new file mode 100644 index 0000000..0e240e3 --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/utility/OfflineUtil.java @@ -0,0 +1,79 @@ +package com.mi.androidarsenal.utility; + +import android.content.Context; +import android.os.AsyncTask; + +import com.mi.androidarsenal.database.DatabaseHandler; +import com.mi.androidarsenal.database.SharedPreferenceUtil; +import com.mi.androidarsenal.model.AndroidInfo; +import com.mi.androidarsenal.parser.ParseResponse; + +/** + * Utility class for offline storage + * + * @author Samir Sarosh + */ +@SuppressWarnings("ALL") +public class OfflineUtil { + + private final Context mContext; + + public interface RefreshDeviceListObserver { + public void onRefreshDeviceList(); + } + + private static RefreshDeviceListObserver mRefreshDeviceListObserver; + + public static void setRefreshDeviceListObserver(RefreshDeviceListObserver observer) { + mRefreshDeviceListObserver = observer; + } + + public interface RefreshVersionListObserver { + public void onRefreshVersionList(); + } + + private static RefreshVersionListObserver mRefreshVersionListObserver; + + public static void setRefreshVersionListObserver(RefreshVersionListObserver observer) { + mRefreshVersionListObserver = observer; + } + + public OfflineUtil(Context context) { + mContext = context; + } + + public void populateResponseInMemory(String response) { + PopulateDevicesTask populateDevicesTask = new PopulateDevicesTask(); + populateDevicesTask.execute(response); + } + + class PopulateDevicesTask extends AsyncTask { + + @Override + protected Void doInBackground(String... response) { + ParseResponse parseResponse = new ParseResponse(); + AndroidInfo androidInfo = parseResponse.parseAndroidInfoResponse(response[0]); + + if (androidInfo != null) { + + DatabaseHandler.getDatabaseHandlerInstance(mContext) + .addAndroidInfo(androidInfo); + } + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + SharedPreferenceUtil.getSharedPreferenceUtilInstance(mContext).saveDbPopulatedSuccess(); + + if (mRefreshDeviceListObserver != null) { + mRefreshDeviceListObserver.onRefreshDeviceList(); + } + if (mRefreshVersionListObserver != null) { + mRefreshVersionListObserver.onRefreshVersionList(); + } + } + } + +} diff --git a/app/src/main/java/com/mi/androidarsenal/utility/OnEditItemListener.java b/app/src/main/java/com/mi/androidarsenal/utility/OnEditItemListener.java new file mode 100644 index 0000000..da349e4 --- /dev/null +++ b/app/src/main/java/com/mi/androidarsenal/utility/OnEditItemListener.java @@ -0,0 +1,7 @@ +package com.mi.androidarsenal.utility; + +import android.os.Bundle; + +public interface OnEditItemListener { + public void onEditItem(Bundle editBundle); +} diff --git a/app/src/main/res/anim/list_down_from_top.xml b/app/src/main/res/anim/list_down_from_top.xml new file mode 100644 index 0000000..e838a19 --- /dev/null +++ b/app/src/main/res/anim/list_down_from_top.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/list_up_from_bottom.xml b/app/src/main/res/anim/list_up_from_bottom.xml new file mode 100644 index 0000000..0befb10 --- /dev/null +++ b/app/src/main/res/anim/list_up_from_bottom.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png new file mode 100644 index 0000000..481643e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png new file mode 100644 index 0000000..977dd34 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png new file mode 100644 index 0000000..6704210 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/device.png b/app/src/main/res/drawable-xxhdpi/device.png new file mode 100644 index 0000000..dc4634e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/device.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png new file mode 100644 index 0000000..72cedca Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_add_white_24dp.png new file mode 100644 index 0000000..2bef059 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_add_white_24dp.png differ diff --git a/app/src/main/res/drawable/dialog_bg.xml b/app/src/main/res/drawable/dialog_bg.xml new file mode 100644 index 0000000..4d37b3f --- /dev/null +++ b/app/src/main/res/drawable/dialog_bg.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shadow_below.xml b/app/src/main/res/drawable/shadow_below.xml new file mode 100644 index 0000000..978ea0a --- /dev/null +++ b/app/src/main/res/drawable/shadow_below.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..1631a26 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/confirmation_dialog.xml b/app/src/main/res/layout/confirmation_dialog.xml new file mode 100644 index 0000000..f4f8a9d --- /dev/null +++ b/app/src/main/res/layout/confirmation_dialog.xml @@ -0,0 +1,35 @@ + + + + + +