Introduction

There are many app/game market operators, such as UC, XiaoMi, Huawei, etc. These operators are also called channels. All of them have different specifications, and those specifications are always evoluting. As a result, it's very difficult to integrate your application with so many channels and keep them updated. To liberate you from this unimaginable task, SG Studios provides a solution by abstracting the channels' client SDKs and server interfaces into thin layers. With this solution you do not need care about details in various SDKs any more. Instead what you need to to is only integrating with SGUtil software package and send your APKs to SG Studios. SG Studios will then re-package them with channel SDKs you need to generate new APKs. Thus your workload get dramatically decreased. For the server side, what you need to do is to implement a simple game server (you may use SG Studios's server if you do not need complex features) that manages app/game properties and orders.

You need to register your application at channel sites and SG Studios site in order to utlize SG Studios's service. You should share the information generated by channel sites with SG Studios.

This page illustrates how to integrate with SGUtil. Please refer to SGUtil Document for details.

Development Environment

1. Minimum Android version requirement:

SGUtil requires Android version not lower than 4.4 (KitKat, API Level 19) to run. We also recommend you use Android Studios as your main development environment.


2. Download SGUtil library


3. Sample application

package com.sg.sgutiltest;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.text.Layout;
import android.text.TextUtils;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;

import com.sg.util.SGActivity;
import com.sg.util.SGAgent;
import com.sg.util.SGGameServerDemo;
import com.sg.util.UOrder;
import com.sg.util.UProduct;

import static android.view.View.TEXT_ALIGNMENT_CENTER;

/**
 * SGTutorialActivity is a main activity that extends SGActivity to utilize its
 * lifecycle callback forwarding. SGActivity has a protected field 'agent', which
 * is an instance of SGAgent.
 *
 * The activity has following content view layout.
 *
 * +-----------------------------------+
 * |                                   |
 * | product list view                 |
 * |                                   |
 * +-----------------------------------+
 * | btnUsr | btnJob | btnPay | btnExt |
 * +-----------------------------------+
 * |                                   |
 * | log view                          |
 * |                                   |
 * +-----------------------------------+
 */
public class SGTutorialActivity extends SGActivity {

	private class XProduct {
		UProduct product;
		String orderID;
		String state;
	}

	private class XOrder {
		String orderID;
		String productID;
		String productName;
		int buyNum;
		String state;
		String time;
	}

	private Handler mHandler = new Handler();

    // an list adapter to manage products
    private ProductListAdapter mProductListAdapter;
    // an list adapter to manage orders
	private OrderListAdapter mOrderListAdapter;
    // button for login/logout
    private Button mBtnUsr;
    // button for get product list
    private Button mBtnJob;
    // button for purchasing product
    private Button mBtnPay;
    // button for exit program
    private Button mBtnExt;

    // enable/disable buttons, change button captions according to latest states
    private void updateButtons() {

        int usrState = agent.getUsrState();
        int payState = agent.getPayState();
        int jobState = agent.getJobState();

		/* mBtnUsr */
        if (usrState < SGAgent.USRSTATE_LOGINEDGAME) {
            setButtonText(mBtnUsr, R.string.btn_main_login);
        } else {
            setButtonText(mBtnUsr, R.string.btn_main_logout);
        }
        if ((usrState == SGAgent.USRSTATE_INITIALIZED || usrState == SGAgent.USRSTATE_LOGINEDGAME) && jobState == SGAgent.JOBSTATE_IDLE && payState == SGAgent.PAYSTATE_IDLE && (usrState < SGAgent.USRSTATE_LOGINEDGAME || agent.isLogoutSupported()))
            mBtnUsr.setEnabled(true);
        else
            mBtnUsr.setEnabled(false);

        boolean idle = usrState == SGAgent.USRSTATE_LOGINEDGAME && jobState == SGAgent.JOBSTATE_IDLE && payState == SGAgent.PAYSTATE_IDLE;

		/* mBtnJob */
		if (mProductListAdapter.getCount() == 0) {
            setButtonText(mBtnJob, R.string.btn_main_products);
			mBtnJob.setEnabled(idle);
		} else {
			setButtonText(mBtnJob, R.string.btn_main_orders);
			mBtnJob.setEnabled(idle);
		}

		/* mBtnPay */
		XProduct p = mProductListAdapter.getHighlightProduct();
		String state = p == null ? "" : p.state;
        if (state.equals("")) {
            setButtonText(mBtnPay, R.string.btn_main_oops);
        } else if (state.equals("delivered") || state.equals("succeeded") || state.equals("cheated")) {
            setButtonText(mBtnPay, R.string.btn_main_info);
        } else if (state.equals("accepted")) {
            setButtonText(mBtnPay, R.string.btn_main_check);
        } else {
            setButtonText(mBtnPay, R.string.btn_main_buy);
        }
        mBtnPay.setEnabled(idle && !state.equals(""));

		/* mBtnExt */
        setButtonText(mBtnExt, R.string.btn_main_exit);
        mBtnExt.setEnabled(idle || usrState <= SGAgent.USRSTATE_INITIALIZED);
    }

    // internal class to hold product information and views
    private static class ProductViewHolder {
        TextView name;          // TextView to show product name
        TextView price;         // TextView to product price
        TextView value;         // TextView to product value
        TextView state;         // TextView to product state
        XProduct productRef;    // product information
    }

    // list adapter to manage products
    private class ProductListAdapter extends BaseAdapter {
        private LayoutInflater mInflator;
        private List<XProduct> mList;
        private XProduct mHighlightProduct; // highlighted product (current, active)

        public ProductListAdapter() {
            super();
            // initialize list to empty
            mInflator = SGTutorialActivity.this.getLayoutInflater();
            mList = new ArrayList<XProduct>();
            mHighlightProduct = null;
        }

        public boolean addProduct(XProduct product) {
            if (!mList.contains(product)) {
                mList.add(product);
                if (mHighlightProduct == null)
                    mHighlightProduct = product;
                return true;
            }
            return false;
        }

        public void changeProductState(String productID, String orderID, String state) {
            for (int i=0; i<mList.size(); i++) {
                XProduct xp = (XProduct)mList.get(i);
                if (!xp.product.getProductID().equals(productID))
                    continue;
                if (xp.orderID != null && !xp.orderID.equals(orderID))
                    continue;
                xp.orderID = orderID;
                xp.state = state;
                notifyDataSetChanged();
                break;
            }
        }

        public void clear() {
            mList.clear();
            mHighlightProduct = null;
        }

		public void setHighlightProduct(XProduct product) {
            if (mHighlightProduct != product) {
                mHighlightProduct = product;
            }
        }

		public XProduct getHighlightProduct() {
            return mHighlightProduct;
        }

        @Override
        public int getCount() {
            return mList.size();
        }

        @Override
        public long getItemId(int i) {
            return i;
        }

        @Override
        public Object getItem(int i) {
            return mList.get(i);
        }

        private void setTextViewHighlight(TextView ... views) {
            int n = views.length;
            for (int i=0; i<n; i++) {
                views[i].setBackgroundColor(Color.parseColor("#c0c080"));
                views[i].setTextColor(Color.parseColor("#ffffff"));
            }
        }

        private void setTextViewNormal(TextView ... views) {
            int n = views.length;
            for (int i=0; i<n; i++) {
                views[i].setBackgroundColor(Color.parseColor("#70b040"));
                views[i].setTextColor(Color.parseColor("#000000"));
            }
        }

        @Override
        public View getView(int idx, View view, ViewGroup viewGroup) {
            ProductViewHolder vh;
            if (view == null) {
                view = mInflator.inflate(R.layout.product_list_item, null);
                vh = new ProductViewHolder();
                vh.name = (TextView)view.findViewById(R.id.product_list_item_name);
                vh.price = (TextView)view.findViewById(R.id.product_list_item_price);
                vh.value = (TextView)view.findViewById(R.id.product_list_item_value);
                vh.state = (TextView)view.findViewById(R.id.product_list_item_state);
                view.setTag(vh);
            } else {
                vh = (ProductViewHolder)view.getTag();
            }
            vh.productRef = mList.get(idx);
			vh.name.setText(vh.productRef.product.getProductName());
			vh.price.setText("" + vh.productRef.product.getPrice());
			vh.value.setText("" + vh.productRef.product.getCoins());
			vh.state.setText("" + vh.productRef.state);
            if (vh.productRef == mHighlightProduct) {
                setTextViewHighlight(vh.name, vh.price, vh.value, vh.state);
            } else {
                setTextViewNormal(vh.name, vh.price, vh.value, vh.state);
            }
            return view;
        }
    }

    // list adapter to manage orders
	private class OrderListAdapter extends BaseAdapter {

		private List<XOrder> mList;

		public OrderListAdapter() {
			super();
			mList = new ArrayList<XOrder>();
		}

		public boolean addOrder(XOrder order) {
			mList.add(order);
			return true;
		}

		public void clear() {
			mList.clear();
		}

		@Override
		public int getCount() {
			return mList.size();
		}

		@Override
		public long getItemId(int i) {
			return i;
		}

		@Override
		public Object getItem(int i) {
			return mList.get(i);
		}

		@Override
		public View getView(int idx, View view, ViewGroup viewGroup) {
			return null;
		}
	}

	private void enableButtons(Button ... btns) {
		for (Button btn : btns) {
			btn.setEnabled(true);
		}
	}

    private void disableButtons(Button ... btns) {
        for (Button btn : btns) {
            btn.setEnabled(false);
        }
    }

    private void setTextViewText(TextView tv, int resid) {
        tv.setText(resid);
        tv.setTextAlignment(TEXT_ALIGNMENT_CENTER);
    }

    private void setButtonText(Button btn, int resid) {
        btn.setTag(resid);
        btn.setText(resid);
    }

    /**
     * Initialize UI of parent class SGActivity. This method should be called before agent.onCreate is called.
     */
    private void initializeUI() {
        setContentView(R.layout.main_activity);

        ((TextView)findViewById(R.id.main_logs)).setMovementMethod(new ScrollingMovementMethod());
        // find product list view
        ListView v = (ListView)findViewById(R.id.main_product_list);
        // create a header bar
        LayoutInflater inflater = getLayoutInflater();
        ViewGroup header = (ViewGroup)inflater.inflate(R.layout.product_list_item, v, false);
        header.setBackgroundColor(Color.parseColor("#f0c080"));
        // set names of columns shown in header bar
        setTextViewText((TextView)header.findViewById(R.id.product_list_item_name), R.string.product_list_item_name_cap);
        setTextViewText((TextView)header.findViewById(R.id.product_list_item_price), R.string.product_list_item_price_cap);
        setTextViewText((TextView)header.findViewById(R.id.product_list_item_value), R.string.product_list_item_value_cap);
        setTextViewText((TextView)header.findViewById(R.id.product_list_item_state), R.string.product_list_item_state_cap);
        // add the header bar to the top of product list
        v.addHeaderView(header, null, false);
        // create list adapter
        mProductListAdapter = new ProductListAdapter();
        v.setAdapter(mProductListAdapter);
        // set up click handler
        v.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                // if agent is idle, highlight clicked product and update buttons according to its state
                int usrState = agent.getUsrState();
                int payState = agent.getPayState();
                int jobState = agent.getJobState();
                boolean idle = usrState == SGAgent.USRSTATE_LOGINEDGAME && jobState == SGAgent.JOBSTATE_IDLE && payState == SGAgent.PAYSTATE_IDLE;
                if (idle) {
                    ProductViewHolder vh = (ProductViewHolder) view.getTag();
                    if (vh.productRef != mProductListAdapter.getHighlightProduct()) {
                        // highlight product if not highlighted
                        mProductListAdapter.setHighlightProduct(vh.productRef);
                        // and show in highlighted color
                        mProductListAdapter.notifyDataSetChanged();
                        // and update buttons
                        updateButtons();
                    }
                }
            }
        });

        mBtnUsr = (Button)findViewById(R.id.main_btn_usr);
        mBtnJob = (Button)findViewById(R.id.main_btn_job);
        mBtnPay = (Button)findViewById(R.id.main_btn_pay);
        mBtnExt = (Button)findViewById(R.id.main_btn_exit);

        // disable job button and purchase button as user is not logined yet
        disableButtons(mBtnJob, mBtnPay);

        // initialize button captions and set up click handler
        setButtonText(mBtnUsr, R.string.btn_main_login);
        mBtnUsr.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int tag = (int)v.getTag();
                if (tag == R.string.btn_main_login) {
                    agent.login();
                } else if (tag == R.string.btn_main_logout) {
                    agent.logout();
                }
            }
        });
        setButtonText(mBtnJob, R.string.btn_main_products);
        mBtnJob.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int tag = (int)v.getTag();
                if (tag == R.string.btn_main_products)
                    agent.getProducts(null, true);
                else if (tag == R.string.btn_main_orders)
					agent.getOrderInfoList(null);
            }
        });
        setButtonText(mBtnPay, R.string.btn_main_buy);
        mBtnPay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int tag = (int)v.getTag();
                if (tag == R.string.btn_main_buy) {
					agent.buyProduct(mProductListAdapter.getHighlightProduct().product.getProductID(), 1, "coin");
                } else if (tag == R.string.btn_main_check) {
					agent.getOrderState(mProductListAdapter.getHighlightProduct().orderID);
                } else if (tag == R.string.btn_main_info) {
					dumpProductInfo();
                } else {
                    ;
                }
            }
        });
        mBtnExt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                agent.exit();
            }
        });
	}

	private void dumpProductInfo() {
		XProduct xp = mProductListAdapter.getHighlightProduct();
		onLog("productInfo = {\n");
		onLog("    productID: " + xp.product.getProductID(), ",\n");
		onLog("    productName: " + xp.product.getProductName(), ",\n");
		onLog("    productDesc: " + xp.product.getProductDesc(), ",\n");
		onLog("    roleID: " + xp.product.getRoleID(), ",\n");
		onLog("    roleName: " + xp.product.getRoleName(), ",\n");
		onLog("    roleLevel: " + xp.product.getRoleLevel(), ",\n");
		onLog("    serverID: " + xp.product.getServerID(), ",\n");
		onLog("    price: " + xp.product.getPrice(), ",\n");
		onLog("    coins: " + xp.product.getCoins(), ",\n");
		onLog("    appID: " + xp.product.getAppID(), ",\n");
		onLog("    appName: " + xp.product.getAppName(), ",\n");
		onLog("    orderID: " + xp.orderID, ",\n");
		onLog("    state: " + xp.state, "\n");
		onLog("}\n");
    }

    /**
     * Override onPreInitialization to do something before initialization (SGAgent.onCreate).
     */
    @Override
    public void onPreInitialization(Bundle savedInstanceState) {
        initializeUI();
    }

    /**
     * Override onPostInitialization to do more jobs than SGActivity.
     */
    @Override
    public void onPostInitialization(Bundle savedInstanceState) {
        // Set order state checking intervals to null to disable auto state query after payment UI
        // operation is completed. That is, you need to click purchase button (caption changed to
        // "Check" in this case) to query the order state manually.
        agent.setOrderStateCheckingIntervals(null);
        // You need to set instance of your own game server - which implements SGGameServerInterface.
	    // class YourGameServer implements SGGameServerInterface {
	    // }
        // agent.setGameServer(new YourGameServer(), params);
	    // A game server instance must be set otherwise the login process does not start.
	    agent.setGameServer(new SGGameServerDemo(), null);
    }

    // SGUtil may print visual logs via this method.
    @Override
    public void onLog(String text) {

        TextView logView = (TextView)findViewById(R.id.main_logs);
        if (logView != null) {
            if (!text.endsWith("\n")) {
                text += "\n";
            }
            logView.append(text);
            Layout layout = logView.getLayout();
            if (layout != null) {
                int n = logView.getLineCount();
                if (n > 0) {
                    int bottom = layout.getLineBottom(n - 1);
                    int top = logView.getScrollY();
                    int height = logView.getHeight();
                    int delta = (bottom - top) - height;
                    if (delta > 0)
                        logView.scrollBy(0, delta);
                }
            }
        }

        Log.d("SGUtil", text);
    }

    // SGUtil may print visual logs via this method.
    @Override
    public void onLog(String text, String postfix) {
	    onLog(text + (postfix != null ? postfix : ""));
    }

    @Override
    public boolean onProductBegin(int num) {
        // clear product list view
        mProductListAdapter.clear();
        // return true to let SGUtil notify us product information right now
        return true;
    }

    @Override
    public void onProductFound(UProduct product) {
        // add new product
		XProduct xp = new XProduct();
		xp.product = product;
		xp.orderID = null;
		xp.state = "buyable";
		mProductListAdapter.addProduct(xp);
    }

    @Override
    public void onProductEnd() {
        // all products notified, redraw product list view
        mProductListAdapter.notifyDataSetChanged();
    }

    @Override
    public boolean onOrderInfoBegin(int num) {
		// clear order list view
		mOrderListAdapter.clear();
		onLog("orderInfos = {\n");
        return true;
    }

    @Override
	public void onOrderInfoFound(UOrder order) {
		// add new order
		XOrder xo = new XOrder();
		xo.orderID = order.getOrderTicket().getOrderID();
		xo.productID = order.getProduct().getProductID();
		xo.productName = order.getProduct().getProductName();
		xo.buyNum = order.getPayParams().getBuyNum();
		xo.state = order.getState();
		long t = xo.state.equals("delivered") ? order.getDeliveredTime() :
				(xo.state.equals("succeeded") || xo.state.equals("failed") || xo.state.equals("cheated") ? order.getCompletedTime() :
				 order.getGeneratedTime());
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
		xo.time = sdf.format(new Date(t));
		onLog("(" + xo.orderID +
			  ", " + (TextUtils.isEmpty(xo.productName) ? xo.productID : xo.productName) +
			  ", " + xo.buyNum +
			  ", " + xo.state +
			  ", " + xo.time +
			  ")\n");
		mOrderListAdapter.addOrder(xo);
    }

    @Override
    public void onOrderInfoEnd() {
        // all orders notified
	    onLog("}\n");
    }

    @Override
    public void onTreasureListFound(String[] treasures) {
    	// got list of treasures
        for (int i=0; i<treasures.length; i++)
	        onLog(treasures[i]);
    }

    @Override
    public void onTreasureChange(String name, int count) {
	    // count of treasure 'name' updated
	    onLog("treasure: " + name + " " + count);
    }

    @Override
	public void onLogValue(String key, int value, String msg) {
    	// got the value of log item 'key'
	    onLog("log: " + key + " " + value + ", " + msg);
	}

	@Override
	public void onExtMethodNotification(String cookie, int code, String msg) {
    	// execution of certain extended command finished, result being notified
		onLog("cookie=" + cookie + ", code=" + code + ", msg=" + msg);
	}

    @Override
	public void onOrderStateChange(String productID, int num, String orderID, String state, String message) {
        // update product view when its state changes
		mProductListAdapter.changeProductState(productID, orderID, state);
    }

    @Override
    public void onStateChange(int type, int orgState, int newState, int reason, int op, String arg) {
        // A state changed. Update buttons
        updateButtons();
        // You may use passed parameters to judge what to do at present.
	    onLog("State change: " + agent.getStateName(type, orgState) + " -> " + agent.getStateName(type, newState) + " # " + agent.getStateChangeReasonName(reason) + "\n");
    }

    @Override
    public void onExitCancelled() {
        // recover screen and go on
    }

    @Override
    public void onFuncRequest(final String func, final String id, String arg) {
        if (func.equals("screenshot")) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    // do nothing but failure notification
                    agent.notifyFuncRequestResult(func, id, "failed");
                }
            });
        } else if (func.equals("logout")) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    // raise logout request (asynchronous)
                    agent.logout();
                    // success notification
                    agent.notifyFuncRequestResult(func, id, "succeeded");
                }
            });
        }
    }

}

