Wednesday, November 28, 2012

Disable Eamil Notification When Adding Colleague in SharePoint 2010

By default SharePoint 2010 an email notification will be sent out when a user has added the other user as a colleague. This may not be a good idea for a big company. Consider the case that thousands of employees may add the CEO as colleague so they could follow what CEO's news feed. That would be a huge spam for the CEO. Of course anyone can go to his/her mysite and change that email notification option:



IT forks have no problem to figure out all that, but most business people in a company are not technical guys, they are busy for other stuff, and have no time or no willing to explore tons of options available in SharePoint settings. Then you may get a request to disable the email notification for all employees.

The easiest way to do that is use Powershell script to update each user's profile. Create a script called "CreateMysites.ps1":
if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) {
    Add-PSSnapin "Microsoft.SharePoint.PowerShell"
}
#[Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server")

$site = Get-SPSite "http://mysites.company.com";
$ServerContext = Get-SPServiceContext $site;
$ProfileManager = new-object Microsoft.Office.Server.UserProfiles.UserProfileManager($ServerContext);
$ProfileEnumerators = $ProfileManager.GetEnumerator();

#Go through each user profile and update its setting
foreach ($profile in $ProfileEnumerators)
{
    try {
        $AccountName = $profile[[Microsoft.Office.Server.UserProfiles.PropertyConstants]::AccountName].Value
        if ($AccountName.ToLower().StartsWith("mydomain\")) 
        {
            #Three values map to three options shown in above screen-shot: 0 is ON and 1 is OFF
            $profile["SPS-EmailOptin"].Value = 010;
            $profile.Commit();
        }
    }
    catch [Exception] {
      write-host $_.Exception.Message;
    }
} 
$site.Dispose();
The script can be executed in SharePoint 2010 Management Shell directly. The powershell script can also be run by Windows Scheduled task. You just need to create a batch file "CreateMysites.bat" to start the powershell:
PowerShell.exe -command C:\Schedule\CreateMysites.ps1
To setup a schedule to run the script in Windows, go to SharePoint server => Administrative Tools => Task Scheduler, click Create Task action from the right-hand side panel, give a task name, set the schedule as daily, weekly or whatever you need in the Triggers tab, and then add a new Action in the Actions tab:

Sunday, November 18, 2012

Toggle Android Toast Message Manually

Android Toast shows a popup notification message for a short time then automatically fades out. Following code example will show a Toast when you touch the phone scree:

public class MainActivity extends Activity implements OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.mainLayout).setOnClickListener(this);
    }
    
    @Override
    public void onClick(View v) {
        Toast.makeText(this, "Toast Message", Toast.LENGTH_LONG).show();
    }
}

From the latest Toast implementation source code I noticed that the Toast message is controlled by INotificationManager:

    /**
     * Show the view for the specified duration.
     */
    public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }
We can see the message text is set to a TN object. TN is an inner class that actually shows or hides the Toast message:
     private static class TN extends ITransientNotification.Stub {
          TN() {
            // XXX This should be changed to use a Dialog, with a Theme.Toast
            // defined that sets up the layout params appropriately.
            final WindowManager.LayoutParams params = mParams;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
            params.format = PixelFormat.TRANSLUCENT;
            params.windowAnimations = com.android.internal.R.style.Animation_Toast;
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            params.setTitle("Toast");
        }

        /**
         * schedule handleShow into the right thread
         */
        public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.post(mShow);
        }

        /**
         * schedule handleHide into the right thread
         */
        public void hide() {
            if (localLOGV) Log.v(TAG, "HIDE: " + this);
            mHandler.post(mHide);
        }
Use reflection we can access the internal TN object, bypass the INotificationManager and show/hide the Toast message manually. Instead of fading out automatically, the result is a permanent Toast message on the screen.

Following code illustrates how to toggle Toast message manually where the Toast Message shows up when you touch the screen, and the message will stay on the screen unless you touch the screen again:

public class MainActivity extends Activity implements OnClickListener {
    private boolean isToastShowing = false;
    private Toast toast;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.mainLayout).setOnClickListener(this);
    }
    
    @Override
    public void onClick(View v) {
        //Toast.makeText(this, "Toast Message", Toast.LENGTH_LONG).show();
        if (toast == null) {
            toast = Toast.makeText(this, "Toast Message", Toast.LENGTH_LONG);
        }
        Field mTNField;
        try {
            mTNField = toast.getClass().getDeclaredField("mTN");
            mTNField.setAccessible(true);
            Object obj = mTNField.get(toast);
            Method method;
            if (isToastShowing) {
                method = obj.getClass().getDeclaredMethod("hide", null);
                isToastShowing = false;
            } else {
                method = obj.getClass().getDeclaredMethod("show", null);
                isToastShowing = true;
            }
            method.invoke(obj, null);
        } catch (Exception e) {
            e.printStackTrace();
        }        
    }
}

Monday, November 12, 2012

Windows 8 JavaScript Access Denied Runtime Error

An exception "0x80070005 - JavaScript runtime error: Access is denied." is thrown from Windows.UI.Popups.MessageDialog.showAsync() method. It turns out that another layer of the app has already shown a MessageDialog. Windows 8 can not support multiple dialogs at one time. It simple throws this "Access is denied" error. A workaround is to chain the dialogs in sequence:

messageDialog1.showAsync().then(function () { messageDialog2.showAsync()...});

Monday, November 05, 2012

Android Service Life Cycle

Unlike Activity Android Service doesn't have a UI for user interaction, it runs in background to perform long-running actions. A Service can be initiated from Activity, AlarmManager, BroacastReceiver, or other Service. In addition, a Service can run in the same process of caller (local Service) or run in different process (remote Service). Started Services are started by startService(), and bound Services are started by bindSerivce() method, as described in Android Developer Guides. The Service life-cycle has two branches for the started Service and the bound Service:

In this post I will do some testing on how Android Service's created by Activities, and examine its clife-cycle. For simplicity the examples are the Local Service, and there's no logic to stop the Service specifically so we can see how a Service instance survives when the Activity that created it is killed. The tests are conducted in Geingerbread 2.3 and latest Jelly Bean 4.1 and they show the exact same results.

Test Service and UI

First we define a dummy TestService for our test where each callback invocation is logged:

public class TestService extends Service {
    private static final String TAG = TestService.class.getSimpleName();
    private final IBinder binder = new ServiceBinder();
    
    @Override
    public IBinder onBind(Intent arg0) {
        Log.d(TAG, "onBind()");
        return binder;
    }
    
    @Override
    public void onRebind(Intent intent) {
        Log.d(TAG, "onRebind()");
        super.onRebind(intent);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "onUnbind()");
        return super.onUnbind(intent);
    }
    
    public class ServiceBinder extends Binder {
        private final String TAGB = ServiceBinder.class.getSimpleName();
        public TestService getService() {
            Log.d(TAGB, "getService()");
            return TestService.this;
        }
    }

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate()");
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy()");
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand()");
        return super.onStartCommand(intent, flags, startId);
    }
}

We also have two Activities to do the test. The main Activity has two buttons that could start a TestService or navigate to SecondActivity. The SecondActivity only has one button to start the TestService.

Activity UI:

Activity XML definition:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:id="@+id/mainLayout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
 
    <Button
        android:id="@+id/btnGoActivty"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Go to SecondActivity" 
        android:onClick="startSecondActivity" />  
     
    <Button
        android:id="@+id/btnStartService"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start TestService"
        android:onClick="startTestService" /> 
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <Button
        android:id="@+id/btnStartService"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start TestService" /> 
        
</LinearLayout>

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidtest"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="11" />

    <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" 
        android:theme="@style/AppTheme" >
        <activity android:name=".MainActivity" android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".SecondActivity"></activity>
        <service android:name=".TestService" ></service>
    </application>

</manifest>

Started Service

MainActivity code:

public class MainActivity extends Activity {
    private static final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.d(TAG, "onCreate()");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onDestroy() {
        Log.d(TAG, "onDestroy()");
        super.onDestroy();
    }

    public void startSecondActivity(View v) {
        Log.d(TAG, "starting ServiceActivity");
        Intent activtyIntent = new Intent(MainActivity.this, SecondActivity.class);
        startActivity(activtyIntent);
    }

    public void startTestService(View v) {
        Log.d(TAG, "starting TestService");
        Intent serviceIntent = new Intent(MainActivity.this, TestService.class);
        startService(serviceIntent);
    }
}
SecondActivity:
public class SecondActivity extends Activity {
    private static final String TAG = SecondActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.d(TAG, "onCreate");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        Button btnStartService = (Button) findViewById(R.id.btnStartService);
        btnStartService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "starting TestService");
                Intent serviceIntent = new Intent(SecondActivity.this, TestService.class);
                startService(serviceIntent);
            }
        });
    }

    @Override
    protected void onDestroy() {
        Log.d(TAG, "onDestroy()");
        super.onDestroy();
    }
}

On the MainActivity I click "Go to SecondActivity" button, then click "Start TestService" to start the dummy service. The LogCat shows:

The TestService.onCreate() and TestService.onStartCommand() call backs are invoked when the service is started. Next click Android Back button to navigate back to MainActivity:

We can see the TestService instance remains when the SecondActivity is destroyed which initiated the Service. This is because the Service is running in background and is not tied to Activity's life-cycle.

Inside MainActivity click "Start TestService" button, the TestServcie.onStartCommand() is called but not the onCreate() callback:

If we click the Back button again the MainActivity is destroyed and the Android Home page shows up, as there's no other Activity instance in the Task stack:

Apparently the TestService is still running even though the MainActivity is gone. Unless the hosting process is killed the Service will remain running in background forever. The only exception is that Android runtime may terminate the service in low resources situations. Such runtime termination can happen in background Activities also.

By default the local Service is running in the same UI thread, and Service and Activity can communicate each other directly. A new thread is required to run the heavy long-running job inside Service to avoid blocking the main thread. Once the Service finishes its work, it can send the result back to Activity through main thread's handler. If Service is running in a different process, Broadcast mechanism could be used to do the cross-process communication.