Using SGUtil

1. Launch Android Studios, create a new project

The example has its application name being "HappyLearning", directory being "hl" and organization name being "cp.com ".


2. Choose a target device


3. Add MainActivity


4. Customize MainActivity

Please pick up appropriate features you need.


When this step finishes, Android Studios will show project screen. You will see 2 build.gradle files, with one for project "hl" and another one for module "app".

5. include sgutil.aar

First, copy the library file "sgutil.aar" you downloaded to directoy "libs" of module "app".

cp sgutil.aar ~/hl/app/libs

Second, modify "build.gradle" of module "app". As shown below, add dependency on sgutil.aar, and specify "libs" as local repository so that gradle knows where to find it.

dependencies {
    ...
    compile(name:"sgutil", ext:"aar")
}
repositories { flatDir { dirs 'libs' } }

6. Modify AndroidManifest.xml

As shown below, change the application name to "com.sg.util.SGApplication".

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.cp.happylearning">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:name="com.sg.util.SGApplication"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

Initialization

You need to create an instance of SGAgent in order to use SGUtil. We suggest you do this in onCreate method of MainActivity. You also need to set instance of your game server implementation after that, otherwise the whole login process does not start.

public class MainActivity extends AppCompatActivity implements SGAgent.SGClient {

    private SGAgent agent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        agent = new SGAgent();
        ...
        agent.setGameServer(new YourGameServerImplemenation());
        ...
    }

SGUtil Lifecycle Events

You should forward Android lifecycle callbacks in MainActivity to SGAgent. Especially, you initialize UI before calling onCreate because SGAgent may print logs to UI. You may implement appendLog functions as empty ones if you do not need this feature.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        agent = new SGAgent();
        agent.onCreate(this, this);
        agent.setGameServer(new YourGameServerImplementation());
    }

    protected void onStart() {
        super.onStart();
        agent.onStart();
    }

    protected void onRestart() {
        super.onRestart();
        agent.onRestart();
    }

    protected void onResume() {
        super.onResume();
        agent.onResume();
    }

    protected void onPause() {
        agent.onPause();
        super.onPause();
    }

    protected void onStop() {
        agent.onStop();
        super.onStop();
    }

    protected void onDestroy() {
        agent.onDestroy();
        super.onDestroy();
        System.exit(0);
    }

    public void onNewIntent(Intent newIntent) {
        agent.onNewIntent(newIntent);
        super.onNewIntent(newIntent);
    }

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        return agent.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
    }

    public void onBackPressed() {
        agent.onBackPressed();
    }

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        agent.onActivityResult(requestCode, resultCode, data);
        super.onActivityResult(requestCode, resultCode, data);
    }

    @TargetApi(23)
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        agent.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

SGUtil callback listening