To stop a started Service just call the stopService() method. It will stop the Service if it's running, otherwise nothing happens. Note that the number of calls to startService() is not counted, and stopService() will simply stop the Service no matter how many times the Service has been started.

Bound Service

Bound services are started by the bindService() method call. Activity or other component wishing to bind to a Service needs to have a ServiceConnection object containing onServiceConnected() and onServiceDisconnected() methods which will be called once the Service connection has been established or disconnected respectively:

    private TestService testService;
    ServiceConnection mConnection = new ServiceConnection() {  
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "Service disconnected");
            testService = null;
        }
          
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "Service connected");
            testService = ((ServiceBinder)service).getService();
            // TestService methods are reachable via testService instance
        }
    };

With the ServiceConnection created, MainActivity and SecondActivity can bind the TestService directly:

    public void startTestService(View v) {
        Log.d(TAG, "starting TestService");
        Intent serviceIntent = new Intent(MainActivity.this, TestService.class);
        bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
    }

Then we redo the experiment again: MainActivity => SecondActivity => Start Service (bindService) => Start Service (binService) => Back to MainActivity => Start Service (bindService) => click Back button to destroy MainActivity. The LogCat records:

We can see that the TestService.onCreate() is called, then the onBind() callback when TestService is bound, but subsequent bind requests do not trigger TestService.onBind(). Another significant difference comparing with started Service is that the TestService is terminated, and TestService.unbind() and TestService.onDestroy() are invoked when the bound Activity object is destroyed.

We change the action a little bit: MainActivity => Start Service (bindService) => SecondActivity => Start Service (bindService) => Start Service (bindService) => Back to MainActivity => Start Service (bindService) => Back button to destroy MainActivity:

This time the TestService is not stopped when SecondActivity is destroyed because the TestService object still has bound reference in MainActivity. Once the MainActivity is destroyed the TestService is also terminated as there's no bound reference for TestService anymore. However, if Service is already started using startService() before the bindService() call, then the Service will remain when all Activity instances are destroyed as we discussed above in started Service section. We change the MainActivity code to use startService and the result becomes:

Once the Service is bound, an IBinder object is returned to the requester, so the Activity would be able to access the Service data directly in local Service secnaros. In remote Service situations where the bound service is not running in the same process as the Activity, the recommended interaction would be the Messenger and Handler implementation.

To stop bound Services, we can invoke the unbindService() method call. As stopService(), the bound Service will be stopped on request of unbindService() invocation if started, no matter how many times bindService() are called, otherwise nothing will happen.

IntentService

IntentService is a special implementation of started Service. Internally IntentService implements a HandlerThread to process the long-running task. A simple IntentService test code:

public class TestIntentService extends IntentService {
    private static final String TAG = TestIntentService.class.getSimpleName();
    
    public TestIntentService() {
        super(TAG);
        Log.d(TAG, "Constructor()");
    }

    @Override
    protected void onHandleIntent(Intent arg0) {
        Log.d(TAG, "onHandleIntent() start...");
        try {
            Thread.sleep(5000); // pause 5 seconds
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.d(TAG, "onHandleIntent() completed");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate()");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy()");
    }
}

We change the Service initialization code in MainActivity and SecondActivity:

    public void startTestService(View v) {
        Log.d(TAG, "starting TestIntentService");
        Intent serviceIntent = new Intent(MainActivity.this, TestIntentService.class);
        startService(serviceIntent);
    }

Run the application with following actions: MainActivity => Go to SecondActivity => Start TestIntentService (wait for more than 5 seconds) => Start TestIntentService (less than 5 seconds) => Back to MainActivity => Back button to destroy MainActivity. The logs:

Unlike started Service IntentService terminates when completes its work. Also the task in IntentService.onHandleIntent() remains running even the Activity started it is gone. This is because the task inside onHandleIntent() callback is running in a separate thread.

If we click "Start TestIntentService" a few times quickly in MainActivity:

It clearly shows that the tasks are processed in sequence, not running in parallel. This is because the tasks are pushed to IntentService's message queue, and its internal handler will execute the task one by one.

Conclusion.

  • The started Service is not bound to Activity life-cycle except the above case. Service could run in background and consuming resources even all Activity instances are closed
  • Service.onStartCommand() is guaranteed to be called for each startService() call, and Service.onCreate() is only called once for the first time the Service object is created.
  • Local bound Service could be related to Activity's life-cycle if it's not started when the bindService() is invoked, i.e. the bound Service instance will be destroyed when the Activity instance is gone.
  • Local bound Service will not be tied to Activity's life-cycle if the Service has started before the bindService() is invoked, i.e. the Service will continue to run in this case.
  • Both Service.onCreate() and Service.onBind() are invoked once for the first bindService() call, all subsequent bindService() calls won't result in any Service callback.
  • IntentService.onHandleIntent() runs in a separate thread, and processes tasks one by one in sequence. Once all tasks are completed the IntentService instance is destroyed. It's not tied to Activity life-cycle either.