Many features of SGAgent are provided as asynchronous operations. SGAgent will notify you the operation results via an interface you provided. That is to say, you must provide an instance of SGAgent.SGClient as the 2nd parameter of SGAgent.onCreate method. You should handle with the notified information within methods of the interface instance. The interface is defined as follows (see SGAgent.SGClient ).

    public interface SGClient {
        void appendLog(String text);
        void appendLog(String text, String postfix);
        boolean onProductBegin(int num);
        void onProductFound(UProduct product);
        void onProductEnd();
        void onOrderStateChange(String productID, String orderID, String state);
        boolean onOrderInfoBegin(int num);
        void onOrderInfoFound(UOrder order);
        void onOrderInfoEnd();
        void onTreasureListFound(String[] treasures);
        void onTreasureChange(String name, int count);
        void onLogValue(String key, int value);
        void onStateChange(int type, int orgState, int newState, int reason, int op, String arg);
        void onExtMethodNotification(String cookie, int code, String message);
        void onExitCancelled();
        void onFuncRequest(String func, String id, String arg);
    }

Repackaging

You can generate APK file with Android Studio after integrating with SGUtil library. You many also test your app on Android devices. However, the APK, has only a demo channel SDK. To bind a real channel SDK, please upload your APK to SG Studios website and download the final APK after repackaging is finished.